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 0 Tracking Error 0 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset |
#region imports from AlgorithmImports import * from System.Drawing import Color #endregion class Benchmark: def __init__(self, algo, underlying, shares = 100, indicators = {}): self.algo = algo self.underlying = underlying self.tradingChart = Chart('Trade Plot') self.sqz = Chart('Squeeze') self.sqz.AddSeries(Series('No SQZ', SeriesType.Scatter, '', Color.Green, ScatterMarkerSymbol.Circle)) self.sqz.AddSeries(Series('Low SQZ', SeriesType.Scatter, '', Color.Black, ScatterMarkerSymbol.Circle)) self.sqz.AddSeries(Series('Mid SQZ', SeriesType.Scatter, '', Color.Red, ScatterMarkerSymbol.Circle)) self.sqz.AddSeries(Series('High SQZ', SeriesType.Scatter, '', Color.Orange, ScatterMarkerSymbol.Circle)) self.sqz.AddSeries(Series('UP Bull MOM', SeriesType.Bar, '', Color.Aqua)) self.sqz.AddSeries(Series('DOWN Bull MOM', SeriesType.Bar, '', Color.Blue)) self.sqz.AddSeries(Series('DOWN Bear MOM', SeriesType.Bar, '', Color.Red)) self.sqz.AddSeries(Series('UP Bear MOM', SeriesType.Bar, '', Color.Yellow)) self.algo.AddChart(self.sqz) self.tradingChart.AddSeries(Series('Price', SeriesType.Line, '$', Color.White)) self.algo.AddChart(self.tradingChart) self.AddIndicators(indicators) self.resample = datetime.min self.resamplePeriod = (self.algo.EndDate - self.algo.StartDate) / 2000 def AddIndicators(self, indicators): self.indicators = indicators for name, _i in indicators.items(): self.algo.AddChart(Chart(name)) def PrintBenchmark(self): if self.algo.Time <= self.resample: return self.resample = self.algo.Time + self.resamplePeriod self.__PrintIndicators() def __PrintIndicators(self): ''' Prints the indicators array values to the Trade Plot chart. ''' for name, indicator in self.indicators.items(): if name == 'Squeeze': self.__PlotSqueeze(indicator) else: self.algo.PlotIndicator(name, indicator) def __PlotSqueeze(self, indicator): if indicator.MomentumHistogramColor() == Color.Aqua: self.algo.Plot('Squeeze', 'UP Bull MOM', indicator.Current.Value) elif indicator.MomentumHistogramColor() == Color.Blue: self.algo.Plot('Squeeze', 'DOWN Bull MOM', indicator.Current.Value) elif indicator.MomentumHistogramColor() == Color.Red: self.algo.Plot('Squeeze', 'DOWN Bear MOM', indicator.Current.Value) elif indicator.MomentumHistogramColor() == Color.Yellow: self.algo.Plot('Squeeze', 'UP Bear MOM', indicator.Current.Value) if indicator.Squeeze == 0: self.algo.Plot('Squeeze', 'No SQZ', 0) elif indicator.Squeeze == 1: self.algo.Plot('Squeeze', 'Low SQZ', 0) elif indicator.Squeeze == 2: self.algo.Plot('Squeeze', 'Mid SQZ', 0) elif indicator.Squeeze == 3: self.algo.Plot('Squeeze', 'High SQZ', 0)
#region imports from AlgorithmImports import * #endregion class MarketHours: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.hours = algorithm.Securities[symbol].Exchange.Hours def get_CurrentOpen(self): return self.hours.GetNextMarketOpen(self.algorithm.Time, False) def get_CurrentClose(self): return self.hours.GetNextMarketClose(self.get_CurrentOpen(), False)
#region imports from AlgorithmImports import * from TTMSqueezePro import TTMSqueezePro from MarketHours import MarketHours # endregion class SqueezeAlphaModel(AlphaModel): algorithm = None def __init__(self, algorithm, ticker, option): self.ticker = ticker self.option = option self.algorithm = algorithm self.symbol = algorithm.AddEquity(self.ticker, resolution = Resolution.Daily) self.marketHours = MarketHours(self.algorithm, self.ticker) # indicators self.squeeze = TTMSqueezePro("Squeeze", length = 20) algorithm.RegisterIndicator(self.ticker, self.squeeze, Resolution.Daily) # history = self.algorithm.History(self.symbol.Symbol, 21, Resolution.Daily) # self.squeeze.Warmup(history.loc[self.ticker]) # TODO: think about warming up the indicator: https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/manual-indicators # self.algorithm.SetWarmUp(TimeSpan.FromDays(60)) self.algorithm.WarmUpIndicator(self.ticker, self.squeeze, Resolution.Daily) self.indicators = { 'Squeeze' : self.squeeze } self.algorithm.benchmark.AddIndicators(self.indicators) def Update(self, algorithm, data): insights = [] if self.ticker not in data.Keys: return insights if algorithm.IsWarmingUp: return insights if not self.squeeze.IsReady: return insights # reset your indicators when splits and dividends occur. # If a split or dividend occurs, the data in your indicators becomes invalid because it doesn't account for the price adjustments that the split or dividend causes. if data.Splits.ContainsKey(self.ticker) or data.Dividends.ContainsKey(self.ticker): # Reset the indicator self.squeeze.Reset() self.algorithm.Log("Squeeze: {}".format(self.squeeze.Current.Value)) self.algorithm.benchmark.PrintBenchmark() return insights
#region imports from AlgorithmImports import * from collections import deque from scipy import stats import talib from numpy import mean, array #endregion # Good indicator template: https://www.quantconnect.com/forum/discussion/12691/python-indicator-template/p1 # I did not use it here but it should derive from. # Use like this: # # self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol # NOT SURE WHERE TO PUT THE EXTRA PARAMETERS LIKE SECURITY AND ALGORITHM§ # 1. in this first try i'm sending the algorithm when creating it. # self.squeeze = TTMSqueezePro("squeeze", 21) # self.RegisterIndicator(self.spy, self.squeeze, Resolution.Daily) # history = self.History(self.spy, 21, Resolution.Daily) # self.squeeze.Warmup(history) # TODO: there is a problem with the values being one day late. I'm not sure if it's because of how we close it but it might be the daily period. class TTMSqueezePro(PythonIndicator): Squeeze = 0 # like value this is the second part of the indicator. # //BOLLINGER BANDS BB_mult = 2.0 # input.float(2.0, "Bollinger Band STD Multiplier") # //KELTNER CHANNELS KC_mult_high = 1.0 # input.float(1.0, "Keltner Channel #1") KC_mult_mid = 1.5 # input.float(1.5, "Keltner Channel #2") KC_mult_low = 2.0 # input.float(2.0, "Keltner Channel #3") SQZ_COLORS = ['green', 'black', 'red', 'orange'] # according to this example we don't need to pass security and algo as that # will be passed with RegisterIndicator. # https://github.com/QuantConnect/Lean/blob/b9d3d999170f385e2371fc3fef1823f2ddd4c83b/Algorithm.Python/CustomWarmUpPeriodIndicatorAlgorithm.py def __init__(self, name, length = 20): # default indicator definition super().__init__() self.Name = name self.Value = 0 self.Time = datetime.min self.Squeeze = 0 # set automatic warmup period self.WarmUpPeriod = length * 3 self.length = length self.queue = deque(maxlen=self.length) self.queueMean = deque(maxlen=self.length) self.queueSqz = deque(maxlen=self.length) # define the base indicators to use self.BB = BollingerBands(self.length, self.BB_mult) self.KCHigh = KeltnerChannels(self.length, self.KC_mult_high) self.KCMid = KeltnerChannels(self.length, self.KC_mult_mid) self.KCLow = KeltnerChannels(self.length, self.KC_mult_low) self.MAX = Maximum(self.length) self.MIN = Minimum(self.length) self.SMA = SimpleMovingAverage('SMA', self.length) @property def IsReady(self) -> bool: # it's ready when: # - we have enough data to calculate the momentum oscilator value # - we have enough data to calculate the squeeze data return (len(self.queueMean) >= self.length) and self.BB.IsReady and self.KCHigh.IsReady and self.KCMid.IsReady and self.KCLow.IsReady def ManualUpdate(self, input) -> bool: self.Update(input) if self.IsReady: self.Current = IndicatorDataPoint(input.Symbol, input.Time, self.Value) return self.IsReady def Update(self, input) -> bool: # update all the indicators with the new data dataPoint = IndicatorDataPoint(input.Symbol, input.Time, input.Close) self.BB.Update(dataPoint) self.SMA.Update(dataPoint) # Feed into the Max and Min indicators the highest and lowest values only self.MAX.Update(IndicatorDataPoint(input.Symbol, input.Time, input.High)) self.MIN.Update(IndicatorDataPoint(input.Symbol, input.Time, input.Low)) # Keltner channels indicators self.KCHigh.Update(input) self.KCMid.Update(input) self.KCLow.Update(input) # Calculate the mom oscillator only after we get the proper amount of values for the array. if len(self.queueMean) >= self.length: self.Time = input.Time self.Value = self.MomOscillator() # self.Current = IndicatorDataPoint(input.Symbol, input.Time, self.Value) # self.OnUpdated(self.Current) data = IndicatorDataPoint(input.Symbol, input.Time, self.Value) if len(self.queue) > 0 and data.Time == self.queue[0].Time: self.queue[0] = data else: self.queue.appendleft(data) # calculate momentum oscilator. if self.MAX.IsReady and self.MIN.IsReady and self.SMA.IsReady: data = IndicatorDataPoint(input.Symbol, input.Time, self.MeanPrice(input.Close)) if len(self.queueMean) > 0 and data.Time == self.queueMean[0].Time: self.queueMean[0] = data else: self.queueMean.appendleft(data) # Add the value and sqz status to a queue so we can check later if # we switched status recently. if self.BB.IsReady and self.KCHigh.IsReady and self.KCMid.IsReady and self.KCLow.IsReady: self.Squeeze = self.SqueezeValue() data = IndicatorDataPoint(input.Symbol, input.Time, self.Squeeze) if len(self.queueSqz) > 0 and data.Time == self.queueSqz[0].Time: self.queueSqz[0] = data else: self.queueSqz.appendleft(data) return self.IsReady def KC_basis(self): return self.sma.Current.Value def BB_basis(self): # return self.sma.Current.Value return self.BB.MiddleBand.Current.Value def BB_upper(self): return self.BB.UpperBand.Current.Value def BB_lower(self): return self.BB.LowerBand.Current.Value def KC_upper_high(self): return self.KCHigh.UpperBand.Current.Value def KC_lower_high(self): return self.KCHigh.LowerBand.Current.Value def KC_upper_mid(self): return self.KCMid.UpperBand.Current.Value def KC_lower_mid(self): return self.KCMid.LowerBand.Current.Value def KC_upper_low(self): return self.KCLow.UpperBand.Current.Value def KC_lower_low(self): return self.KCLow.LowerBand.Current.Value # //SQUEEZE CONDITIONS def NoSqz(self): return self.BB_lower() < self.KC_lower_low() or self.BB_upper() > self.KC_upper_low() # NO SQUEEZE: GREEN def LowSqz(self): return self.BB_lower() >= self.KC_lower_low() or self.BB_upper() <= self.KC_upper_low() # LOW COMPRESSION: BLACK def MidSqz(self): return self.BB_lower() >= self.KC_lower_mid() or self.BB_upper() <= self.KC_upper_mid() # MID COMPRESSION: RED def HighSqz(self): return self.BB_lower() >= self.KC_lower_high() or self.BB_upper() <= self.KC_upper_high() # HIGH COMPRESSION: ORANGE # //SQUEEZE DOTS COLOR # sq_color = HighSqz ? color.new(color.orange, 0) : MidSqz ? color.new(color.red, 0) : LowSqz ? color.new(color.black, 0) : color.new(color.green, 0) def SqueezeColor(self): if self.HighSqz(): return 'orange' elif self.MidSqz(): return 'red' elif self.LowSqz(): return 'black' else: return 'green' def SqueezeValue(self): return self.SQZ_COLORS.index(self.SqueezeColor()) def MomentumHistogramColor(self): bullish = Color.Aqua if self.queue[0].Value > self.queue[1].Value else Color.Blue bearish = Color.Red if self.queue[0].Value < self.queue[1].Value else Color.Yellow return bullish if self.Bullish() else bearish def Bullish(self): return self.queue[0].Value > 0 def Bearish(self): return self.queue[0].Value <= 0 # This calculates the mean price value that we'll add to a series type/array and we'll use to # calculate the momentum oscilator (aka linear regression) def MeanPrice(self, price): return price - mean([mean([self.MAX.Current.Value, self.MIN.Current.Value]), self.SMA.Current.Value]) def LosingMomentum(self, maxDays = 2): # reverse the momentum values slicedQueue = list(self.queue)[:maxDays] # if absolute then we also check the momentum `color` # go over the reversed values and make sure they are decreasing return all(earlier.Value > later.Value for later, earlier in zip(slicedQueue, slicedQueue[1:])) def GainingMomentum(self, maxDays = 2): # reverse the momentum values slicedQueue = list(self.queue)[:maxDays] # if absolute then we also check the momentum `color` # go over the reversed values and make sure they are increasing return all(earlier.Value < later.Value for later, earlier in zip(slicedQueue, slicedQueue[1:])) # It's squeezing if the colors are different than green. def Squeezing(self): current = self.queueSqz[0] return current.Value != self.SQZ_COLORS.index('green') def SqueezeChange(self, toColor = 'green'): earlier = self.queueSqz[1] last = self.queueSqz[0] colorIndex = self.SQZ_COLORS.index(toColor) return last.Value == colorIndex and earlier.Value != last.Value def SqueezeDuration(self, over = 2): # pick last `over` days but today/current value slicedQueue = list(self.queueSqz)[1:over+1] colorIndex = self.SQZ_COLORS.index('green') # go over the reversed values and make sure they are increasing return all(val.Value != colorIndex for val in slicedQueue) def MomOscillator(self): ''' //MOMENTUM OSCILLATOR mom = ta.linreg(close - math.avg(math.avg( ta.highest(high, length), ta.lowest(low, length) ), ta.sma(close, length) ), length, 0) https://www.quantconnect.com/forum/discussion/10168/least-squares-linear-regression/p1/comment-28627 https://www.tradingview.com/pine-script-reference/v4/#fun_linreg // linreg = intercept + slope * (length - 1 - offset) // where length is the y argument, // offset is the z argument, // intercept and slope are the values calculated with the least squares method on source series (x argument). -> linreg(source, length, offset) → series[float] ''' # x = [range(len(self.queueMean))] # y = self.queueMean # slope, intercept = stats.linregress(x, y)[0], stats.linregress(x, y)[1] # linreg = intercept + slope * (self.length - 1) # we need to reverse the queue in order to get the most recent regression series = array([m.Value for m in reversed(self.queueMean)]) size = len(series) # considering we are not taking a shorter regression value we are going to get the last value # of the returned array as that is where the linar regression value sits the rest are `nan` linreg = talib.LINEARREG(series, size)[size - 1] return linreg def Warmup(self, history): for index, row in history.iterrows(): self.Update(row)
#region imports from AlgorithmImports import * from TTMSqueezePro import TTMSqueezePro from SqueezeAlphaModel import SqueezeAlphaModel from Benchmark import Benchmark # endregion class AddAlphaModelAlgorithm(QCAlgorithm): def Initialize(self): ''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' # self.SetStartDate(2012, 2, 4) # Set Start Date # self.SetEndDate(2020, 31, 7) # Set End Date self.SetStartDate(2021, 7, 1) # Set Start Date self.SetEndDate(2021, 9, 1) # Set End Date self.SetCash(100_000) # Set Strategy Cash # Set settings and account setup self.UniverseSettings.Resolution = Resolution.Daily self.UniverseSettings.FillForward = False self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) # Set InteractiveBrokers Brokerage model # Main variables self.ticker = "TSLA" self.benchmark = Benchmark(self, self.ticker) self.option = Symbol.Create(self.ticker, SecurityType.Option, Market.USA, f"?{self.ticker}") # Squeeze model self.SetAlpha(SqueezeAlphaModel(self, self.ticker, self.option))