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()