Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
8.374
Tracking Error
0.055
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
# TAKES JOVAD-UPDATED VERSION 2 of MACD CROSSOVER STRAT WITH CORRECT TRADE LOGIC TO GO LONG ON 3/6 CROSS UP SIGNAL IF NOT ALREADY INVESTED
# AND ADDS MY UPDATES FOR CONSOLID+RW (see code after ###)

# RETURNING ERRORS:
# stop logic: @ ENTRY through ENTRY BAR CLOSE: stop = LOW OF PRIOR BAR.  AFTER ENTRY BAR CLOSE:  stop = FILL PRICE
# PROBLEM IS LIQUIDATION (NOT STOP) TRIGGERING SECONDS AFTER ENTRY
# swapped out lines 67 and 69 to try limit order instad of market order (works fine)
# commented out 93, 94, 95 and 96 for the time being

# SOLUTION:  we're working in second resolution, so in order to get the low of the 1minute bar prior to entry we need to 
# consolidate 60 bars into a 1 minute bar (CREATE 1MIN BAR EVERY 60SEC) and reference the most recent one at the entry to set stop loss
# then when the next 1minute bar closes update the stoploss ticket to stop the fill price.  
# STEPS:
#Created consolidator classes for minute and daily (daily will be used in future)
#Created handlers to be executed every time new bar of consolidated data is created
#Added the consolidators via the SubscriptionManager


import pandas as pd
import decimal as d

class MACDCrossoverStratVer2(QCAlgorithm):

    stopMarketTicket = None
    stopLimitTicket = None
    profitTargetTicket = None
    stopMarketFillTime = datetime.min
    stopLimitFillTime = datetime.min
    
    def Initialize(self):
        self.SetStartDate(2021, 8, 3)
        self.SetEndDate(2021, 8, 4)
        self.SetCash(100000)
        self.spy = self.AddEquity("SPY", Resolution.Second).Symbol
        self.symbolDataBySymbol = {}
        symbolData = SymbolData(self.spy)
        self.symbolDataBySymbol[self.spy] = symbolData
        self.SetWarmUp(30000)

        self.__macd = self.MACD("SPY", 3*60, 6*60, 9*60, MovingAverageType.Exponential, Resolution.Second)
        self.__previous = datetime.min
        self.PlotIndicator("MACD", True, self.__macd, self.__macd.Signal)  
        self.PlotIndicator("SPY", self.__macd.Fast, self.__macd.Slow)

        self.entryTicket = None
        self.stopLimitTicket = None
        self.profit1Ticket = None
        self.profit2Ticket = None
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 1), self.LiquidateAtClose)
        # removed datanormalizationmode raw

        self.previousBound = "None"

        #self.lowValues = RollingWindow[float](1+60) 
        #self.highValues = RollingWindow[float](5*60) 

### MY CONS+RW LOGIC
        # minute bar will be used in this strat version, daily for future version
        consolidator_daily = TradeBarConsolidator(timedelta(1))
        consolidator_daily.DataConsolidated += self.OnDailyData
        self.SubscriptionManager.AddConsolidator("SPY", consolidator_daily)

        consolidator_minute = TradeBarConsolidator(60)
        consolidator_minute.DataConsolidated += self.OnMinuteData
        self.SubscriptionManager.AddConsolidator("SPY", consolidator_minute)

        self.daily_rw = RollingWindow[TradeBar](2)
        self.minute_rw = RollingWindow[TradeBar](2)
        self.window = RollingWindow[TradeBar](2)
 

### MY ROLLING WINDOW LOGIC 
     # Adds daily and minute bars to their rolling windows
    def OnDailyData(self, sender, bar):
        self.daily_rw.Add(bar)

    def OnMinuteData(self, sender, bar):
        self.minute_rw.Add(bar)

         
        
    def OnData(self, data):
        
        if self.IsWarmingUp:
            return

        if not self.__macd.IsReady:       
            return

### MY ROLLING WINDOW LOGIC     
        if not (self.window.IsReady and self.daily_rw.IsReady and self.minute_rw.IsReady):
            return

        if data["SPY"] is None: 
            return
        self.window.Add(data["SPY"]) # adds second bar to window
        if not (self.window.IsReady):
            return

        last_close = self.window[0].Close
        yesterday_daily_close = self.daily_rw[1].Close
        last_minute_low = self.minute_rw[1].Low  # note: previous bar is [1], current is [0], oldest is [-1]
        last_minute_high = self.minute_rw[1].High 
###        

        tolerance = 0.0025
        holdings = self.Portfolio[self.spy].Quantity
        signalDeltaPercent = (self.__macd.Current.Value - self.__macd.Signal.Current.Value)
        # Trade signal:  When MACD crosses up its signal go long 
        if holdings <= 0 and signalDeltaPercent > tolerance:         # ALT 'if not self.Portfolio.Invested' 
##            self.Liquidate("SPY")  # first liquidate any short position before going long (no longer needed for this long-only algo)
            if self.previousBound == "Below" and not self.Portfolio[self.spy].IsLong:
                price = self.Securities[self.spy].Price
                quantity = self.CalculateOrderQuantity(self.spy, 0.95)  
                self.SetHoldings(self.spy, 0.95) ### YOU NEED A STOPLIMIT TICKET HERE
                self.symbolDataBySymbol[self.spy].quantity = quantity
##            self.LimitOrder("SPY", quantity, price + 0.02)  # Places a limit entry order (ALT w/market order)
            self.previousBound = "Above"

        elif holdings >= 0 and signalDeltaPercent < -tolerance:
            self.previousBound = "Below"
            #self.Liquidate(self.spy)
            return          # deleted following code:
                            ## self.Liquidate("SPY")
                            ## self.SetHoldings("SPY", -0.95)

            self.Debug(str(self.Portfolio["SPY"].AveragePrice))
            
                    
        self.StopLossTracking()


    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status != OrderStatus.Filled and self.entryTicket == orderEvent.OrderId:
            return
        
        # if self.entryTicket != None and self.entryTicket.OrderId == orderEvent.OrderId:
        if orderEvent.OrderId != self.stopLimitTicket and orderEvent.OrderId != self.symbolDataBySymbol[self.spy].stopTicket:

           # Stop limit exit order
            self.holdings = self.Securities[self.spy].Holdings
###         self.low = self.Securities[self.spy].Low  # REPLACED WITH CODE BELOW
            self.low = last_minute_low  # low of minute bar prior to entry 
            self.stopPrice = self.low - 0.01 # Trigger stop limit when price falls 1ct below the low of the bar prior to entry bar (based on ask price, if this takes bid then reduce to 0 cts)
            self.limitPrice = self.low -0.03 # Sell equal or better than 3cts below the low of the bar prior to entry bar (based on ask price, if this takes bid then reduce to 2cts)
            self.stopLimitTicket = self.StopLimitOrder("SPY", -1, self.stopPrice, self.limitPrice) # ***THIS WAS CAUSING STOP LIMIT ORDER TO BE PLACED FOR QTY OF 1 SHARE AS IT WAS -1 so i replaced with '-self.holdings' and it rerturned error: bad operand for unary 'Equity HOlding'

### REVISED LINES BELOW FOLLOWED BY ###                    
          # defines stop loss at time step 1: opening trade execution
            lastBar = self.History(self.spy, 1, Resolution.Minute)
            self.symbolDataBySymbol[self.spy].stopPrice = last_minute_low["low"][0]   ### was lastBar instead of last_minute_low
            self.symbolDataBySymbol[self.spy].currentTimeStep = 1
            self.symbolDataBySymbol[self.spy].entryTime = self.Time
            self.symbolDataBySymbol[self.spy].fillPrice = self.Securities[self.spy].Price
            self.symbolDataBySymbol[self.spy].entryHigh = last_minute_high["high"][0] ### was lastBar instead of last_minute_high
        
        if self.window.IsReady and window[0].Low > window[1].Low:  ### ADDED AS TEST
            self.Log("Higher low than low 1 minute ago")
     
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        if orderEvent.Status == OrderStatus.Filled: 
            self.Log("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent))        
        
    def StopLossTracking(self):
        for symbol, symbolData in self.symbolDataBySymbol.items():
            if symbolData.stopPrice == None: continue
        
            # If using limit order for stop (the 'if' below) then this first 'if' statement wont be needed since it is executing market order instead
 #           if self.Securities[symbol].Price <= symbolData.stopPrice:
 #               self.Liquidate(symbol)

            #symbolData.ResetVariables()
            delta = (self.Time - symbolData.entryTime)
            
            if symbolData.currentTimeStep > 0 and self.Securities[self.spy].Price <= symbolData.stopPrice:
                self.Liquidate(self.spy)
            
            # defines 1st stop loss update at time step 2 which is at close of entry bar
            if symbolData.currentTimeStep == 1 and symbolData.entryTime != self.Time and self.Portfolio[self.spy].IsLong:
                #self.stopLimitTicket.Cancel()
                symbolData.stopPrice = symbolData.fillPrice
                symbolData.currentTimeStep = 2
                #symbolData.stopTicket = self.StopLimitOrder(self.spy, -symbolData.quantity, symbolData.stopPrice, symbolData.stopPrice-0.03)

            # defines 2nd stop loss update at time step 3 which is at close of 4minutes after close of entry bar
            elif symbolData.currentTimeStep == 2 and delta.seconds >= 4*60 and self.Portfolio[self.spy].IsLong:
                symbolData.stopPrice = symbolData.entryHigh
                symbolData.currentTimeStep = 3
                #symbolData.stopTicket.Cancel()
                #self.Transactions.CancelOpenOrders(symbol)
                #symbolData.stopTicket = self.StopLimitOrder(self.spy, -symbolData.quantity, symbolData.entryHigh, symbolData.entryHigh - 0.03)mb  
                
            

        
            # self.lastOrderEvent = orderEvent  # save order
            # self.Debug(orderEvent.OrderId)  # print order ID
            self.Debug(self.Securities["SPY"].Close)  # print fill prices BUT WE ALREADY HAVE THIS ABOVE

        # if self.stopLimitTicket is not None and self.stopLimitTicket.OrderId == orderEvent.OrderId:

            self.stopMarketFillTime = self.Time  # store datetime
            self.stopLimitFillTime = self.Time
            self.Debug(self.stopMarketFillTime)

    def LiquidateAtClose(self):
        if self.Securities["SPY"].Price is not None:
           self.Liquidate()   #  ALT: self.MarketOnCloseOrder()
           
           
class SymbolData:
    def __init__(self, symbol):
        self.Symbol = symbol
        self.stopPrice = None
        self.currentTimeStep = 0 # 0 no position, 1, 2, 3
        self.entryTime = None
        self.fillPrice = None
        self.entryHigh = None
        self.stopTicket = None
        self.closeWindow = None  # 10:25 DEBUGGING: added this to SEE IF IF SOLVES ISSUE
        self.quantity = 0
        
    def ResetVariables(self):
        self.stopPrice = None
        self.currentTimeStep = 0 # 0 no position, 1, 2, 3
        self.entryTime = None
        self.fillPrice = None
        self.entryHigh = None
        if self.stopTicket != None: self.stopTicket.Cancel()