Hello all, On TradingView there is a popular coder by the name of Lazy Bear. He made a script in Pine Script that he calls the “Squeeze Momentum Indicator [LazyBear]”. The code for that is open source so I thought it would be fun to try and replicate it in python. This is what I came up with. It is not a perfect 1:1 because my linear regression does not return a list. Instead of checking the values in a list from linear regression against each other I look at the strength of the linear regression. We see in the code that I define that strength as a value greater then 2 or less than -2 to trade. Super excited to see what you guys think of this. I haven't even run a back test with this code trading yet. Hope you enjoy, open to any suggestions and criticisms. 

 

Link to google doc from Lazy Bear with all of his indicators: Master List of all my indicators - Google Docs 

Link to him on TradingView: Trader LazyBear — Trading Ideas & Charts — TradingView

Link to this indicator specifically: Squeeze Momentum Indicator [LazyBear] by LazyBear — TradingView 

 

# region imports
from AlgorithmImports import *
import numpy as np
from sklearn.linear_model import LinearRegression
# endregion

class AdaptableFluorescentPinkBee(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)  # Set Start Date
        self.SetEndDate(2023,1,1)
        self.SetCash(100000)  # Set Strategy Cash

        self.futureSP500 = self.AddFuture(Futures.Indices.SP500EMini, extendedMarketHours= True)
        self.futureSP500.SetFilter(timedelta(0), timedelta(182))
        self.symbol = self.futureSP500.Symbol
 
        seeder = FuncSecuritySeeder(self.GetLastKnownPrices)
        self.SetSecurityInitializer(lambda security: seeder.SeedSecurity(security))

        self.bb = self.BB(self.symbol, 20, 2)
        self.kch = self.KCH(self.symbol, 20, 1.5)
        #self.sma = SimpleMovingAverage(name, period)

        self.RegisterIndicator(self.symbol, self.kch, Resolution.Minute)
        self.RegisterIndicator(self.symbol, self.bb, Resolution.Minute)

        self.LongcontractSymbol = None
        self.ShortcontractSymbol = None

        self.prices_window = RollingWindow[QuoteBar](20)

        self.prices = []
        self.lins = RollingWindow[float](20)
        self.pylins = []

    def OnData(self, data: Slice):

        if not(data.ContainsKey(self.symbol) and data[self.symbol] is not None):
            return

        if not self.kch.IsReady or not self.bb.IsReady:
            return

        future_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Future]
       
        if future_invested:
            if self.Time + timedelta(1) > future_invested[0].ID.Date:
                self.Liquidate(future_invested[0], "Future too close to expiration.")

        KCmiddle_band = self.kch.MiddleBand.Current.Value
        KCupper_band = self.kch.UpperBand.Current.Value
        KClower_band = self.kch.LowerBand.Current.Value

        BBmiddle_band = self.bb.MiddleBand.Current.Value
        BBupper_band = self.bb.UpperBand.Current.Value
        BBlower_band = self.bb.LowerBand.Current.Value

        ########################################################################################
 
        # Add the current data to the rolling window
        self.prices_window.Add(data[self.symbol])


        # Ensure the rolling window is fully populated before calculating entropy
        if not self.prices_window.IsReady:
            return
   
        self.prices = [bar.Close for bar in self.prices_window]

        avg1 = (max(self.prices) + min(self.prices) + BBmiddle_band) / 3
        avg2 = self.futureSP500.Price - ((avg1 + 20) / 2)

        self.lins.Add(avg2)

        if not self.lins.IsReady:
            return

        self.pylins = [num for num in self.lins]

        ########################################################################################


        sqzOn  = (BBlower_band > KClower_band) and (BBupper_band < KCupper_band)
        sqzOff = (BBlower_band < KClower_band) and (BBupper_band > KCupper_band)
        noSqz  = (sqzOn == False) and (sqzOff == False)

        val = self.LinReg()
        self.Log(val)

        #bcolor = ("lime" if val[-1] > 0 and val[-1] > val[-2] else "green") if val[-1] > 0 else ("red" if val[-1] < val[-2] else "maroon")

        if val > 0:
            if val < 2:
                bcolor = "green"
            elif val > 2:
                bcolor = "lime"
        
        else:
            if val > -2:
                bcolor = "maroon"
            elif val < -2:
                bcolor = "red"

        scolor = 'blue' if noSqz else ('black' if sqzOn else 'gray')

        if bcolor == "lime" and scolor == "grey":
            "long"
        
        elif bcolor == "red" and scolor == "grey":
            "short"

        self.Log(bcolor)
        self.Log(scolor)

    def LinReg(self):
 
        # A is the design matrix
        A = range(20 + 1)
        # response
        Y = self.pylins
        # features
        X = np.column_stack([np.ones(len(A)), A])
    
        # data preparation
        length = min(len(X), len(Y))
        X = X[-length:]
        Y = Y[-length:]
        A = A[-length:]
   
        # fit the linear regression
        reg = LinearRegression().fit(X, Y)
   
        # run linear regression y = ax + b
        b = reg.intercept_
        a = reg.coef_[1]
   
        # store slopes for symbols
        slopes = a/b
        return slopes