For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Futures rollover - data feed produced seems to be empty



  • Dear All,

    I'm trying to backtest a VIX futures strategy that requires actual futures contract. It is the first time i'm looking at the rollover feature of backtester. My code is fairly simple and inspired from the rollover sample / code I found on this forum. At a high level, I load each indidivudal futures (only 4 contracts in this example) contract from CSV, then i try to "chain" them using cerebro.rollover (with checkvolume). After that, i'm adding the strategy 'TheStrategy' (same as in sample code) to display the daily bars. Unfortunately, nothing gets displayed. it seems that the datafeed generated by cerebro.rollover is empty. I have tried to investigate this issue but i have not found anything (difficult since there is not even an error message). On thing i did is to replace cerebro.rollover by cerebro.chaindata. In this case, it works well, and i see the 4 futures contracts being displayed one after the others. Code used is shown below.

    Any help to resolve/investigate this issue will be extremely welcome!

    Thanks in advance and cheers!
    Lamp'

    data from CBOE website, e.g.
    VX18 - https://markets.cboe.com/us/futures/market_statistics/historical_data/products/csv/VX/2018-12-19

    import datetime
    import time
    import argparse
    import bisect
    import calendar
    import datetime
    import backtrader as bt
    import backtrader.filters as btfilters
    from backtrader.order import Order
    
    class TheStrategy(bt.Strategy):
        def start(self):
            header = ['Len', 'Name', 'RollName', 'Datetime', '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.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 get_data_feed(data_filename):
        data = bt.feeds.GenericCSVData(
            dataname=data_filename,
            headers=True,
            dtformat=('%Y-%m-%d'),
            datetime=0,
            open=3,
            high=4,
            low=5,
            close=6,
            volume=8,
            openinterest=10,
            timeframe=bt.TimeFrame.Days,
            compression=1
        )
    
        return data
    
    def checkvolume(d0, d1):
        return d0.volume[0] < d1.volume[0]  # Switch if volume from d0 < d1
    
    month_codes = ['U18', 'V18', 'X18', 'Z18']
    ffeeds = [get_data_feed('./CFE_{0}_VX.csv'.format(x)) for x in month_codes]
    
    month_codes = ['U18', 'V18', 'X18', 'Z18']
    ffeeds = [get_data_feed('./CFE_{0}_VX.csv'.format(x)) for x in month_codes]
    cerebro = bt.Cerebro()
    rollkwargs = dict()
    rollkwargs['checkcondition'] = checkvolume
    cerebro.rolloverdata(name='FESX', *ffeeds, **rollkwargs)
    #cerebro.chaindata(name='FESX', *ffeeds)
    cerebro.addstrategy(TheStrategy)
    cerebro.run()
    
    

  • administrators

    Documentation - Docs - Rolling over Futures

      - checkcondition (default: None)
    
        Note: This will only be called if checkdate has returned True
    

    You have not specified a checkdate, which means that your checkcondition is not being called. In any case that should not be the problem, because it will simply be ignored.

    @lampalork said in Futures rollover - data feed produced seems to be empty:

            open=3,
            high=4,
            low=5,
            close=6,
    

    Those indices are offset by +1. You want 2, 3, 4, 5.

    @lampalork said in Futures rollover - data feed produced seems to be empty:

    class TheStrategy(bt.Strategy):
        def start(self):
            header = ['Len', 'Name', 'RollName', 'Datetime', 'Open',
                                'High', 'Low', 'Close', 'Volume', 'OpenInterest']
            print(', '.join(header))
    

    You say it seems to be empty, but we don't even know if you are seeing the header printed.

    I would

    • Load one of those single data feeds and see if it loads and is displayed
    • Add 2 of them manually to cerebro.rollover and see what happens.


  • Hi Backtrader,

    Thanks for getting back.

    1. i have fixed the field indices but obviously this was not the issue
    2. when i load one feed straight (i.e. using cerebro.adddata(ffeeds[0]) instead of rollover), i can see the header and the data being displayed
    3. When i load one feed or multiple feeds using rolloverdata (e.g. cerebro.rolloverdata(ffeeds[0], name='MyRoll', **rollkwargs) or cerebro.rolloverdata(ffeeds[0],ffeeds[1], name='MyRoll', **rollkwargs)), i can see the header but not the header
    4. When i load the data with chaindata (e.g. cerebro.chaindata(name='MyRoll', *ffeeds)), i can see the header and all the data (i.e. the data of the four futures one after the other)

    Hope this clarifies the issue i'm facing. In case it is relevant, i'm running backtrader version 1.9.66.122

    Thanks and regards
    Lamp'



  • Dear backtrader,

    Any idea how I could investigate this further ?
    thanks in advance

    Regards
    Lamp'


  • administrators

    Not really. The sample which is in the repository works.

    My suggestion is that you use that sample with as little modifications as possible, i.e.: change only

    ffeeds = [bt.stores. ...]
    

    and use

    ffeeds = [getdata(x) for x in fcodes]
    

    Where getdata is the one in where you fetch the data using GenericCSV

    @lampalork said in Futures rollover - data feed produced seems to be empty:

    month_codes = ['U18', 'V18', 'X18', 'Z18']
    ffeeds = [get_data_feed('./CFE_{0}_VX.csv'.format(x)) for x in month_codes]
    
    month_codes = ['U18', 'V18', 'X18', 'Z18']
    ffeeds = [get_data_feed('./CFE_{0}_VX.csv'.format(x)) for x in month_codes]
    

    In any case you may also want to remove those duplicates