Curious on Negative Balance Occurring in My Pair Trading Strategy Algo



  • Hello,

    I am attempting to create a pair trading algorithm between DG and DLTR as tickers. While the algorithm is running, there seems to be some odd error I have not been able to figure out. While I start balance at 100,000, ending balance is - 3,513,102; which obviously does not make sense.

    Wondering if anyone can please take a quick glance at my code and see where the problem could be lying here. Very much appreciate the assistance and thanks in advance. Full code below:

    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=100000,
        )
    
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or self.data.datetime[0] # set date time
                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 :                                   # signal performed using ordinary least regression
            self.transform = btind.OLS_TransformationN(self.data0, self.data1,
                                                       period=self.p.period)
            self.zscore = self.transform.zscore         # calculate z score ( - mean / std deviation)
    
            # 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)) # length of time series for data 0
                print('Data1 len:', len(self.data1)) # length of time series for data 1
                print('Data0 len == Data1 len:',
                      len(self.data0) == len(self.data1)) # set lengths equal to each other (ensure they are)
    
                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 the spread & place the order
            # Checking the condition for SHORT
            if (self.zscore[0] > self.upper_limit) and (self.status != 1):  # get short if z score is above upper limit and not short the spread already (!=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' % ("DG", self.data0.close[0], x + self.qty1))    # sell DG and assign to x
                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' % ("DLTR", self.data1.close[0], y + self.qty2))    # buy DLTR and assign to y
                self.buy(data=self.data1, size=(y + self.qty2))  # Place an order for selling x + qty1 shares (DG)
    
                # 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 the spread & place the order
                # Checking the condition for LONG
            elif (self.zscore[0] < self.lower_limit) and (self.status != 2): # get long if z score is below lower limit and not long the spread already (!=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' % ("DG", self.data0.close[0], x + self.qty1)) # buy DG and assigned to x
                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' % ("DLTR", self.data1.close[0], y + self.qty2)) # sell DLTR and assign to y
                self.sell(data=self.data1, size=(y + self.qty2))  # Place an order for selling y + qty2
    
                # 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' % ("DG", self.data0.close[0]))
                self.close(self.data0)
                self.log('CLOSE LONG %s, price = %.2f' % ("DLTR", 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("1/4/2010", "%m/%d/%Y")
        todate = datetime.datetime.strptime("10/17/2017", "%m/%d/%Y")
    
        # Create the 1st data
        data0 = btfeeds.GenericCSVData( # DG Data
            dataname='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DG.csv',
    
            fromdate=fromdate,
            todate=todate,
    
            nullvalue=0.0,  # missing values to be replaced with 0
    
            dtformat=('%m/%d/%Y'),
    
            datetime=0,
            time=-1,
            open=-1,
            high=-1,
            low=-1,
            close=4,
            adjclose=-1,
            volume=-1,
            openinterest=-1,
    
        )
    
        # Add the 1st data to cerebro
        cerebro.adddata(data0)
    
        # Create the 2nd data
        data1 = btfeeds.GenericCSVData(  # DLTR Data
            dataname='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DLTR.csv',
    
            fromdate=fromdate,
            todate=todate,
    
            nullvalue=0.0,  # missing values to be replaced with 0
    
            dtformat=('%m/%d/%Y'),
    
            datetime=0,
            time=-1,
            open=-1,
            high=-1,
            low=-1,
            close=4,
            adjclose=-1,
            volume=-1,
            openinterest=-1,
    
        )
    
        # 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='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DG.csv',
                            help='1st data into the system')
    
        parser.add_argument('--data1', '-d1',
                            default='C:\\Users\\Sam\PycharmProjects\\Test\\.ipynb_checkpoints\\DLTR.csv',
                            help='2nd data into the system')
    
        parser.add_argument('--fromdate', '-f',
                            default='01/04/2010',
                            help='Starting date in %m/%d/%Y format')
    
        parser.add_argument('--todate', '-t',
                            default='10/17/2017',
                            help='Starting date in %m/%d/%Y 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()
    


  • @backtrader @administrators

    Any idea what is going on here by any chance?



  • @samk why don't you want to print all related to your system conditions such as prices, indicator values, order statuses, positions etc on every next() call and go thru the whole test and check out what is going on?

    it can be faster then waiting for somebody to do this work for you. By the way @backtrader will not answer any question according to his statement on top of the page.



  • Hi @ab_trader ,

    Thank you very much for the response. I have created multiple print statements as you suggested, however just am having a hard time seeing where it is flushing in negative balance territory, or why it is for that matter. Any help in terms of what I can be missing here would be greatly appreciated. I have pasted new adjusted code with print statements below. Thanks for the help. I am very new to backtrader still, so could jut be missing something very silly here....

    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
    import backtrader.analyzers as btan
    
    
    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=100000,
        )
    
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or self.data.datetime[0] # set date time
                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 :                                   # signal performed using ordinary least regression
            self.transform = btind.OLS_TransformationN(self.data0, self.data1,
                                                       period=self.p.period)
            self.zscore = self.transform.zscore         # calculate z score ( - mean / std deviation)
    
            # 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)) # length of time series for data 0
                print('Data1 len:', len(self.data1)) # length of time series for data 1
                print('Data0 len == Data1 len:',
                      len(self.data0) == len(self.data1)) # set lengths equal to each other (ensure they are)
    
                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 the spread & place the order
            # Checking the condition for SHORT
            if (self.zscore[0] > self.upper_limit) and (self.status != 1):  # get short if z score is above upper limit and not short the spread already (!=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' % ("DG", self.data0.close[0], x + self.qty1))    # sell DG and assign to x
                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' % ("DLTR", self.data1.close[0], y + self.qty2))    # buy DLTR and assign to y
                self.buy(data=self.data1, size=(y + self.qty2))  # Place an order for selling x + qty1 shares (DG)
    
                # 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 the spread & place the order
                # Checking the condition for LONG
            elif (self.zscore[0] < self.lower_limit) and (self.status != 2): # get long if z score is below lower limit and not long the spread already (!=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' % ("DG", self.data0.close[0], x + self.qty1)) # buy DG and assigned to x
                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' % ("DLTR", self.data1.close[0], y + self.qty2)) # sell DLTR and assign to y
                self.sell(data=self.data1, size=(y + self.qty2))  # Place an order for selling y + qty2
    
                # 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' % ("DG", self.data0.close[0]))
                self.close(self.data0)
                self.log('CLOSE LONG %s, price = %.2f' % ("DLTR", 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("1/4/2010", "%m/%d/%Y")
        todate = datetime.datetime.strptime("10/17/2017", "%m/%d/%Y")
    
        # Create the 1st data
        data0 = btfeeds.GenericCSVData( # DG Data
            dataname='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DG.csv',
    
            fromdate=fromdate,
            todate=todate,
    
            nullvalue=0.0,  # missing values to be replaced with 0
    
            dtformat=('%m/%d/%Y'),
    
            datetime=0,
            time=-1,
            open=-1,
            high=-1,
            low=-1,
            close=4,
            adjclose=-1,
            volume=-1,
            openinterest=-1,
    
        )
    
        # Add the 1st data to cerebro
        cerebro.adddata(data0)
    
        # Create the 2nd data
        data1 = btfeeds.GenericCSVData(  # DLTR Data
            dataname='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DLTR.csv',
    
            fromdate=fromdate,
            todate=todate,
    
            nullvalue=0.0,  # missing values to be replaced with 0
    
            dtformat=('%m/%d/%Y'),
    
            datetime=0,
            time=-1,
            open=-1,
            high=-1,
            low=-1,
            close=4,
            adjclose=-1,
            volume=-1,
            openinterest=-1,
    
        )
    
        # 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='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DG.csv',
                            help='1st data into the system')
    
        parser.add_argument('--data1', '-d1',
                            default='C:\\Users\\Sam\PycharmProjects\\Test\\.ipynb_checkpoints\\DLTR.csv',
                            help='2nd data into the system')
    
        parser.add_argument('--fromdate', '-f',
                            default='01/04/2010',
                            help='Starting date in %m/%d/%Y format')
    
        parser.add_argument('--todate', '-t',
                            default='10/17/2017',
                            help='Starting date in %m/%d/%Y 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()
    


  • @ab_trader

    Sorry sent the wrong revised code. The correct one with print statements shown below.....

    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
    import backtrader.analyzers as btan
    
    
    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=100000,
        )
    
        def log(self, txt, dt=None):
            if self.p.printout:
                dt = dt or self.data.datetime[0] # set date time
                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 :                                   # signal performed using ordinary least regression
            self.transform = btind.OLS_TransformationN(self.data0, self.data1,
                                                       period=self.p.period)
            self.zscore = self.transform.zscore         # calculate z score ( - mean / std deviation)
    
            # 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)) # length of time series for data 0
                print('Data1 len:', len(self.data1)) # length of time series for data 1
                print('Data0 len == Data1 len:',
                      len(self.data0) == len(self.data1)) # set lengths equal to each other (ensure they are)
    
                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 the spread & place the order
            # Checking the condition for SHORT
            if (self.zscore[0] > self.upper_limit) and (self.status != 1):  # get short if z score is above upper limit and not short the spread already (!=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' % ("DG", self.data0.close[0], x + self.qty1))    # sell DG and assign to x
                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' % ("DLTR", self.data1.close[0], y + self.qty2))    # buy DLTR and assign to y
                self.buy(data=self.data1, size=(y + self.qty2))  # Place an order for selling x + qty1 shares (DG)
    
                # 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 the spread & place the order
                # Checking the condition for LONG
            elif (self.zscore[0] < self.lower_limit) and (self.status != 2): # get long if z score is below lower limit and not long the spread already (!=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' % ("DG", self.data0.close[0], x + self.qty1)) # buy DG and assigned to x
                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' % ("DLTR", self.data1.close[0], y + self.qty2)) # sell DLTR and assign to y
                self.sell(data=self.data1, size=(y + self.qty2))  # Place an order for selling y + qty2
    
                # 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' % ("DG", self.data0.close[0]))
                self.close(self.data0)
                self.log('CLOSE SHORT %s, price = %.2f' % ("DLTR", self.data1.close[0]))
                self.close(self.data1)
    
            elif (self.zscore[0] < self.up_medium and self.zscore[0] > self.low_medium):
                self.log('CLOSE SHORT %s, price = %.2f' % ("DG", self.data0.close[0]))
                self.close(self.data0)
                self.log('CLOSE LONG %s, price = %.2f' % ("DLTR", 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("1/4/2010", "%m/%d/%Y")
        todate = datetime.datetime.strptime("10/17/2017", "%m/%d/%Y")
    
        # Create the 1st data
        data0 = btfeeds.GenericCSVData( # DG Data
            dataname='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DG.csv',
    
            fromdate=fromdate,
            todate=todate,
    
            nullvalue=0.0,  # missing values to be replaced with 0
    
            dtformat=('%m/%d/%Y'),
    
            datetime=0,
            time=-1,
            open=-1,
            high=-1,
            low=-1,
            close=4,
            adjclose=-1,
            volume=-1,
            openinterest=-1,
    
        )
    
        # Add the 1st data to cerebro
        cerebro.adddata(data0)
    
        # Create the 2nd data
        data1 = btfeeds.GenericCSVData(  # DLTR Data
            dataname='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DLTR.csv',
    
            fromdate=fromdate,
            todate=todate,
    
            nullvalue=0.0,  # missing values to be replaced with 0
    
            dtformat=('%m/%d/%Y'),
    
            datetime=0,
            time=-1,
            open=-1,
            high=-1,
            low=-1,
            close=4,
            adjclose=-1,
            volume=-1,
            openinterest=-1,
    
        )
    
        # 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='C:\\Users\\Sam\\PycharmProjects\\Test\\.ipynb_checkpoints\\DG.csv',
                            help='1st data into the system')
    
        parser.add_argument('--data1', '-d1',
                            default='C:\\Users\\Sam\PycharmProjects\\Test\\.ipynb_checkpoints\\DLTR.csv',
                            help='2nd data into the system')
    
        parser.add_argument('--fromdate', '-f',
                            default='01/04/2010',
                            help='Starting date in %m/%d/%Y format')
    
        parser.add_argument('--todate', '-t',
                            default='10/17/2017',
                            help='Starting date in %m/%d/%Y 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()
    


  • Great! Now you can run your script, go thru logs and identify if your orders were issued correctly, trades were opened and closed correctly, maybe do some hand calcs etc. Finally I believe you will find what is wrong.

    You have negative balance, my guess that your trades were not closed correctly and you might have number of opened trades with the loss.



  • Seems like you are short selling something which than raises in value by a lot. To simplify analyzing I think, that you should add writer and analyzer (pyfolio analyzer adds a lot of useful output):

    cerebro.addwriter(bt.WriterFile, out='OUTPUT_FILE_PATH')
    cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
    

    After you add those 2 you will have all positions written to your file, which should be quite easy to analyze when the value went below 0 and what trades did you make before.

    Hope that it will help.



  • @mpskowron

    Thanks a lot. I was not aware of this option at all! It definitely helped me. Looks like I was not allocating the shares equally amongst the two stocks (calculation was off). Anyhow, what you mentioned helped me see what was going on a lot and will definitely be using it for more algo. Thanks!

    Sam


Log in to reply
 

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