| 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