Advanced approaches to detect Trend vs Sideways

D_Tramp

New member
VIP
Hi All,

I’m specifically looking for non-standard and more advanced approaches in thinkScript to classify whether a stock is currently in a trending regime or a ranging / sideways / choppy regime. I’m already very familiar with the classic tools (ADX thresholds, moving average slope/alignment, Bollinger Bandwidth, ATR vs its average, basic higher-highs/higher-lows structure, etc.), so please skip those.

What I’m interested in:
- statistical / quantitative measures that can be coded in thinkScript
(Kaufman Efficiency Ratio and its variations, approximations of Hurst exponent, Fractal Dimension, Choppiness Index if used in a more sophisticated way, linear regression R-squared + slope combinations, entropy-based measures, variance ratio tests, etc.)​
- any custom multi-factor or hybrid regime filters that go beyond the usual indicators
- scripts that produce a clean regime label / state (Trend / Range / Transition) using less common math
- anything people have developed or found useful that feels more “quantitative” and less laggy than the standard toolkit

If you have (or know of) threads/scripts that implement any of the above - or your own experimental approaches - I’d really appreciate links, code, or descriptions of what actually works well on stocks in TOS.

Thanks a lot!
 
Solution
"Sideways" is a ranging result that is caused by multi-dimensional footprint across Price Action, Candle Patterns, and Volume Dynamics


Market BehaviorCandle / Price Action PatternVolume DynamicsResulting Regime StateWhy Standard Metrics Fail
Institutional Accumulation / DistributionTight price bounds, frequent long wicks on one side (rejection).High, steady, or spiking volume at bound edges without price continuation.Squeeze / CoilingHigh volume tricks trend/momentum indicators into expecting a breakout prematurely.
Market Apathy / Low Liquidity
...
"Sideways" is a ranging result that is caused by multi-dimensional footprint across Price Action, Candle Patterns, and Volume Dynamics


Market BehaviorCandle / Price Action PatternVolume DynamicsResulting Regime StateWhy Standard Metrics Fail
Institutional Accumulation / DistributionTight price bounds, frequent long wicks on one side (rejection).High, steady, or spiking volume at bound edges without price continuation.Squeeze / CoilingHigh volume tricks trend/momentum indicators into expecting a breakout prematurely.
Market Apathy / Low LiquiditySmall real bodies, overlapping bars, low intra-bar range (narrow spreads).Substantially below-average, declining volume.Dormant SidewaysVolatility-based metrics drop, but breakout signals produce high slippage and false triggers.
Equilibrium Price Discovery (Value Area)Symmetric bell-curve distribution of price around a point of control (POC).High volume centered at the middle of the range; dropping volume at edges.Balanced RangeStatistical metrics see zero directional drift, mistaking consolidation for zero momentum.
Volatility Compression (Contraction)Decreasing high-to-low ranges per candle; forming wedges/pennants.Volume steadily drying up as price moves toward the apex.Volatility SqueezeLinear regression slope drops to near 0, missing the explosive potential energy build-up.
Stop-Hunting / Mean-Reverting ChurnLarge candle bodies with immediate full-bar reversals (whipsaws), breaking recent swings.High, erratic volume spikes on Reversal bars.Choppy / Expansion-Range SidewaysADX drops, but standard Deviation/ATR explodes—tricking filters into seeing a "trending" state.

As you have seen, there are issues when using standard indicators. Some can be overcome; some cannot.

Which specific market behavior are you attempting to identify?
 
Solution

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

"Sideways" is a ranging result that is caused by multi-dimensional footprint across Price Action, Candle Patterns, and Volume Dynamics


Market BehaviorCandle / Price Action PatternVolume DynamicsResulting Regime StateWhy Standard Metrics Fail
Institutional Accumulation / DistributionTight price bounds, frequent long wicks on one side (rejection).High, steady, or spiking volume at bound edges without price continuation.Squeeze / CoilingHigh volume tricks trend/momentum indicators into expecting a breakout prematurely.
Market Apathy / Low LiquiditySmall real bodies, overlapping bars, low intra-bar range (narrow spreads).Substantially below-average, declining volume.Dormant SidewaysVolatility-based metrics drop, but breakout signals produce high slippage and false triggers.
Equilibrium Price Discovery (Value Area)Symmetric bell-curve distribution of price around a point of control (POC).High volume centered at the middle of the range; dropping volume at edges.Balanced RangeStatistical metrics see zero directional drift, mistaking consolidation for zero momentum.
Volatility Compression (Contraction)Decreasing high-to-low ranges per candle; forming wedges/pennants.Volume steadily drying up as price moves toward the apex.Volatility SqueezeLinear regression slope drops to near 0, missing the explosive potential energy build-up.
Stop-Hunting / Mean-Reverting ChurnLarge candle bodies with immediate full-bar reversals (whipsaws), breaking recent swings.High, erratic volume spikes on Reversal bars.Choppy / Expansion-Range SidewaysADX drops, but standard Deviation/ATR explodes—tricking filters into seeing a "trending" state.

As you have seen, there are issues when using standard indicators. Some can be overcome; some cannot.

Which specific market behavior are you attempting to identify?

Thank you for the detailed breakdown — this is extremely helpful.You’re absolutely right: treating “sideways” as a single regime is too crude. The multi-dimensional view (Price Action + Candle Patterns + Volume Dynamics) makes much more sense, especially when standard metrics start failing.

What I’m ultimately trying to build is a reliable, codeable regime filter that can distinguish:

1) True trending conditions
2) Different subtypes of non-trending / ranging environments (particularly the ones that look like Squeeze/Coiling, Volatility Compression, and Choppy/Expansion-Range Sideways)

The goal is to have a clean state output (or probability scores) that I can use as a filter in strategies — preferably something quantitative enough to be implemented in thinkScript without heavy discretionary interpretation.Do you (or anyone else) have any existing scripts, approaches, or combinations that try to classify these more nuanced regimes using a multi-factor footprint (price action structure + volume behavior + candle characteristics)?Even partial implementations or ideas on how to quantify these distinctions would be gold.Thanks again for taking the time to explain this so clearly.
 
Interesting idea: I took a stab at it, minor testing, still debugging. Heading out of town so maybe someone can pick up the ball and run with it. I quickly ran into TOS math limitations using actual Quant statistics🤬


1786434956570.png


1786435069771.png


1786435208478.png


# atcsam 08/11/26
Ruby:
input debug = yes;

# =========================
# Three fixed window sets
# =========================
input fdiShort = 50;
input fdiMed   = 100;
input fdiLong  = 150;

input vrShort = 10;
input vrMed   = 20;
input vrLong  = 30;

input rsqShort = 20;
input rsqMed   = 50;
input rsqLong  = 80;

# =========================
# Efficiency Ratio (ER)
# =========================
def chgER  = AbsValue(close - close[20]);
def noiseER = Sum(AbsValue(close - close[1]), 20);
def ER = if noiseER != 0 then chgER / noiseER else 0;

# =========================
# FDI (short/med/long)
# =========================
def HHs = Highest(high, fdiShort);
def LLs = Lowest(low, fdiShort);
def Rs  = HHs - LLs;
def Ss  = Sum(AbsValue(close - close[1]), fdiShort);
def FDI_s_raw = if Rs != 0 then Log(Ss / Rs) / Log(fdiShort) else 1.5;
def FDI_s = Min(2.0, Max(1.0, FDI_s_raw));

def HHm = Highest(high, fdiMed);
def LLm = Lowest(low, fdiMed);
def Rm  = HHm - LLm;
def Sm  = Sum(AbsValue(close - close[1]), fdiMed);
def FDI_m_raw = if Rm != 0 then Log(Sm / Rm) / Log(fdiMed) else 1.5;
def FDI_m = Min(2.0, Max(1.0, FDI_m_raw));

def HHl = Highest(high, fdiLong);
def LLl = Lowest(low, fdiLong);
def Rl  = HHl - LLl;
def Sl  = Sum(AbsValue(close - close[1]), fdiLong);
def FDI_l_raw = if Rl != 0 then Log(Sl / Rl) / Log(fdiLong) else 1.5;
def FDI_l = Min(2.0, Max(1.0, FDI_l_raw));

# =========================
# VR (short/med/long)
# =========================
def r1 = close - close[1];

def rNs = close - close[vrShort];
def var1s = Average(r1 * r1, vrShort);
def varNs = Average(rNs * rNs, vrShort);
def VR_s_raw = if var1s != 0 then varNs / (vrShort * var1s) else 1;
def VR_s = Min(2.0, Max(0.5, VR_s_raw));

def rNm = close - close[vrMed];
def var1m = Average(r1 * r1, vrMed);
def varNm = Average(rNm * rNm, vrMed);
def VR_m_raw = if var1m != 0 then varNm / (vrMed * var1m) else 1;
def VR_m = Min(2.0, Max(0.5, VR_m_raw));

def rNl = close - close[vrLong];
def var1l = Average(r1 * r1, vrLong);
def varNl = Average(rNl * rNl, vrLong);
def VR_l_raw = if var1l != 0 then varNl / (vrLong * var1l) else 1;
def VR_l = Min(2.0, Max(0.5, VR_l_raw));

# =========================
# RSQ (short/med/long)
# =========================
def avgS = Average(close, rsqShort);
def devS = close - avgS;
def varS = Average(devS * devS, rsqShort);
def covS = Average(devS * (close - close[1]), rsqShort);
def RSQ_s = if varS != 0 then Sqr(covS / varS) else 0;

def avgM = Average(close, rsqMed);
def devM = close - avgM;
def varM = Average(devM * devM, rsqMed);
def covM = Average(devM * (close - close[1]), rsqMed);
def RSQ_m = if varM != 0 then Sqr(covM / varM) else 0;

def avgL = Average(close, rsqLong);
def devL = close - avgL;
def varL = Average(devL * devL, rsqLong);
def covL = Average(devL * (close - close[1]), rsqLong);
def RSQ_l = if varL != 0 then Sqr(covL / varL) else 0;

# =========================
# Option A: TrendScore – RangeScore selection
# =========================

# SHORT window scores
def trendScore_s =
    (ER > 0.30) +
    (FDI_s < 1.55) +
    (RSQ_s > 0.30) +
    (VR_s > 1.05);

def rangeScore_s =
    (ER < 0.20) +
    (FDI_s > 1.60) +
    (RSQ_s < 0.20) +
    (VR_s < 0.95);

def signal_s = trendScore_s - rangeScore_s;

# MEDIUM window scores
def trendScore_m =
    (ER > 0.30) +
    (FDI_m < 1.55) +
    (RSQ_m > 0.30) +
    (VR_m > 1.05);

def rangeScore_m =
    (ER < 0.20) +
    (FDI_m > 1.60) +
    (RSQ_m < 0.20) +
    (VR_m < 0.95);

def signal_m = trendScore_m - rangeScore_m;

# LONG window scores
def trendScore_l =
    (ER > 0.30) +
    (FDI_l < 1.55) +
    (RSQ_l > 0.30) +
    (VR_l > 1.05);

def rangeScore_l =
    (ER < 0.20) +
    (FDI_l > 1.60) +
    (RSQ_l < 0.20) +
    (VR_l < 0.95);

def signal_l = trendScore_l - rangeScore_l;

# =========================
# Window Selection
# =========================
def useShort = signal_s > signal_m and signal_s > signal_l;
def useMed   = signal_m > signal_s and signal_m > signal_l;
def useLong  = signal_l > signal_s and signal_l > signal_m;

# Tie-breaker: MED if equal
def FDI = if useShort then FDI_s else if useMed then FDI_m else FDI_l;
def VR  = if useShort then VR_s  else if useMed then VR_m  else VR_l;
def RSQ = if useShort then RSQ_s else if useMed then RSQ_m else RSQ_l;

# =========================
# Slope
# =========================
def slope = close - close[1];

# =========================
# Trend / Range Scores (final)
# =========================
def trendScore =
    (ER > 0.30) +
    (FDI < 1.55) +
    (RSQ > 0.30) +
    (VR > 1.05);

def rangeScore =
    (ER < 0.20) +
    (FDI > 1.60) +
    (RSQ < 0.20) +
    (VR < 0.95);

def regime =
    if trendScore >= 3 then 1
    else if rangeScore >= 3 then -1
    else 0;

# =========================
# Strength / Quality
# =========================
def strengthRaw =
    (ER * 25) +
    ((2 - FDI) * 25) +
    (RSQ * 25) +
    (VR * 25);

def trendStrength = Max(0, Min(100, strengthRaw));

def qualityRaw =
    (ER * 20) +
    ((2 - FDI) * 20) +
    (RSQ * 30) +
    (VR * 20) +
    (AbsValue(slope) * 10);

def trendQuality = Max(0, Min(100, qualityRaw));

# =========================
# Directional Label
# =========================
def trendDir = if slope > 0 then 1 else -1;

AddLabel(yes,
    if regime == 1 then
        (if trendDir == 1 then "TREND ↑ | Strength " + Round(trendStrength,2) + " | Quality " + Round(trendQuality,2)
         else "TREND ↓ | Strength " + Round(trendStrength,2) + " | Quality " + Round(trendQuality,2))
    else if regime == -1 then
        "RANGE | Strength " + Round(trendStrength,2) + " | Quality " + Round(trendQuality,2)
    else
        "TRANSITION | Strength " + Round(trendStrength,2) + " | Quality " + Round(trendQuality,2),

    if regime == 1 then
        (if trendDir == 1 then Color.GREEN else Color.RED)
    else if regime == -1 then
        Color.YELLOW
    else
        Color.YELLOW
);

# =========================
# Debug Labels
# =========================
AddLabel(debug, "FDI S/M/L: " + Round(FDI_s,2) + " / " + Round(FDI_m,2) + " / " + Round(FDI_l,2), Color.WHITE);
AddLabel(debug, "VR  S/M/L: " + Round(VR_s,2)  + " / " + Round(VR_m,2)  + " / " + Round(VR_l,2), Color.WHITE);
AddLabel(debug, "RSQ S/M/L: " + Round(RSQ_s,2) + " / " + Round(RSQ_m,2) + " / " + Round(RSQ_l,2), Color.WHITE);
AddLabel(debug, "Signal S/M/L: " + signal_s + " / " + signal_m + " / " + signal_l, Color.WHITE);
AddLabel(debug, "Using: " + (if useShort then "SHORT" else if useMed then "MEDIUM" else "LONG"), Color.CYAN);
 
Last edited:
Thread starter Similar threads Forum Replies Date
M Advanced Market Moves 2.0 Questions 3
W Advanced TRIX Questions 3
W Advanced VWAP Questions 13
W Advanced Pivot Point Indicator Questions 6
W Advanced EMAD Modification Request Questions 6

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
1167 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