基准测试#

Ticket #89 <https://github.com/mementum/backtrader/issues/89> _ 是关于

添加针对资产的基准测试的。这是很明智的,因为人们可能会拥有一种策略, 即使是正面的,但其表现仍然低于简单跟踪该资产所获得的结果。

backtrader 包含了两种不同类型的对象,可以帮助跟踪:

  • 观察者

  • 分析器

分析器 领域中,已经有了一个名为 TimeReturn 的对象,用于跟踪整个投资组合价值的回报率的演变(即:包括现金)

这当然也可以是一个 观察者 ,因此在添加一些 基准测试 的功能时,还可以将一个 观察者 和一个 分析器 组合在一起,用于跟踪相同的内容。

注意

观察者 和*分析器 之间的主要区别是 观察者 的*lines 特性,它记录每个值并且适合于绘图和实时查询。但这当然会占用内存。

另一方面, 分析器 通过 get_analysis 返回一组结果,而且在*运行*结束之前可能不会提供任何结果。

分析器 - 基准测试 ************************标准的 TimeReturn 分析器已扩展支持跟踪 数据源 。两个主要参数如下:

  • timeframe (默认值: None ) 如果为 None ,则将报告整个回测期间的完整收益

    传递 TimeFrame.NoTimeFrame 以考虑没有时间约束的整个数据集

  • data (默认值: None

    要跟踪的参考资产,而不是投资组合价值。

    注意

    此数据必须使用 cerebro 实例的 addataresampledatareplaydata 方法添加

有关详细信息和参数,请参阅: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*,使用相同的底层代码/计算:

  • ObserversTimeReturnBenchmark

  • AnalyzerTimeReturn``和``使用``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()