# MACD CROSSOVER STRATEGY (testing latency and capabilities of system, not real strat)
# 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 on 1 minute chart
# 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(2020, 1, 1)
self.SetEndDate(2020, 1, 31)
self.SetCash(100000)
self.AddEquity("SPY", Resolution.Minute)
# self.SetDataNormalizationMode(DataNormalizationMode.Raw) # ***DO WE NEED THIS? I THINK MAYBE FOR TICK BY TICK DATA TO FORM CHARTS?
self.SetWarmUp(500) # Warm up using 500 minute bars for all subscribed data
# defines minute macd(3,6) with a 9 minute signal
self.__macd = self.MACD("SPY", 3, 6, 9, MovingAverageType.Exponential, Resolution.Minute)
self.__previous = datetime.min
self.PlotIndicator("MACD", True, self.__macd, self.__macd.Signal) # ***NEED 1MIN CHART & SEE IT MOVING TICK BY TICK (LOWEST TIMEFRAME NOW IS 10MIN); 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)' [SOURCE: https://www.quantconnect.com/docs/algorithm-reference/charting]
self.PlotIndicator("SPY", self.__macd.Fast, self.__macd.Slow)
# NEED TO IMPORT PLOTTING PROGRAM?
# defines entry + SP + 2 TP orders
self.entryTicket = None
self.stopLimitTicket = 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)
self.highValues = RollingWindow[float](5)
# 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
# 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
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
# ***I THINK BELOW NEEDS TO HAVE END FROM "/" REMOVED AS IT'S NOT WORKING BUT WHEN I REMOVE IT IT DOES WORK, HOWEVER IT GOES SHORT TOO=WHAT CODE DOES THIS??
signalDeltaPercent = (self.__macd.Current.Value - self.__macd.Signal.Current.Value)/self.__macd.Fast.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
# about quantity below: 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
quantity = self.CalculateOrderQuantity("SPY", 0.95) # FORMERLY USED self.SetHoldings("SPY", 0.95)
self.entryTicket = self.LimitOrder("SPY", 1, self.LastPrice + 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"???
# of our macd is less than our signal, then let's go short
elif holdings >= 0 and signalDeltaPercent < -tolerance:
self.Liquidate("SPY") # ***NEED TO CHANGE THIS TO REVERSE POSITION FROM LONG TO SHORT, NOT JUST LIQUIDATE LONG, ESPEC B/C ORDER TICKET CREATED WILL BE TRACKED BY SL/TP - SO WE NEED 2 ORDERS: (1) TAKING LONG POSITION TO ZERO, THEN (2) TAKING THE NEGATIVE OF THE LONG TRADE ORDER
## BELOW IS THE CODE THAT WOULD REPLACE ABOVE BEGINNING AT "IF HOLDINGS...."
#if self.macd.IsReady:
# if self.Portfolio[self.syl].Quantity == 0 and self.macd.Current.Value > self.macd.Signal.Current.Value: # NOTE THIS SEEMS TO GO LONG WHEN MACD IS ABOVE SIGNAL, NOT WHEN IT FIRST CROSSES ABOVE IT
## self.Buy(self.syl,100)
# # <1> if there is a MACD short signal, liquidate the stock
# elif self.Portfolio[self.syl].Quantity > 0 and self.macd.Current.Value < self.macd.Signal.Current.Value:
# self.Liquidate()
# # <2> if today's close < lowest close of last 30 days, liquidate the stock
# history = self.History([self.syl], 30, Resolution.Daily).loc[self.syl]['close']
# self.Plot('Stock Plot','stop loss frontier', min(history))
# if self.Portfolio[self.syl].Quantity > 0:
# if self.Securities[self.syl].Price < min(history):
# self.Liquidate()
# <3> if there is a MACD short signal, trade the options
# elif self.Portfolio[self.syl].Quantity > 0 and self.macd.Current.Value < self.macd.Signal.Current.Value:
# try:
# if self.Portfolio[self.syl].Invested and not self.Portfolio[self.contract].Invested \
# and self.Time.hour != 0 and self.Time.minute == 1:
# self.SellCall()
# except:
# if self.Portfolio[self.syl].Invested and self.Time.hour != 0 and self.Time.minute == 1:
# self.SellCall()
## END OF CODE - CAN FIND IT AT THIS POST: https://www.quantconnect.com/forum/discussion/2894/the-options-trading-strategy-based-on-macd-indicator/p1
# NEED CODE FOR THIS: no buy orders to be submitted within 30 minutes of the close
## self.buyToOpenPrice = data["SPY"].LastPrice + 0.02
## self.stopLossPrice = NEED CODE FOR THIS
## self.profitTarget1a = NEED CODE FOR THIS
## self.profitTarget1b = NEED CODE FOR THIS
## self.profitTarget2 = NEED CODE FOR THE FIRST OF "EITHER"[X] or self.buyToOpenPrice + 1.11
# ROLLING WINDOWS: 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)
# WHERE AND HOW TO HAVE ALGO UPDATE THE STOP LOSS ORDER TWICE & UPDATE PROFIT TARGETS WHEN CONDITIONS ARE MET? NOTE WHEN MOVING STOP AFTER 3 BARS POST ENTRY HAVE CLOSED I THINK NEED TO USE A ROLLING WINDOW
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):
# verify whether the order event refers to a filled order with the TP or SL order. If our TP or SL was filled, cancel the other(s). CRITICAL!
# REFERENCE https://www.quantconnect.com/docs/algorithm-reference/trading-and-orders#Trading-and-Orders-Limit-Orders
if orderevent.Status != OrderStatus.Filled:
return
# Now submit the sell stop limit and profit target limit orders (algo was already told to cancel whichever was not filled so no need to do that again)
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
self.stopPrice = 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 = 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, stopPrice, limitPrice)
# 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.profit2Ticket = self.LimitOrder("SPY",-.5,self.profitTarget2)
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.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.LastPrice + .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()
# 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