分享

BackTrader官方文档 06 - 大脑(2) 节省内存

 禁忌石 2022-03-23

Release 1.3.1.92已经重新工作并完全实现了之前的内存节省方案,尽管没有被大肆吹捧和使用。
BackTrader是在拥有大量RAM的机器上开发的,再加上通过绘图获得的视觉反馈是一种非常棒且必不可少的功能,这让设计决策变得更加简单:将所有内容都保存在内存中。
这一决定存在一些缺陷:

  • array.array 用于存储数组,但在数组越界时必须分配和移动数据
  • 内存较少的机器可能会受到影响
  • 连接的实时数据槽为了生成周线和月线可能需要接收上千秒或者分钟的tick数据
    后者比前者更重要,基于BackTrader的另一个设计决定:
  • 如果需要,使用纯Python可以在嵌入式系统中运行
    未来的一个场景可能由BackTrader连接到第2台机器,它提供实时数据,而BackTrader本身运行在树莓派或更有限的资源上,像ADSL路由器(AVM Frit!Box 7490 with a Freetz image)
  • 因此,需要BackTrader支持动态内存方案。现在,大脑可以被实例化或以以下语法运行:

  • exactbars(默认值: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就不会把所有的数据都保存在内存中这允许保持plotting和preloading被激活runonce将被停用
    一个例子胜过千言万语。示例脚本显示了这些区别。它与1996年至2015年的雅虎每日数据(总计4965天)相比较。
  • 注意: 这是一个小样本。每天交易14小时的EuroStoxx50期货,在仅仅一个月的交易时间内将生产约18000个1分钟K线。
    脚本第一次执行,看看当没有节省内存时使用了多少内存:

    $ ./memory-savings.py --save 0Total memory cells used: 506430

    对于第一级(总节省):

    $ ./memory-savings.py --save 1Total memory cells used: 2041

    天呐!!!从50万下降到2041。系统中的每个every lines对象都使用一个collections.deque作为缓冲区(而不是array.array),并且长度限制为所请求操作所需的绝对最小值。例子:

  • 一个使用简单易懂平均线,周期为30的策略
    在这种情况下,将作出下列调整:
  • 数据槽将有一个30个位置的缓冲区,这是简单移动平均线生成下一个值所需的数量
  • 简单移动平均线将有一个1个位置的缓冲区,因为除非其他的指标(依赖于移动平均线)需要,否则没有必要保留一个更大的缓冲区。
  • 注意: 这种模式最吸引人(可能也是最重要的)的特性是,在脚本的整个生命周期中,所使用的内存量保持不变。

    无论数据槽的大小。
    例如,如果长时间连接到一个实时槽,这将是非常有用的。
    但是要考虑到:

    1. 绘图不可用
    2. 随着时间的推移,还有其他内存消耗,比如策略生成的订单。
    3. 该模式只能在大脑的runonce=False下使用。这对于实时数据槽也是强制性的,但在简单的回测情况下,这比runonce=True要慢。
      内存管理肯定比逐步执行回测更昂贵,但这只能由平台的最终用户根据具体情况来判断。

    现在来看负等级。这意味着在保持绘图可用的同时还能节省相当多的内存。第一级:

    $ ./memory-savings.py --save -1Total memory cells used: 184623

    在这种情况下,第一级指标(在策略中声明的那些)保持其全长度缓冲区。但是,如果这个指标依赖于其他指标(这种情况)来完成它的工作,那么子对象将是有长度限制的。在这种情况下,我们从:

  • 506430 内存位置到184623
  • 节省了50%以上。

    注意: 当然array.array对象被交换为collections.deque,后者在内存方面更昂贵,但在操作方面更快。但是collection.deque对象相当小,节省的内存接近使用的粗略计算的内存位置。

    现在是-2级,这也意味着在不做绘图的策略级声明指标同样节约了内存:

    $ ./memory-savings.py --save -2Total memory cells used: 174695

    并没有节省太多。这是因为有一个指标被标记为“未绘制”:TestInd().plotinfo.plot = False

    让我们看看最后一个例子的绘图:

    $ ./memory-savings.py --save -2 --plotTotal memory cells used: 174695
    文章图片1

    对于感兴趣的读者,样例脚本可以生成指标层次结构中遍历的每个线对象的详细分析。运行时启用绘图(保存为-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 4965Total 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 --helpusage: memory-savings.py [-h] [--data DATA] [--save SAVE] [--datalines]                         [--lendetails] [--plot]Check Memory Savingsoptional 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 argparseimport sysimport BackTrader as btimport BackTrader.feeds as btfeedsimport BackTrader.indicators as btindimport BackTrader.utils.flushfileclass 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 + tsubdef 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()

      本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
      转藏 分享 献花(0

      0条评论

      发表

      请遵守用户 评论公约

      类似文章 更多