Python量化交易学习笔记(32)——backtrader的StopTrailLimit订单

本文将继续对backtrader的order进行介绍,具体介绍StopTrailLimit订单的使用。

在backtrader的官方文档中,对StopTrailLimit的介绍一笔带过,只是提到StopTrailLimit和StopTrail订单区别仅是订单被触发后的表现不同,但实际上通过分析源代码可以发现StopTrailLimit订单与StopTrail订单区别还是比较大的。

其中,是否指定参数中的price值,StopTrailLimit会产生大不相同的两种订单执行逻辑。目前还不知道这是backtrader有意为之,还是实现出现了问题。下面分别就指定参数price值及不指定参数price值分开讨论(这里假定限制价格plimit已指定,不指定plimit又是另一个故事了,考虑到我们是分析StopTrailLimit订单,因此假定plimit已指定)。

在之前笔记中分析过,Limit类型的订单不适合做平仓操作,因为可能造成长时间无法平仓,而造成重大的损失。因此,以下示例均使用buy方法进行建仓来演示StopTrailLimit订单的执行逻辑

指定参数price值

1. 订单逻辑

  • 当指定参数price值时,订单的执行逻辑与StopLimit订单相似。二者的区别在于,在StopLimit订单中,触发价格stopprice和限制价格limitprice均为固定值,而在StopTrailLimit订单中触发价格stopprice和限制价格limitprice都会随着收盘价的变化进行变化。

  • 在StopTrailLimit订单中,在触发价格与限制价格更新的同时,会检测新的K线是否满足当前触发价格与限制价格条件下StopLimit订单的执行逻辑,若已满足则按照StopLimit订单的规则执行订单,即达到触发价格后,再按照StopLimit订单的逻辑执行订单。

2. 初始化

  • 在初始化阶段,使用指定的price值及跟踪比例trailpercent来计算触发价格,即stopprice = price * (1 + trailpercent)

  • 然后使用指定的price值及plimit值计算一个偏差值limitoffset = price - plimit

  • 最后使用limitoffset及触发价格stopprice来更新限制价格limitprice = stopprice -
    limitoffset。后续在更新触发价格stopprice及限制价格limitprice时,始终保持二者存在这一固定的差值limitoffset。

3. 价格更新与订单执行

  • 按照Stop订单规则判断订单已被触发,则按照StopLimit订单规则执行订单

  • 按照Stop订单规则判断订单未被触发

    • 如果收盘价高于(或等于)StopTrailLimit买单创建后的最低收盘价及初始指定的price值,则不更新触发价格stopprice及限制价格limitprcie

    • 如果收盘价低于StopTrailLimit买单创建后的最低收盘价或初始指定的price值,则更新触发价格stopprice及限制价格limitprcie。触发价格stopprice = price * (1 + trailpercent),其中price为所在K线的收盘价,trailpercent为给定的跟踪比例。限制价格limitprice = stopprice - limitoffset,limitprice为之前计算的触发价格及限制价格之间的固定差值。

4. 示例

策略:

  • 买入条件:收盘价高于15日均线后,创建StopTrailLimit买单,当买单创建后,价格较最低收盘价向上突破3%,并回撤买单创建当日收盘价的2%时买入
  • 卖出条件:收盘价低于15日均线卖出
        if self.order:
						...
            elif self.order.isbuy() and self.p.exectype in ['StopTrailLimit']:
                if self.p.trailamount:
                    check = self.data.close + self.p.trailamount
                else:
                    check = self.data.close * (1.0 + self.p.trailpercent)
                self.log('Open: %.2f, High: %.2f, Low: %.2f, Close: %.2f, Stop Price: %.2f, Check Price: %.2f, Limit Price: %.2f' %
                    (self.data.open[0],
                    self.data.high[0],
                    self.data.low[0], 
                    self.data.close[0],
                    self.order.created.price,
                    check,
                    self.order.created.pricelimit
                    ))

        # 检查是否持仓
        if self.position:
            # 检查是否达到卖出条件
            if self.buysell < 0 and self.p.exectype not in ['StopTrail']:
								...
                if self.p.exectype in ['Market', 'StopTrailLimit']:
                    self.sell(exectype = bt.Order.Market)
                    self.log('SELL CREATE, exectype Market, close %.2f' % 
                             self.data.close[0])
               ...
        # 不在场内且出现买入信号
        elif self.buysell > 0:
						...
            elif 'StopTrailLimit' == self.p.exectype:
                    price = self.data.close[0]
                    plimit = self.data.close[0] * (1.0 - self.p.traillimit)
                    st_order = self.buy(exectype=bt.Order.StopTrailLimit,
                                   trailamount=self.p.trailamount,
                                   trailpercent=self.p.trailpercent,
                                   price = price,
                                   plimit = plimit)
                    if self.p.trailamount:
                        check = self.data.close + self.p.trailamount
                    else:
                        check = self.data.close * (1.0 + self.p.trailpercent)
                        #check = plimit * (1.0 + self.p.trailpercent)
                    txt = 'BUY CREATE, exectype StopTrailLimit, close %.2f, stop price %.2f, check price %.2f, limit price %.2f'
                    self.log(txt % (self.data.close[0], st_order.created.price, check, st_order.created.pricelimit))

将图表显示时间范围调整为2019年4、5月,输出图形如下:
在这里插入图片描述

上图中,红色的曲线表示15日均线。

部分输出结果为:

2019-04-29, BUY CREATE, exectype StopTrailLimit, close 14.10, stop price 14.52, check price 14.52, limit price 14.24
2019-04-30, ORDER SUBMITTED
2019-04-30, ORDER ACCEPTED
2019-04-30, Open: 13.99, High: 14.05, Low: 13.59, Close: 13.85, Stop Price: 14.27, Check Price: 14.27, Limit Price: 13.98
2019-05-06, Open: 13.10, High: 13.35, Low: 12.71, Close: 12.87, Stop Price: 13.26, Check Price: 13.26, Limit Price: 12.97
2019-05-07, Open: 13.03, High: 13.09, Low: 12.72, Close: 12.95, Stop Price: 13.26, Check Price: 13.34, Limit Price: 12.97
2019-05-08, Open: 12.72, High: 12.91, Low: 12.50, Close: 12.60, Stop Price: 12.98, Check Price: 12.98, Limit Price: 12.70
2019-05-09, Open: 12.52, High: 12.58, Low: 12.05, Close: 12.16, Stop Price: 12.52, Check Price: 12.52, Limit Price: 12.24
2019-05-10, Open: 12.34, High: 12.75, Low: 12.10, Close: 12.68, Stop Price: 12.52, Check Price: 13.06, Limit Price: 12.24
2019-05-13, Open: 12.33, High: 12.54, Low: 12.23, Close: 12.30
2019-05-13, BUY EXECUTED, Price: 12.24, Cost: 1224.28.

下面结合输出打印结果及图形进行分析。

(1) 4月29日,收盘价高于15日均线,达到了买入条件,创建StopTrailLimit买单。当日收盘价close(也是设定的price值)为14.10,向上突破3%后的触发价格stop price为14.52,较收盘价回撤2%指定的限制价格plimit为13.82,记录price值(收盘价)与plimit间的偏差值limitoffset = price - plimit = 0.28,使用偏差值更新限制价格limitprice = stopprice - limitoffset = 14.24。

(2) 4月30日,收到买单提交通知。(29日创建的订单,在30日收到订单状态通知,是因为notify_order方法会在Strategy的next方法前被调用,即在29日的next方法中创建了买单,在30日的notify_order方法中通知订单被提交、接受。)

(3) 4月30日,收到买单接受通知。

(4) 4月30日,最低价13.59,最高价14.05,未达到触发价格stopprice值14.52。收盘价13.85,低于买单创建后最低收盘价14.10,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价13.85,trailpercent在程序中设定为0.03,计算得到新的触发价格stopprice为14.27。需要同步更新限制价格limitprice = stopprice - limitoffset = 13.98。(按照小数点后2位计算的结果应该为13.99,但实际计算是精确到小数点后三位,我们这里打印小数点后2位)

(5) 5月6日(5月1日至5日为假期,未开市),最低价12.71,最高价13.35,未达到触发价格stopprice值14.27。收盘价12.87,低于买单创建后最低收盘价13.85,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价12.87,trailpercent在程序中设定为0.03,计算得到新的触发价格stopprice为13.26。需要同步更新限制价格limitprice = stopprice - limitoffset = 12.97。(按照小数点后2位计算的结果应该为12.98,但实际计算是精确到小数点后三位,我们这里打印小数点后2位)

(6) 5月7日,最低价12.72,最高价13.09,未达到触发价格stop price值13.26。收盘价12.95,未低于买单创建后最低收盘价12.87,因此不更新触发价格13.26和限制价格12.97。

(7) 5月8日,最低价12.50,最高价12.91,未达到触发价格stopprice值13.26。收盘价12.60,低于买单创建后最低收盘价12.87,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价12.60,trailpercent在程序中设定为0.03,计算得到新的触发价格stop price为12.98。需要同步更新限制价格limitprice = stopprice - limitoffset = 12.70

(8) 5月9日,最低价12.05,最高价12.58,未达到触发价格stopprice值12.98。收盘价12.16,低于买单创建后最低收盘价12.60,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价12.16,trailpercent在程序中设定为0.03,计算得到新的触发价格stop price为12.52。需要同步更新限制价格limitprice = stopprice - limitoffset = 12.24

(9) 5月10日,最低价12.10,最高价12.75,达到触发价格stopprice值12.52,按照StopLimit订单规则,订单被触发,订单将以限制价格limitprice12.24按照Limit订单规则被执行。(无法判断5月10日的K线价格在达到触发价格后,能否回撤到限制价格,因此将在下一日K线上判断订单是否成交。)

(10) 5月13日(11日和12日为周末,未开市),开盘价为12.33,高于限制价格limitprice值12.24,不满足Limit订单价格匹配的规则1;但是最低价为12.23,低于限制价格limitprice值12.24,满足Limit订单价格匹配的规则2,因此将以limitprice值成交。

(11) 5月13日,买单以limitprice值12.24成交。

通过这个例子可以发现,使用StopTrailLimit订单进行买入,避免了假突破(突破15日均线)买入的情况发生。

不指定参数price值

1. 订单逻辑

当不指定参数price值时,订单的执行逻辑与指定参数price值相似。二者的区别在于:

  • 若不指定price值,StopTrailLimit订单将以指定的plimit值初始化price值。
  • 若不指定price值,在触发价格stopprice和限制价格limitprice的更新过程中,二者始终保持相等。

2. 初始化

  • 在初始化阶段,将指定的plimit值初始化为price值,即price = plimit
  • 使用price值计算触发价格stopprice = price * (1 + trailpercent)
  • 设定限制价格与触发价格相等,即limitprice = stopprice (在bt源代码中,实际上与指定price值的情况相同,也使用了偏差值limitoffset来计算limitprice = stopprice - limitoffset,不过在不指定price值的情况下,limitoffset = 0,因此触发价格stopprice与限制价格limitprice始终相等。)

3. 价格更新与订单执行

  • 按照Stop订单规则判断订单已被触发,则按照StopLimit订单规则执行订单

  • 按照Stop订单规则判断订单未被触发

    • 如果收盘价高于(或等于)StopTrailLimit买单创建后的最低收盘价及初始指定的price值,则不更新触发价格stopprice及限制价格limitprcie

    • 如果收盘价低于StopTrailLimit买单创建后的最低收盘价或初始指定的price值,则更新触发价格stopprice及限制价格limitprcie。触发价格stopprice = price * (1 + trailpercent),其中price为所在K线的收盘价,trailpercent为给定的跟踪比例。限制价格limitprice = stopprice (limitoffset = 0)

4. 示例

策略:

  • 买入条件:收盘价高于15日均线后,创建StopTrailLimit买单,当买单创建后,价格较最低收盘价(及指定价格中的较小者)向上突破3%时买入

  • 卖出条件:收盘价低于15日均线卖出

						...
            elif 'StopTrailLimit' == self.p.exectype:
                    price = self.data.close[0]
                    plimit = self.data.close[0] * (1.0 - self.p.traillimit)
                    st_order = self.buy(exectype=bt.Order.StopTrailLimit,
                                   trailamount=self.p.trailamount,
                                   trailpercent=self.p.trailpercent,
                                   #price = price,
                                   plimit = plimit)
                    if self.p.trailamount:
                        check = self.data.close + self.p.trailamount
                    else:
                        # 指定price值
                        #check = self.data.close * (1.0 + self.p.trailpercent)
                        # 不指定price值
                        check = plimit * (1.0 + self.p.trailpercent)
                    txt = 'BUY CREATE, exectype StopTrailLimit, close %.2f, stop price %.2f, check price %.2f, limit price %.2f'
                    self.log(txt % (self.data.close[0], st_order.created.price, check, st_order.created.pricelimit))
...

实现代码与指定参数price值的区别是,在调用buy方法时,注释掉了对price参数的赋值。

为了便于对比说明,示例依然以4月29日创建的买单为例。将图表显示时间范围调整为2019年4、5月,输出图形如下:
在这里插入图片描述

对应的部分输出结果为:

2019-04-29, BUY CREATE, exectype StopTrailLimit, close 14.10, stop price 14.23, check price 14.23, limit price 14.23
2019-04-30, ORDER SUBMITTED
2019-04-30, ORDER ACCEPTED
2019-04-30, Open: 13.99, High: 14.05, Low: 13.59, Close: 13.85, Stop Price: 14.23, Check Price: 14.27, Limit Price: 14.23
2019-05-06, Open: 13.10, High: 13.35, Low: 12.71, Close: 12.87, Stop Price: 13.26, Check Price: 13.26, Limit Price: 13.26
2019-05-07, Open: 13.03, High: 13.09, Low: 12.72, Close: 12.95, Stop Price: 13.26, Check Price: 13.34, Limit Price: 13.26
2019-05-08, Open: 12.72, High: 12.91, Low: 12.50, Close: 12.60, Stop Price: 12.98, Check Price: 12.98, Limit Price: 12.98
2019-05-09, Open: 12.52, High: 12.58, Low: 12.05, Close: 12.16, Stop Price: 12.52, Check Price: 12.52, Limit Price: 12.52
2019-05-10, Open: 12.34, High: 12.75, Low: 12.10, Close: 12.68
2019-05-10, BUY EXECUTED, Price: 12.52, Cost: 1252.48.

下面结合输出打印结果及图形进行分析。

(1) 4月29日,收盘价高于15日均线,达到了买入条件,创建StopTrailLimit买单。当日收盘价close为14.10,不指定price的值,将以指定的plimit值初始化为price值,即price = plimit,使用price值计算触发价格stopprice = price * (1 + trailpercent) = 14.23,设定限制价格与触发价格相等,即limitprice = stopprice = 14.23。(在bt源代码中,实际上与指定price值的情况相同,也使用了偏差值limitoffset来计算limitprice = stopprice - limitoffset,不过在不指定price值的情况下,limitoffset = 0,因此触发价格stopprice与限制价格limitprice始终相等。)

(2) 4月30日,收到买单提交通知。(29日创建的订单,在30日收到订单状态通知,是因为notify_order方法会在Strategy的next方法前被调用,即在29日的next方法中创建了买单,在30日的notify_order方法中通知订单被提交、接受。)

(3) 4月30日,收到买单接受通知。

(4) 4月30日,最低价13.59,最高价14.05,未达到触发价格stopprice值14.23。收盘价13.85,未低于买单创建后初始化price值13.82,因此不更新触发价格14.23和限制价格14.23。

(5) 5月6日(5月1日至5日为假期,未开市),最低价12.71,最高价13.35,未达到触发价格stopprice值14.23。收盘价12.87,低于买单创建后初始化price值13.82,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价12.87,trailpercent在程序中设定为0.03,计算得到新的触发价格stopprice为13.26。需要同步更新限制价格limitprice = stopprice - limitoffset = 13.26(limitoffset = 0)。

(6) 5月7日,最低价12.72,最高价13.09,未达到触发价格stop price值13.26。收盘价12.95,未低于买单创建后最低收盘价12.87,因此不更新触发价格13.26和限制价格13.26。

(7) 5月8日,最低价12.50,最高价12.91,未达到触发价格stopprice值13.26。收盘价12.60,低于买单创建后最低收盘价12.87,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价12.60,trailpercent在程序中设定为0.03,计算得到新的触发价格stop price为12.98。需要同步更新限制价格limitprice = stopprice - limitoffset = 12.98

(8) 5月9日,最低价12.05,最高价12.58,未达到触发价格stopprice值12.98。收盘价12.16,低于买单创建后最低收盘价12.60,因此需要更新触发价格。触发价格按照stopprice = price * (1 + trailpercent)计算,这里price值为当日收盘价12.16,trailpercent在程序中设定为0.03,计算得到新的触发价格stop price为12.52。需要同步更新限制价格limitprice = stopprice - limitoffset = 12.52

(9) 5月10日,最低价12.10,最高价12.75,达到触发价格stopprice值12.52,订单被触发,订单以限制价格limitprice12.52,按照触发后的StopLimit订单规则被执行。对于该日open(12.34) < stopprice(12.52) & high(12.75) > stopprice(12.52) & open(12.34) < (12.68) & limitprice(12.52) >= stopprice(12.52) 的情况,将以stopprice值12.52成交(具体逻辑参考源码)。

(10) 5月13日,买单以stopprice值12.52成交。

通过这个例子可以看到StopTrailLimit订单在指定与不指定参数price值的情况下存在着一定的差异表现。在不指定参数price值的情况下,由于stopprice与limitprice一直保持相等,也就是说订单一旦被触发,限制价格limitprice也立即可以达到,订单立即成交,订单的limit(回撤建仓)作用就无法发挥出来。

总结

  • 在backtrader源代码中,StopTrailLimit订单与StopLimit订单实现逻辑基本一致,区别在于StopLimit订单的触发价格为固定值,而StopTrailLimit订单会根据收盘价的变化更新触发价格。

  • StopTrialLimit订单进行建仓时,可以实现突破回踩建仓的效果,并在一定程度上避免假突破造成高位建仓的情况。

  • StopTrailLimit订单在创建时指定与不指定参数price值,订单执行时表现存在一定差异(个人感觉这又是个bug)。

为了便于相互交流学习,新建了微信群,感兴趣的读者请加微信。
在这里插入图片描述

Guess you like

Origin blog.csdn.net/m0_46603114/article/details/106537138