Overall Statistics |
Total Trades 18 Average Win 0.10% Average Loss -0.07% Compounding Annual Return 1.854% Drawdown 0.300% Expectancy 0.038 Net Profit 0.025% Sharpe Ratio -2.489 Probabilistic Sharpe Ratio 35.822% Loss Rate 56% Win Rate 44% Profit-Loss Ratio 1.34 Alpha -0.062 Beta -0.114 Annual Standard Deviation 0.018 Annual Variance 0 Information Ratio 0.592 Tracking Error 0.169 Treynor Ratio 0.397 Total Fees $33.30 Estimated Strategy Capacity $98000000.00 Lowest Capacity Asset NQ XUERCWA6EWAP |
# Prices, RSI & MACD values match SSE # reEntry on MACD works. # need to: # try adding higher/lower than previous day close in addition to RSI to entries # try creating a custom indicator for previous day close. Add the following to the SymbolData class and Update section for SymbolData Stop_Loss = 30 Take_Profit = 50 class FocusedApricotAlpaca(QCAlgorithm): def Initialize(self): # Training Set self.SetStartDate(2021, 10, 1) self.SetEndDate(2021, 10, 5) # Validation Set # self.SetStartDate(2021, 9, 1) # self.SetEndDate(2021, 9, 23) self.SetCash(1000000) self.nq = self.AddFuture(Futures.Indices.NASDAQ100EMini) """ AddFuture adds a universe of all available contracts for a market/symbol. this grabs all the E-mini Nasdaq futures - all the same except for their expiration date """ self.nq.SetFilter(5, 120) self.contracts = {} self.oi_contract = None self.macd = None self.rsi = None self.longTakeProfit = None self.longStopLoss = None self.shortTakeProfit = None self.shortStopLoss = None self.longRecentlyLiquidated = False self.shortRecentlyLiquidated = False def OnData(self, slice): """ looping through the slice object of the futures universe to sort the most liquid contract (contract with the most open interest) to the top.""" for chain in slice.FutureChains: contracts = sorted([k for k in chain.Value if k.OpenInterest > 0], key=lambda k : k.OpenInterest, reverse=True) if not contracts: continue self.oi_contract = contracts[0] symbol = self.oi_contract.Symbol if symbol not in self.contracts: self.contracts[symbol] = SymbolData(self, symbol) self.contracts[symbol].consolidators['15M'].DataConsolidated += self.On15MData def OnSecuritiesChanged(self, changes): """ when a contract is removed from the universe (reaches expiration), remove the contract and its information from the 15M data consolidation """ for security in changes.RemovedSecurities: symbol = security.Symbol symbolData = self.contracts.get(symbol, None) if symbolData: symbolData.consolidators['15M'].DataConsolidated -= self.On15MData for consolidator in symbolData.consolidators.values(): self.SubscriptionManager.RemoveConsolidator(symbol, consolidator) def On15MData(self, sender, bar): """ trade signals are based off 15m MACD indicator, so trading is done in the 15MData. """ if not self.oi_contract or self.oi_contract.Symbol != bar.Symbol: return symbol = self.oi_contract.Symbol security = self.Securities[symbol] symbolData = self.contracts.get(symbol, None) if not symbolData or not symbolData.macd.IsReady: return msg = f'{symbol.Value} :: {symbolData}' if self.LiveMode: self.Log(msg) # Don't trade when QC thinks market is out so we don't break the OCO code w/ a market on open order. self.exchange = security.Exchange if not self.exchange.ExchangeOpen: return # Only new positions not invested if security.Invested: # Look to exit position return price = security.Price # Set MACD threshold """ arbitrary low number to simply indicate if MACD is in uptrend (+) or downtrend (-) """ tolerance = 0.003 signalDeltaPercent = symbolData.macd.Current.Value - symbolData.macd.Signal.Current.Value #re-enter threshold for MACD if self.longRecentlyLiquidated == True: if signalDeltaPercent > tolerance: return else: self.longRecentlyLiquidated = False if self.shortRecentlyLiquidated == True: if signalDeltaPercent < tolerance: return else: self.shortRecentlyLiquidated = False # Go Long """ go long when 15m MACD crosses (+) and the daily RSI is above 50 """ if signalDeltaPercent > tolerance and symbolData.rsi.Current.Value > 50: self.MarketOrder(symbol, 1) self.longTakeProfit = self.LimitOrder(symbol, -1, price+Take_Profit) # 1% is too far for day trades self.longStopLoss = self.StopMarketOrder(symbol, -1, price-Stop_Loss) # self.Debug(str(self.Time) + " buy " + str(price) + " MACD Current: " + str(symbolData.macd.Current.Value) + " MACD Signal:" + str(symbolData.macd.Signal.Current.Value) + " RSI: " + str(symbolData.rsi.Current.Value)) # self.Debug(str(self.Time) + " buy " + str(price) + " RSI: " + str(symbolData.rsi.Current.Value)) # self.Debug(str(self.Time) + " Yesterday Close: " + self.yesterdayClosePrice) #Go short """ go short when 15m MACD crosses (-) and daily RSI is below 50 """ if signalDeltaPercent < -tolerance and symbolData.rsi.Current.Value < 50: self.MarketOrder(symbol, -1) self.shortTakeProfit = self.LimitOrder(symbol, 1, price-Take_Profit) self.shortStopLoss = self.StopMarketOrder(symbol, 1, price+Stop_Loss) # self.Debug(str(self.Time) + " sell " + str(price) + " MACD Current: " + str(symbolData.macd.Current.Value) + " MACD Signal:" + str(symbolData.macd.Signal.Current.Value) + " RSI: " + str(symbolData.rsi.Current.Value)) # self.Debug(str(self.Time) + " sell " + str(price) + " RSI: " + str(symbolData.rsi.Current.Value)) # self.Debug(str(self.Time) + " Yesterday Close: " + self.yesterdayClosePrice) def OnOrderEvent(self, orderEvent): """ when a takeProfit or stopLoss is filled, it triggers a cancel for the other order """ if orderEvent.Status != OrderStatus.Filled: return else: self.Cancel(orderEvent.OrderId) def Cancel(self, id): '''cancel one order if the other was filled''' # cancel long take profit and stop loss if self.longTakeProfit is not None and id == self.longTakeProfit.OrderId: self.longStopLoss.Cancel() self.longRecentlyLiquidated = True elif self.longStopLoss is not None and id == self.longStopLoss.OrderId: self.longTakeProfit.Cancel() self.longRecentlyLiquidated = True else: self.longTakeProfit = None self.longStopLoss = None #cancel short take profit and stop loss if self.shortTakeProfit is not None and id == self.shortTakeProfit.OrderId: self.shortStopLoss.Cancel() self.shortRecentlyLiquidated = True elif self.shortStopLoss is not None and id == self.shortStopLoss.OrderId: self.shortTakeProfit.Cancel() self.shortRecentlyLiquidated = True else: self.shortTakeProfit = None self.shortStopLoss = None class SymbolData: """ setting up the 15minute and 1day consolidators and the RSI and MACD indicators """ def __init__(self, algorithm, symbol): self.consolidators = { '1D': TradeBarConsolidator(timedelta(1)), '15M': TradeBarConsolidator(timedelta(minutes=15)), } # rolling windows & indicators - https://www.quantconnect.com/docs/algorithm-reference/rolling-window#Rolling-Window-Combining-with-Indicators """ Custom Indicator Initialize""" self.previousDayCloseIndicator = CustomPreviousDayClose('PreviousClose', 2) # algorithm.RegisterIndicator(symbol, self.previousDayCloseIndicator, self.consolidators['1D']) # Rolling Window # self.previousDayCloseIndicator.Update += self.previousDayCloseIndicatorUpdated # self.pdiWindow = RollingWindow[float](2) # Set up Indicators # Use constructor, register to consolidator, warm up with history self.rsi = RelativeStrengthIndex(9, MovingAverageType.Wilders) algorithm.RegisterIndicator(symbol, self.rsi, self.consolidators['1D']) self.macd = MovingAverageConvergenceDivergence(12, 26, 9, MovingAverageType.Exponential) algorithm.RegisterIndicator(symbol, self.macd, self.consolidators['15M']) # Need 15 days of minute-resolution data to account weekends # wams up every contract that gets pulled as the most liquid contract history = algorithm.History(symbol, timedelta(days=120), Resolution.Minute) for index, row in history.iterrows(): bar = TradeBar(index[2]-Time.OneMinute, symbol, row.open, row.high, row.low, row.close, row.volume) bar.Symbol = symbol for consolidator in self.consolidators.values(): consolidator.Update(bar) msg = f'{symbol.Value} :: RSI.IsReady? {self.rsi.IsReady} :: MACD.IsReady? {self.macd.IsReady} :: {self}' algorithm.Log(msg) def previousDayCloseIndicatorUpdated(self, sender, updated): self.previousDayCloseIndicator.Add(updated) def __repr__(self): return f'RSI: {self.rsi.Current.Value:.4f} MACD: {self.macd.Current.Value:.4f}' initialMargin = self.Securities[contract.Symbol].BuyingPowerModel.InitialOvernightMarginRequirement class CustomPreviousDayClose: def __init__(self, name, period): self.Name = name self.Time = datetime.min self.Value = 0 self.closePrice = 0 def Update(self, input): self.Time = input.EndTime self.closePrice = symbol.close[1] # how to get Update to pull yesterday's closing price? # - may need to add rollingwindow, preferably of daily info # - https://www.quantconnect.com/docs/algorithm-reference/rolling-window#Rolling-Window-Combining-with-Indicators # same error message in https://www.quantconnect.com/forum/discussion/12190/help-needed-registering-indicators-to-custom-consolidators/p1