Backtrader标杆

  |  

backtrader 包括 2 种不同类型的对象,可帮助进行跟踪:

  • Observers

  • Analyzers

工单 #89 是关于添加资产基准测试的。明智的是,人们实际上可能有一个策略,即使积极,也低于简单地跟踪资产所能提供的策略。

Analyzers 领域已经有一个TimeReturn 对象跟踪整个投资组合价值(即:包括现金)的回报演变。

这显然也是一个 Observer,所以在添加一些基准测试的同时,一些工作也使得能够将 Observer 和分析仪插入在一起,这些工作旨在跟踪同样的事情。

注意

ObserversAnalyzers之间的主要区别在于observerslines性质,即记录每个值,并使它们适合绘图并始终用于实时查找。这当然会消耗内存。

另一方面,Analyzers返回一组结果,get_analysis并且实现可能直到运行结束才提供任何结果。

Analyzers - 基准测试

标准TimeReturn 分析器已扩展为支持跟踪 data feed。输入的2个主要参数:

  • timeframe (默认值: None)如果 None 届时将报告整个回溯测试期间的完整回报

    通过TimeFrame.NoTimeFrame 以考虑整个数据集,没有时间限制

  • data (默认值: None

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

    注意

    此数据必须已添加到具有 addataresampledatacerebro实例中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

要么策略需要改变,要么交易另一种资产更好。

总结

现在有两种方法,使用相同的基础代码/计算,来跟踪时间返回和基准测试

  • ObserversTimeReturnBenchmark

  • 分析仪(TimeReturnTimeReturndata参数)

当然,基准测试并不能保证利润,只是比较。

范例用法:

$ ./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()

推荐阅读

相关文章

Backtrader按日线交易

似乎在世界某个地方有一种权益(Interest)可以总结如下: 使用每日柱线引入订单,但使用开盘价 这来自工单#105订单执行逻辑与当前数据和#101动态投注计算中的对话 backtrader 尝试尽可能保持现实,并且在处理每日柱线时适用以下前提: 当每日柱被评估时,柱线已经结束 这是有道理的,

Backtrader教程:数据馈送 - 扩展(Extending DataFeed)

GitHub 中的问题实际上是在推动文档部分的完成,或者説明我了解我是否backtrader 具有我从一开始就设想的易用性和灵活性以及在此过程中做出的决定。 在本例中为问题 #9。

Backtrader教程:Cerebro - 优化 - 改进

backtrader版本1.8.12.99改进了在多处理过程中管理data feeds和结果的方式。

Backtrader智能质押

版本 1.6.4.93 标志着 backtrader 的一个重要里程碑,即使版本号的更改很小。 职位大小调整是阅读Van K. Tharp的《Trade Your Way To Financial Freedom 》后,为这个项目奠定基础的事情之一。

Backtrader教程:日期时间 - 管理

在 1.5.0 版之前, backtrader 使用直接的方法来进行时间管理,因为数据源计算的任何日期时间都只是按面值使用。 对于任何用户输入也是如此,例如可以提供给任何数据源的参数fromdate (或 sessionstart)的情况 考虑到直接控制冻结的数据源以进行回溯测试,这种方法很好。

Backtrader蟒蛇隐藏的力量3

Last,但并非最不重要的一点是,在这个系列中,关于如何在 backtrader 中使用Python的隐藏功能是一些神奇变量是如何出现的。

Backtrader教程:数据馈送 - 熊猫

注意 pandas 并且必须安装其依赖项 支持Pandas Dataframes似乎受到很多人的关注,他们依赖于已经可用的解析代码来分析不同的数据源(包括CSV)和Pandas提供的其他功能。 数据馈送的重要声明。 注意 这些只是 声明。不要盲目拷贝此代码。

Backtrader教程:分析仪 - PyFolio

注意 从(至少)2017-07-25pyfolio 开始,API已更改,不再 create_full_tear_sheet 具有 gross_lev 作为命名参数的参数。

Backtrader信贷利息

在某些情况下,真实经纪人的现金金额可能会减少,因为资产操作包括利率。例子: 卖空股票 交易所买卖基金包括多头和空头 这意味着不仅交易构成了系统的盈利能力,因为信贷上的利息在帐户上佔有一席之地。 为了涵盖这种情况, backtrader 包括(从发佈1.8.8.96开始)功能来考虑这一点。

Backtrader动量策略

在另一篇伟大的文章中,泰迪·科克(Teddy Koker)再次展示了算法交易策略的发展之路: 研究优先应用 pandas 回溯测试,然后使用 backtrader 荣誉!!! 该帖子可以在以下位置找到: 泰迪·科克(Teddy Koker)给我留言,问我是否可以评论 backtrader的用法。