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/

    Trades don't happen until WAY late (NO, THEY DON'T)

    General Code/Help
    2
    6
    1984
    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.
    • Curtis Miller
      Curtis Miller last edited by backtrader

      I'm trying to implement a simple moving average crossover strategy that trades multiple symbols. Following this example, I came up with the following strategy:

      import datetime
      import backtrader as bt
      import backtrader.indicators as btind
      
      class PropSizer(bt.Sizer):
          """A position sizer that will buy as many stocks as necessary for a certain proportion of the portfolio
             to be committed to the position, while allowing stocks to be bought in batches (say, 100)"""
          params = {"prop": 0.1, "batch": 100}
          
          def _getsizing(self, comminfo, cash, data, isbuy):
              """Returns the proper sizing"""
              
              if isbuy:    # Buying
                  target = self.broker.getvalue() * self.params.prop    # Ideal total value of the position
                  price = data.close[0]
                  shares_ideal = target / price    # How many shares are needed to get target
                  batches = int(shares_ideal / self.params.batch)    # How many batches is this trade?
                  shares = batches * self.params.batch    # The actual number of shares bought
                  
                  if shares * price > cash:
                      return 0    # Not enough money for this trade
                  else:
                      return shares
                  
              else:    # Selling
                  return self.broker.getposition(data).size    # Clear the position
      
      class SMAC(bt.Strategy):
          """A simple moving average crossover strategy; crossing of a fast and slow moving average generates buy/sell
             signals"""
          params = {"slow": 50, "fast": 20}    # The windows for both fast and slow moving averages
          
          def __init__(self):
              """Initialize the strategy"""
              # Tracking pending orders
              # self.order = None
              
              self.fastma = dict()
              self.slowma = dict()
              self.regime = dict()
              
              self._addobserver(True, bt.observers.BuySell)
              
              for d in self.datas:
                  
                  # The moving averages
                  self.fastma[d._name] = btind.SimpleMovingAverage(d, period=self.params.fast,    # Fast moving average
                                                                   plotname="FastMA: " + d._name)
                  self.slowma[d._name] = btind.SimpleMovingAverage(d, period=self.params.slow,    # Slow moving average
                                                                   plotname="SlowMA: " + d._name)
      
                  # Get the regime
                  self.regime[d._name] = self.fastma[d._name] - self.slowma[d._name](-1)    # Positive when bullish
          
          def next(self):
              """Define what will be done in a single step, including creating and closing trades"""
              for d in self.datas:
                  pos = self.getposition(d).size or 0
                  if pos == 0:    # Are we out of the market?
                      # Consider the possibility of entrance
                      if self.regime[d._name][0] > 0 and self.regime[d._name][-1] <= 0:    # A buy signal
                          self.buy(data=d)
      
                  else:    # We have an open position
                      if self.regime[d._name][0] <= 0 and self.regime[d._name][-1] <= 0:    # A sell signal
                          self.sell(data=d)
      
      cerebro = bt.Cerebro(stdstats=True)
      cerebro.broker.set_cash(1000000)    # Set our starting cash to $1,000,000
      cerebro.broker.setcommission(0.02)
      
      start = datetime.datetime(2010, 1, 1)
      end = datetime.datetime(2016, 10, 31)
      is_first = True
      symbols = ["AAPL", "GOOG", "MSFT", "FB", "TWTR", "NFLX", "AMZN", "YHOO",
                 "SNY", "NTDOY", "IBM", "HPQ"]
      plot_symbols = ["AAPL"]
      #plot_symbols = []
      for s in symbols:
          data = bt.feeds.YahooFinanceData(dataname=s, fromdate=start, todate=end)
          if s in plot_symbols:
              if is_first:
                  data_main_plot = data
                  is_first = False
              else:
                  data.plotinfo.plotmaster = data_main_plot
          else:
              data.plotinfo.plot = False
          cerebro.adddata(data)    # Give the data to cerebro
      cerebro.addstrategy(SMAC)
      cerebro.addsizer(PropSizer)
      cerebro.run()
      cerebro.plot(volume=False)
      

      Unfortunately, trades don't occur until WAY late, perhaps halfway through the period of interest. Trades should be happening as soon as 50 days in; that's the window length of the slow moving average. Why are trades happening so late? What's wrong with my strategy?

      EDIT: I think I know what the problem is; some symbols are new and don't exist, so the backtester must be not checking until all symbols have data. That said, is there a way to work with data like this, when some symbols don't exist all in the same time frame?

      Blog posts: Walk Forward Analysis Demonstration, Stock Trading Analytics and Optimization

      Books/Video Courses: Unpacking NumPy and Pandas

      B 1 Reply Last reply Reply Quote 0
      • Curtis Miller
        Curtis Miller last edited by

        I revised the strategy to use the methods of the Strategy class for accessing data. No improvement. Here's the code of the new version of SMAC:

        class SMAC(bt.Strategy):
            """A simple moving average crossover strategy; crossing of a fast and slow moving average generates buy/sell
               signals"""
            params = {"slow": 50, "fast": 20}    # The windows for both fast and slow moving averages
            
            def __init__(self):
                """Initialize the strategy"""
                # Tracking pending orders
                # self.order = None
                
                self.fastma = dict()
                self.slowma = dict()
                self.regime = dict()
                
                self._addobserver(True, bt.observers.BuySell)
                
                for d in self.getdatanames():
                    
                    # The moving averages
                    self.fastma[d] = btind.SimpleMovingAverage(self.getdatabyname(d), period=self.params.fast,    # Fast moving average
                                                                     plotname="FastMA: " + d)
                    self.slowma[d] = btind.SimpleMovingAverage(self.getdatabyname(d), period=self.params.slow,    # Slow moving average
                                                                     plotname="SlowMA: " + d)
        
                    # Get the regime
                    self.regime[d] = self.fastma[d] - self.slowma[d](-1)    # Positive when bullish
            
            def next(self):
                """Define what will be done in a single step, including creating and closing trades"""
                for d in self.getdatanames():
                    pos = self.getpositionbyname(d).size or 0
                    if pos == 0:    # Are we out of the market?
                        # Consider the possibility of entrance
                        if self.regime[d][0] > 0 and self.regime[d][-1] <= 0:    # A buy signal
                            self.buy(data=self.getdatabyname(d))
        
                    else:    # We have an open position
                        if self.regime[d][0] <= 0 and self.regime[d][-1] <= 0:    # A sell signal
                            self.sell(data=self.getdatabyname(d))
        

        Blog posts: Walk Forward Analysis Demonstration, Stock Trading Analytics and Optimization

        Books/Video Courses: Unpacking NumPy and Pandas

        1 Reply Last reply Reply Quote 0
        • Curtis Miller
          Curtis Miller last edited by

          Okay, I think I figured out the issue, but I don't know how to address it. I edited the opening post in response.

          Blog posts: Walk Forward Analysis Demonstration, Stock Trading Analytics and Optimization

          Books/Video Courses: Unpacking NumPy and Pandas

          1 Reply Last reply Reply Quote 0
          • Curtis Miller
            Curtis Miller last edited by

            I changed the list of symbols to the one below and the problem was "fixed":

            symbols = ["AAPL", "GOOG", "MSFT", "AMZN", "YHOO", "SNY", "NTDOY", "IBM", "HPQ", "QCOM", "NVDA"]
            

            That said, I hope there's a better solution to this problem than "Make sure all stocks have the same time frame," because I would prefer that, during periods certain symbols are not available/don't exist, they're just ignored and don't back up the whole backtest until they become available.

            Blog posts: Walk Forward Analysis Demonstration, Stock Trading Analytics and Optimization

            Books/Video Courses: Unpacking NumPy and Pandas

            1 Reply Last reply Reply Quote 0
            • B
              backtrader administrators @Curtis Miller last edited by

              @Curtis-Miller said in Trades don't happen until WAY late:

              EDIT: I think I know what the problem is; some symbols are new and don't exist, so the backtester must be not checking until all symbols have data. That said, is there a way to work with data like this, when some symbols don't exist all in the same time frame?

              All lines objects in backtrader have a minimum period and the strategy uses that to autocalculate when it can first go into next. If some of your data feeds are not delivering, nothing can be guaranteed with regards to accessing the data feeds and values of indicators calculated on them.

              There is no better solution (if you have one, feel free to propose it) If some of your data feeds are not delivering the following would break

              def next(self):
                  # data0 is any of the above, it doesn't matter it just an example, the key: IT DOESN'T HAVE ANY DATA YET
                  if self.data0.close[0]:  # breaking here
                      print('something')
              

              So: NO. Trades ARE NOT HAPPENING WAY TOO LATE

              You may always override prenext, check if the data feeds have already delivered and act accordingly. See the following parts of the documentation:

              • Docs - Operating the platform
              • Docs - Strategy
              1 Reply Last reply Reply Quote 0
              • B
                backtrader administrators last edited by backtrader

                That's exactly the implemented behavior: no-op and that's why the strategy remains in the prenext phase, moving only to the next (yes-op) when data is available.

                You may forward each prenext call to next and always be in the yes-op mode.

                1 Reply Last reply Reply Quote 0
                • 1 / 1
                • First post
                  Last post
                Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors