括号订单
原文: https://www.backtrader.com/blog/posts/2017-04-01-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 个buy
订单。
低端/高端订单实际上在主端订单周围创建了一个括号。
要将一些逻辑放在其中,以下规则适用:
-
这 3 个订单一起提交,以避免单独触发其中任何一个订单
-
低端/高端订单标记为主端的子级
-
在执行主侧之前,子级不会处于活动状态
-
主侧的取消将同时取消低端和高端
-
主侧的执行同时激活低压侧和高压侧
-
活跃时
- 执行或取消任何低端/高端订单都会自动取消另一个订单
使用模式
创建订单的括号集有两种可能性
-
单次发出 3 份订单
-
手动发出 3 份订单
单次发出括号
反向交易者在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
将由默认的大小器自动确定。当然,可以指定这两个参数和许多其他参数,以便对执行进行精细控制。
返回值为:
- A
list
包含此订单中的 3 个订单:[main, stop, limit]
因为在发出sell_bracket
指令时,低端和高端都会被调到 aound,参数的命名遵循约定stop
和limit
-
stop
用于停止损失(长时间运行时低压侧,短时间运行时高压侧) -
limit
是为了获取利润(长操作高,短操作低)
人工签发括号
这涉及到 3 个命令的生成,以及对transmit
和parent
参数的处理。规则:
-
主侧订单必须创建 1st且有
transmit=False
-
低端/高端订单必须有
parent=main_side_order
-
要创建的 1st低端/高端订单必须有
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
必须相同。如果没有手动指定参数,并且最终用户引入了一个 sizer,那么 sizer 实际上可以为订单指示一个不同的值。这就是为什么在为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 种不同的结果:
-
在 1st案例中,主辅助订单过期,自动取消了其他两个
-
在第 2和案例中,主侧订单已完成,低(买入案例中的停止)已执行,以限制损失
-
在 3rd案例中,完成了主侧指令,并执行了高侧(限制)
可以注意到这一点,因为完成的id 为
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 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 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()