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)