backtrader订单详情


本文来源:码农甲,个人学习笔记,详情见原博主文章

提前小结:


订单创建
通过调用Strategy的buy()、sell()、close()方法来返回订单实例。

订单取消
通过调用Strategy的cancel(order)方法来取消订单

订单通知通过调用Strategy的notify_order(order)方法来通知order的状态。

订单类型: ExecTypes = ['Market', 'Close', 'Limit', 'Stop', 'StopLimit', 'StopTrail',
'StopTrailLimit', 'Historical']

1、Market订单
以下一根K线的开盘价执行订单。

2、Close订单
当下一根K线结束时,以下一根K线的收盘价执行订单。

3、Limit订单

在Limit订单创建时,会设置一个price和valid时间,如果超过valid时间订单仍未满足执行条件,订单就会过期被取消。在valid时间内,订单会按照下面描述的价格匹配规则判断订单是否会成交。

4、Stop订单

在Stop订单创建时,会设置一个price和valid时间,如果超过valid时间订单仍未满足执行条件,订单就会过期被取消。在valid时间内,订单会按照下面描述的价格匹配规则判断订单是否会成交。

5、StopLimit订单

在StopLimit订单创建时,会设置price、plimit和valid时间,如果超过valid时间订单仍未满足执行条件,订单就会过期被取消。在valid时间内,订单会按照下面描述的价格匹配规则判断订单是否会成交。

6、StopTrail订单

给订单设置跟踪量(trail amount)或者跟踪比例(trail percent),用于计算跟踪价差,订单会使用跟踪价差按照下面描述的触发价格(跟踪价格/trail price/stop price/trigger price)计算规则计算更新触发价格,若价格回撤至触发价格,则订单成交。

7、StopTrailLimit订单


在StopLimit订单中,触发价格stopprice和限制价格limitprice均为固定值,而在StopTrailLimit订单中触发价格stopprice和限制价格limitprice都会随着收盘价的变化进行变化。


在backtrader中,Strategy类是用户制定回测策略的核心。
我们可以用Strategy的各个方法来表示它的整个生命周期:孕育期——出生——儿童期——成年期——繁殖期——死亡。
孕育期:init
当实例化Strategy时,__init__方法将被调用。技术指标和一些其他的属性需要在这时候创建。例如:
def __init__(self):
self.sma = btind.SimpleMovingAverage(period=15)
上面的代码创建了一个15日简单移动均线的技术指标。
出生:start
backtrader的核心cerebro会告诉strategy是时候开始行动了。这里默认会创建一个空的start方法。在出生阶段,strategy的创建可能被中断,并抛出StrategySkipError异常。
儿童期:prenext
在孕育期声明的技术指标,有的需要经过某个周期长度才能得到有效数值,这个周期称为最小周期(minimum period)。
例如,在__init__方法中,我们创建了一个15日均线的指标,这个指标就需要经过15根K线才能计算出有效的数值,前14根K线无法得到15日均线的数值。backtrader将在访问到15根K线以后,才会调用prenext方法。prenext方法默认不进行任何操作。
成年期:next
当backtrader经过了minimum period(在上面的例子中,就是访问到第15根K线时),并且有足够的空间来存储将要计算得到的数据时,strategy就开始执行next来进行具体的买卖操作。
这里实际上调用且仅调用一次nextstart方法,来作为从prenext转到next标志。在nextstart的默认实现中,仅仅是调用了一次next。
繁殖期:None
strategy是不会做复制的。但是当我们使用不同的参数来优化策略时,backtrader就会多次实例化strategy,在这种情况下,也可以看作是对strategy进行了复制。
死亡:stop
backtrader告知strategy是时候重置把所有事情安排妥当了。backtrader提供一个默认为空的stop方法。
Strategy也会收到通知,并且在每个next周期内都会被通知一次,这些通知包括:
通过notify_order(order)发出订单状态变化的通知
通过notify_trade(trade)发出交易开始/更新/关闭的通知
通过notify_cashvalue(cash, value)发出代理手中当前现金和资产的通知
通过notify_fund(cash, value, fundvalue, shares)发出代理手中当前现金和资产,以及正在交易的资金和股票的通知
通过notify_store(msg, *args, **kwargs)发出的事件通知(需要单独实现)
在next方法中,Strategy可以通过以下操作来尝试盈利:
使用buy方法,来做多或者减少/关闭空头交易
使用sell方法,来做空或者减少/关闭多头交易
使用close方法,来关闭一个现有的交易
使用cancel方法,来取消一个尚未执行的订单
当调用buy和sell方法后,都会生成订单,并且返回一个order(或者order子类)的实例,每个实例都包含有一个唯一的ref标识符,用于区分不同的order


订单创建
通过调用Strategy的buy()、sell()、close()方法来返回订单实例。

订单取消
通过调用Strategy的cancel(order)方法来取消订单
订单通知
通过调用Strategy的notify_order(order)方法来通知order的状态。
订单可能参数
data(默认值:None)
order所要操作的数据。如果为None,则系统中的第一组数据(第一只股票/品种)将被使用,也就是self.datas[0]、self.data0、self.data,上面3种形式均可以表示系统中的第一组数据。
size(默认值:None)
交易单位,size是一个正数。对于买单,如果size=100,就是买100个单位数量的仓位;对于卖单,如果size=100,就是卖100个单位数量的仓位。
如果size=None,sizer实例就会通过getsizer方法来获取size的值。也就是说,除了使用buy、sell方法来设置单个订单的交易单位大小,还有方法设置全局交易单位的大小(通过Cerebro.addsizer方法来设置),这样就避免了每个order都要设置交易单位。
price(默认值:None)
交易价格。
默认值None适用于Market、Close订单(后面的exectype参数会介绍各类型订单的意义),由市场决定具体的交易价格。
对于Limit、Stop、StopLimit订单,必须显式给price赋值,price值决定了交易的触发点(trigger point)。
对于StopTrail、StopTrailLimit订单,是否显示设置price,将决定不同的交易触发点。(后续文章将详细介绍)
plimit(默认值:None)
只适用于StopLimit订单。在StopLimit订单中,plimit值被设置为隐含的Limit订单price值,而price值被用于触发当前StopLimit订单的Stop条件。(后续文章将详细介绍)
exectype(默认值:None)
可能的取值
Order.Market或者None:Market订单将以下一个可行的价格进行交易,在回测中,就将以下一根K线的开盘价进行交易。
Order.Limit:在给定的价位price或者更好的价位执行的订单。
Order.Stop:当价格突破price时,触发订单成交。
Order.StopLimit:当价格突破price时触发订单(类似于Order.Stop订单),之后以给定的价位plimit或者更好的价位执行订单(相当于以参数plimit为price的Order.Limit订单)。
Order.StopTrail:根据收盘价的变化,动态调整订单的交易价格,以实现利润的保护。
Order.StopTrailLimit:Order.StopTrail和Order.Limit的组合,按照Order.StopTrail条件触发,按照Order.Limit条件成交。
Order.Historical:尚未发现相关说明及应用。
valid(默认值:None)
可能的取值:
None。生成的订单将不会过期,将一直在市场中等待满足条件后执行或者等待被手动取消。
datetime.datetime或者datetime.date的实例。这个日期将被用来创建这样一个订单,如果截止到该给定的日期,该订单仍未满足执行条件,那么这个订单就会过期而取消。
Order.DAY或者timedelta()。生成一个单日订单,有效期为1天,单日未满足执行条件,订单就会过期取消。
numeric value。对应于matplotlib中的日期格式,将被用来创建订单,订单有效期截止日为该日期。
tradeid(默认值:0)
这是backtrader应用的一个内部值,用于跟踪相同资产上的重叠交易。当通知订单状态的变化时,此tradeid被发送回策略。
??kwargs
其他代理的实现可能支持额外的参数。backtrader将kwarg向下传递到创建的order对象。
close方法将首先检查当前的持仓情况,然后根据持仓情况对应地使用buy或者sell方法来清空仓位。如果用户不指定具体的size值,size会被自动计算。如果指定了size值,那么将实现部分close或者reversal订单。



订单通知

为了收到订单通知,需要在用户自定义的Strategy子类中,重写notify_order方法,该方法默认为空。notify_order方法将会被以如下方式使用:

notify_order方法会在Strategy的next方法前被调用
在同一个next周期内,同一个order的通知,可以以相同或者不同的状态在notify_order方法中出现多次。
例如:一个订单先被提交给代理,然后立即被代理接受,且已经满足了执行的条件,它的执行就会在下一个next方法被调用前就已经完成。在这个例子中,至少有以下3个状态通知产生:
Order.Submitted:产生于订单提交给代理
Order.Accepted:产生于代理接受订单,等待被执行
Order.Completed:产生于订单满足了执行条件,被立即执行完成
在实盘交易中,对于订单状态同为Order.Partial的通知可能会出现多次,因为实盘中订单可能会被拆分为多笔交易被执行完成。而在回测中,由于不考虑成交量的匹配,所以不会出现这种情况。

被执行的订单数据被记录在属性order.executed中,它是一个类型为OrderData的实例,包含size和price字段。

订单创建时的数据被记录在order.created中,这些数据在order的整个生命周期中保持不变。

订单状态值
Order.Created:order实例被创建后的状态。当使用buy、sell、close创建订单时,该状态对用户不可见,需要手动创建order的实例,才能获取到该状态。
Order.Submitted:当order实例被发送给broker后的状态。在回测模式下,订单发送是一个即时动作,不需要花费时间。而在实盘中,订单发送将要实际花费时间,代理收到订单后,将订单转发给交易所,随即通知订单已提交。
Order.Accepted:当order处于该状态时,该order已经在系统或者交易所中等待被执行,会根据设置的exectype、size、price、valid等参数确定何时被执行。
Order.Partial:order部分成交时的状态。order.executed属性里记录了已经成交的size及平均价格。order.executed.exbits里包含了分批成交的详细情况完整列表。
Order.Complete:order全部成交的状态(平均成交价格被计算并记录下来)。
Order.Rejected:order被broker拒绝的状态。如果某个参数不被broker所接受,那么order也将不被broker接受。订单被拒的原因将通过Strategy的notify_store方法通知用户。该状态对于回测代理不可见。
Order.Margin:资金不足,订单无法成交,之前接受的订单会被从系统中删除。
Order.Cancelled(或者Order.Canceled): 对用户订单取消要求的确认。用户通过Strategy的cancel方法提交取消订单申请,可能无法成功地取消订单。订单可能已经成交,但是代理尚未反馈成交结果,或者成交通知还没有发送到Strategy。因此需要Order.Canncelled对是否成功取消订单进行确认。
Order.Expired:在该状态下,之前被提交的包含有效时间的订单已经过期,订单被从系统中删除。


order类型
翻看backtrader源代码,在order.py中可以看到ExecTypes列表共有8种不同类型的订单,即按照执行类型,可以分为以下8种:
ExecTypes = ['Market', 'Close', 'Limit', 'Stop', 'StopLimit', 'StopTrail',
'StopTrailLimit', 'Historical']
各种执行类型的简单说明可以参见上文中exectype参数描述部分,后续文章将进行详细描述。
order成交原则
在backtrader中,代理(broker)使用以下2个原则来进行订单交易:
1. 当前数据已经产生,不能被用于执行交易。
例如,Strategy的逻辑如下:
if self.data.close > self.sma: # sma表示简单移动平均线
self.buy()
在这个逻辑中,将收盘价close与移动平均线做比较,如果满足条件,就下买单。但是买单是无法用close进行交易的,因为close是已经产生的数据。只能用下一根K线的某个价位成交,成交价格取决于order类型。
订单的首个可交易的时间在下一根K线上。
2. 成交量在交易中不发挥作用
在实际交易中,成交量是会影响交易的。如果交易员是在进行非流动资产的买卖,或者是在K线的极端点(最高点或者最低点)进行的交易,成交量将影响实盘交易。
但是触及最高点/最低点是很少发生的(A股例外,有涨跌停。。。),所选资产将有足够的流动性来吸收任何常规交易的指令。因此backtrader假设成交量在交易中不发挥作用。


Market订单

执行规则:以下一根K线的开盘价执行订单。
原理:假设在某一时刻满足了交易的条件,这时生成一个Market订单,如果想成交这一订单,就需要下一个将要出现价格点位,这个点位就是下一根K线的开盘价(open)。
对于Market订单,参数price和valid将被忽略。
Market订单以下一根K线的开盘价成交
# 检查是否持仓
if self.position:
# 检查是否达到卖出条件
if self.buysell < 0:
...
if self.p.exectype == 'Market':
self.sell(exectype = bt.Order.Market)
self.log('SELL CREATE, exectype Market, close %.2f' %
self.data.close[0])
...
# 不在场内且出现买入信号
elif self.buysell > 0:
...
if self.p.exectype == 'Market':
self.buy(exectype=bt.Order.Market) # Market是默认的订单类型
self.log('BUY CREATE, exectype Market, close %.2f' %
self.data.close[0])



Close订单
执行规则:当下一根K线结束时,以下一根K线的收盘价执行订单。
原理:以日线回测为例,它包含了已经结束的日K数据,在满足交易条件后,订单将以下一根K线的收盘价(close)立即成交。大多数回测数据属于这种情况,但是也有例外的情况,比如分时数据。在下一个分时K线产生之前,当前的分时K线由于分时时段未结束,数据会保持不断的更新。在这种情况下,只有当前分时时段结束,该分时K线不再变化,才会以收盘价进行交易。
# 检查是否持仓
if self.position:
# 检查是否达到卖出条件
if self.buysell < 0:
...
elif self.p.exectype == 'Close':
self.sell(exectype = bt.Order.Close)
self.log('SELL CREATE, exectype Close, close %.2f' %
self.data.close[0])
...

# 不在场内且出现买入信号
elif self.buysell > 0:
...
elif self.p.exectype == 'Close':
self.buy(exectype=bt.Order.Close)
self.log('BUY CREATE, exectype Close, close %.2f' %
self.data.close[0])



Limit订单

执行规则
在Limit订单创建时,会设置一个price和valid时间,如果超过valid时间订单仍未满足执行条件,订单就会过期被取消。在valid时间内,订单会按照下面描述的价格匹配规则判断订单是否会成交。
价格匹配
Limit订单使用K线4个价格点(Open/High/Low/Close)来推断是否有比price更优的成交价格。
对于买单
如果某根K线的open低于设定的price,则以open值成交。订单在这根K线的开始阶段就被执行完成。
如果某根K线的open高于设定的price,但是low低于price,也就是price在这根K线的范围内,那么订单将以price值成交。
对于卖单
如果某根K线的open值高于设定的price,则以open值成交。订单在这根K线的开始阶段就被执行完成。
如果某根K线的open值低于设定的price,但是high高于price,也就是price在这根K线的范围内,那么订单将以price值成交。
# 检查是否持仓
if self.position:
# 检查是否达到卖出条件
if self.buysell < 0:
if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None
...
elif self.p.exectype == 'Limit':
price = self.data.close * (1.0 + self.p.perc1 / 100.0)
self.sell(exectype=bt.Order.Limit, price=price, valid=valid)
if self.p.valid:
txt = 'SELL CREATE, exectype Limit, close %.2f, price %.2f, valid: %s'
self.log(txt % (self.data.close[0], price, valid.strftime('%Y-%m-%d')))
else:
txt = 'SELL CREATE, exectype Limit, close %.2f, price %.2f'
self.log(txt % (self.data.close[0], price))
...

# 不在场内且出现买入信号
elif self.buysell > 0:
if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None
...
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, close %.2f, price %.2f, valid: %s'
self.log(txt % (self.data.close[0], price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Limit, close %.2f, price %.2f'
self.log(txt % (self.data.close[0], price))

为了说明Limit订单可能出现的所有成交情况,在代码中使用p.perc1 = 0.5,来控制价格回撤的比例,使用p.valid = 4,来控制订单的有效期。
买单的3种情况:
. 过期取消. 以开盘价成交.以设定的price成交
卖单的3种情况:
. 过期取消. 以开盘价成交. 以设定的price成交



Stop订单
执行规则
在Stop订单创建时,会设置一个price和valid时间,如果超过valid时间订单仍未满足执行条件,订单就会过期被取消。在valid时间内,订单会按照下面描述的价格匹配规则判断订单是否会成交。
价格匹配
Stop订单使用K线4个价格点(Open/High/Low/Close)来判断是否有效突破设定的price值。
对于买单
如果某根K线的open高于设定的price,则立刻以open值成交。对于空头来说,若已持有做空的仓位,价格较设定的price持续走高,这时要及时平仓止损(stop a loss),因此这类订单成为Stop订单。
如果某根K线的open低于设定的price,但是high高于price,也就是price在这根K线的范围内,那么订单将以price值成交。
对于卖单
如果某根K线的open值低于设定的price,则以open值成交。
如果某根K线的open值高于设定的price,但是low低于price,也就是price在这根K线的范围内,那么订单将以price值成交。



StopLimit订单创建

执行规则

在StopLimit订单创建时,会设置price、plimit和valid时间,如果超过valid时间订单仍未满足执行条件,订单就会过期被取消。在valid时间内,订单会按照下面描述的价格匹配规则判断订单是否会成交。

价格匹配

StopLimit订单使用K线4个价格点(Open/High/Low/Close)以及price和plimit,来判断订单是否会成交。

首先以price作为触发价格,使用Stop订单价格匹配规则判断订单是否会被触发,参阅笔记(29)。

对于已触发订单,以plimit作为限制价格,使用Limit订单价格匹配规则判断订单是否会成交,参阅笔记(28)。

StopTrail订单

执行规则

给订单设置跟踪量(trail amount)或者跟踪比例(trail percent),用于计算跟踪价差,订单会使用跟踪价差按照下面描述的触发价格(跟踪价格/trail price/stop price/trigger price)计算规则计算更新触发价格,若价格回撤至触发价格,则订单成交。

触发价格计算规则

假设已经通过buy方法建仓,那么对于使用sell方法的平仓StopTrail订单,将会按照下面的规则计算跟踪价格:

指定price值,如果没有指定,则以最新的收盘价close作为price值

根据trailamount或trailpercent计算触发价格

如果指定了trailamount,触发价格stopprice = price - trailamount

如果指定了trailpercent,触发价格stopprice = price * (1 - trailpercent)

如果同时制定了trailamount和trailpercent,则按照上面给定trailamount的情况计算触发价格

检查在下一根K线是否达到触发价格

如果达到触发价格,那么订单将按照Stop订单规则成交(官网上描述订单会按照Market订单规则成交,经过实验和查看源代码,应该是按照Stop订单规则成交)

如果未达到触发价格,那么触发价格将会使用新的收盘价close结合trailamount及trailpercent,按照上面的计算方式计算出一个新的触发价格newstopprice

如果新的触发价格高于原触发价格(newstopprice > stopprice),那么就更新触发价格(stopprice = newstopprice)

如果新的触发价格低于或等于原触发价格(newstopprice <= stopprice),那么触发价格保持不变

也就是说,触发价格随收盘价的上涨而上涨,如果收盘价不变或下跌,触发价格则保持不变,由此来实现保护利润的目的。

类似地,假设已经通过sell方法建仓,那么对于使用buy方法的平仓StopTrail订单,成交规则与上述描述的过程相反,即触发价格随收盘价下跌而更新


StopTrailLimit订单

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

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

指定参数price值

1. 订单逻辑

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

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

2. 初始化

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

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

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

3. 价格更新与订单执行

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

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

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

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




相关