Backtrader 教程:Cerebro - 节省内存

  |  

版本 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值,存储在一行中的每个值都保存在内存中

    可能的值:

    • True1 :所有“”对象将内存使用量减少到自动计算的最小周期。

      如果简单移动平均线的周期为 30,则基础数据将始终具有 30 个柱的运行缓冲区,以允许计算简单移动平均线

      • 此设置将停用preloadrunonce

      • 使用此设置也会停用绘图

    • -1 :策略级别的数据和指针/操作将所有数据保存在内存中。

      例如: RSI内部使用指针UpDay进行计算。该子指针不会将所有数据保存在内存中

      • 这允许保持plottingpreloading处于活动状态。

      • runonce将被停用

    • -2 :作为策略属性保存的数据和指针将所有数据保存在内存中。

      例如: RSI内部使用指针UpDay进行计算。该子指针不会将所有数据保存在内存中

      如果在__init__中类似于a = self.data. close - self.data. high定义了a = self.data. close - self.data. high ,那么a不会将所有数据保存在内存中

      • 这允许保持plottingpreloading处于活动状态。

      • 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 ),并且长度限制为请求操作所需的绝对最小值。例子:

  • 数据馈送上使用周期30SimpleMovingAverage的策略。

在这种情况下,将进行以下调整:

  • 数据馈送将具有30位置的缓冲区,即SimpleMovingAverage产生下一个值所需的数量

  • SimpleMovingAverage将具有1位置的缓冲区,因为除非其他指针(将依赖于移动平均线)需要,否则无需保留更大的缓冲区。

笔记

此模式最吸引人且可能最重要的特性是在脚本的整个生命周期中使用的内存量保持不变。

无论数据馈送的大小。

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

但要考虑到:

  1. 绘图不可用

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

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

推荐阅读

相关文章

Backtrader教程:分析仪

无论是回溯测试还是交易,能够分析交易系统的性能是了解是否不仅获得了利润,而且是否在风险太大的情况下实现了利润,或者与参考资产(或无风险资产)相比,是否真的值得付出努力的关键。 这就是对象家族的用武之Analyzer 地:提供对所发生事件甚至实际发生的事情的分析。

Backtrader分数大小

首先,让我们用两行总结反向交易方法的工作原理:它就像一个带有基本构建块 ( Cerebro ) 的建筑套件,可以插入许多不同的部件基本分布包含许多部分,如指针、分析器、观察器、 Sizer 、过滤器、数据馈送、经纪人、佣金/资产信息方案,...

Backtrader数据重采样

当数据仅在一个时间范围内可用并且必须在不同的时间范围内进行分析时,是时候进行一些重新采样了。 “重新采样”实际上应该称为“上采样”,因为从源时间范围到更大的时间范围(例如:几天到几周) “下采样”尚不可能。 backtrader通过将原始数据传递给智能命名为DataResampler的过滤器对象,内置了对重采样的支持。

Backtrader实际使用方式

最后,似乎已经付出了开发 backtrader是值得的。 在观察 last 周的欧洲市场时,似乎世界末日了,一位朋友问我是否可以看看我们图表包中的数据,看看与以前类似情况相比,下跌幅度如何。 当然可以,但我说我可以做的不仅仅是查看图表,因为我可以快速: 创建一个快速LegDown 指示器来测量跌落的范围。

BacktraderPyFolio 集成

注意 2017年2月 pyfolio API 已更改,不再 create_full_tear_sheet 具有 gross_lev 作为命名参数的参数。

Backtrader教程:筛检程序

此功能是 backtrader 的相对较新的补充,必须安装到已经存在的内部结构中。这使得它不像希望的那样灵活且100%功能齐全,但在许多情况下它仍然可以达到目的。 尽管该实现试图允许随插即用的筛检程序链接,但预先存在的内部结构使得很难确保始终可以实现。因此,某些筛选器可能是链接的,而其他一些筛选器可能不是。

Backtrader 教程:经纪人 - 滑点

回测不能保证真实的市场状况。无论市场仿真有多好,在真实的市场条件下都会发生滑点。这意味着:要求的价格可能不匹配。集成的回测代理支持滑点。

Backtrader python 隐藏的细节

只有当遇到 backtrader 的真实用户时,人们才能意识到平台中使用的抽象和Python功能是否有意义。 在不撇开python的座右铭的情况下, backtrader 试图为用户提供尽可能多的控制权,同时通过将Python提供的隐藏功能付诸行动来简化使用。 第一个示例是系列文章的第一篇。

Backtrader交叉回溯测试陷阱

在backtrader 社区中 ,倾向于重复的事情是,用户解释了拷贝在例如 TradingView 中获得的回溯测试结果的意愿,这些天非常流行,或者其他一些回溯测试平台。

Backtrader买入/卖出箭头

backtrader 旨在尝试提供易用性。创建指针和其他常见嫌疑人应该很容易。 当然,定制现有项目也应该是交易的一部分。 社区中的一个主题,BuySell Arrows,它起源于从问题迁移而来的,就是一个很好的例子。