Backtrader節省記憶體

  |  

1.3.1.92版本已經重新設計並完全實現了以前到位的記憶體節省方案,儘管沒有太多的吹捧和使用。

發佈:https://github.com/mementum/backtrader/發佈/標籤/1.3.1.92

backtrader 在具有大量RAM的機器中(並將進一步開發),並且通過繪圖進行視覺反饋是一個很好的,幾乎是必須的,因此很容易做出設計決策:將所有內容保存在記憶體中。

這個決定有一些缺點:

  • array.array 用於數據存儲的,當超出某些邊界時必須分配和行動數據

  • RAM 量 low 的計算機可能會受到影響

  • 連接到即時 data feed 可以在線數周/數月,將數千秒/分鐘的解析度滴答輸入系統

後者甚至比第1個更重要, 因為另一個設計決策是為 backtrader

  • 如果需要,可以純Python允許在嵌入式系統中運行

    未來的場景可以backtrader 連接到提供即時饋送的第 2台機器,而它本身在 backtrader Raspberry Pi或更受限制的東西(如ADSL路由器(AVM Frit!帶有Freetz圖像的框7490)

因此,需要有backtrader 支持動態記憶體方案。現在可以 Cerebro 實例化或 run 使用以下語義:

  • 精確柱 (預設值: False

    使用預設值False ,存儲在 line 中的每個值都保存在記憶體中

    可能的值:

    - `True` or `1`: all “lines” objects reduce memory usage to the
      automatically calculated minimum period.
    
      If a Simple Moving Average has a period of 30, the underlying data
      will have always a running buffer of 30 bars to allow the
      calculation of the Simple Moving Average
    
      - This setting will deactivate `preload` and `runonce`
    
      - Using this setting also deactivates **plotting**
    
    - `-1`: datas and indicators/operations at strategy level will keep
      all data in memory.
    
      For example: a `RSI` internally uses the indicator `UpDay` to
      make calculations. This subindicator will not keep all data in
      memory
    
      - This allows to keep `plotting` and `preloading` active.
    
      - `runonce` will be deactivated
    
    - `-2`: datas and indicators kept as attributes of the strategy
      will keep all data in memory.
    
      For example: a `RSI` internally uses the indicator `UpDay` to
      make calculations. This subindicator will not keep all data in
      memory
    
      If in the `__init__` something like
      `a = self.data.close - self.data.high` is defined, then `a`
      will not keep all data in memory
    
      - This allows to keep `plotting` and `preloading` active.
    
      - `runonce` will be deactivated
    

與往常一樣,一個例子勝過千言萬語。示例腳本顯示了這些差異。它與雅虎1996年至2015年的每日數據一起運行,總共4965 幾天。

注意

這是一個小樣本。EuroStoxx50 期貨每天交易 14 小時,在短短 1 個月的交易中將產生大約 18000 根 1 分鐘柱線。

執行文稿 1st 以檢視在沒有請求記憶體節省的情況下使用了多少個記憶體位置:

$ ./memory-savings.py --save 0
Total memory cells used: 506430

對於級別 1(總節省):

$ ./memory-savings.py --save 1
Total memory cells used: 2041

天哪!!!從五十萬下降到2041。事實上。系統中的每個 lines 物件都使用 collections.deque as 緩衝區(而不是 array.array),並且長度限定為請求操作所需的絕對最小值。例:

  • data feed上使用 a SimpleMovingAverage 的週期30的策略。

在這種情況下,將進行以下調整:

  • data feed將具有倉位緩衝區30,即生成下一個值所需的SimpleMovingAverage數量

  • SimpleMovingAverage 有一個倉位緩衝區 1 ,因為除非其他指標(依賴於移動平均線)需要,否則不需要保持更大的緩衝區。

注意

此模式最吸引人且可能最重要的功能是,在腳本的整個生命週期中,使用的內存量保持不變。

無論 data feed的大小如何。

例如,如果長時間連接到即時源,這將非常有用。

但要考慮到:

  1. 繪圖不可用

  2. 還有其他記憶體消耗來源會隨著時間的推移而累積,就像策略生成的一樣orders

  3. 此模式只能在runonce=False 中使用 cerebro。對於即時 data feed來說,這也是強制性的,但是在簡單回溯測試的情況下,這比 runonce=True慢。

    可以肯定的是,記憶體管理比分步執行回溯測試更昂貴,但這只能由平臺的最終使用者根據具體情況進行判斷。

現在是負水準。這些旨在保持繪圖可用,同時仍然節省相當多的記憶體。第一級-1

$ ./memory-savings.py --save -1
Total memory cells used: 184623

在這種情況下,第 1 級 指標(在策略中聲明的指標)保留其完整長度緩衝區。但是,如果此指標依賴於其他指標(情況確實如此)來完成其工作,則子物件將具有長度限制。在這種情況下,我們已經從:

  • 506430 記憶體位置為 -> 184623

節省超過 50%。

注意

當然array.array ,已經交換了 collections.deque 對象,它們在記憶體方面更昂貴,儘管在操作方面更快。 collection.deque 但是對象相當小,節省接近粗略計算的記憶體位置。

現在的水準-2 ,這意味著也是為了節省在策略層面宣佈的指標,這些指標已被標記為no要繪製:

$ ./memory-savings.py --save -2
Total memory cells used: 174695

現在節省的不多。這是因為單個指標已被標記為未繪製:TestInd().plotinfo.plot = False

讓我們看看這個 last 例子中的繪圖:

$ ./memory-savings.py --save -2 --plot
Total memory cells used: 174695

對於感興趣的讀者,示例腳本可以對指標層次結構中遍歷的每個 lines 對象進行詳細分析。開啟繪圖的情況下執行(儲存於-1):

$ ./memory-savings.py --save -1 --lendetails
-- Evaluating Datas
---- Data 0 Total Cells 34755 - Cells per Line 4965
-- Evaluating Indicators
---- Indicator 1.0 Average Total Cells 30 - Cells per line 30
---- SubIndicators Total Cells 1
---- Indicator 1.1 _LineDelay Total Cells 1 - Cells per line 1
---- SubIndicators Total Cells 1
...
---- Indicator 0.5 TestInd Total Cells 9930 - Cells per line 4965
---- SubIndicators Total Cells 0
-- Evaluating Observers
---- Observer 0 Total Cells 9930 - Cells per Line 4965
---- Observer 1 Total Cells 9930 - Cells per Line 4965
---- Observer 2 Total Cells 9930 - Cells per Line 4965
Total memory cells used: 184623

相同但啟用了最大節省 (1):

$ ./memory-savings.py --save 1 --lendetails
-- Evaluating Datas
---- Data 0 Total Cells 266 - Cells per Line 38
-- Evaluating Indicators
---- Indicator 1.0 Average Total Cells 30 - Cells per line 30
---- SubIndicators Total Cells 1
...
---- Indicator 0.5 TestInd Total Cells 2 - Cells per line 1
---- SubIndicators Total Cells 0
-- Evaluating Observers
---- Observer 0 Total Cells 2 - Cells per Line 1
---- Observer 1 Total Cells 2 - Cells per Line 1
---- Observer 2 Total Cells 2 - Cells per Line 1

第 2輸出立即顯示data feed中的lines如何被限制為38記憶體位置,4965而不是包含完整數據源長度的記憶體位置。

指標和 observers*在可能的情況下被限制在1 產出 last lines 中。

腳本代碼和用法

可作為樣品在源。backtrader用法:

$ ./memory-savings.py --help
usage: memory-savings.py [-h] [--data DATA] [--save SAVE] [--datalines]
                         [--lendetails] [--plot]

Check Memory Savings

optional arguments:
  -h, --help    show this help message and exit
  --data DATA   Data to be read in (default: ../../datas/yhoo-1996-2015.txt)
  --save SAVE   Memory saving level [1, 0, -1, -2] (default: 0)
  --datalines   Print data lines (default: False)
  --lendetails  Print individual items memory usage (default: False)
  --plot        Plot the result (default: False)

代碼:

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

import argparse
import sys

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


class TestInd(bt.Indicator):
    lines = ('a', 'b')

    def __init__(self):
        self.lines.a = b = self.data.close - self.data.high
        self.lines.b = btind.SMA(b, period=20)


class St(bt.Strategy):
    params = (
        ('datalines', False),
        ('lendetails', False),
    )

    def __init__(self):
        btind.SMA()
        btind.Stochastic()
        btind.RSI()
        btind.MACD()
        btind.CCI()
        TestInd().plotinfo.plot = False

    def next(self):
        if self.p.datalines:
            txt = ','.join(
                ['%04d' % len(self),
                 '%04d' % len(self.data0),
                 self.data.datetime.date(0).isoformat()]
            )

            print(txt)

    def loglendetails(self, msg):
        if self.p.lendetails:
            print(msg)

    def stop(self):
        super(St, self).stop()

        tlen = 0
        self.loglendetails('-- Evaluating Datas')
        for i, data in enumerate(self.datas):
            tdata = 0
            for line in data.lines:
                tdata += len(line.array)
                tline = len(line.array)

            tlen += tdata
            logtxt = '---- Data {} Total Cells {} - Cells per Line {}'
            self.loglendetails(logtxt.format(i, tdata, tline))

        self.loglendetails('-- Evaluating Indicators')
        for i, ind in enumerate(self.getindicators()):
            tlen += self.rindicator(ind, i, 0)

        self.loglendetails('-- Evaluating Observers')
        for i, obs in enumerate(self.getobservers()):
            tobs = 0
            for line in obs.lines:
                tobs += len(line.array)
                tline = len(line.array)

            tlen += tdata
            logtxt = '---- Observer {} Total Cells {} - Cells per Line {}'
            self.loglendetails(logtxt.format(i, tobs, tline))

        print('Total memory cells used: {}'.format(tlen))

    def rindicator(self, ind, i, deep):
        tind = 0
        for line in ind.lines:
            tind += len(line.array)
            tline = len(line.array)

        thisind = tind

        tsub = 0
        for j, sind in enumerate(ind.getindicators()):
            tsub += self.rindicator(sind, j, deep + 1)

        iname = ind.__class__.__name__.split('.')[-1]

        logtxt = '---- Indicator {}.{} {} Total Cells {} - Cells per line {}'
        self.loglendetails(logtxt.format(deep, i, iname, tind, tline))
        logtxt = '---- SubIndicators Total Cells {}'
        self.loglendetails(logtxt.format(deep, i, iname, tsub))

        return tind + tsub


def runstrat():
    args = parse_args()

    cerebro = bt.Cerebro()
    data = btfeeds.YahooFinanceCSVData(dataname=args.data)
    cerebro.adddata(data)
    cerebro.addstrategy(
        St, datalines=args.datalines, lendetails=args.lendetails)

    cerebro.run(runonce=False, exactbars=args.save)
    if args.plot:
        cerebro.plot(style='bar')


def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Check Memory Savings')

    parser.add_argument('--data', required=False,
                        default='../../datas/yhoo-1996-2015.txt',
                        help='Data to be read in')

    parser.add_argument('--save', required=False, type=int, default=0,
                        help=('Memory saving level [1, 0, -1, -2]'))

    parser.add_argument('--datalines', required=False, action='store_true',
                        help=('Print data lines'))

    parser.add_argument('--lendetails', required=False, action='store_true',
                        help=('Print individual items memory usage'))

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    return parser.parse_args()


if __name__ == '__main__':
    runstrat()

推薦閱讀

相關文章

Backtrader教程:觀察者 - 參考

基準 backtrader類 .observers.基準() 此 observer 存儲策略的回報和參考資產的回報,參考資產是傳遞到系統的數據之一。

Backtrader改進代碼

時不時地,帶有 backtrader 代碼的示例會在互聯網上彈出。在我看來,有幾個是中國人。最新的一個在這裡: 標題是: backtrader-學習筆記2,這顯然(感謝谷歌)翻譯成 backtrader- 學習筆記2。

Backtrader教程:安裝

要求和版本 backtrader 是獨立的,沒有外部依賴關係(除非要繪圖) 基本要求是: Python 2.7 Python 3.2 / 3.3/ 3.4 / 3.5 pypy/pypy3 如果需要繪圖,則其他要求: Matplotlib >= 1.4.

BacktraderPyFolio 集成

注意 2017年2月 pyfolio API 已更改,不再 create_full_tear_sheet 具有 gross_lev 作為命名參數的參數。

Backtrader教程:數據饋送 - 展期交割

並非每個供應商都為可以交易的工具提供連續的未來。有時提供的數據是仍然有效的到期日期的數據,即:仍在交易的日期 這在回溯測試方面並不是很有幫助,因為數據分散在幾個不同的儀器上,這些儀器另外...時間重疊。 能夠正確地將這些儀器的數據從過去連接到連續的流中,可以減輕疼痛。

Backtrader繪製日期範圍

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

Backtrader教程:日期時間 - 管理

在 1.5.0 版之前, backtrader 使用直接的方法來進行時間管理,因為數據源計算的任何日期時間都只是按面值使用。 對於任何使用者輸入也是如此,例如可以提供給任何數據源的參數fromdate (或 sessionstart)的情況 考慮到直接控制凍結的數據源以進行回溯測試,這種方法很好。

Backtrader教程:數據饋送 - 熊貓

注意 pandas 並且必須安裝其依賴項 支援Pandas Dataframes似乎受到很多人的關注,他們依賴於已經可用的解析代碼來分析不同的數據源(包括CSV)和Pandas提供的其他功能。 數據饋送的重要聲明。 注意 這些只是 聲明。不要盲目複製此代碼。

Backtrader現實咬合

上一篇文章設法複製了該BTFD 策略,發現真正的收益 16x 而不是 31x。 但正如複製期間所指出的: 不收取傭金 使用2x 槓桿不收取利息 這就提出了一個顯而易見的問題: 當收取傭金和利息時,這16x中有多少會存在? 幸運的是,前面的示例足夠靈活,可以對其進行試驗。

Backtrader教程:分析儀 - PyFolio

注意 從(至少)2017-07-25pyfolio 開始,API已更改,不再 create_full_tear_sheet 具有 gross_lev 作為命名參數的參數。