订单
订单。
Cerebro
是 backtrader
中的关键控制系统,而 Strategy
(一个子类)是最终用户的关键控制点。后者需要一种方法将系统的其他部分链接起来,这就是订单发挥关键作用的地方。
订单将 Strategy
中的逻辑决策转化为适合 Broker
执行操作的消息。这是通过以下方式完成的:
-
创建。
通过 Strategy 的方法:
buy\``,
sell和
close(Strategy),它们返回一个
order`实例作为参考。 -
取消。
通过 Strategy 的方法:
cancel
(Strategy),它接受一个订单实例进行操作。
订单也作为一种向用户传达信息的方法,通知经纪人运行情况。
-
通知。
对 Strategy 方法:
notify_order
(Strategy),报告一个order
实例。
订单创建。
在调用 buy
、sell
和 close
时,以下参数适用于创建:
-
data
(默认值:None
)。要创建订单的数据。如果为
None
,则系统中的第一个数据,self.datas[0] 或 self.data0
(又名self.data
),将被使用。 -
size
(默认值:None
)。要使用的单位数据的大小(正数)用于订单。
如果为
None
,则将使用通过getsizer
检索到的sizer
实例来确定大小。 -
price
(默认值:None
)。要使用的价格(实时经纪人可能对实际格式施加限制,如果不符合最小跳动要求)。
None
对于Market
和Close
订单是有效的(市场确定价格)。对于
Limit
、Stop
和StopLimit
订单,此值确定触发点(在Limit
的情况下,触发显然是订单应该匹配的价格)。 -
plimit
(默认值:None
)。仅适用于
StopLimit
订单。这是在Stop触发后设置隐式限价订单的价格(其中使用了price
)。 -
exectype
(默认值:None
)。可能的值:
-
Order.Market
或None
。市价订单将以下一个可用价格执行。在回测中,它将是下一个柱的开盘价。 -
Order.Limit
。只能以给定的price
或更好的价格执行的订单。 -
Order.Stop
。在price
触发并像Order.Market
订单一样执行的订单。 -
Order.StopLimit
。在price
触发并作为隐式限价订单执行的订单,其价格由pricelimit
给出。
-
-
valid
(默认值:None
)。可能的值:
-
None
:这将生成一个不会过期的订单(也称为有效直至取消),并保持在市场上直到匹配或取消。实际上,经纪人往往会强加时间限制,但通常时间跨度很长,可以视为不会过期。 -
datetime.datetime
或datetime.date
实例:日期将用于生成一个在给定日期时间之前有效的订单(也称为有效截止日期)。 -
Order.DAY
或0
或timedelta()
: 将生成一个直到Session 结束(又称day订单)有效的订单 -
numeric value
: 假定这是与matplotlib
编码中的日期时间相对应的值(backtrader
使用的编码),并将用于生成直到那个时间点有效的订单(good till date)。
-
-
tradeid
(默认:0
)这是
backtrader
用来跟踪同一资产上重叠交易的内部值。当订单状态发生变化时,该tradeid
会发送回策略。 -
**kwargs
:其他经纪人实现可能支持额外的参数。backtrader
将kwargs传递给创建的订单对象例如:如果
backtrader
直接支持的 4 种订单执行类型不够用,例如Interactive Brokers的情况下,可以将以下内容作为kwargs传递:orderType='LIT', lmtPrice=10.0, auxPrice=9.8`
这将覆盖由
backtrader
创建的设置,并生成具有touched价格为 9.8 和limit价格为 10.0 的LIMIT IF TOUCHED
订单。
注意
close
方法将检查当前仓位,并相应地使用buy
或sell
来有效地close该仓位。 size
也将自动计算,除非参数是来自用户的输入,在这种情况下可以实现部分close或reversal。
订单通知
要接收通知,用户子类的Strategy
必须重写notify_order
方法(默认行为是什么也不做)。以下内容适用于这些通知:
-
在调用策略的
next
方法之前发出 -
可能(并且将)在相同的next周期内多次发生对于相同或不同状态的相同order。
订单可能在
next
再次被调用之前提交给经纪人并接受并且其执行完成。在这种情况下,至少会有 3 个通知,其
status
值如下:-
Order.Submitted
因为订单已发送给经纪人 -
Order.Accepted
因为订单已被经纪人接受并等待可能的执行。 -
Order.Completed
因为在示例中它被迅速匹配和完全填充(这在Market
订单通常情况下可能是发生的)。
-
即使在相同的状态下,通知也可能多次发生在Order.Partial
的情况下。这个状态不会在backtesting经纪人(不考虑匹配时的成交量)中看到,但是肯定会被真实的经纪人设置。
真实的经纪人可能在更新仓位之前发出一个或多个执行,这组执行将组成Order.Partial
通知。
实际执行数据在属性中:order.executed
,它是OrderData
类型的对象(参见下文的引用),具有常见的字段如size
和price
创建时的值存储在order.created
中,在order
的整个生命周期内保持不变。
订单状态值
以下内容已定义:
-
Order.Created
:在创建Order
实例时设置。 除非订单
实例是手动创建而不是通过买入
,卖出
和关闭
,否则永远不会被最终用户看到。 -
Order.Submitted
:在订单
实例已传输到经纪人
时设置。 这只意味着它已经发送。 在回测模式下,这将是一个立即的动作,但是在实际的时间中,这可能需要一段时间才能与真正的经纪人完成,这可能会接收订单,只有在转发到交易所时才会首次通知。 -
Order.Accepted
:broker
已接受订单并且已经在系统中(或已经在交易所中)等待根据设置的参数(如执行类型,大小,价格和有效性)执行 -
Order.Partial
:订单已部分执行。order.executed
包含当前填充的size
和平均价格。order.executed.exbits
包含一个详细的ExecutionBits
列表,详细说明了部分填充情况 -
Order.Complete
:订单已完全填充的平均价格。 -
Order.Rejected
:经纪人已拒绝订单。 一个参数(例如确定其生存期的valid
)可能不被经纪人接受,订单将无法被接受。原因将通过
strategy
的notify_store
方法通知。 尽管这可能看起来很奇怪,但原因是真实生活的经纪人将通过事件通知这一点,该事件可能与订单直接相关也可能与订单无关。 但是来自经纪人的通知仍然可以在notify_store
中看到。此状态将不会在回测经纪人中看到
-
Order.Margin
:订单执行将意味着保证金调用,并且先前接受的订单已从系统中移除 -
Order.Cancelled
(或Order.Canceled
):用户请求取消的确认必须考虑到通过策略的
cancel
方法取消订单的请求不是取消的保证。 订单可能已经被执行,但是经纪人可能尚未通知,和/或通知可能尚未传递给策略。 -
Order.Expired
:先前接受的订单其具有时间有效性已过期并已从系统中移除
参考:订单和相关类
这些对象是backtrader
生态系统中的通用类。 在与其他经纪人操作时,它们可以被扩展和/或包含额外的嵌入信息。 请参阅适当经纪人的参考资料。
类backtrader.order.Order()
持有创建/执行数据和订单类型的类。
订单可能具有以下状态:
-
已提交:已发送到经纪人并等待确认
-
已接受:经纪人接受的
-
部分:部分执行
-
已完成:已完全执行
-
已取消:用户取消的已取消
-
已过期:已过期
-
保证金:没有足够的现金执行订单。
-
被拒绝:被经纪人拒绝
这可能发生在订单提交时(因此订单不会达到已接受的状态)或在执行之前,每个新的条价格因为现金已经被其他来源提取(类似期货的工具可能会减少现金或订单已经被执行)
成员属性:
-
ref:唯一的订单标识符
-
created:持有创建数据的 OrderData
-
executed:持有执行数据的订单数据
-
info:通过方法
addinfo()
传递的自定义信息。它以 OrderedDict 的形式保留,已经被子类化,因此还可以使用‘.’符号指定键
用户方法:
-
isbuy():返回一个布尔值,指示订单是否购买
-
issell():返回一个布尔值,指示订单是否卖出
-
alive():如果订单处于部分或已接受状态,则返回布尔值
类 backtrader.order.OrderData(dt=None, size=0, price=0.0, pricelimit=0.0, remsize=0, pclose=0.0, trailamount=0.0, trailpercent=0.0)
包含创建和执行的实际订单数据。
在创建时进行的请求,在执行时进行的实际结果。
成员属性:
-
exbits:此 OrderData 的 OrderExecutionBits 的可迭代对象
-
dt:日期时间(浮点数)创建/执行时间
-
size:请求/执行大小
-
price:执行价格注意:如果未给出价格且未给出价格限制,则将在订单创建时使用当前收盘价作为参考
-
pricelimit:保存 StopLimit 的价格限制(先触发)
-
trailamount:追踪止损的绝对价格距离
-
trailpercent:追踪止损的百分比价格距离
-
value:整个比特大小的市场价值
-
comm:整个比特执行的佣金
-
pnl:由此比特生成的 pnl(如果有东西被关闭)
-
margin:订单所产生的保证金(如果有)
-
psize:当前开放位置大小
-
pprice:当前开放位置价格
类 backtrader.order.OrderExecutionBit(dt=None, size=0, price=0.0, closed=0, closedvalue=0.0, closedcomm=0.0, opened=0, openedvalue=0.0, openedcomm=0.0, pnl=0.0, psize=0, pprice=0.0)
用于保存订单执行信息。 “比特” 不确定订单是否已完全/部分执行,它只是保存信息。
成员属性:
-
dt:日期时间(浮点数)执行时间
-
size:执行了多少
-
price:执行价格
-
closed:执行关闭了现有的位置多少
-
opened:执行打开了多少新的位置
-
openedvalue:打开部分的市值
-
closedvalue:关闭部分的市值
-
closedcomm:关闭部分的佣金
-
openedcomm:打开部分的佣金
-
value:整个比特大小的市场价值
-
comm:整个比特执行的佣金
-
pnl:由此比特生成的 pnl(如果有东西被关闭)
-
psize:当前开放位置大小
-
pprice:当前开放位置价格
订单管理和执行
原文:
www.backtrader.com/docu/order-creation-execution/order-creation-execution/
如果订单不能模拟,回测和因此backtrader
将不完整。为此,平台提供了以下功能。
对于订单管理的 3 个基本原则:
-
buy
-
sell
-
cancel
注意
显然,update
原语是一种逻辑,但常识告诉我们,这种方法主要由使用判断性交易方法的手动操作者使用。
对于订单执行逻辑,有以下执行类型:
-
Market
-
Close
-
Limit
-
Stop
-
StopLimit
订单管理
一些例子:
# buy the main date, with sizer default stake, Market order
order = self.buy()
# Market order - valid will be "IGNORED"
order = self.buy(valid=datetime.datetime.now() + datetime.timedelta(days=3))
# Market order - price will be IGNORED
order = self.buy(price=self.data.close[0] * 1.02)
# Market order - manual stake
order = self.buy(size=25)
# Limit order - want to set the price and can set a validity
order = self.buy(exectype=Order.Limit,
price=self.data.close[0] * 1.02,
valid=datetime.datetime.now() + datetime.timedelta(days=3)))
# StopLimit order - want to set the price, price limit
order = self.buy(exectype=Order.StopLimit,
price=self.data.close[0] * 1.02,
plimit=self.data.close[0] * 1.07)
# Canceling an existing order
self.broker.cancel(order)
注意
所有订单类型都可以通过创建一个Order
实例(或其子类之一),然后通过以下方式传递给经纪人:
order = self.broker.submit(order)
注意
broker
本身有buy
和sell
的基本原则,但对于默认参数要求更严格。
订单执行逻辑
经纪人对订单执行有两个主要准则(假设?)
-
当前数据已经发生,不能用于执行订单。
如果策略中的逻辑是这样的:
if self.data.close > self.sma: # where sma is a Simple Moving Average self.buy()`
期望不能是订单将以正在逻辑中检查的
close
价格执行,因为它已经发生。订单可以在下一个一组开/高/低/收价格点的范围内(以及订单中规定的条件)第一次执行
-
交易量不起作用
如果交易者选择非流动资产或者精确地击中价格柱的极端点(高/低),它在实际交易中确实会发生。
但是击中高/低点是很少发生的(如果你这样做...你就不需要
backtrader
),并且所选择的资产将有足够的流动性来吸收任何常规交易的订单
市场
执行:
- 下一个一组开/高/低/收价格的开盘价(通常称为柱)
原因:
- 如果逻辑在时间点 X 执行并发出
Market
订单,那么接下来会发生的价格是即将到来的open
价格
注意
这个订单总是执行,并且忽略了用于创建它的price
和valid
参数
Close
执行:
- 当下一个柱子实际关闭时,使用下一个柱子的
close
价格
原因:
-
大多数回测数据源已经包含了关闭的柱子,订单将立即以下一个柱子的
close
价格执行。日线数据源是最常见的例子。但是系统可以提供“tick”价格,并且实际柱(时间/日期方面)会不断更新新的 tick,而不会实际移动到下一个柱(因为时间和/或日期尚未更改)
只有当时间或日期发生变化时,柱子才会真正关闭并执行订单
Limit
执行:
-
订单创建时设置的
price
,如果data
触及它,从下一个价格柱开始。如果设置了
valid
且时间点已到达,则订单将被取消
价格匹配:
-
backtrader
尝试为Limit
订单提供最逼真的执行价格。使用 4 个价格点(开盘价/最高价/最低价/收盘价),可以部分推断请求的
价格
是否可以改善。对于
Buy
订单-
情况 1:
如果柱的
开盘
价低于限价,则订单立即以开盘
价执行。订单已在会话开启阶段清算 -
情况 2:
如果
开盘
价没有穿过限价但最低
价低于限价,则在会话期间已见到限价,订单可以执行
对于
Sell
订单,逻辑显然是相反的。 -
停止
执行:
-
如果
数据
触及触发价格
,则设置在订单创建时,从下一个价格柱开始。如果设置了
valid
且时间点已到达,则订单将被取消
价格匹配:
-
backtrader
尝试为Stop
订单提供最逼真的触发价格。使用 4 个价格点(开盘价/最高价/最低价/收盘价),可以部分推断请求的
价格
是否可以改善。对于
Buy
的\
Stoporders
-
情况 1:
如果柱的
开盘
价高于止损价,则订单立即以开盘
价执行。旨在在价格向上移动反对现有空头头寸时停止损失
-
情况 2:
如果
开盘
价没有穿过止损价但最高
价高于止损价,则在会话期间已见到止损价,订单可以执行
对于
Sell
的Stop
订单,逻辑显然是相反的。 -
停止限价
执行:
- 触发
价格
设置订单,从下一个价格柱开始。
价格匹配:
-
触发:使用
Stop
匹配逻辑(但仅触发并将订单转换为Limit
订单) -
限价:使用
Limit
价格匹配逻辑
一些样本
一如既往,图片(带代码)价值数百万长的解释。请注意,片段集中在订单创建部分。完整代码在底部。
使用价格在简单移动平均线上/下方关闭策略来生成买入/卖出信号
信号在图表底部可见:使用交叉指示器的CrossOver
。
将生成的“买入”订单的参考保留,以允许系统中最多同时存在一个订单。
执行类型:市价
在图表中看到,订单是在信号生成后的一个价格柱后执行的,使用开盘价。
if self.p.exectype == 'Market':
self.buy(exectype=bt.Order.Market) # default if not given
self.log('BUY CREATE, exectype Market, price %.2f' %
self.data.close[0])
输出图表。
命令行和输出:
$ ./order-execution-samples.py --exectype Market
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Market, price 3641.42
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3643.35, Cost: 3643.35, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
...
...
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Market, price 4052.89
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-12T23:59:59+00:00, BUY EXECUTED, Price: 4052.55, Cost: 4052.55, Comm 0.00
执行类型:关闭
现在订单也是在信号后一个柱执行的,但是使用收盘价。
elif self.p.exectype == 'Close':
self.buy(exectype=bt.Order.Close)
self.log('BUY CREATE, exectype Close, price %.2f' %
self.data.close[0])
输出图表。
命令行和输出:
$ ./order-execution-samples.py --exectype Close
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Close, price 3641.42
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3685.48, Cost: 3685.48, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
...
...
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Close, price 4045.22
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-07T23:59:59+00:00, BUY EXECUTED, Price: 4072.86, Cost: 4072.86, Comm 0.00
2006-11-24T23:59:59+00:00, SELL CREATE, 4048.16
2006-11-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-27T23:59:59+00:00, SELL EXECUTED, Price: 4045.05, Cost: 4045.05, Comm 0.00
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Close, price 4052.89
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-12T23:59:59+00:00, BUY EXECUTED, Price: 4059.74, Cost: 4059.74, Comm 0.00
执行类型:限价
在几行之前计算有效性,以防已作为参数传递。
if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None
设置了比信号生成价格(信号栏收盘价)低 1%的限价。请注意,这样可以防止上述许多订单被执行。
elif self.p.exectype == 'Limit':
price = self.data.close * (1.0 - self.p.perc1 / 100.0)
self.buy(exectype=bt.Order.Limit, price=price, valid=valid)
if self.p.valid:
txt = 'BUY CREATE, exectype Limit, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Limit, price %.2f'
self.log(txt % price)
输出图表。
仅发出了 4 个订单。试图捕捉小幅下跌的价格限制完全改变了输出结果。
命令行和输出:
$ ./order-execution-samples.py --exectype Limit --perc1 1
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Limit, price 3605.01
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-05-18T23:59:59+00:00, BUY EXECUTED, Price: 3605.01, Cost: 3605.01, Comm 0.00
2006-06-05T23:59:59+00:00, SELL CREATE, 3604.33
2006-06-05T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-06T23:59:59+00:00, SELL EXECUTED, Price: 3598.58, Cost: 3598.58, Comm 0.00
2006-06-21T23:59:59+00:00, BUY CREATE, exectype Limit, price 3491.57
2006-06-21T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-28T23:59:59+00:00, BUY EXECUTED, Price: 3491.57, Cost: 3491.57, Comm 0.00
2006-07-13T23:59:59+00:00, SELL CREATE, 3562.56
2006-07-13T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-07-14T23:59:59+00:00, SELL EXECUTED, Price: 3545.92, Cost: 3545.92, Comm 0.00
2006-07-24T23:59:59+00:00, BUY CREATE, exectype Limit, price 3596.60
2006-07-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
执行类型:带有效期的限价
为了不永远等待限价订单,该订单仅在 4(日历)天内有效,而这可能只有在价格对“买入”订单不利时才能执行。
输出图表。
生成了更多订单,但除了一个“买入”订单外,所有订单均已到期,进一步限制了操作数量。
命令行和输出:
$ ./order-execution-samples.py --exectype Limit --perc1 1 --valid 4
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Limit, price 3605.01, valid: 2006-01-30
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-30T23:59:59+00:00, BUY EXPIRED
2006-03-10T23:59:59+00:00, BUY CREATE, exectype Limit, price 3760.48, valid: 2006-03-14
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-14T23:59:59+00:00, BUY EXPIRED
2006-03-30T23:59:59+00:00, BUY CREATE, exectype Limit, price 3835.86, valid: 2006-04-03
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-03T23:59:59+00:00, BUY EXPIRED
2006-04-20T23:59:59+00:00, BUY CREATE, exectype Limit, price 3821.40, valid: 2006-04-24
2006-04-20T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-24T23:59:59+00:00, BUY EXPIRED
2006-05-04T23:59:59+00:00, BUY CREATE, exectype Limit, price 3804.65, valid: 2006-05-08
2006-05-04T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-05-08T23:59:59+00:00, BUY EXPIRED
2006-06-01T23:59:59+00:00, BUY CREATE, exectype Limit, price 3611.85, valid: 2006-06-05
2006-06-01T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-05T23:59:59+00:00, BUY EXPIRED
2006-06-21T23:59:59+00:00, BUY CREATE, exectype Limit, price 3491.57, valid: 2006-06-25
2006-06-21T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-26T23:59:59+00:00, BUY EXPIRED
2006-07-24T23:59:59+00:00, BUY CREATE, exectype Limit, price 3596.60, valid: 2006-07-28
2006-07-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-07-28T23:59:59+00:00, BUY EXPIRED
2006-09-12T23:59:59+00:00, BUY CREATE, exectype Limit, price 3751.07, valid: 2006-09-16
2006-09-12T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-18T23:59:59+00:00, BUY EXPIRED
2006-09-20T23:59:59+00:00, BUY CREATE, exectype Limit, price 3802.90, valid: 2006-09-24
2006-09-20T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-22T23:59:59+00:00, BUY EXECUTED, Price: 3802.90, Cost: 3802.90, Comm 0.00
2006-11-02T23:59:59+00:00, SELL CREATE, 3974.62
2006-11-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-03T23:59:59+00:00, SELL EXECUTED, Price: 3979.73, Cost: 3979.73, Comm 0.00
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Limit, price 4004.77, valid: 2006-11-10
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-10T23:59:59+00:00, BUY EXPIRED
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Limit, price 4012.36, valid: 2006-12-15
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-15T23:59:59+00:00, BUY EXPIRED
执行类型:Stop
设置了比信号价格高 1%的停止价格。这意味着策略只有在信号产生且价格继续上涨时才会购买,这可能被解释为一种强势信号。
这完全改变了执行全景。
elif self.p.exectype == 'Stop':
price = self.data.close * (1.0 + self.p.perc1 / 100.0)
self.buy(exectype=bt.Order.Stop, price=price, valid=valid)
if self.p.valid:
txt = 'BUY CREATE, exectype Stop, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Stop, price %.2f'
self.log(txt % price)
输出图表。
命令行和输出:
$ ./order-execution-samples.py --exectype Stop --perc1 1
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Stop, price 3677.83
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3677.83, Cost: 3677.83, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
2006-03-10T23:59:59+00:00, BUY CREATE, exectype Stop, price 3836.44
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-15T23:59:59+00:00, BUY EXECUTED, Price: 3836.44, Cost: 3836.44, Comm 0.00
2006-03-28T23:59:59+00:00, SELL CREATE, 3811.45
2006-03-28T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-29T23:59:59+00:00, SELL EXECUTED, Price: 3811.85, Cost: 3811.85, Comm 0.00
2006-03-30T23:59:59+00:00, BUY CREATE, exectype Stop, price 3913.36
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-29T23:59:59+00:00, BUY EXECUTED, Price: 3913.36, Cost: 3913.36, Comm 0.00
2006-11-02T23:59:59+00:00, SELL CREATE, 3974.62
2006-11-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-03T23:59:59+00:00, SELL EXECUTED, Price: 3979.73, Cost: 3979.73, Comm 0.00
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Stop, price 4085.67
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-13T23:59:59+00:00, BUY EXECUTED, Price: 4085.67, Cost: 4085.67, Comm 0.00
2006-11-24T23:59:59+00:00, SELL CREATE, 4048.16
2006-11-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-27T23:59:59+00:00, SELL EXECUTED, Price: 4045.05, Cost: 4045.05, Comm 0.00
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Stop, price 4093.42
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-13T23:59:59+00:00, BUY EXECUTED, Price: 4093.42, Cost: 4093.42, Comm 0.00
执行类型:StopLimit
设置了比信号价格高 1%的停止价格。但限价设置在信号(收盘)价格的 0.5%以上,这可能被解释为:等待力量显现,但不要买入顶峰。等待下跌。
有效期限制为 20(日历)天
elif self.p.exectype == 'StopLimit':
price = self.data.close * (1.0 + self.p.perc1 / 100.0)
plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)
self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,
plimit=plimit)
if self.p.valid:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' valid: %s, pricelimit: %.2f')
self.log(txt % (price, valid.strftime('%Y-%m-%d'), plimit))
else:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' pricelimit: %.2f')
self.log(txt % (price, plimit))
输出图表。
命令行和输出:
$ ./order-execution-samples.py --exectype StopLimit --perc1 1 --perc2 0.5 --valid 20
2006-01-26T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3677.83, valid: 2006-02-15, pricelimit: 3659.63
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-02-03T23:59:59+00:00, BUY EXECUTED, Price: 3659.63, Cost: 3659.63, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
2006-03-10T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3836.44, valid: 2006-03-30, pricelimit: 3817.45
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-21T23:59:59+00:00, BUY EXECUTED, Price: 3817.45, Cost: 3817.45, Comm 0.00
2006-03-28T23:59:59+00:00, SELL CREATE, 3811.45
2006-03-28T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-29T23:59:59+00:00, SELL EXECUTED, Price: 3811.85, Cost: 3811.85, Comm 0.00
2006-03-30T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3913.36, valid: 2006-04-19, pricelimit: 3893.98
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-19T23:59:59+00:00, BUY EXPIRED
...
...
2006-12-11T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 4093.42, valid: 2006-12-31, pricelimit: 4073.15
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-22T23:59:59+00:00, BUY EXECUTED, Price: 4073.15, Cost: 4073.15, Comm 0.00
测试脚本执行
在命令行help
中详细说明:
$ ./order-execution-samples.py --help
usage: order-execution-samples.py [-h] [--infile INFILE]
[--csvformat {bt,visualchart,sierrachart,yahoo,yahoo_unreversed}]
[--fromdate FROMDATE] [--todate TODATE]
[--plot] [--plotstyle {bar,line,candle}]
[--numfigs NUMFIGS] [--smaperiod SMAPERIOD]
[--exectype EXECTYPE] [--valid VALID]
[--perc1 PERC1] [--perc2 PERC2]
Showcase for Order Execution Types
optional arguments:
-h, --help show this help message and exit
--infile INFILE, -i INFILE
File to be read in
--csvformat {bt,visualchart,sierrachart,yahoo,yahoo_unreversed},
-c {bt,visualchart,sierrachart,yahoo,yahoo_unreversed}
CSV Format
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format
--todate TODATE, -t TODATE
Ending date in YYYY-MM-DD format
--plot, -p Plot the read data
--plotstyle {bar,line,candle}, -ps {bar,line,candle}
Plot the read data
--numfigs NUMFIGS, -n NUMFIGS
Plot using n figures
--smaperiod SMAPERIOD, -s SMAPERIOD
Simple Moving Average Period
--exectype EXECTYPE, -e EXECTYPE
Execution Type: Market (default), Close, Limit,
Stop, StopLimit
--valid VALID, -v VALID
Validity for Limit sample: default 0 days
--perc1 PERC1, -p1 PERC1
% distance from close price at order creation time for
the limit/trigger price in Limit/Stop orders
--perc2 PERC2, -p2 PERC2
% distance from close price at order creation time for
the limit price in StopLimit orders
完整代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import os.path
import time
import sys
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
class OrderExecutionStrategy(bt.Strategy):
params = (
('smaperiod', 15),
('exectype', 'Market'),
('perc1', 3),
('perc2', 1),
('valid', 4),
)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.data.datetime[0]
if isinstance(dt, float):
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
self.order = order
return
if order.status in [order.Expired]:
self.log('BUY EXPIRED')
elif order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# Sentinel to None: new orders allowed
self.order = None
def __init__(self):
# SimpleMovingAverage on main data
# Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
sma = btind.SMA(period=self.p.smaperiod)
# CrossOver (1: up, -1: down) close / sma
self.buysell = btind.CrossOver(self.data.close, sma, plot=True)
# Sentinel to None: new ordersa allowed
self.order = None
def next(self):
if self.order:
# An order is pending ... nothing can be done
return
# Check if we are in the market
if self.position:
# In the maerket - check if it's the time to sell
if self.buysell < 0:
self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell()
elif self.buysell > 0:
if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None
# Not in the market and signal to buy
if self.p.exectype == 'Market':
self.buy(exectype=bt.Order.Market) # default if not given
self.log('BUY CREATE, exectype Market, price %.2f' %
self.data.close[0])
elif self.p.exectype == 'Close':
self.buy(exectype=bt.Order.Close)
self.log('BUY CREATE, exectype Close, price %.2f' %
self.data.close[0])
elif self.p.exectype == 'Limit':
price = self.data.close * (1.0 - self.p.perc1 / 100.0)
self.buy(exectype=bt.Order.Limit, price=price, valid=valid)
if self.p.valid:
txt = 'BUY CREATE, exectype Limit, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Limit, price %.2f'
self.log(txt % price)
elif self.p.exectype == 'Stop':
price = self.data.close * (1.0 + self.p.perc1 / 100.0)
self.buy(exectype=bt.Order.Stop, price=price, valid=valid)
if self.p.valid:
txt = 'BUY CREATE, exectype Stop, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Stop, price %.2f'
self.log(txt % price)
elif self.p.exectype == 'StopLimit':
price = self.data.close * (1.0 + self.p.perc1 / 100.0)
plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)
self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,
plimit=plimit)
if self.p.valid:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' valid: %s, pricelimit: %.2f')
self.log(txt % (price, valid.strftime('%Y-%m-%d'), plimit))
else:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' pricelimit: %.2f')
self.log(txt % (price, plimit))
def runstrat():
args = parse_args()
cerebro = bt.Cerebro()
data = getdata(args)
cerebro.adddata(data)
cerebro.addstrategy(
OrderExecutionStrategy,
exectype=args.exectype,
perc1=args.perc1,
perc2=args.perc2,
valid=args.valid,
smaperiod=args.smaperiod
)
cerebro.run()
if args.plot:
cerebro.plot(numfigs=args.numfigs, style=args.plotstyle)
def getdata(args):
dataformat = dict(
bt=btfeeds.BacktraderCSVData,
visualchart=btfeeds.VChartCSVData,
sierrachart=btfeeds.SierraChartCSVData,
yahoo=btfeeds.YahooFinanceCSVData,
yahoo_unreversed=btfeeds.YahooFinanceCSVData
)
dfkwargs = dict()
if args.csvformat == 'yahoo_unreversed':
dfkwargs['reverse'] = True
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dfkwargs['fromdate'] = fromdate
if args.todate:
fromdate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dfkwargs['todate'] = todate
dfkwargs['dataname'] = args.infile
dfcls = dataformat[args.csvformat]
return dfcls(**dfkwargs)
def parse_args():
parser = argparse.ArgumentParser(
description='Showcase for Order Execution Types')
parser.add_argument('--infile', '-i', required=False,
default='../../datas/2006-day-001.txt',
help='File to be read in')
parser.add_argument('--csvformat', '-c', required=False, default='bt',
choices=['bt', 'visualchart', 'sierrachart',
'yahoo', 'yahoo_unreversed'],
help='CSV Format')
parser.add_argument('--fromdate', '-f', required=False, default=None,
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t', required=False, default=None,
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--plot', '-p', action='store_false', required=False,
help='Plot the read data')
parser.add_argument('--plotstyle', '-ps', required=False, default='bar',
choices=['bar', 'line', 'candle'],
help='Plot the read data')
parser.add_argument('--numfigs', '-n', required=False, default=1,
help='Plot using n figures')
parser.add_argument('--smaperiod', '-s', required=False, default=15,
help='Simple Moving Average Period')
parser.add_argument('--exectype', '-e', required=False, default='Market',
help=('Execution Type: Market (default), Close, Limit,'
' Stop, StopLimit'))
parser.add_argument('--valid', '-v', required=False, default=0, type=int,
help='Validity for Limit sample: default 0 days')
parser.add_argument('--perc1', '-p1', required=False, default=0.0,
type=float,
help=('%% distance from close price at order creation'
' time for the limit/trigger price in Limit/Stop'
' orders'))
parser.add_argument('--perc2', '-p2', required=False, default=0.0,
type=float,
help=('%% distance from close price at order creation'
' time for the limit price in StopLimit orders'))
return parser.parse_args()
if __name__ == '__main__':
runstrat()
目标订单
直到版本1.8.10.96
,通过Strategy方法buy
和sell
在backtrader上实现了智能的投注。这一切都是关于在方程中添加一个Sizer
,负责赌注的大小。
Sizer无法决定操作是买入还是卖出。这意味着需要一个新概念,其中添加了一个小智能层来做出这样的决定。
这就是Strategy中的order_target_xxx
方法家族发挥作用的地方。受到zipline
中方法的启发,这些方法提供了简单指定最终target的机会,无论目标是什么:
-
size
-> 特定资产组合中的股票、合约数量 -
value
-> 组合中资产的货币单位价值 -
percent
-> 百分比(来自当前组合)资产在当前组合中的价值
注意
方法的参考可以在 Strategy 中找到。总结是,这些方法使用与buy
和sell
相同的signature,只是参数size
被参数target
替换
在这种情况下,关键在于指定最终的target,而方法决定操作是买入还是卖出。这个逻辑也适用于 3 种方法。让我们从order_target_size
开始
-
如果target大于持仓量,则发出买入指令,差额为
target - position_size
示例:
-
Pos:
0
, target:7
-> 买入(size=7 - 0) -> 买入(size=7) -
Pos:
3
, target:7
-> 买入(size=7 - 3) -> 买入(size=4) -
Pos:
-3
, target:7
-> 买入(size=7 - -3) -> 买入(size=10) -
Pos:
-3
, target:-2
-> 买入(size=-2 - -3) -> 买入(size=1)
-
-
如果target小于持仓量,则发出卖出指令,差额为
position_size - target
示例:
-
Pos:
0
, target:-7
-> 卖出(size=0 - -7) -> 卖出(size=7) -
Pos:
3
, target:-7
-> 卖出(size=3 - -7) -> 卖出(size=10) -
Pos:
-3
, target:-7
-> sell(size=-3 - -7) -> sell(size=4) -
Pos:
3
, target:2
-> sell(size=3 - 2) -> sell(size=1)
-
使用order_target_value
来设定目标值时,会考虑组合中资产的当前value和position size,以决定最终的基础操作。推理如下:
- 如果position size为负(空头)且target value必须大于当前值,这意味着:卖出更多
因此,逻辑如下:
-
如果
target > value
且size >=0
-> 买入 -
如果
target > value
且size < 0
-> 卖出 -
如果
target < value
且size >= 0
-> 卖出 -
如果
target < value
且size < 0
-> 买入
order_target_percent
的逻辑与order_target_value
相同。该方法仅考虑组合的当前总价值,以确定资产的目标值。
示例
backtrader尝试为每个新功能提供一个示例,这不例外。没有花里胡哨,只是为了测试结果是否符合预期。这个示例在order_target
目录中。
示例中的逻辑相当愚蠢,只是用于测试:
-
在奇数月(一月,三月,...),使用日作为目标(对于
order_target_value
,将日乘以1000
)这模仿了一个递增的目标。
-
在偶数月(二月,四月,...)使用
31 - day
作为目标。这模仿了一个递减的目标。
order_target_size
让我们看看在一月和二月会发生什么。
$ ./order_target.py --target-size -- plot
0001 - 2005-01-03 - Position Size: 00 - Value 1000000.00
0001 - 2005-01-03 - Order Target Size: 03
0002 - 2005-01-04 - Position Size: 03 - Value 999994.39
0002 - 2005-01-04 - Order Target Size: 04
0003 - 2005-01-05 - Position Size: 04 - Value 999992.48
0003 - 2005-01-05 - Order Target Size: 05
0004 - 2005-01-06 - Position Size: 05 - Value 999988.79
...
0020 - 2005-01-31 - Position Size: 28 - Value 999968.70
0020 - 2005-01-31 - Order Target Size: 31
0021 - 2005-02-01 - Position Size: 31 - Value 999954.68
0021 - 2005-02-01 - Order Target Size: 30
0022 - 2005-02-02 - Position Size: 30 - Value 999979.65
0022 - 2005-02-02 - Order Target Size: 29
0023 - 2005-02-03 - Position Size: 29 - Value 999966.33
0023 - 2005-02-03 - Order Target Size: 28
...
在一月,目标从第一交易日的3
开始增加。初始时,仓位大小从0
到3
,然后以1
的增量递增。
完成一月时,最后一个order_target为31
,当进入二月的第一天时,报告了该仓位大小,新的目标方向要求为30
,并随着仓位递减。
order_target_value
类似的行为可预期来自目标值。
$ ./order_target.py --target-value --plot
0001 - 2005-01-03 - Position Size: 00 - Value 1000000.00
0001 - 2005-01-03 - data value 0.00
0001 - 2005-01-03 - Order Target Value: 3000.00
0002 - 2005-01-04 - Position Size: 78 - Value 999854.14
0002 - 2005-01-04 - data value 2853.24
0002 - 2005-01-04 - Order Target Value: 4000.00
0003 - 2005-01-05 - Position Size: 109 - Value 999801.68
0003 - 2005-01-05 - data value 3938.17
0003 - 2005-01-05 - Order Target Value: 5000.00
0004 - 2005-01-06 - Position Size: 138 - Value 999699.57
...
0020 - 2005-01-31 - Position Size: 808 - Value 999206.37
0020 - 2005-01-31 - data value 28449.68
0020 - 2005-01-31 - Order Target Value: 31000.00
0021 - 2005-02-01 - Position Size: 880 - Value 998807.33
0021 - 2005-02-01 - data value 30580.00
0021 - 2005-02-01 - Order Target Value: 30000.00
0022 - 2005-02-02 - Position Size: 864 - Value 999510.21
0022 - 2005-02-02 - data value 30706.56
0022 - 2005-02-02 - Order Target Value: 29000.00
0023 - 2005-02-03 - Position Size: 816 - Value 999130.05
0023 - 2005-02-03 - data value 28633.44
0023 - 2005-02-03 - Order Target Value: 28000.00
...
有一行额外的信息告诉实际数据值(在投资组合中)是什么。这有助于确定是否已达到目标值。
初始目标是3000.0
,报告的初始值是2853.24
。这里的问题是这是否足够接近。答案是是的。
-
示例在每日 K 线图结束时使用
Market
订单和最后可用价格来计算满足目标价值的目标大小。 -
执行随后使用下一天的
open
价格,这不太可能是前一天的close
。
以任何其他方式做都意味着一个人在欺骗自己。
下一个目标值和最终值要接近得多:4000
和3938.17
。
当转换为二月时,目标值从31000
减少到30000
和29000
。数据值也是如此,从30580.00
到30706.56
,然后到28633.44
。等等:
-
30580
->30706.56
是一个正变化。确实。在这种情况下,计算出的目标值的大小遇到了一个将值提高到
30706.56
的开盘价。
如何避免这种影响:
-
示例在订单中使用
Market
类型的执行,这种效果无法避免。 -
方法
order_target_xxx
允许指定执行类型和价格。一个可以指定
Limit
作为执行订单的方式,并让价格成为收盘价格(如果没有提供其他内容,则由该方法选择)或者甚至提供具体定价。
order_target_percent
在这种情况下,它只是当前投资组合价值的百分比。
$ ./order_target.py --target-percent --plot
0001 - 2005-01-03 - Position Size: 00 - Value 1000000.00
0001 - 2005-01-03 - data percent 0.00
0001 - 2005-01-03 - Order Target Percent: 0.03
0002 - 2005-01-04 - Position Size: 785 - Value 998532.05
0002 - 2005-01-04 - data percent 0.03
0002 - 2005-01-04 - Order Target Percent: 0.04
0003 - 2005-01-05 - Position Size: 1091 - Value 998007.44
0003 - 2005-01-05 - data percent 0.04
0003 - 2005-01-05 - Order Target Percent: 0.05
0004 - 2005-01-06 - Position Size: 1381 - Value 996985.64
...
0020 - 2005-01-31 - Position Size: 7985 - Value 991966.28
0020 - 2005-01-31 - data percent 0.28
0020 - 2005-01-31 - Order Target Percent: 0.31
0021 - 2005-02-01 - Position Size: 8733 - Value 988008.94
0021 - 2005-02-01 - data percent 0.31
0021 - 2005-02-01 - Order Target Percent: 0.30
0022 - 2005-02-02 - Position Size: 8530 - Value 995005.45
0022 - 2005-02-02 - data percent 0.30
0022 - 2005-02-02 - Order Target Percent: 0.29
0023 - 2005-02-03 - Position Size: 8120 - Value 991240.75
0023 - 2005-02-03 - data percent 0.29
0023 - 2005-02-03 - Order Target Percent: 0.28
...
数据信息已更改,以查看投资组合中数据所代表的%
。
示例用法
$ ./order_target.py --help
usage: order_target.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--cash CASH]
(--target-size | --target-value | --target-percent)
[--plot [kwargs]]
Sample for Order Target
optional arguments:
-h, --help show this help message and exit
--data DATA Specific data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--cash CASH Ending date in YYYY-MM-DD format (default: 1000000)
--target-size Use order_target_size (default: False)
--target-value Use order_target_value (default: False)
--target-percent Use order_target_percent (default: False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
from datetime import datetime
import backtrader as bt
class TheStrategy(bt.Strategy):
'''
This strategy is loosely based on some of the examples from the Van
K. Tharp book: *Trade Your Way To Financial Freedom*. The logic:
- Enter the market if:
- The MACD.macd line crosses the MACD.signal line to the upside
- The Simple Moving Average has a negative direction in the last x
periods (actual value below value x periods ago)
- Set a stop price x times the ATR value away from the close
- If in the market:
- Check if the current close has gone below the stop price. If yes,
exit.
- If not, update the stop price if the new stop price would be higher
than the current
'''
params = (
('use_target_size', False),
('use_target_value', False),
('use_target_percent', False),
)
def notify_order(self, order):
if order.status == order.Completed:
pass
if not order.alive():
self.order = None # indicate no order is pending
def start(self):
self.order = None # sentinel to avoid operrations on pending order
def next(self):
dt = self.data.datetime.date()
portfolio_value = self.broker.get_value()
print('%04d - %s - Position Size: %02d - Value %.2f' %
(len(self), dt.isoformat(), self.position.size, portfolio_value))
data_value = self.broker.get_value([self.data])
if self.p.use_target_value:
print('%04d - %s - data value %.2f' %
(len(self), dt.isoformat(), data_value))
elif self.p.use_target_percent:
port_perc = data_value / portfolio_value
print('%04d - %s - data percent %.2f' %
(len(self), dt.isoformat(), port_perc))
if self.order:
return # pending order execution
size = dt.day
if (dt.month % 2) == 0:
size = 31 - size
if self.p.use_target_size:
target = size
print('%04d - %s - Order Target Size: %02d' %
(len(self), dt.isoformat(), size))
self.order = self.order_target_size(target=size)
elif self.p.use_target_value:
value = size * 1000
print('%04d - %s - Order Target Value: %.2f' %
(len(self), dt.isoformat(), value))
self.order = self.order_target_value(target=value)
elif self.p.use_target_percent:
percent = size / 100.0
print('%04d - %s - Order Target Percent: %.2f' %
(len(self), dt.isoformat(), percent))
self.order = self.order_target_percent(target=percent)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
cerebro.broker.setcash(args.cash)
dkwargs = dict()
if args.fromdate is not None:
dkwargs['fromdate'] = datetime.strptime(args.fromdate, '%Y-%m-%d')
if args.todate is not None:
dkwargs['todate'] = datetime.strptime(args.todate, '%Y-%m-%d')
# data
data = bt.feeds.YahooFinanceCSVData(dataname=args.data, **dkwargs)
cerebro.adddata(data)
# strategy
cerebro.addstrategy(TheStrategy,
use_target_size=args.target_size,
use_target_value=args.target_value,
use_target_percent=args.target_percent)
cerebro.run()
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Order Target')
parser.add_argument('--data', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Specific data to be read in')
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--cash', required=False, action='store',
type=float, default=1000000,
help='Ending date in YYYY-MM-DD format')
pgroup = parser.add_mutually_exclusive_group(required=True)
pgroup.add_argument('--target-size', required=False, action='store_true',
help=('Use order_target_size'))
pgroup.add_argument('--target-value', required=False, action='store_true',
help=('Use order_target_value'))
pgroup.add_argument('--target-percent', required=False,
action='store_true',
help=('Use order_target_percent'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()
OCO 订单
原文:
www.backtrader.com/docu/order-creation-execution/oco/oco/
版本1.9.34.116
添加了OCO
(也称为One Cancel Others)到回测工具中。
注意
这仅在回测中实现,尚未为实时经纪人实现
注意
更新至版本1.9.36.116
。交互经纪人支持StopTrail
、StopTrailLimit
和OCO
。
-
OCO
总是将组中的第 1 个订单指定为参数oco
-
StopTrailLimit
: 经纪人模拟和IB
经纪人具有相同的行为。指定:price
作为初始止损触发价格(还要指定trailamount
),然后plimi
作为初始限价。两者之间的差异将确定limitoffset
(限价与止损触发价格之间的距离)
使用模式尽量保持用户友好。因此,如果策略中的逻辑决定发出订单,使用OCO
可以这样做:
def next(self):
...
o1 = self.buy(...)
...
o2 = self.buy(..., oco=o1)
...
o3 = self.buy(..., oco=o1) # or even oco=o2, o2 is already in o1 group
简单。第 1 个订单o1
将成为组长。o2
和o3
通过指定o1
的oco
命名参数成为OCO 组的一部分。请注意代码片段中的注释指出,o3
也可以通过指定o2
(已经是组的一部分)成为组的一部分
组成后将发生以下情况:
- 如果组中的任何订单被执行、取消或到期,其他订单将被取消
下面的示例展示了OCO
概念的运用。一个带有绘图的标准执行:
$ ./oco.py --broker cash=50000 --plot
注意
现金增加到50000
,因为资产达到4000
的值,3 个1
个项目的订单至少需要12000
货币单位(经纪人的默认值为10000
)
使用以下图表。
实际上并没有提供太多信息(这是一个标准的SMA Crossover
策略)。示例执行以下操作:
-
当快速SMA向上穿越慢速SMA时,将发出 3 个订单
-
order1
是一个Limit
订单,将在limdays
天(策略的参数)内到期,限价为close
价格减少的百分比 -
order2
是一个Limit
订单,具有更长的到期时间和更低的限价。 -
order3
是一个Limit
订单,进一步降低了限价
因此,order2
和order3
的执行不会发生,因为:
order1
将首先执行,这应该触发其他订单的取消
或
order1
将到期,这将触发其他订单的取消
系统保留 3 个订单的ref
标识符,并且只有在notify_order
中看到这三个ref
标识符为Completed
、Cancelled
、Margin
或Expired
时才会发出新的buy
订单
退出只需在持有头寸一段时间后进行。
为了尝试跟踪实际执行情况,会产生文本输出。其中一些内容:
2005-01-28: Oref 1 / Buy at 2941.11055
2005-01-28: Oref 2 / Buy at 2896.7722
2005-01-28: Oref 3 / Buy at 2822.87495
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Buy / Status Submitted
2005-01-31: Order ref: 3 / Type Buy / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Buy / Status Accepted
2005-01-31: Order ref: 3 / Type Buy / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
2005-02-01: Order ref: 3 / Type Buy / Status Canceled
2005-02-01: Order ref: 2 / Type Buy / Status Canceled
...
2006-06-23: Oref 49 / Buy at 3532.39925
2006-06-23: Oref 50 / Buy at 3479.147
2006-06-23: Oref 51 / Buy at 3390.39325
2006-06-26: Order ref: 49 / Type Buy / Status Submitted
2006-06-26: Order ref: 50 / Type Buy / Status Submitted
2006-06-26: Order ref: 51 / Type Buy / Status Submitted
2006-06-26: Order ref: 49 / Type Buy / Status Accepted
2006-06-26: Order ref: 50 / Type Buy / Status Accepted
2006-06-26: Order ref: 51 / Type Buy / Status Accepted
2006-06-26: Order ref: 49 / Type Buy / Status Completed
2006-06-26: Order ref: 51 / Type Buy / Status Canceled
2006-06-26: Order ref: 50 / Type Buy / Status Canceled
...
2006-11-10: Order ref: 61 / Type Buy / Status Canceled
2006-12-11: Oref 63 / Buy at 4032.62555
2006-12-11: Oref 64 / Buy at 3971.8322
2006-12-11: Oref 65 / Buy at 3870.50995
2006-12-12: Order ref: 63 / Type Buy / Status Submitted
2006-12-12: Order ref: 64 / Type Buy / Status Submitted
2006-12-12: Order ref: 65 / Type Buy / Status Submitted
2006-12-12: Order ref: 63 / Type Buy / Status Accepted
2006-12-12: Order ref: 64 / Type Buy / Status Accepted
2006-12-12: Order ref: 65 / Type Buy / Status Accepted
2006-12-15: Order ref: 63 / Type Buy / Status Expired
2006-12-15: Order ref: 65 / Type Buy / Status Canceled
2006-12-15: Order ref: 64 / Type Buy / Status Canceled
出现了以下情况:
-
第 1 个订单批次被下发。订单 1 到期,而 2 和 3 被取消。正如预期的那样。
-
几个月后,又下发了另一批 3 个订单。在这种情况下,订单 49 被标记为
已完成
,而 50 和 51 则立即被取消。 -
最后一个批次与第 1 个批次完全相同
现在让我们来检查一下没有OCO
时的行为:
$ ./oco.py --strat do_oco=False --broker cash=50000
2005-01-28: Oref 1 / Buy at 2941.11055
2005-01-28: Oref 2 / Buy at 2896.7722
2005-01-28: Oref 3 / Buy at 2822.87495
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Buy / Status Submitted
2005-01-31: Order ref: 3 / Type Buy / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Buy / Status Accepted
2005-01-31: Order ref: 3 / Type Buy / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
这就是全部,其实并不多(没有顺序执行,也不需要太多图表)
-
订单批次被下发
-
订单 1 到期了,但是因为策略已经设置了参数
do_oco=False
,订单 2 和 3 没有被纳入OCO
组 -
因此,订单 2 和 3 并没有被取消,而且由于默认到期时间差为
1000
天后,根据样本数据(2 年的数据)它们永远不会到期。 -
系统从未下发第 2 批订单。
使用示例
$ ./oco.py --help
usage: oco.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,
switchp1p2=False, # switch prices of order1 and order2
oco1oco2=False, # False - use order1 as oco for order3, else order2
do_oco=True, # use oco or not
)
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()
def next(self):
if self.orefs:
return # pending orders do nothing
if not self.position:
if self.cross > 0.0: # crossing up
p1 = self.data.close[0] * (1.0 - self.p.limit)
p2 = self.data.close[0] * (1.0 - 2 * 2 * self.p.limit)
p3 = self.data.close[0] * (1.0 - 3 * 3 * self.p.limit)
if self.p.switchp1p2:
p1, p2 = p2, p1
o1 = self.buy(exectype=bt.Order.Limit, price=p1,
valid=datetime.timedelta(self.p.limdays))
print('{}: Oref {} / Buy at {}'.format(
self.datetime.date(), o1.ref, p1))
oco2 = o1 if self.p.do_oco else None
o2 = self.buy(exectype=bt.Order.Limit, price=p2,
valid=datetime.timedelta(self.p.limdays2),
oco=oco2)
print('{}: Oref {} / Buy at {}'.format(
self.datetime.date(), o2.ref, p2))
if self.p.do_oco:
oco3 = o1 if not self.p.oco1oco2 else oco2
else:
oco3 = None
o3 = self.buy(exectype=bt.Order.Limit, price=p3,
valid=datetime.timedelta(self.p.limdays2),
oco=oco3)
print('{}: Oref {} / Buy at {}'.format(
self.datetime.date(), o3.ref, p3))
self.orefs = [o1.ref, o2.ref, o3.ref]
else: # in the market
if (len(self) - self.holdstart) >= self.p.hold:
self.close()
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()
括号订单
原文:
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()
标签:中文,59,BackTrader,self,00,Order,文档,2006,order
From: https://www.cnblogs.com/apachecn/p/18135480