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