BackTrader 中文文档(八)(3)https://developer.aliyun.com/article/1505281
括号订单
原文:
www.backtrader.com/docu/order-creation-execution/bracket/bracket/
发布 1.9.37.116
添加了 bracket
订单,提供了由回测经纪人支持的非常广泛的订单范围(Market
、Limit
、Close
、Stop
、StopLimit
、StopTrail
、StopTrailLimit
、OCO
)
注意
这是为了 回测 和 交互式经纪人 存储而实现的
bracket
订单不是单个订单,而实际上是由 3 个订单组成的。让我们考虑长侧
- 一个主要的
buy
订单,通常设置为Limit
或StopLimit
订单 - 一个低侧
sell
订单,通常设置为Stop
订单以限制损失 - 高侧
sell
订单,通常设置为Limit
订单以获利
对应的 sell
和 2 x buy
订单用于短侧。
低/高侧订单实际上会围绕主要侧订单创建一个括号。
为了加入一些逻辑,以下规则适用:
- 3 个订单一起提交,以避免它们中的任何一个独立触发
- 低/高侧订单被标记为主要侧的子订单
- 子订单在主要侧执行前不活动
- 取消主要侧会同时取消低侧和高侧
- 主要侧的执行会激活低侧和高侧
- 一旦激活
- 任何一侧订单的执行或取消都会自动取消另一侧订单
使用模式
创建括号订单集的两种可能性
- 单独发出 3 个订单
- 手动发出 3 个订单
单独发出括号
backtrader 在 Strategy
中提供了两种控制 bracket 订单的新方法。
buy_bracket
和sell_bracket
注意
签名和信息如下或在 Strategy
参考部分中。
通过一个单一语句完成 3 个订单的完整集合。例如:
brackets = self.buy_bracket(limitprice=14.00, price=13.50, stopprice=13.00)
注意 stopprice
和 limitprice
如何围绕主要的 price
设置。
这应该足够了。实际目标 data
将是 data0
,而 size
将由默认大小器自动确定。当然,可以指定许多其他参数以对执行进行精细控制。
返回值为:
- 一个包含这 3 个订单的列表,顺序如下:
[main, stop, limit]
因为在发出 sell_bracket
订单时,低侧和高侧会被调转,参数按照约定命名为 stop
和 limit
stop
旨在停止损失(长操作的低侧和短操作的高侧)limit
旨在获取利润(长操作的高侧和短操作的低侧)
手动发出括号
这涉及生成 3 个订单并玩弄 transmit
和 parent
参数。规则如下:
- 主要侧订单必须首先创建并具有
transmit=False
- 低/高侧订单必须具有
parent=main_side_order
- 要创建的第 1 个低/高侧订单必须具有
transmit=False
- 最后创建的订单(无论是低端还是高端)设置
transmit=True
执行上面单个命令的实际示例:
mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False) lowside = self.sell(price=13.00, size=mainside.size, exectype=bt.Order.Stop, transmit=False, parent=mainside) highside = self.sell(price=14.00, size=mainside.size, exectype=bt.Order.Limit, transmit=True, parent=mainside)
还有很多工作要做的地方:
- 跟踪
mainside
订单以指示它是其他订单的父订单 - 控制
transmit
以确保只有最后一个订单触发联合传输 - 指定执行类型
- 为低端和高端指定
size
因为size
必须相同。如果未手动指定参数并且最终用户已引入调整器,则调整器实际上可以指示订单的不同值。这就是为什么在为mainside
订单设置后必须手动将其添加到调用中的原因。
其中的一个样本
从下面的示例运行产生了这个输出(为简洁起见截断)
$ ./bracket.py --plot 2005-01-28: Oref 1 / Buy at 2941.11055 2005-01-28: Oref 2 / Sell Stop at 2881.99275 2005-01-28: Oref 3 / Sell Limit at 3000.22835 2005-01-31: Order ref: 1 / Type Buy / Status Submitted 2005-01-31: Order ref: 2 / Type Sell / Status Submitted 2005-01-31: Order ref: 3 / Type Sell / Status Submitted 2005-01-31: Order ref: 1 / Type Buy / Status Accepted 2005-01-31: Order ref: 2 / Type Sell / Status Accepted 2005-01-31: Order ref: 3 / Type Sell / Status Accepted 2005-02-01: Order ref: 1 / Type Buy / Status Expired 2005-02-01: Order ref: 2 / Type Sell / Status Canceled 2005-02-01: Order ref: 3 / Type Sell / Status Canceled ... 2005-08-11: Oref 16 / Buy at 3337.3892 2005-08-11: Oref 17 / Sell Stop at 3270.306 2005-08-11: Oref 18 / Sell Limit at 3404.4724 2005-08-12: Order ref: 16 / Type Buy / Status Submitted 2005-08-12: Order ref: 17 / Type Sell / Status Submitted 2005-08-12: Order ref: 18 / Type Sell / Status Submitted 2005-08-12: Order ref: 16 / Type Buy / Status Accepted 2005-08-12: Order ref: 17 / Type Sell / Status Accepted 2005-08-12: Order ref: 18 / Type Sell / Status Accepted 2005-08-12: Order ref: 16 / Type Buy / Status Completed 2005-08-18: Order ref: 17 / Type Sell / Status Completed 2005-08-18: Order ref: 18 / Type Sell / Status Canceled ... 2005-09-26: Oref 22 / Buy at 3383.92535 2005-09-26: Oref 23 / Sell Stop at 3315.90675 2005-09-26: Oref 24 / Sell Limit at 3451.94395 2005-09-27: Order ref: 22 / Type Buy / Status Submitted 2005-09-27: Order ref: 23 / Type Sell / Status Submitted 2005-09-27: Order ref: 24 / Type Sell / Status Submitted 2005-09-27: Order ref: 22 / Type Buy / Status Accepted 2005-09-27: Order ref: 23 / Type Sell / Status Accepted 2005-09-27: Order ref: 24 / Type Sell / Status Accepted 2005-09-27: Order ref: 22 / Type Buy / Status Completed 2005-10-04: Order ref: 24 / Type Sell / Status Completed 2005-10-04: Order ref: 23 / Type Sell / Status Canceled ...
显示了 3 种不同的结果:
- 在第一种情况下,主要的副作用已经过期,这自动取消了其他两个
- 在第二种情况下,主要的副作用已经完成,低(在购买情况下停止)被执行以限制损失
- 在第三种情况下,主要的副作用已经完成,而高侧(限制)被执行
这可以注意到,因为已完成的 ids 是22
和24
,高端订单最后被发出,这意味着未执行的低端订单的 id 为 23。
视觉上
可以立即看到,失败的交易围绕着相同的值以及成功的交易,这就是背书的目的。控制两侧。
正在运行的示例手动发出了 3 个订单,但可以告诉它使用buy_bracket
。让我们看看输出:
$ ./bracket.py --strat usebracket=True
具有相同结果
一些参考资料
查看新的buy_bracket
和sell_bracket
方法
def buy_bracket(self, data=None, size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs): ''' Create a bracket order group (low side - buy order - high side). The default behavior is as follows: - Issue a **buy** order with execution ``Limit` - Issue a *low side* bracket **sell** order with execution ``Stop`` - Issue a *high side* bracket **sell** order with execution ``Limit``. See below for the different parameters - ``data`` (default: ``None``) For which data the order has to be created. If ``None`` then the first data in the system, ``self.datas[0] or self.data0`` (aka ``self.data``) will be used - ``size`` (default: ``None``) Size to use (positive) of units of data to use for the order. If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will be used to determine the size. **Note**: The same size is applied to all 3 orders of the bracket - ``price`` (default: ``None``) Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements) ``None`` is valid for ``Market`` and ``Close`` orders (the market determines the price) For ``Limit``, ``Stop`` and ``StopLimit`` orders this value determines the trigger point (in the case of ``Limit`` the trigger is obviously at which price the order should be matched) - ``plimit`` (default: ``None``) Only applicable to ``StopLimit`` orders. This is the price at which to set the implicit *Limit* order, once the *Stop* has been triggered (for which ``price`` has been used) - ``trailamount`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop - ``trailpercent`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if ``trailamount`` is also specified it will be used) - ``exectype`` (default: ``bt.Order.Limit``) Possible values: (see the documentation for the method ``buy`` - ``valid`` (default: ``None``) Possible values: (see the documentation for the method ``buy`` - ``tradeid`` (default: ``0``) Possible values: (see the documentation for the method ``buy`` - ``oargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the main side order. Arguments from the default ``**kwargs`` will be applied on top of this. - ``**kwargs``: additional broker implementations may support extra parameters. ``backtrader`` will pass the *kwargs* down to the created order objects Possible values: (see the documentation for the method ``buy`` **Note**: this ``kwargs`` will be applied to the 3 orders of a bracket. See below for specific keyword arguments for the low and high side orders - ``stopprice`` (default: ``None``) Specific price for the *low side* stop order - ``stopexec`` (default: ``bt.Order.Stop``) Specific execution type for the *low side* order - ``stopargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the low side order. Arguments from the default ``**kwargs`` will be applied on top of this. - ``limitprice`` (default: ``None``) Specific price for the *high side* stop order - ``stopexec`` (default: ``bt.Order.Limit``) Specific execution type for the *high side* order - ``limitargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the high side order. Arguments from the default ``**kwargs`` will be applied on top of this. Returns: - A list containing the 3 bracket orders [order, stop side, limit side] ''' def sell_bracket(self, data=None, size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs): ''' Create a bracket order group (low side - buy order - high side). The default behavior is as follows: - Issue a **sell** order with execution ``Limit` - Issue a *high side* bracket **buy** order with execution ``Stop`` - Issue a *low side* bracket **buy** order with execution ``Limit``. See ``bracket_buy`` for the meaning of the parameters Returns: - A list containing the 3 bracket orders [order, stop side, limit side] '''
样本用法
$ ./bracket.py --help usage: bracket.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] Sample Skeleton optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: ../../datas/2005-2006-day-001.txt) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --cerebro kwargs kwargs in key=value format (default: ) --broker kwargs kwargs in key=value format (default: ) --sizer kwargs kwargs in key=value format (default: ) --strat kwargs kwargs in key=value format (default: ) --plot [kwargs] kwargs in key=value format (default: )
样本代码
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt class St(bt.Strategy): params = dict( ma=bt.ind.SMA, p1=5, p2=15, limit=0.005, limdays=3, limdays2=1000, hold=10, usebracket=False, # use order_target_size switchp1p2=False, # switch prices of order1 and order2 ) def notify_order(self, order): print('{}: Order ref: {} / Type {} / Status {}'.format( self.data.datetime.date(0), order.ref, 'Buy' * order.isbuy() or 'Sell', order.getstatusname())) if order.status == order.Completed: self.holdstart = len(self) if not order.alive() and order.ref in self.orefs: self.orefs.remove(order.ref) def __init__(self): ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2) self.cross = bt.ind.CrossOver(ma1, ma2) self.orefs = list() if self.p.usebracket: print('-' * 5, 'Using buy_bracket') def next(self): if self.orefs: return # pending orders do nothing if not self.position: if self.cross > 0.0: # crossing up close = self.data.close[0] p1 = close * (1.0 - self.p.limit) p2 = p1 - 0.02 * close p3 = p1 + 0.02 * close valid1 = datetime.timedelta(self.p.limdays) valid2 = valid3 = datetime.timedelta(self.p.limdays2) if self.p.switchp1p2: p1, p2 = p2, p1 valid1, valid2 = valid2, valid1 if not self.p.usebracket: o1 = self.buy(exectype=bt.Order.Limit, price=p1, valid=valid1, transmit=False) print('{}: Oref {} / Buy at {}'.format( self.datetime.date(), o1.ref, p1)) o2 = self.sell(exectype=bt.Order.Stop, price=p2, valid=valid2, parent=o1, transmit=False) print('{}: Oref {} / Sell Stop at {}'.format( self.datetime.date(), o2.ref, p2)) o3 = self.sell(exectype=bt.Order.Limit, price=p3, valid=valid3, parent=o1, transmit=True) print('{}: Oref {} / Sell Limit at {}'.format( self.datetime.date(), o3.ref, p3)) self.orefs = [o1.ref, o2.ref, o3.ref] else: os = self.buy_bracket( price=p1, valid=valid1, stopprice=p2, stopargs=dict(valid=valid2), limitprice=p3, limitargs=dict(valid=valid3),) self.orefs = [o.ref for o in os] else: # in the market if (len(self) - self.holdstart) >= self.p.hold: pass # do nothing in this case def runstrat(args=None): args = parse_args(args) cerebro = bt.Cerebro() # Data feed kwargs kwargs = dict() # Parse from/to-date dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S' for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']): if a: strpfmt = dtfmt + tmfmt * ('T' in a) kwargs[d] = datetime.datetime.strptime(a, strpfmt) # Data feed data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs) cerebro.adddata(data0) # Broker cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')')) # Sizer cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')')) # Strategy cerebro.addstrategy(St, **eval('dict(' + args.strat + ')')) # Execute cerebro.run(**eval('dict(' + args.cerebro + ')')) if args.plot: # Plot if requested to cerebro.plot(**eval('dict(' + args.plot + ')')) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=( 'Sample Skeleton' ) ) parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt', required=False, help='Data to read in') # Defaults for dates parser.add_argument('--fromdate', required=False, default='', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--todate', required=False, default='', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--cerebro', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--broker', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--sizer', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--strat', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--plot', required=False, default='', nargs='?', const='{}', metavar='kwargs', help='kwargs in key=value format') return parser.parse_args(pargs) if __name__ == '__main__': runstrat()