Overall Statistics |
Total Trades 169 Average Win 0.10% Average Loss -0.07% Compounding Annual Return 12.342% Drawdown 0.800% Expectancy 0.031 Net Profit 0.736% Sharpe Ratio 2.884 Probabilistic Sharpe Ratio 72.490% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 1.48 Alpha 0.085 Beta -0.03 Annual Standard Deviation 0.031 Annual Variance 0.001 Information Ratio 2.383 Tracking Error 0.107 Treynor Ratio -3.034 Total Fees $312.65 Estimated Strategy Capacity $490000000.00 Lowest Capacity Asset NQ XUERCWA6EWAP |
# Forum response - has figured out how to get the different cosolidations to warmup for each contract as it is added to the universe. # Prices, RSI & MACD values match SSE # include re-enter threshhold for MACD # - not sure how to implement w/o it preventing going long/short in the other direction # - w/o something, almost guaranteed to have a losing trade after a winning trade # - will need to distinguish between Long/Short takeProfit, stopLoss, and recentlyLiquidated # - this is only addition / change from Keep.py # - worked up until a sell market on open occurred and SL/TP didn't cancel each other. Both executed. Algo placed 1 more trade and then stopped. Left 1 one long position. # -- need to look up if sell market on open created a different ticket or something. # --- maybe just adding an 'if market open:' to beginning of algo. Stop_Loss = 30 Take_Profit = 50 class FocusedApricotAlpaca(QCAlgorithm): def Initialize(self): # Training Set self.SetStartDate(2021, 9, 1) self.SetEndDate(2021, 9, 23) # 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 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) # - need to reference symbolData to get the RSI and MACD attributes # 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)) #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)) 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 self.Cancel(orderEvent.OrderId) # this would probably be good spot to set longRecentlyLiquidated & shortRecentlyLiquidated == True if distiction can be made 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() elif self.longStopLoss is not None and id == self.longStopLoss.OrderId: self.longTakeProfit.Cancel() # else: # return 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() elif self.shortStopLoss is not None and id == self.shortStopLoss.OrderId: self.shortTakeProfit.Cancel() # else: # return 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)) } # 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(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 __repr__(self): return f'RSI: {self.rsi.Current.Value:.4f} MACD: {self.macd.Current.Value:.4f}' initialMargin = self.Securities[contract.Symbol].BuyingPowerModel.InitialOvernightMarginRequirement