| 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