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 循环计算,而不是逐步计算。
如果两个条件均为True
且optdatas= True
,则:
- 在生成新的子进程(负责运行回测的子进程)之前,将在主进程中预加载数据馈送
结果管理
在优化方案中,在评估运行每个 *Strategy 的不同参数时,有两件事应该发挥最重要的作用:
strategy.params
(或strategy.p
)用于回测的实际值集
strategy. analyzers
负责评估策略实际运行情况的对象。例子:
SharpeRatio_A
(年化 SharpeRatio)
当optreturn= True
时,将创建占位符对象,而不是返回完整的策略实例,该对象带有上述两个属性以进行评估。
这避免了传回大量生成的数据,例如回测期间指针生成的值
如果需要完整的策略对象,只需在cerebro实例化期间或运行cerebro .run
时设置optreturn= False
。
一些测试运行
backtrader源中的优化示例已扩展为添加对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 数量的情况下,Python multiprocessing
模块将尝试使用所有 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()