Overall Statistics
from QuantConnect.Indicators import RelativeStrengthIndex, AverageTrueRange

'''
Universe: SPY and IEF
Timeframe: Daily (the only reason why it is on minute is because we need the OnOrderEvent)
Position size: 50%
Buy rules: After the market closes, buy on Market-On-Open order if the 3-day cumulative RSI(2) < 15.
Use a stoploss with 2*ATR(1) below the open price (which is the same as fill price)
Sell rules: After the market closes, sell if RSI(2) < 70 using MOO order.

Needing almost 80 lines of code for this simple strategy seems a bit too much. Can the code be made more efficient/smaller?
Also: is there an easy way to 'attach' a stop order to a market order such that when the position gets closed,
the stop order is automatically cancelled?
'''
class RSI_Strategy(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)

tickers = ['SPY', 'IEF']
self.symbol_data_by_symbol = {}
for ticker in tickers:
self.symbol_data_by_symbol[symbol] = SymbolData(self, symbol)

def OnData(self, data):
for symbol, symbol_data in self.symbol_data_by_symbol.items():
if not data.ContainsKey(symbol):
continue

if not self.Securities[symbol].Invested:
if sum(list(symbol_data.rsi_window)) < 15:
quantity = self.CalculateOrderQuantity(symbol, 1 / len(self.symbol_data_by_symbol))
if quantity:
self.MarketOrder(symbol, quantity)

elif symbol_data.rsi.Current.Value > 70:
self.Liquidate(symbol)

#Attach a stop order to the market order. The reason why this cannot be done in the OnData code is
#that we cannot know the fill price before the order gets filled.
def OnOrderEvent(self, orderEvent):
order = self.Transactions.GetOrderById(orderEvent.OrderId)
#If a market on open order gets filled
if orderEvent.Status == OrderStatus.Filled and orderEvent.FillQuantity > 0:
fillPrice = orderEvent.FillPrice

#Set SL order
symbol_data = self.symbol_data_by_symbol[orderEvent.Symbol]
stop_price = fillPrice - 2 * symbol_data.atr.Current.Value
symbol_data.stopTicket = self.StopMarketOrder(orderEvent.Symbol, -symbol_data.amount, stop_price)

class SymbolData:
amount = 0
stopTicket = None

def __init__(self, algorithm, symbol, rsi_indicator_length=2, rsi_window_length=3):

# Create indicators
self.atr = AverageTrueRange(1, MovingAverageType.Simple)
self.rsi = RelativeStrengthIndex(rsi_indicator_length, MovingAverageType.Wilders)
self.rsi_window = RollingWindow[float](rsi_window_length)

# Warmup indicators
history = algorithm.History(symbol, rsi_indicator_length + rsi_window_length, Resolution.Daily)
for time, row in history.loc[symbol].iterrows():
self.rsi.Update(time, row.close)

self.rsi_window.Add(self.rsi.Current.Value)