回测无法保证真实的市场条件。无论市场模拟有多好,在真实市场条件下都可能发生滑点。这意味着:
滑点是如何工作的
为了决定何时应用滑点,考虑订单执行类型:
滑点试图在模拟和可用数据的限制内,提供最真实的可能回测交易方法 。
每次运行时,cerebro引擎都会为每个运行实例化一个broker,并使用默认参数。有两种方法可以更改设置操作:
使用方法配置滑点:
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()
#0.5%的百分比滑点设置
cerebro.broker = bt.brokers.BackBroker(slip_perc=0.005) # 0.5%
包含一个使用订单执行类型市场和使用信号的多头/空头方法的示例。
应该允许理解逻辑, 无滑点运行和供以后参考的初始图:
python ./slippage.py
01 2005-03-22 SELL Size: -1 / Price: 3040.55
02 2005-04-11 BUY Size: +1 / Price: 3088.47
03 2005-04-11 BUY Size: +1 / Price: 3088.47
04 2005-04-19 SELL Size: -1 / Price: 2948.38
05 2005-04-19 SELL Size: -1 / Price: 2948.38
06 2005-05-19 BUY Size: +1 / Price: 3034.88
07 2005-05-19 BUY Size: +1 / Price: 3034.88
08 2005-08-26 SELL Size: -1 / Price: 3258.45
09 2005-08-26 SELL Size: -1 / Price: 3258.45
10 2005-09-13 BUY Size: +1 / Price: 3353.61
11 2005-09-13 BUY Size: +1 / Price: 3353.61
12 2005-10-19 SELL Size: -1 / Price: 3330.00
13 2005-10-19 SELL Size: -1 / Price: 3330.00
14 2005-11-14 BUY Size: +1 / Price: 3405.94
15 2005-11-14 BUY Size: +1 / Price: 3405.94
16 2006-01-26 SELL Size: -1 / Price: 3578.92
17 2006-01-26 SELL Size: -1 / Price: 3578.92
18 2006-02-03 BUY Size: +1 / Price: 3677.05
19 2006-02-03 BUY Size: +1 / Price: 3677.05
20 2006-04-20 SELL Size: -1 / Price: 3820.93
21 2006-04-20 SELL Size: -1 / Price: 3820.93
22 2006-05-02 BUY Size: +1 / Price: 3839.24
23 2006-05-02 BUY Size: +1 / Price: 3839.24
24 2006-05-16 SELL Size: -1 / Price: 3711.46
25 2006-05-16 SELL Size: -1 / Price: 3711.46
26 2006-07-04 BUY Size: +1 / Price: 3664.59
27 2006-07-04 BUY Size: +1 / Price: 3664.59
28 2006-07-27 SELL Size: -1 / Price: 3649.29
29 2006-07-27 SELL Size: -1 / Price: 3649.29
30 2006-07-28 BUY Size: +1 / Price: 3671.71
31 2006-07-28 BUY Size: +1 / Price: 3671.71
32 2006-12-04 SELL Size: -1 / Price: 3935.81
33 2006-12-04 SELL Size: -1 / Price: 3935.81
34 2006-12-19 BUY Size: +1 / Price: 4121.01
35 2006-12-19 BUY Size: +1 / Price: 4121.01
看第一个成交的原始数据:
2005-03-22,3040.55,3053.18,3021.66,3050.44,0,0
用的是开盘价成交 。
python ./slippage.py --slip_perc 0.015
01 2005-03-22 SELL Size: -1 / Price: 3040.55
02 2005-04-11 BUY Size: +1 / Price: 3088.47
03 2005-04-11 BUY Size: +1 / Price: 3088.47
04 2005-04-19 SELL Size: -1 / Price: 2948.38
05 2005-04-19 SELL Size: -1 / Price: 2948.38
06 2005-05-19 BUY Size: +1 / Price: 3034.88
07 2005-05-19 BUY Size: +1 / Price: 3034.88
08 2005-08-26 SELL Size: -1 / Price: 3258.45
09 2005-08-26 SELL Size: -1 / Price: 3258.45
10 2005-09-13 BUY Size: +1 / Price: 3353.61
11 2005-09-13 BUY Size: +1 / Price: 3353.61
12 2005-10-19 SELL Size: -1 / Price: 3330.00
13 2005-10-19 SELL Size: -1 / Price: 3330.00
14 2005-11-14 BUY Size: +1 / Price: 3405.94
15 2005-11-14 BUY Size: +1 / Price: 3405.94
16 2006-01-26 SELL Size: -1 / Price: 3578.92
17 2006-01-26 SELL Size: -1 / Price: 3578.92
18 2006-02-03 BUY Size: +1 / Price: 3677.05
19 2006-02-03 BUY Size: +1 / Price: 3677.05
20 2006-04-20 SELL Size: -1 / Price: 3820.93
21 2006-04-20 SELL Size: -1 / Price: 3820.93
22 2006-05-02 BUY Size: +1 / Price: 3839.24
23 2006-05-02 BUY Size: +1 / Price: 3839.24
24 2006-05-16 SELL Size: -1 / Price: 3711.46
25 2006-05-16 SELL Size: -1 / Price: 3711.46
26 2006-07-04 BUY Size: +1 / Price: 3664.59
27 2006-07-04 BUY Size: +1 / Price: 3664.59
28 2006-07-27 SELL Size: -1 / Price: 3649.29
29 2006-07-27 SELL Size: -1 / Price: 3649.29
30 2006-07-28 BUY Size: +1 / Price: 3671.71
31 2006-07-28 BUY Size: +1 / Price: 3671.71
32 2006-12-04 SELL Size: -1 / Price: 3935.81
33 2006-12-04 SELL Size: -1 / Price: 3935.81
34 2006-12-19 BUY Size: +1 / Price: 4121.01
35 2006-12-19 BUY Size: +1 / Price: 4121.01
对比没有变化。符合预期的行为。
python ./slippage.py --slip_perc 0.015 --slip_open
01 2005-03-22 SELL Size: -1 / Price: 3021.66
02 2005-04-11 BUY Size: +1 / Price: 3088.47
03 2005-04-11 BUY Size: +1 / Price: 3088.47
04 2005-04-19 SELL Size: -1 / Price: 2948.38
05 2005-04-19 SELL Size: -1 / Price: 2948.38
06 2005-05-19 BUY Size: +1 / Price: 3055.14
07 2005-05-19 BUY Size: +1 / Price: 3055.14
08 2005-08-26 SELL Size: -1 / Price: 3224.10
09 2005-08-26 SELL Size: -1 / Price: 3224.10
10 2005-09-13 BUY Size: +1 / Price: 3358.04
11 2005-09-13 BUY Size: +1 / Price: 3358.04
12 2005-10-19 SELL Size: -1 / Price: 3280.05
13 2005-10-19 SELL Size: -1 / Price: 3280.05
14 2005-11-14 BUY Size: +1 / Price: 3426.51
15 2005-11-14 BUY Size: +1 / Price: 3426.51
16 2006-01-26 SELL Size: -1 / Price: 3577.98
17 2006-01-26 SELL Size: -1 / Price: 3577.98
18 2006-02-03 BUY Size: +1 / Price: 3696.00
19 2006-02-03 BUY Size: +1 / Price: 3696.00
20 2006-04-20 SELL Size: -1 / Price: 3820.93
21 2006-04-20 SELL Size: -1 / Price: 3820.93
22 2006-05-02 BUY Size: +1 / Price: 3864.19
23 2006-05-02 BUY Size: +1 / Price: 3864.19
24 2006-05-16 SELL Size: -1 / Price: 3692.35
25 2006-05-16 SELL Size: -1 / Price: 3692.35
26 2006-07-04 BUY Size: +1 / Price: 3670.75
27 2006-07-04 BUY Size: +1 / Price: 3670.75
28 2006-07-27 SELL Size: -1 / Price: 3649.29
29 2006-07-27 SELL Size: -1 / Price: 3649.29
30 2006-07-28 BUY Size: +1 / Price: 3711.41
31 2006-07-28 BUY Size: +1 / Price: 3711.41
32 2006-12-04 SELL Size: -1 / Price: 3927.40
33 2006-12-04 SELL Size: -1 / Price: 3927.40
34 2006-12-19 BUY Size: +1 / Price: 4121.01
35 2006-12-19 BUY Size: +1 / Price: 4121.01
滑点设置起作用了,非开盘价成交。
2005-03-22,3040.55,3053.18,3021.66,3050.44,0,0
SELL成交价格使用的Low 最低价3021.66 。
2006-12-19,4121.01,4121.01,4085.18,4100.48,0,0
BUY成交价格使用了当天的开盘价,也是最高价成交。
再看一个BUY 的例子,交易30号,原始数据:
2006-07-28,3671.71,3711.41,3659.67,3710.60,0,0
BUY是当天的最高价成交。
通过示例理解滑点 ?
滑点的意义,就是不可能买在预期的低价,卖单,不可能卖在预期的高点:
当然BT允许在希望的情况下匹配“高”-“低”范围之外的价格,使用“slip_out”。激活它的运行 :
python ./slippage.py --slip_perc 0.015 --slip_open --slip_out
01 2005-03-22 SELL Size: -1 / Price: 2994.94
02 2005-04-11 BUY Size: +1 / Price: 3134.80
03 2005-04-11 BUY Size: +1 / Price: 3134.80
04 2005-04-19 SELL Size: -1 / Price: 2904.15
05 2005-04-19 SELL Size: -1 / Price: 2904.15
06 2005-05-19 BUY Size: +1 / Price: 3080.40
07 2005-05-19 BUY Size: +1 / Price: 3080.40
08 2005-08-26 SELL Size: -1 / Price: 3209.57
09 2005-08-26 SELL Size: -1 / Price: 3209.57
10 2005-09-13 BUY Size: +1 / Price: 3403.91
11 2005-09-13 BUY Size: +1 / Price: 3403.91
12 2005-10-19 SELL Size: -1 / Price: 3280.05
13 2005-10-19 SELL Size: -1 / Price: 3280.05
14 2005-11-14 BUY Size: +1 / Price: 3457.03
15 2005-11-14 BUY Size: +1 / Price: 3457.03
16 2006-01-26 SELL Size: -1 / Price: 3525.24
17 2006-01-26 SELL Size: -1 / Price: 3525.24
18 2006-02-03 BUY Size: +1 / Price: 3732.21
19 2006-02-03 BUY Size: +1 / Price: 3732.21
20 2006-04-20 SELL Size: -1 / Price: 3763.62
21 2006-04-20 SELL Size: -1 / Price: 3763.62
22 2006-05-02 BUY Size: +1 / Price: 3896.83
23 2006-05-02 BUY Size: +1 / Price: 3896.83
24 2006-05-16 SELL Size: -1 / Price: 3655.79
25 2006-05-16 SELL Size: -1 / Price: 3655.79
26 2006-07-04 BUY Size: +1 / Price: 3719.56
27 2006-07-04 BUY Size: +1 / Price: 3719.56
28 2006-07-27 SELL Size: -1 / Price: 3594.55
29 2006-07-27 SELL Size: -1 / Price: 3594.55
30 2006-07-28 BUY Size: +1 / Price: 3726.79
31 2006-07-28 BUY Size: +1 / Price: 3726.79
32 2006-12-04 SELL Size: -1 / Price: 3876.77
33 2006-12-04 SELL Size: -1 / Price: 3876.77
34 2006-12-19 BUY Size: +1 / Price: 4182.83
35 2006-12-19 BUY Size: +1 / Price: 4182.83
第一个SELL单:
3040.55 *(1 - 0.0015)= 2994.94
2994.94 没有在当天的价格中出现,以不在最高和最低价格中成交的滑点价格。
2005-03-22,3040.55,3053.18,3021.66,3050.44,0,0
最后一个BUY单:
价格显然超出了范围。只需查看操作35,它已在“4182.83”处匹配。快速检查本文档中的图表,可以看到该资产从未接近过该价格。
2006-12-19,4121.01,4121.01,4085.18,4100.48,0,0
“slip_match”默认为“True”,这意味着BT提供了匹配,无论是带有限制的价格还是不带限制的价格 。
python ./slippage.py --slip_perc 0.015 --slip_open --no-slip_match
01 2005-04-15 SELL Size: -1 / Price: 3028.10
02 2005-06-01 BUY Size: +1 / Price: 3124.03
03 2005-06-01 BUY Size: +1 / Price: 3124.03
04 2005-10-06 SELL Size: -1 / Price: 3365.57
05 2005-10-06 SELL Size: -1 / Price: 3365.57
06 2005-12-01 BUY Size: +1 / Price: 3499.95
07 2005-12-01 BUY Size: +1 / Price: 3499.95
08 2006-02-28 SELL Size: -1 / Price: 3782.71
09 2006-02-28 SELL Size: -1 / Price: 3782.71
10 2006-05-23 BUY Size: +1 / Price: 3594.68
11 2006-05-23 BUY Size: +1 / Price: 3594.68
12 2006-11-27 SELL Size: -1 / Price: 3984.37
13 2006-11-27 SELL Size: -1 / Price: 3984.37
2005-04-15,3074.21,3074.21,3013.79,3013.89,0,0
3074.21 * (1 - 0.015 )= 3028.10
这个价格符合在最高价3074.21 和 最低价 3013.79 中间的价格,并且符合滑点。如果是市场上实际交易,这个价格一定是存在的。
结果让人震惊!操作成交的数量从35下降到13。
理由:
禁用“slip_match”会禁止匹配操作,如果滑点将匹配价格推到bar的“高”以上或“低”以下。似乎在请求的滑点“1.5%”左右,有22个操作未能执行。
以上示例应该展示了不同的滑点选项如何一起工作。
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015-2023 Daniel Rodriguez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import collections
import datetime
import itertools
import backtrader as bt
class SMACrossOver(bt.Indicator):
lines = ('signal',)
params = (('p1', 10), ('p2', 30),)
def __init__(self):
sma1 = bt.indicators.SMA(period=self.p.p1)
sma2 = bt.indicators.SMA(period=self.p.p2)
self.lines.signal = bt.indicators.CrossOver(sma1, sma2)
class SlipSt(bt.SignalStrategy):
opcounter = itertools.count(1)
def notify_order(self, order):
if order.status == bt.Order.Completed:
t = ''
t += '{:02d}'.format(next(self.opcounter))
t += ' {}'.format(order.data.datetime.date())
t += ' BUY ' * order.isbuy() or ' SELL'
t += ' Size: {:+d} / Price: {:.2f}'
print(t.format(order.executed.size, order.executed.price))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)
dkwargs = dict()
if args.fromdate is not None:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate is not None:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
# if dataset is None, args.data has been given
data = bt.feeds.BacktraderCSVData(dataname=args.data, **dkwargs)
cerebro.adddata(data)
cerebro.signal_strategy(SlipSt)
if not args.longonly:
stype = bt.signal.SIGNAL_LONGSHORT
else:
stype = bt.signal.SIGNAL_LONG
cerebro.add_signal(stype, SMACrossOver, p1=args.period1, p2=args.period2)
if args.slip_perc is not None:
cerebro.broker.set_slippage_perc(args.slip_perc,
slip_open=args.slip_open,
slip_match=not args.no_slip_match,
slip_out=args.slip_out)
elif args.slip_fixed is not None:
cerebro.broker.set_slippage_fixed(args.slip_fixed,
slip_open=args.slip_open,
slip_match=not args.no_slip_match,
slip_out=args.slip_out)
cerebro.run()
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Slippage')
parser.add_argument('--data', required=False,
default='./datas/2005-2006-day-001.txt',
help='Specific data to be read in')
parser.add_argument('--fromdate', required=False, default=None,
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False, default=None,
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--period1', required=False, action='store',
type=int, default=10,
help=('Fast moving average period'))
parser.add_argument('--period2', required=False, action='store',
type=int, default=30,
help=('Slow moving average period'))
parser.add_argument('--longonly', required=False, action='store_true',
help=('Long only strategy'))
pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--slip_perc', required=False, default=None,
type=float,
help='Set the value for commission percentage')
pgroup.add_argument('--slip_fixed', required=False, default=None,
type=float,
help='Set the value for commission percentage')
parser.add_argument('--no-slip_match', required=False, action='store_true',
help=('Match by capping slippage at bar ends'))
parser.add_argument('--slip_out', required=False, action='store_true',
help=('Disable capping and return non-real prices'))
parser.add_argument('--slip_open', required=False, action='store_true',
help=('Slip even if match price is next open'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()