Overall Statistics
Total Trades
4
Average Win
0.65%
Average Loss
0%
Compounding Annual Return
1.296%
Drawdown
0.600%
Expectancy
0
Net Profit
1.295%
Sharpe Ratio
1.123
Probabilistic Sharpe Ratio
56.374%
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0
Alpha
0.008
Beta
0.017
Annual Standard Deviation
0.012
Annual Variance
0
Information Ratio
-2.512
Tracking Error
0.123
Treynor Ratio
0.776
Total Fees
$6.61
'''
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(2019, 1, 1) 
        self.SetEndDate(2020, 1, 1)
        self.SetCash(100000)
        self.percentPerStock = 0.5 #Position size is 50%
        
        self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
        self.ief = self.AddEquity("IEF", Resolution.Minute).Symbol
        
        self.amountSPY = 0
        self.amountIEF = 0
        
        self.stopTicketSPY = 0
        self.stopTicketIEF = 0
        
        #Indicators
        self.rsi_SPY = self.RSI("SPY", 2, MovingAverageType.Wilders, Resolution.Daily)
        self.RW_rsi_SPY = RollingWindow[float](3)
        
        self.rsi_IEF = self.RSI("IEF", 2, MovingAverageType.Wilders, Resolution.Daily)
        self.RW_rsi_IEF = RollingWindow[float](3)

    def OnData(self, data):
        if not data.ContainsKey(self.spy) or not data.ContainsKey(self.ief) or not self.Time.hour == 16 or not self.Time.minute == 0:
            return
        
        if self.rsi_SPY.IsReady and self.rsi_IEF.IsReady:
            self.RW_rsi_SPY.Add(self.rsi_SPY.Current.Value)
            self.RW_rsi_IEF.Add(self.rsi_IEF.Current.Value)
            
        if self.RW_rsi_SPY.IsReady and self.RW_rsi_IEF.IsReady:
            cumRSI_SPY = sum(list(self.RW_rsi_SPY))
            cumRSI_IEF = sum(list(self.RW_rsi_IEF))
            
            dollarAmount = self.percentPerStock*self.Portfolio.TotalPortfolioValue 
            
            #Buy rules
            if cumRSI_SPY < 15 and not self.Securities[self.spy].Invested:
                self.amountSPY = int(dollarAmount/data[self.spy].Close)
                self.MarketOnOpenOrder(self.spy, self.amountSPY)
                
            if cumRSI_IEF < 15 and not self.Securities[self.ief].Invested:
                self.amountIEF = int(dollarAmount/data[self.ief].Close)
                self.MarketOnOpenOrder(self.ief, self.amountIEF)
        
        #Sell rules
        if self.Securities[self.spy].Invested and self.rsi_SPY.Current.Value > 70:
            self.Liquidate(self.spy)
            #Cancel SL order
            self.stopTicketSPY.Cancel()
            
        if self.Securities[self.ief].Invested and self.rsi_IEF.Current.Value > 70:
            self.Liquidate(self.ief)
            #Cancel SL order
            self.stopTicketIEF.Cancel()
        
    #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
            
            #Calculate 1 day ATR
            history = self.History(orderEvent.Symbol, 2, Resolution.Daily)
            previousClose = history['close'].iloc[0]
            high = history['high'].iloc[1]
            low = history['low'].iloc[1]
            atr = max(abs(high - low), abs(low - previousClose), abs(high - previousClose))
            
            #Set SL order
            if orderEvent.Symbol == self.spy:
                self.stopTicketSPY = self.StopMarketOrder(self.spy, -self.amountSPY, fillPrice - 2*atr)
            if orderEvent.Symbol == self.ief:
                self.stopTicketIEF = self.StopMarketOrder(self.ief, -self.amountIEF, fillPrice - 2*atr)