版本 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值,存儲在一行中的每個值都保存在內存中可能的值:
True或1:所有“行”對象將內存使用量減少到自動計算的最小周期。如果簡單移動平均線的周期為 30,則基礎數據將始終具有 30 個柱的運行緩衝區,以允許計算簡單移動平均線
此設置將停用
preload和runonce使用此設置也會停用繪圖
-1:策略級別的數據和指標/操作將所有數據保存在內存中。例如:
RSI內部使用指標UpDay進行計算。該子指標不會將所有數據保存在內存中這允許保持
plotting和preloading處於活動狀態。runonce將被停用
-2:作為策略屬性保存的數據和指標將所有數據保存在內存中。例如:
RSI內部使用指標UpDay進行計算。該子指標不會將所有數據保存在內存中如果在
__init__中類似於a = self.data. close - self.data. high定義了a = self.data. close - self.data. high,那麼a不會將所有數據保存在內存中這允許保持
plotting和preloading處於活動狀態。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 ),並且長度限制為請求操作所需的絕對最小值。例子:
- 在數據饋送上使用周期
30的SimpleMovingAverage的策略。
在這種情況下,將進行以下調整:
數據饋送將具有
30位置的緩衝區,即SimpleMovingAverage產生下一個值所需的數量SimpleMovingAverage將具有1位置的緩衝區,因為除非其他指標(將依賴於移動平均線)需要,否則無需保留更大的緩衝區。
筆記
此模式最吸引人且可能最重要的特性是在腳本的整個生命週期中使用的內存量保持不變。
無論數據饋送的大小。
例如,如果長時間連接到實時源,這將非常有用。
但要考慮到:
繪圖不可用
還有其他內存消耗來源會隨著時間的推移而累積,例如策略生成的
orders。此模式只能與 cerebro 中的
cerebrorunonce= 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()