基准测试
原文:
www.backtrader.com/blog/posts/2016-07-22-benchmarking/benchmarking/
backtrader包括两种不同类型的对象,可以帮助跟踪:
- 观察者
- 分析器
问题 #89是关于添加针对资产的基准测试。这是合理的,因为一个人可能实际上有一种策略,即使是正的,也低于简单跟踪资产所能提供的回报。
在分析器领域中,已经有一个TimeReturn
对象,用于跟踪整个投资组合价值的回报演变(即:包括现金)
这显然也可以是一个观察者,因此在添加一些基准测试的同时,还进行了一些工作,以便能够将观察者和分析器组合在一起,这两者都旨在跟踪相同的内容。
注意
观察者和分析器之间的主要区别是观察者的线性质,记录每��值并使其适合绘图和实时查询。当然,这会消耗内存。
另一方面,分析器通过get_analysis
返回一组结果,实现可能直到运行结束才提供任何结果。
分析器 - 基准测试
标准TimeReturn
分析器已扩展以支持跟踪数据源。涉及的两个主要参数:
timeframe
(默认:None
)如果为None
,则将报告整个回测期间的完整回报
传递TimeFrame.NoTimeFrame
以考虑整个数据集而没有时间限制data
(默认:None
)
用于跟踪而不是投资组合价值的参考资产。
注意
这些数据必须已经通过addata
、resampledata
或replaydata
添加到cerebro
实例中
(有关更多详细信息和参数,请参阅文档中的参考资料)
因此,可以像这样跟踪投资组合的年度回报:
import backtrader as bt cerebro = bt.Cerebro() cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years) ... # add datas, strategies ... results = cerebro.run() strat0 = results[0] # If no name has been specified, the name is the class name lowercased tret_analyzer = strat0.analyzers.getbyname('timereturn') print(tret_analyzer.get_analysis())
如果我们想要跟踪数据的回报:
import backtrader as bt cerebro = bt.Cerebro() data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...) cerebro.adddata(data) cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years, data=data) ... # add strategies ... results = cerebro.run() strat0 = results[0] # If no name has been specified, the name is the class name lowercased tret_analyzer = strat0.analyzers.getbyname('timereturn') print(tret_analyzer.get_analysis())
如果两者都要被跟踪,最好给分析器分配名称:
import backtrader as bt cerebro = bt.Cerebro() data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...) cerebro.adddata(data) cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years, data=data, _name='datareturns') cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years) _name='timereturns') ... # add strategies ... results = cerebro.run() strat0 = results[0] # If no name has been specified, the name is the class name lowercased tret_analyzer = strat0.analyzers.getbyname('timereturns') print(tret_analyzer.get_analysis()) tdata_analyzer = strat0.analyzers.getbyname('datareturns') print(tdata_analyzer.get_analysis())
观察者 - 基准测试
由于背景机制允许在观察者内部使用分析器,因此添加了 2 个新的观察者:
TimeReturn
基准
两者都使用bt.analyzers.TimeReturn
分析器来收集结果。
而不是像上面那样有代码片段,一个完整的示例带有一些运行来展示它们的功能。
观察 TimeReturn
执行:
$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe
输出。
注意执行选项:
--timereturn
:我们告诉示例就是这样做的--timeframe notimeframe
:告诉分析器考虑整个数据集,而不考虑时间范围边界。
最后绘制的数值为-0.26
。
- 起始现金(从图表中明显)为
50,000
货币单位,策略最终以36,970
货币单位结束,因此价值下降了-26%
。
观察基准测试
因为基准也会显示时间回报率的结果,让我们运行相同的内容,但激活基准:
$ ./observer-benchmark.py --plot --timeframe notimeframe
输出。
嘿,嘿,嘿!!!
- 策略比资产更好:
-0.26
对-0.33
这不应该是一个值得庆祝的事情,但至少很明显策略甚至没有资产那么糟糕。
转而以年度为基础追踪事物:
$ ./observer-benchmark.py --plot --timeframe years
输出
注意!
- 策略的最后一个值从
-0.26
变化到-0.27
- 另一方面,资产显示的最后一个值是
-0.35
(与上述的-0.33
相比)
两者值如此接近的原因是,从 2005 年到 2006 年,无论是策略还是基准资产,几乎都处于 2005 年的起始水平。
转换到较低的时间范围,比如周,整个情况都会改变:
$ ./observer-benchmark.py --plot --timeframe weeks
输出
现在:
- 基准 观察者显示出更加紧张的状态。事物上下移动,因为现在每周都在跟踪组合和数据的回报率
- 因为在年底的最后一周没有交易活动,资产几乎没有变动,所以最后显示的值为 0.00(最后一周之前的最后收盘价为
25.54
,样本数据收盘价为25.55
,差异首先在第 4 位小数点上感受到)
观察基准 - 另一数据
该样本允许针对不同的数据进行基准测试。默认情况下,使用--benchdata1
时,基准测试的对象是Oracle。考虑整个数据集,使用--timeframe notimeframe
:
$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1
输出:
现在清楚为什么上面没有值得庆祝的原因了:
- 策略的结果对于
notimeframe
没有改变,仍然为-26%
(-0.26
) - 但是当与另一数据进行基准比较时,这个数据在同一时期有
+23%
(0.23
)的增长
要么策略需要改变,要么另一种资产最好交易。
结论
现在有两种方法,使用相同的底层代码/计算,来跟踪时间回报率和基准
- 观察者(
TimeReturn
和Benchmark
)
而
- 分析器(带有
data
参数的TimeReturn
和TimeReturn
)
当然,基准 不保证盈利,只是比较。
使用样本的方法:
$ ./observer-benchmark.py --help usage: observer-benchmark.py [-h] [--data0 DATA0] [--data1 DATA1] [--benchdata1] [--fromdate FROMDATE] [--todate TODATE] [--printout] [--cash CASH] [--period PERIOD] [--stake STAKE] [--timereturn] [--timeframe {months,days,notimeframe,years,None,weeks}] [--plot [kwargs]] Benchmark/TimeReturn Observers Sample optional arguments: -h, --help show this help message and exit --data0 DATA0 Data0 to be read in (default: ../../datas/yhoo-1996-2015.txt) --data1 DATA1 Data1 to be read in (default: ../../datas/orcl-1995-2014.txt) --benchdata1 Benchmark against data1 (default: False) --fromdate FROMDATE Starting date in YYYY-MM-DD format (default: 2005-01-01) --todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31) --printout Print data lines (default: False) --cash CASH Cash to start with (default: 50000) --period PERIOD Period for the crossover moving average (default: 30) --stake STAKE Stake to apply for the buy operations (default: 1000) --timereturn Use TimeReturn observer instead of Benchmark (default: None) --timeframe {months,days,notimeframe,years,None,weeks} TimeFrame to apply to the Observer (default: None) --plot [kwargs], -p [kwargs] Plot the read data applying any kwargs passed For example: --plot style="candle" (to plot candles) (default: None)
代码
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import random import backtrader as bt class St(bt.Strategy): params = ( ('period', 10), ('printout', False), ('stake', 1000), ) def __init__(self): sma = bt.indicators.SMA(self.data, period=self.p.period) self.crossover = bt.indicators.CrossOver(self.data, sma) def start(self): if self.p.printout: txtfields = list() txtfields.append('Len') txtfields.append('Datetime') txtfields.append('Open') txtfields.append('High') txtfields.append('Low') txtfields.append('Close') txtfields.append('Volume') txtfields.append('OpenInterest') print(','.join(txtfields)) def next(self): if self.p.printout: # Print only 1st data ... is just a check that things are running txtfields = list() txtfields.append('%04d' % len(self)) txtfields.append(self.data.datetime.datetime(0).isoformat()) txtfields.append('%.2f' % self.data0.open[0]) txtfields.append('%.2f' % self.data0.high[0]) txtfields.append('%.2f' % self.data0.low[0]) txtfields.append('%.2f' % self.data0.close[0]) txtfields.append('%.2f' % self.data0.volume[0]) txtfields.append('%.2f' % self.data0.openinterest[0]) print(','.join(txtfields)) if self.position: if self.crossover < 0.0: if self.p.printout: print('CLOSE {} @%{}'.format(size, self.data.close[0])) self.close() else: if self.crossover > 0.0: self.buy(size=self.p.stake) if self.p.printout: print('BUY {} @%{}'.format(self.p.stake, self.data.close[0])) TIMEFRAMES = { None: None, 'days': bt.TimeFrame.Days, 'weeks': bt.TimeFrame.Weeks, 'months': bt.TimeFrame.Months, 'years': bt.TimeFrame.Years, 'notimeframe': bt.TimeFrame.NoTimeFrame, } def runstrat(args=None): args = parse_args(args) cerebro = bt.Cerebro() cerebro.broker.set_cash(args.cash) dkwargs = dict() if args.fromdate: fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') dkwargs['fromdate'] = fromdate if args.todate: todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') dkwargs['todate'] = todate data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs) cerebro.adddata(data0, name='Data0') cerebro.addstrategy(St, period=args.period, stake=args.stake, printout=args.printout) if args.timereturn: cerebro.addobserver(bt.observers.TimeReturn, timeframe=TIMEFRAMES[args.timeframe]) else: benchdata = data0 if args.benchdata1: data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **dkwargs) cerebro.adddata(data1, name='Data1') benchdata = data1 cerebro.addobserver(bt.observers.Benchmark, data=benchdata, timeframe=TIMEFRAMES[args.timeframe]) cerebro.run() if args.plot: pkwargs = dict() if args.plot is not True: # evals to True but is not True pkwargs = eval('dict(' + args.plot + ')') # args were passed cerebro.plot(**pkwargs) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Benchmark/TimeReturn Observers Sample') parser.add_argument('--data0', required=False, default='../../datas/yhoo-1996-2015.txt', help='Data0 to be read in') parser.add_argument('--data1', required=False, default='../../datas/orcl-1995-2014.txt', help='Data1 to be read in') parser.add_argument('--benchdata1', required=False, action='store_true', help=('Benchmark against data1')) parser.add_argument('--fromdate', required=False, default='2005-01-01', help='Starting date in YYYY-MM-DD format') parser.add_argument('--todate', required=False, default='2006-12-31', help='Ending date in YYYY-MM-DD format') parser.add_argument('--printout', required=False, action='store_true', help=('Print data lines')) parser.add_argument('--cash', required=False, action='store', type=float, default=50000, help=('Cash to start with')) parser.add_argument('--period', required=False, action='store', type=int, default=30, help=('Period for the crossover moving average')) parser.add_argument('--stake', required=False, action='store', type=int, default=1000, help=('Stake to apply for the buy operations')) parser.add_argument('--timereturn', required=False, action='store_true', default=None, help=('Use TimeReturn observer instead of Benchmark')) parser.add_argument('--timeframe', required=False, action='store', default=None, choices=TIMEFRAMES.keys(), help=('TimeFrame to apply to the Observer')) # 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: return parser.parse_args(pargs) return parser.parse_args() if __name__ == '__main__': runstrat()
Pyfolio 集成
原文:
www.backtrader.com/blog/posts/2016-07-17-pyfolio-integration/pyfolio-integration/
注意
2017 年 2 月
pyfolio
的 API 已更改,create_full_tear_sheet
不再具有gross_lev
作为命名参数。
因此,下面的示例不起作用
初次查看教程时,由于 zipline 和 pyfolio 之间的紧密集成,被认为很难,但是 pyfolio 提供的用于其他用途的样本测试数据实际上非常有用,可以解码幕后运行的内容,从而实现集成的奇迹。
一个名为pyfolio
的投资组合工具的集成是在Ticket #108中提出的。
大部分组件已经就位在backtrader中:
- 分析器基础设施
- 子分析器
- 一个 TimeReturn 分析器
只需要一个主要的PyFolio
分析器和 3 个简单的子分析器。再加上一个依赖于pyfolio
已经需要的pandas
的方法。
最具挑战性的部分是…“确保所有依赖项正确”。
- 更新
pandas
- 更新
numpy
- 更新
scikit-lean
- 更新
seaborn
在类 Unix 环境中使用C编译器,一切都是关于时间的。在 Windows 下,即使安装了特定的Microsoft编译器(在本例中是Python 2.7的链),事情也会失败。但是一个收集最新软件包的著名网站对Windows有所帮助。如果您需要,请访问:
如果没有经过测试,集成将不完整,这就是为什么通常的示例总是存在的原因。
没有 PyFolio
该示例使用random.randint
来决定何时买入/卖出,因此这只是一个检查事情是否正常运行的方法:
$ ./pyfoliotest.py --printout --no-pyfolio --plot
输出:
Len,Datetime,Open,High,Low,Close,Volume,OpenInterest 0001,2005-01-03T23:59:59,38.36,38.90,37.65,38.18,25482800.00,0.00 BUY 1000 @%23.58 0002,2005-01-04T23:59:59,38.45,38.54,36.46,36.58,26625300.00,0.00 BUY 1000 @%36.58 SELL 500 @%22.47 0003,2005-01-05T23:59:59,36.69,36.98,36.06,36.13,18469100.00,0.00 ... SELL 500 @%37.51 0502,2006-12-28T23:59:59,25.62,25.72,25.30,25.36,11908400.00,0.00 0503,2006-12-29T23:59:59,25.42,25.82,25.33,25.54,16297800.00,0.00 SELL 250 @%17.14 SELL 250 @%37.01
有 3 个数据和几个买入和卖出操作是随机选择并分散在测试运行的默认 2 年寿命中
BackTrader 中文文档(二十三)(2)https://developer.aliyun.com/article/1505435