forked from jrothschild33/learn_backtrader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Lesson4.py
572 lines (476 loc) · 27.7 KB
/
Lesson4.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# Lesson4:Backtrader来啦:交易篇(上)
# link: https://mp.weixin.qq.com/s/30ShvEKmoyP07QBxHXnmUQ
'''
Step1:设置交易条件:初始资金、交易税费、滑点、成交量限制等;
Step2:在 Strategy 策略逻辑中下达交易指令 buy、sell、close,或取消交易 cancel;
Step3:Order 模块会解读交易订单,解读的信息将交由经纪商 Broker 模块处理;
Step4:经纪商 Broker 会根据订单信息检查订单并确定是否接收订单;
Step5:经纪商 Broker 接收订单后,会按订单要求撮合成交 trade,并进行成交结算;
Step6:Order 模块返回经纪商 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=True,margin 参数才会起作用 。
'''
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节 形式1:bt.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节 形式2:bt.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节 形式3:bt.broker.fillers.BarPointPerc(minmov=0.01,perc=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.01,perc=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.01,perc=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() 函数中
方式1:bt.Cerebro(cheat_on_open=True)
方式2:cerebro.broker.set_coo(True)
方式3:BackBroker(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() 中
方式1:cerebro.broker.set_coc(True)
方式2:BackBroker(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 生成的下单指令,当天就被发送,而且当天就以 收盘价 执行了;并未在指令发出的下一日执行。
'''