Bracket订单#
发布版本``1.9.37.116``新增了Bracket订单功能,提供了后测经纪商支持的各种订单类型(Market
,Limit
,Close
,Stop
,StopLimit
,StopTrail
,StopTrailLimit
,OCO
)。
Bracket订单不是一个单独的订单,实际上由*3*个订单组成。让我们考虑看多的情况:
主要的买入订单,通常设为``Limit``或``StopLimit``订单
低价位的卖出订单,通常设为``Stop``订单以限制损失
高价位的卖出订单,通常设为``Limit``订单以获利
而看空情况则是相应的卖出和2个买入订单。
低价位和高价位的订单实际上围绕主要订单形成一个Bracket。
为了使其具备逻辑,以下规则适用:使用模式#
有两种方法可以创建订单的括号设置
单次提交3个订单- 手动发出三个订单
单独发出一组括号订单#
backtrader 在``Strategy``中提供了两种控制*括号*订单的新方法。
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``将会由默认的sizer自动确定。当然,还可以指定其他参数来对执行进行细致控制。
返回值为:- 包含这个顺序的三个订单的 list
: [主订单, 停止订单, 限制订单]
因为在发出一个 sell_bracket
订单时,低位和高位将被颠倒,参数的命名遵循惯例,即以 stop
和 limit
命名。
stop
用于止损(在做多操作中的低位,以及在做空操作中的高位)limit
用于获利(在做多操作中的高位,以及在做空操作中的低位)
手动发起 Bracket#
这涉及到生成三个订单并调整 transmit
和 parent
参数。规则如下:
主订单必须首先创建,并且具有
transmit=False
。低位/高位订单必须具有
parent=主订单
。第一个低位/高位订单必须具有
transmit=False
。最后一个被创建的订单(无论是低位还是高位)设置
transmit=True
。上面的单个命令实际示例:mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False) lowside = self.sell(price=13.00, size=mainsize.size, exectype=bt.Order.Stop,
transmit=False, parent=mainside)
- highside = self.sell(price=14.00, size=mainsize.size, exectype=bt.Order.Limit,
transmit=True, parent=mainside)
这里还有更多要做的事情:
跟踪
mainside
订单,指示它是其他订单的父订单控制
transmit
以确保只有最后一个订单触发联合传输指定执行类型
为低侧和高侧指定
size
因为
size
必须 是相同的。如果未手动指定该参数,并且最终用户引入了一个定量器,定量器实际上可以指示订单的不同值。这就是为什么必须在为mainside
订单设置完毕之后,在调用中手动添加它的原因。
示例#
运行下面的示例会产生以下输出(为了简洁起见):
$ ./bracket.py --plot
2005-01-28: 订单编号 1 / 以 2941.11055 的价格购买
2005-01-28: 订单编号 2 / 以 2881.99275 的价格卖出止损单
2005-01-28: 订单编号 3 / 以 3000.22835 的价格卖出限价单
2005-01-31: 订单编号: 1 / 类型 购买 / 状态 已提交
2005-01-31: 订单编号: 2 / 类型 卖出 / 状态 已提交
2005-01-31: 订单编号: 3 / 类型 卖出 / 状态 已提交
2005-01-31: 订单编号: 1 / 类型 购买 / 状态 已接受
2005-01-31: 订单编号: 2 / 类型 卖出 / 状态 已接受
2005-01-31: 订单编号: 3 / 类型 卖出 / 状态 已接受
2005-02-01: 订单编号: 1 / 类型 购买 / 状态 已过期
2005-02-01: 订单编号: 2 / 类型 卖出 / 状态 已取消
2005-02-01: 订单编号: 3 / 类型 卖出 / 状态 已取消
...
2005-08-11: 订单编号 16 / 以 3337.3892 的价格购买
2005-08-11: 订单编号 17 / 以 3270.306 的价格卖出止损单
2005-08-11: 订单编号 18 / 以 3404.4724 的价格卖出限价单
2005-08-12: 订单编号: 16 / 类型 购买 / 状态 已提交
2005-08-12: 订单编号: 17 / 类型 卖出 / 状态 已提交
2005-08-12: 订单编号: 18 / 类型 卖出 / 状态 已提交
2005-08-12: 订单编号: 16 / 类型 购买 / 状态 已接受
2005-08-12: 订单编号: 17 / 类型 卖出 / 状态 已接受
2005-08-12: 订单编号: 18 / 类型 卖出 / 状态 已接受
2005-08-12: 订单编号: 16 / 类型 购买 / 状态 已完成
2005-08-18: 订单编号: 17 / 类型 卖出 / 状态 已完成
2005-08-18: 订单编号: 18 / 类型 卖出 / 状态 已取消
...
2005-09-26: 订单编号 22 / 以 3383.92535 的价格购买
2005-09-26: 订单编号 23 / 以 3315.90675 的价格卖出止损单
2005-09-26: 订单编号 24 / 以 3451.94395 的价格卖出限价单
2005-09-27: 订单编号: 22 / 类型 购买 / 状态 已提交
2005-09-27: 订单编号: 23 / 类型 卖出 / 状态 已提交
2005-09-27: 订单编号: 24 / 类型 卖出 / 状态 已提交
2005-09-27: 订单编号: 22 / 类型 购买 / 状态 已接受
2005-09-27: 订单编号: 23 / 类型 卖出 / 状态 已接受
2005-09-27: 订单编号: 24 / 类型 卖出 / 状态 已接受
2005-09-27: 订单编号: 22 / 类型 购买 / 状态 已完成
2005-10-04: 订单编号: 24 / 类型 卖出 / 状态 已完成
2005-10-04: 订单编号: 23 / 类型 卖出 / 状态 已取消
...
显示了3种不同的结果:- 在第一种情况下,主要的附加订单已过期,从而自动取消了其他两个订单。
在第二种情况下,主要的附加订单已完成,并且低价(在买入情况下为止损)被执行,限制了损失。
在第三种情况下,主要的附加订单已完成,并且高价(限价)被执行。
可以注意到,“已完成”订单的id为22和24,而**高**附加订单是最后发出的,这意味着未执行的低附加订单的id为23。
视觉上
可以立即看出亏损交易和盈利交易都围绕着相同的值对齐,这是括号的目的。控制两侧。
运行样例时,可以手动发出3个订单,但也可以告诉它使用``buy_bracket``。让我们看看输出结果:
$ ./bracket.py --strat usebracket=True
结果相同.. thumbnail:: bracket-buy_bracket.png
一些参考#
查看新的 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):
'''
创建一个买卖订单组(低侧 - 买单 - 高侧)。默认行为如下:
- 发出一个**买**单,执行为 ``Limit``
- 发出一个低侧**卖**断订单,执行为 ``Stop``
- 发出一个高侧**卖**断订单,执行为 ``Limit``
以下是不同参数的说明
- ``data`` (默认值: ``None``)
要创建订单的数据。如果为 ``None``,则使用系统中的第一个数据,即 ``self.datas[0] or self.data0``(也称为 ``self.data``)。- `size` (默认值: `None` )
用于订单的数据单位的大小(正数)。
如果为 `None` ,则会使用通过 `getsizer` 获取的 `sizer` 实例来确定大小。
**注意**:相同的大小适用于括号的所有3个订单
price (默认值: None )
要使用的价格(如果不符合最小tick大小的要求,实时经纪商可能会对实际格式施加限制)
对于 Market 和`Close 订单, None`是有效的(市场确定价格)
对于 Limit 、Stop `和`StopLimit 订单,此值确定触发点(对于 Limit ,明显触发是订单应该匹配的价格)
plimit (默认值: None )
仅适用于 StopLimit 订单。这是在触发 Stop (使用了 price )后设置隐式 Limit 订单的价格。 -
trailamount``(默认值:``None
)如果订单类型是停止跟踪或停止跟踪限价订单,这是一个绝对金额,用于确定价格的距离(对于卖出订单是低于价格,对于买入订单是高于价格)以保持追踪停止。
trailpercent``(默认值:``None
)如果订单类型是停止跟踪或停止跟踪限价订单,这是一个百分比金额,用于确定价格的距离(对于卖出订单是低于价格,对于买入订单是高于价格)以保持追踪停止(如果还指定了``trailamount``,则会使用它)。
exectype``(默认值:``bt.Order.Limit
)可能的值:(请参阅方法``buy``的文档)
valid``(默认值:``None
)可能的值:(请参阅方法``buy``的文档)
tradeid``(默认值:``0
)可能的值:(请参阅方法``buy``的文档)- ``oargs``(默认值:“{}”)
传递给主体交易订单的特定关键字参数(以 dict 形式)。默认的 **kwargs 参数将在此基础上应用。
**kwargs
:其他经纪人实现可能支持额外的参数。 backtrader 将把 kwargs 传递给创建的订单对象。可能的值:(请参阅``buy``方法的文档)
注意:这些``kwargs``将应用于bracket中的3个订单。有关低侧和高侧订单的特定关键字参数,请参阅下文
stopprice``(默认值:``None
)低侧止损订单的特定价格
stopexec``(默认值:``bt.Order.Stop
)低侧订单的特定执行类型
stopargs``(默认值:“{}”)特定关键字参数(在 ``dict
中)传递给低位订单。默认**kwargs
中的参数将被应用在这个参数的顶部。limitprice``(默认值: ``None
)高位 止损单的特定价格
stopexec``(默认值: ``bt.Order.Limit
)高位 订单的特定执行类型
limitargs``(默认值: ``{}
)传递给高位订单的特定关键字参数(在
dict
中)。默认**kwargs
中的参数将被应用在这个参数的顶部。
返回值: - 包含 3 个括号订单 [order, 停损方向, 止盈方向] 的列表 ‘’’
- 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):
‘’’ 创建一个括号订单组(低位 - 卖出订单 - 高位)。默认行为如下:
发出一个具有执行方式
Limit
的 卖出 订单- 发出一份*高侧*括号**买入**订单,执行方式为“Stop”发出一份*低侧*括号**买入**订单,执行方式为“Limit”
有关参数含义,请参考“bracket_buy”
返回: - 包含3个括号订单[订单,止损方向,限价方向]的列表
示例用法#
$ ./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
可选参数:
-h, --help 显示此帮助消息并退出
--data0 DATA0 要读取的数据(默认值:../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE 格式为YYYY-MM-DD[THH:MM:SS]的日期[时间](默认值:)
--todate TODATE 格式为YYYY-MM-DD[THH:MM:SS]的日期[时间](默认值:)
--cerebro kwargs 以键=值格式的kwargs(默认值:)
--broker kwargs 以键=值格式的kwargs(默认值:)
--sizer kwargs 以键=值格式的kwargs(默认值:)
--strat kwargs 以键=值格式的kwargs(默认值:)
--plot [kwargs] 以键=值格式的kwargs(默认值:)
示例代码#
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()