Backtrader同步不同市场

  |  

使用次数越多, backtrader 必须面对的想法和意外场景的混合就越多。对于每个新平台,一个挑战是要看看平台是否能够达到开发开始时设置的期望,灵活性和易用性是目标,Python被选为基石。

工单#76 提出了一个问题,即是否可以完成具有不同交易日历的同步市场。直接尝试这样做失败了,问题创建者想知道为什么不 backtrader 查看日期。

在给出任何答案之前,必须考虑以下几点:

  • 未对齐的天数的指示器的行为

对后者的回答是:

  • 该平台尽可能地 datetime 不可知论,不会查看字段的内容来评估这些概念

考虑到股票市场价格是datetime 串行的事实,上述可以保持 true 达到一定的限度。对于多个数据,以下设计注意事项适用:

  • 添加到cerebro的第 1数据是datamaster

  • 所有其他数据必须在时间上对齐/同步,它永远无法超越(就datetime 而言) datamaster

将上面的3个项目符号放在一起,可以提供问题创建者所经历的组合。场景:

  • 历年:2012

  • 数据0:^GSPC (或朋友的标准普尔500指数)

  • 数据1:^GDAXI (或朋友的Dax指数)

通过以下方式运行自订文稿以查看数据是如何同步的backtrader

$ ./weekdaysaligner.py --online --data1 '^GSPC' --data0 '^GDAXI'

输出:

0001,  True, data0, 2012-01-03T23:59:59, 2012-01-03T23:59:59, data1
0002,  True, data0, 2012-01-04T23:59:59, 2012-01-04T23:59:59, data1
0003,  True, data0, 2012-01-05T23:59:59, 2012-01-05T23:59:59, data1
0004,  True, data0, 2012-01-06T23:59:59, 2012-01-06T23:59:59, data1
0005,  True, data0, 2012-01-09T23:59:59, 2012-01-09T23:59:59, data1
0006,  True, data0, 2012-01-10T23:59:59, 2012-01-10T23:59:59, data1
0007,  True, data0, 2012-01-11T23:59:59, 2012-01-11T23:59:59, data1
0008,  True, data0, 2012-01-12T23:59:59, 2012-01-12T23:59:59, data1
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010, False, data0, 2012-01-17T23:59:59, 2012-01-16T23:59:59, data1
0011, False, data0, 2012-01-18T23:59:59, 2012-01-17T23:59:59, data1
...

一旦2012-01-16 交易日历发散。这是 data0 datamaster^GSPC),即使 data1^GDAXI)会有一个柱线来兑现 2012-01-16,这不是标准普尔500指数 的交易日

最好的是,backtrader 当下一个交易日 ^GSPC 进来时,可以对上述设计限制做些什么, 2012-01-17 交付下一个尚未处理的日期 ^GDAXI2012-01-16.

同步问题随着每个发散日而累积。在它的末尾2012 如下所示:

...
0249, False, data0, 2012-12-28T23:59:59, 2012-12-19T23:59:59, data1
0250, False, data0, 2012-12-31T23:59:59, 2012-12-20T23:59:59, data1

原因应该很明显:欧洲人比美国人交易的天数更多。

在票证 #76https://github.com/mementum/backtrader/issues/76 中,海报显示了什么 zipline 。让我们来看看 2012-01-13 - 2012-01-17 难题:

0009 : True : 2012-01-13 : close 1289.09 - 2012-01-13 :  close 6143.08
0010 : False : 2012-01-13 : close 1289.09 - 2012-01-16 :  close 6220.01
0011 : True : 2012-01-17 : close 1293.67 - 2012-01-17 :  close 6332.93

起泡的藤壶!2012-01-13 的数据只是简单地 拷贝, 而没有明显要求用户许可。恕我直言,这不应该是因为平台的最终用户无法撤消此自发添加。

注意

除了简要介绍zipline之外,作者不知道这是否是脚本开发人员配置的标准行为,以及是否可以撤消

一旦我们看到其他人,让我们再次backtrader尝试使用积累的智能:欧洲人比美国人更频繁地交易。让我们反转 和 ^GDAXI 的角色^GSPC,看看结果:

$ ./weekdaysaligner.py --online --data1 '^GSPC' --data0 '^GDAXI'

输出(跳到2012-01-13 直接):

...
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010, False, data0, 2012-01-16T23:59:59, 2012-01-13T23:59:59, data1
0011,  True, data0, 2012-01-17T23:59:59, 2012-01-17T23:59:59, data1
...

再次起泡藤壶!backtrader还拷贝了 2012-01-13 (在本例^GSPC中 ) 的值data1作为 (现在 ^GDAXI) 交付的2012-01-16匹配data0项。

甚至更好:

  • 在下一个日期实现同步: 2012-01-17

同样的重新同步很快就会再次出现:

...
0034,  True, data0, 2012-02-17T23:59:59, 2012-02-17T23:59:59, data1
0035, False, data0, 2012-02-20T23:59:59, 2012-02-17T23:59:59, data1
0036,  True, data0, 2012-02-21T23:59:59, 2012-02-21T23:59:59, data1
...

其次是不太容易的重新同步:

...
0068,  True, data0, 2012-04-05T23:59:59, 2012-04-05T23:59:59, data1
0069, False, data0, 2012-04-10T23:59:59, 2012-04-09T23:59:59, data1
...
0129, False, data0, 2012-07-04T23:59:59, 2012-07-03T23:59:59, data1
0130,  True, data0, 2012-07-05T23:59:59, 2012-07-05T23:59:59, data1
...

这样的情节不断重复,直到 last^GDAXI 交付:

...
0256,  True, data0, 2012-12-31T23:59:59, 2012-12-31T23:59:59, data1
...

此同步问题backtrader 的原因是不会拷贝数据。

  • datamaster一旦交付了一个新的酒吧,另datas一个就会被要求交付

  • 如果没有柱线可以传递电流datetime datamaster (例如,因为它将被超越),那么下一个最佳数据就是,可以说,重新传递

    这是一个已经看到的酒吧date

正确同步

但并非所有的希望都消失了。backtrader 可以交付。让我们使用 筛检程序backtrader 这项技术允许在数据到达平台最深处之前对其进行操作,例如计算指针。

注意

delivering 是一个感知问题,因此交付的 backtrader 可能不是接收者所期望的 delivery

实际代码

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime


class WeekDaysFiller(object):
    '''Bar Filler to add missing calendar days to trading days'''
    # kickstart value for date comparisons
    lastdt = datetime.datetime.max.toordinal()

    def __init__(self, data, fillclose=False):
        self.fillclose = fillclose
        self.voidbar = [float('Nan')] * data.size()  # init a void bar

    def __call__(self, data):
        '''Empty bars (NaN) or with last close price are added for weekdays with no
        data

        Params:
          - data: the data source to filter/process

        Returns:
          - True (always): bars are removed (even if put back on the stack)

        '''
        dt = data.datetime.dt()  # current date in int format
        lastdt = self.lastdt + 1  # move the last seen data once forward

        while lastdt < dt:  # loop over gap bars
            if datetime.date.fromordinal(lastdt).isoweekday() < 6:  # Mon-Fri
                # Fill in date and add new bar to the stack
                if self.fillclose:
                    self.voidbar = [self.lastclose] * data.size()
                self.voidbar[-1] = float(lastdt) + data.sessionend
                data._add2stack(self.voidbar[:])

            lastdt += 1  # move lastdt forward

        self.lastdt = dt  # keep a record of the last seen date

        self.lastclose = data.close[0]
        data._save2stack(erase=True)  # dt bar to the stack and out of stream
        return True  # bars are on the stack (new and original)

测试文稿已经具备了使用它的功能:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler

--filler 添加到 WeekDaysFillerdata0 data1中。输出:

0001,  True, data0, 2012-01-03T23:59:59, 2012-01-03T23:59:59, data1
...
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010,  True, data0, 2012-01-16T23:59:59, 2012-01-16T23:59:59, data1
0011,  True, data0, 2012-01-17T23:59:59, 2012-01-17T23:59:59, data1
...

第1 难题 - 2012-01-13 2012-01-17 已经消失了。并且整个集合是同步的:

...
0256,  True, data0, 2012-12-25T23:59:59, 2012-12-25T23:59:59, data1
0257,  True, data0, 2012-12-26T23:59:59, 2012-12-26T23:59:59, data1
0258,  True, data0, 2012-12-27T23:59:59, 2012-12-27T23:59:59, data1
0259,  True, data0, 2012-12-28T23:59:59, 2012-12-28T23:59:59, data1
0260,  True, data0, 2012-12-31T23:59:59, 2012-12-31T23:59:59, data1

值得注意的事情:

  • 正如我们^GSPClines250(指数交易250日)2012data0

  • data0 256 我们有^GDAXIlines(指数交易256日)2012

  • 随着到位,WeekDaysFiller 两个数据的长度已扩展到 260

    添加52 * 2 (周末和周末中的几天),我们最终会得到 364.在一年中正常 365 日子之前的剩余日子肯定是星期六或星期日。

筛选器填充NaN 给定数据未进行交易的天数的值。让我们把它画出来:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot

充满的日子是显而易见的:

  • 柱线之间的间隙就在那里

  • 对于 volume 图来说,差距更加明显

第2个图将尝试回答顶部的问题:指针会发生什么?请记住,新柱线的值已指定为 NaN (这就是为什么它们不显示):

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot --sma 10

重新起泡藤壶!简单移动平均线打破了时空连续体,在没有连续性解的情况下跳过了一些柱。这当然是用Not a Number(又名NaN)填充的效果:数学运算不再有意义。

如果使用lastNaN看到的收盘价:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot --sma 10 --fillclose

在整个260天内,使用常规SMA,该情节看起来要好得多。

结论

将两种工具与不同的交易日历同步是一个决策和妥协的问题。backtrader 需要时间一致的数据来处理多个数据,不同的交易日历无济于事。

使用这里描述的WeekDaysFiller 可以缓解这种情况,但它绝不是普遍的灵丹妙药,因为用哪些值来填补是一个长期和长期考虑的问题。

脚本代码和用法

可作为以下来源backtrader的样品:

$ ./weekdaysaligner.py --help
usage: weekdaysaligner.py [-h] [--online] --data0 DATA0 [--data1 DATA1]
                          [--sma SMA] [--fillclose] [--filler] [--filler0]
                          [--filler1] [--fromdate FROMDATE] [--todate TODATE]
                          [--plot]

Sample for aligning with trade

optional arguments:
  -h, --help            show this help message and exit
  --online              Fetch data online from Yahoo (default: False)
  --data0 DATA0         Data 0 to be read in (default: None)
  --data1 DATA1         Data 1 to be read in (default: None)
  --sma SMA             Add a sma to the datas (default: 0)
  --fillclose           Fill with Close price instead of NaN (default: False)
  --filler              Add Filler to Datas 0 and 1 (default: False)
  --filler0             Add Filler to Data 0 (default: False)
  --filler1             Add Filler to Data 1 (default: False)
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format (default:
                        2012-01-01)
  --todate TODATE, -t TODATE
                        Ending date in YYYY-MM-DD format (default: 2012-12-31)
  --plot                Do plot (default: False)

代码:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import backtrader.utils.flushfile

# from wkdaysfiller import WeekDaysFiller
from weekdaysfiller import WeekDaysFiller


class St(bt.Strategy):
    params = (('sma', 0),)

    def __init__(self):
        if self.p.sma:
            btind.SMA(self.data0, period=self.p.sma)
            btind.SMA(self.data1, period=self.p.sma)

    def next(self):
        dtequal = (self.data0.datetime.datetime() ==
                   self.data1.datetime.datetime())

        txt = ''
        txt += '%04d, %5s' % (len(self), str(dtequal))
        txt += ', data0, %s' % self.data0.datetime.datetime().isoformat()
        txt += ', %s, data1' % self.data1.datetime.datetime().isoformat()
        print(txt)


def runstrat():
    args = parse_args()

    fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
    todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')

    cerebro = bt.Cerebro(stdstats=False)

    DataFeed = btfeeds.YahooFinanceCSVData
    if args.online:
        DataFeed = btfeeds.YahooFinanceData

    data0 = DataFeed(dataname=args.data0, fromdate=fromdate, todate=todate)

    if args.data1:
        data1 = DataFeed(dataname=args.data1, fromdate=fromdate, todate=todate)
    else:
        data1 = data0.clone()

    if args.filler or args.filler0:
        data0.addfilter(WeekDaysFiller, fillclose=args.fillclose)

    if args.filler or args.filler1:
        data1.addfilter(WeekDaysFiller, fillclose=args.fillclose)

    cerebro.adddata(data0)
    cerebro.adddata(data1)

    cerebro.addstrategy(St, sma=args.sma)
    cerebro.run(runonce=True, preload=True)

    if args.plot:
        cerebro.plot(style='bar')


def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for aligning with trade ')

    parser.add_argument('--online', required=False, action='store_true',
                        help='Fetch data online from Yahoo')

    parser.add_argument('--data0', required=True, help='Data 0 to be read in')
    parser.add_argument('--data1', required=False, help='Data 1 to be read in')

    parser.add_argument('--sma', required=False, default=0, type=int,
                        help='Add a sma to the datas')

    parser.add_argument('--fillclose', required=False, action='store_true',
                        help='Fill with Close price instead of NaN')

    parser.add_argument('--filler', required=False, action='store_true',
                        help='Add Filler to Datas 0 and 1')

    parser.add_argument('--filler0', required=False, action='store_true',
                        help='Add Filler to Data 0')

    parser.add_argument('--filler1', required=False, action='store_true',
                        help='Add Filler to Data 1')

    parser.add_argument('--fromdate', '-f', default='2012-01-01',
                        help='Starting date in YYYY-MM-DD format')

    parser.add_argument('--todate', '-t', default='2012-12-31',
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--plot', required=False, action='store_true',
                        help='Do plot')

    return parser.parse_args()


if __name__ == '__main__':
    runstrat()

推荐阅读

相关文章

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

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

Backtrader观察员和统计

在 backtrader 内部运行的策略主要处理数据 和 指针。 数据被添加到Cerebro 实例中,并最终成为策略输入的一部分(解析并用作实例的属性),而指针由策略本身声明和管理。

Backtrader标杆

backtrader 包括 2 种不同类型的对象,可帮助进行跟踪: Observers Analyzers 工单 #89 是关于添加资产基准测试的。明智的是,人们实际上可能有一个策略,即使积极,也低于简单地跟踪资产所能提供的策略。

Backtrader同步不同市场

使用次数越多, backtrader 必须面对的想法和意外场景的混合就越多。对于每个新平台,一个挑战是要看看平台是否能够达到开发开始时设置的期望,灵活性和易用性是目标,Python被选为基石。 工单#76 提出了一个问题,即是否可以完成具有不同交易日历的同步市场。

Backtrader教程:过滤器 - 参考

工作阶段筛检程序 类 backtrader.filters。

Backtrader python 隐藏的细节

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

Backtrader教程:日期时间 - 管理

在 1.5.0 版之前, backtrader 使用直接的方法来进行时间管理,因为数据源计算的任何日期时间都只是按面值使用。 对于任何用户输入也是如此,例如可以提供给任何数据源的参数fromdate (或 sessionstart)的情况 考虑到直接控制冻结的数据源以进行回溯测试,这种方法很好。

Backtrader教程:经纪商

经纪商仿真器该模拟支持不同的订单类型,根据当前现金检查提交的订单现金需求,跟踪每次反复运算的cerebro 现金和价值,并在不同数据上保持当前位置。

Backtrader蟒蛇隐藏的力量3

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

Backtrader卡尔曼等

注意 对以下指令的支持从提交开始 发布1.9.30.x 将是包含它的第1个版本 。 backtrader的原始目标之一是成为纯python,即:仅使用标准发行版中可用的软件包。只有一个例外是matplotlib在没有重新发明轮子的情况下进行绘图。