基准测试#
Ticket #89 <https://github.com/mementum/backtrader/issues/89> _ 是关于
添加针对资产的基准测试的。这是很明智的,因为人们可能会拥有一种策略, 即使是正面的,但其表现仍然低于简单跟踪该资产所获得的结果。
backtrader 包含了两种不同类型的对象,可以帮助跟踪:
观察者
分析器
在 分析器 领域中,已经有了一个名为 TimeReturn
的对象,用于跟踪整个投资组合价值的回报率的演变(即:包括现金)
这当然也可以是一个 观察者 ,因此在添加一些 基准测试 的功能时,还可以将一个 观察者 和一个 分析器 组合在一起,用于跟踪相同的内容。
注意
观察者 和*分析器 之间的主要区别是 观察者 的*lines 特性,它记录每个值并且适合于绘图和实时查询。但这当然会占用内存。
另一方面, 分析器 通过 get_analysis
返回一组结果,而且在*运行*结束之前可能不会提供任何结果。
分析器 - 基准测试
************************标准的 TimeReturn
分析器已扩展支持跟踪 数据源 。两个主要参数如下:
timeframe
(默认值:None
) 如果为None
,则将报告整个回测期间的完整收益传递
TimeFrame.NoTimeFrame
以考虑没有时间约束的整个数据集
data
(默认值:None
)要跟踪的参考资产,而不是投资组合价值。
注意
此数据必须使用
cerebro
实例的addata
、resampledata
或replaydata
方法添加
有关详细信息和参数,请参阅:doc:../analyzers-reference。
因此,可以像这样跟踪投资组合的年度收益
import backtrader as bt```
cerebro = bt.Cerebro() cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
… # 添加数据和策略 …
results = cerebro.run() strat0 = results[0]
# 如果没有指定名称,名称将是类名的小写形式 tret_analyzer = strat0.analyzers.getbyname(‘timereturn’) print(tret_analyzer.get_analysis()) ```
如果我们想追踪一个 数据 的回报率
```python 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)
results = cerebro.run() strat0 = results[0]
# 如果没有指定名称,则名称为类名的小写形式 tret_analyzer = strat0.analyzers.getbyname(‘timereturn’) print(tret_analyzer.get_analysis()) ```
如果两者都要跟踪,则最好为 analyzers 分配名称
```python 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’)
``` … # 添加策略 …
results = cerebro.run() strat0 = results[0]
# 如果没有指定名称,名称就是类名转换为小写 tret_analyzer = strat0.analyzers.getbyname(‘timereturns’) print(tret_analyzer.get_analysis()) tdata_analyzer = strat0.analyzers.getbyname(‘datareturns’) print(tdata_analyzer.get_analysis())
观察者 - 基准测试#
在 观察者 中使用 分析器 的背景机制的帮助下,增加了2个新的观察者:
TimeReturn
Benchmark
两者都使用 bt.analyzers.TimeReturn
分析器收集结果。
不再提供如上所示的代码片段,而是提供一个完整示例以展示它们的功能。
观察 TimeReturn#
执行: $ ./observer-benchmark.py –plot –timereturn –timeframe notimeframe
注意执行选项:
--timereturn
告诉示例执行此操作
--timeframe notimeframe
告诉分析器考虑整个数据集,不考虑时间范围。
最后的绘图值为 -0.26
。
起始现金(从图表上可以看出)为
50K
货币单位,策略最终以36,970
货币单位结束,因此价值减少了-26%
。
观察基准测试#
由于 基准测试 还会显示 时间回报 的结果,让我们运行相同的命令,但启用 基准测试 功能:
$ ./observer-benchmark.py –plot –timeframe notimeframe.. 缩略图:: 02-benchmarking-no-timeframe.png
嘿,嘿,嘿!!
策略比资产更好:
-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
现在:
Benchmark
观察者显示了一个更加紧张的状态。事情上上下下,因为现在追踪的是weekly
返回的投资组合和数据。由于在年底的最后一周没有交易活动,并且资产几乎没有波动,最后显示的数值为0.00(最后一周之前的最后收盘价为
25.54
,样本数据收盘价为25.55
,差异首先在第4位小数点上有所感觉)。
观察基准对照 - 另一个数据#
样本允许对照不同的数据进行基准测试。默认情况下,使用 --benchdata1
时,默认与 Oracle 进行基准测试。考虑整个数据集,使用 --timeframe notimeframe
进行基准测试:
`
$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1
`
.. thumbnail:: 05-benchmarking-data1-no-timeframe.png
现在很明显为什么没有理由庆祝:
策略的结果对于
notimeframe
没有改变,仍然是-26%
(-0.26
)但是,当与另一份数据进行基准对比时,该数据在同一时期内的增长为
+23%
(0.23
)
要么策略需要改变,要么应该交易另一种资产。
总结 *** **
现在有两种方式可以跟踪 TimeReturn 和*Benchmark*,使用相同的底层代码/计算:
Observers (
TimeReturn
和Benchmark
)
和
Analyzer (
TimeReturn``和``使用``data``参数的``TimeReturn
)当然,“基准测试”并不能保证盈利,只是“比较”而已。
使用示例:
$ ./observer-benchmark.py –help 用法: 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]]
基准测试/时间回报观察器示例
- 可选参数:
- -h, --help
显示此帮助消息并退出
- --data0 DATA0
要读入的Data0(默认值:../../datas/yhoo-1996-2015.txt)
- --data1 DATA1
要读入的Data1(默认值:../../datas/orcl-1995-2014.txt)
- --benchdata1
与data1进行基准测试(默认值:False)
- --fromdate FROMDATE
开始日期,格式为YYYY-MM-DD(默认值:2005-01-01)
- --todate TODATE
结束日期,格式为YYYY-MM-DD(默认值:2006-12-31)
- --printout
打印数据行(默认值:False)
- --cash CASH
起始现金(默认值:50000)
- --period PERIOD
交叉移动平均线的周期(默认值:30)
- --stake STAKE
买入操作的股权(默认值:1000)
- --timereturn
使用时间回报观察器而不是基准测试(默认值:None)
- –timeframe {months,days,notimeframe,years,None,weeks}
适用于观察器的时间框架(默认值:None)
- –plot [kwargs], -p [kwargs]
绘制读取的数据并应用任何传递的kwargs。例如:–plot style=”candle”(绘制蜡烛图)(默认值: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()