Hi All,

We are trying to implement a simple RSI strategy using consolidators to create two RSI indicators (15 mins and 2 mins). We have come across a weird problem/potential bug, whereby the second RSI indicator stops reporting values after a certain number of days (see attached plot).

159198_1624317584.jpg

Is there a problem with the way we are implementing the RSI indicators via consolidation, or is this something weirder?

Here is our backtest code:


from datetime import datetime, date, time
from datetime import timedelta
class TradeStrategyTest(QCAlgorithm):
  
  def Initialize(self):
       self.SetStartDate(2021,3, 1)  #Set Start Date
       self.SetEndDate(2021,6,14)    #Set End Date
       self.SetCash(30000)           #Set Strategy Cash
       self.AddEquity("OCGN", Resolution.Minute)
       self.RSI1 = self.RSI("OCGN", 14, MovingAverageType.Wilders)
       self.RSI2 = self.RSI("OCGN", 14, MovingAverageType.Wilders)
       self.SetWarmUp(210)
       self.Securities["OCGN"].FeeModel = ConstantFeeModel(1.00)
       self.ticket = None # Flag for position status
       self.previous_day_close = self.Securities["OCGN"].Price
       self.expiry = self.Time
       self.openPrice = -1   # Pointer to store opening price
       self.lowestPrice = -1 # pointer to store latest daily high
       
       # Set TimeZone
       self.SetTimeZone("America/New_York")
       
       # Create our consolidators
       Con1 = TradeBarConsolidator(timedelta(minutes=15))
       Con2 = TradeBarConsolidator(timedelta(minutes=2))
       
       # Register our Handlers
       Con1.DataConsolidated += self.On_W1
       Con2.DataConsolidated += self.On_W2
   
       # Register the indicaors with our stock and consolidator
       RSI1_Ind = self.RegisterIndicator("OCGN", self.RSI1, Con1)
       RSI2_Ind = self.RegisterIndicator("OCGN", self.RSI2, Con2)
           
       # Finally add our consolidators to the subscription
       # manager in order to receive updates from the engine
       RSI1_Sub = self.SubscriptionManager.AddConsolidator("OCGN", Con1)
       RSI2_Sub = self.SubscriptionManager.AddConsolidator("OCGN", Con2)
           
       
  def OnData(self, data):
           
         
               
           # Set local variables
           close = self.Securities["OCGN"].Close
           quantity = self.CalculateOrderQuantity("OCGN",1)
           AskPrice = self.Securities["OCGN"].AskPrice
           BidPrice = self.Securities["OCGN"].BidPrice
           Spread = (AskPrice - BidPrice)
   
           # Warm up Codition
           if self.IsWarmingUp or not data.Bars.ContainsKey("OCGN") or not self.RSI1.IsReady or not self.RSI2.IsReady: 
               return
           
           # Setup Open and Close Prices and Bars
           if self.Time >= self.expiry:
               self.previous_day_close = data["OCGN"].Close
               self.expiry = Expiry.EndOfDay(self.Time)
           
           
           self.previous_bar_close = data["OCGN"].Close
           change_from_close = (((self.previous_bar_close - self.previous_day_close) / self.previous_bar_close)*100) 
     
           #Obtain Low of Day and Update 
           bar = data["OCGN"]
           
           if not bar.IsFillForward and self.lowestPrice < 0:
               self.openPrice = bar.Open
               self.lowestPrice = bar.Low
           
           if self.lowestPrice < 0:
               return
           
           price = bar.Low
           if price < self.lowestPrice:  # If we observe a new low
               self.lowestPrice = price
           
           # DEBUG FLAGS - IMPORTANT!!!!
           
           
           #self.Debug(f"Equity Data: {data['OCGN']}")
           #self.Debug(f"RSI2: {self.RSI2.Current.Value}")
           #self.Debug(f"Time: {self.Time}")
           #self.Debug(f"UTC Time: {self.UtcTime}")
           
           # IMPORTANT!!! Time variables to set open/close times and compare them to current time.
           
           # Convert times to variables (necessary for time comparison)
           currentTime = self.Time
           openTime = time(9,30)
           closeTime = time(12,0)
           
           # Convert string to format that can be compared (comparison does not work if you do not do this)
           # These comparisons are to test it works, before implementing them in the # Buy Conditions function below
           # It is OK to comment them out here, as they are just for testing. Real function below.
           #currentTime.strftime('%H%M') >= openTime.strftime('%H%M')
           #currentTime.strftime('%H%M') <= closeTime.strftime('%H%M')
           
           # Buy Conditions 
           if not self.Securities["OCGN"].Invested and self.ticket is None and (currentTime.strftime('%H%M') >= openTime.strftime('%H%M') and currentTime.strftime('%H%M') <= closeTime.strftime('%H%M')):
               
               
               # If buy conditions are satisfied then place MarketOrder
               if  ((self.RSI1.Current.Value <=70 and self.RSI1.Current.Value >=20) and (self.RSI2.Current.Value >=0 and self.RSI2.Current.Value <=25)): 
                   self.ticket = self.MarketOrder("OCGN", quantity, tag ="Market Buy") and self.Debug(f"RSI1 Value: {self.RSI1.Current.Value}, RSI2 Value: {self.RSI2.Current.Value}") 
   
                   # Place Profit take and Stop Loss orders then reset to None
                   self.LimitOrder("OCGN", -quantity, close * 1.03, tag = "Profit Take")
                   self.StopMarketOrder("OCGN", -quantity, close * 0.99, tag = "Stopped Out")
                   self.ticket = None
   
               # Close position if open for more than 15 minutes and set ticket to None
               if  self.ticket is not None and (self.Time > self.ticket.Time + timedelta(minutes = 15)):
                   self.Liquidate()
                   self.ticket = None
                   
           
  # Cancel remaining order if limit order or stop loss order is executed 
  def OnOrderEvent(self, orderEvent):
       order = self.Transactions.GetOrderById(orderEvent.OrderId)
       
       if order.Status == OrderStatus.Filled:
           if order.Type == OrderType.Limit or order.Type == OrderType.StopMarket:
               self.Transactions.CancelOpenOrders(order.Symbol)
               
       if order.Status == OrderStatus.Canceled:
           self.Log(str(orderEvent))
           
  #Reset Daily :Pointer
  def OnEndOfDay(self, symbol):
       self.lowestPrice = -1
       
  def On_W1(self,sender,bar):
       '''
       This method will be called every time a new 30 minute bar is ready. 
  
       bar = The incoming Tradebar. This is different to the data object in OnData()
       '''
       self.Plot('RSI', 'W1', self.RSI1.Current.Value)
       #self.Plot('RSI', 'W2', self.RSI2.Current.Value)
   
  def On_W2(self,sender,bar):
       '''
       This method will be called every time a new 30 minute bar is ready. 
  
       bar = The incoming Tradebar. This is different to the data object in OnData()
       '''
       #self.Plot('RSI', 'W1', self.RSI1.Current.Value)
       self.Plot('RSI', 'W2', self.RSI2.Current.Value)

Any help would be greatly appreciated!