| 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)