Indicator · Pine Script v6 · Own pane

Regime Classifier [WavesUnchained]

by casoon

This indicator classifies market conditions into Trend, Range, or Chaos regimes by combining the Kaufman Efficiency Ratio, Fractal Dimension Index, and Choppiness Index into a single trendiness metric.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code283
Settings30inputs
LicenceMIT
Last updateyesterday27 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

283 lines of Pine Script v6 from indicators/trend_strength/regime_classifier/regime_classifier.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

regime_classifier.pine
//@version=6
// ============================================================================
// Regime Classifier [WavesUnchained]
// Version: 1.1.3
// Author: WavesUnchained
// Build: 2026-10-02 00:00:00
// ============================================================================
// Stage-1 regime filter for the reversal pipeline: fuses Fractal Dimension Index,
// Kaufman Efficiency Ratio and the Choppiness Index into one Trend / Range / Chaos
// classification. It is a Quality/context layer — it answers "is a reversal even
// sensible here?", it never fires a long/short trigger.
//
// Features:
//   ✓ Three regime sensors (FDI, Efficiency Ratio, Choppiness) on one trendiness axis
//   ✓ Trend / Range / Chaos classification with threshold hysteresis
//   ✓ Symmetric reversal- and trend-permission outputs (0..1) for downstream gating
//   ✓ Per-sensor visual breakdown + light-theme dashboard
//   ✓ Regime-change watch markers and alerts (no trade triggers)
//   ✓ Optional higher-timeframe regime line (visual context only, no veto)
//   ✓ Debug log on every regime change
//
// ============================================================================

indicator("Regime Classifier [WavesUnchained]", "REGIME", format=format.price, precision=2, overlay=false, max_labels_count=500)

// ─────────────────────────────────────────────────────────────────────────────
// INPUTS
// ─────────────────────────────────────────────────────────────────────────────

g_sens = "1 · Sensors"
erLen   = input.int(20, "Efficiency Ratio Length", minval=2, group=g_sens,
     tooltip="Kaufman ER over this lookback. High ER = efficient/trending move, low ER = back-and-forth.")
fdiLen  = input.int(30, "Fractal Dimension Length", minval=6, group=g_sens,
     tooltip="Box-counting fractal dimension over this (even) lookback. D≈1 = smooth/trend, D≈1.5+ = rough/range.")
chopLen = input.int(14, "Choppiness Length",        minval=2, group=g_sens,
     tooltip="Choppiness Index lookback. High = choppy/ranging, low = directional.")

g_map = "2 · Sensor → Trendiness Mapping"
erTrendAt  = input.float(0.55, "ER: full-trend at",  minval=0.0, maxval=1.0, step=0.05, group=g_map)
erRangeAt  = input.float(0.20, "ER: full-range at",  minval=0.0, maxval=1.0, step=0.05, group=g_map)
fdiTrendAt = input.float(1.35, "FDI: full-trend at", minval=1.0, maxval=2.0, step=0.05, group=g_map)
fdiRangeAt = input.float(1.55, "FDI: full-range at", minval=1.0, maxval=2.0, step=0.05, group=g_map)
chopTrendAt= input.float(38.2, "Chop: full-trend at",minval=0.0, maxval=100.0, group=g_map)
chopRangeAt= input.float(61.8, "Chop: full-range at",minval=0.0, maxval=100.0, group=g_map)

g_w = "3 · Sensor Weights"
wEr   = input.float(1.0, "Weight · Efficiency Ratio", minval=0.0, step=0.1, group=g_w)
wFdi  = input.float(1.0, "Weight · Fractal Dimension",minval=0.0, step=0.1, group=g_w)
wChop = input.float(1.0, "Weight · Choppiness",       minval=0.0, step=0.1, group=g_w)

g_reg = "4 · Regime Thresholds"
trendHi = input.float(0.62, "Trend enters above", minval=0.5, maxval=1.0, step=0.01, group=g_reg,
     tooltip="Trendiness ≥ this → Trend regime. Between this and the Range level the previous regime is held (hysteresis).")
rangeLo = input.float(0.38, "Range enters below", minval=0.0, maxval=0.5, step=0.01, group=g_reg)

g_chaos = "5 · Chaos Override"
chopChaos = input.float(65.0, "Chaos: Chop above", minval=0.0, maxval=100.0, group=g_chaos,
     tooltip="Chaos requires ALL three: Chop very high AND ER very low AND FDI very high — i.e. no exploitable structure either way.")
erChaos   = input.float(0.18, "Chaos: ER below",   minval=0.0, maxval=1.0, step=0.01, group=g_chaos)
fdiChaos  = input.float(1.55, "Chaos: FDI above",  minval=1.0, maxval=2.0, step=0.05, group=g_chaos)
chaosDamp = input.float(0.30, "Chaos permission multiplier", minval=0.0, maxval=1.0, step=0.05, group=g_chaos)

g_disp = "6 · Display"
showSensors = input.bool(true,  "Show per-sensor lines", group=g_disp)
showWatch   = input.bool(true,  "Show regime-change markers", group=g_disp)
showTable   = input.bool(true,  "Show dashboard", group=g_disp)
tablePos    = input.string("Top Right", "Dashboard position",
     options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group=g_disp)
enableLog   = input.bool(false, "Debug log on regime change", group=g_disp)

g_col = "7 · Colors"
colTrend = input.color(#00c853,             "Trend",  group=g_col)
colRange = input.color(#2962ff,             "Range",  group=g_col)
colChaos = input.color(#d50000,             "Chaos",  group=g_col)

g_htf = "8 · Higher-Timeframe Regime (visual)"
showHtf  = input.bool(false, "Show HTF regime line", group=g_htf,
     tooltip="Visual context only — the HTF regime is NOT used as a veto/gate (§3 over-filtering). Off by default.")
htfMode  = input.string("Auto (4× chart)", "HTF source",
     options=["Auto (4× chart)", "Manual"], group=g_htf)
htfManual= input.timeframe("240", "Manual HTF", group=g_htf,
     tooltip="Used only when HTF source = Manual.")

// ─────────────────────────────────────────────────────────────────────────────
// HELPERS
// ─────────────────────────────────────────────────────────────────────────────

// Map a sensor reading onto a 0..1 trendiness axis given its full-trend / full-range
// anchor values. Direction-agnostic: works whether trend is the high or low anchor.
f_map(float v, float trendAt, float rangeAt) =>
    float span = trendAt - rangeAt
    float t = span == 0.0 ? 0.5 : (v - rangeAt) / span
    math.max(0.0, math.min(1.0, t))

// ─────────────────────────────────────────────────────────────────────────────
// REGIME ENGINE  (one source of truth — reused for the chart TF and the HTF line)
// ─────────────────────────────────────────────────────────────────────────────
// Returns [trendScore, er, fdi, chop, tEr, tFdi, tChop] on whatever timeframe it
// is evaluated on. Sensor convention: ER high = trend, FDI low = trend, Chop high
// = range; each is mapped to a 0..1 trendiness via its own anchors.

f_regime() =>
    // Sensor 1 · Kaufman Efficiency Ratio (0..1, high = trend)
    float erChange = math.abs(close - close[erLen])
    float erVol    = math.sum(math.abs(close - close[1]), erLen)
    float erV      = erVol > 0.0 ? erChange / erVol : 0.0
    // Sensor 2 · Fractal Dimension Index (box-counting, 1..2, low = trend)
    int   fdiHalf = int(math.max(2, fdiLen / 2))
    float r1 = (ta.highest(high, fdiHalf) - ta.lowest(low, fdiHalf)) / fdiHalf
    float r2 = (ta.highest(high, fdiHalf)[fdiHalf] - ta.lowest(low, fdiHalf)[fdiHalf]) / fdiHalf
    float r3 = (ta.highest(high, fdiLen) - ta.lowest(low, fdiLen)) / fdiLen
    float fdiV = na
    if r1 + r2 > 0.0 and r3 > 0.0
        fdiV := (math.log(r1 + r2) - math.log(r3)) / math.log(2.0)
    fdiV := math.max(1.0, math.min(2.0, nz(fdiV, 1.5)))
    // Sensor 3 · Choppiness Index (0..100, high = chop)
    float trV     = ta.tr(true)
    float sumTr   = math.sum(trV, chopLen)
    float chRange = ta.highest(high, chopLen) - ta.lowest(low, chopLen)
    float chopV   = chRange > 0.0 ? 100.0 * math.log10(sumTr / chRange) / math.log10(chopLen) : na
    chopV := math.max(0.0, math.min(100.0, nz(chopV, 50.0)))
    // Composite trendiness (0..1)
    float tErV   = f_map(erV,   erTrendAt,   erRangeAt)
    float tFdiV  = f_map(fdiV,  fdiTrendAt,  fdiRangeAt)
    float tChopV = f_map(chopV, chopTrendAt, chopRangeAt)
    float wSum   = wEr + wFdi + wChop
    float tsV    = wSum > 0.0 ? (wEr * tErV + wFdi * tFdiV + wChop * tChopV) / wSum : 0.5
    [tsV, erV, fdiV, chopV, tErV, tFdiV, tChopV]

[trendScore, er, fdi, chop, tEr, tFdi, tChop] = f_regime()

// ─────────────────────────────────────────────────────────────────────────────
// REGIME STATE  (hysteresis: hold previous inside the dead zone)
// ─────────────────────────────────────────────────────────────────────────────

var int regimeState = 0   // 1 = Trend, -1 = Range, 0 = uninitialised
if trendScore >= trendHi
    regimeState := 1
else if trendScore <= rangeLo
    regimeState := -1
else if regimeState == 0
    regimeState := trendScore >= 0.5 ? 1 : -1

bool chaos = chop >= chopChaos and er <= erChaos and fdi >= fdiChaos

string regimeLabel = chaos ? "Chaos" : (regimeState == 1 ? "Trend" : "Range")
color  regimeCol   = chaos ? colChaos : (regimeState == 1 ? colTrend : colRange)

// ─────────────────────────────────────────────────────────────────────────────
// PERMISSIONS  (Quality outputs, symmetric — never a trade trigger)
// ─────────────────────────────────────────────────────────────────────────────

float chaosMult          = chaos ? chaosDamp : 1.0
float reversalPermission = (1.0 - trendScore) * chaosMult
float trendPermission    = trendScore * chaosMult

// ─────────────────────────────────────────────────────────────────────────────
// HIGHER-TIMEFRAME REGIME LINE  (visual context only — never a veto/gate, §3)
// ─────────────────────────────────────────────────────────────────────────────

f_htfScore() =>
    [tsV, erV, fdiV, chopV, tErV, tFdiV, tChopV] = f_regime()
    tsV

int    chartSec = timeframe.in_seconds(timeframe.period)
string htfTf    = htfMode == "Manual" ? htfManual : timeframe.from_seconds(chartSec * 4)
float  htfScore = request.security(syminfo.tickerid, htfTf, f_htfScore())

string htfReg = htfScore >= trendHi ? "Trend" : htfScore <= rangeLo ? "Range" : "Neutral"
color  htfCol = htfScore >= trendHi ? colTrend : htfScore <= rangeLo ? colRange : color.new(color.gray, 40)

// ─────────────────────────────────────────────────────────────────────────────
// PLOTS
// ─────────────────────────────────────────────────────────────────────────────

color scoreCol = color.from_gradient(trendScore, rangeLo, trendHi, colRange, colTrend)
plot(trendScore * 100.0, "Trendiness", color=chaos ? colChaos : scoreCol, linewidth=2)

plot(showHtf ? htfScore * 100.0 : na, "HTF Trendiness", color=color.new(htfCol, 30),
     linewidth=2, style=plot.style_stepline)

plot(showSensors ? tEr   * 100.0 : na, "ER (mapped)",   color=color.new(#ffb300, 35), linewidth=1)
plot(showSensors ? tFdi  * 100.0 : na, "FDI (mapped)",  color=color.new(#00bcd4, 35), linewidth=1)
plot(showSensors ? tChop * 100.0 : na, "Chop (mapped)", color=color.new(#ab47bc, 35), linewidth=1)

hline(trendHi * 100.0, "Trend Level", color=color.new(colTrend, 50), linestyle=hline.style_dashed)
hline(rangeLo * 100.0, "Range Level", color=color.new(colRange, 50), linestyle=hline.style_dashed)
hline(50.0,            "Mid",         color=color.new(color.gray, 70), linestyle=hline.style_dotted)

bgcolor(color.new(regimeCol, 90), title="Regime Tint")

// ─────────────────────────────────────────────────────────────────────────────
// REGIME-CHANGE WATCH MARKERS  (state change, not a trigger — see §7)
// ─────────────────────────────────────────────────────────────────────────────

bool toRange = regimeLabel == "Range" and regimeLabel[1] != "Range"
bool toTrend = regimeLabel == "Trend" and regimeLabel[1] != "Trend"
bool toChaos = regimeLabel == "Chaos" and regimeLabel[1] != "Chaos"

plotshape(showWatch and toRange, "→ Range (reversal window)",
     style=shape.circle, location=location.bottom, color=colRange, size=size.tiny, text="R")
plotshape(showWatch and toTrend, "→ Trend",
     style=shape.circle, location=location.top, color=colTrend, size=size.tiny, text="T")
plotshape(showWatch and toChaos, "→ Chaos",
     style=shape.xcross, location=location.top, color=colChaos, size=size.tiny, text="X")

// ─────────────────────────────────────────────────────────────────────────────
// DASHBOARD
// ─────────────────────────────────────────────────────────────────────────────

f_pos(string p) =>
    switch p
        "Top Left"     => position.top_left
        "Bottom Right" => position.bottom_right
        "Bottom Left"  => position.bottom_left
        =>                position.top_right

var table t = table.new(f_pos(tablePos), 2, 9,
     bgcolor=color.new(color.white, 5),
     border_color=color.new(color.gray, 60),
     border_width=1,
     frame_color=color.new(color.gray, 40),
     frame_width=1)

color tc = color.new(color.gray, 20)
color hd = color.new(color.gray, 90)

if showTable and barstate.islast
    table.cell(t, 0, 0, "REGIME", text_color=tc, bgcolor=hd, text_size=size.small)
    table.cell(t, 1, 0, regimeLabel, text_color=color.white, bgcolor=regimeCol, text_size=size.small)

    table.cell(t, 0, 1, "Trendiness", text_color=tc, text_size=size.small)
    table.cell(t, 1, 1, str.tostring(trendScore * 100.0, "#.0"), text_color=tc, text_size=size.small)

    table.cell(t, 0, 2, "Efficiency Ratio", text_color=tc, text_size=size.small)
    table.cell(t, 1, 2, str.tostring(er, "#.00"), text_color=tc, text_size=size.small)

    table.cell(t, 0, 3, "Fractal Dimension", text_color=tc, text_size=size.small)
    table.cell(t, 1, 3, str.tostring(fdi, "#.00"), text_color=tc, text_size=size.small)

    table.cell(t, 0, 4, "Choppiness", text_color=tc, text_size=size.small)
    table.cell(t, 1, 4, str.tostring(chop, "#.0"), text_color=tc, text_size=size.small)

    table.cell(t, 0, 5, "", bgcolor=color.new(color.gray, 80), text_size=size.small)
    table.cell(t, 1, 5, "", bgcolor=color.new(color.gray, 80), text_size=size.small)

    table.cell(t, 0, 6, "Reversal OK", text_color=tc, text_size=size.small)
    table.cell(t, 1, 6, str.tostring(reversalPermission * 100.0, "#") + "%",
         text_color=reversalPermission >= 0.5 ? colRange : tc, text_size=size.small)

    table.cell(t, 0, 7, "Trend-follow OK", text_color=tc, text_size=size.small)
    table.cell(t, 1, 7, str.tostring(trendPermission * 100.0, "#") + "%",
         text_color=trendPermission >= 0.5 ? colTrend : tc, text_size=size.small)

    if showHtf
        table.cell(t, 0, 8, "HTF " + htfTf + " (visual)", text_color=tc, text_size=size.small)
        table.cell(t, 1, 8, htfReg + " " + str.tostring(htfScore * 100.0, "#"),
             text_color=color.white, bgcolor=htfCol, text_size=size.small)

// ─────────────────────────────────────────────────────────────────────────────
// ALERTS  (regime context, not entries)
// ─────────────────────────────────────────────────────────────────────────────

bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed

alertcondition(alertOk and (regimeLabel == "Range" and regimeLabel[1] != "Range"),
     "Regime → Range", "RGC · REGIME RANGE · {{ticker}} {{interval}}")
alertcondition(alertOk and (regimeLabel == "Trend" and regimeLabel[1] != "Trend"),
     "Regime → Trend", "RGC · REGIME TREND · {{ticker}} {{interval}}")
alertcondition(alertOk and (regimeLabel == "Chaos" and regimeLabel[1] != "Chaos"),
     "Regime → Chaos", "RGC · REGIME CHAOS · {{ticker}} {{interval}}")

// ─────────────────────────────────────────────────────────────────────────────
// DEBUG LOG  (§10 — answer "why this regime?")
// ─────────────────────────────────────────────────────────────────────────────

if enableLog and barstate.isconfirmed and regimeLabel != regimeLabel[1]
    log.info("REGIME → {0} | trendScore={1} er={2} fdi={3} chop={4} | tEr={5} tFdi={6} tChop={7}",
         regimeLabel,
         str.tostring(trendScore, "#.000"),
         str.tostring(er, "#.000"), str.tostring(fdi, "#.000"), str.tostring(chop, "#.0"),
         str.tostring(tEr, "#.00"), str.tostring(tFdi, "#.00"), str.tostring(tChop, "#.00"))

Overview

Regime Classifier [WavesUnchained] normalizes and applies custom weightings to each sensor to evaluate overall market structure. The script plots the combined regime line, individual sensor values, and regime-change markers in a separate pane alongside an optional information dashboard. Users can adjust sensor lengths, weighting parameters, regime thresholds, and configure higher-timeframe overlays.

Details

Published by

casoon on GitHub

Type

Indicator, Pine Script v6

Draws

Separate pane below the chart

Category

Trend, Volatility & bands, Screeners & dashboards

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

pine-scriptpinescripttechnical-analysistrading-indicatorstrading-strategiestradingview

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Efficiency Ratio Lengthint20erLen
Fractal Dimension Lengthint30fdiLen
Choppiness Lengthint14chopLen
ER: full-trend atfloat0.55erTrendAt
ER: full-range atfloat0.20erRangeAt
FDI: full-trend atfloat1.35fdiTrendAt
FDI: full-range atfloat1.55fdiRangeAt
Chop: full-trend atfloat38.2chopTrendAt
Chop: full-range atfloat61.8chopRangeAt
Weight · Efficiency Ratiofloat1.0wEr
Weight · Fractal Dimensionfloat1.0wFdi
Weight · Choppinessfloat1.0wChop
Show all 30 settings
SettingTypeDefaultVariable
Trend enters abovefloat0.62trendHi
Range enters belowfloat0.38rangeLo
Chaos: Chop abovefloat65.0chopChaos
Chaos: ER belowfloat0.18erChaos
Chaos: FDI abovefloat1.55fdiChaos
Chaos permission multiplierfloat0.30chaosDamp
Show per-sensor linesbooltrueshowSensors
Show regime-change markersbooltrueshowWatch
Show dashboardbooltrueshowTable
Dashboard positionstringTop RighttablePos
Debug log on regime changeboolfalseenableLog
Trendcolor#00c853colTrend
Rangecolor#2962ffcolRange
Chaoscolor#d50000colChaos
Show HTF regime lineboolfalseshowHtf
HTF sourcestringAuto (4× chart)htfMode
Manual HTFtimeframe240htfManual
Alerts only on bar close (confirmed)booltruealertsOnBarClose
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

This indicator scans a pasted list of symbols and shows matching long and short setups in a table in a separate pane. It uses a Supertrend crossover, then applies an ATR or fixed-point volatility range so quiet, sideways conditions can be skipped. Optional fil

Indicator v5 TrendVolatility & bands
Updated 1 year ago · MPL-2.0 GitHub repository of Trend Following Setup - Sideways Market Skipper Scanner

Strategy-Picker

by hintheman
13 stars

Strategy-Picker evaluates market trend, momentum, and volatility across multiple timeframes (5m, 15m, and 60m) to recommend suitable options trading structures. It combines MACD, an 8-period EMA, and ADX readings with external VIX levels to suggest Put Credit

Indicator v6 TrendVolatility & bands
Updated 2 months ago · Custom licence GitHub repository of Strategy-Picker

Manias, Panics and Crashes

by MeridianAlgo
9 stars

Manias, Panics and Crashes is a separate-pane market regime indicator that quantifies market sentiment and systemic risk. It processes technical metrics including RSI, moving averages, and volume alongside optional intermarket data such as VIX volatility and c

Indicator v6 Oscillators & momentumVolatility & bands
Updated 4 months ago · MIT GitHub repository of Manias, Panics and Crashes
92 stars

This indicator classifies the market into gamma regime types (negative gamma trend, positive gamma mean-reversion, or transition) by calculating a composite score based on daily ATR ratios, session VWAP crossover frequencies, and opening range displacement. It

Indicator v5 Volatility & bandsTrend
Updated 8 weeks ago · Custom licence GitHub repository of Moon Dev GEX Regime & Trap
46 stars

This indicator uses a Hidden Markov Model (HMM) framework combined with an adaptive dual moving average engine and Directional Movement Index (DMI) features to identify market regimes (bullish, bearish, or ranging/chop). It calculates regime probabilities on e

Indicator v5 AI / machine learningTrend
Updated 7 weeks ago · None stated GitHub repository of HMM Enhanced: Regime Probability
43 stars

This indicator calculates standardized trend strength and historical volatility to estimate position sizes. It computes the differences between three user-defined EMAs standardized by price using MACD logic, while historical volatility is derived from the stan

Indicator v5 TrendVolatility & bands
Updated 1 year ago · None stated GitHub repository of Trend Strength & Volatility v2

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers