BackTrader 中文文档(二十四)(2)

简介: BackTrader 中文文档(二十四)

BackTrader 中文文档(二十四)(1)https://developer.aliyun.com/article/1505445

代码:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import datetime
# The above could be sent to an independent module
import backtrader as bt
from backtrader.utils import flushfile  # win32 quick stdout flushing
class TestStrategy(bt.Strategy):
    params = dict(
        smaperiod=5,
        trade=False,
        stake=10,
        exectype=bt.Order.Market,
        stopafter=0,
        valid=None,
        cancel=0,
        donotsell=False,
    )
    def __init__(self):
        # To control operation entries
        self.orderid = list()
        self.order = None
        self.counttostop = 0
        self.datastatus = 0
        # Create SMA on 2nd data
        self.sma = bt.indicators.MovAv.SMA(self.data, period=self.p.smaperiod)
        print('--------------------------------------------------')
        print('Strategy Created')
        print('--------------------------------------------------')
    def notify_data(self, data, status, *args, **kwargs):
        print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
        if status == data.LIVE:
            self.counttostop = self.p.stopafter
            self.datastatus = 1
    def notify_store(self, msg, *args, **kwargs):
        print('*' * 5, 'STORE NOTIF:', msg)
    def notify_order(self, order):
        if order.status in [order.Completed, order.Cancelled, order.Rejected]:
            self.order = None
        print('-' * 50, 'ORDER BEGIN', datetime.datetime.now())
        print(order)
        print('-' * 50, 'ORDER END')
    def notify_trade(self, trade):
        print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
        print(trade)
        print('-' * 50, 'TRADE END')
    def prenext(self):
        self.next(frompre=True)
    def next(self, frompre=False):
        txt = list()
        txt.append('%04d' % len(self))
        dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
        txt.append('%s' % self.data.datetime.datetime(0).strftime(dtfmt))
        txt.append('{}'.format(self.data.open[0]))
        txt.append('{}'.format(self.data.high[0]))
        txt.append('{}'.format(self.data.low[0]))
        txt.append('{}'.format(self.data.close[0]))
        txt.append('{}'.format(self.data.volume[0]))
        txt.append('{}'.format(self.data.openinterest[0]))
        txt.append('{}'.format(self.sma[0]))
        print(', '.join(txt))
        if len(self.datas) > 1:
            txt = list()
            txt.append('%04d' % len(self))
            dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
            txt.append('%s' % self.data1.datetime.datetime(0).strftime(dtfmt))
            txt.append('{}'.format(self.data1.open[0]))
            txt.append('{}'.format(self.data1.high[0]))
            txt.append('{}'.format(self.data1.low[0]))
            txt.append('{}'.format(self.data1.close[0]))
            txt.append('{}'.format(self.data1.volume[0]))
            txt.append('{}'.format(self.data1.openinterest[0]))
            txt.append('{}'.format(float('NaN')))
            print(', '.join(txt))
        if self.counttostop:  # stop after x live lines
            self.counttostop -= 1
            if not self.counttostop:
                self.env.runstop()
                return
        if not self.p.trade:
            return
        if self.datastatus and not self.position and len(self.orderid) < 1:
            self.order = self.buy(size=self.p.stake,
                                  exectype=self.p.exectype,
                                  price=round(self.data0.close[0] * 0.90, 2),
                                  valid=self.p.valid)
            self.orderid.append(self.order)
        elif self.position.size > 0 and not self.p.donotsell:
            if self.order is None:
                self.order = self.sell(size=self.p.stake // 2,
                                       exectype=bt.Order.Market,
                                       price=self.data0.close[0])
        elif self.order is not None and self.p.cancel:
            if self.datastatus > self.p.cancel:
                self.cancel(self.order)
        if self.datastatus:
            self.datastatus += 1
    def start(self):
        if self.data0.contractdetails is not None:
            print('Timezone from ContractDetails: {}'.format(
                  self.data0.contractdetails.m_timeZoneId))
        header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume',
                  'OpenInterest', 'SMA']
        print(', '.join(header))
        self.done = False
def runstrategy():
    args = parse_args()
    # Create a cerebro
    cerebro = bt.Cerebro()
    storekwargs = dict(
        host=args.host, port=args.port,
        clientId=args.clientId, timeoffset=not args.no_timeoffset,
        reconnect=args.reconnect, timeout=args.timeout,
        notifyall=args.notifyall, _debug=args.debug
    )
    if args.usestore:
        ibstore = bt.stores.IBStore(**storekwargs)
    if args.broker:
        if args.usestore:
            broker = ibstore.getbroker()
        else:
            broker = bt.brokers.IBBroker(**storekwargs)
        cerebro.setbroker(broker)
    timeframe = bt.TimeFrame.TFrame(args.timeframe)
    if args.resample or args.replay:
        datatf = bt.TimeFrame.Ticks
        datacomp = 1
    else:
        datatf = timeframe
        datacomp = args.compression
    fromdate = None
    if args.fromdate:
        dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.fromdate))
        fromdate = datetime.datetime.strptime(args.fromdate, dtformat)
    IBDataFactory = ibstore.getdata if args.usestore else bt.feeds.IBData
    datakwargs = dict(
        timeframe=datatf, compression=datacomp,
        historical=args.historical, fromdate=fromdate,
        rtbar=args.rtbar,
        qcheck=args.qcheck,
        what=args.what,
        backfill_start=not args.no_backfill_start,
        backfill=not args.no_backfill,
        latethrough=args.latethrough,
        tz=args.timezone
    )
    if not args.usestore and not args.broker:   # neither store nor broker
        datakwargs.update(storekwargs)  # pass the store args over the data
    data0 = IBDataFactory(dataname=args.data0, **datakwargs)
    data1 = None
    if args.data1 is not None:
        data1 = IBDataFactory(dataname=args.data1, **datakwargs)
    rekwargs = dict(
        timeframe=timeframe, compression=args.compression,
        bar2edge=not args.no_bar2edge,
        adjbartime=not args.no_adjbartime,
        rightedge=not args.no_rightedge,
        takelate=not args.no_takelate,
    )
    if args.replay:
        cerebro.replaydata(dataname=data0, **rekwargs)
        if data1 is not None:
            cerebro.replaydata(dataname=data1, **rekwargs)
    elif args.resample:
        cerebro.resampledata(dataname=data0, **rekwargs)
        if data1 is not None:
            cerebro.resampledata(dataname=data1, **rekwargs)
    else:
        cerebro.adddata(data0)
        if data1 is not None:
            cerebro.adddata(data1)
    if args.valid is None:
        valid = None
    else:
        datetime.timedelta(seconds=args.valid)
    # Add the strategy
    cerebro.addstrategy(TestStrategy,
                        smaperiod=args.smaperiod,
                        trade=args.trade,
                        exectype=bt.Order.ExecType(args.exectype),
                        stake=args.stake,
                        stopafter=args.stopafter,
                        valid=valid,
                        cancel=args.cancel,
                        donotsell=args.donotsell)
    # Live data ... avoid long data accumulation by switching to "exactbars"
    cerebro.run(exactbars=args.exactbars)
    if args.plot and args.exactbars < 1:  # plot if possible
        cerebro.plot()
def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Test Interactive Brokers integration')
    parser.add_argument('--exactbars', default=1, type=int,
                        required=False, action='store',
                        help='exactbars level, use 0/-1/-2 to enable plotting')
    parser.add_argument('--plot',
                        required=False, action='store_true',
                        help='Plot if possible')
    parser.add_argument('--stopafter', default=0, type=int,
                        required=False, action='store',
                        help='Stop after x lines of LIVE data')
    parser.add_argument('--usestore',
                        required=False, action='store_true',
                        help='Use the store pattern')
    parser.add_argument('--notifyall',
                        required=False, action='store_true',
                        help='Notify all messages to strategy as store notifs')
    parser.add_argument('--debug',
                        required=False, action='store_true',
                        help='Display all info received form IB')
    parser.add_argument('--host', default='127.0.0.1',
                        required=False, action='store',
                        help='Host for the Interactive Brokers TWS Connection')
    parser.add_argument('--qcheck', default=0.5, type=float,
                        required=False, action='store',
                        help=('Timeout for periodic '
                              'notification/resampling/replaying check'))
    parser.add_argument('--port', default=7496, type=int,
                        required=False, action='store',
                        help='Port for the Interactive Brokers TWS Connection')
    parser.add_argument('--clientId', default=None, type=int,
                        required=False, action='store',
                        help='Client Id to connect to TWS (default: random)')
    parser.add_argument('--no-timeoffset',
                        required=False, action='store_true',
                        help=('Do not Use TWS/System time offset for non '
                              'timestamped prices and to align resampling'))
    parser.add_argument('--reconnect', default=3, type=int,
                        required=False, action='store',
                        help='Number of recconnection attempts to TWS')
    parser.add_argument('--timeout', default=3.0, type=float,
                        required=False, action='store',
                        help='Timeout between reconnection attempts to TWS')
    parser.add_argument('--data0', default=None,
                        required=True, action='store',
                        help='data 0 into the system')
    parser.add_argument('--data1', default=None,
                        required=False, action='store',
                        help='data 1 into the system')
    parser.add_argument('--timezone', default=None,
                        required=False, action='store',
                        help='timezone to get time output into (pytz names)')
    parser.add_argument('--what', default=None,
                        required=False, action='store',
                        help='specific price type for historical requests')
    parser.add_argument('--no-backfill_start',
                        required=False, action='store_true',
                        help='Disable backfilling at the start')
    parser.add_argument('--latethrough',
                        required=False, action='store_true',
                        help=('if resampling replaying, adjusting time '
                              'and disabling time offset, let late samples '
                              'through'))
    parser.add_argument('--no-backfill',
                        required=False, action='store_true',
                        help='Disable backfilling after a disconnection')
    parser.add_argument('--rtbar', default=False,
                        required=False, action='store_true',
                        help='Use 5 seconds real time bar updates if possible')
    parser.add_argument('--historical',
                        required=False, action='store_true',
                        help='do only historical download')
    parser.add_argument('--fromdate',
                        required=False, action='store',
                        help=('Starting date for historical download '
                              'with format: YYYY-MM-DD[THH:MM:SS]'))
    parser.add_argument('--smaperiod', default=5, type=int,
                        required=False, action='store',
                        help='Period to apply to the Simple Moving Average')
    pgroup = parser.add_mutually_exclusive_group(required=False)
    pgroup.add_argument('--replay',
                        required=False, action='store_true',
                        help='replay to chosen timeframe')
    pgroup.add_argument('--resample',
                        required=False, action='store_true',
                        help='resample to chosen timeframe')
    parser.add_argument('--timeframe', default=bt.TimeFrame.Names[0],
                        choices=bt.TimeFrame.Names,
                        required=False, action='store',
                        help='TimeFrame for Resample/Replay')
    parser.add_argument('--compression', default=1, type=int,
                        required=False, action='store',
                        help='Compression for Resample/Replay')
    parser.add_argument('--no-takelate',
                        required=False, action='store_true',
                        help=('resample/replay, do not accept late samples '
                              'in new bar if the data source let them through '
                              '(latethrough)'))
    parser.add_argument('--no-bar2edge',
                        required=False, action='store_true',
                        help='no bar2edge for resample/replay')
    parser.add_argument('--no-adjbartime',
                        required=False, action='store_true',
                        help='no adjbartime for resample/replay')
    parser.add_argument('--no-rightedge',
                        required=False, action='store_true',
                        help='no rightedge for resample/replay')
    parser.add_argument('--broker',
                        required=False, action='store_true',
                        help='Use IB as broker')
    parser.add_argument('--trade',
                        required=False, action='store_true',
                        help='Do Sample Buy/Sell operations')
    parser.add_argument('--donotsell',
                        required=False, action='store_true',
                        help='Do not sell after a buy')
    parser.add_argument('--exectype', default=bt.Order.ExecTypes[0],
                        choices=bt.Order.ExecTypes,
                        required=False, action='store',
                        help='Execution to Use when opening position')
    parser.add_argument('--stake', default=10, type=int,
                        required=False, action='store',
                        help='Stake to use in buy operations')
    parser.add_argument('--valid', default=None, type=int,
                        required=False, action='store',
                        help='Seconds to keep the order alive (0 means DAY)')
    parser.add_argument('--cancel', default=0, type=int,
                        required=False, action='store',
                        help=('Cancel a buy order after n bars in operation,'
                              ' to be combined with orders like Limit'))
    return parser.parse_args()
if __name__ == '__main__':
    runstrategy()

节省内存

原文:www.backtrader.com/blog/posts/2016-05-09-memory-savings/memory-savings/

版本 1.3.1.92 已经重新设计并完全实现了之前的内存节省方案,虽然之前并没有被过多宣传和使用。

发布:github.com/mementum/backtrader/releases/tag/1.3.1.92

backtrader在拥有很好内存量的机器上(并将进一步开发)开发,加上通过绘图进行可视化反馈几乎是必需的,这使得设计决策变得容易:将所有内容保存在内存中。

这个决定有一些缺点:

  • array.array用于数据存储,当超过一些边界时必须分配和移动数据
  • 具有较少内存的机器可能会受到影响。
  • 连接到在线数据源,可以在线运行数周/数月,并将数千秒/分钟的分辨率 tick 送入系统

后者比第一个更重要,因为为backtrader做出了另一个设计决策:

  • 需要是纯 Python,以便在需要时在嵌入式系统中运行。
    将来的一个场景可能是backtrader连接到提供实时数据源的第二台机器,而backtrader本身运行在一个树莓派或者甚至更有限的设备上,比如一个 ADSL 路由器(AVM Frit!Box 7490,配备Freetz镜像)。

因此需要backtrader支持动态内存方案。现在可以使用以下语义来实例化或运行Cerebro

  • exactbars(默认值:False)
    默认为False值时,每个线条中存储的每个值都会保留在内存中。
    可能的值:
- `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 年的Yahoo每日数据运行,总计4965天。

注意

这只是一个小样本。每天交易 14 小时的 EuroStoxx50 期货品种,在只有 1 个月的交易中将产生约 18000 个 1 分钟 K 线。

执行第一个脚本以查看在不请求内存节省时使用了多少内存位置:

$ ./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作为缓冲区(而不是array.array)并且被长度边界到请求操作的绝对最小值。例如:

  • 在数据源上使用周期为30SimpleMovingAverage的策略。

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

  • 数据源 将有一个30位置的缓冲区,这是由SimpleMovingAverage产生下一个值所需的量。
  • SimpleMovingAverage将有一个1位置的缓冲区,因为除非被其他指标(依赖移动平均线)需要,否则没有必要保留更大的缓冲区。

注意

这种模式最吸引人且可能最重要的特点是脚本的整个生命周期内使用的内存量保持恒定。

无论数据源的大小如何。

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

但要考虑:

  1. 绘图不可用
  2. 还有其他会随时间累积的内存消耗源,比如策略生成的orders
  3. 这种模式只能在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对象相当小,节省的内存位置大致相等。

现在是第二级,也旨在节省在策略级别声明为不绘制的指标上的内存:

$ ./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

对于感兴趣的读者,示例脚本可以生成对指标层次结构中遍历的每个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

第二个输出立即显示了数据源中的行数被限制为38个内存位置,而不是完整数据源长度的4965个。

并且指标观察者在可能的情况下被限制为1,如输出的最后几行所示。


BackTrader 中文文档(二十四)(3)https://developer.aliyun.com/article/1505449

相关文章
|
5天前
|
存储 监控 网络协议
BackTrader 中文文档(二十四)(1)
BackTrader 中文文档(二十四)
12 0
|
5天前
|
索引 Python
BackTrader 中文文档(二十四)(3)
BackTrader 中文文档(二十四)
11 0
|
5天前
|
开发者 Python
BackTrader 中文文档(二十四)(4)
BackTrader 中文文档(二十四)
11 0
|
5天前
BackTrader 中文文档(二十八)(2)
BackTrader 中文文档(二十八)
11 0
|
5天前
|
存储 算法 C++
BackTrader 中文文档(二十八)(3)
BackTrader 中文文档(二十八)
11 0
|
5天前
|
Python
BackTrader 中文文档(二十八)(1)
BackTrader 中文文档(二十八)
10 0
|
5天前
|
测试技术 C语言 Python
BackTrader 中文文档(二十八)(4)
BackTrader 中文文档(二十八)
14 0
|
5天前
|
Oracle 数据可视化 关系型数据库
BackTrader 中文文档(二十六)(3)
BackTrader 中文文档(二十六)
10 0
|
5天前
|
测试技术
BackTrader 中文文档(二十六)(2)
BackTrader 中文文档(二十六)
10 0
|
5天前
|
数据可视化 测试技术 API
BackTrader 中文文档(二十六)(4)
BackTrader 中文文档(二十六)
10 0