Backtrader 教程:Cerebro - 節省內存

  |  

版本 1.3.1.92重新設計並完全實現了以前存在的內存節省方案,儘管沒有太多吹捧和較少使用。

backtrader是(並將進一步)在具有大量 RAM 的機器上開發的,再加上通過繪圖的視覺反饋是一個很好的擁有並且幾乎是必須擁有的事實,mde 很容易做出設計決策:保留所有內容記憶。

這個決定有一些缺點:

  • 用於數據存儲的array.array必須在超出某些界限時分配和移動數據

  • 內存不足的機器可能會受到影響

  • 連接到可以在線數週/數月的實時數據饋送,將數千秒/分鐘的分辨率輸入系統

由於為backtrader backtrader的另一個設計決定,後者甚至比第一個更重要:

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

    未來的一個場景可能是將backtrader連接到提供實時饋送的第二台機器,而backtrader本身在 Raspberry Pi 內運行,或者像 ADSL 路由器這樣更受限制的東西(AVM Frit!Box 7490 帶有 Freetz 圖像)

因此需要讓backtrader支持動態內存方案。現在Cerebro可以使用以下語義實例化或run

  • 精確條(默認值: False

    使用默認的False值,存儲在一行中的每個值都保存在內存中

    可能的值:

    • True1 :所有“”對象將內存使用量減少到自動計算的最小周期。

      如果簡單移動平均線的周期為 30,則基礎數據將始終具有 30 個柱的運行緩衝區,以允許計算簡單移動平均線

      • 此設置將停用preloadrunonce

      • 使用此設置也會停用繪圖

    • -1 :策略級別的數據和指標/操作將所有數據保存在內存中。

      例如: RSI內部使用指標UpDay進行計算。該子指標不會將所有數據保存在內存中

      • 這允許保持plottingpreloading處於活動狀態。

      • runonce將被停用

    • -2 :作為策略屬性保存的數據和指標將所有數據保存在內存中。

      例如: RSI內部使用指標UpDay進行計算。該子指標不會將所有數據保存在內存中

      如果在__init__中類似於a = self.data. close - self.data. high定義了a = self.data. close - self.data. high ,那麼a不會將所有數據保存在內存中

      • 這允許保持plottingpreloading處於活動狀態。

      • runonce將被停用

與往常一樣,一個例子值一千字。示例腳本顯示了差異。它與雅虎 1996 年至 2015 年的每日數據進行對比,總共4965天。

筆記

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

執行腳本 1查看在不請求內存節省時使用了多少內存位置:

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

對於 1 級(總節省):

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

我的天啊!!!從 50 萬降至2041年。的確。系統中的每個每對像都使用一個collections.deque作為緩衝區(而不是array.array ),並且長度限制為請求操作所需的絕對最小值。例子:

  • 數據饋送上使用周期30SimpleMovingAverage的策略。

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

  • 數據饋送將具有30位置的緩衝區,即SimpleMovingAverage產生下一個值所需的數量

  • SimpleMovingAverage將具有1位置的緩衝區,因為除非其他指標(將依賴於移動平均線)需要,否則無需保留更大的緩衝區。

筆記

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

無論數據饋送的大小。

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

但要考慮到:

  1. 繪圖不可用

  2. 還有其他內存消耗來源會隨著時間的推移而累積,例如策略生成的orders

  3. 此模式只能與 cerebro 中的cerebro runonce= False一起使用。這對於實時數據饋送也是強制性的,但在簡單的回測的情況下,這比runonce= True慢。

    肯定有一個權衡點,內存管理比逐步執行回測更昂貴,但這只能由平台的最終用戶根據具體情況來判斷。

現在是負數。這些旨在保持繪圖可用,同時仍節省大量內存。第一級-1

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

在這種情況下,第一級指標(在策略中聲明的指標)保持其全長緩衝區。但是如果這個指標依賴於其他指標(就是這種情況)來完成它的工作,那麼子對象將是有長度限制的。在這種情況下,我們已經從:

  • 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

讓我們看看最後一個例子的繪圖:

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

對於感興趣的讀者,示例腳本可以生成對指標層次結構中遍歷的每個對象的詳細分析。在啟用繪圖的情況下運行(保存在-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

第二個輸出立即顯示數據饋送中的如何被限制為38內存位置,而不是包含完整數據源長度的4965

如輸出的最後幾行所示,指標和觀察者已盡可能限制為1

腳本代碼和用法

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 1.8.12.99版改進了多處理期間數據饋送和結果的管理方式。

Backtrader做空現金

從一開始,反向交易者就可以做空任何東西,包括類似股票和類似期貨的工具。當做空時,現金減少,被賣空資產的價值用於總淨清算價值。從一側移除並添加到另一側可以保持平衡。人們似乎更喜歡增加現金,這可能會增加支出。在1.9.7.105版本中,經紀人已將默認行為更改為添加現金和移除價值。

Backtrader逃離 OHLC 土地

在backtrader的概念和開發過程中應用的關鍵概念之一是靈活性。 Python 的元編程和自省功能曾經是(現在仍然是)保持許多東西靈活同時仍然能夠交付的基礎。一篇舊帖子顯示了擴展概念。

Backtrader繪製日期範圍

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

Backtrader教程:數據饋送

backtrader 附帶一組 Data Feed 解析器(在編寫所有基於CSV時),可讓您從不同的來源載入數據。

Backtrader數據多時間幀

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

Backtrader教程:繪圖 - 日期範圍

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

Backtrader策略選擇

最初的策略選擇方法使用兩個策略,手動註冊和一個簡單的[0, 1]列表來決定哪個是策略的目標。因為 Python 為元類提供了許多自省的可能性,所以實際上可以自動化該方法。

Backtrader 教程:策略 - 參考

內置策略的參考MA_CrossOver別名: * SMA_CrossOver 這是一個多頭策略,

Backtrader動量策略

在另一篇偉大的文章中,泰迪·科克(Teddy Koker)再次展示了演算法交易策略的發展之路: 研究優先應用 pandas 回溯測試,然後使用 backtrader 榮譽!!! 該帖子可以在以下位置找到: 泰迪·科克(Teddy Koker)給我留言,問我是否可以評論 backtrader的用法。