优化改进
backtrader的 1.8.12.99 版包括了在多处理期间如何管理数据源和结果的改进。
笔记
这两种行为都已发生
这些选项的行为可以通过两个新的大脑参数进行控制:
- 
optdatas(默认为True)如果 True和优化(并且系统可以preload和使用runonce,则在主流程中只进行一次数据预加载,以节省时间和资源。
- 
optreturn(默认为True)如果 True优化结果不是完整的Strategy对象(以及所有数据、指标、观察者…),而是具有以下属性的对象(与Strategy相同):- 
params(或p)执行的策略
- 
analyzers策略已经执行
 在大多数情况下,只有分析器和参数是评估策略性能所需的东西。如果需要对(例如)指标的生成值进行详细分析,请关闭此选项 
- 
数据馈送管理
在优化场景中,这可能是大脑参数的组合:
- 
preload=True(默认)在运行任何回溯测试代码之前,将预加载数据 feeed 
- 
runonce=True(默认)指标将在回路的批次模式 a 紧下计算,而不是逐步计算。 
如果两个条件都是True和optdatas=True,则:
- 数据源将在主流程中预加载,然后再生成新的子流程(负责执行回测的子流程)
成果管理
在优化场景中,在评估每个策略运行时使用的不同参数时,两件事应该起到最重要的作用:*
- 
strategy.params(或strategy.p)用于回溯测试的实际值集 
- 
strategy.analyzers负责评估策略实际执行情况的对象。例子: SharpeRatio_A(年化SharpeRatio)
当optreturn=True时,不返回完整的策略实例,而是创建带有上述两个属性的占位符对象,以便进行评估。
这避免了传回大量生成的数据,例如在回测期间由指示器生成的值
如果希望使用全策略对象,只需在大脑实例化或执行cerebro.run时设置optreturn=False。
一些测试运行
反向交易者来源中的优化样本已经扩展,增加了对optdatas和optreturn的控制(实际上是为了禁用它们)
单芯运行
作为参考,当 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 数量的情况下,Pythonmultiprocessing模块将尝试使用所有 CPU。optdatas和optreturn将被禁用
optdata和optreturn都处于激活状态
默认行为:
$ ./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。在其他条件下,里程数可能会有所不同
总结
- 
优化过程中时间减少的最大因素是使用多核 
- 
以 optdatas和optreturn运行的样本显示速度分别约为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() 

