节省内存
原文: https://www.backtrader.com/blog/posts/2016-05-09-memory-savings/memory-savings/
1.3.1.92 版重新设计并全面实施了以前采用的内存节省方案,尽管没有太多宣传和较少使用。
发布:https://github.com/mementum/backtrader/releases/tag/1.3.1.92
backtrader是(并将进一步)在具有大量 RAM 的机器上开发的,加上通过绘图获得的视觉反馈是一个很好的选择,而且几乎是必须的,因此设计决策很容易:将所有内容都保存在内存中。
这一决定有一些缺点:
- 
用于数据存储的 array.array必须在超出某些界限时分配和移动数据
- 
内存量低的机器可能会受到影响 
- 
与实时数据源的连接可以在线数周/数月,并将数千秒/分钟的分辨率刻度输入系统 
后者甚至比 1st更重要,因为backtrader的另一个设计决策:
- 
如果需要,可以使用纯 Python 在嵌入式系统中运行 未来的场景可能会有 backtrader连接到第 2台机器,提供实时反馈,而backtrader本身运行在树莓 Pi或更有限的 ADSL 路由器(AVM Frit!Box 7490,带有Freetz图像)
因此需要backtrader支持动态内存方案。现在可以用以下语义实例化Cerebro或run:
- 
exactbars(默认值:False) 使用默认的 False值,存储在一行中的每个值都保存在内存中可能值: ``py -Trueor1`: 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 preloadandrunonce
- 
Using this setting also deactivates plotting 
- 
-1: datas and indicators/operations at strategy level will keep all data in memory.
 For example: a RSIinternally uses the indicatorUpDayto make calculations. This subindicator will not keep all data in memory- 
This allows to keep plottingandpreloadingactive.
- 
runoncewill be deactivated
- 
-2: datas and indicators kept as attributes of the strategy will keep all data in memory.
 For example: a RSIinternally uses the indicatorUpDayto make calculations. This subindicator will not keep all data in memoryIf in the __init__something likea = self.data.close - self.data.highis defined, thenawill not keep all data in memory- 
This allows to keep plottingandpreloadingactive.
- 
runoncewill be deactivated ```
 
- 
一如往常,一个例子胜过千言万语。示例脚本显示了这些差异。它与雅虎 1996 年至 2015 年的每日数据(共4965天)背道而驰。
笔记
这是一个小样本。EuroStoxx50 期货每天交易 14 小时,仅在一个月的交易时间内就将产生约 18000 根 1 分钟的金条。
执行脚本 1st以查看在未请求内存节省时使用了多少内存位置:
$ ./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中的runonce=False一起使用。这对于实时数据馈送也是强制性的,但在简单的回溯测试中,这比runonce=True慢。确实存在一个折衷点,从这个折衷点来看,内存管理比逐步执行回测更昂贵,但这只能由平台的最终用户根据具体情况来判断。 
现在是负水平。这意味着保持绘图可用,同时仍能节省大量内存。第一级-1:
$ ./memory-savings.py --save -1
Total memory cells used: 184623 
在这种情况下,指标(在策略中声明的指标)的 1st级别保留其全长缓冲区。但是,如果该指示器依赖于其他指示器(事实就是如此)来执行其工作,则子对象将是长度有界的。在这种情况下,我们从:
- 506430记忆位置至->- 184623
节省 50%以上。
笔记
当然,array.array对象已被交换为collections.deque,后者在内存方面更昂贵,但在操作方面更快。但是collection.deque对象非常小,节省的内存接近所使用的粗略计数的内存位置。
级别-2现在,这也意味着保存在策略级别上声明的、标记为“否”的待绘制指标上:
$ ./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 
第 2和输出立即显示数据馈送中的行如何被限制到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() 


