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磚塊

Renko Bricks 是呈現價格演變的另一種方式,其中價格比時間發揮更重要的作用。這已在1.9.54.122的1.9.54.122版本中作為過濾器引入Stockcharts 對 Renko Bricks 有很好的參考。

Backtrader寫下來

隨著 1.1.7.88 版本的發布, backtrader有了一個新的補充:作家這可能早就到期了,應該已經存在了,問題 #14中的討論也應該已經開始了開發。但遲到總比沒有好。

Backtrader教程:篩檢程式

此功能是 backtrader 的相對較新的補充,必須安裝到已經存在的內部結構中。這使得它不像希望的那樣靈活且100%功能齊全,但在許多情況下它仍然可以達到目的。 儘管該實現試圖允許隨插即用的篩檢程式連結,但預先存在的內部結構使得很難確保始終可以實現。因此,某些篩選器可能是連結的,而其他一些篩選器可能不是。

Backtrader 教程:經紀人 - 滑點

回測不能保證真實的市場狀況。無論市場模擬有多好,在真實的市場條件下都會發生滑點。這意味著:要求的價格可能不匹配。集成的回測代理支持滑點。

Backtrader數據多時間幀

有時投資決策是使用不同的時間框架做出的: 每周評估趨勢 每天執行條目 或者5分鐘對60分鐘。 這意味著需要將多個時間幀的數據組合在 backtrader 中以支援此類組合。 對它的本機支持已經內置。

Backtrader教程:繪圖 - 日期範圍

該版本1.9.31.x 增加了製作部分繪圖的功能。 使用策略實例中保存的完整時間戳陣列的索引 或者使用實際datetime.date 或 datetime.datetime 實例來限制必須繪製的內容。 一切都超過標準cerebro.plot。

Backtrader教程:經紀商

經紀商模擬器該類比支援不同的訂單類型,根據當前現金檢查提交的訂單現金需求,跟蹤每次反覆運算的cerebro 現金和價值,並在不同數據上保持當前位置。

Backtrader卡爾曼等

注意 對以下指令的支援從提交開始 發佈1.9.30.x 將是包含它的第1個版本 。 backtrader的原始目標之一是成為純python,即:僅使用標準發行版中可用的軟體包。只有一個例外是matplotlib在沒有重新發明輪子的情況下進行繪圖。

Backtrader 教程:訂單 - 目標訂單

在1.8.10.96版本之前,可以使用反向交易者通過策略方法進行智能質押: buy和sell 。這一切都是為了在負責賭注大小的等式中添加一個Sizer 。 Sizer不能做的是決定操作是買入還是賣出。這意味著需要一個新概念,在其中添加一個小的智能層來做出這樣的決定。

Backtrader教程:指標 - ta-lib

即使 backtrader 提供了已經 high 數量的內置指標,並且開發指標主要是定義輸入,輸出和以自然的方式編寫公式的問題,有些人也希望使用TA-LIB。