Adaptive Market State Indicator for ThinkOrSwim

Sesqui

Member
VIP
Have you ever noticed that extra zing to the price action when trading with the trend? To help better identify the market state the script below modified the Dr Ehlers CorrelationCycleMarketState indicator to make it adaptive to the dominant market cycle using two different methods: the EhlersAutocorrelationPeriodogram (which is not very precise since TOS does not support matrices as needed to fully implement the complete spectral analysis), and the Band-Pass Filter method of measuring the market dominant cycle period which tests against a sinewave across a wide range of periods to provide precise, closely matching cycle lengths. As a result the indicator tells you if the market is trending up, down or is ranging. And it uses the dominant market cycle to do that to ensure there is no question as to whether its length is set right.

The market state is displayed in a simple label on the bottom right side of the price chart. Here is a screenshot of the label made using the periodogram method.

Screenshot from 2025-08-31 11-50-02.png


And here is a screenshot of the label made using the Band-Pass method. This screenshot also shows in the bottom-most study what the market state values were as a function of time as computed via the Band-Pass filter method. The study 2nd from the bottom illustrates the dominant cycle lengths produced by the band-Pass filter method.

1760322574724.png


Here is the script:

CSS:
#Adaptive Market State Label for ThinkOrSwim by Sesqui
# [30AUG2025] - AutocorrelationPeriodogram method which is not precise since TOS does not support matrices needed to fully implemetn the method
# [12OCT2025] - Adds Band-Pass Method of measuring dominant cycle length, which tests against sinewaves to provide very precise cycle lengths

input UseBandPassFilter_DC = yes;
input UseVolatility_Adapter = no;

#===========================================================================================================================
script GetCycle {
    # Returns the dominant market cycle for use in adaptive indicators

    #------------------------------------------
    # Charles Schwab & Co. (c) 2016-2025
    #
    def lag = 48;
    def x = EhlersRoofingFilter("cutoff length" = 8, "roof cutoff length" = 48);
    def cosinePart = fold i = 3 to 48 with cosPart do cosPart + (3 * (x * GetValue(x, i) + GetValue(x, 1) * GetValue(x, i + 1) + GetValue(x, 2) * GetValue(x, i + 2)) - (x + GetValue(x, 1) + GetValue(x, 2)) * (GetValue(x, i) + GetValue(x, i + 1) + GetValue(x, i + 2))) / Sqrt((3 * (x * x + GetValue(x, 1) * GetValue(x, 1) + GetValue(x, 2) * GetValue(x, 2)) - Sqr(x + GetValue(x, 1) + GetValue(x, 2))) * (3 * (GetValue(x, i) * GetValue(x, i) +  GetValue(x, i + 1) * GetValue(x, i + 1) + GetValue(x, i + 2) * GetValue(x, i + 2)) - Sqr(GetValue(x, i) + GetValue(x, i + 1) + GetValue(x, i + 2)))) * Cos(2 * Double.Pi * i / lag);
    def sinePart = fold j = 3 to 48 with sinPart do sinPart + (3 * (x * GetValue(x, j) + GetValue(x, 1) * GetValue(x, j + 1) + GetValue(x, 2) * GetValue(x, j + 2)) - (x + GetValue(x, 1) + GetValue(x, 2)) * (GetValue(x, j) + GetValue(x, j + 1) + GetValue(x, j + 2))) / Sqrt((3 * (x * x + GetValue(x, 1) * GetValue(x, 1) + GetValue(x, 2) * GetValue(x, 2)) - Sqr(x + GetValue(x, 1) + GetValue(x, 2))) * (3 * (GetValue(x, j) * GetValue(x, j) +  GetValue(x, j + 1) * GetValue(x, j + 1) + GetValue(x, j + 2) * GetValue(x, j + 2)) - Sqr(GetValue(x, j) + GetValue(x, j + 1) + GetValue(x, j + 2)))) * Sin(2 * Double.Pi * j / lag);
    def sqSum = Sqr(cosinePart) + Sqr(sinePart);

    plot Cycle = ExpAverage(sqSum, 9);
    #-------------------------------------------
}# end Script GetCycle{}

script getStdDeviation {
    input Cycle = 10;
    input source = close;

    def CycleLength = if IsNaN(Floor(Cycle)) then CycleLength[1] else Floor(Cycle);
    def sum = fold i = 0 to CycleLength with s = 0 do s + GetValue(source, i);
    def x_bar = sum / CycleLength;
    def SumDeviationSquared = fold j = 0 to CycleLength with sd = 0 do sd + Sqr(GetValue(source, j) - x_bar);
    def avgDev = SumDeviationSquared / CycleLength;
    def Sigma = Sqrt(avgDev);

    plot StdDev = Sigma;
}#end Script getStdDeviation{}

script GetAvg {
    input Cycle = 10;
    input source = close;

    def CycleLength = if IsNaN(Floor(Cycle)) then CycleLength[1] else Floor(Cycle);
    def sum = fold i = 0 to CycleLength with s = 0 do s + GetValue(source, i);

    plot AvgDev = sum / CycleLength;

}# End Script AgetAvg{}
#======================================================================================================

## Script to determine the length needed to adapt to market volatility
script AdaptLengthToVolatility {
    # *** Computes the Efficiency Ratio ***
    def effRatioLength = 10;
    def fastLength = 2;
    def slowLength = 30;

    def ER = AbsValue((close - Lowest(low, effRatioLength)) - (Highest(high, effRatioLength) - close)) / (Highest(high, effRatioLength) - Lowest(low, effRatioLength));


    def FastSF = 2 / (fastLength + 1);
    def SlowSF = 2 / (slowLength + 1);
    def ScaledSF = ER * (FastSF - SlowSF) + SlowSF;

    # *** Determines the Length ***
    # for an EMA: alpha = 1/ (L+1); where L is the lag
    # Using this expression and solving for Lag:
    # L = 2/alpha -1; let ScaledSF = alpha, and solve for the
    # value of lag, L, to get: L = 2/scaledSF - 1;
    # This Length is returned by the script

    def cutoffLength = 2 / ScaledSF - 1;

    plot Length = cutoffLength;
    Length.HideBubble();
}# End script AdaptLengthToVolatility{}
#===================================================================================
#*** Script to measure dominant cycle length using band-pass
#*** filter zero crossings method per Dr Ehlers
#*** Chapter 5 of "Cycle Analytics for Traders". 
script GetCycleViaBandPassFilter {
    input source = ohlc4;
    input Period = 20;
    input Bandwidth = 0.70;

    def cosPart = Cos(0.25 * Bandwidth * 2.0 * Double.Pi / Period);
    def sinPart = Sin(0.25 * Bandwidth * 2.0 * Double.Pi / Period);

    def alpha2 = (cosPart + sinPart - 1) / cosPart;
    def HP = (1 + alpha2 / 2) * (source - source[1]) + (1 - alpha2) * HP[1];

    def beta1 = Cos(2.0 * Double.Pi / Period);
    def gamma1 = 1 / Cos(2.0 * Double.Pi * Bandwidth / Period);

    def alpha1 = gamma1 - Sqrt(gamma1 * gamma1 - 1);

    def BP = if BarNumber() == 1 or BarNumber() == 2 then 0 else 0.5 * (1 - alpha1) * (HP - HP[2]) + beta1 * (1 + alpha1) * BP[1] - alpha1 * BP[2];

    def Peak = if AbsValue(BP) > 0.991 * Peak[1] then AbsValue(BP) else 0.991 * Peak[1];

    def Real = if Peak <> 0 then BP / Peak else Double.NaN;

    def crossAbove = Real crosses above 0;
    def crossBelow = Real crosses below 0;
    def zeroCrossing = crossAbove or crossBelow;

    # Uses a recursive variable to count the bars between zero-crossings
    # If a zero-crossing happens, resets the count to 0.
    # Otherwise, increments the count by 1.
    def barsSinceCross = if zeroCrossing then 0 else barsSinceCross[1] + 1;
    def barsBetweenCrossings = barsSinceCross + 1;
    def val = if barsBetweenCrossings < barsBetweenCrossings[1] then 2 * barsBetweenCrossings[1] else val[1];

    # The following line is a port of Dr Ehlers EasyLanguage program, kept here for reference
    #def DC = If barsBetweenCrossings < 3 then 3 else If 2*(barsBetweenCrossings) > 1.25*DC[1] then 1.25*DC[1] else if 2*(barsBetweenCrossings) < 0.8*DC[1] then 0.8*DC[1] else DC[1];

    plot DC = val;
    DC.HideBubble();

} # End script GetCycleViaBandPassFilter{}
#-----------------------------------------------------------
script GetMarketState {
    input limit = 20;

    def sx = fold ii = 0 to limit with S do S + GetValue(close, ii);
    def sxx = fold jj = 0 to limit with SS do SS + GetValue(Sqr(close), jj);
    def sy1 = fold i1 = 0 to limit with s1 do (s1 + Cos(2 * Double.Pi * i1 / limit));
    def sxy1 = fold i2 = 0 to limit with s2 do (s2 + GetValue(close, i2) * Cos(2 * Double.Pi * i2 / limit));
    def syy1 = fold i3 = 0 to limit with s3 do (s3 + Sqr(Cos(2 * Double.Pi * i3 / limit)));

    def sxx_sx = limit * sxx - Sqr(sx);
    def corrCosine = if sxx_sx == 0 then 0 else (limit * sxy1 - sx * sy1) / Sqrt(sxx_sx * (limit * syy1 - Sqr(sy1)));

    def sy2 = fold j1 = 0 to limit with t1 do (t1 - Sin(2 * Double.Pi * t1 / limit));
    def sxy2 = fold j2 = 0 to limit with t2 do (t2 - GetValue(close, j2) * Sin(2 * Double.Pi * j2 / limit));
    def syy2 = fold j3 = 0 to limit with t3 do (t3 + Sqr(Sin(2 * Double.Pi * j3 / limit)));

    def corrNegSine = if sxx_sx == 0 then 0 else (limit * sxy2 - sx * sy2) / Sqrt(sxx_sx * (limit * syy2 - Sqr(sy2)));

    def CorrelationWithCosine = corrCosine;
    def CorrelationWithNegativeSine = corrNegSine;

    # CorrelationCycleAngle Indicator made adaptive

    def real = CorrelationWithCosine;
    def imag = CorrelationWithNegativeSine;

    def angleRad = if imag != 0 then Double.Pi / 2 + ATan(real / imag) - (if imag > 0 then Double.Pi else 0) else 0;
    def angle = 180 / Double.Pi * angleRad;
    def positiveAngle = if IsNaN(positiveAngle[1]) then 0 else if positiveAngle[1] - angle < 270 and angle < positiveAngle[1] then positiveAngle[1] else angle;

    def CorrelationAngle = positiveAngle;

    #----MarketState indicator modified to make it adpative----

    #
    # Charles Schwab & Co. (c) 2008-2025
    #

    input trendLength = 40;
    def trendAngle = 360 / trendLength;

    def MarketState;
    if (AbsValue(angle - angle[1]) < trendAngle and angle < 0) {
        MarketState = -1;
    } else if (AbsValue(angle - angle[1]) < trendAngle and angle >= 0) {
        MarketState = 1;
    } else {
        MarketState = 0;
    }

    plot State = MarketState;
    State.HideBubble();

} # End Script GetMarketState{}
#================================================================================
# Makes the CorrelationCycle Indicator Adaptable
# Modified by implementing nested folds to enable adaptive indicator

input BandPassSource = ohlc4;
input BandPassPeriod = 20;
input BandPassBandwidth = 0.70;

#input UseBandPassFilter_DC = yes;
#input UseVolatility_Adapter = no;

def Length = If UseBandPassFilter_DC then GetCycleViaBandPassFilter(BandPassSource, BandPassPeriod, BandPassBandwidth).DC else AdaptLengthToVolatility().Length;

def limit = if IsNaN(Length) then limit[1] else Floor(Length);

def MarketState = GetMarketState(Limit).State;
#MarketState.SetLineWeight(3);
#MarketState.AssignValueColor(Color.WHITE);

# Label for Market State----------------------------
AddLabel(yes, if MarketState == 1 then "Adaptive: Trending UP" else if MarketState == -1 then "Adaptive: Trending DOWN" else "Adaptive: RANGING", if MarketState == 1 then Color.GREEN else if MarketState == -1 then Color.RED else CreateColor(255, 255, 204), Location.BOTTOM_RIGHT, FontSize.SMALL);


Note: Articles from Dr Ehlers use arrays to normalize the periodogram data and then a center of gravity method to pin point the dominant cycle. However, thinkscript does not support arrays as needed to do the drill down further from the periodogram to the precise dominant cycle, per se. However, the cycle value provided by the Band-Pass Filter method provided in the for TOS code above when tested against a sinewave proved to be very precise. Here are a couple screenshots capturing the test results.

The red line is the Dominant Cycle length measured by the zero crossings of the band-pass filter. Zero crossings of the yellow line are watched and the number of candles between crossings are counted and multiplied by two to get the measured value of the dominant cycle length. The yellow line was made by the band-pass filter. The Cyan line is the SineWave signal of given period length = 8. As shown in the screenshot, the band-pass filter measured the period length correctly getting DC = 8:

1760323455127.png


The and-pass filter exactly matched the sinewave period for each sinewave tested hainvg even period lengths. I tested up to length 40:

1760323559446.png


But because the band-pass filter method is only counting one half of the cycle then multiplying by two, it does not get the correct DC length when the sinewave has a period length that is odd. However, the values that it gets are only off by +/- 1. This was the case for each odd period length value tested.

For sinewave period = 7, the band-pass filter gets DC = 6 or DC = 8, with the correct number being the average of the two:

1760323643162.png


Finally we now have a computationally inexpensive yet precise method for computing the dominant cycle of the market, the Band-Pass filter method. This method is described in more detail in chapter 5 of Dr Ehlers book, "Cycle analytics for traders". Here it has been used to make the Market State indicator adaptive. However, with this tool in your arsenal you can tune any of your indicators to the market dominant cycle. Enjoy!
 
Last edited:
Is the label displaying on screen or? It's hard to tell. I checked the code and it looks to match what I am running. Here is a different variant of the same thing that uses the Homodyne Discriminator method rather than the periodogram to get the dominant cycle.

Perhaps this one will run on your end, fingers crossed:

CSS:
#Adaptive Market State Label R1 using Homdyne Discriminator method for ThinkOrSwim by Sesqui (3SEP2025)
# Uses method of Homdyne Discriminator

#-----------------------------------------------
Script GetPeriod{
    input Period_Prev_Bar = 6;
    input Price = HL2;
    plot Smooth = (4*Price + 3*Price[1] + 2*Price[2] + Price[3])/10;

    plot Detrender = (0.0962*Smooth + 0.5769*Smooth[2] - 0.5769*Smooth[4] - 0.0962*Smooth[6])*(0.075*Period_Prev_Bar + 0.54);
  
    plot Q1 = (0.0962*Detrender + 0.5769*Detrender[2] - 0.5769*Detrender[4] - 0.0962*Detrender[6])*(0.075*Period_Prev_Bar + 0.54);
    plot I1 = Detrender[3];

    # Advance the phase of I1 and Q1 by 90 degrees
    plot jI = (0.0962*I1 + 0.5769*I1[2] - 0.5769*I1[4] - 0.0962*I1[6])*(0.075*Period_Prev_Bar + 0.54);
    plot jQ = (0.0962*Q1 + 0.5769*Q1[2] - 0.5769*Q1[4] - 0.0962*Q1[6])*(0.075*Period_Prev_Bar + 0.54);

    # Phasor addition for 3 bar averaging
    plot I2 = I1 - jQ;
    plot Q2 = Q1 + jI;

    # Smooth the I and Q components before applying the discriminator
    plot I2smooth = 0.2*I2 + 0.8*I2[1];
    plot Q2smooth = 0.2*Q2 + 0.8*Q2[1];

    # Homodyne Discriminator
    plot Re = I2smooth*I2smooth[1] + Q2smooth*Q2smooth[1];
    plot Im = I2smooth*Q2smooth[1] + Q2smooth*I2smooth[1];

    plot Resmooth = 0.2*Re + 0.8*Re[1];
    plot Imsmooth = 0.2*Im + 0.8*Im[1];

    plot P = if Imsmooth <> 0 and Resmooth <> 0 and 360.0/ATan(Imsmooth/Resmooth) < 1.5*Period_Prev_Bar and 360.0/ATan(Imsmooth/Resmooth) > 0.67*Period_Prev_Bar then 360.0/ATan(Imsmooth/Resmooth) else if 360.0/ATan(Imsmooth/Resmooth) > 1.5*Period_Prev_Bar then 1.5*Period_Prev_Bar else if 360.0/ATan(Imsmooth/Resmooth) < 0.67*Period_Prev_Bar then 0.67*Period_Prev_Bar else if 360.0/ATan(Imsmooth/Resmooth) < 6 then 6 else if 360.0/ATan(Imsmooth/Resmooth) > 50 then 50 else 6;


    #plot Psmooth = 0.2*P + 0.8*P[1];
    #def Psmoothed_twice = if !IsNaN(Psmoothed_twice[1]) then 0.33*Psmooth + 0.67*Psmoothed_twice[1] else 0.33*Psmooth;
    #-----------------------------------------
    #Ehlers SuperSmoother
    input cutoffLength = 10;

    def a1 = Exp(-Double.Pi * Sqrt(2) / cutoffLength);
    def coeff2 = 2 * a1 * Cos(Sqrt(2) * Double.Pi / cutoffLength);
    def coeff3 = - Sqr(a1);
    def coeff1 = 1 - coeff2 - coeff3;
    def filt1 = if IsNaN(P + P[1]) then filt1[1] else coeff1 * (P + P[1]) / 2 + coeff2 * filt1[1] + coeff3 * filt1[2];
    def filt2 = if IsNaN(filt1 + filt1[1]) then filt2[1] else coeff1 * (filt1 + filt1[1]) / 2 + coeff2 * filt2[1] + coeff3 * filt2[2];

    plot Period_smoothed = filt2; #Psmoothed_twice; #returns the newly computed smoothed period value 

}# end Script GetPeriod()

#=============================================================================

def Period_ = CompoundValue(4,
                          if BarNumber() > 5 then GetPeriod(Period_[1],HL2).Period_smoothed  else Period_[1],
                          20);

def CycleLength = Period_;

#================================================================================
# Makes the CorrelationCycle Indicator Adaptable
# Modified by implementing nested folds to enable adaptive indicator
def limit = if IsNaN(CycleLength) then limit[1] else Floor(CycleLength);

def sx = fold ii = 0 to limit with S do S + GetValue(close, ii); #Sum(close, length);
def sxx = fold jj = 0 to limit with SS do SS + GetValue(Sqr(close), jj); #Sum(Sqr(close), length);
def sy1 = fold i1 = 0 to limit with s1 do (s1 + Cos(2 * Double.Pi * i1 / limit));
def sxy1 = fold i2 = 0 to limit with s2 do (s2 + GetValue(close, i2) * Cos(2 * Double.Pi * i2 / limit));
def syy1 = fold i3 = 0 to limit with s3 do (s3 + Sqr(Cos(2 * Double.Pi * i3 / limit)));

def sxx_sx = limit * sxx - Sqr(sx);
def corrCosine = if sxx_sx == 0 then 0 else (limit * sxy1 - sx * sy1) / Sqrt(sxx_sx * (limit * syy1 - Sqr(sy1)));

def sy2 = fold j1 = 0 to limit with t1 do (t1 - Sin(2 * Double.Pi * t1 / limit));
def sxy2 = fold j2 = 0 to limit with t2 do (t2 - GetValue(close, j2) * Sin(2 * Double.Pi * j2 / limit));
def syy2 = fold j3 = 0 to limit with t3 do (t3 + Sqr(Sin(2 * Double.Pi * j3 / limit)));

def corrNegSine = if sxx_sx == 0 then 0 else (limit * sxy2 - sx * sy2) / Sqrt(sxx_sx * (limit * syy2 - Sqr(sy2)));

def CorrelationWithCosine = corrCosine;
def CorrelationWithNegativeSine = corrNegSine;

#============================================================
# CorrelationCycleAngle Indicator made adaptive

#
# Charles Schwab & Co. (c) 2008-2025
#

def real = CorrelationWithCosine;
def imag = CorrelationWithNegativeSine;

def angleRad = if imag != 0 then Double.Pi / 2 + ATan(real / imag) - (if imag > 0 then Double.Pi else 0) else 0;
def angle = 180 / Double.Pi * angleRad;
def positiveAngle = if IsNaN(positiveAngle[1]) then 0 else if positiveAngle[1] - angle < 270 and angle < positiveAngle[1] then positiveAngle[1] else angle;

def CorrelationAngle = positiveAngle;

#==============================================================

#----MarketState indicator modified to make it adpative----

#
# Charles Schwab & Co. (c) 2008-2025
#

input trendLength = 40;
def trendAngle = 360 / trendLength;

def MarketState;
if (AbsValue(angle - angle[1]) < trendAngle and angle < 0) {
    MarketState = -1;
} else if (AbsValue(angle - angle[1]) < trendAngle and angle >= 0) {
    MarketState = 1;
} else {
    MarketState = 0;
}

#================================================================
# Label for Market State----------------------------
AddLabel(yes, if MarketState == 1 then "Adaptive r1: Trending UP" else if MarketState == -1 then "Adaptive r1: Trending DOWN" else "Adaptive r1: RANGING", if MarketState == 1 then Color.GREEN else if MarketState == -1 then Color.RED else CreateColor(255, 255, 204), Location.BOTTOM_RIGHT, FontSize.SMALL);
 
Is the label displaying on screen or? It's hard to tell. I checked the code and it looks to match what I am running. Here is a different variant of the same thing that uses the Homodyne Discriminator method rather than the periodogram to get the dominant cycle.

Perhaps this one will run on your end, fingers crossed:

CSS:
#Adaptive Market State Label R1 using Homdyne Discriminator method for ThinkOrSwim by Sesqui (3SEP2025)
# Uses method of Homdyne Discriminator

#-----------------------------------------------
Script GetPeriod{
    input Period_Prev_Bar = 6;
    input Price = HL2;
    plot Smooth = (4*Price + 3*Price[1] + 2*Price[2] + Price[3])/10;

    plot Detrender = (0.0962*Smooth + 0.5769*Smooth[2] - 0.5769*Smooth[4] - 0.0962*Smooth[6])*(0.075*Period_Prev_Bar + 0.54);
 
    plot Q1 = (0.0962*Detrender + 0.5769*Detrender[2] - 0.5769*Detrender[4] - 0.0962*Detrender[6])*(0.075*Period_Prev_Bar + 0.54);
    plot I1 = Detrender[3];

    # Advance the phase of I1 and Q1 by 90 degrees
    plot jI = (0.0962*I1 + 0.5769*I1[2] - 0.5769*I1[4] - 0.0962*I1[6])*(0.075*Period_Prev_Bar + 0.54);
    plot jQ = (0.0962*Q1 + 0.5769*Q1[2] - 0.5769*Q1[4] - 0.0962*Q1[6])*(0.075*Period_Prev_Bar + 0.54);

    # Phasor addition for 3 bar averaging
    plot I2 = I1 - jQ;
    plot Q2 = Q1 + jI;

    # Smooth the I and Q components before applying the discriminator
    plot I2smooth = 0.2*I2 + 0.8*I2[1];
    plot Q2smooth = 0.2*Q2 + 0.8*Q2[1];

    # Homodyne Discriminator
    plot Re = I2smooth*I2smooth[1] + Q2smooth*Q2smooth[1];
    plot Im = I2smooth*Q2smooth[1] + Q2smooth*I2smooth[1];

    plot Resmooth = 0.2*Re + 0.8*Re[1];
    plot Imsmooth = 0.2*Im + 0.8*Im[1];

    plot P = if Imsmooth <> 0 and Resmooth <> 0 and 360.0/ATan(Imsmooth/Resmooth) < 1.5*Period_Prev_Bar and 360.0/ATan(Imsmooth/Resmooth) > 0.67*Period_Prev_Bar then 360.0/ATan(Imsmooth/Resmooth) else if 360.0/ATan(Imsmooth/Resmooth) > 1.5*Period_Prev_Bar then 1.5*Period_Prev_Bar else if 360.0/ATan(Imsmooth/Resmooth) < 0.67*Period_Prev_Bar then 0.67*Period_Prev_Bar else if 360.0/ATan(Imsmooth/Resmooth) < 6 then 6 else if 360.0/ATan(Imsmooth/Resmooth) > 50 then 50 else 6;


    #plot Psmooth = 0.2*P + 0.8*P[1];
    #def Psmoothed_twice = if !IsNaN(Psmoothed_twice[1]) then 0.33*Psmooth + 0.67*Psmoothed_twice[1] else 0.33*Psmooth;
    #-----------------------------------------
    #Ehlers SuperSmoother
    input cutoffLength = 10;

    def a1 = Exp(-Double.Pi * Sqrt(2) / cutoffLength);
    def coeff2 = 2 * a1 * Cos(Sqrt(2) * Double.Pi / cutoffLength);
    def coeff3 = - Sqr(a1);
    def coeff1 = 1 - coeff2 - coeff3;
    def filt1 = if IsNaN(P + P[1]) then filt1[1] else coeff1 * (P + P[1]) / 2 + coeff2 * filt1[1] + coeff3 * filt1[2];
    def filt2 = if IsNaN(filt1 + filt1[1]) then filt2[1] else coeff1 * (filt1 + filt1[1]) / 2 + coeff2 * filt2[1] + coeff3 * filt2[2];

    plot Period_smoothed = filt2; #Psmoothed_twice; #returns the newly computed smoothed period value

}# end Script GetPeriod()

#=============================================================================

def Period_ = CompoundValue(4,
                          if BarNumber() > 5 then GetPeriod(Period_[1],HL2).Period_smoothed  else Period_[1],
                          20);

def CycleLength = Period_;

#================================================================================
# Makes the CorrelationCycle Indicator Adaptable
# Modified by implementing nested folds to enable adaptive indicator
def limit = if IsNaN(CycleLength) then limit[1] else Floor(CycleLength);

def sx = fold ii = 0 to limit with S do S + GetValue(close, ii); #Sum(close, length);
def sxx = fold jj = 0 to limit with SS do SS + GetValue(Sqr(close), jj); #Sum(Sqr(close), length);
def sy1 = fold i1 = 0 to limit with s1 do (s1 + Cos(2 * Double.Pi * i1 / limit));
def sxy1 = fold i2 = 0 to limit with s2 do (s2 + GetValue(close, i2) * Cos(2 * Double.Pi * i2 / limit));
def syy1 = fold i3 = 0 to limit with s3 do (s3 + Sqr(Cos(2 * Double.Pi * i3 / limit)));

def sxx_sx = limit * sxx - Sqr(sx);
def corrCosine = if sxx_sx == 0 then 0 else (limit * sxy1 - sx * sy1) / Sqrt(sxx_sx * (limit * syy1 - Sqr(sy1)));

def sy2 = fold j1 = 0 to limit with t1 do (t1 - Sin(2 * Double.Pi * t1 / limit));
def sxy2 = fold j2 = 0 to limit with t2 do (t2 - GetValue(close, j2) * Sin(2 * Double.Pi * j2 / limit));
def syy2 = fold j3 = 0 to limit with t3 do (t3 + Sqr(Sin(2 * Double.Pi * j3 / limit)));

def corrNegSine = if sxx_sx == 0 then 0 else (limit * sxy2 - sx * sy2) / Sqrt(sxx_sx * (limit * syy2 - Sqr(sy2)));

def CorrelationWithCosine = corrCosine;
def CorrelationWithNegativeSine = corrNegSine;

#============================================================
# CorrelationCycleAngle Indicator made adaptive

#
# Charles Schwab & Co. (c) 2008-2025
#

def real = CorrelationWithCosine;
def imag = CorrelationWithNegativeSine;

def angleRad = if imag != 0 then Double.Pi / 2 + ATan(real / imag) - (if imag > 0 then Double.Pi else 0) else 0;
def angle = 180 / Double.Pi * angleRad;
def positiveAngle = if IsNaN(positiveAngle[1]) then 0 else if positiveAngle[1] - angle < 270 and angle < positiveAngle[1] then positiveAngle[1] else angle;

def CorrelationAngle = positiveAngle;

#==============================================================

#----MarketState indicator modified to make it adpative----

#
# Charles Schwab & Co. (c) 2008-2025
#

input trendLength = 40;
def trendAngle = 360 / trendLength;

def MarketState;
if (AbsValue(angle - angle[1]) < trendAngle and angle < 0) {
    MarketState = -1;
} else if (AbsValue(angle - angle[1]) < trendAngle and angle >= 0) {
    MarketState = 1;
} else {
    MarketState = 0;
}

#================================================================
# Label for Market State----------------------------
AddLabel(yes, if MarketState == 1 then "Adaptive r1: Trending UP" else if MarketState == -1 then "Adaptive r1: Trending DOWN" else "Adaptive r1: RANGING", if MarketState == 1 then Color.GREEN else if MarketState == -1 then Color.RED else CreateColor(255, 255, 204), Location.BOTTOM_RIGHT, FontSize.SMALL);[/CODE
[/QUOTE]

Is the label displaying on screen or? It's hard to tell. I checked the code and it looks to match what I am running. Here is a different variant of the same thing that uses the Homodyne Discriminator method rather than the periodogram to get the dominant cycle.

Perhaps this one will run on your end, fingers crossed:

CSS:
#Adaptive Market State Label R1 using Homdyne Discriminator method for ThinkOrSwim by Sesqui (3SEP2025)
# Uses method of Homdyne Discriminator

#-----------------------------------------------
Script GetPeriod{
    input Period_Prev_Bar = 6;
    input Price = HL2;
    plot Smooth = (4*Price + 3*Price[1] + 2*Price[2] + Price[3])/10;

    plot Detrender = (0.0962*Smooth + 0.5769*Smooth[2] - 0.5769*Smooth[4] - 0.0962*Smooth[6])*(0.075*Period_Prev_Bar + 0.54);
 
    plot Q1 = (0.0962*Detrender + 0.5769*Detrender[2] - 0.5769*Detrender[4] - 0.0962*Detrender[6])*(0.075*Period_Prev_Bar + 0.54);
    plot I1 = Detrender[3];

    # Advance the phase of I1 and Q1 by 90 degrees
    plot jI = (0.0962*I1 + 0.5769*I1[2] - 0.5769*I1[4] - 0.0962*I1[6])*(0.075*Period_Prev_Bar + 0.54);
    plot jQ = (0.0962*Q1 + 0.5769*Q1[2] - 0.5769*Q1[4] - 0.0962*Q1[6])*(0.075*Period_Prev_Bar + 0.54);

    # Phasor addition for 3 bar averaging
    plot I2 = I1 - jQ;
    plot Q2 = Q1 + jI;

    # Smooth the I and Q components before applying the discriminator
    plot I2smooth = 0.2*I2 + 0.8*I2[1];
    plot Q2smooth = 0.2*Q2 + 0.8*Q2[1];

    # Homodyne Discriminator
    plot Re = I2smooth*I2smooth[1] + Q2smooth*Q2smooth[1];
    plot Im = I2smooth*Q2smooth[1] + Q2smooth*I2smooth[1];

    plot Resmooth = 0.2*Re + 0.8*Re[1];
    plot Imsmooth = 0.2*Im + 0.8*Im[1];

    plot P = if Imsmooth <> 0 and Resmooth <> 0 and 360.0/ATan(Imsmooth/Resmooth) < 1.5*Period_Prev_Bar and 360.0/ATan(Imsmooth/Resmooth) > 0.67*Period_Prev_Bar then 360.0/ATan(Imsmooth/Resmooth) else if 360.0/ATan(Imsmooth/Resmooth) > 1.5*Period_Prev_Bar then 1.5*Period_Prev_Bar else if 360.0/ATan(Imsmooth/Resmooth) < 0.67*Period_Prev_Bar then 0.67*Period_Prev_Bar else if 360.0/ATan(Imsmooth/Resmooth) < 6 then 6 else if 360.0/ATan(Imsmooth/Resmooth) > 50 then 50 else 6;


    #plot Psmooth = 0.2*P + 0.8*P[1];
    #def Psmoothed_twice = if !IsNaN(Psmoothed_twice[1]) then 0.33*Psmooth + 0.67*Psmoothed_twice[1] else 0.33*Psmooth;
    #-----------------------------------------
    #Ehlers SuperSmoother
    input cutoffLength = 10;

    def a1 = Exp(-Double.Pi * Sqrt(2) / cutoffLength);
    def coeff2 = 2 * a1 * Cos(Sqrt(2) * Double.Pi / cutoffLength);
    def coeff3 = - Sqr(a1);
    def coeff1 = 1 - coeff2 - coeff3;
    def filt1 = if IsNaN(P + P[1]) then filt1[1] else coeff1 * (P + P[1]) / 2 + coeff2 * filt1[1] + coeff3 * filt1[2];
    def filt2 = if IsNaN(filt1 + filt1[1]) then filt2[1] else coeff1 * (filt1 + filt1[1]) / 2 + coeff2 * filt2[1] + coeff3 * filt2[2];

    plot Period_smoothed = filt2; #Psmoothed_twice; #returns the newly computed smoothed period value

}# end Script GetPeriod()

#=============================================================================

def Period_ = CompoundValue(4,
                          if BarNumber() > 5 then GetPeriod(Period_[1],HL2).Period_smoothed  else Period_[1],
                          20);

def CycleLength = Period_;

#================================================================================
# Makes the CorrelationCycle Indicator Adaptable
# Modified by implementing nested folds to enable adaptive indicator
def limit = if IsNaN(CycleLength) then limit[1] else Floor(CycleLength);

def sx = fold ii = 0 to limit with S do S + GetValue(close, ii); #Sum(close, length);
def sxx = fold jj = 0 to limit with SS do SS + GetValue(Sqr(close), jj); #Sum(Sqr(close), length);
def sy1 = fold i1 = 0 to limit with s1 do (s1 + Cos(2 * Double.Pi * i1 / limit));
def sxy1 = fold i2 = 0 to limit with s2 do (s2 + GetValue(close, i2) * Cos(2 * Double.Pi * i2 / limit));
def syy1 = fold i3 = 0 to limit with s3 do (s3 + Sqr(Cos(2 * Double.Pi * i3 / limit)));

def sxx_sx = limit * sxx - Sqr(sx);
def corrCosine = if sxx_sx == 0 then 0 else (limit * sxy1 - sx * sy1) / Sqrt(sxx_sx * (limit * syy1 - Sqr(sy1)));

def sy2 = fold j1 = 0 to limit with t1 do (t1 - Sin(2 * Double.Pi * t1 / limit));
def sxy2 = fold j2 = 0 to limit with t2 do (t2 - GetValue(close, j2) * Sin(2 * Double.Pi * j2 / limit));
def syy2 = fold j3 = 0 to limit with t3 do (t3 + Sqr(Sin(2 * Double.Pi * j3 / limit)));

def corrNegSine = if sxx_sx == 0 then 0 else (limit * sxy2 - sx * sy2) / Sqrt(sxx_sx * (limit * syy2 - Sqr(sy2)));

def CorrelationWithCosine = corrCosine;
def CorrelationWithNegativeSine = corrNegSine;

#============================================================
# CorrelationCycleAngle Indicator made adaptive

#
# Charles Schwab & Co. (c) 2008-2025
#

def real = CorrelationWithCosine;
def imag = CorrelationWithNegativeSine;

def angleRad = if imag != 0 then Double.Pi / 2 + ATan(real / imag) - (if imag > 0 then Double.Pi else 0) else 0;
def angle = 180 / Double.Pi * angleRad;
def positiveAngle = if IsNaN(positiveAngle[1]) then 0 else if positiveAngle[1] - angle < 270 and angle < positiveAngle[1] then positiveAngle[1] else angle;

def CorrelationAngle = positiveAngle;

#==============================================================

#----MarketState indicator modified to make it adpative----

#
# Charles Schwab & Co. (c) 2008-2025
#

input trendLength = 40;
def trendAngle = 360 / trendLength;

def MarketState;
if (AbsValue(angle - angle[1]) < trendAngle and angle < 0) {
    MarketState = -1;
} else if (AbsValue(angle - angle[1]) < trendAngle and angle >= 0) {
    MarketState = 1;
} else {
    MarketState = 0;
}

#================================================================
# Label for Market State----------------------------
AddLabel(yes, if MarketState == 1 then "Adaptive r1: Trending UP" else if MarketState == -1 then "Adaptive r1: Trending DOWN" else "Adaptive r1: RANGING", if MarketState == 1 then Color.GREEN else if MarketState == -1 then Color.RED else CreateColor(255, 255, 204), Location.BOTTOM_RIGHT, FontSize.SMALL);
Im going to try today and let u know,thanks
 
If the "tag" is the label, is it displaying anything? It is supposed to have text that states whether the market is trending up, down or ranging...
 
I looked over your indicators and it seems we are on the same path somehow. I was struggling with trying to find a way to to better stats and Fourier transforms in TS anemic capabilities and we seem to have found similar tactics but you are a week or two ahead of me.

Anyway I tried these out today and they show different trend results at the same time so the scripts are not equal in output if anyone is using them.
11-06-32.png
 
Good point. Rev 1 I believe uses a different method, the homodyne discriminator method. So they will likely get different results.
 
I cant get it to work, do you have a link that i can try with ? Thanks

@Milianis4 I discovered a less computatioanlly demanding method of measuring the market dominant cycle and have revised the original code. If you have a chance, try the new code. It should run without bogging down the PC. Again this is to make a label on the bottom right of the upper price chart that indicates what the market state is as it tracks the market dominant cycle using the band-pass filter method. The code is at the top of page here: Adaptive Market State Indicator For ThinkorSwim

Best regards,

Sesqui
 
I looked over your indicators and it seems we are on the same path somehow. I was struggling with trying to find a way to to better stats and Fourier transforms in TS anemic capabilities and we seem to have found similar tactics but you are a week or two ahead of me.

Anyway I tried these out today and they show different trend results at the same time so the scripts are not equal in output if anyone is using them.
View attachment 25775
@lastar2693

I discovered a less computationally demanding method of measuring the market dominant cycle (one that also tested out to be very precise) and have revised the original code on the site. If you have a chance, try the new code. It should run without bogging down the PC and should get much better results. Again this is to make a label on the bottom right of the upper price chart that indicates what the market state is as it tracks the market dominant cycle using the band-pass filter method. The code is at the top of page here: Adaptive Market State Indicator For ThinkorSwim

Best regards,

Sesqui
 

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
437 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top