Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    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

    General Code/Help
    2
    4
    201
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J
      juliev last edited by

      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

      1 Reply Last reply Reply Quote 0
      • A
        ab_trader last edited by

        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.

        1 Reply Last reply Reply Quote 0
        • J
          juliev last edited by

          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

          1 Reply Last reply Reply Quote 0
          • A
            ab_trader last edited by

            @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.

            1 Reply Last reply Reply Quote 0
            • 1 / 1
            • First post
              Last post
            Copyright © 2016, 2017, 2018 NodeBB Forums | Contributors
            $(document).ready(function () { app.coldLoad(); }); }