自动回溯交易者回溯测试
到目前为止,所有 backtrader 示例和工作示例都是从零开始创建一个主要的Python模块,用于加载数据、策略、观察者并准备现金和佣金方案。
算法交易的目标之一是交易自动化,鉴于 bactrader 是一个回溯测试平台,旨在检查交易算法(因此是一个算法交易平台),自动化回溯交易的使用是一个明显的目标。
笔记
2015 年 8 月 22 日
包括bt-run.py
中的Analyzer
支架
backtrader
的开发版本现在包含bt-run.py
脚本,该脚本自动执行大多数任务,并将作为常规包的一部分随backtrader
一起安装。
bt-run.py
允许最终用户:
-
说明必须加载哪些数据
-
设置加载数据的格式
-
指定数据的日期范围
-
禁用标准观察者
-
从内置观察者或 python 模块加载一个或多个观察者(例如:DrawDown)
-
为经纪人设置现金和佣金方案参数(佣金、保证金、mult)
-
启用打印,控制显示数据的图表数量和样式
最后:
-
加载策略(内置策略或来自 Python 模块)
-
将参数传递给加载的策略
脚本的用法*见下文。
应用用户定义的策略
让我们考虑以下策略:
-
仅加载 SimpleMovingAverage(默认时段 15)
-
打印输出
-
在一个名为 mymod.py 的文件中
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.indicators as btind
class MyTest(bt.Strategy):
params = (('period', 15),)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.data.datetime[0]
if isinstance(dt, float):
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
sma = btind.SMA(period=self.p.period)
def next(self):
ltxt = '%d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f'
self.log(ltxt %
(len(self),
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0],
self.data.volume[0], self.data.openinterest[0]))
使用常规测试样本执行策略很简单:简单:
./bt-run.py --csvformat btcsv \
--data ../samples/data/sample/2006-day-001.txt \
--strategy ./mymod.py
图表输出
控制台输出:
2006-01-20T23:59:59+00:00, 15, 3593.16, 3612.37, 3550.80, 3550.80, 0.00, 0.00
2006-01-23T23:59:59+00:00, 16, 3550.24, 3550.24, 3515.07, 3544.31, 0.00, 0.00
2006-01-24T23:59:59+00:00, 17, 3544.78, 3553.16, 3526.37, 3532.68, 0.00, 0.00
2006-01-25T23:59:59+00:00, 18, 3532.72, 3578.00, 3532.72, 3578.00, 0.00, 0.00
...
...
2006-12-22T23:59:59+00:00, 252, 4109.86, 4109.86, 4072.62, 4073.50, 0.00, 0.00
2006-12-27T23:59:59+00:00, 253, 4079.70, 4134.86, 4079.70, 4134.86, 0.00, 0.00
2006-12-28T23:59:59+00:00, 254, 4137.44, 4142.06, 4125.14, 4130.66, 0.00, 0.00
2006-12-29T23:59:59+00:00, 255, 4130.12, 4142.01, 4119.94, 4119.94, 0.00, 0.00
同样的策略,但:
- 将参数
period
设置为 50
命令行:
./bt-run.py --csvformat btcsv \
--data ../samples/data/sample/2006-day-001.txt \
--strategy ./mymod.py \
period 50
图表输出。
使用内置策略
backtrader
将慢慢包括样本(教科书)策略。除了bt-run.py
脚本外,还包括一个标准的简单移动平均交叉策略。姓名:
-
SMA_CrossOver
-
参数
-
快速移动平均线的快速(默认值 10)周期
-
慢速移动平均线的慢速(默认 30)周期
-
该策略在快速移动平均线与快速移动平均线交叉时买入,在快速移动平均线与慢速移动平均线交叉时卖出(仅在之前买入的情况下)。
代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.indicators as btind
class SMA_CrossOver(bt.Strategy):
params = (('fast', 10), ('slow', 30))
def __init__(self):
sma_fast = btind.SMA(period=self.p.fast)
sma_slow = btind.SMA(period=self.p.slow)
self.buysig = btind.CrossOver(sma_fast, sma_slow)
def next(self):
if self.position.size:
if self.buysig < 0:
self.sell()
elif self.buysig > 0:
self.buy()
标准执行:
./bt-run.py --csvformat btcsv \
--data ../samples/data/sample/2006-day-001.txt \
--strategy :SMA_CrossOver
请注意“:”。加载策略的标准符号(见下文)为:
- 模块:战略
遵守以下规则:
-
如果模块存在并且指定了策略,则将使用该策略
-
如果模块存在但未指定策略,则返回模块中找到的 1st策略
-
如果未指定模块,则假定“策略”指的是
backtrader
包中的策略
后者是我们的情况。
输出。
最后一个示例是添加佣金方案、现金和更改参数:
./bt-run.py --csvformat btcsv \
--data ../samples/data/sample/2006-day-001.txt \
--cash 20000 \
--commission 2.0 \
--mult 10 \
--margin 2000 \
--strategy :SMA_CrossOver \
fast 5 slow 20
输出。
我们对该策略进行了回溯测试:
-
改变移动平均周期
-
设置新的起始现金
-
为类似期货的工具制定佣金计划
查看每一条的现金连续变化,因为现金是针对期货(如工具每日变化)进行调整的
添加分析器
笔记
添加了分析器示例
bt-run.py
还支持使用与选择内部/外部分析器的策略相同的语法添加Analyzers
。
2005-2006 年SharpeRatio
分析示例:
./bt-run.py --csvformat btcsv \
--data ../samples/data/sample/2005-2006-day-001.txt \
--strategy :SMA_CrossOver \
--analyzer :SharpeRatio
输出:
====================
== Analyzers
====================
## sharperatio
-- sharperatio : 11.6473326097
好策略!!!(对于实际也不承担佣金的示例,纯粹是运气)
图表(仅显示分析仪不在绘图中,因为无法绘制分析仪,它们不是直线对象)
脚本的使用
直接从脚本:
$ ./bt-run.py --help
usage: bt-run.py [-h] --data DATA
[--csvformat {yahoocsv_unreversed,vchart,sierracsv,yahoocsv,vchartcsv,btcsv}]
[--fromdate FROMDATE] [--todate TODATE] --strategy STRATEGY
[--nostdstats] [--observer OBSERVERS] [--analyzer ANALYZERS]
[--cash CASH] [--commission COMMISSION] [--margin MARGIN]
[--mult MULT] [--noplot] [--plotstyle {bar,line,candle}]
[--plotfigs PLOTFIGS]
...
Backtrader Run Script
positional arguments:
args args to pass to the loaded strategy
optional arguments:
-h, --help show this help message and exit
Data options:
--data DATA, -d DATA Data files to be added to the system
--csvformat {yahoocsv_unreversed,vchart,sierracsv,yahoocsv,vchartcsv,btcsv}, -c {yahoocsv_unreversed,vchart,sierracsv,yahoocsv,vchartcsv,btcsv}
CSV Format
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD[THH:MM:SS] format
--todate TODATE, -t TODATE
Ending date in YYYY-MM-DD[THH:MM:SS] format
Strategy options:
--strategy STRATEGY, -st STRATEGY
Module and strategy to load with format
module_path:strategy_name. module_path:strategy_name
will load strategy_name from the given module_path
module_path will load the module and return the first
available strategy in the module :strategy_name will
load the given strategy from the set of built-in
strategies
Observers and statistics:
--nostdstats Disable the standard statistics observers
--observer OBSERVERS, -ob OBSERVERS
This option can be specified multiple times Module and
observer to load with format
module_path:observer_name. module_path:observer_name
will load observer_name from the given module_path
module_path will load the module and return all
available observers in the module :observer_name will
load the given strategy from the set of built-in
strategies
Analyzers:
--analyzer ANALYZERS, -an ANALYZERS
This option can be specified multiple times Module and
analyzer to load with format
module_path:analzyer_name. module_path:analyzer_name
will load observer_name from the given module_path
module_path will load the module and return all
available analyzers in the module :anaylzer_name will
load the given strategy from the set of built-in
strategies
Cash and Commission Scheme Args:
--cash CASH, -cash CASH
Cash to set to the broker
--commission COMMISSION, -comm COMMISSION
Commission value to set
--margin MARGIN, -marg MARGIN
Margin type to set
--mult MULT, -mul MULT
Multiplier to use
Plotting options:
--noplot, -np Do not plot the read data
--plotstyle {bar,line,candle}, -ps {bar,line,candle}
Plot style for the input data
--plotfigs PLOTFIGS, -pn PLOTFIGS
Plot using n figures
以及守则:
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import inspect
import itertools
import random
import string
import sys
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btinds
import backtrader.observers as btobs
import backtrader.strategies as btstrats
import backtrader.analyzers as btanalyzers
DATAFORMATS = dict(
btcsv=btfeeds.BacktraderCSVData,
vchartcsv=btfeeds.VChartCSVData,
vchart=btfeeds.VChartData,
sierracsv=btfeeds.SierraChartCSVData,
yahoocsv=btfeeds.YahooFinanceCSVData,
yahoocsv_unreversed=btfeeds.YahooFinanceCSVData
)
def runstrat():
args = parse_args()
stdstats = not args.nostdstats
cerebro = bt.Cerebro(stdstats=stdstats)
for data in getdatas(args):
cerebro.adddata(data)
# Prepare a dictionary of extra args passed to push them to the strategy
# pack them in pairs
packedargs = itertools.izip_longest(*[iter(args.args)] * 2, fillvalue='')
# prepare a string for evaluation, eval and store the result
evalargs = 'dict('
for key, value in packedargs:
evalargs += key + '=' + value + ','
evalargs += ')'
stratkwargs = eval(evalargs)
# Get the strategy and add it with any arguments
strat = getstrategy(args)
cerebro.addstrategy(strat, **stratkwargs)
obs = getobservers(args)
for ob in obs:
cerebro.addobserver(ob)
ans = getanalyzers(args)
for an in ans:
cerebro.addanalyzer(an)
setbroker(args, cerebro)
runsts = cerebro.run()
runst = runsts[0] # single strategy and no optimization
if runst.analyzers:
print('====================')
print('== Analyzers')
print('====================')
for name, analyzer in runst.analyzers.getitems():
print('## ', name)
analysis = analyzer.get_analysis()
for key, val in analysis.items():
print('-- ', key, ':', val)
if not args.noplot:
cerebro.plot(numfigs=args.plotfigs, style=args.plotstyle)
def setbroker(args, cerebro):
broker = cerebro.getbroker()
if args.cash is not None:
broker.setcash(args.cash)
commkwargs = dict()
if args.commission is not None:
commkwargs['commission'] = args.commission
if args.margin is not None:
commkwargs['margin'] = args.margin
if args.mult is not None:
commkwargs['mult'] = args.mult
if commkwargs:
broker.setcommission(**commkwargs)
def getdatas(args):
# Get the data feed class from the global dictionary
dfcls = DATAFORMATS[args.csvformat]
# Prepare some args
dfkwargs = dict()
if args.csvformat == 'yahoo_unreversed':
dfkwargs['reverse'] = True
fmtstr = '%Y-%m-%d'
if args.fromdate:
dtsplit = args.fromdate.split('T')
if len(dtsplit) > 1:
fmtstr += 'T%H:%M:%S'
fromdate = datetime.datetime.strptime(args.fromdate, fmtstr)
dfkwargs['fromdate'] = fromdate
fmtstr = '%Y-%m-%d'
if args.todate:
dtsplit = args.todate.split('T')
if len(dtsplit) > 1:
fmtstr += 'T%H:%M:%S'
todate = datetime.datetime.strptime(args.todate, fmtstr)
dfkwargs['todate'] = todate
datas = list()
for dname in args.data:
dfkwargs['dataname'] = dname
data = dfcls(**dfkwargs)
datas.append(data)
return datas
def getmodclasses(mod, clstype, clsname=None):
clsmembers = inspect.getmembers(mod, inspect.isclass)
clslist = list()
for name, cls in clsmembers:
if not issubclass(cls, clstype):
continue
if clsname:
if clsname == name:
clslist.append(cls)
break
else:
clslist.append(cls)
return clslist
def loadmodule(modpath, modname=''):
# generate a random name for the module
if not modname:
chars = string.ascii_uppercase + string.digits
modname = ''.join(random.choice(chars) for _ in range(10))
version = (sys.version_info[0], sys.version_info[1])
if version < (3, 3):
mod, e = loadmodule2(modpath, modname)
else:
mod, e = loadmodule3(modpath, modname)
return mod, e
def loadmodule2(modpath, modname):
import imp
try:
mod = imp.load_source(modname, modpath)
except Exception, e:
return (None, e)
return (mod, None)
def loadmodule3(modpath, modname):
import importlib.machinery
try:
loader = importlib.machinery.SourceFileLoader(modname, modpath)
mod = loader.load_module()
except Exception, e:
return (None, e)
return (mod, None)
def getstrategy(args):
sttokens = args.strategy.split(':')
if len(sttokens) == 1:
modpath = sttokens[0]
stname = None
else:
modpath, stname = sttokens
if modpath:
mod, e = loadmodule(modpath)
if not mod:
print('')
print('Failed to load module %s:' % modpath, e)
sys.exit(1)
else:
mod = btstrats
strats = getmodclasses(mod=mod, clstype=bt.Strategy, clsname=stname)
if not strats:
print('No strategy %s / module %s' % (str(stname), modpath))
sys.exit(1)
return strats[0]
def getanalyzers(args):
analyzers = list()
for anspec in args.analyzers or []:
tokens = anspec.split(':')
if len(tokens) == 1:
modpath = tokens[0]
name = None
else:
modpath, name = tokens
if modpath:
mod, e = loadmodule(modpath)
if not mod:
print('')
print('Failed to load module %s:' % modpath, e)
sys.exit(1)
else:
mod = btanalyzers
loaded = getmodclasses(mod=mod, clstype=bt.Analyzer, clsname=name)
if not loaded:
print('No analyzer %s / module %s' % ((str(name), modpath)))
sys.exit(1)
analyzers.extend(loaded)
return analyzers
def getobservers(args):
observers = list()
for obspec in args.observers or []:
tokens = obspec.split(':')
if len(tokens) == 1:
modpath = tokens[0]
name = None
else:
modpath, name = tokens
if modpath:
mod, e = loadmodule(modpath)
if not mod:
print('')
print('Failed to load module %s:' % modpath, e)
sys.exit(1)
else:
mod = btobs
loaded = getmodclasses(mod=mod, clstype=bt.Observer, clsname=name)
if not loaded:
print('No observer %s / module %s' % ((str(name), modpath)))
sys.exit(1)
observers.extend(loaded)
return observers
def parse_args():
parser = argparse.ArgumentParser(
description='Backtrader Run Script')
group = parser.add_argument_group(title='Data options')
# Data options
group.add_argument('--data', '-d', action='append', required=True,
help='Data files to be added to the system')
datakeys = list(DATAFORMATS.keys())
group.add_argument('--csvformat', '-c', required=False,
default='btcsv', choices=datakeys,
help='CSV Format')
group.add_argument('--fromdate', '-f', required=False, default=None,
help='Starting date in YYYY-MM-DD[THH:MM:SS] format')
group.add_argument('--todate', '-t', required=False, default=None,
help='Ending date in YYYY-MM-DD[THH:MM:SS] format')
# Module where to read the strategy from
group = parser.add_argument_group(title='Strategy options')
group.add_argument('--strategy', '-st', required=True,
help=('Module and strategy to load with format '
'module_path:strategy_name.\n'
'\n'
'module_path:strategy_name will load '
'strategy_name from the given module_path\n'
'\n'
'module_path will load the module and return '
'the first available strategy in the module\n'
'\n'
':strategy_name will load the given strategy '
'from the set of built-in strategies'))
# Observers
group = parser.add_argument_group(title='Observers and statistics')
group.add_argument('--nostdstats', action='store_true',
help='Disable the standard statistics observers')
group.add_argument('--observer', '-ob', dest='observers',
action='append', required=False,
help=('This option can be specified multiple times\n'
'\n'
'Module and observer to load with format '
'module_path:observer_name.\n'
'\n'
'module_path:observer_name will load '
'observer_name from the given module_path\n'
'\n'
'module_path will load the module and return '
'all available observers in the module\n'
'\n'
':observer_name will load the given strategy '
'from the set of built-in strategies'))
# Anaylzers
group = parser.add_argument_group(title='Analyzers')
group.add_argument('--analyzer', '-an', dest='analyzers',
action='append', required=False,
help=('This option can be specified multiple times\n'
'\n'
'Module and analyzer to load with format '
'module_path:analzyer_name.\n'
'\n'
'module_path:analyzer_name will load '
'observer_name from the given module_path\n'
'\n'
'module_path will load the module and return '
'all available analyzers in the module\n'
'\n'
':anaylzer_name will load the given strategy '
'from the set of built-in strategies'))
# Broker/Commissions
group = parser.add_argument_group(title='Cash and Commission Scheme Args')
group.add_argument('--cash', '-cash', required=False, type=float,
help='Cash to set to the broker')
group.add_argument('--commission', '-comm', required=False, type=float,
help='Commission value to set')
group.add_argument('--margin', '-marg', required=False, type=float,
help='Margin type to set')
group.add_argument('--mult', '-mul', required=False, type=float,
help='Multiplier to use')
# Plot options
group = parser.add_argument_group(title='Plotting options')
group.add_argument('--noplot', '-np', action='store_true', required=False,
help='Do not plot the read data')
group.add_argument('--plotstyle', '-ps', required=False, default='bar',
choices=['bar', 'line', 'candle'],
help='Plot style for the input data')
group.add_argument('--plotfigs', '-pn', required=False, default=1,
type=int, help='Plot using n figures')
# Extra arguments
parser.add_argument('args', nargs=argparse.REMAINDER,
help='args to pass to the loaded strategy')
return parser.parse_args()
if __name__ == '__main__':
runstrat()