AdaptiveTrend Chop Indicator — Indicator Description
This indicator is a standalone extraction of the “chop” (neutral) condition from the AdaptiveTrend framework. Its sole purpose is to identify periods where price is not exhibiting meaningful directional behavior, and where trend-following signals are statistically less reliable.
The study classifies each bar using a combination of three structural factors:
When these conditions align, the market is considered to be in a structurally neutral state (“chop”), defined internally as trendState == 0 after initialization.
Key Features:
- Precise Chop Detection:
Identifies low-conviction, rotational market conditions where neither buyers nor sellers have control.
- Symbol-Specific Tuning:
Automatically adjusts internal parameters for major futures markets (/ES, /NQ, /RTY, /GC, /CL and micros) when enabled, improving consistency across instruments.
- Initialization Safety:
Excludes early bars to prevent unstable statistical values from affecting classification.
- Optional Visual Marking:
• Chop dots can be plotted at the top, bottom, or both locations of chop candles
• Chop candles can be colored grey for immediate visual identification
- Clean, Minimal Design:
All non-essential AdaptiveTrend components have been removed, leaving only the logic required for accurate chop detection.
Purpose in Trading:
Interpretation:
This tool is best used in combination with directional or momentum-based systems to determine when NOT to trade, rather than when to enter.
This indicator is a standalone extraction of the “chop” (neutral) condition from the AdaptiveTrend framework. Its sole purpose is to identify periods where price is not exhibiting meaningful directional behavior, and where trend-following signals are statistically less reliable.
The study classifies each bar using a combination of three structural factors:
- Volatility compression (ATR relative to its own baseline)
- Statistical positioning (Z-score of price relative to its mean)
- Candle range contraction (current bar range vs expected range)
When these conditions align, the market is considered to be in a structurally neutral state (“chop”), defined internally as trendState == 0 after initialization.
Key Features:
- Precise Chop Detection:
Identifies low-conviction, rotational market conditions where neither buyers nor sellers have control.
- Symbol-Specific Tuning:
Automatically adjusts internal parameters for major futures markets (/ES, /NQ, /RTY, /GC, /CL and micros) when enabled, improving consistency across instruments.
- Initialization Safety:
Excludes early bars to prevent unstable statistical values from affecting classification.
- Optional Visual Marking:
• Chop dots can be plotted at the top, bottom, or both locations of chop candles
• Chop candles can be colored grey for immediate visual identification
- Clean, Minimal Design:
All non-essential AdaptiveTrend components have been removed, leaving only the logic required for accurate chop detection.
Purpose in Trading:
- Acts as an environment filter rather than a signal generator
- Helps avoid trading during consolidation, balance, or transition phases
- Highlights areas where mean-reversion behavior is more likely than trend continuation
- Improves trade selection by filtering out low-quality directional setups
Interpretation:
- Grey candles or dots = structural indecision
- Clusters of chop = consolidation, absorption, or pre-expansion conditions
- Extended chop = increased risk for breakout failure or false signals
This tool is best used in combination with directional or momentum-based systems to determine when NOT to trade, rather than when to enter.
Code:
# =====================================================================
# ADAPTIVETREND CHOP INDICATOR — NEUTRAL / GREY CANDLE EXTRACT
# Chop Code Adapted from AdaptiveTrend V13 - Plus for ThinkOrSwim
# https://usethinkscript.com/threads/adaptivetrend-v13-%E2%80%93-plus-for-thinkorswim.22152/
#
# Modified by: baTrader
# Version: 1.6
# Last Update: 2026-04-02
#
# DESCRIPTION:
# This study isolates and reproduces the structural “chop” condition from
# the AdaptiveTrend framework, where price is neither trending nor
# directionally expanding. Instead of evaluating trend strength broadly,
# this script focuses on identifying low-conviction, rotational market
# conditions where directional continuation is less dependable.
#
# The classification logic blends volatility compression (ATR), relative
# price location (Z-score), and candle-range contraction to determine when
# price is behaving in a structurally neutral manner. These conditions
# often correspond to balance, pause, absorption, or transition regimes in
# the auction, where trend-following signals tend to lose reliability.
#
# PURPOSE:
# - Provide a standalone method for identifying structural chop regimes
# - Isolate neutral conditions that would otherwise appear as grey candles
# in the parent AdaptiveTrend framework
# - Help filter out poor directional trade conditions
# - Highlight areas where rotational or mean-reversion behavior may be
# more likely than clean expansion
#
# FEATURES:
# - Optional chart-symbol defaults for:
# /ES, /MES
# /NQ, /MNQ
# /RTY, /M2K
# /GC, /MGC
# /CL, /MCL
# - Symbols not explicitly mapped fall back to manual base inputs
# - Optional visualization controls:
# • Plot chop dots (Top / Bottom / Both)
# • Color chop candles gray for immediate chart context
#
# IMPLEMENTATION:
# - Chop is defined strictly as:
# trendState == 0
# after initialization is complete
# - Initialization bars are excluded to avoid unstable early statistical
# values from affecting state classification
# - Symbol-specific defaults are used only when enabled
#
# NOTES:
# - This is a reduced, purpose-built extraction from AdaptiveTrend
# - Non-essential modules have been removed for clarity and deterministic
# behavior
# - No repainting is introduced in the chop classification layer
# - Best used as an environment filter, not as a standalone entry signal
#
# INTERPRETATION:
# - Grey candles and/or dots indicate structural indecision
# - Clusters of chop can mark balance, absorption, consolidation, or
# pre-expansion transition zones
# - Persistent chop usually argues for caution with directional signals
# =====================================================================
declare upper;
# =====================================================================
# 1. GLOBAL COLOR SYSTEM
# =====================================================================
DefineGlobalColor("Chop", Color.LIGHT_GRAY);
# =====================================================================
# 2. INPUTS
# =====================================================================
input useChartSymbolSpecificSettings = yes;
input LengthBase = 34;
input VolLookbackBase = 14;
input ZLenBase = 34;
input ZNeutralLevelBase = 0.5;
input ATRCompressLvlBase = 0.9;
input RangeFactorBase = 0.8;
input showChopDots = no;
input chopDotLocation = {Default Both, Top, Bottom};
input colorChopCandles = yes;
# =====================================================================
# 3. BASE SERIES
# =====================================================================
def price = close;
def bn = BarNumber();
# =====================================================================
# 4. REC STATE SYSTEMS
# =====================================================================
# No rec variables required in this build.
# =====================================================================
# 5. DERIVED INDICATORS
# =====================================================================
# ---------------------------------------------------------------------
# SYMBOL DETECTION (CANONICAL / SAFE)
# ---------------------------------------------------------------------
def isES = GetSymbol() == "/ES" or GetSymbol() == "/ES:XCME";
def isMES = GetSymbol() == "/MES" or GetSymbol() == "/MES:XCME";
def isNQ = GetSymbol() == "/NQ" or GetSymbol() == "/NQ:XCME";
def isMNQ = GetSymbol() == "/MNQ" or GetSymbol() == "/MNQ:XCME";
def isRTY = GetSymbol() == "/RTY" or GetSymbol() == "/RTY:XCME";
def isM2K = GetSymbol() == "/M2K" or GetSymbol() == "/M2K:XCME";
def isGC = GetSymbol() == "/GC" or GetSymbol() == "/GC:XCEC";
def isMGC = GetSymbol() == "/MGC" or GetSymbol() == "/MGC:XCEC";
def isCL = GetSymbol() == "/CL" or GetSymbol() == "/CL:XCME";
def isMCL = GetSymbol() == "/MCL" or GetSymbol() == "/MCL:XCME";
def isESGroup = isES or isMES;
def isNQGroup = isNQ or isMNQ;
def isRTYGroup = isRTY or isM2K;
def isGCGroup = isGC or isMGC;
def isCLGroup = isCL or isMCL;
# ---------------------------------------------------------------------
# EFFECTIVE PARAMETER SELECTION
# ---------------------------------------------------------------------
def Length =
if useChartSymbolSpecificSettings and isESGroup then 34
else if useChartSymbolSpecificSettings and isNQGroup then 42
else if useChartSymbolSpecificSettings and isRTYGroup then 30
else if useChartSymbolSpecificSettings and isGCGroup then 28
else if useChartSymbolSpecificSettings and isCLGroup then 24
else LengthBase;
def VolLookback =
if useChartSymbolSpecificSettings and isESGroup then 14
else if useChartSymbolSpecificSettings and isNQGroup then 16
else if useChartSymbolSpecificSettings and isRTYGroup then 12
else if useChartSymbolSpecificSettings and isGCGroup then 12
else if useChartSymbolSpecificSettings and isCLGroup then 10
else VolLookbackBase;
def ZLen =
if useChartSymbolSpecificSettings and isESGroup then 34
else if useChartSymbolSpecificSettings and isNQGroup then 42
else if useChartSymbolSpecificSettings and isRTYGroup then 30
else if useChartSymbolSpecificSettings and isGCGroup then 28
else if useChartSymbolSpecificSettings and isCLGroup then 24
else ZLenBase;
def ZNeutralLevel =
if useChartSymbolSpecificSettings and isESGroup then 0.45
else if useChartSymbolSpecificSettings and isNQGroup then 0.55
else if useChartSymbolSpecificSettings and isRTYGroup then 0.50
else if useChartSymbolSpecificSettings and isGCGroup then 0.45
else if useChartSymbolSpecificSettings and isCLGroup then 0.60
else ZNeutralLevelBase;
def ATRCompressLvl =
if useChartSymbolSpecificSettings and isESGroup then 0.92
else if useChartSymbolSpecificSettings and isNQGroup then 0.88
else if useChartSymbolSpecificSettings and isRTYGroup then 0.93
else if useChartSymbolSpecificSettings and isGCGroup then 0.90
else if useChartSymbolSpecificSettings and isCLGroup then 0.86
else ATRCompressLvlBase;
def RangeFactor =
if useChartSymbolSpecificSettings and isESGroup then 0.75
else if useChartSymbolSpecificSettings and isNQGroup then 0.85
else if useChartSymbolSpecificSettings and isRTYGroup then 0.78
else if useChartSymbolSpecificSettings and isGCGroup then 0.72
else if useChartSymbolSpecificSettings and isCLGroup then 0.90
else RangeFactorBase;
# ---------------------------------------------------------------------
# INITIALIZATION STATE
# ---------------------------------------------------------------------
def initializing = bn < ZLen;
# ---------------------------------------------------------------------
# VOLATILITY ENGINE
# ---------------------------------------------------------------------
def tr = TrueRange(high, close, low);
def atr = Average(tr, VolLookback);
def atrAvg = Average(atr, VolLookback);
def atrNorm = if atrAvg != 0 then atr / atrAvg else 1;
def atrClamp = Min(Max(atrNorm, 0.5), 2.0);
# ---------------------------------------------------------------------
# Z-SCORE ENGINE
# ---------------------------------------------------------------------
def meanP = Average(price, ZLen);
def stdevP = StDev(price, ZLen);
def z = if stdevP != 0 then (price - meanP) / stdevP else 0;
# ---------------------------------------------------------------------
# STRUCTURAL NEUTRAL / CHOP CLASSIFIER
# ---------------------------------------------------------------------
def atrCompress = atrClamp < ATRCompressLvl;
def zNeutral = AbsValue(z) < ZNeutralLevel;
def rangeNeutral = (high - low) < (atr * RangeFactor);
def strongNeutral = atrCompress and zNeutral and rangeNeutral;
def dir =
if strongNeutral then 0
else if z > ZNeutralLevel then 1
else if z < -ZNeutralLevel then -1
else 0;
def strongUp =
dir > 0 and
AbsValue(z) > 1 and
atrClamp >= 1 and
!strongNeutral;
def weakUp =
dir > 0 and
AbsValue(z) > 0.3 and
AbsValue(z) <= 1 and
!strongNeutral;
def weakDown =
dir < 0 and
AbsValue(z) > 0.3 and
AbsValue(z) <= 1 and
!strongNeutral;
def neutral = strongNeutral or dir == 0;
def trendState =
if initializing then 0
else if strongUp then 1
else if weakUp then 2
else if neutral then 0
else if weakDown then -2
else -1;
# =====================================================================
# 6. DECISION LOGIC
# =====================================================================
def isChop = !initializing and trendState == 0;
# =====================================================================
# 7. VISUALIZATION LAYER
# =====================================================================
def showTop =
chopDotLocation == chopDotLocation.Both or
chopDotLocation == chopDotLocation.Top;
def showBottom =
chopDotLocation == chopDotLocation.Both or
chopDotLocation == chopDotLocation.Bottom;
plot ChopDotTop =
if showChopDots and isChop and showTop then high
else Double.NaN;
ChopDotTop.SetPaintingStrategy(PaintingStrategy.POINTS);
ChopDotTop.SetLineWeight(5);
ChopDotTop.AssignValueColor(GlobalColor("Chop"));
plot ChopDotBottom =
if showChopDots and isChop and showBottom then low
else Double.NaN;
ChopDotBottom.SetPaintingStrategy(PaintingStrategy.POINTS);
ChopDotBottom.SetLineWeight(5);
ChopDotBottom.AssignValueColor(GlobalColor("Chop"));
AssignPriceColor(
if colorChopCandles and isChop then GlobalColor("Chop")
else Color.CURRENT
);
Last edited by a moderator: