并非每个供应商都为可以交易的工具提供连续的未来。有时提供的数据是仍然有效的到期日期的数据,即:仍在交易的日期
这在回溯测试方面并不是很有帮助,因为数据分散在几个不同的仪器上,这些仪器另外...时间重叠。
能够正确地将这些仪器的数据从过去连接到连续的流中,可以减轻疼痛。问题:
- 没有关于如何最好地将不同的到期日期加入连续未来的法律。
 
一些文献,由SierraChart提供,
- 我的链接
 
展期 Data Feed
backtrader 在1.8.10.99'中增加了将不同到期日的期货数据联接到连续未来的可能性:
import backtrader as bt cerebro = bt.Cerebro() data0 = bt.feeds.MyFeed(dataname='Expiry0') data1 = bt.feeds.MyFeed(dataname='Expiry1') ... dataN = bt.feeds.MyFeed(dataname='ExpiryN') drollover = cerebro.rolloverdata(data0, data1, ..., dataN, name='MyRoll', **kwargs) cerebro.run()
注意
下面解释可能\*\*kwargs
也可以通过直接访问RollOver 源来完成此操作(如果子类化完成,这将很有说明):
import backtrader as bt cerebro = bt.Cerebro() data0 = bt.feeds.MyFeed(dataname='Expiry0') data1 = bt.feeds.MyFeed(dataname='Expiry1') ... dataN = bt.feeds.MyFeed(dataname='ExpiryN') drollover = bt.feeds.RollOver(data0, data1, ..., dataN, dataname='MyRoll', **kwargs) cerebro.adddata(drollover) cerebro.run()
注意
下面解释可能\*\*kwargs
注意
使用RollOver 时,使用 dataname指定名称。这是用于传递名称/股票代码的所有 data feeds 的标准参数。在这种情况下,它将被重用,为完整的展期期货集分配一个通用名称。
在这种情况下cerebro.rolloverdata,使用 将名称分配给源,该源 name已经是该方法的一个命名参数
归纳总结:
-  
Data Feeds 像往常一样创建,但不会添加到
cerebro -  
这些 data feeds 作为输入提供给
bt.feeds.RollOver还给出了A
dataname,主要用于识别目的。 -  
然后将此翻转 data feed 添加到
cerebro 
展期选项
提供两个参数来控制展期过程
-  
checkdate(默认值:None)这必须是具有以下签名的可调用项:
checkdate(dt, d):
哪里:
-  
dt是一个datetime.datetime对象 -  
d是活跃未来的当前data feed 
预期返回值:
-  
True:只要可调用返回此值,切换即可在下一个将来发生如果商品在3月的第3个星期 五到期,
checkdate则可以在到期发生的整个星期内返回True。 -  
False:无法进行过期 
 -  
 -  
checkcondition(默认值:None)注意:仅当已返回时
checkdate才会调用此函数True如果
None这将在True内部评估(运行滚动更新)否则,这必须是具有以下签名的可调用项:
checkcondition(d0, d1)
哪里:
-  
d0是活跃未来的当前data feed -  
d1是下一个到期日的data feed 
预期返回值:
-  
True:展期到下一个未来按照from的示例
checkdate,这可以说,仅当from的volume已经小于from的volumed0时,才会发生翻转。d1 -  
False:无法进行过期 
 -  
 
子类RollOver
 如果指定可调用对象还不够,则始终有机会进行子类化RollOver。要子类化的方法:
-  
def _checkdate(self, dt, d):这与上面同名参数的签名相匹配。预期的返回值也是 saame。
 -  
def _checkcondition(self, d0, d1)这与上面同名参数的签名相匹配。预期的返回值也是 saame。
 
让我们滚吧
注意
范例中的缺省行为是使用cerebro.rolloverdata。这可以通过传递 -no-cerebro 标志来更改。在这种情况下,示例使用 RollOver 和 cerebro.adddata
该实现包括一个示例,该示例在 backtrader 源中提供。
期货串联
让我们首先通过运行没有参数的示例来查看纯串联。
$ ./rollover.py Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume, OpenInterest 0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0, 1000.0 0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0, 1101.0 ... 0176, FESX, 199FESXM4, 2014-06-20, Fri, 3315.0, 3324.0, 3307.0, 3322.0, 134777.0, 520978.0 0177, FESX, 199FESXU4, 2014-06-23, Mon, 3301.0, 3305.0, 3265.0, 3285.0, 730211.0, 3003692.0 ... 0241, FESX, 199FESXU4, 2014-09-19, Fri, 3287.0, 3308.0, 3286.0, 3294.0, 144692.0, 566249.0 0242, FESX, 199FESXZ4, 2014-09-22, Mon, 3248.0, 3263.0, 3231.0, 3240.0, 582077.0, 2976624.0 ... 0306, FESX, 199FESXZ4, 2014-12-19, Fri, 3196.0, 3202.0, 3131.0, 3132.0, 226415.0, 677924.0 0307, FESX, 199FESXH5, 2014-12-22, Mon, 3151.0, 3177.0, 3139.0, 3168.0, 547095.0, 2952769.0 ... 0366, FESX, 199FESXH5, 2015-03-20, Fri, 3680.0, 3698.0, 3672.0, 3695.0, 147632.0, 887205.0 0367, FESX, 199FESXM5, 2015-03-23, Mon, 3654.0, 3655.0, 3608.0, 3618.0, 802344.0, 3521988.0 ... 0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0, 1173246.0, 811805.0 0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0, 516792.0
这个用途cerebro.chaindata 和结果应该是明确的:
-  
每当一个 data feed 结束时,下一个接手
 -  
这总是发生在周五和周一之间:样本中的期货总是在周五到期
 
期货展期,无需支票
让我们运行--rollover
$ ./rollover.py --rollover --plot Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume, OpenInterest 0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0, 1000.0 0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0, 1101.0 ... 0176, FESX, 199FESXM4, 2014-06-20, Fri, 3315.0, 3324.0, 3307.0, 3322.0, 134777.0, 520978.0 0177, FESX, 199FESXU4, 2014-06-23, Mon, 3301.0, 3305.0, 3265.0, 3285.0, 730211.0, 3003692.0 ... 0241, FESX, 199FESXU4, 2014-09-19, Fri, 3287.0, 3308.0, 3286.0, 3294.0, 144692.0, 566249.0 0242, FESX, 199FESXZ4, 2014-09-22, Mon, 3248.0, 3263.0, 3231.0, 3240.0, 582077.0, 2976624.0 ... 0306, FESX, 199FESXZ4, 2014-12-19, Fri, 3196.0, 3202.0, 3131.0, 3132.0, 226415.0, 677924.0 0307, FESX, 199FESXH5, 2014-12-22, Mon, 3151.0, 3177.0, 3139.0, 3168.0, 547095.0, 2952769.0 ... 0366, FESX, 199FESXH5, 2015-03-20, Fri, 3680.0, 3698.0, 3672.0, 3695.0, 147632.0, 887205.0 0367, FESX, 199FESXM5, 2015-03-23, Mon, 3654.0, 3655.0, 3608.0, 3618.0, 802344.0, 3521988.0 ... 0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0, 1173246.0, 811805.0 0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0, 516792.0
相同的行为。可以清楚地看到,合同变更是在3月,6月,9月,12月的第三 个星期五进行的。
但这大多是错误的。回溯器无法知道,但作者知道EuroStoxx 50期货在12:00 CET停止交易。因此,即使到期月份的第 3个星期五有每日柱线,这种变化也为时已晚。
一周内的变化
在示例中实现了一个checkdate callabe,它计算当前活动合约的到期日期。
checkdate 一旦达到当月第 3个星期五的那一周,将允许展期(例如,如果星期一是银行假日,则可能是星期二)
$ ./rollover.py --rollover --checkdate --plot Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume, OpenInterest 0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0, 1000.0 0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0, 1101.0 ... 0171, FESX, 199FESXM4, 2014-06-13, Fri, 3283.0, 3292.0, 3253.0, 3276.0, 734907.0, 2715357.0 0172, FESX, 199FESXU4, 2014-06-16, Mon, 3261.0, 3275.0, 3252.0, 3262.0, 180608.0, 844486.0 ... 0236, FESX, 199FESXU4, 2014-09-12, Fri, 3245.0, 3247.0, 3220.0, 3232.0, 650314.0, 2726874.0 0237, FESX, 199FESXZ4, 2014-09-15, Mon, 3209.0, 3224.0, 3203.0, 3221.0, 153448.0, 983793.0 ... 0301, FESX, 199FESXZ4, 2014-12-12, Fri, 3127.0, 3143.0, 3038.0, 3042.0, 1409834.0, 2934179.0 0302, FESX, 199FESXH5, 2014-12-15, Mon, 3041.0, 3089.0, 2963.0, 2980.0, 329896.0, 904053.0 ... 0361, FESX, 199FESXH5, 2015-03-13, Fri, 3657.0, 3680.0, 3627.0, 3670.0, 867678.0, 3499116.0 0362, FESX, 199FESXM5, 2015-03-16, Mon, 3594.0, 3641.0, 3588.0, 3629.0, 250445.0, 1056099.0 ... 0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0, 1173246.0, 811805.0 0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0, 516792.0
好多了。现在,翻滚发生在5天前。对Len指数的快速目视检查显示出来。例如:
199FESXM4发生在199FESXU4len171-172.没有checkdate它发生在176-177
展期在到期月份的第 三个星期五之前的星期一进行。
添加 volume 条件
即使有所改善,情况也可以进一步改善,因为不仅日期,而且 volume 都将得到考虑。当新合约的交易量比当前活跃的合约 volume 时,请切换。
让我们将 acheckcondition 添加到组合中并运行。
$ ./rollover.py --rollover --checkdate --checkcondition --plot Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume, OpenInterest 0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0, 1000.0 0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0, 1101.0 ... 0175, FESX, 199FESXM4, 2014-06-19, Thu, 3307.0, 3330.0, 3300.0, 3321.0, 717979.0, 759122.0 0176, FESX, 199FESXU4, 2014-06-20, Fri, 3309.0, 3318.0, 3290.0, 3298.0, 711627.0, 2957641.0 ... 0240, FESX, 199FESXU4, 2014-09-18, Thu, 3249.0, 3275.0, 3243.0, 3270.0, 846600.0, 803202.0 0241, FESX, 199FESXZ4, 2014-09-19, Fri, 3273.0, 3293.0, 3250.0, 3252.0, 1042294.0, 3021305.0 ... 0305, FESX, 199FESXZ4, 2014-12-18, Thu, 3095.0, 3175.0, 3085.0, 3172.0, 1309574.0, 889112.0 0306, FESX, 199FESXH5, 2014-12-19, Fri, 3195.0, 3200.0, 3106.0, 3147.0, 1329040.0, 2964538.0 ... 0365, FESX, 199FESXH5, 2015-03-19, Thu, 3661.0, 3691.0, 3646.0, 3668.0, 1271122.0, 1054639.0 0366, FESX, 199FESXM5, 2015-03-20, Fri, 3607.0, 3664.0, 3595.0, 3646.0, 1182235.0, 3407004.0 ... 0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0, 1173246.0, 811805.0 0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0, 516792.0
甚至更好*。我们已将切换日期移至到期月份众所周知的第 3 个星期五之前的星期四
这应该不足为奇,因为到期的未来交易在周五的小时数要少得多,而且 volume 必须很小。
注意
展期日期也可以由可调用者设置为该checkdate 星期四。但这不是样本的重点。
总结
backtrader 现在包括一个灵活的机制,允许展期期货以创建连续流。
示例用法
$ ./rollover.py --help
usage: rollover.py [-h] [--no-cerebro] [--rollover] [--checkdate]
                   [--checkcondition] [--plot [kwargs]]
Sample for Roll Over of Futures
optional arguments:
  -h, --help            show this help message and exit
  --no-cerebro          Use RollOver Directly (default: False)
  --rollover
  --checkdate           Change during expiration week (default: False)
  --checkcondition      Change when a given condition is met (default: False)
  --plot [kwargs], -p [kwargs]
                        Plot the read data applying any kwargs passed For
                        example: --plot style="candle" (to plot candles)
                        (default: None)
示例代码
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import bisect
import calendar
import datetime
import backtrader as bt
class TheStrategy(bt.Strategy):
    def start(self):
        header = ['Len', 'Name', 'RollName', 'Datetime', 'WeekDay', 'Open',
                  'High', 'Low', 'Close', 'Volume', 'OpenInterest']
        print(', '.join(header))
    def next(self):
        txt = list()
        txt.append('%04d' % len(self.data0))
        txt.append('{}'.format(self.data0._dataname))
        # Internal knowledge ... current expiration in use is in _d
        txt.append('{}'.format(self.data0._d._dataname))
        txt.append('{}'.format(self.data.datetime.date()))
        txt.append('{}'.format(self.data.datetime.date().strftime('%a')))
        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]))
        print(', '.join(txt))
def checkdate(dt, d):
    # Check if the date is in the week where the 3rd friday of Mar/Jun/Sep/Dec
    # EuroStoxx50 expiry codes: MY
    # M -> H, M, U, Z (Mar, Jun, Sep, Dec)
    # Y -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 -> year code. 5 -> 2015
    MONTHS = dict(H=3, M=6, U=9, Z=12)
    M = MONTHS[d._dataname[-2]]
    centuria, year = divmod(dt.year, 10)
    decade = centuria * 10
    YCode = int(d._dataname[-1])
    Y = decade + YCode
    if Y < dt.year:  # Example: year 2019 ... YCode is 0 for 2020
        Y += 10
    exp_day = 21 - (calendar.weekday(Y, M, 1) + 2) % 7
    exp_dt = datetime.datetime(Y, M, exp_day)
    # Get the year, week numbers
    exp_year, exp_week, _ = exp_dt.isocalendar()
    dt_year, dt_week, _ = dt.isocalendar()
    # print('dt {} vs {} exp_dt'.format(dt, exp_dt))
    # print('dt_week {} vs {} exp_week'.format(dt_week, exp_week))
    # can switch if in same week
    return (dt_year, dt_week) == (exp_year, exp_week)
def checkvolume(d0, d1):
    return d0.volume[0] < d1.volume[0]  # Switch if volume from d0 < d1
def runstrat(args=None):
    args = parse_args(args)
    cerebro = bt.Cerebro()
    fcodes = ['199FESXM4', '199FESXU4', '199FESXZ4', '199FESXH5', '199FESXM5']
    store = bt.stores.VChartFile()
    ffeeds = [store.getdata(dataname=x) for x in fcodes]
    rollkwargs = dict()
    if args.checkdate:
        rollkwargs['checkdate'] = checkdate
        if args.checkcondition:
            rollkwargs['checkcondition'] = checkvolume
    if not args.no_cerebro:
        if args.rollover:
            cerebro.rolloverdata(name='FESX', *ffeeds, **rollkwargs)
        else:
            cerebro.chaindata(name='FESX', *ffeeds)
    else:
        drollover = bt.feeds.RollOver(*ffeeds, dataname='FESX', **rollkwargs)
        cerebro.adddata(drollover)
    cerebro.addstrategy(TheStrategy)
    cerebro.run(stdstats=False)
    if args.plot:
        pkwargs = dict(style='bar')
        if args.plot is not True:  # evals to True but is not True
            npkwargs = eval('dict(' + args.plot + ')')  # args were passed
            pkwargs.update(npkwargs)
        cerebro.plot(**pkwargs)
def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for Roll Over of Futures')
    parser.add_argument('--no-cerebro', required=False, action='store_true',
                        help='Use RollOver Directly')
    parser.add_argument('--rollover', required=False, action='store_true')
    parser.add_argument('--checkdate', required=False, action='store_true',
                        help='Change during expiration week')
    parser.add_argument('--checkcondition', required=False,
                        action='store_true',
                        help='Change when a given condition is met')
    # Plot options
    parser.add_argument('--plot', '-p', nargs='?', required=False,
                        metavar='kwargs', const=True,
                        help=('Plot the read data applying any kwargs passed\n'
                              '\n'
                              'For example:\n'
                              '\n'
                              '  --plot style="candle" (to plot candles)\n'))
    if pargs is not None:
        return parser.parse_args(pargs)
    return parser.parse_args()
if __name__ == '__main__':
    runstrat()