Pairs Trading example error



  • I am exploring backtrader.com and making use of all the examples as you might noticed :').
    Script:

    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
    import backtrader.feeds as btfeeds
    import backtrader.indicators as btind
    
    class PairTradingStrategy(bt.Strategy):
        params = dict(
            period=10,
            stake=10,
            qty1=0,
            qty2=0,
            printout=True,
            upper=2.1,
            lower=-2.1,
            up_medium=0.5,
            low_medium=-0.5,
            status=0,
            portfolio_value=10000,
        )
    
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or self.data.datetime[0]
                dt = bt.num2date(dt)
                print('%s, %s' % (dt.isoformat(), txt))
    
        def notify_order(self, order):
            if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
                return  # Await further notifications
    
            if order.status == order.Completed:
                if order.isbuy():
                    buytxt = 'BUY COMPLETE, %.2f' % order.executed.price
                    self.log(buytxt, order.executed.dt)
                else:
                    selltxt = 'SELL COMPLETE, %.2f' % order.executed.price
                    self.log(selltxt, order.executed.dt)
    
            elif order.status in [order.Expired, order.Canceled, order.Margin]:
                self.log('%s ,' % order.Status[order.status])
                pass  # Simply log
    
            # Allow new orders
            self.orderid = None
    
        def __init__(self):
            # To control operation entries
            self.orderid = None
            self.qty1 = self.p.qty1
            self.qty2 = self.p.qty2
            self.upper_limit = self.p.upper
            self.lower_limit = self.p.lower
            self.up_medium = self.p.up_medium
            self.low_medium = self.p.low_medium
            self.status = self.p.status
            self.portfolio_value = self.p.portfolio_value
    
            # Signals performed with PD.OLS :
            self.transform = btind.OLS_TransformationN(self.data0, self.data1,
                                                       period=self.p.period)
            self.zscore = self.transform.zscore
    
            # Checking signals built with StatsModel.API :
            # self.ols_transfo = btind.OLS_Transformation(self.data0, self.data1,
            #                                             period=self.p.period,
            #                                             plot=True)
    
        def next(self):
    
            if self.orderid:
                return  # if an order is active, no new orders are allowed
    
            if self.p.printout:
                print('Self  len:', len(self))
                print('Data0 len:', len(self.data0))
                print('Data1 len:', len(self.data1))
                print('Data0 len == Data1 len:',
                      len(self.data0) == len(self.data1))
    
                print('Data0 dt:', self.data0.datetime.datetime())
                print('Data1 dt:', self.data1.datetime.datetime())
    
            print('status is', self.status)
            print('zscore is', self.zscore[0])
    
            # Step 2: Check conditions for SHORT & place the order
            # Checking the condition for SHORT
            if (self.zscore[0] > self.upper_limit) and (self.status != 1):
    
                # Calculating the number of shares for each stock
                value = 0.5 * self.portfolio_value  # Divide the cash equally
                x = int(value / (self.data0.close))  # Find the number of shares for Stock1
                y = int(value / (self.data1.close))  # Find the number of shares for Stock2
                print('x + self.qty1 is', x + self.qty1)
                print('y + self.qty2 is', y + self.qty2)
    
                # Placing the order
                self.log('SELL CREATE %s, price = %.2f, qty = %d' % ("ABN", self.data0.close[0], x + self.qty1))
                self.sell(data=self.data0, size=(x + self.qty1))  # Place an order for buying y + qty2 shares
                self.log('BUY CREATE %s, price = %.2f, qty = %d' % ("ING", self.data1.close[0], y + self.qty2))
                self.buy(data=self.data1, size=(y + self.qty2))  # Place an order for selling x + qty1 shares
    
                # Updating the counters with new value
                self.qty1 = x  # The new open position quantity for Stock1 is x shares
                self.qty2 = y  # The new open position quantity for Stock2 is y shares
    
                self.status = 1  # The current status is "short the spread"
    
                # Step 3: Check conditions for LONG & place the order
                # Checking the condition for LONG
            elif (self.zscore[0] < self.lower_limit) and (self.status != 2):
    
                # Calculating the number of shares for each stock
                value = 0.5 * self.portfolio_value  # Divide the cash equally
                x = int(value / (self.data0.close))  # Find the number of shares for Stock1
                y = int(value / (self.data1.close))  # Find the number of shares for Stock2
                print('x + self.qty1 is', x + self.qty1)
                print('y + self.qty2 is', y + self.qty2)
    
                # Place the order
                self.log('BUY CREATE %s, price = %.2f, qty = %d' % ("ABN", self.data0.close[0], x + self.qty1))
                self.buy(data=self.data0, size=(x + self.qty1))  # Place an order for buying x + qty1 shares
                self.log('SELL CREATE %s, price = %.2f, qty = %d' % ("ING", self.data1.close[0], y + self.qty2))
                self.sell(data=self.data1, size=(y + self.qty2))  # Place an order for selling y + qty2 shares
    
                # Updating the counters with new value
                self.qty1 = x  # The new open position quantity for Stock1 is x shares
                self.qty2 = y  # The new open position quantity for Stock2 is y shares
                self.status = 2  # The current status is "long the spread"
    
    
                # Step 4: Check conditions for No Trade
                # If the z-score is within the two bounds, close all
            """
            elif (self.zscore[0] < self.up_medium and self.zscore[0] > self.low_medium):
                self.log('CLOSE LONG %s, price = %.2f' % ("ABN", self.data0.close[0]))
                self.close(self.data0)
                self.log('CLOSE LONG %s, price = %.2f' % ("ING", self.data1.close[0]))
                self.close(self.data1)
            """
    
        def stop(self):
            print('==================================================')
            print('Starting Value - %.2f' % self.broker.startingcash)
            print('Ending   Value - %.2f' % self.broker.getvalue())
            print('==================================================')
    
    
    def runstrategy():
        args = parse_args()
    
        # Create a cerebro
        cerebro = bt.Cerebro()
    
        # Get the dates from the args
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y%m%d %H:%M:%S')
        todate = datetime.datetime.strptime(args.todate, '%Y%m%d %H:%M:%S')
    
        # Create the 1st data
        class Tickstory(btfeeds.GenericCSVData):
            params = (
                ('dtformat', '%Y%m%d %H:%M:%S'),
                ('datetime', 0),
                ('time', -1),
                ('open', 1),
                ('high', 2),
                ('low', 3),
                ('close', 4),
                ('volume', -1),
                ('openinterest', -1),
                ('timeframe', bt.TimeFrame.Minutes),
                ('compression', 1)
            )
        # Develop feed    
        data0 = Tickstory(dataname='/Users/rf/Documents/DSC/Datasets/abn.csv')
    
        # Add the 1st data to cerebro
        cerebro.adddata(data0)
    
        # Create the 1st data
        class Tickstory(btfeeds.GenericCSVData):
            params = (
                ('dtformat', '%Y%m%d %H:%M:%S'),
                ('datetime', 0),
                ('time', -1),
                ('open', 1),
                ('high', 2),
                ('low', 3),
                ('close', 4),
                ('volume', -1),
                ('openinterest', -1),
                ('timeframe', bt.TimeFrame.Minutes),
                ('compression', 1)
            )
        # Develop feed    
        data1 = Tickstory(dataname='/Users/rf/Documents/DSC/Datasets/inga.csv')
        # Add the 2nd data to cerebro
        cerebro.adddata(data1)
        # Add the strategy
        cerebro.addstrategy(PairTradingStrategy,
                            period=args.period,
                            stake=args.stake)
    
        # Add the commission - only stocks like a for each operation
        cerebro.broker.setcash(args.cash)
    
        # Add the commission - only stocks like a for each operation
        cerebro.broker.setcommission(commission=args.commperc)
    
        # And run it
        cerebro.run(runonce=not args.runnext,
                    preload=not args.nopreload,
                    oldsync=args.oldsync)
    
        # Plot if requested
        if args.plot:
            cerebro.plot(numfigs=args.numfigs, volume=False, zdown=False)
    
    
    def parse_args():
        parser = argparse.ArgumentParser(description='MultiData Strategy')
    
        parser.add_argument('--data0', '-d0',
                            default='/Users/rf/Documents/DSC/Datasets/abn.csv',
                            help='1st data into the system')
    
        parser.add_argument('--data1', '-d1',
                            default='/Users/rf/Documents/DSC/Datasets/inga.csv',
                            help='2nd data into the system')
    
        parser.add_argument('--fromdate', required=False,
                            default='20170704  09:00:00',
                            help='Starting date in YYYY-MM-DD format')
    
        parser.add_argument('--todate', required=False,
                            default='20170704  09:00:00',
                            help='Starting date in YYYY-MM-DD format')
    
        parser.add_argument('--period', default=10, type=int,
                            help='Period to apply to the Simple Moving Average')
    
        parser.add_argument('--cash', default=100000, type=int,
                            help='Starting Cash')
    
        parser.add_argument('--runnext', action='store_true',
                            help='Use next by next instead of runonce')
    
        parser.add_argument('--nopreload', action='store_true',
                            help='Do not preload the data')
    
        parser.add_argument('--oldsync', action='store_true',
                            help='Use old data synchronization method')
    
        parser.add_argument('--commperc', default=0.005, type=float,
                            help='Percentage commission (0.005 is 0.5%%')
    
        parser.add_argument('--stake', default=10, type=int,
                            help='Stake to apply in each operation')
    
        parser.add_argument('--plot', '-p', default=True, action='store_true',
                            help='Plot the read data')
    
        parser.add_argument('--numfigs', '-n', default=1,
                            help='Plot using numfigs figures')
    
        return parser.parse_args()
    
    
    if __name__ == '__main__':
        runstrategy()
    

    Gives the error:

    Traceback (most recent call last):
    
      File "<ipython-input-18-d6922e16d55d>", line 278, in <module>
        runstrategy()
    
      File "<ipython-input-18-d6922e16d55d>", line 219, in runstrategy
        oldsync=args.oldsync)
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/cerebro.py", line 1142, in run
        runstrat = self.runstrategies(iterstrat)
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/cerebro.py", line 1305, in runstrategies
        self._runonce(runstrats)
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/cerebro.py", line 1663, in _runonce
        strat._once()
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/lineiterator.py", line 292, in _once
        indicator._once()
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/lineiterator.py", line 292, in _once
        indicator._once()
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/lineiterator.py", line 313, in _once
        self.once(self._minperiod, self.buflen())
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/indicator.py", line 136, in once_via_next
        self.next()
    
      File "/anaconda/lib/python3.6/site-packages/backtrader/indicators/ols.py", line 52, in next
        slope, intercept = sm.OLS(p0, p1).fit().params
    
    ValueError: not enough values to unpack (expected 2, got 1)
    

    1 feed is correct lined but the other one?



  • Solved. Backtrader is limited to less than 10000 lines. Very strange. Any idea how to extend this range?

    Well, at least in this script.


  • administrators

    @fbleaux said in Pairs Trading example error:

    Backtrader is limited to less than 10000 lines. Very strange. Any idea how to extend this range?

    There is NO LIMIT in backtrader. So that's simply a bold statement out of the blue.



  • Just curious to know how did you solve it? I get the same error when I run it. My data is definitely shorter than 10000 lines (even though there is no such limit anyway).. I am also studying paris trading. What is the problem and solution ?

    thanks

    Michael


  • administrators

    @fbleaux said in Pairs Trading example error:

    ValueError: not enough values to unpack (expected 2, got 1)

    Without the actual data it is difficult to say, but an educated guess would hint that a change in the API return values of sm.OLS has happened.

    The Pairs Trading Example was contributed by a user some time ago and just like Pyfolio, those APIs keep changing.



  • Right, I have searched the forum and saw you guys had a lot of discussion on this back in Dec, 2016 and made many changes along the way. I'll keep reading and researching, but is there a github depository or somewhere else where the latest working version of this example reside? That would save a lot of time.

    thanks

    Michael


  • administrators

    There was only 1 version of this, which was committed and never touched again. It's the one you find in the sources.


Log in to reply
 

Looks like your connection to Backtrader Community was lost, please wait while we try to reconnect.