Skip to content

同步不同的市场

原文: https://www.backtrader.com/blog/posts/2016-04-19-sync-different-markets/sync-different-markets/

使用越多,backtrader 必须面对的想法和意外场景的组合就越多。对于每一个新的平台,我们都面临着一个挑战,看看该平台是否能够达到开发开始时提出的期望,灵活性和易用性是目标,而Python被选为基石。

Ticket提出了一个问题,即是否可以使用不同的交易日历同步市场。这样做的直接尝试失败了,问题创建者想知道backtrader为什么不看日期。

在给出任何答案之前,必须考虑:

  • 不一致天数的指标行为

后者的答案是:

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

考虑到股票市场价格是datetime系列的事实,上述数据在一定范围内可以保持真实。对于多个数据,以下设计注意事项适用:

  • 添加到cerebro的 1st数据为datamaster

  • 所有其他数据必须与时间对齐/同步,且永远无法超过(以datetime术语)的datamaster

将上面的 3 个要点放在一起,可以提供问题创建者所经历的组合。情景:

  • 日历年:2012

  • 数据 0:^GSPC(或 S&P500 为好友)

  • 数据 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
... 

一旦交易日历出现分歧。data0datamaster^GSPC),即使data1^GDAXI)会在2012-01-16上提供一个酒吧,的&P500也不是一个交易日

^GSPC的下一个交易日到来时,backtrader对上述设计限制所能做的最好的处理是2012-01-17^GDAXI的下一个尚未处理的日期交付2012-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再次尝试:欧洲人的交易频率高于美国人。让我们把^GSPC^GDAXI的角色颠倒过来,看看结果:

$ ./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复制了data1(本例中为^GSPC)的2012-01-13值,作为data0(现在为^GDAXI)交付2012-01-16的匹配。

更好的是:

  • 与下一个日期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
... 

这样的情节不断重复,直到^GDAXI的最后一个小节交付:

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

同步问题的原因是backtrader没有复制数据。

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

  • 如果无法为datamaster的当前datetime发送条带(例如,因为它将被超越),那么次佳数据就是重新发送

    这是一个酒吧,有一个已经见过的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 

通过--fillerWeekDaysFiller添加到data0data1中。以及输出:

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

2012-01-13-2012-01-17的 1st难题已经过去。而整台是同步

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

值得注意的是:

  • ^GSPCdata0我们有250行(指数在2012交易250天)

  • 对于^GDAXI我们data0256行(该指数在2012交易256天)

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

    加上522(周末和周末中的几天),我们将得到364。在一年中正常的365日之前,剩下的一天肯定是周六周日*。

过滤器正在填充给定数据未发生交易的日期的NaN值。让我们来描绘它:

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

!image

充满的日子很明显:

  • 钢筋之间的间隙在那里

  • 对于图而言,差距更为明显

一个 2图将试图回答顶部的问题:指示器会发生什么?。请记住,新条形图的值为NaN(这就是它们不显示的原因):

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

!image

你在泡藤壶!简单移动平均线打破了时空连续性,跳过了一些没有连续性解的条。这当然是填充而不是数字akaNaN数学运算不再有意义的效果。

如果使用上次看到的收盘价而不是NaN

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

在整整 260 天的时间里,用一个普通的形状记忆合金,情节看起来好多了

!image

结论

将两种工具与不同的交易日历同步是一个决策和妥协的问题。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() 

我们一直在努力

apachecn/AiLearning

【布客】中文翻译组