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

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.