Overall Statistics |
Total Trades 6 Average Win 0.58% Average Loss -0.12% Compounding Annual Return 16.406% Drawdown 1.000% Expectancy 0.423 Net Profit 0.211% Sharpe Ratio 1.474 Probabilistic Sharpe Ratio 0% Loss Rate 75% Win Rate 25% Profit-Loss Ratio 4.69 Alpha 0.611 Beta 1.379 Annual Standard Deviation 0.098 Annual Variance 0.01 Information Ratio 6.22 Tracking Error 0.078 Treynor Ratio 0.105 Total Fees $6.31 Estimated Strategy Capacity $240000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X |
# MACD CROSSOVER STRATEGY (testing latency and capabilities of system, not real strat) # USE THIS AS THE MASTER FOR PAPER TRADING # 12:07 DEBUG: says i have no attribute macd in line 111 (plot..my indicators), COMMENTED OUT THAT LINE # 12:12 DEBUG: GAVE CODE 'This operation is not allowed in Initialize or during warm up: OrderRequest.Submit. Please move this code to the OnWarmupFinished() method. Inserted a check for warmup to be done. COMMENTED OUT LINE 110 if self.__previous.date() == self.Time.date(): return # 2:00 DEBUG: orders triggering 2 bars after signal & submitting stop limit for 1 share 1 second later which tends to be filled b/c price bouncing # to address above: changed from Ondata(data) to (slice); in Init added 'self' to beginning of 'symbolData = SymbolData(self.spy)' # updated stop limit order at line 191 from -1 to -self.Portfolio (see other options if tht doesn't work) # NEED JOVAD HELP: received this error (see pdf for detail): Runtime Error: KeyNotFoundException : 'SPY' wasn't found in the Slice object, likely because there was no-data at this moment in time and it wasn't possible to fillforward historical data. Please check the data exists before accessing it with data.ContainsKey("SPY") # ask him: do I need 'self' before 'tolerance', 'holdings' and 'signalDeltaPercent'? # Description: # CHANGE FROM SPY TO A GROUP OF INDICES [OR FOR ALTERNATE VERSION: A DYNAMIC UNIVERSE OF STOCKS BASED ON THEIR AVGVOLUME IN PAST 120(?) MINUTES AND ONES THAT RECENTLY HIT THEIR 50SMA OR EMA ON HIGHER TIMEFRAME E.G. 2H OR 4H OR DAILY (??? CHECK FRANK'S LOGIC) # Goes long SPY when 3/6 MACD crosses up its signal # Buy order position-sized to 80% of portfolio submitted with a stop loss (to be updated based on elapsed time) and 2 take profit orders # If previous entries were at higher prices then a second buy order position-sized to 20% of portfolio is to be entered with same entry and stop loss but profit target will be MOC # All are limit orders and because of this algo will adjust them by 1-2cts if not filled within a certain time # Buy limit order is SPY ask price at time of signal + 0.02 for slippage # Sell stop limit order is the low of the [0] bar - $0.01 ([0] referring the bar prior to the entry bar, not sure I have that right) # Profit target #1 for half of position: # if price <18ema & 50ema sell half position when price breaks the low of the prior bar (trails low of prior bar), using limit order of low of prior bar -$0.01 OR when price hits the 50sma overhead @ 50sma price less $0.01 # if price >18ema sell when there are 2 lower lows in a row and price breaks the low of the 2nd low bar, using limit order of low of 2nd bar - $0.01 # Profit target #2 for remaining half of position: sell at fill price + $1.11 [IF ENTRY IS NEAR DAILY OR 4H 18EMA OR 50EMA CHANGE TARGET TO FILL + $5] # Stop loss update #1: After entry bar closes, stop limit should be moved to fill price - $0.01 # Stop loss update #2: 4 minutes after entry bar closes, stop limit should be moved to high of entry bar # Profit target update: when price breaks above the 18ema the target logic will change as noted in profit target #1 above # IMPORTANT: profit target 1a vs 1b may be too complicated for this first pass, I am open to suggestions # Print last trade P&L in terminal window # Chart SPY 1min price and plot filled entry orders and the SL & TP orders on the chart # IMPORTANT RE: BUY ORDERS: BUY SHOULD TRIGGER AS SOON AS MACD CROSSES ABOVE SIGNAL, WHICH IS "SUB MINUTE" TIMEFRAME. SO IS MINUTE OK OR DO WE NEED A LOWER RESOLUTION LIKE TICK IN ONDATA? # THIS IS ALSO NEEDED TO AVOID MULTIPLE SELL ORDERS BEING TRIGGERED WHEN TWO PROFIT TARGETS ARE MET BY A BAR THAT SPIKES UP BIG, THEREBY CAUSING MORE SELL ORDERS THAN EXPECTED # Conditions: # ALL MANUALLY ENTERED ORDERS MUST HAVE THE SAME STOP LOSS/PROFIT TARGET LOGIC APPLIED # no buy orders to be submitted within 30 minutes of the close # holdings to be liquidated by end of day at MOC # max loss in a day to be set at 1% of portfolio value, with alert from algo when 0.75% is reached # alert from algo when there are 2 stop losses hit in past 12 bars # Questions: # How to liquidate at specific time, e.g. 1 hr before the close? # How to go long multiple indices simultaneously? # not sure about imports clr and decimal (copied from code) # does sell orders use bid prices and buy orders use ask? # if I want algo to trade extended hours, I'd like it to liquidate holdings by 8pm ET but not end of morning pretrade at 9:30 when RTH starts # how do Webhook notifications work? # RESOURCES: # github macd x code - https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/MACDTrendAlgorithm.py # for orders: compare OCO w/2 take profit orders to yours below - project 'clone of stop loss with 2 take profit.....'' # C# code here https://www.quantconnect.com/forum/discussion/1072/setting-a-stop-loss-and-take-profit/p1 # for plotting: ? ##import clr ##import decimal as d ##import pandas as pd class MACDCrossOverStrat(QCAlgorithm): def Initialize(self): ## self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash) # remove ## to use this in live trading when you commence, and change from cash to margin if you want algo to automatically use DTBP # SEE HERE FOR GETTING SHORT AVAILABILITY FROM IBKR https://www.quantconnect.com/forum/discussion/11855/us-equities-short-availability/p1 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.SetDataNormalizationMode(DataNormalizationMode.Raw) # ***DO WE NEED THIS? I THINK MAYBE FOR TICK BY TICK DATA TO FORM CHARTS? self.SetWarmUp(30000) # Warm up using 30000 minute bars for all subscribed data # defines minute macd(3,6) with a 9 minute signal self.__macd = self.MACD("SPY", 3*60, 6*60, 9*60, MovingAverageType.Exponential, Resolution.Second) # DID NOT TRIGGER ORDERS WHEN RESOLUTION 'SECOND' AND MACD COMPONENTS MULTIPLIED BY 60 self.__previous = datetime.min self.PlotIndicator("MACD", True, self.__macd, self.__macd.Signal) # OTHER CODE: 'self.macd = MACD("SPY", 3, 6,9, MovingAverageType.Exponential, Resolution.Minute)' then in OnData add: 'self.Plot("My Indicators", "MACD Signal", self.macd.Signal)' self.PlotIndicator("SPY", self.__macd.Fast, self.__macd.Slow) # NEED TO IMPORT PLOTTING PROGRAM? # defines entry + SL + 2 TP orders self.entryTicket = None self.stopLimitTicket = None self.profit1Ticket = None # self.profit1aTicket = None # self.profit1bTicket = None self.profit2Ticket = None # schedule liquidation of portfolio right before market close self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 1), self.ClosePositions) # CREATE ROLLING WINDOWS FOR TRADE BAR FOR STOP LOSS UPDATES # note length is (desired lookback period + 1) - BELOW IS FOR OUR ORIGNAL STOP SET AT ENTRY, NEXT IS UPDATED STOP SET 4MIN AFTER ENTRY BAR CLOSE self.lowValues = RollingWindow[float](1*60) # had to multiply by 60 seconds in a minute, like we did for MACD above self.highValues = RollingWindow[float](5*60) # also for above see code at https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/RollingWindowAlgorithm.py # VIX PLACEHOLDER: ## self.vix = self.AddData(CBOE, "VIX").Symbol ## self.vix3m = self.AddData(CBOE, "VIX3M").Symbol def OnData(self, data): # *** DOES THIS NEED TO SAY SLICE INSTEAD OF DATA? # wait for macd to fully initialize if not self.__macd.IsReady: return # NOT SURE I NEED BELOW CODE LINE, IT WAS FROM GITHUB MACD CROSS DOC # only once per day ## if self.__previous.date() == self.Time.date(): return ## self.Plot("My Indicators", "MACD Signal", self.macd.Signal) # ADDED THIS AS REFERENCED IN INITIALIZE LINE BUT WAS BUG SO COMMENTED OUT HERE self.StopLossTracking() # add the low and high value to the RollingWindow each time the data updates ## self.lowValues.Add(data["SPY"].Low) ## self.highValues.Add(data["SPY"].High) # make sure window is properly warmed up before using ## if not self.lowValues.IsReady and self.highValues.IsReady: ## return # Add SPY TradeBar in rolling window #UNCOMMENTED - MAY NEED TO RECOMMENT ## self.window.Add(data["SPY"]) # Wait for windows to be ready. # " " " " ## if not (self.window.IsReady): return # define a small tolerance on our checks to avoid bouncing tolerance = 0.0025 holdings = self.Portfolio["SPY"].Quantity signalDeltaPercent = (self.__macd.Current.Value - self.__macd.Signal.Current.Value) # At MACD crossover signal go long if holdings <= 0 and signalDeltaPercent > tolerance: # INSTEAD OF 'if holdings <=0' can also use 'if not self.Portfolio.Invested' HOWEVER CAN'T USE THAT IF YOU GO SHORT # To prevent orders firing before warmup is finished (was error) self.IsWarmingUp = False quantity = self.CalculateOrderQuantity("SPY", 0.95) # FORMERLY USED self.SetHoldings("SPY", 0.95) # Q about quantity: if I can use logic that enters 2 positions at 80%/20% of portfolio based on the previous higher price entry criteria then this will have to be changed accordingly self.entryTicket = self.LimitOrder("SPY", quantity, self.Securities[self.spy].Price + 0.02) ## REPLACED THIS: order = self.LimitOrder(self.symbol, quantity, self.LastPrice + 0.02) # ENTRY ORDER:HAVE TO MAKE SURE WE GO LONG VIA LIMIT ORDER AT ASK+0.02 WHEN SIGNAL TRIGGERS SO IS "LAST" THE "ASK"??? # Prints (Debug) the AveragePrice of the filled buy order self.Debug(str(self.Portfolio["SPY"].AveragePrice)) # NEED CODE FOR THIS: no buy orders to be submitted within 30 minutes of the close # ROLLING WINDOWS [WE HAVE NOT USED THESE YET]: # update the rolling windows ## if data.ContainsKey("SPY"): ## # Add SPY bar close in the rolling window ## I NEED TO WORK ON THIS, I THINK I NEED THE LOW? ## self.closeWindow.Add(data["SPY"].Low) ## # Add SPY TradeBar in rolling window ## I NEED TO WORK ON THIS, DOES IT CONTAIN OHLC? ## self.tradeBarWindow.Add(data["SPY"]) # ALSO ROLLING WINDOWS STORE PAST VALUES SO MAYBE THIS IS HOW I STORE DATA THAT ALGO CAN REFERNCE IN CHOOSING PROFIT TARGET) self.__previous = self.Time # what is this?? # max loss in a day to be set at 1% of portfolio value, with alert from algo when 0.75% is reached # VIX PLACEHOLDER ## self.Plot("VIX Data", "VIX", data[self.vix].Close) ## self.Plot("VIX3M Data", "VIX3M", data[self.vix3m].Close) def OnOrderEvent(self, orderEvent): if orderEvent.Status != OrderStatus.Filled: # save orderEvent to lastOrderEvent self.lastOrderEvent = orderEvent #print out order ID self.Debug(orderEvent.OrderId) # Printing the security fill prices. self.Debug(self.Securities["SPY"].Close) if orderEvent.Status != OrderStatus.Filled: return # Sell stop order (market or limit - choose below) if self.entryTicket != None and self.entryTicket.OrderId == orderEvent.OrderId: # Enter stop limit sell order 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) # ***instead of 'self.Portfolio' COULD TRY 'SELF.HOLDINGS' OR 'SELF.FILLQUANTITY' IF IT DOESN'T WORK # Enter take profit #1 sell order - 2 "either/or" orders, NEED CODE FOR CONDITIONS # if price <18ema & 50ema sell half position when price breaks the low of the prior bar, using limit order of low of prior bar -$0.01 OR when price hits the 50sma overhead @ 50sma price less $0.01 # if xxxxxx # else yyyyy ## self.profit1aTicket = self.LimitOrder("SPY", -.5, self.profitTarget1a) # if price >18ema sell when there are 2 lower lows in a row and price breaks the low of the 2nd low bar, using limit order of low of 2nd bar - $0.01 ## self.profit1bTicket = self.LimitOrder("SPY", -.5, self.profitTarget1b) # Enter take profit #2 sell order for remaining half of position at fill price + $1.11 # self.profitTarget1 = symbolData.fillPrice + 0.20 # JUST USING 50CTS INSTEAD OF 1.11 TO TEST IT # self.profit1Ticket = self.LimitOrder("SPY",-1, self.profitTarget1) # right now just testing at .50 profit # self.profitTarget2 = symbolData.fillPrice + 0.50 # JUST USING 50CTS INSTEAD OF 1.11 TO TEST IT # self.profit2Ticket = self.LimitOrder("SPY",-1, self.profitTarget2) # right now just testing at .50 profit # BELOW IS THE CODE FROM JOVAD - MAKE SURE THE STOP LOSS OF EACH TIME STEP IS CORRECT IN PAPER TRADING # 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] # self.symbolDataBySymbol[self.spy].fillQuantity = self.Securities[self.spy].FillQuantity # NOT SURE I NEED SINCE I'M USING PORTFOLIO IN STOP LIMIT ORDER ABOVE, BUT I ADDED THIS B/C WE NEED TO ADD FILL QUANTITY FROM OPEN ORDER SO IT CAN BE USED FOR THE SELL STOP AS THE STOP WAS ENTERING A QTY OF 1 # ***CRITICAL NEED: If our TP or SL was filled, cancel the other(s). # BELOW IS THE CODE THAT CANCELS THE SL OR TP WHEN THE OTHER IS EXECUTED ## if self.profit1Ticket != None and self.profit1Ticket.OrderId == orderevent.OrderId: ## self.stopLimitTicket.UpdateQuantity(-50) ## ## if self.stopLimitTicket != None and self.stoplossTicket.OrderId == orderevent.OrderId: ## self.profit1Ticket.Cancel() # self.profit1aTicket.Cancel() # self.profit1bTicket.Cancel() ## self.profit2Ticket.Cancel() ## self.entryTicket = None ## if self.profit2Ticket != None and self.profit2Ticket.OrderId == orderevent.OrderId: ## self.stopLimitTicket.Cancel() ## self.entryTicket = None order = self.Transactions.GetOrderById(orderEvent.OrderId) if orderEvent.Status == OrderStatus.Filled: self.Log("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent)) # self.Debug = self.Portfolio[symbol].LastTradeProfit # alert from algo when there are 2 stop losses hit in past 12 bars # STOP LOSS UPDATES: # UPDATE #1: Need code for updating stop price at close of entry bar to low of entry bar # INSERT CODE HERE - NEED CONDITION ON WHICH THIS IS BASED, & USE ROLLING WINDOW and [0] as index for prior bar to entry bar...update the code below ## self.StopLimitOrder(self.Symbol, -self.FillQuantity, self.Low - .02) # STOP LOSS - MAKE SURE THIS REFERS TO BAR PRIOR TO ENTRY - I THINK HAVE TO ACCESS VIA ROLLING WINDOW # UPDATE #2: Need code for updating stop above to stop price at high of entry bar once the 3rd bar after entry bar closes # INSERT CODE HERE - NEED CONDITION ON WHICH THIS IS BASED, & USE ROLLING WINDOW and [3] as index for entry bar HIGH price as new stop ## self.StopLimitOrder(self.Symbol, -self.FillQuantity, self.Low - .02) # STOP LOSS - MAKE SURE THIS REFERS TO BAR PRIOR TO ENTRY - I THINK HAVE TO ACCESS VIA ROLLING WINDOW # PROFIT TARGET UPDATES: # If price is below the 18ema: EXIT AT BREACH OF THE PRIOR CLOSED BAR’S LOW (e,g the first higher lower that’s breached to the downside) OR PRICE HIT OF THE 50SMA, EXCEPT AFTER 3:30 THEN EXIT WILL BE MOC # REVISE CODE BELOW # self.LimitOrder(self.Symbol, -1, self.Price + .02) # PROFIT TARGET - QUESTION: IS THE NEGATIVE 1 APPROPRIATE FOR SELLING 100% OF HOLDINGS? # ALSO MAKE SURE THAT IF PRICE DESCENDS DOWN PAST STOP LIMIT PRICE YOU REPLACE THAT ORDER WITH ONE AT THEN CURRENT BID LESS 1CT # HAVE TO SENT UPDATE ORDERS TO UPDATE THE STOP LOSS ORDERS USING # profitTargetPrice = 0 # stopLossPrice=0 # NEED CODE TO VERIFY WHETHER ORDER FILLED WAS FOR STOP LOSS OR PROFIT TARGET SO ALGO CAN CANCEL OTHER ORDER # SEE HERE FOR MODELING SLIPPAGE: https://www.quantconnect.com/docs/algorithm-reference/reality-modelling def ClosePositions(self): if self.Portfolio.Invested: self.Liquidate() # def PositionSizing(self, allInvested): # JOVAD STARTED WITH THIS CODE THEN USED DIFFERENT SO COMMENTED THIS OUT # return {} 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) # Resetting needs to go somewhere...here or OnOrderEvent accdg to Jovad 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) 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() # THIS WILL BE POSSIBLE FOR FUTURE UPDATES, KEEPING HERE FOR NOW: # coarse/fine for stock selection: coarse=stock price, avg daily volume, ATR or some similar to ID high momo // fine=volume in past 180 minutes (or such), no halts in last 2 hours # with limit orders I want to adjust them by 1-2cts if they're not filled to ensure fills quickly before price declines fast, and since I'm not using market orders