| Overall Statistics |
|
Total Trades 12 Average Win 0.01% Average Loss -0.02% Compounding Annual Return -0.197% Drawdown 0.100% Expectancy -0.549 Net Profit -0.080% Sharpe Ratio -2.106 Probabilistic Sharpe Ratio 0.040% Loss Rate 67% Win Rate 33% Profit-Loss Ratio 0.35 Alpha -0.002 Beta -0 Annual Standard Deviation 0.001 Annual Variance 0 Information Ratio -2.504 Tracking Error 0.144 Treynor Ratio 4.211 Total Fees $12.00 Estimated Strategy Capacity $11000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X |
class LogicalLightBrownDolphin(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 1, 1)
self.SetCash(100000)
equity = self.AddEquity("SPY", Resolution.Minute)
self.spy = equity.Symbol
# define our 30 minute trade bar consolidator. we can
# access the 30 minute bar from the DataConsolidated events
thirtyMinuteConsolidator = TradeBarConsolidator(timedelta(minutes=30))
# attach our event handler. the event handler is a function that will
# be called each time we produce a new consolidated piece of data.
thirtyMinuteConsolidator.DataConsolidated += self.ThirtyMinuteBarHandler
# this call adds our 30 minute consolidator to
# the manager to receive updates from the engine
#self.SubscriptionManager.AddConsolidator("SPY", thirtyMinuteConsolidator)
# Indicators
self.rsi = self.RSI("SPY", 14)
self.ema200 = self.EMA("SPY", 200)
self.sto = self.STO("SPY", 14, 14, 3)
self.atr = self.ATR(self.spy, 14)
# register indicators with consolidator so they are updated on the same time frame
self.RegisterIndicator(self.spy, self.rsi, thirtyMinuteConsolidator)
self.RegisterIndicator(self.spy, self.ema200, thirtyMinuteConsolidator)
self.RegisterIndicator(self.spy, self.sto, thirtyMinuteConsolidator)
self.RegisterIndicator(self.spy, self.atr, thirtyMinuteConsolidator)
# Indicator rolling windows
self.rsiWin = RollingWindow[float](25)
self.priceWin = RollingWindow[float](25)
self.stoKWin = RollingWindow[float](25)
self.stoDWin = RollingWindow[float](25)
self.atrWin = RollingWindow[float](25)
self.ema200Win = RollingWindow[float](25)
# buy flags
self.priceActionTrigger = False
self.rsiTrigger = False
self.holdCount = 0
# orders
self.currentOrder = None
self.stopLossOrder = None
self.takeProfitOrder = None
# This defines a scheduled event which fires self.ClosePositions everyday SPY is trading 1 minute before SPY stops trading
#self.Schedule.On(self.DateRules.EveryDay(self.spy), self.TimeRules.BeforeMarketClose(self.spy, 1), self.ClosePositions)
#overlayPlot = Chart("Overlay Plot")
#overlayPlot.AddSeries(Series("SPY", SeriesType.Line, 0))
#overlayPlot.AddSeries(Series("Buy", SeriesType.Scatter, 0))
#overlayPlot.AddSeries(Series("Sell", SeriesType.Scatter, 0))
#overlayPlot.AddSeries(Series("ema200", SeriesType.Scatter, 0))
#overlayPlot.AddSeries(Series("rsi", SeriesType.Line, 1))
#overlayPlot.AddSeries(Series("sto", SeriesType.Line, 2))
#self.AddChart(overlayPlot)
def OnData(self, data):
''' OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
pass
def ClosePositions(self):
# don't hold any positions over night
if self.Portfolio.Invested:
self.Liquidate()
def ThirtyMinuteBarHandler(self, sender, consolidated):
#if self.Portfolio.Invested:
# if self.holdCount >= 10:
# self.Liquidate()
# self.holdCount = 0
# else:
# self.holdCount += 1
if self.IsEveryoneReady(consolidated) and not self.Portfolio.Invested:
self.Log("Consolidated and ema200 delta: " + str(abs(consolidated.Close - self.ema200.Current.Value)))
#self.Plot("Overlay Plot", "rsi", self.rsi.Current.Value)
#self.Plot("Overlay Plot", "sto", self.sto.Current.Value)
#self.Plot("Overlay Plot", "ema200", self.ema200.Current.Value)
# Check if we're not invested and then put portfolio 100% in the SPY ETF.
# if price is lower then previous 5 candles but on an uptrend,
# but RSI is diverging on a downtrend,
# use Stochastic crossover for entry signal
(isPriceActionSignal, lowest_price_idx) = self.IsPriceActionSignaling(consolidated)
if isPriceActionSignal or self.priceActionTrigger and lowest_price_idx > -1:
#self.Log("Price Action Signaling")
self.priceActionTrigger = True
if self.IsRSI_Signaling(lowest_price_idx):
#self.Log("RSI Signaling")
self.rsiTrigger = True
#self.Plot("Overlay Plot", "Buy", consolidated.Close)
if self.priceActionTrigger and self.rsiTrigger and self.IsStoCrossOver():
stopLossPrice = self.FindStopLossPrice()
takeProfitPrice = self.FindTakeProfitPrice(stopLossPrice)
if takeProfitPrice != -1:
self.PlaceOrder(stopLossPrice, takeProfitPrice)
self.priceActionTrigger = False
self.rsiTrigger = False
return
else:
return
def OnOrderEvent(self, orderEvent):
# ignore events that are not closed
if orderEvent.Status != OrderStatus.Filled:
return
# sanity check
if self.takeProfitOrder == None or self.stopLossOrder == None:
return
filledOrderId = orderEvent.OrderId
# if takeProfitOrder was filled, cancel stopLossOrder
if self.takeProfitOrder.OrderId == filledOrderId and orderEvent.Status == OrderStatus.Filled:
self.stopLossOrder.Cancel()
# if stopLossOrder was filled, cancel takeProfitOrder
if self.stopLossOrder.OrderId == filledOrderId and orderEvent.Status == OrderStatus.Filled:
self.takeProfitOrder.Cancel()
def PlaceOrder(self, stopLossPrice, takeProfitPrice):
self.Log("Placing Order")
self.Log("Current Order Price: " + str(self.priceWin[0]))
self.Log("Stop Loss Price: " + str(stopLossPrice))
self.Log("Take Profit Price: " + str(takeProfitPrice))
self.Log("ATR: " + str(self.atrWin[0]))
order_quantity = 50
# place order
self.currentOrder = self.MarketOrder(self.spy, order_quantity)
# create stop loss order
self.stopLossOrder = self.StopMarketOrder(self.spy, -order_quantity, stopLossPrice)
# create take profit order
self.takeProfitOrder = self.LimitOrder(self.spy, -order_quantity, takeProfitPrice)
def FindStopLossPrice(self):
#return self.priceWin[0] - (multiple * self.atrWin[0])
lowestPrice = self.priceWin[0]
# stop loss is the most recent swing low point
for i in range(5):
# making the assumption that the most recent bottom is in the last 10 time units
if self.priceWin[i] < lowestPrice:
lowestPrice = self.priceWin[i]
if lowestPrice == self.priceWin[0]:
multiple = 2
lowestPrice = lowestPrice - (self.atrWin[0] * multiple)
return lowestPrice
def FindTakeProfitPrice(self, stopLossPrice):
#return (multiple * self.atrWin[0]) + self.priceWin[0]
takeProfitPrice = -1
# we aim for a profit to loss ratio of 2:1
currentPrice = self.priceWin[0]
if stopLossPrice != currentPrice:
delta = currentPrice - stopLossPrice
takeProfitPrice = (2 * delta) + currentPrice
return takeProfitPrice
# has stochastic crossover occurred in the last 10 time units
def IsStoCrossOver(self):
return self.stoKWin[0] < self.stoDWin[0] and self.stoKWin[10] > self.stoDWin[10]
# is rsi on a downtrend
def IsRSI_Signaling(self, lowest_price_idx):
lowest_rsi = self.rsiWin[lowest_price_idx]
return lowest_rsi > self.rsiWin[0]
def IsPriceActionSignaling(self, consolidated):
# price must be above 200 ema
if consolidated.Close > self.ema200.Current.Value:
self.Log("Price Action is signaling")
self.Log("Consolidated Price: " + str(consolidated.Close))
self.Log("ema200 Price: " + str(self.ema200.Current.Value))
# price must be lower then previous 5 closes
priceBottom = True
for i in range(5):
if consolidated.Close > self.priceWin[i]:
priceBottom = False
# price must be higher then previous low
lowestLow = min(self.priceWin)
idx = list(self.priceWin).index(lowestLow)
if priceBottom and lowestLow < consolidated.Close:
return (True, idx)
return (False, -1)
def IsEveryoneReady(self, consolidated):
# Are indicators ready?
if self.rsi.IsReady and self.sto.IsReady:
# if indicators are ready, create rolling window for them
self.rsiWin.Add(self.rsi.Current.Value)
self.priceWin.Add(consolidated.Close)
self.stoKWin.Add(self.sto.StochK.Current.Value)
self.stoDWin.Add(self.sto.StochD.Current.Value)
self.atrWin.Add(self.atr.Current.Value)
self.ema200Win.Add(self.ema200.Current.Value)
if self.rsiWin.IsReady and self.priceWin.IsReady:
return True and self.ema200.IsReady
return False