版本 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 中的
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()