Overall Statistics |
Total Trades 53 Average Win 0.51% Average Loss -0.48% Compounding Annual Return 0.579% Drawdown 5.200% Expectancy 0.263 Net Profit 3.311% Sharpe Ratio 0.195 Probabilistic Sharpe Ratio 1.733% Loss Rate 38% Win Rate 62% Profit-Loss Ratio 1.05 Alpha 0.005 Beta -0.003 Annual Standard Deviation 0.026 Annual Variance 0.001 Information Ratio -0.607 Tracking Error 0.171 Treynor Ratio -1.667 Total Fees $108.48 |
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: symbol = self.AddEquity(ticker, Resolution.Daily).Symbol 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(): trade_bar = TradeBar(time, symbol, row.open, row.high, row.low, row.close, row.volume) self.atr.Update(trade_bar) self.rsi.Update(time, row.close) if self.rsi.IsReady: self.rsi_window.Add(self.rsi.Current.Value) # Setup consolidators to update indicators self.consolidator = TradeBarConsolidator(timedelta(1)) self.consolidator.DataConsolidated += self.CustomHandler algorithm.SubscriptionManager.AddConsolidator(symbol, self.consolidator) def CustomHandler(self, sender, consolidated): self.atr.Update(consolidated) self.rsi.Update(consolidated.Time, consolidated.Close) self.rsi_window.Add(self.rsi.Current.Value)