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()