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