backtrader 包括 2 种不同类型的对象,可帮助进行跟踪:
-
Observers
-
Analyzers
工单 #89 是关于添加资产基准测试的。明智的是,人们实际上可能有一个策略,即使积极,也低于简单地跟踪资产所能提供的策略。
在 Analyzers 领域已经有一个TimeReturn
对象跟踪整个投资组合价值(即:包括现金)的回报演变。
这显然也是一个 Observer,所以在添加一些基准测试的同时,一些工作也使得能够将 Observer 和分析仪插入在一起,这些工作旨在跟踪同样的事情。
注意
Observers和Analyzers之间的主要区别在于observers的lines性质,即记录每个值,并使它们适合绘图并始终用于实时查找。这当然会消耗内存。
另一方面,Analyzers返回一组结果,get_analysis
并且实现可能直到运行结束才提供任何结果。
Analyzers - 基准测试
标准TimeReturn
分析器已扩展为支持跟踪 data feed。输入的2个主要参数:
-
timeframe
(默认值:None
)如果None
届时将报告整个回溯测试期间的完整回报通过
TimeFrame.NoTimeFrame
以考虑整个数据集,没有时间限制 -
data
(默认值:None
)引用要跟踪的资产,而不是投资组合价值。
注意
此数据必须已添加到具有
addata
或resampledata
的cerebro
实例中replaydata
(有关更多详细信息和参数,请转到文档中的参考)
因此,porftolio每年的回报可以像这样跟踪:
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())
如果要跟踪两者,最好是为 analyzers分配名称:
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())
Observers - 基准测试
由于后台机械允许在Observers内部使用Analyzers,因此增加了2个新observers:
-
TimeReturn
-
Benchmark
两者都使用bt.analyzers.TimeReturn
分析器来收集结果。
与其像上面那样使用代码片段,不如运行一些完整的示例来显示其功能。
观察时间返回
运行:
$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe
输出。
请注意运行选项:
-
--timereturn
:我们告诉样品要这样做 -
--timeframe notimeframe
:告诉分析器考虑整个数据集,而不考虑时间帧边界。
last绘制的值为-0.26
。
- 起始现金(从图表中显而易见)是
50,000
货币单位,策略以36,970
货币单位结束,因此-26%
价值递减。
遵守基准测试
由于基准测试还将显示时间返回结果,因此让我们运行相同的操作,但基准测试处于活动状态:
$ ./observer-benchmark.py --plot --timeframe notimeframe
输出。
嘿 嘿 嘿!!!
-
策略作为资产更好:
-0.26
vs-0.33
这不应该是一个值得庆祝的问题,但至少很明显,战略甚至没有资产那么糟糕。
每年向下移动以跟踪事物:
$ ./observer-benchmark.py --plot --timeframe years
输出
小心!
-
策略 last 值从
-0.26
到 略有变化-0.27
-
另一方面,资产显示 last 值
-0.35
(与上面相比-0.33
)
价值如此 close 的原因是,从2005年到2006年,战略和基准资产在2005年几乎都处于起始水准。
切换到较低的时间范围,如周,整个画面会发生变化:
$ ./observer-benchmark.py --plot --timeframe weeks
输出
现在:
-
Benchmark
observer显示出更加紧张的一面。事情上下移动,因为现在weekly
投资组合的回报和数据都被跟踪 -
由于在一年last周没有交易活跃,资产几乎没有移动,last显示的值为0.00(last周之前的last收盘价为
25.54
,样本数据收盘价为25.55
,差异首先在小数点后4点感觉到)
观察基准测试 - 另一个数据
该示例允许针对不同的数据进行基准测试。默认情况下,在使用--benchdata1
时针对 Oracle 进行基准测试。考虑整个数据集:--timeframe notimeframe
$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1
输出:
现在已经很清楚为什么没有理由在上面庆祝:
-
策略的结果没有改变
notimeframe
,并保持在-26%
(-0.26
) -
但是,当与另一个数据进行基准测试时,此数据在同一
+23%
时期具有(0.23
)
要么策略需要改变,要么交易另一种资产更好。
总结
现在有两种方法,使用相同的基础代码/计算,来跟踪时间返回和基准测试
- Observers (
TimeReturn
与Benchmark
)
和
- 分析仪(
TimeReturn
带TimeReturn
data
参数)
当然,基准测试并不能保证利润,只是比较。
范例用法:
$ ./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()