Backtrader优化改进

  |  

backtrader 1.8.12.99版改进了多处理期间数据馈送和结果的管理方式。

笔记

两者的行为已经完成

这些选项的行为可以通过两个新的Cerebro参数来控制:

  • optdatas (默认值: True

    如果True和优化(并且系统可以preload和使用runonce ,数据预加载将在主进程中只进行一次,以节省时间和资源。

  • optreturn (默认值: True

    如果为True ,则优化结果将不是完整的Strategy对象(以及所有数据、指针、观察者……),而是具有以下属性的对象(与Strategy中的相同):

    • 策略用于运行的params (或p

    • analyzers策略已运行

    在大多数情况下,只有分析器和哪些参数是评估策略性能所需的东西。如果需要对(例如)指针的生成值进行详细分析,请将其关闭

数据馈送管理

在优化方案中,这可能是Cerebro参数的组合:

  • preload= True (默认)

    在运行任何回测代码之前,将预加载数据源

  • runonce= True (默认)

    指针将在批处理模式下以紧密的 for 循环计算,而不是逐步计算。

如果两个条件均为Trueoptdatas= True ,则:

  • 在生成新的子进程(负责运行回测的子进程)之前,将在主进程中预加载数据馈送

结果管理

在优化方案中,在评估运行每个 *Strategy 的不同参数时,有两件事应该发挥最重要的作用:

  • strategy.params (或strategy.p

    用于回测的实际值集

  • strategy. analyzers

    负责评估策略实际运行情况的对象。例子:

    SharpeRatio_A (年化 SharpeRatio)

optreturn= True时,将创建占位符对象,而不是返回完整的策略实例,该对象带有上述两个属性以进行评估。

这避免了传回大量生成的数据,例如回测期间指针生成的值

如果需要完整的策略对象,只需在cerebro实例化期间或运行cerebro .run时设置optreturn= False

一些测试运行

backtrader源中的优化示例已扩展为添加对optdatasoptreturn的控制(实际上是禁用它们)

单核运行

作为参考,当 CPU 数量限制为1并且未使用multiprocessing模块时会发生什么:

$ ./optimization.py --maxcpus 1
==================================================
**************************************************
--------------------------------------------------
OrderedDict([(u'smaperiod', 10), (u'macdperiod1', 12), (u'macdperiod2', 26), (u'macdperiod3', 9)])
**************************************************
--------------------------------------------------
OrderedDict([(u'smaperiod', 10), (u'macdperiod1', 13), (u'macdperiod2', 26), (u'macdperiod3', 9)])
...
...
OrderedDict([(u'smaperiod', 29), (u'macdperiod1', 19), (u'macdperiod2', 29), (u'macdperiod3', 14)])
==================================================
Time used: 184.922727833

多内核运行

在不限制 CPU 数量的情况下,Python multiprocessing模块将尝试使用所有 CPU。 optdatasoptreturn将被禁用

optdataoptreturn都处于活动状态

默认行为:

$ ./optimization.py
...
...
...
==================================================
Time used: 56.5889185394

多核以及数据馈送和结果改进的总体改进意味着从184.92秒下降到56.58秒。

考虑到样本使用252根柱线,指针仅生成长度为252点的值。这只是一个例子。

真正的问题是这在多大程度上归因于新行为。

optreturn已停用

让我们将完整的策略对像传回给调用者:

$ ./optimization.py --no-optreturn
...
...
...
==================================================
Time used: 67.056914007

运行时间增加了18.50% (或加速了15.62% )。

optdatas已停用

每个子进程都被迫为数据馈送加载自己的一组值:

$ ./optimization.py --no-optdatas
...
...
...
==================================================
Time used: 72.7238112637

运行时间增加了28.52% (或加速了22.19% )。

两者都停用

仍在使用多核但具有旧的未改进行为:

$ ./optimization.py --no-optdatas --no-optreturn
...
...
...
==================================================
Time used: 83.6246643786

运行时间增加了47.79% (或加速32.34% )到位。

这表明多核的使用是时间改进的主要贡献者。

笔记

运行是在具有i7-4710HQ (4 核 / 8 逻辑)的笔记本电脑上完成的,在 Windows 10 64 位下具有 16 GB 的 RAM。在其他条件下里程可能会有所不同

结束语

  • 优化期间减少时间的最大因素是使用多个内核

  • 使用optdatasoptreturn运行的样本分别显示了大约22.19%15.62%的加速(测试中两者加起来为32.34%

示例使用

$ ./optimization.py --help
usage: optimization.py [-h] [--data DATA] [--fromdate FROMDATE]
                       [--todate TODATE] [--maxcpus MAXCPUS] [--no-runonce]
                       [--exactbars EXACTBARS] [--no-optdatas]
                       [--no-optreturn] [--ma_low MA_LOW] [--ma_high MA_HIGH]
                       [--m1_low M1_LOW] [--m1_high M1_HIGH] [--m2_low M2_LOW]
                       [--m2_high M2_HIGH] [--m3_low M3_LOW]
                       [--m3_high M3_HIGH]

Optimization

optional arguments:
  -h, --help            show this help message and exit
  --data DATA, -d DATA  data to add to the system
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format
  --todate TODATE, -t TODATE
                        Starting date in YYYY-MM-DD format
  --maxcpus MAXCPUS, -m MAXCPUS
                        Number of CPUs to use in the optimization
                          - 0 (default): use all available CPUs
                          - 1 -> n: use as many as specified
  --no-runonce          Run in next mode
  --exactbars EXACTBARS
                        Use the specified exactbars still compatible with preload
                          0 No memory savings
                          -1 Moderate memory savings
                          -2 Less moderate memory savings
  --no-optdatas         Do not optimize data preloading in optimization
  --no-optreturn        Do not optimize the returned values to save time
  --ma_low MA_LOW       SMA range low to optimize
  --ma_high MA_HIGH     SMA range high to optimize
  --m1_low M1_LOW       MACD Fast MA range low to optimize
  --m1_high M1_HIGH     MACD Fast MA range high to optimize
  --m2_low M2_LOW       MACD Slow MA range low to optimize
  --m2_high M2_HIGH     MACD Slow MA range high to optimize
  --m3_low M3_LOW       MACD Signal range low to optimize
  --m3_high M3_HIGH     MACD Signal range high to optimize

示例代码

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime
import time

from backtrader.utils.py3 import range

import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds


class OptimizeStrategy(bt.Strategy):
    params = (('smaperiod', 15),
              ('macdperiod1', 12),
              ('macdperiod2', 26),
              ('macdperiod3', 9),
              )

    def __init__(self):
        # Add indicators to add load

        btind.SMA(period=self.p.smaperiod)
        btind.MACD(period_me1=self.p.macdperiod1,
                   period_me2=self.p.macdperiod2,
                   period_signal=self.p.macdperiod3)


def runstrat():
    args = parse_args()

    # Create a cerebro entity
    cerebro = bt.Cerebro(maxcpus=args.maxcpus,
                         runonce=not args.no_runonce,
                         exactbars=args.exactbars,
                         optdatas=not args.no_optdatas,
                         optreturn=not args.no_optreturn)

    # Add a strategy
    cerebro.optstrategy(
        OptimizeStrategy,
        smaperiod=range(args.ma_low, args.ma_high),
        macdperiod1=range(args.m1_low, args.m1_high),
        macdperiod2=range(args.m2_low, args.m2_high),
        macdperiod3=range(args.m3_low, args.m3_high),
    )

    # Get the dates from the args
    fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
    todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')

    # Create the 1st data
    data = btfeeds.BacktraderCSVData(
        dataname=args.data,
        fromdate=fromdate,
        todate=todate)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # clock the start of the process
    tstart = time.clock()

    # Run over everything
    stratruns = cerebro.run()

    # clock the end of the process
    tend = time.clock()

    print('==================================================')
    for stratrun in stratruns:
        print('**************************************************')
        for strat in stratrun:
            print('--------------------------------------------------')
            print(strat.p._getkwargs())
    print('==================================================')

    # print out the result
    print('Time used:', str(tend - tstart))


def parse_args():
    parser = argparse.ArgumentParser(
        description='Optimization',
        formatter_class=argparse.RawTextHelpFormatter,
    )

    parser.add_argument(
        '--data', '-d',
        default='../../datas/2006-day-001.txt',
        help='data to add to the system')

    parser.add_argument(
        '--fromdate', '-f',
        default='2006-01-01',
        help='Starting date in YYYY-MM-DD format')

    parser.add_argument(
        '--todate', '-t',
        default='2006-12-31',
        help='Starting date in YYYY-MM-DD format')

    parser.add_argument(
        '--maxcpus', '-m',
        type=int, required=False, default=0,
        help=('Number of CPUs to use in the optimization'
              '\n'
              '  - 0 (default): use all available CPUs\n'
              '  - 1 -> n: use as many as specified\n'))

    parser.add_argument(
        '--no-runonce', action='store_true', required=False,
        help='Run in next mode')

    parser.add_argument(
        '--exactbars', required=False, type=int, default=0,
        help=('Use the specified exactbars still compatible with preload\n'
              '  0 No memory savings\n'
              '  -1 Moderate memory savings\n'
              '  -2 Less moderate memory savings\n'))

    parser.add_argument(
        '--no-optdatas', action='store_true', required=False,
        help='Do not optimize data preloading in optimization')

    parser.add_argument(
        '--no-optreturn', action='store_true', required=False,
        help='Do not optimize the returned values to save time')

    parser.add_argument(
        '--ma_low', type=int,
        default=10, required=False,
        help='SMA range low to optimize')

    parser.add_argument(
        '--ma_high', type=int,
        default=30, required=False,
        help='SMA range high to optimize')

    parser.add_argument(
        '--m1_low', type=int,
        default=12, required=False,
        help='MACD Fast MA range low to optimize')

    parser.add_argument(
        '--m1_high', type=int,
        default=20, required=False,
        help='MACD Fast MA range high to optimize')

    parser.add_argument(
        '--m2_low', type=int,
        default=26, required=False,
        help='MACD Slow MA range low to optimize')

    parser.add_argument(
        '--m2_high', type=int,
        default=30, required=False,
        help='MACD Slow MA range high to optimize')

    parser.add_argument(
        '--m3_low', type=int,
        default=9, required=False,
        help='MACD Signal range low to optimize')

    parser.add_argument(
        '--m3_high', type=int,
        default=15, required=False,
        help='MACD Signal range high to optimize')

    return parser.parse_args()


if __name__ == '__main__':
    runstrat()

推荐阅读

相关文章

Backtrader佣金计划

backtrader 的诞生是出于必要。我自己的...有一种感觉,我控制着自己的回溯测试平台,可以尝试新的想法。但是,在这样做并且从一开始就完全 open 采购它时,很明显它必须有一种方法来满足他人的需求和愿望。 作为未来的交易者,我本可以选择基于点的计算和每轮佣金的固定价格,但这将是一个错误。

Backtrader教程:数据馈送 - 重新采样

如果数据仅在单个时间范围内可用,并且必须在不同的时间范围内进行分析,则是时候进行一些重新采样了。 “重采样”实际上应该称为“上采样”,因为一个人从源时间帧到更大的时间帧(例如:几天到几周) backtrader 内置支持通过筛选器对象传递原始数据,从而进行重采样。

BacktraderOCO订单

Release1.9.34.116 将 OCO (又名One Cancel Others)添加到回溯测试武器库中。 注意 这仅在回溯测试中实现,并且还没有针对即时代理的实现 注意 随版本1.9.36.116更新。盈透证券支持 StopTrail和 StopTrailLimit OCO。

Backtrader同步不同市场

使用次数越多, backtrader 必须面对的想法和意外场景的混合就越多。对于每个新平台,一个挑战是要看看平台是否能够达到开发开始时设置的期望,灵活性和易用性是目标,Python被选为基石。 工单#76 提出了一个问题,即是否可以完成具有不同交易日历的同步市场。

Backtrader股票筛选

在寻找其他一些东西时,我在StackOverlow家族网站之一上遇到了一个问题:Quantitative Finance aka Quant StackExchange。问题: 它被标记为Python,因此值得一看的是 backtrader 是否能够胜任这项任务。 分析仪本身 该问题似乎适合用于简单的分析器。

Backtrader教程:数据馈送 - 展期交割

并非每个供应商都为可以交易的工具提供连续的未来。有时提供的数据是仍然有效的到期日期的数据,即:仍在交易的日期 这在回溯测试方面并不是很有帮助,因为数据分散在几个不同的仪器上,这些仪器另外...时间重叠。 能够正确地将这些仪器的数据从过去连接到连续的流中,可以减轻疼痛。

Backtrader扩展数据馈送

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

Backtrader佣金计划

发布 backtrader 使用示例使我对缺失的东西有了深刻的了解。

Backtrader教程:分析仪 - PyFolio

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

Backtrader动量策略

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