本文将介绍Backtrader的交易系统,包括Order、Broker、Trade和Sizer等和交易相关关键类。
Order(订单)
这个有翻译为订单,也有翻译为委托单的,后续统一为订单。
如之前文章所述,Cerebro是Backtrader的关键控制中心,是大脑。而Strategy是大脑的神经,基于数据和分析做出最终的决策,那么这个决策如何由系统的其他部件去成交呢?订单就承担这样的责任,将Strategy的做出的决策转换为由券商(Broker)执行操作的消息,通过如下三个方式完成:
- 创建
这个在咱们之前的代码中可以看出,通过Strategy的buy,sell和close,返回的是一个Order实例。
- 取消
可以通过Strategy的Cancel函数取消,参数必须指定操作的Order
另外,Order也可以反向给使用者Strategy回馈信息,通过通知的方式告知订单的成交情况:
- 通知
使用Strategy的notify_order函数返回订单实例。
以上咱们之前的示例中均有涉及,下面重点从代码的角度看看咱们是如何使用Order。
Order的创建
在介绍Order操作之前,咱们先看看Order类:
class Order(OrderBase):
class OrderBase(with_metaclass(MetaParams, object)):
从代码可以看出Order类继承自OrderBase,而OrderBase直接继承了所有类的最顶层object和元类MetaParams。从继承关系可以看出,Order是一个极普通的类,没有复杂的继承关系,咱们就不画图了。另外,Order也继承了MetaParams,也就是Order的创建会受到元类的控制,MetaParams之前咱们讲过,主要是针对参数的处理。
先看订单的创建,有多单和空单以及对应的平仓订单,通常,订单是在Strategy的next中根据策略来创建。主要体现就是3个函数:buy、sell和close。比如我们之前示例中,最简单的使用方式如下:
self.order = self.buy()
self.order = self.sell()
顺着这个线索,我们看看Backtrader是如何处理的。
def buy(self, data=None,
size=None, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
parent=None, transmit=True,
**kwargs):
if isinstance(data, string_types):
data = self.getdatabyname(data)
data = data if data is not None else self.datas[0]
size = size if size is not None else self.getsizing(data, isbuy=True)
if size:
return self.broker.buy(
self, data,
size=abs(size), price=price, plimit=plimit,
exectype=exectype, valid=valid, tradeid=tradeid, oco=oco,
trailamount=trailamount, trailpercent=trailpercent,
parent=parent, transmit=transmit,
**kwargs)
return None
首先看函数的关键参数,咱们之前在讲解Strategy的时候详细介绍过,不过为了方便理解Order,咱们还是在这里再提供下:
| 参数 | 缺省值 | 含义 |
|---|---|---|
| data | None | 指定本次操作归属的data。每个data记录的是每个标的(股票、期货等等)的数据(open/close…),买卖操作就是基于这些数据。在多个资产(或者证券,包括股票期货等等)的情况下,你可能需要针对不同的数据创建订单。缺省情况就是针对第一个数据(data0)。 |
| size | None | 本单买卖的数量。比如说股票,本次你要买卖多少股。有些地方可能有最小限制,比如国内最小一手100股。这个可以通过addsizer的stake指定。size也是很重要的策略,这一单下多少?还是很有学问,后面我们还要单独研讨。 |
| price | None | 指定价格。这个参数在市价订单单(Market,通常是下一个开市价格)或者收市订单(close价格)的时候,不需要设置(也就是None)。因为价格由市场来决定,在Backtrader中使用的开市订单 对于限价委托(Limit)单、止损订单(Stop)和止损限价订单(StopLimit),这个price就是订单的触发价格。几种单子的情况下文还要详细描述。 |
| plimit | None | 止损限价。这个只有止损限价订单的有效。因为这种类型的订单需要两个价格,具体参见下文描述。 |
| exectype | None | 订单成交类型: None:这个就是市价委托,在backtrader中,采取的下一个bar的开市(open)价格创建订单。 Close:采取下一个bar的收盘价(close)创建订单。 Limit:限价订单。这种在向broker发出买卖某种股票的指令时,对买卖的价格作出限定,对于多单(买),限定一个最高价,只允许broker按其规定的最高价或低于最高价的价格成交,对于空单(卖),限定一个最低价。限价委托的最大特点是,股票的买卖可按照投资人希望的价格或者更好的价格成交,有利于投资人实现预期投资计划。 Stop: 止损订单。对于多单:低于指定价格卖出,防止亏损扩大。对于空单,高于指定价格卖出。这个价格采用的是市价(也就是下一个开市价open),也成为止损市价订单。还有一种止盈订单,和上述策略相反 StopLimit:止损限价订单,就是以限价委托的止损单。止损限价指令避免了止损指令成交价格不确定的不足,在止损价委托中,投资者要注明两个价格:止损价(对应参数price)和限价(对应参数plimit),一旦市场价格达到或超过止损价格,止损限价委托自动形成一个限价委托。 国内后两种券商都不支持,据说期货支持,没玩过。不过现在很多券商会提供一些条件单功能,基本上也可以达成相同的效果。因此,我们在做好策略回测之后,对于验证好的策略,可以通过券商的条件单设置自动完成交易。 还有追踪止损(StopTrail)、追踪踪止损限价(StopTrailLimit)等,订单的成交方式是策略的重要手段,后面专门讨论。 |
| valid | None | 有效期。有如下取值: None:无有限期,这种情况下,改订单一致存在直到订单满足条件被成交或者被取消。现实中,通常会有时间限制,但是我们这里还是当做无期限。 datetime.datetime 或者datetime.date 实例:也就是指定时间或者日期。也就是订单截止时间。 Order.DAY 或者0 或者 timedelta():也就是指定订单的持续时间。 数值:使用数值指定的截止时间,这个主要用于matplotlib(Backtrader用于画图)的时间编码方式。 |
| tradeid | 0 | 这是一个内部标识。如果多个交易(trade)使用的相同的资产,那么通过整个标识区分不同的交易。在后续通知的处理中,tradeid会返回给Strategy进行区分处理 |
| **kwargs | 还要一些broker的实现会支持更多的参数,那么通过**kwargs传递。 |
此外,还有几个参数用于一些特殊的订单,后续专门说明。
回到代码,Strategy中执行buy函数,其实就是调用broker的buy函数。策略的broker来自哪里呢?可以回头看看Strategy的代码解读,来自Cerebro。
那么在broker函数中会做啥呢?
def buy(self, owner, data,
size, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
parent=None, transmit=True,
histnotify=False, _checksubmit=True,
**kwargs):
order = BuyOrder(owner=owner, data=data,
size=size, price=price, pricelimit=plimit,
exectype=exectype, valid=valid, tradeid=tradeid,
trailamount=trailamount, trailpercent=trailpercent,
parent=parent, transmit=transmit,
histnotify=histnotify)
order.addinfo(**kwargs)
self._ocoize(order, oco)
return self.submit(order, check=_checksubmit)
看第9行,就是创建了一个BuyOrder(就是Order的子类),这里会实例化和初始化一个Order实例,并返回。这个代码后续的处理咱们在Broker部分再讲。至此,一个Order就创建成功了。
对于sell也类似,也就是在broker中实例化和初始化一个SellOrder。
close就是平仓操作,什么是平仓?翻译成专业术语就是执行和现有持仓未平仓头寸完全相反的证券交易,也就是关闭证券的多头头寸需要卖出,而关闭证券的空头头寸则需要买回。
def close(self, data=None, size=None, **kwargs):
if isinstance(data, string_types):
data = self.getdatabyname(data)
elif data is None:
data = self.data
possize = self.getposition(data, self.broker).size
size = abs(size if size is not None else possize)
if possize > 0:
return self.sell(data=data, size=size, **kwargs)
elif possize < 0:
return self.buy(data=data, size=size, **kwargs)
return None
关键点在于:
- 可以指定平仓对应的数据,没有指定,就是缺省第一个数据。
- 然后获取该数据的持仓情况。如果是多头持仓,就创建卖单。如果是空头持仓,就创建卖单。
- 当然还可以指定size,也就是可以用于减仓,不是完全平掉。
至于cancel,就是将订单置为Canceled状态,后续不再成交就行了,具体就不讲了。
创建订单的示例
Backtrader官网提供了一些示例,可供我们代码的时候参考:
# 这是最简单的使用方法,创建买单,使用缺省的规模(size),使用市价成交。
order = self.buy()
# 市价单,指定有效期,这个有效期对于事件单是无效的,因为市价单是下一天成交。
order = self.buy(valid=datetime.datetime.now() + datetime.timedelta(days=3))
# 市价单,指定成交价格,这个价格也会无效,因为市价单使用open价格成交。
order = self.buy(price=self.data.close[0] * 1.02)
# 市价单,手动指定规模。
order = self.buy(size=25)
# 限价单,设定价格和有效期
order = self.buy(exectype=Order.Limit,
price=self.data.close[0] * 1.02,
valid=datetime.datetime.now() + datetime.timedelta(days=3)))
# 止损限价单,设定价格和限定价。
order = self.buy(exectype=Order.StopLimit,
price=self.data.close[0] * 1.02,
plimit=self.data.close[0] * 1.07)
# 所有订单全部取消,是否能取消成功,取决于订单当前的实际状态。
self.broker.cancel(order)
order通知
通常我们会在定制Strategy中定义notify_order函数用于跟踪订单的状态信息,如下示例:
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# 提交和接受委托单不做任何处理
return
# 订单完成,记录。
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'买单成交 成交价格: %.2f, 成交金额: %.2f, 佣金 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('卖单成交 成交价格: %.2f, 成交金额: %.2f, 佣金 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order 取消/金额不足/拒绝')
self.order = None
在该函数中,我们可以或者定制的状态信息,以及成交价格、金额等信息,也可以将相关信息计入日志供后续分析。
如下几点需要关注:
-
notify是在next函数中触发的
-
在一次next中,对相同的订单也可能触发多个通知(相同状态或者不同状态)。
-
订单提交给broker之后,可能会在下个next中之前就会立即成交,这种情况下通常是如下三种状态的订单:
- Order.Submitted(提交):因为订单提交给broker,这个在代码中就是立即完成的。
- Order.Accepted(接受):订单立即被broker接受,在回测系统中,broker没理由拒绝订单。
- Order.Completed(成交):这种情况下,就是订单条件立即满足,所以实时成交。
-
在相同状态下,例如Order.Partial(部分成交),Backtrader也可能产生多个通知。这个在回测系统中不会出现,你就是买1个亿的股票,还是会立即成交。但是在实际交易中可不行。所以说规模是量化交易基金的天敌,你看有些量化基金规模小业绩很好,一上规模就不行了,就是很多交易机会在大资金量下无法成交。
-
交易数据会保存在order.executed中,这个数据在订单的整个生命周期会一直存在。
订单的状态咱们之前也介绍过,这里再说明下:
- 创建(Create):实例化后就是这个状态,这个状态对用户没啥意义,不可见。
- 提交(Submitted):订单发送给broker。在回测的时候,这个就是一个及时的动作(前面broker的buy函数中,立即调用了submit),但是在实际券商中,可能会需要一定的时间才能完成。实际券商可能会收到订单,并且只有在订单被转发到交易所时才通知。
- 接受(Accepted):经纪人已接受订单,并且根据设置的参数(如成交类型、大小、价格和时效)在系统中(或已在交易所中)等待成交。
- 部分成交(Partial):订单只有部分被成交,比如你要买100股,实际成交50股。order.executed包含当前完成的数量以及平均价格。order.executed.exbits则包含了所有部分订单的信息。
- 完全成交(Completed):订单全部成功成交,记录成交的平均价格。
- 取消(Canceled):订单被用户取消。这里需要考虑到,通过Strategy的取消方法取消订单的请求并不保证取消。实际中订单可能已经成交。
- 超时(Expired):订单因为超时被取消。订单如果超过设定的时间,就会被系统取消。
- 金额不足(Margin):订单因为现金金额不足被取消。回测中不会出现。
- 拒绝(Rejected):订单被broker拒绝。券商有可能会因为各种原因拒绝订单,当然回测的时候咱们是模拟系统,就不会有这个拒绝。拒绝原因将通过Strategy的notify_store方法通知。为啥不在notify_order中通知呢?原因是现实中券商 拒绝一个订单可能订单直接相关,也可能与订单无关。所以统一通过notify_store通知。
一些关键order类的说明
在Backtrader中,Order相关的关键类包括Order,OrderData和OrderExecutionBit。
Order类
order类主要用于记录订单相关信息,例如类型、状态,成交方式等。
状态以及成交类型前文已叙及,下面提供下几个关键属性:
- ref: 参考号,订单的唯一编号。
- created: OrderData对象, 用于存储创建时的数据。
- executed: OrderData对象,用于存储成交时的数据。
- info: 在Order中,可以通过addinfo(**kwargs)函数添加自定义的数据,就保存在这个属性里面,保存形式是字典。这样的话,如果进行一些策略的定制开发,可以在订单中传递一些信息。
还有几个方法经常用到:
- isbuy(): 如果是买单,返回True.
- issell(): 如果是卖单,返回True.
- alive(): 如果订单在创建状态(Create)、提交状态(Submitted)和部分成交状态(Partial)和接受状态(Accepted),返回True。
OrderData
这个类在Order中使用,用于记录订单创建和成交时的数据,可以用于对比创建和实际成交的差别,主要包含以下关键属性:
- exbits :记录一组OrderExecutionBits(或者说迭代器),用于记录所有成交(包括部分成交)的信息。
- dt:创建/成交的时间
- size:请求和成交的规模,就是这一单的下注金额。
- price:成交的价格 注意: 如果没有设定price和pricelimite参数, 当前的收盘价或者订单创建时的收盘价会作为参考。
- pricelimit:为 StopLimit类型的订单保存保存 pricelimit价格。
- trailamount:追踪止损单的价格绝对差值。
- trailpercent:追踪止损单的价格百分比差值。
- value:当前订单头寸下的市值。
- comm:订单本次成交的佣金。
- pnl:全称profit and loss,订单的利润/损失,只有订单平仓,一次交易完成才有pnl。
- margin:订单导致的保证金,这个在支持保证金(融资)交易的券商有作用。
- psize:当前开仓的规模。
- pprice:当前开仓的价格。
OrderExecutionBit
这个类在OrderData中使用,用于记录订单的成交情况,这个类并不能指示订单是完全成交还是部分成交,仅仅指示记录信息。
- dt:订单成交时间。
- size:本次成交的规模,可能只有部分成交。
- price:成交价格
- closed:已成交部分有多少是关闭现有持仓。
- opened:已成交部分有多少是新开持仓。这个怎么理解?比如你有10股股票,创建个卖单卖掉11股,其中10股是关闭现有持仓,1股是创建新的空头持仓。
- openedvalue:新开仓头寸的市值。
- closedvalue:已关闭头寸的市值。
- closedcomm:已关闭头寸的佣金。
- openedcomm:新开仓头寸的佣金。
- value:整个头寸的市值。
- comm:整个头寸的佣金。
- pnl:订单的利润/损失。
- psize:新开仓的规模。
- pprice:新开仓价格。
目标订单
Backtrader还提供了一种智能化的下单方式:目标订单。咱们前面所述的buy,sell和close都需要指定size(订单的规模,例如1手100股),这个通过Sizer类来完成。但是我们在进行资产配置的时候,希望对资产组合中的进行调整,设定规模就不太方便,因为你很难知道最后的效果,这时,可以通过设定目标,由系统自动根据目标规模下单。
对于规模的设定,可以有3种方式:
- 目标规模(size):可以设定资产组合中特定资产的规模大小。
- 目标市值(value):可以设定资产组合中特定资产的目标市值。
- 目标百分比(percent):设定资产组合中特定资产的所占比例。
一个组合中,特定资产(例如股票)如何指定?通过data来指定,不同的data对应具体的资产。比如,你要建一个组合,其中包含10个股票,那么需要增加10个股票对应的数据,各种操作针对具体的数据,前面参数表格中已有描述。
在Backtrader中,你设定具体的资产目标规模/市值/目标百分比,系统根据当前持仓情况,和目标进行对比,来决定是buy还是sell,抑或是close。
以目标规模为例,如果目标规模大于当前持仓,就会进行买入操作,规模为:目标规模-当前持仓。例如
- 当前持仓:0,目标规模:7,那么就会buy(size=7)
- 当前持仓:-3(负值表示空头持仓),目标规模:7,那么就会buy(size=10)
- 当前持仓:-3(负值表示空头持仓),目标规模:-2,那么就会buy(size=1)
如果目标规模小于当前持仓,就会进行卖出操作,规模为:当前持仓-目标规模。例如:
- 当前持仓0,目标规模:-7,那么就会sell(size=7)
- 当前持仓3,目标规模:-7,那么就会sell(size=10)
- 当前持仓3,目标规模:2,那么就会sell(size=1)
在Backtrader中,分别提供了如下3个函数(定义在Strategy类中,因此直接在Strategy中调用)完成目标订单的设定:
order_target_size
下面我们通过源代码看看系统是如何实现目标订单的设置。
def order_target_size(self, data=None, target=0, **kwargs):
if isinstance(data, string_types):
data = self.getdatabyname(data)
elif data is None:
data = self.data
possize = self.getposition(data, self.broker).size
if

本文深入解析Backtrader中的交易系统,涵盖Order、Broker、Trade等关键组件的工作原理及应用,介绍了订单创建、取消、通知的过程,以及模拟券商BackBroker的运作机制。
-交易系统代码详解&spm=1001.2101.3001.5002&articleId=123717292&d=1&t=3&u=6a19751b0ef042df927ea2fda1b81a5c)
7956

被折叠的 条评论
为什么被折叠?



