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:

  1. from datetime import datetime, date, time
  2. from datetime import timedelta
  1. class TradeStrategyTest(QCAlgorithm):
  2. def Initialize(self):
  3. self.SetStartDate(2021,3, 1) #Set Start Date
  4. self.SetEndDate(2021,6,14) #Set End Date
  5. self.SetCash(30000) #Set Strategy Cash
  6. self.AddEquity("OCGN", Resolution.Minute)
  7. self.RSI1 = self.RSI("OCGN", 14, MovingAverageType.Wilders)
  8. self.RSI2 = self.RSI("OCGN", 14, MovingAverageType.Wilders)
  9. self.SetWarmUp(210)
  10. self.Securities["OCGN"].FeeModel = ConstantFeeModel(1.00)
  11. self.ticket = None # Flag for position status
  12. self.previous_day_close = self.Securities["OCGN"].Price
  13. self.expiry = self.Time
  14. self.openPrice = -1 # Pointer to store opening price
  15. self.lowestPrice = -1 # pointer to store latest daily high
  16. # Set TimeZone
  17. self.SetTimeZone("America/New_York")
  18. # Create our consolidators
  19. Con1 = TradeBarConsolidator(timedelta(minutes=15))
  20. Con2 = TradeBarConsolidator(timedelta(minutes=2))
  21. # Register our Handlers
  22. Con1.DataConsolidated += self.On_W1
  23. Con2.DataConsolidated += self.On_W2
  24. # Register the indicaors with our stock and consolidator
  25. RSI1_Ind = self.RegisterIndicator("OCGN", self.RSI1, Con1)
  26. RSI2_Ind = self.RegisterIndicator("OCGN", self.RSI2, Con2)
  27. # Finally add our consolidators to the subscription
  28. # manager in order to receive updates from the engine
  29. RSI1_Sub = self.SubscriptionManager.AddConsolidator("OCGN", Con1)
  30. RSI2_Sub = self.SubscriptionManager.AddConsolidator("OCGN", Con2)
  31. def OnData(self, data):
  32. # Set local variables
  33. close = self.Securities["OCGN"].Close
  34. quantity = self.CalculateOrderQuantity("OCGN",1)
  35. AskPrice = self.Securities["OCGN"].AskPrice
  36. BidPrice = self.Securities["OCGN"].BidPrice
  37. Spread = (AskPrice - BidPrice)
  38. # Warm up Codition
  39. if self.IsWarmingUp or not data.Bars.ContainsKey("OCGN") or not self.RSI1.IsReady or not self.RSI2.IsReady:
  40. return
  41. # Setup Open and Close Prices and Bars
  42. if self.Time >= self.expiry:
  43. self.previous_day_close = data["OCGN"].Close
  44. self.expiry = Expiry.EndOfDay(self.Time)
  45. self.previous_bar_close = data["OCGN"].Close
  46. change_from_close = (((self.previous_bar_close - self.previous_day_close) / self.previous_bar_close)*100)
  47. #Obtain Low of Day and Update
  48. bar = data["OCGN"]
  49. if not bar.IsFillForward and self.lowestPrice < 0:
  50. self.openPrice = bar.Open
  51. self.lowestPrice = bar.Low
  52. if self.lowestPrice < 0:
  53. return
  54. price = bar.Low
  55. if price < self.lowestPrice: # If we observe a new low
  56. self.lowestPrice = price
  57. # DEBUG FLAGS - IMPORTANT!!!!
  58. #self.Debug(f"Equity Data: {data['OCGN']}")
  59. #self.Debug(f"RSI2: {self.RSI2.Current.Value}")
  60. #self.Debug(f"Time: {self.Time}")
  61. #self.Debug(f"UTC Time: {self.UtcTime}")
  62. # IMPORTANT!!! Time variables to set open/close times and compare them to current time.
  63. # Convert times to variables (necessary for time comparison)
  64. currentTime = self.Time
  65. openTime = time(9,30)
  66. closeTime = time(12,0)
  67. # Convert string to format that can be compared (comparison does not work if you do not do this)
  68. # These comparisons are to test it works, before implementing them in the # Buy Conditions function below
  69. # It is OK to comment them out here, as they are just for testing. Real function below.
  70. #currentTime.strftime('%H%M') >= openTime.strftime('%H%M')
  71. #currentTime.strftime('%H%M') <= closeTime.strftime('%H%M')
  72. # Buy Conditions
  73. 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')):
  74. # If buy conditions are satisfied then place MarketOrder
  75. if ((self.RSI1.Current.Value <=70 and self.RSI1.Current.Value >=20) and (self.RSI2.Current.Value >=0 and self.RSI2.Current.Value <=25)):
  76. 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}")
  77. # Place Profit take and Stop Loss orders then reset to None
  78. self.LimitOrder("OCGN", -quantity, close * 1.03, tag = "Profit Take")
  79. self.StopMarketOrder("OCGN", -quantity, close * 0.99, tag = "Stopped Out")
  80. self.ticket = None
  81. # Close position if open for more than 15 minutes and set ticket to None
  82. if self.ticket is not None and (self.Time > self.ticket.Time + timedelta(minutes = 15)):
  83. self.Liquidate()
  84. self.ticket = None
  85. # Cancel remaining order if limit order or stop loss order is executed
  86. def OnOrderEvent(self, orderEvent):
  87. order = self.Transactions.GetOrderById(orderEvent.OrderId)
  88. if order.Status == OrderStatus.Filled:
  89. if order.Type == OrderType.Limit or order.Type == OrderType.StopMarket:
  90. self.Transactions.CancelOpenOrders(order.Symbol)
  91. if order.Status == OrderStatus.Canceled:
  92. self.Log(str(orderEvent))
  93. #Reset Daily :Pointer
  94. def OnEndOfDay(self, symbol):
  95. self.lowestPrice = -1
  96. def On_W1(self,sender,bar):
  97. '''
  98. This method will be called every time a new 30 minute bar is ready.
  99. bar = The incoming Tradebar. This is different to the data object in OnData()
  100. '''
  101. self.Plot('RSI', 'W1', self.RSI1.Current.Value)
  102. #self.Plot('RSI', 'W2', self.RSI2.Current.Value)
  103. def On_W2(self,sender,bar):
  104. '''
  105. This method will be called every time a new 30 minute bar is ready.
  106. bar = The incoming Tradebar. This is different to the data object in OnData()
  107. '''
  108. #self.Plot('RSI', 'W1', self.RSI1.Current.Value)
  109. self.Plot('RSI', 'W2', self.RSI2.Current.Value)
+ Expand

Any help would be greatly appreciated!

Author

Mak K

June 2021