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台機器,而它本身在backtraderRaspberry 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的大小如何。
例如,如果長時間連接到即時源,這將非常有用。
但要考慮到:
-
繪圖不可用
-
還有其他記憶體消耗來源會隨著時間的推移而累積,就像策略生成的一樣
orders。 -
此模式只能在
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()