BackTrader 中文文档(九)(1)https://developer.aliyun.com/article/1505288
经纪人
经纪人
参考
类backtrader.brokers.BackBroker()
经纪人模拟器
该模拟支持不同的订单类型,检查提交订单的现金需求与当前现金的情况,跟踪每次cerebro
迭代的现金和价值,并在不同的数据上保持当前位置。
cash根据仪器进行每次迭代调整,如期货
which a price change implies in real brokers the addition/substracion of cash.
支持的订单类型:
Market
:在下一个柱子的第 1^(st)个 tick(即open
价格)执行Close
:用于一日内,在该订单以会话最后一个柱的收盘价执行Limit
:在会话期间看到给定的限价时执行Stop
:如果看到给定的停止价格,则执行Market
订单StopLimit
:如果看到给定的停止价格,则启动Limit
订单
因为经纪人是由Cerebro
实例化的,而且应该(大多数情况下)没有理由替换经纪人,所以实例的参数不受用户控制。要更改此参数,有两种选择:
- 手动创建此类的实例,并使用
cerebro.broker = instance
将实例设置为run
执行的经纪人 - 使用
set_xxx
通过cerebro.broker.set_xxx
来设置值,其中\
xxx`代表要设置的参数的名称
注意
cerebro.broker
是Cerebro
的getbroker
和setbroker
方法支持的property
参数:
cash
(默认值:10000
):起始现金commission
(默认值:CommInfoBase(percabs=True)
)适用于所有资产的基础佣金方案checksubmit
(默认值:True
):在将订单接受到系统之前检查保证金/现金eosbar
(默认值:False
):对于一日内柱,如果某个柱具有与会话结束相同的time
,则将其视为会话结束。这通常不是情况,因为一些柱(最终拍卖)会在会话结束后几分钟内由许多交易所为许多产品产生eosbar
(默认值:False
):对于一日内柱,如果某个柱具有与会话结束相同的time
,则将其视为会话结束。这通常不是情况,因为一些柱(最终拍卖)会在会话结束后几分钟内由许多交易所为许多产品产生filler
(默认值:None
)一个带有签名的可调用函数:callable(order, price, ago)
order
:显然是执行的订单。这提供了对数据(以及ohlc和volume值)、执行类型、剩余大小(order.executed.remsize
)和其他内容的访问。
请检查Order
文档和参考,以了解Order
实例中可用的内容price
:订单将在ago
柱中执行的价格ago
:用于与order.data
一起使用以提取ohlc和volume价格的索引。在大多数情况下,这将是0
,但对于Close
订单的一个特殊情况,这将是-1
。
为了获得柱形体积(例如):volume = order.data.voluume[ago]
- 可调用函数必须返回执行大小(大于等于 0 的值)
可调用函数当然可以是一个与前述签名匹配的对象__call__
默认值为None
时,订单将在一次性完全执行 slip_perc
(默认值:0.0
)应该使用的百分比绝对值(和正数)使买入/卖出订单的价格上涨/下跌注意:
0.01
是1%
0.001
是0.1%
slip_fixed
(默认值:0.0
)应使用的以单位(和正数)表示的百分比来使价格上涨/下跌以进行买入/卖出订单滑点
注意:如果slip_perc
不为零,则它优先于此。slip_open
(默认值:False
)是否为订单执行滑点价格,其将特定使用下一个条的开盘价格。一个例子是使用下一个可用的刻度执行的Market
订单,即:该条的开盘价。
这也适用于一些其他执行,因为逻辑试图检测开盘价格是否会与移动到新条时请求的价格/执行类型匹配。slip_match
(默认值:True
)
如果设置为True
,则经纪人将通过将滑点限制在高/低
价格上来提供匹配,以防超出。
如果为False
,则经纪人不会以当前价格匹配订单,并将尝试在下一次迭代期间执行slip_limit
(默认值:True
)Limit
订单,给定所请求的确切匹配价格,即使slip_match
为False
,也将匹配。
此选项控制该行为。
如果为True
,则Limit
订单将通过将价格限制在限价
/高/低
价格上来匹配
如果False
且滑点超过上限,则不会匹配slip_out
(默认值:False
)
即使价格跌出高
-低
范围,也要提供滑点。coc
(默认值:False
)
Cheat-On-Close 将其设置为True
并使用set_coc
启用
matching a `Market` order to the closing price of the bar in which the order was issued. This is actually *cheating*, because the bar is *closed* and any order should first be matched against the prices in the next bar`
coo
(默认值:False
)
Cheat-On-Open 将其设置为True
并使用set_coo
启用
matching a `Market` order to the opening price, by for example using a timer with `cheat` set to `True`, because such a timer gets executed before the broker has evaluated`
int2pnl
(默认值:True
)
将生成的利息(如果有)分配给减少持仓的操作的损益。可能存在这种情况:不同的策略正在竞争,利息将以不确定的方式分配给其中任何一个。shortcash
(默认值:True
)
如果为 True,则在对类似股票的资产进行空头操作时会增加现金,并且计算出的资产值将为负。
如果为False
,则现金将被扣除为操作成本,并且计算出的值将为正,以保持相同数量fundstartval
(默认值:100.0
)
此参数控制以类似基金的方式测量绩效的起始值,即:可以添加和扣除现金以增加股份数量。绩效不是使用投资组合的净资产值来衡量的,而是使用基金的价值来衡量。fundmode
(默认值:False
)
如果设置为True
,分析器如TimeReturn
可以根据基金价值而不是总净资产自动计算回报率
set_cash(cash)
设置 cash
参数(别名:setcash
)
get_cash()
返回当前现金(别名:getcash
)
get_value(datas=None, mkt=False, lever=False)
返回给定数据的投资组合价值(如果数据为 None
,则将返回总投资组合价值)(别名:getvalue
)
set_eosbar(eosbar)
设置 eosbar
参数(别名:seteosbar
)
set_checksubmit(checksubmit)
设置 checksubmit
参数
set_filler(filler)
为成交量填充执行设置一个成交量填充器
set_coc(coc)
配置“在订单栏上购买收盘价”的“Cheat-On-Close”方法
set_coo(coo)
配置“在订单栏上购买开盘价”的“Cheat-On-Open”方法
set_int2pnl(int2pnl)
配置利息分配给损益
set_fundstartval(fundstartval)
设置基金样式表现追踪器的起始值
set_slippage_perc(perc, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点以百分比为基础
set_slippage_fixed(fixed, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点以基于固定点进行
get_orders_open(safe=False)
返回仍然打开的订单的可迭代对象(未执行或部分执行)
返回的订单不得被更改。
如果需要订单操作,请将参数 safe
设置为 True
getcommissioninfo(data)
检索与给定 data
相关联的 CommissionInfo
方案
setcommission(commission=0.0, margin=None, mult=1.0, commtype=None, percabs=True, stocklike=False, interest=0.0, interest_long=False, leverage=1.0, automargin=False, name=None)
此方法使用参数为经纪人管理的资产设置 CommissionInfo
对象。请查阅 CommInfoBase
的参考资料
如果名称为 None
,则对于找不到其他 CommissionInfo
方案的资产,这将是默认值
addcommissioninfo(comminfo, name=None)
如果 name
为 None
,则添加一个 CommissionInfo
对象,如果 name
为 None
,则为所有资产设置默认值
getposition(data)
返回给定 data
的当前持仓状态(Position
实例)
get_fundshares()
返回基金模式中当前股份数量
get_fundvalue()
返回基金样式股票价值
add_cash(cash)
向系统添加/移除现金(使用负值来移除)
滑点
回测无法保证真实市场条件。无论市场模拟有多好,在真实市场条件下都可能发生滑点。这意味着:
- 请求的价格可能无法匹配。
集成的回测经纪人支持滑点。以下参数可以传递给经纪人
slip_perc
(默认值:0.0
) 绝对百分比(正数),用于上下滑动买入/卖出订单的价格注意:
0.01
是1%
0.001
是0.1%
slip_fixed
(默认值:0.0
) 单位百分比(正数),用于上下滑动买入/卖出订单的价格
注意:如果slip_perc
不为零,则它优先于此。slip_open
(默认值:False
) 是否为订单执行滑动价格,该价格特别使用下一个柱的开盘价格。一个示例是使用下一个可用刻度执行的Market
订单,即柱的开盘价格。
这也适用于其他一些执行,因为逻辑试图检测开盘价格是否会在移动到新柱时匹配请求的价格/执行类型。slip_match
(默认值:True
)
如果为True
,经纪人将通过在超出时将滑点限制在high/low
价格上来提供匹配。
如果False
,经纪人将不会根据当前价格匹配订单,并将在下一次迭代中尝试执行slip_limit
(默认值:True
)
即使slip_match
为False
,给定请求的精确匹配价格的Limit
订单也会被匹配。
此选项控制该行为。
如果为True
,则Limit
订单将通过将价格限制在limit
/high/low
价格上来匹配
如果False
并且滑点超过上限,则不会匹配slip_out
(默认值:False
)
即使价格超出high
-low
范围,也提供滑点。
工作原理
为了决定何时应用滑点,订单执行类型被考虑在内:
Close
- 不应用滑点
此订单与close
价格匹配,此价格是当天的最后一个价格。滑点不会发生,因为订单只能在会话的最后一个刻度发生,这是一个唯一的价格,没有容忍度。Market
- 滑点被应用
请检查slip_open
异常。因为Market
订单将与下一个柱的开盘价格匹配。Limit
-滑点按照这个逻辑应用
- 如果匹配价格将是开盘价格,则根据参数
slip_open
应用滑点。如果应用,则价格永远不会比请求的limit
价格更差 - 如果匹配价格不是
limit
价格,则应用滑点,并在high/low
上限制。在这种情况下,slip_mlimit
适用于决定是否在超出上限时会发生匹配 - 如果匹配价格是
limit
价格,则不会应用滑点
Stop
- 一旦订单被触发,与市价
订单相同的逻辑将适用StopLimit
- 一旦订单被触发,与限价
订单相同的逻辑将适用
该方法试图在模拟和可用数据的限制内提供尽可能逼真的方法
配置滑点
经纪人已经由每个运行的智能交易引擎实例化,具有默认参数。有两种方法可以更改其行为:
- 使用方法来配置滑点
BackBroker.set_slippage_perc(perc, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点为基于百分比的
BackBroker.set_slippage_fixed(fixed, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点为固定点位基础 - 将经纪人替换为:
import backtrader as bt cerebro = bt.Cerebro() cerebro.broker = bt.brokers.BackBroker(slip_perc=0.005) # 0.5%`
实际示例
源代码包含一个示例,该示例使用了执行类型为市价
的订单以及使用信号的多头/空头方法。这应该能帮助理解其逻辑。
一个没有滑点并且具有初始图表供以后参考的运行:
$ ./slippage.py --plot 01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3040.55 02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47 03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47 04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38 05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38 06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3034.88 ... 35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01
同样的运行使用了配置为1.5%
的滑点:
$ ./slippage.py --slip_perc 0.015 01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3040.55 02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47 03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47 04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38 05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38 06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3034.88 ... 35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01
没有变动。这是该场景的预期行为。
- 执行类型:
市价
- 而
slip_open
没有被设置为True
市价
订单与下一根柱的开盘价格匹配,我们不允许open
价格被移动。
将slip_open
设置为True
的一个运行设置:
$ ./slippage.py --slip_perc 0.015 --slip_open 01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3021.66 02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47 03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47 04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38 05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38 06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3055.14 ...
人们可以立即看到价格已经变动。并且分配的价格比操作 35 的最坏或相等。这不是复制粘贴错误
- 2016-12-19 的
open
和high
是相同的。
价格不能被推高到high
之上,因为那将意味着返回一个不存在的价格。
当然,backtrader允许匹配超出high
-low
范围的价格,如果愿意的话,可以激活slip_out
进行运行:
$ ./slippage.py --slip_perc 0.015 --slip_open --slip_out 01 2005-03-22 23:59:59 SELL Size: -1 / Price: 2994.94 02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3134.80 03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3134.80 04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2904.15 05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2904.15 06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3080.40 ... 35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4182.83
用于匹配的匹配表达式将是:哦天呐!(我的天啊!)。价格明显超出了范围。只需看看操作 35,该操作在4182.83
处匹配。快速检查本文档中的图表可以看到该资产从未接近过该价格。
slip_match
的默认值为True
,这意味着backtrader提供了一个匹配,无论是带上限价还是不带上限价,如上所示。让我们将其禁用:
$ ./slippage.py --slip_perc 0.015 --slip_open --no-slip_match 01 2005-04-15 23:59:59 SELL Size: -1 / Price: 3028.10 02 2005-05-18 23:59:59 BUY Size: +1 / Price: 3029.40 03 2005-06-01 23:59:59 BUY Size: +1 / Price: 3124.03 04 2005-10-06 23:59:59 SELL Size: -1 / Price: 3365.57 05 2005-10-06 23:59:59 SELL Size: -1 / Price: 3365.57 06 2005-12-01 23:59:59 BUY Size: +1 / Price: 3499.95 07 2005-12-01 23:59:59 BUY Size: +1 / Price: 3499.95 08 2006-02-28 23:59:59 SELL Size: -1 / Price: 3782.71 09 2006-02-28 23:59:59 SELL Size: -1 / Price: 3782.71 10 2006-05-23 23:59:59 BUY Size: +1 / Price: 3594.68 11 2006-05-23 23:59:59 BUY Size: +1 / Price: 3594.68 12 2006-11-27 23:59:59 SELL Size: -1 / Price: 3984.37 13 2006-11-27 23:59:59 SELL Size: -1 / Price: 3984.37
天啊!从 35 降到了 13。原因是:
停用slip_match
将阻止匹配操作,如果滑点将匹配价格推高到high
之上或推低到柱的low
之下。看起来,随着请求的滑点为1.5%
,大约有 22 个操作无法执行。
这些示例应该已经展示了不同的滑点选项是如何共同工作的。
以开盘作弊
原文:
www.backtrader.com/docu/cerebro/cheat-on-open/cheat-on-open/
版本 1.9.44.116
增加了对 Cheat-On-Open
的支持。这似乎是对那些全情投入的人的一项需求功能,他们在柱结束后做了计算,但期望与open
价格匹配。
当开盘价格出现差距(向上或向下,取决于buy
或sell
是否生效)并且现金不足以进行全情投入操作时,此用例会失败。这迫使经纪人拒绝操作。
虽然人们可以尝试用积极的[1]
索引方法来展望未来,但这需要预加载数据,而这并不总是可用的。
模式:
cerebro = bt.Cerebro(cheat_on_open=True)
这个:
- 在系统中激活了额外的周期,该周期调用策略中的
next_open
、nextstart_open
和prenext_open
方法。
决定增加一个额外的方法系列是为了清晰地区分常规方法和作弊模式之间的区别。常规方法是基于所检查的价格不再可用且未来未知的基础上运行的,而作弊模式则是另一种操作。
这也避免了对常规next
方法进行两次调用。
当处于xxx_open
方法内部时,以下情况成立:
- 指标尚未重新计算,并保留了上一个周期中在等效的
xxx
常规方法中最后看到的值。 - 经纪人尚未评估新周期的待处理订单,可以引入新订单,如果可能的话将进行评估。
请注意:
Cerebro
还有一个名为broker_coo
(默认为True
)的参数,告诉cerebro
如果已经激活了cheat-on-open
,它应该尽可能地在经纪人中也激活它。
仿真经纪人有一个名为:coo
的参数和一个设置它的方法名为set_coo
尝试作弊开盘
下面的示例具有具有 2 种不同行为的策略:
- 如果cheat-on-open是True,它将只从
next_open
操作 - 如果cheat-on-open是False,它将只从
next
操作
在这两种情况下,匹配价格必须是相同的
- 如果不作弊,订单将在前一天结束时发布,并将与下一个输入价格(即
open
价格)匹配 - 如果作弊,订单将在执行当天发布。因为订单是在经纪人评估订单之前发布的,所以它也将与下一个输入价格(即
open
价格)匹配。
第二种情况允许计算全情投入策略的确切赌注,因为可以直接访问当前的open
价格。
在这两种情况下
- 当前的
open
和close
价格将从next
打印出来。
常规执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=False ... 2005-04-07 next, open 3073.4 close 3090.72 2005-04-08 next, open 3092.07 close 3088.92 Strat Len 68 2005-04-08 Send Buy, fromopen False, close 3088.92 2005-04-11 Buy Executed at price 3088.47 2005-04-11 next, open 3088.47 close 3080.6 2005-04-12 next, open 3080.42 close 3065.18 ...
顺序:
- 在 2005-04-08close之后发布
- 它是在 2005-04-11 执行的,以
3088.47
的open
价格执行。
作弊执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=True ... 2005-04-07 next, open 3073.4 close 3090.72 2005-04-08 next, open 3092.07 close 3088.92 2005-04-11 Send Buy, fromopen True, close 3080.6 2005-04-11 Buy Executed at price 3088.47 2005-04-11 next, open 3088.47 close 3080.6 2005-04-12 next, open 3080.42 close 3065.18 ...
顺序:
- 在 2005-04-11 开盘 之前发布
- 它在 2005-04-11 执行,开盘价为
3088.47
而图表上整体的结果也是相同的。
结论
在开盘前作弊允许在开盘前发布订单,例如允许精确计算全仓情况下的股份。
样本用法
$ ./cheat-on-open.py --help usage: cheat-on-open.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] Cheat-On-Open Sample 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( periods=[10, 30], matype=bt.ind.SMA, ) def __init__(self): self.cheating = self.cerebro.p.cheat_on_open mas = [self.p.matype(period=x) for x in self.p.periods] self.signal = bt.ind.CrossOver(*mas) self.order = None def notify_order(self, order): if order.status != order.Completed: return self.order = None print('{} {} Executed at price {}'.format( bt.num2date(order.executed.dt).date(), 'Buy' * order.isbuy() or 'Sell', order.executed.price) ) def operate(self, fromopen): if self.order is not None: return if self.position: if self.signal < 0: self.order = self.close() elif self.signal > 0: print('{} Send Buy, fromopen {}, close {}'.format( self.data.datetime.date(), fromopen, self.data.close[0]) ) self.order = self.buy() def next(self): print('{} next, open {} close {}'.format( self.data.datetime.date(), self.data.open[0], self.data.close[0]) ) if self.cheating: return self.operate(fromopen=False) def next_open(self): if not self.cheating: return self.operate(fromopen=True) 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=( 'Cheat-On-Open Sample' ) ) 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()
BackTrader 中文文档(九)(3)https://developer.aliyun.com/article/1505291