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

Statistical arbitrage framework help



  • I'm trying to simply create a framework where I can follow the order placement and execution for a statistical arbitrage strategy.
    It is not working as expected leading to incorrect order executions. I would appreciate it if anyone can provide me with some assistance.

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    import datetime  # For datetime objects
    import os.path  # To manage paths
    import sys  # To find out the script name (in argv[0])
    
    import backtrader as bt
    
    # Create a Stratey
    class TestStrategy(bt.Strategy):
    
        def log(self, txt, dt=None):
            ''' Logging function for this strategy'''
            dt = dt or self.data0.datetime.date(0)
            tt = self.data.datetime.time(0)
            print('%s, %s, %s' % (dt.isoformat(), tt.isoformat(), txt))
    
        def __init__(self):
            # Keep a reference to the "close" line in the data[0] dataseries
            self.dataclose1 = self.datas[0].close
            self.dataclose2 = self.datas[1].close
    
            # To keep track of pending orders and buy price/commission
            self.order = None
            self.orderid = None
            self.buyprice1 = None
            self.buyprice2 = None
            self.buycomm = None
    
            # Add a MovingAverageSimple indicator
            self.sma = bt.indicators.SimpleMovingAverage(
                self.data0, period=15)
    
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                return
    
            # Check if an order has been completed
            # Attention: broker could reject order if not enough cash
            if order.status in [order.Completed]:
                if order.isbuy() and self.position.size > 0:
                    self.log(
                        'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' %
                        (order.executed.price,
                         order.executed.value,
                         order.executed.comm,
                         self.position.size))
    
                    self.buyprice = order.executed.price
                    self.buycomm = order.executed.comm
                elif order.issell() and self.position.size < 0:
                    self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' %
                             (order.executed.price,
                              order.executed.value,
                              order.executed.comm,
                              self.position.size))
                    self.sellprice = order.executed.price
                    self.sellcomm = order.executed.comm
    
                elif order.issell() and self.position.size == 0:
                    self.log(
                        'CLOSING LONG POSITION, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' %
                            (order.executed.price,
                             order.executed.value,
                             order.executed.comm,
                             self.position.size))
                elif order.isbuy() and self.position.size == 0:
                    self.log(
                        'CLOSING SHORT POSITION, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' %
                            (order.executed.price,
                             order.executed.value,
                             order.executed.comm,
                             self.position.size))
                else:  # Sell
                    self.log('ERROR - Something went wrong!')
    
                self.bar_executed = len(self)
    
            elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                self.log('Order Canceled/Margin/Rejected')
    
            self.order = None
    
        def notify_trade(self, trade):
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
    
        def next(self):
            # Check if an order is pending ... if yes, we cannot send a 2nd one
            if self.orderid:
                return
    
            # Check if we are in the market
            if not self.position:
    
                # Not yet ... we MIGHT BUY if ...
                if self.dataclose1[0] > self.sma[0]:
    
                    # BUY, BUY, BUY!!! (with all possible default parameters)
                    self.log('BUY CREATE, %.2f' % self.dataclose1[0])
                    self.order = self.buy(data=self.data0)
                    self.log('SELL CREATE, %.2f' % self.dataclose2[0])
                    self.order = self.sell(data = self.data1)
                    # Keep track of the created order to avoid a 2nd order
    
    
            else:
    
                if self.dataclose1[0] < self.sma[0]:
                    # SELL, SELL, SELL!!! (with all possible default parameters)
                    self.log('CLOSE series1, %.2f' % self.dataclose1[0])
                    self.close(self.data0)
                    self.log('CLOSE series2, %.2f' % self.dataclose2[0])
                    self.close(self.data1)
                    # Keep track of the created order to avoid a 2nd order
    
    
    if __name__ == '__main__':
        cerebro = bt.Cerebro(writer=True)
        cerebro.addstrategy(TestStrategy)
    
        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath1 = os.path.join(modpath, 'SPI#5min.txt')
        datapath2 = os.path.join(modpath, '@ES#.txt')
    
        #Create a data feed
        data0 = bt.feeds.GenericCSVData(
            dataname=datapath1,
            # Do not pass values before this date
            fromdate=datetime.datetime(2019, 5, 25),
            # Do not pass values after this date
            todate=datetime.datetime(2019, 5, 27),
            timeframe = bt.TimeFrame.Minutes,
            compression = 5,
    
            nullvalue = 0.0,
    
            dtformat = ('%d/%m/%Y'),
            tmformat = ('%H:%M:%S'),
    
            datetime=0,
            time=1,
            open=2,
            low=3,
            high=4,
            close=5,
            volume=6,
            openinterest=-1
        )
    
        # Add the 1st data to cerebro
        cerebro.adddata(data0)
    
        # Create the 2nd data
        data1 = bt.feeds.GenericCSVData(
            dataname=datapath2,
            # Do not pass values before this date
            fromdate=datetime.datetime(2019, 1, 1),
            # Do not pass values after this date
            todate=datetime.datetime(2019, 5, 27),
            timeframe = bt.TimeFrame.Minutes,
            compression = 5,
    
            nullvalue = 0.0,
    
            dtformat = ('%d/%m/%Y'),
            tmformat = ('%H:%M:%S'),
    
            datetime=0,
            time=1,
            open=2,
            low=3,
            high=4,
            close=5,
            volume=6,
            openinterest=-1
        )
    
        # Add the 2nd data to cerebro
        cerebro.adddata(data1)
    
        cerebro.broker.setcash(100000.0)
        cerebro.addwriter(bt.WriterFile, csv=True, out="trade_history.csv")
    
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
        cerebro.run()
    
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
    

    440ea11b-46d8-4021-ad97-7e9b962b1a1c-image.png



  • Maybe you can share more detail on what you expect and what is wrong?
    From what I can see the orders are executed, so seems nothing wrong happens.

    PS sorry didn't get my mind-reading skill yet.



  • Hi ab_trader
    Thanks for your response.
    I am expecting that once a trade signal is generated in the def next() method, the next method to be called is def notify_order() followed by def notify_trade().
    When an entry signal is generated in def next() this should result in an output of:
    BUY CREATE
    SELL CREATE
    followed by:
    BUY EXECUTED
    SELL EXECUTED
    from the def notify_order() method followed by:
    no log output from the def notify_trade() method.

    Once an exit signal is generated in def next() the following outputs are expected:
    CLOSE series1
    CLOSE series2
    followed by:
    CLOSING LONG POSITION
    CLOSING SHORT POSITION
    from def notify_order() followed by:
    OPERATION PROFIT,
    from the def notify_trade() method

    I'm not getting this flow at all in the output.
    The csv file is also showing strange results. At the beginning of the file, entries of the 2 symbols are happening on consecutive bars instead simultaneously and eventually only one symbol gets opened and not the second symbol.

    I hope this provides you with enough information as to what I am hoping to resolve.
    Thanks



  • @juliev it seems you have some issues with the data feeds. First I've ran you script on the daily bars (I have no intraday data) and it worked well with one exception: for order notification you have the following cases only:

    • order.isbuy() and self.position.size > 0 for BUY EXECUTED
    • order.issell() and self.position.size > 0 for SELL EXECUTED
    • order.isbuy() and self.position.size == 0 for CLOSING SHORT POSITION
    • order.issell() and self.position.size == 0 for CLOSING LONG POSITION

    Second, your order issue statements (BUY CREATE, SELL CREATE) should be printed with te current bar timestamp, but order notification statements sould be printed with the timestamp of the next bar. It seems to me that somehow next() is called twice in your case. Add the following logging line:

        def next(self):
            # Check if an order is pending ... if yes, we cannot send a 2nd one
            if self.orderid:
                return
    
            self.log('%s %0.2f - %s %0.2f:  pos %d' %
                (self.data0._name, self.dataclose1[0], self.data1._name,
                 self.dataclose2[0], self.position.size))
    
    ...
    

    If it appears only once per timestamp in the output than it is good.


Log in to reply
 

});