Adaptive Chop Indicator for Thinkorswim

baTrader

New member
Plus
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:
  • 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.
AdaptiveChopCandles.png

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:

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
775 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