Backtrader节省内存

  |  

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台机器,而它本身在 backtrader Raspberry 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的大小如何。

例如,如果长时间连接到即时源,这将非常有用。

但要考虑到:

  1. 绘图不可用

  2. 还有其他内存消耗来源会随着时间的推移而累积,就像策略生成的一样orders

  3. 此模式只能在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()

推荐阅读

相关文章

Backtrader教程:观察者 - 参考

基准 backtrader类 .observers.基准() 此 observer 存储策略的回报和参考资产的回报,参考资产是传递到系统的数据之一。

Backtrader教程:经纪人 - 开仓作弊

“发布”1.9.44.116 添加了对 Cheat-On-Open的支持。这似乎是那些全力以赴的人的需求功能,他们在酒吧 close 后进行了计算,但希望与 open 价格相匹配。 当开盘价跳空(上涨或下跌,取决于是否buysell有效)并且现金不足以进行全面运营时,这样的用例就会失败。这将强制代理拒绝该操作。

Backtrader对逐笔报价数据重新采样

backtrader 已经可以从分钟数据中重新采样。接受价格变动数据不是问题,只需将 4 个常用字段(open、 high、 low、 close)设置为价格变动值。 但是传递要重新采样的逐笔报价数据再次生成相同的数据。作为或版本 1.1.11.88,情况已不再如此。

Backtrader教程:操作平台

Line 反复运算器 为了参与操作,plaftorm使用 line 反复运算器的概念。它们已经松散地模仿了Python的反复运算器,但实际上与它们无关。 策略和指针是 line 反复运算器。

Backtrader股票筛选

在寻找其他一些东西时,我在StackOverlow家族网站之一上遇到了一个问题:Quantitative Finance aka Quant StackExchange。问题: 它被标记为Python,因此值得一看的是 backtrader 是否能够胜任这项任务。 分析仪本身 该问题似乎适合用于简单的分析器。

Backtrader迪克森移动平均线

下面的reddit帖子以自己的作者Nathan Dickson(reddit句柄)命名了这个平均值Dickson移动平均线。 在一次对reddit Algotrading 的定期访问中,我发现了一篇关于移动平均线的帖子,该移动平均线试图模仿Jurik移动平均线(又名JMA)。

Backtrader跨越数字

《backtrader》的发布1.9.27.105纠正了一个疏忽。这是一个疏忽,因为拼图的所有部分都已到位,但启动并不是在所有角落都进行的。 该机制使用一个名为的属性_mindatas,因此让我们将其称为: mindatas。 社区问了这个问题,答案并不是很到位。

Backtrader教程:Cerebro - 优化 - 改进

backtrader版本1.8.12.99改进了在多处理过程中管理data feeds和结果的方式。

Backtrader混合时间帧

1.3.0.92版本带来了混合来自不同时间帧的数据(来自 data feeds 和/或指针)的可能性。 到版本:https://github.com/mementum/backtrader/发布/标签/1.3.0.92 背景:指示器是智能哑对象。 他们很聪明,因为他们可以进行复杂的计算。

Backtrader蟒蛇隐藏的力量3

Last,但并非最不重要的一点是,在这个系列中,关于如何在 backtrader 中使用Python的隐藏功能是一些神奇变量是如何出现的。