Files
learn_backtrader/Lesson4.py
Jianan Zhou 2fbd4e6389 2021-10-19
2021-10-19 17:20:53 +08:00

572 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lesson4Backtrader来啦交易篇
# link: https://mp.weixin.qq.com/s/30ShvEKmoyP07QBxHXnmUQ
'''
Step1设置交易条件初始资金、交易税费、滑点、成交量限制等
Step2在 Strategy 策略逻辑中下达交易指令 buy、sell、close或取消交易 cancel
Step3Order 模块会解读交易订单,解读的信息将交由经纪商 Broker 模块处理;
Step4经纪商 Broker 会根据订单信息检查订单并确定是否接收订单;
Step5经纪商 Broker 接收订单后,会按订单要求撮合成交 trade并进行成交结算
Step6Order 模块返回经纪商 Broker 中的订单执行结果。
'''
# =============================================================================
#%%
# 第1章 Broker 中的交易条件
'''
回测过程中涉及的交易条件设置,最常见的有初始资金、交易税费、滑点、期货保证金比率等,
有时还会对成交量做限制、对涨跌幅做限制、对订单生成和执行时机做限制,
上述大部分交易条件都可以通过 Broker 来管理,主要有 2 中操作方式:
方式1通过设置 backtrader.brokers.BackBroker() 类中的参数,生成新的 broker 实例,再将新的实例赋值给 cerebro.broker ;
方式2通过调用 broker 中的 ”set_xxx“ 方法来修改条件,还可通过 ”get_xxx“ 方法查看当前设置的条件取值。
'''
# 第1.1节 资金管理
'''
Broker 默认的初始资金 cash 是 10000可通过 “cash” 参数、set_cash() 方法修改初始资金,
此外还提供了add_cash() 方法增加或减少资金。Broker 会检查提交的订单现金需求与当前现金是否匹配,
cash 也会随着每次交易进行迭代更新用以匹配当前头寸。
'''
# 初始化时
cerebro.broker.set_cash(100000000.0) # 设置初始资金
cerebro.broker.get_cash() # 获取当前可用资金
# 简写形式
cerebro.broker.setcash(100000000.0) # 设置初始资金
cerebro.broker.getcash() # 获取当前可用资金
# 在 Strategy 中添加资金或获取当前资金
self.broker.add_cash(10000) # 正数表示增加资金
self.broker.add_cash(-10000) # 负数表示减少资金
self.broker.getcash() # 获取当前可用资金
#%%
# 第1.2节 持仓查询
'''
Broker 在每次交易后更新 cash 外,还会同时更新当前总资产 value 和当前持仓 position通常在 Strategy 中进行持仓查询操作;
当前总资产 = 当前可用资金 + 当前持仓总市值,而当前持仓总市值为当前持仓中所有标的各自持仓市值之和,
如果只有一个标的,就有:当前总资产 999943.18 = 当前可用资金 996735.39 + 当前持仓数量 100.00 × 当前 close 32.0779
在计算当前可用资金时,除了考虑扣除购买标的时的费用外,还需要考虑扣除交易费用 。
'''
class TestStrategy(bt.Strategy):
def next(self):
print('当前可用资金', self.broker.getcash())
print('当前总资产', self.broker.getvalue())
print('当前持仓量', self.broker.getposition(self.data).size)
print('当前持仓成本', self.broker.getposition(self.data).price)
# 也可以直接获取持仓
print('当前持仓量', self.getposition(self.data).size)
print('当前持仓成本', self.getposition(self.data).price)
# 注getposition() 需要指定具体的标的数据集
# =============================================================================
#%%
# 第2章 滑点管理
'''
在实际交易中,由于市场波动、网络延迟等原因,交易指令中指定的交易价格与实际成交价格会存在较大差别,出现滑点。
为了让回测结果更真实,在交易前可以通过 brokers 设置滑点,滑点的类型有 2 种:【百分比滑点】和【固定滑点】。
不论哪种设置方式,都是起到相同的作用:买入时,在指定价格的基础上提高实际买入价格;
卖出时,在指定价格的基础上,降低实际卖出价格;买的 “更贵”,卖的 “更便宜” 。
注:在 Backtrader 中,如果同时设置了百分比滑点和固定滑点,前者的优先级高于后者,最终按百分比滑点的设置处理。
'''
# 第2.1节 百分比滑点
'''
假设设置了 n% 的滑点,如果指定的买入价为 x那实际成交时的买入价会提高至 x * (1+ n%)
同理,若指定的卖出价为 x那实际成交时的卖出价会降低至 x * (1- n%),下面时将滑点设置为 0.01% 的例子:
'''
# 方式1通过 BackBroker 类中的 slip_perc 参数设置百分比滑点
cerebro.broker = bt.brokers.BackBroker(slip_perc=0.0001)
# 方式2通过调用 brokers 的 set_slippage_perc 方法设置百分比滑点
cerebro.broker.set_slippage_perc(perc=0.0001)
#%%
# 第2.2节 固定滑点
'''
假设设置了大小为 n 的固定滑点,如果指定的买入价为 x那实际成交时的买入价会提高至 x + n
同理,若指定的卖出价为 x那实际成交时的卖出价会降低至 x - n下面时将滑点固定为 0.001 的例子:
'''
# 方式1通过 BackBroker 类中的 slip_fixed 参数设置固定滑点
cerebro.broker = bt.brokers.BackBroker(slip_fixed=0.001)
# 方式2通过调用 brokers 的 set_slippage_fixed 方法设置固定滑点
cerebro.broker.set_slippage_fixed(fixed=0.001)
#%%
# 第2.3节 有关滑点的其他设置
'''
除了用于设置滑点的 slip_perc 和 slip_fixed 参数外broker 还提供了其他参数用于处理价格出现滑点后的极端情况:
slip_open是否对开盘价做滑点处理;
该参数在 BackBroker() 类中默认为 False
在 set_slippage_perc 和set_slippage_fixed 方法中默认为 True
slip_match是否将滑点处理后的新成交价与成交当天的价格区间 low ~ high 做匹配;
如果为 True则根据新成交价重新匹配调整价格区间确保订单能被执行默认取值为 True
如果为 False则不会与价格区间做匹配订单不会执行但会在下一日执行一个空订单
slip_out如果新成交价高于最高价或低于最高价是否以超出的价格成交;
如果为 True则允许以超出的价格成交(仅在slip_match=True时才有用可以超出最高价/最低价执行;否则只能执行空订单)
如果为 False实际成交价将被限制在价格区间内 low ~ high默认取值为 False
slip_limit是否对限价单执行滑点;
如果为 True即使 slip_match 为False也会对价格做匹配确保订单被执行默认取值为 True
如果为 False则不做价格匹配
'''
# 方法1
cerebro.broker = bt.brokers.BackBroker(..., slip_perc=0, slip_fixed=0, slip_open=False, slip_match=True, slip_out=False, slip_limit=True, ...)
# 方法2
cerebro.broker.set_slippage_fixed(..., fixed=..., slip_open=False, slip_match=True, slip_out=False, slip_limit=True, ...)
# 下面是将滑点设置为固定 0.35 ,对上述参数去不同的值,标的 600466.SH 在 2019-01-17 的成交情况做对比:
# 情况1
'''由于 slip_open=False ,不会对开盘价做滑点处理,所以仍然以原始开盘价 32.63307367 成交'''
set_slippage_fixed(fixed=0.35,
slip_open=False,
slip_match=True,
slip_out=False)
# 情况2
'''
滑点调整的新成交价为 32.63307367+0.35 = 32.98307367,超出了当天最高价 32.94151482
由于允许做价格匹配 slip_match=True, 但不以超出价格区间的价格执行 slip_out=False
最终以最高价 32.9415 成交
'''
set_slippage_fixed(fixed=0.35,
slip_open=True,
slip_match=True,
slip_out=False)
# 情况3
'''
滑点调整的新成交价为 32.63307367+0.35 = 32.98307367,超出了当天最高价 32.94151482
允许做价格匹配 slip_match=True, 而且运行以超出价格区间的新成交价执行 slip_out=True
最终以新成交价 32.98307367 成交
'''
set_slippage_fixed(fixed=0.35,
slip_open=True,
slip_match=True,
slip_out=True)
# 情况4
'''
滑点调整的新成交价为 32.63307367+0.35 = 32.98307367,超出了当天最高价 32.94151482
由于不进行价格匹配 slip_match=False新成交价超出价格区间无法成交
2019-01-17 这一天订单不会执行,但会在下一日 2019-01-18 执行一个空订单
再往后的 2019-07-02也未执行订单下一日 2019-07-03 执行空订单
即使 2019-07-03的 open 39.96627412+0.35 < high 42.0866713 满足成交条件,也不会补充成交
'''
set_slippage_fixed(fixed=0.35,
slip_open=True,
slip_match=False,
slip_out=True)
# =============================================================================
#%%
# 第3章 交易税费管理
'''
【交易费收取规则】----------------------------------
股票:目前 A 股的交易费用分为 2 部分:佣金和印花税,
1.佣金:双边征收,不同证券公司收取的佣金各不相同,一般在 0.02%-0.03% 左右,单笔佣金不少于 5 元;
2.印花税:只在卖出时收取,税率为 0.1%
期货:期货交易费用包括交易所收取手续费和期货公司收取佣金 2 部分,
1.交易所手续费较为固定;
2.不同期货公司佣金不一致,而且不同期货品种的收取方式不相同,有的按照固定费用收取,有的按成交金额的固定百分比收取:
合约现价*合约乘数*手续费费率
3.除了交易费用外,期货交易时还需上交一定比例的保证金
【交易费设置方式】----------------------------------
根据交易品种的不同:
1. 股票 Stock-like 模式
2. 期货 Futures-like 模式
根据计算方式的不同:
1. PERC 百分比费用模式
2. FIXED 固定费用模式
【交易费参数】----------------------------------
1. commission手续费 / 佣金;
2. mult乘数
3. margin保证金 / 保证金比率 。
4. 双边征收:买入和卖出操作都要收取相同的交易费用
'''
# 第3.1节 通过 BackBroker() 设置
# BackBroker 中有一个 commission 参数,用来全局设置交易手续费。如果是股票交易,可以简单的通过该方式设置交易佣金,但该方式无法满足期货交易费用的各项设置。
cerebro.broker = bt.brokers.BackBroker(commission= 0.0002) # 设置 0.0002 = 0.02% 的手续费
#%%
# 第3.2节 通过 setcommission() 设置
# 如果想要完整又方便的设置交易费用,可以调用 broker 的 setcommission() 方法,该方法基本上可以满足大部分的交易费用设置需求
'''
从上述各参数的含义和作用可知margin 、commtype、stocklike 存在 2 种默认的配置规则:股票百分比费用、期货固定费用,具体如下:
1. 未设置 margin即 margin 为 0 / None / False:
commtype 会指向 COMM_PERC 百分比费用 → 底层的 _stocklike 属性会设置为 True → 对应的是“股票百分比费用”。
所以如果想为股票设置交易费用,就令 margin = 0 / None / False或者令 stocklike=True
2. 为 margin 设置了取值 → commtype 会指向 COMM_FIXED 固定费用 → 底层的 _stocklike 属性会设置为 False → 对应的是“期货固定费用”,因为只有期货才会涉及保证金。
所以如果想为期货设置交易费用,就需要设置 margin此外还需令 stocklike=Truemargin 参数才会起作用 。
'''
cerebro.broker.setcommission(
# 交易手续费根据margin取值情况区分是百分比手续费还是固定手续费
commission=0.0,
# 期货保证金,决定着交易费用的类型,只有在stocklike=False时起作用
margin=None,
# 乘数,盈亏会按该乘数进行放大
mult=1.0,
# 交易费用计算方式,取值有:
# 1.CommInfoBase.COMM_PERC 百分比费用
# 2.CommInfoBase.COMM_FIXED 固定费用
# 3.None 根据 margin 取值来确定类型
commtype=None,
# 当交易费用处于百分比模式下时commission 是否为 % 形式
# True表示不以 % 为单位0.XX 形式False表示以 % 为单位XX% 形式
percabs=True,
# 是否为股票模式该模式通常由margin和commtype参数决定
# margin=None或COMM_PERC模式时就会stocklike=True对应股票手续费
# margin设置了取值或COMM_FIXED模式时,就会stocklike=False对应期货手续费
stocklike=False,
# 计算持有的空头头寸的年化利息
# days * price * abs(size) * (interest / 365)
interest=0.0,
# 计算持有的多头头寸的年化利息
interest_long=False,
# 杠杆比率,交易时按该杠杆调整所需现金
leverage=1.0,
# 自动计算保证金
# 如果False, 则通过margin参数确定保证金
# 如果automargin<0, 通过mult*price确定保证金
# 如果automargin>0, 如果automargin*price确定保证金
automargin=False,
# 交易费用设置作用的数据集(也就是作用的标的)
# 如果取值为None则默认作用于所有数据集(也就是作用于所有assets)
name=None
)
#%%
# 第3.3节 通过 addcommissioninfo() 设置
# 如果想要更灵活的设置交易费用,可以在继承 CommInfoBase 基础类的基础上自定义交易费用子类 ,然后通过 addcommissioninfo() 方法将实例添加进 broker。
'''
Backtrader 中与交易费用相关的设置都是由 CommInfoBase 类管理的,
setcommission() 方法中的参数就是 CommInfoBase 类中 params 属性里包含的参数,
此外还内置许多 getxxx 方法,用于计算并返回交易产生的指标:
计算成交量 getsize(price, cash)
计算持仓市值 getvalue(position, price)
计算佣金getcommission(size, price) 或 _getcommission(self, size, price, pseudoexec)
计算保证金 get_margin(price)
其中自定义时最常涉及的就是上面案例中显示的 _getcommission 和 get_margin
'''
# 在继承 CommInfoBase 基础类的基础上自定义交易费用
class MyCommission(bt.CommInfoBase):
# 对应 setcommission 中介绍的那些参数,也可以增添新的全局参数
params = ((xxx, xxx),)
# 自定义交易费用计算方式
def _getcommission(self, size, price, pseudoexec):
pass
# 自定义佣金计算方式
def get_margin(self, price):
pass
...
# 实例化
mycomm = MyCommission(...)
cerebro = bt.Cerebro()
# 添加进 broker
cerebro.broker.addcommissioninfo(mycomm, name='xxx') # name 用于指定该交易费用函数适用的标的
#%%
# 第3.3.1节 自定义交易费用的例子1自定义期货百分比费用
# 方法1通过 setcommission 实现
cerebro.broker.setcommission(commission=0.1, #0.1%
mult=10,
margin=2000,
percabs=False,
commtype=bt.CommInfoBase.COMM_PERC,
stocklike=False)
# 方法2通过 addcommissioninfo 实现
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
params = (
('stocklike', False), # 指定为期货模式
('commtype', bt.CommInfoBase.COMM_PERC), # 使用百分比费用
('percabs', False), # commission 以 % 为单位
)
def _getcommission(self, size, price, pseudoexec):
# 计算交易费用
return (abs(size) * price) * (self.p.commission/100) * self.p.mult
# pseudoexec 用于提示当前是否在真实统计交易费用如果只是试算费用pseudoexec=False如果是真实的统计费用pseudoexec=True
comminfo = CommInfo_Fut_Perc_Mult(commission=0.1, # 0.1%
mult=10,
margin=2000)
cerebro.broker.addcommissioninfo(comminfo)
#%%
# 第3.3.2节 自定义交易费用的例子2考虑佣金和印花税的股票百分比费用
class StockCommission(bt.CommInfoBase):
params = (
('stocklike', True), # 指定为股票模式
('commtype', bt.CommInfoBase.COMM_PERC), # 使用百分比费用模式
('percabs', True), # commission 不以 % 为单位
('stamp_duty', 0.001), # 印花税默认为 0.1%
)
# 自定义费用计算公式
def _getcommission(self, size, price, pseudoexec):
if size > 0: # 买入时,只考虑佣金
return abs(size) * price * self.p.commission
elif size < 0: # 卖出时,同时考虑佣金和印花税
return abs(size) * price * (self.p.commission + self.p.stamp_duty)
else:
return 0
# =============================================================================
#%%
# 第4章 成交量限制管理
'''
默认情况下Broker 在撮合成交订单时,不会将订单上的购买数量与成交当天 bar 的总成交量 volume 进行对比,
即使购买数量超出了当天该标的的总成交量,也会按购买数量全部撮合成交,显然这种“无限的流动性”是不现实的,
这种 “不考虑成交量,默认全部成交” 的交易模式,也会使得回测结果与真实结果产生较大偏差。
如果想要修改这种默认模式,可以通过 Backtrader 中的 fillers 模块来限制实际成交量,
fillers 会告诉 Broker 在各个成交时间点应该成交多少量,一共有 3 种形式。
'''
# 第4.1节 形式1bt.broker.fillers.FixedSize(size)
'''
通过 FixedSize() 方法设置最大的固定成交量size该种模式下的成交量限制规则如下
1. 订单实际成交量的确定规则size、订单执行那天的 volume 、订单中要求的成交数量)中的最小者;
2. 订单执行那天,如果订单中要求的成交数量无法全部满足,则只成交部分数量。
'''
## 方法1通过 BackBroker() 类直接设置
cerebro = Cerebro()
filler = bt.broker.fillers.FixedSize(size=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker
## 方法2通过 set_filler 方法设置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=xxx))
# 输出案例(部分示例代码)
......
self.order = self.buy(size=2000) # 每次买入 2000 股
......
cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=3000)) # 固定最大成交量
'''
【输出】===================================================================================
情况1
2019-01-17 这天执行买入订单,当天 volume 869.0 < buy(size=2000) < FixedSize(size=3000)
所以当天只买入了最小的 volume 869 股,剩余未成交数量 Remsize: 1131.00 2019-02-22 这天情况类似;
情况2
2019-03-15 这天执行买入订单,当天 buy(size=2000) < FixedSize(size=3000)< volume 3063.0
所以可以全部成交,剩余未成交数量 Remsize: 0
情况3
2019-05-20 这天执行卖出订单,当天 close 平仓时的仓位 2000.0 > volume 1686.0,无法全部平仓,
所以只卖出了 1686 股,剩余未成交数量 Remsize: -314.00
随后,在 2019-06-12 再次触发卖出信号2019-06-13 执行卖出,对剩余仓位 341 股 进行了平仓。
【结果】===================================================================================
1. 对订单执行当天未成交的剩余数量,并不会在第二天接着成交;
2. 在订单执行当天,如果遇到对于无法全部成交的情况,订单会被部分执行,然后在第二天取消该订单,并打印 notify_order
2019-01-16 这一天触发买入信号,下达订单指令,创建订单;
2019-01-17 订单被传递给 broker并由 broker 接受,然后由于成交量限制,订单被部分执行;
2019-01-18 这天,剩余订单会被取消,同时打印 notify_order。
'''
#%%
# 第4.2节 形式2bt.broker.fillers.FixedBarPerc(perc)
'''
通过 FixedBarPerc(perc) 将 订单执行当天 bar 的总成交量 volume 的 perc % 设置为最大的固定成交量,该模式的成交量限制规则如下:
1. 订单实际成交量的确定规则:取 volume * perc /100、订单中要求的成交数量的最小者
2. 订单执行那天,如果订单中要求的成交数量无法全部满足,则只成交部分数量。
'''
# 方法1通过 BackBroker() 类直接设置
cerebro = Cerebro()
filler = bt.broker.fillers.FixedBarPerc(perc=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker
# 方法2通过 set_filler 方法设置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=xxx)) # perc 以 % 为单位,取值范围为[0.0,100.0]
# 输出案例(部分示例代码)
......
self.order = self.buy(size=2000) # 以下一日开盘价买入2000股
......
cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=50)) # perc=50 表示 50%
'''
【输出】===================================================================================
情况1
2019-01-17 这天执行买入订单订单的buy(size=2000) > 当天 volume 869.0,只能部分成交,
数量为 volume 869.0 * 50/100= 434 ,订单剩余数量 Remsize: 1566.00 不会成交2019-02-22 这天情况类似;
情况2
2019-03-15 这天执行买入订单,当天 buy(size=2000) < volume 3063.0,所以可以全部成交,
剩余未成交数量 Remsize: 0
情况3
2019-04-03 这天执行卖出订单,当天要 close 平仓的量仓位为 2000.0 ,虽然小于当天的 volume 3826.0
但是大于 volume 3826.0 *50/100= 1913.00,所以最多只成交了 1913.00,还剩 Remsize: -87.00 未成交;
随后,在 2019-05-17 再次触发卖出信号2019-05-20 剩余仓位 87 进行了平仓。
'''
#%%
# 第4.3节 形式3bt.broker.fillers.BarPointPerc(minmov=0.01perc=100.0)
'''
BarPointPerc() 在考虑了价格区间的基础上确定成交量,在订单执行当天,成交量确定规则为:
1. 通过 minmov 将 当天 bar 的价格区间 low ~ high 进行均匀划分,得到划分的份数:
part = (high - low + minmov) // minmov (向下取整)
2. 再对当天 bar 的总成交量 volume 也划分成相同的份数 part ,这样就能得到每份的平均成交量:
volume_per = volume // part
3. 最终volume_per * perc / 100就是允许的最大成交量实际成交时对比订单中要求的成交量就可以得到最终实际成交量:
实际成交量 = min ( volume_per * perc / 100, 订单中要求的成交数量 )
'''
# 方法1通过 BackBroker() 类直接设置
cerebro = Cerebro()
filler = bt.broker.fillers.BarPointPerc(minmov=0.01perc=100.0)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker
# 方法2通过 set_filler 方法设置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.01perc=100.0)) # perc 以 % 为单位,取值范围为[0.0,100.0]
# 输出案例(部分示例代码)
......
self.order = self.buy(size=2000) # 以下一日开盘价买入2000股
......
cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.1, perc=50)) # 表示 50%
'''
【输出】===================================================================================
part = (high 32.94151482 - low 31.83112668 + minmov 0.1) // minmov 0.1 = 12.0
volume_per = volume 869.0 // 12.0 = 72.0
最终成交数量 = min ( volume_per 72.0 * perc 50 / 100, 订单中要求的成交数量 2000 ) = 36.0
【结果】原计划买入2000股结果只能买入36股
'''
# =============================================================================
#%%
# 第5章 交易时机管理
'''
对于交易订单生成和执行时间Backtrader 默认是 “当日收盘后下单,次日以开盘价成交”,这种模式在回测过程中能有效避免使用未来数据。
但对于一些特殊的交易场景比如“all_in”情况下当日所下订单中的数量是用当日收盘价计算的总资金 / 当日收盘价),
次日以开盘价执行订单时,如果开盘价比昨天的收盘价提高了,就会出现可用资金不足的情况。
为了应对一些特殊交易场景Backtrader 还提供了一些 cheating 式的交易时机模式Cheat-On-Open 和 Cheat-On-Close。
'''
# 第5.1节 Cheat-On-Open
'''
Cheat-On-Open当日下单当日以开盘价成交
在该模式下Strategy 中的交易逻辑不再写在 next() 方法里,而是写在特定的 next_open()、nextstart_open() 、prenext_open() 函数中
方式1bt.Cerebro(cheat_on_open=True)
方式2cerebro.broker.set_coo(True)
方式3BackBroker(coo=True)
'''
class TestStrategy(bt.Strategy):
......
def next_open(self):
# 取消之前未执行的订单
if self.order:
self.cancel(self.order)
# 检查是否有持仓
if not self.position:
# 10日均线上穿5日均线买入
if self.crossover > 0:
print('{} Send Buy, open {}'.format(self.data.datetime.date(),self.data.open[0]))
self.order = self.buy(size=100) # 以下一日开盘价买入100股
# # 10日均线下穿5日均线卖出
elif self.crossover < 0:
self.order = self.close() # 平仓,以下一日开盘价卖出
......
# 方法1实例化大脑开启cheat_on_open
cerebro= bt.Cerebro(cheat_on_open=True)
.......
# 方法2当日下单当日开盘价成交
cerebro.broker.set_coo(True)
'''
【结果】===================================================================================
1. 原本 2019-01-16 生成的下单指令,被延迟到了 2019-01-17 日才发出;
2. 2019-01-17 发出的订单,在 2019-01-17 当日就以 开盘价 执行成交了。
'''
#%%
# 第5.2节 Cheat-On-Close
'''
Cheat-On-Close当日下单当日以收盘价成交
在该模式下Strategy 中的交易逻辑仍写在 next() 中
方式1cerebro.broker.set_coc(True)
方式2BackBroker(coc=True)
'''
class TestStrategy(bt.Strategy):
......
def next(self):
# 取消之前未执行的订单
if self.order:
self.cancel(self.order)
# 检查是否有持仓
if not self.position:
# 10日均线上穿5日均线买入
if self.crossover > 0:
print('{} Send Buy, open {}'.format(self.data.datetime.date(),self.data.open[0]))
self.order = self.buy(size=100) # 以下一日开盘价买入100股
# # 10日均线下穿5日均线卖出
elif self.crossover < 0:
self.order = self.close() # 平仓,以下一日开盘价卖出
......
# 实例化大脑不能在这里开启Cheat-On-Close
cerebro= bt.Cerebro()
.......
# 当日下单,当日收盘价成交
cerebro.broker.set_coc(True)
'''
【结果】===================================================================================
2019-01-16 生成的下单指令,当天就被发送,而且当天就以 收盘价 执行了;并未在指令发出的下一日执行。
'''