扫码链接5000+新基建产业链上下游从业者,入群请备注“机智地+姓名+公司+岗位”
在开始文章阅读时,有些交易名词需要先了解一下:
· 头寸:指的是银行当前所有可以运用的资金的总和。
· candle:与条形图一样,该图表显示高低价以及开盘价和收盘价,蜡烛的形状反映了这些价格之间的关系。
· RSI:其可识别走势将要达到当前方向尽头的时间以及超卖和超买市场条件。
既要开始使用算法交易又要获利,趋势跟踪通常是最容易的。在上一篇文章中,我写了一个趋势跟踪策略,甚至通过使用更长的时间框架来确定趋势来提高其性能。但这是一次简单的一次进入和一次退出的策略。
本教程的重点是让您开始使用Jesse的一些功能,这些功能有助于您编写大多数趋势跟踪策略。这些功能中很少有:
1. 在两个点退出交易,一个是固定价格,另一个是动态价格。
2. 将停止损失更新为退出一半位置后盈亏平衡。
3. 过滤盈亏比不好的交易。
4. 使用事件挂钩交易的生命周期。
首先我使用make strategy命令创建一个新策略:
jesse make-strategy TrendFollowingStrategy
然后我编辑routes.py文件以使用此策略,并将时间范围设置为4h:
# trading routes
routes = [
(‘Bitfinex’, ‘BTCUSD’, ‘4h’, ‘TrendFollowingStrategy’),
]
# in case your strategy required extra candles, timeframes, …
extra_candles = []
然后打开新创建的策略文件,如下所示:
from jesse.strategies import Strategy
import jesse.indicators as ta
from jesse import utils
class TrendFollowingStrategy(Strategy):
def should_long(self) -> bool:
return False
def should_short(self) -> bool:
return False
def should_cancel(self) -> bool:
return False
def go_long(self):
pass
def go_short(self):
pass
进入规则
我想在以下情况走多长时间:
1. 我们处于上升趋势(空头交易反之亦然)
2. 收盘价(当前价)触及50 EMA线
def should_long(self) -> bool:
return self.current_candle_touches_long_ema and self.trend == 1
def should_short(self) -> bool:
return self.current_candle_touches_long_ema and self.trend == -1
他们说一张图片值1000个字说明,所以这里有一张图片显示了在这种情况下我认为是上升趋势:
这是我所说的,当前candle碰到50 EMA(橙色线是50 EMA):
现在让我们为current_candle_touch_long_ema和trend编写代码:
@property
def trend(self):
short_ema = ta.ema(self.candles, 50)
long_ema = ta.ema(self.candles, 100)
longer_ema = ta.ema(self.candles, 200)
if short_ema > long_ema > longer_ema:
return 1
elif short_ema < long_ema < longer_ema:
return -1
else:
return 0
@property
def current_candle_touches_long_ema(self):
long_ema = ta.ema(self.candles, 50)
return self.high >= long_ema >= self.low
我使用了3条EMA线来确定趋势方向。我返回1代表上升趋势,返回-1代表下降趋势。current_candle_touches_long_ema非常简单,我只需要确保当前candle的高价大于long_ema线(周期为50的EMA),并且当前candle的低价低于long_ema线。
设定入场价格和头寸规模
进入价格将是目前长期交易的最高点。止损价格将是当前ATR的3倍,而不是我的进场价格。
在这种策略中,我想一次全部进入交易,但要在两个点退出。我将在趋势的前一个高点退出头寸。这就是我所说的上升趋势的最高点(蓝线是我的目标):
要对此进行编码,我首先选择最近20条candle的高价。然后简单地返回它们的最大值。
对于头寸规模,我希望每次交易的风险为总资本的5%。要计算qty,我使用risk_to_qty实用程序。
当然,做空交易则相反。这是代码:
def go_long(self):
entry = self.high
stop = entry – ta.atr(self.candles)*3
qty = utils.risk_to_qty(self.capital, 5, entry, stop)
# highest price of the last 20 bars
last_20_highs = self.candles[-20:, 3]
previous_high = np.max(last_20_highs)
self.buy = qty, entry
self.stop_loss = qty, stop
self.take_profit = qty/2, previous_high
def go_short(self):
entry = self.low
stop = entry + ta.atr(self.candles) * 3
qty = utils.risk_to_qty(self.capital, 5, entry, stop)
# lowest price of the last 20 bars
last_20_lows = self.candles[-20:, 4]
previous_low = np.min(last_20_lows)
self.sell = qty, entry
self.stop_loss = qty, stop
self.take_profit = qty / 2, previous_low
使止损达到收支平衡
如您所见,我仅以获利价格退出一半的头寸规模。换句话说,在降低头寸之后,我想将止损价调整为收支平衡。要在Jesse中为其编写代码,我将使用内置的on_reduced_position方法。
我只需要更新self.stop_loss即可告诉jesse更新我的止损订单。Jesse会自动选择它,取消上一个停止订单,然后提交新的停止订单。再简单不过了!
def on_reduced_position(self):
self.stop_loss = self.position.qty, self.position.entry_price
动态退出交易的后半部分
对于这种策略,我打算在动态情况下退出交易我的剩余的头寸。这种情况背后的想法是,在价格高度超买并即将进行严重调整时退出。用quant的语言来说,我想在RSI指标达到80以上时退出。
首先我将使用内置的update_position()方法写入我的逻辑。只有在我们有未平仓合约时,此方法才会在每次添加新candle后执行。因此它用于更新位置。这意味着我们不需要检查仓位是否打开。
在此要考虑的下一件事情是,我只想退出头寸的剩余半部分。换句话说,如果未结头寸已减少,我想将其平仓。检查我的位置是否已降低的最简单方法是使用内置的is_reduced属性和liquidate()方法。
def update_position(self):
# the RSI logic is intended for the second half of the trade
if self.is_reduced:
rsi = ta.rsi(self.candles)
if self.is_long and rsi > 80:
# self.liquidate() closes the position with a market order
self.liquidate()
elif self.is_short and rsi < 20:
self.liquidate()
使用过滤器(filter)
到目前为止,我的策略看起来不错,让我们进行一次回溯测试,看看其进展如何:
jesse backtest 2019-01-01 2020-05-01
经过约4%的回测后,我得到一个错误:
Uncaught Exception: InvalidStrategy: take-profit(3601.6) must be below entry-price(3601.6) in a short position
这个错误试图告诉我们,在某种程度上,我们策略的止盈和入场价相等($ 3601.6),这是不可接受的。这是一个棘手的调试问题,但是您在编写了最初的几条策略后就会熟练使用Jesse进行调试。
def go_long(self):
# entry: the high of the current candle
entry = self.high
stop = entry – ta.atr(self.candles)*3
qty = utils.risk_to_qty(self.capital, 5, entry, stop)
last_20_highs = self.candles[-20:, 3]
previous_high = np.max(last_20_highs)
self.buy = qty, entry
self.stop_loss = qty, stop
# (first) take-profit: the high of the last 20 candles
self.take_profit = qty/2, previous_high
这个错误告诉我们,在某些情况下,入场和获利在某些时候是相同的。也就是说,在那一点上,当前蜡烛的高位是最近20个candle中的最高点。我们考虑的不是这种策略的交易类型。
我们可以通过在should_long方法中使用一些肮脏的if-else语句或使用针对此类情况设计的过滤器(filter)来防止这种情况的发生。
过滤器(filter)只是一个返回布尔值的函数。通过返回True值来传递过滤器(filter),反之亦然。我定义一个过滤器并将其命名为reward_to_risk_filter。名称可以是任何东西,但是通常最好的做法是使用单词filter来开始或结束过滤方法的名称。该筛选器(filter)的工作是确保我们尝试输入的交易值得。
def reward_to_risk_filter(self):
profit = abs(self.average_entry_price – self.average_take_profit)
loss = abs(self.average_entry_price – self.average_stop_loss)
win_loss_ratio = profit / loss
return win_loss_ratio > 1
目前Jesse仍然不知道reward_to_risk_filter()是一个过滤器(filter)。为了使其能够识别我的过滤器(filter),我需要将其添加到filters()方法中,该方法是一个返回Python列表的内置方法:
def filters(self):
return [
]
现在,我将在返回列表中添加reward_to_risk_filter作为变量。这意味着它的结尾不能有括号:
def filters(self):
return [
self.reward_to_risk_filter,
]
现在让我们再执行一次回测:
jesse backtest 2019-01-01 2020-05-01
这次一切进展顺利。
结 论
保持策略越简单,随着时间的推移调试策略甚至改进策略就越容易。
使用Jesse编写策略就像手动交易策略一样简单。因此下次您找到交易手册或交易专家介绍的策略时,只需为其编写代码,然后对其进行回测。
声明: 本文由入驻基智地平台的作者撰写,观点仅代表作者本人,不代表基智地立场;基智地发布此信息的目的在于传播更多信息,与本站立场无关。