Overall Statistics |
Total Trades 4 Average Win 0.27% Average Loss -0.18% Compounding Annual Return 17.503% Drawdown 0.200% Expectancy 0.242 Net Profit 0.088% Sharpe Ratio 2.166 Probabilistic Sharpe Ratio 0% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.48 Alpha 0.577 Beta 0.992 Annual Standard Deviation 0.055 Annual Variance 0.003 Information Ratio 1290.259 Tracking Error 0 Treynor Ratio 0.12 Total Fees $4.00 Estimated Strategy Capacity $320000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X |
# VERSION 2 of MACD CROSSOVER STRAT: GOES LONG ON 3/6 CROSS UP SIGNAL IF NOT ALREADY INVESTED # EXECUTION LOGIC FROM SUCCESSFUL LIVE TRADING # FOR JOVAD 8/6 SESSION 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) # Warm up using 500*60 minute bars for all subscribed data 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) # SWAPPED OUT FOR LINE BELOW TESTING CURRENT.VALUE BUT DIDN'T WORK 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.lowValues = RollingWindow[float](1+60) self.highValues = RollingWindow[float](5*60) def OnData(self, data): if not self.__macd.IsReady: return self.StopLossTracking() tolerance = 0.0025 holdings = self.Portfolio["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) price = self.Securities["SPY"].Price quantity = self.CalculateOrderQuantity("SPY", .5*.95) # CHANGING ENTRY ORDER TO ENTER HALF ORDER AS MARKET AND HALF AS LIMIT + 0.02 TO COMPARE FILLS self.MarketOrder("SPY", quantity) # Places a market order using half of portfolio buying power (ALT USE: 'self.SetHoldings("SPY", 0.95) ## self.LimitOrder("SPY", quantity, price + 0.02) # Places a limit entry order using half of portfolio buying power (to compare with market price) elif holdings >= 0 and signalDeltaPercent < -tolerance: return # deleted following code: ## self.Liquidate("SPY") ## self.SetHoldings("SPY", -0.95) self.Debug(str(self.Portfolio["SPY"].AveragePrice)) # print avg fill ## ISN'T ABOVE DUPE OF LINE 64? ## self.__previous = self.Time def OnOrderEvent(self, orderEvent): if orderEvent.Status != OrderStatus.Filled: return if self.entryTicket != None and self.entryTicket.OrderId == orderEvent.OrderId: # Stop limit exit order self.holdings = self.Securities["SPY"].Holdings self.low = self.Securities["SPY"].Low # low of bar prior to entry #*** NEED TO ADD 60 TO THIS TO INDEX IT TO PREVIOUS MINUTE BAR INSTEAD OF PREVIOUS SECOND BAR 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' # defines stop loss at time step 1: opening trade execution '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' lastBar = self.History(self.spy, 1, Resolution.Minute) self.symbolDataBySymbol[self.spy].stopPrice = lastBar["low"][0] 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 = lastBar["high"][0] 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() # 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: symbolData.stopPrice = symbolData.fillPrice symbolData.currentTimeStep = 2 symbolData.stopTicket = self.StopLimitOrder(self.spy, symbolData.fillPrice) # 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 (self.Time - symbolData.entryTime).Minute >= 4: symbolData.stopPrice = symbolData.entryHigh symbolData.currentTimeStep = 3 symbolData.stopTicket.Cancel() symbolData.stopTicket = self.StopLimitOrder(self.spy, symbolData.entryHigh) 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 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()