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