An overlay indicator that classifies the market into regimes such as trend, chop and squeeze using ADX, range efficiency, relative volume, ATR expansion and VWAP slope.
1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.
Source Code
1,902 lines of Pine Script v6 from Regime_Engine_TCO_Gatekeeper.pine, published under GPL-3.0. Keep the author's credit and licence lines when you reuse it.
Regime_Engine_TCO_Gatekeeper.pine
//@version=6
indicator(
title="Regime Engine + TCO Gatekeeper",
overlay=true,
max_labels_count=500,
max_lines_count=50,
max_boxes_count=50,
max_bars_back=2000
)
// =====================================================
// 1. ENGINE INPUTS
// =====================================================
string groupCore = "Core Settings"
int atrLen = input.int(
defval=14,
title="ATR Length",
minval=1,
group=groupCore
)
int adxLen = input.int(
defval=14,
title="ADX Length",
minval=1,
group=groupCore
)
int rvolLen = input.int(
defval=50,
title="RVOL Length",
minval=1,
group=groupCore
)
int effLen = input.int(
defval=20,
title="Range Efficiency Length",
minval=2,
group=groupCore
)
int slopeLen = input.int(
defval=10,
title="VWAP Slope Length",
minval=1,
group=groupCore
)
// =====================================================
// Regime thresholds
// =====================================================
string groupThresholds = "Regime Thresholds"
float adxTrendMin = input.float(
defval=25.0,
title="ADX Trend Minimum",
minval=0.0,
group=groupThresholds
)
float adxChopMax = input.float(
defval=18.0,
title="ADX Chop Maximum",
minval=0.0,
group=groupThresholds
)
float effTrendMin = input.float(
defval=0.55,
title="Trend Efficiency Minimum",
minval=0.0,
maxval=1.0,
step=0.05,
group=groupThresholds
)
float effChopMax = input.float(
defval=0.30,
title="Chop Efficiency Maximum",
minval=0.0,
maxval=1.0,
step=0.05,
group=groupThresholds
)
float rvolExpansion = input.float(
defval=1.25,
title="RVOL Expansion Minimum",
minval=0.0,
step=0.05,
group=groupThresholds
)
float atrExpansion = input.float(
defval=1.25,
title="ATR Expansion Multiple",
minval=0.0,
step=0.05,
group=groupThresholds
)
float atrSqzMult = input.float(
defval=0.80,
title="ATR Squeeze Multiple",
minval=0.0,
step=0.05,
group=groupThresholds
)
// =====================================================
// Relative accepted mispricing
// =====================================================
string groupMisprice = "Relative Accepted Mispricing"
bool useVWAP = input.bool(
defval=true,
title="Use VWAP In RAP",
group=groupMisprice
)
bool useEMA = input.bool(
defval=true,
title="Use EMA In RAP",
group=groupMisprice
)
int emaLen = input.int(
defval=50,
title="EMA Length For RAP",
minval=1,
group=groupMisprice
)
float misRich = input.float(
defval=1.50,
title="Rich/Cheap Threshold",
minval=0.0,
step=0.10,
group=groupMisprice
)
float misExtreme = input.float(
defval=2.50,
title="Extreme Threshold",
minval=0.0,
step=0.10,
group=groupMisprice
)
// =====================================================
// Engine router
// =====================================================
string groupRouter = "Engine Router"
int mrWeakAcceptMax = input.int(
defval=55,
title="MR Max Acceptance Score",
minval=0,
maxval=100,
group=groupRouter
)
int trendAcceptMinRouter = input.int(
defval=55,
title="Trend Min Acceptance Score",
minval=0,
maxval=100,
group=groupRouter
)
float trendSlopeMin = input.float(
defval=0.10,
title="Trend VWAP Slope Minimum",
minval=0.0,
step=0.01,
group=groupRouter
)
bool blockChop = input.bool(
defval=true,
title="Block In CHOP",
group=groupRouter
)
bool waitSqueeze = input.bool(
defval=true,
title="Wait During SQZ",
group=groupRouter
)
// =====================================================
// Display settings
// =====================================================
string groupDisplay = "Display"
bool showTable = input.bool(
defval=true,
title="Show Engine Dashboard",
group=groupDisplay
)
string dashboardPos = input.string(
defval="Top Right",
title="Dashboard Position",
options=[
"Top Right",
"Top Left",
"Bottom Right",
"Bottom Left"
],
group=groupDisplay
)
bool showBG = input.bool(
defval=true,
title="Color Background By Regime",
group=groupDisplay
)
bool showLabels = input.bool(
defval=false,
title="Show Regime Change Labels",
group=groupDisplay
)
// =====================================================
// 2. TCO VISUAL GATEKEEPER INPUTS
// =====================================================
string groupTCO = "TCO Visual Gatekeeper"
bool showUB = input.bool(
defval=true,
title="Show Unfinished Business (PDH/PDL)",
group=groupTCO
)
bool showMB = input.bool(
defval=true,
title="Show Must Break Levels",
group=groupTCO
)
bool showRT = input.bool(
defval=true,
title="Show Retracement Zones",
group=groupTCO
)
bool showPsy = input.bool(
defval=true,
title="Show Psychology Banner",
group=groupTCO
)
int swingLen = input.int(
defval=10,
title="Swing Length (Structure)",
minval=1,
group=groupTCO
)
// =====================================================
// 2B. ORDER FLOW (CVD) INPUTS
//
// Real order flow -- bid/ask depth, trade-by-trade aggressor side,
// footprint charts -- is a native TradingView platform feature (Premium+),
// built on the exchange's real tick/quote feed. Pine Script has no access
// to that data at all, scriptable or not.
//
// This is the closest legitimate approximation available in Pine: pull the
// actual lower-timeframe bars inside each chart bar via
// request.security_lower_tf(), classify each one up/down by its own
// close-vs-open, and accumulate the volume on each side into a running
// Cumulative Volume Delta (CVD). It is NOT true bid/ask delta -- a large
// passive limit order sitting at the ask still gets counted as "buy volume"
// if price ticks up through it -- but it is a materially better proxy than
// the single-bar close-position heuristic (Chaikin-style), since it is
// built from many smaller directional prints instead of one bar's shape.
//
// Pick a CVD Sub-Timeframe meaningfully lower than the chart timeframe but
// not absurdly so -- TradingView caps how much intrabar history
// request.security_lower_tf() can return, and an extreme mismatch (e.g. 1
// second sub-bars under a Daily chart) will silently return less history
// than the full period, degrading the delta calculation quietly rather
// than erroring.
// =====================================================
string groupCVD = "Order Flow (CVD)"
bool useCVDInput = input.bool(
defval=true,
title="Use CVD (Order Flow Proxy)",
group=groupCVD,
tooltip="Master toggle. When off, CVD is not computed and the divergence gate on TREND LONG/SHORT never blocks a signal."
)
string cvdSourceTfInput = input.timeframe(
defval="1",
title="CVD Sub-Timeframe",
group=groupCVD,
tooltip="Lower timeframe sampled for up/down volume classification. Must be lower than the chart timeframe. See note above on picking a reasonable gap, not an extreme one."
)
bool resetCvdDailyInput = input.bool(
defval=true,
title="Reset CVD Daily",
group=groupCVD,
tooltip="Zeroes the running CVD total at the start of each new day, the standard convention -- CVD is meant to read as \"today's net order flow,\" not an unbounded running total since the symbol's inception."
)
int cvdLookbackInput = input.int(
defval=20,
title="CVD Divergence Lookback (Bars)",
minval=5,
maxval=200,
group=groupCVD,
tooltip="Window used both for the CVD trend readout (rising/falling) and for divergence detection: compares this window's price/CVD extremes against the PRECEDING equal window, rather than matching literal swing pivots -- simpler and more robust against pivot-detection lag, at the cost of being a coarser read than a true swing-to-swing divergence."
)
bool showCvdDivergenceMarkersInput = input.bool(
defval=true,
title="Show CVD Divergence Markers",
group=groupCVD,
tooltip="Marks the bar where a fresh price/CVD divergence is first detected (edge-triggered -- one marker per divergence, not one every bar it persists)."
)
// =====================================================
// 2C. PULLBACK TARGET ZONE INPUTS
//
// Answers "where do I want price to come to" after an expansion, instead
// of just "wait." Anchored to RAP mean-reversion rather than a Fibonacci
// ratio of the swing -- VWAP-reversion has a real mechanism behind it
// (a large share of institutional execution algorithms are literally
// benchmarked to VWAP, which creates structural flow back toward it after
// a stretch away), where a fixed 38.2%/61.8% retracement of a swing does
// not have a comparable causal story. This does not replace the existing
// Fibonacci retracement zone below -- both are shown, and either one being
// in play is enough to clear the "don't chase" message.
//
// The zone is a target BAND, not a single price -- deliberately, since
// treating any single level as an exact prediction overstates what this
// (or any) retracement method can actually promise. It freezes at the
// moment a fresh EXPANSION regime begins (using that bar's RAP/ATR/bias),
// rather than recalculating every bar, so it reads as a stable level to
// watch rather than a jittery number that moves with every tick.
// =====================================================
string groupPullback = "Pullback Target Zone"
bool showPullbackZoneInput = input.bool(
defval=true,
title="Show Pullback Target Zone",
group=groupPullback,
tooltip="Master toggle. When off, the zone is not computed and only the Fibonacci retracement zone drives the psychology banner's \"ready\" state."
)
float pullbackTargetZInput = input.float(
defval=1.0,
title="Target Z (ATR From RAP)",
minval=0.0,
maxval=5.0,
step=0.05,
group=groupPullback,
tooltip="How far from RAP, in ATR, the zone is centered -- on the same side as the current stretch (e.g. above RAP for a bullish expansion), so this is a PARTIAL retracement toward fair value, not a full round-trip to it. 1.0 sits between the Rich/Cheap and Extreme thresholds by default."
)
float pullbackZoneWidthAtrInput = input.float(
defval=0.5,
title="Zone Width (ATR)",
minval=0.05,
maxval=3.0,
step=0.05,
group=groupPullback,
tooltip="Total width of the target band, in ATR, centered on Target Z. A band, not a single price -- this method estimates a reasonable zone to watch, not an exact tick."
)
// =====================================================
// 2D. VOLUME TREND INPUTS
//
// A genuinely distinct axis from RVOL and CVD, not a third measurement of
// either: RVOL reads whether THIS bar is unusually active relative to its
// own average (a snapshot); CVD reads which SIDE is winning (direction).
// Neither says whether participation itself has been rising or falling
// over the recent past. This tracks that -- the percentage change in
// average volume over a lookback window -- as a fading-breakout warning:
// thinning participation as a move continues is a classic tell distinct
// from any single quiet bar or from delta direction.
//
// CHANGE: originally shipped as informational-only (dashboard row +
// banner caution, no gating), out of concern that stacking a second hard
// volume-based block alongside CVD divergence would make trend signals
// too rare. Switched to a hard gate on request -- avoiding thin,
// institutionally-unconfirmed participation matters more here than
// signal frequency, so volumeTrendConfirms is now baked directly into
// trendLongContext/trendShortContext (see the ENGINE ROUTER section),
// the same pattern cvdConfirmsLong/cvdConfirmsShort already use.
// =====================================================
string groupVolTrend = "Volume Trend"
bool useVolumeTrendInput = input.bool(
defval=true,
title="Use Volume Trend",
group=groupVolTrend,
tooltip="Master toggle. When off, Volume Trend reads FLAT unconditionally, never blocks TREND LONG/SHORT, and the psychology banner's thinning-volume caution never fires."
)
int volumeTrendLenInput = input.int(
defval=20,
title="Volume Trend Lookback (Bars)",
minval=5,
maxval=200,
group=groupVolTrend,
tooltip="Compares today's average volume (the same smoothed series RVOL is built from) against its value this many bars ago."
)
float volumeTrendRisingMinInput = input.float(
defval=0.15,
title="Rising Threshold (% Change)",
minval=0.0,
step=0.05,
group=groupVolTrend,
tooltip="Minimum increase in average volume over the lookback, as a fraction (0.15 = 15%), to classify as RISING."
)
float volumeTrendFallingMaxInput = input.float(
defval=-0.15,
title="Falling Threshold (% Change)",
maxval=0.0,
step=0.05,
group=groupVolTrend,
tooltip="Maximum decrease in average volume over the lookback, as a fraction (-0.15 = -15%), to classify as FALLING."
)
// =====================================================
// 2E. VOLUME PROFILE INPUTS
//
// Ported from Supply_and_Demand_Zones_XL.pine, lean version: just the
// Point of Control and Value Area High/Low as three lines, not the full
// visual histogram (that script draws up to vpRows*2 boxes per refresh --
// fine as a standalone indicator, wasteful here where only the three
// resulting price levels matter).
//
// Evaluated against the DeepSupp paper (arXiv:2507.01971, see the
// quant-research-notes skill's deepsupp-attention-support-resistance.md)
// as the practical alternative: that paper's own attention-autoencoder +
// DBSCAN pipeline isn't portable to Pine at all (no ML training, matrix
// pipeline, or clustering primitive), and its reported evidence for
// beating simpler methods is weak on its own numbers. What it's actually
// chasing -- structural levels that reflect where volume concentrated
// instead of a mechanical swing pivot -- is exactly what Volume Profile
// already gives for free, using real traded volume instead of a learned
// embedding space. This sits alongside the existing swing-pivot "must
// break" lines rather than replacing them: must-break lines are about
// price structure (the last swing extreme), POC/VAH/VAL are about where
// volume actually transacted -- different evidence, both worth having on
// the chart.
// =====================================================
string groupVolProfile = "Volume Profile"
bool useVolumeProfileInput = input.bool(
defval=true,
title="Show Volume Profile (POC / VAH / VAL)",
group=groupVolProfile,
tooltip="Master toggle. When off, the profile is not computed, the dashboard row reads OFF, and Value Area no longer contributes to the \"good entry zone\" psychology check."
)
int vpRowsInput = input.int(
defval=50,
title="Price Rows",
minval=10,
maxval=100,
group=groupVolProfile,
tooltip="Number of horizontal price bins the lookback range is split into before finding the highest-volume row (POC). Higher = finer price resolution, more computation."
)
int vpLookbackInput = input.int(
defval=200,
title="Lookback (Bars)",
minval=20,
maxval=2000,
group=groupVolProfile,
tooltip="How many bars back the profile is built from. Also sets the price high/low range the rows span."
)
float vpVaPercentInput = input.float(
defval=70,
title="Value Area %",
minval=10,
maxval=95,
group=groupVolProfile,
tooltip="Share of total volume the Value Area covers, centered on the POC and expanded outward one row at a time toward whichever side has more volume. 70% is the standard convention."
)
bool showVolumeProfileLinesInput = input.bool(
defval=true,
title="Draw POC / VAH / VAL Lines",
group=groupVolProfile,
tooltip="Draws POC as a solid line and VAH/VAL as dashed lines, extending from the start of the lookback window to the current bar. Recomputed on the last bar only, same as the must-break lines, so it stays cheap."
)
// =====================================================
// 3. THEME COLORS
// =====================================================
color colorThemeBorder = color.rgb(35, 125, 255)
color colorThemeHeader = color.rgb(7, 24, 47)
color colorThemeLabel = color.rgb(8, 20, 38)
color colorThemeValue = color.rgb(86, 105, 132)
color colorThemeGrid = color.rgb(55, 75, 100)
color colorThemeText = color.white
color colorBlue = color.rgb(50, 130, 245)
color colorGreen = color.rgb(25, 195, 100)
color colorRed = color.rgb(245, 55, 80)
color colorOrange = color.rgb(245, 160, 20)
color colorPurple = color.rgb(165, 45, 195)
color colorGray = color.rgb(88, 105, 132)
// =====================================================
// 4. ENGINE CALCULATIONS
// =====================================================
float atr = ta.atr(atrLen)
float atrAvg = ta.sma(atr, 50)
float emaRAP = ta.ema(close, emaLen)
float vwapVal = ta.vwap(hlc3)
float volAvg = ta.sma(volume, rvolLen)
float rvol = 0.0
if not na(volAvg) and volAvg > 0.0
rvol := volume / volAvg
// =====================================================
// VOLUME TREND -- see the "2D" input group above for the full reasoning.
// Built from volAvg (the same smoothed series RVOL uses), so this is a
// genuinely different axis (is participation itself rising or falling
// over time) rather than a second read of the same single-bar snapshot.
// =====================================================
float volAvgPrior = volAvg[volumeTrendLenInput]
float volumeTrendChange = not na(volAvgPrior) and volAvgPrior > 0.0 ? (volAvg - volAvgPrior) / volAvgPrior : 0.0
string volumeTrendState = "FLAT"
if useVolumeTrendInput and volumeTrendChange >= volumeTrendRisingMinInput
volumeTrendState := "RISING"
else if useVolumeTrendInput and volumeTrendChange <= volumeTrendFallingMaxInput
volumeTrendState := "FALLING"
color volumeTrendColor = colorGray
if volumeTrendState == "RISING"
volumeTrendColor := colorBlue
else if volumeTrendState == "FALLING"
volumeTrendColor := colorRed
// CHANGE: was a psychology-banner-only warning; now a hard gate matching
// cvdConfirmsLong/cvdConfirmsShort's pattern below. Not direction-specific
// like those two -- thinning participation is a red flag for either side
// of a trend equally, so one boolean gates both trendLongContext and
// trendShortContext.
bool volumeTrendConfirms = not useVolumeTrendInput or volumeTrendState != "FALLING"
[plusDI, minusDI, adx] = ta.dmi(adxLen, adxLen)
float vwapSlope = vwapVal - vwapVal[slopeLen]
float vwapSlopeNorm = 0.0
if not na(atr) and atr > 0.0
vwapSlopeNorm := vwapSlope / atr
float rangeHigh = ta.highest(high, effLen)
float rangeLow = ta.lowest(low, effLen)
float rangeSpan = rangeHigh - rangeLow
float netMove = math.abs(close - close[effLen - 1])
float rangeEff = 0.0
if not na(rangeSpan) and rangeSpan > 0.0
rangeEff := math.min(netMove / rangeSpan, 1.0)
float atrRatio = 1.0
if not na(atrAvg) and atrAvg > 0.0
atrRatio := atr / atrAvg
// =====================================================
// ORDER FLOW (CVD) -- see the "2B" input group above for the full
// explanation of why this is an approximation, not real order flow.
// =====================================================
[cvdLtfClose, cvdLtfOpen, cvdLtfVolume] = request.security_lower_tf(
syminfo.tickerid,
cvdSourceTfInput,
[close, open, volume]
)
float cvdBarBuyVol = 0.0
float cvdBarSellVol = 0.0
int cvdLtfCount = array.size(cvdLtfClose)
if cvdLtfCount > 0
for cvdI = 0 to cvdLtfCount - 1
float cvdC = array.get(cvdLtfClose, cvdI)
float cvdO = array.get(cvdLtfOpen, cvdI)
float cvdV = array.get(cvdLtfVolume, cvdI)
if cvdC > cvdO
cvdBarBuyVol += cvdV
else if cvdC < cvdO
cvdBarSellVol += cvdV
else
// Sub-bar closed flat -- can't classify direction, so split the
// volume evenly rather than assigning it to either side.
cvdBarBuyVol += cvdV * 0.5
cvdBarSellVol += cvdV * 0.5
float cvdBarDelta = cvdBarBuyVol - cvdBarSellVol
bool cvdNewDay = ta.change(time("D")) != 0
var float cvd = 0.0
if resetCvdDailyInput and cvdNewDay
cvd := 0.0
cvd := cvd + cvdBarDelta
float cvdSlope = cvd - cvd[cvdLookbackInput]
string cvdState = "FLAT"
if cvdSlope > 0
cvdState := "RISING"
else if cvdSlope < 0
cvdState := "FALLING"
color cvdColor = colorGray
if cvdState == "RISING"
cvdColor := colorBlue
else if cvdState == "FALLING"
cvdColor := colorRed
// ── Divergence: this window's price/CVD extreme vs the PRECEDING equal
// window's extreme (not literal swing-pivot matching -- see tooltip). ──
float cvdPriceHighest = ta.highest(high, cvdLookbackInput)
float cvdPriceHighestPrev = ta.highest(high, cvdLookbackInput)[cvdLookbackInput]
float cvdHighest = ta.highest(cvd, cvdLookbackInput)
float cvdHighestPrev = ta.highest(cvd, cvdLookbackInput)[cvdLookbackInput]
float cvdPriceLowest = ta.lowest(low, cvdLookbackInput)
float cvdPriceLowestPrev = ta.lowest(low, cvdLookbackInput)[cvdLookbackInput]
float cvdLowest = ta.lowest(cvd, cvdLookbackInput)
float cvdLowestPrev = ta.lowest(cvd, cvdLookbackInput)[cvdLookbackInput]
// Bearish: price pressed to a fresh high over the window but CVD failed to
// confirm it -- the rally isn't backed by net buying, a classic absorption
// tell right where a false breakout would show up.
bool bearishCvdDivergence =
useCVDInput and
cvdPriceHighest > cvdPriceHighestPrev and
cvdHighest <= cvdHighestPrev
// Bullish: mirror image at the lows.
bool bullishCvdDivergence =
useCVDInput and
cvdPriceLowest < cvdPriceLowestPrev and
cvdLowest >= cvdLowestPrev
bool cvdConfirmsLong = not useCVDInput or not bearishCvdDivergence
bool cvdConfirmsShort = not useCVDInput or not bullishCvdDivergence
string cvdDivergenceLabel = "NONE"
color cvdDivergenceColor = colorGray
if bearishCvdDivergence
cvdDivergenceLabel := "BEARISH"
cvdDivergenceColor := colorRed
else if bullishCvdDivergence
cvdDivergenceLabel := "BULLISH"
cvdDivergenceColor := colorGreen
// Edge-triggered: fire the marker once, on the bar the divergence first
// appears, not on every bar it continues to read true.
bool freshBearishCvdDivergence = bearishCvdDivergence and not bearishCvdDivergence[1]
bool freshBullishCvdDivergence = bullishCvdDivergence and not bullishCvdDivergence[1]
if showCvdDivergenceMarkersInput and freshBearishCvdDivergence
label.new(
x=bar_index,
y=high + atr * 0.50,
text="CVD DIV",
style=label.style_label_down,
color=colorRed,
textcolor=color.white,
size=size.small
)
if showCvdDivergenceMarkersInput and freshBullishCvdDivergence
label.new(
x=bar_index,
y=low - atr * 0.50,
text="CVD DIV",
style=label.style_label_up,
color=colorGreen,
textcolor=color.white,
size=size.small
)
// =====================================================
// Relative Accepted Price
// =====================================================
float rap = na
if useVWAP and useEMA
rap := vwapVal * 0.60 + emaRAP * 0.40
else if useVWAP
rap := vwapVal
else if useEMA
rap := emaRAP
else
rap := vwapVal
float rapZ = 0.0
if not na(atr) and atr > 0.0 and not na(rap)
rapZ := (close - rap) / atr
string mispriceLabel = "FAIR"
if rapZ >= misExtreme
mispriceLabel := "EXT RICH"
else if rapZ >= misRich
mispriceLabel := "RICH"
else if rapZ <= -misExtreme
mispriceLabel := "EXT CHEAP"
else if rapZ <= -misRich
mispriceLabel := "CHEAP"
color mispriceColor = colorGray
if rapZ >= misExtreme
mispriceColor := colorRed
else if rapZ >= misRich
mispriceColor := colorOrange
else if rapZ <= -misExtreme
mispriceColor := color.rgb(0, 225, 130)
else if rapZ <= -misRich
mispriceColor := color.teal
// =====================================================
// Directional bias
// =====================================================
bool bullBiasRaw = plusDI > minusDI and close > rap
bool bearBiasRaw = minusDI > plusDI and close < rap
string bias = "NEUTRAL"
if bullBiasRaw
bias := "BULL"
else if bearBiasRaw
bias := "BEAR"
color biasColor = colorGray
if bias == "BULL"
biasColor := colorBlue
else if bias == "BEAR"
biasColor := colorRed
// =====================================================
// Acceptance score
// =====================================================
int closeAwayScore = math.abs(rapZ) > 0.75 ? 20 : 0
int rvolScore = rvol > 1.0 ? 20 : 0
int adxScore = adx > 20.0 ? 20 : 0
int slopeScore = math.abs(vwapSlopeNorm) > 0.10 ? 20 : 0
int diScore = math.abs(plusDI - minusDI) > 5.0 ? 20 : 0
int acceptScore =
closeAwayScore +
rvolScore +
adxScore +
slopeScore +
diScore
string acceptLabel = "WEAK"
if acceptScore >= 70
acceptLabel := "STRONG"
else if acceptScore >= 45
acceptLabel := "MIXED"
color acceptColor = colorRed
if acceptScore >= 70
acceptColor := colorBlue
else if acceptScore >= 45
acceptColor := colorOrange
// =====================================================
// Volatility state
// =====================================================
string volState = "NORMAL"
if atrRatio >= atrExpansion and rvol >= rvolExpansion
volState := "EXPANSION"
else if atrRatio <= atrSqzMult
volState := "SQZ"
color volColor = colorGray
if volState == "EXPANSION"
volColor := colorRed
else if volState == "SQZ"
volColor := colorOrange
// =====================================================
// Regime classification
// =====================================================
bool isTrend =
adx >= adxTrendMin and
rangeEff >= effTrendMin and
math.abs(vwapSlopeNorm) > 0.10 and
acceptScore >= 45
bool isChop =
adx <= adxChopMax and
rangeEff <= effChopMax and
rvol < 1.0
bool isExpansion =
atrRatio >= atrExpansion and
rvol >= rvolExpansion and
rangeEff >= 0.45
bool isSqueeze =
atrRatio <= atrSqzMult and
rangeEff <= 0.45
string regime = "RANGE"
if isExpansion
regime := "EXPANSION"
else if isTrend
regime := "TREND"
else if isChop
regime := "CHOP"
else if isSqueeze
regime := "SQZ"
color regimeColor = colorOrange
if regime == "TREND"
regimeColor := colorBlue
else if regime == "CHOP"
regimeColor := colorRed
else if regime == "EXPANSION"
regimeColor := colorPurple
else if regime == "SQZ"
regimeColor := colorGray
// =====================================================
// PULLBACK TARGET ZONE
// See the "2C" input group above for the full reasoning. Freezes on the
// bar a fresh EXPANSION begins, using THAT bar's rap/atr/bias -- not
// recalculated every bar afterward, so it reads as a stable level.
// =====================================================
bool freshExpansion = regime == "EXPANSION" and regime[1] != "EXPANSION"
var float pullbackZoneTop = na
var float pullbackZoneBottom = na
var int pullbackZoneAnchorBar = na
var string pullbackZoneBiasAtSet = ""
if showPullbackZoneInput and freshExpansion and bias != "NEUTRAL" and not na(rap) and not na(atr)
float pullbackSign = bias == "BULL" ? 1.0 : -1.0
float pullbackTargetPrice = rap + atr * pullbackTargetZInput * pullbackSign
float pullbackHalfWidth = atr * pullbackZoneWidthAtrInput / 2.0
pullbackZoneTop := pullbackTargetPrice + pullbackHalfWidth
pullbackZoneBottom := pullbackTargetPrice - pullbackHalfWidth
pullbackZoneAnchorBar := bar_index
pullbackZoneBiasAtSet := bias
// Invalidate the zone if the trend that produced it has since reversed --
// a pullback target for a bull expansion means nothing once bias has
// flipped bearish.
if pullbackZoneBiasAtSet != "" and bias != "NEUTRAL" and bias != pullbackZoneBiasAtSet
pullbackZoneTop := na
pullbackZoneBottom := na
pullbackZoneAnchorBar := na
pullbackZoneBiasAtSet := ""
bool inPullbackZone =
showPullbackZoneInput and
not na(pullbackZoneTop) and
not na(pullbackZoneBottom) and
close <= pullbackZoneTop and
close >= pullbackZoneBottom
// =====================================================
// Volume Profile (POC / VAH / VAL)
// See the "2E" input group above for the full reasoning. Lean port of
// Supply_and_Demand_Zones_XL.pine's volume profile: bins the lookback
// range into rows, sums each row's overlapping volume, takes the
// highest-volume row as POC, then expands outward one row at a time
// toward whichever side has more volume until the Value Area % is
// covered. Recomputed on the last bar only -- the nested loop is
// vpRowsInput * vpLookbackInput work in the worst case, too expensive to
// run every bar. Placed before the dashboard/router (rather than down in
// the visual layer with the must-break lines) so vpPOC/vpVAH/vpVAL and
// inValueArea are fresh -- not one tick stale -- by the time the
// dashboard row and psychology check below read them.
// =====================================================
var line vpPocLine = na
var line vpVahLine = na
var line vpValLine = na
var label vpPocLabel = na
var float vpPOC = na
var float vpVAH = na
var float vpVAL = na
if barstate.islast
if not na(vpPocLine)
line.delete(vpPocLine)
vpPocLine := na
if not na(vpVahLine)
line.delete(vpVahLine)
vpVahLine := na
if not na(vpValLine)
line.delete(vpValLine)
vpValLine := na
if not na(vpPocLabel)
label.delete(vpPocLabel)
vpPocLabel := na
vpPOC := na
vpVAH := na
vpVAL := na
if useVolumeProfileInput
int vpUse = math.min(vpLookbackInput, bar_index + 1)
float vpRangeHigh = na
float vpRangeLow = na
for i = 0 to vpUse - 1
float hh = high[i]
float ll = low[i]
vpRangeHigh := na(vpRangeHigh) ? hh : math.max(vpRangeHigh, hh)
vpRangeLow := na(vpRangeLow) ? ll : math.min(vpRangeLow, ll)
if not na(vpRangeHigh) and not na(vpRangeLow) and vpRangeHigh > vpRangeLow
float vpRowH = (vpRangeHigh - vpRangeLow) / vpRowsInput
array<float> vpVol = array.new<float>(vpRowsInput, 0.0)
for i = 0 to vpUse - 1
float bH = high[i]
float bL = low[i]
float bVol = nz(volume[i])
float bRange = bH - bL
if bRange <= 0
int idx = math.max(0, math.min(vpRowsInput - 1, int((close[i] - vpRangeLow) / vpRowH)))
array.set(vpVol, idx, array.get(vpVol, idx) + bVol)
else
for r = 0 to vpRowsInput - 1
float rLow = vpRangeLow + r * vpRowH
float rHigh = rLow + vpRowH
float overlap = math.min(bH, rHigh) - math.max(bL, rLow)
if overlap > 0
array.set(vpVol, r, array.get(vpVol, r) + bVol * overlap / bRange)
float vpMaxVol = 0.0
int vpPocIdx = 0
float vpGrandTot = 0.0
for r = 0 to vpRowsInput - 1
float t = array.get(vpVol, r)
vpGrandTot += t
if t > vpMaxVol
vpMaxVol := t
vpPocIdx := r
float vpVaSum = array.get(vpVol, vpPocIdx)
int vpUpPtr = vpPocIdx
int vpDnPtr = vpPocIdx
while vpVaSum < vpGrandTot * (vpVaPercentInput / 100.0) and (vpUpPtr < vpRowsInput - 1 or vpDnPtr > 0)
float va = vpUpPtr < vpRowsInput - 1 ? array.get(vpVol, vpUpPtr + 1) : -1.0
float vb = vpDnPtr > 0 ? array.get(vpVol, vpDnPtr - 1) : -1.0
if va >= vb
vpUpPtr += 1
vpVaSum += va
else
vpDnPtr -= 1
vpVaSum += vb
vpPOC := vpRangeLow + vpPocIdx * vpRowH + vpRowH / 2
vpVAH := vpRangeLow + (vpUpPtr + 1) * vpRowH
vpVAL := vpRangeLow + vpDnPtr * vpRowH
if showVolumeProfileLinesInput
int vpLineX1 = bar_index - vpUse + 1
vpPocLine := line.new(x1=vpLineX1, y1=vpPOC, x2=bar_index + 5, y2=vpPOC, color=colorOrange, width=2, style=line.style_solid)
vpPocLabel := label.new(
x=bar_index + 5,
y=vpPOC,
text="POC",
color=color.new(color.white, 100),
textcolor=colorOrange,
style=label.style_label_left,
size=size.small
)
vpVahLine := line.new(x1=vpLineX1, y1=vpVAH, x2=bar_index + 5, y2=vpVAH, color=colorGray, width=1, style=line.style_dashed)
vpValLine := line.new(x1=vpLineX1, y1=vpVAL, x2=bar_index + 5, y2=vpVAL, color=colorGray, width=1, style=line.style_dashed)
bool inValueArea =
useVolumeProfileInput and
not na(vpVAH) and
not na(vpVAL) and
close <= vpVAH and
close >= vpVAL
// =====================================================
// 5. ENGINE ROUTER
// =====================================================
bool richWeak =
rapZ >= misRich and
acceptScore < mrWeakAcceptMax
bool cheapWeak =
rapZ <= -misRich and
acceptScore < mrWeakAcceptMax
// cvdConfirmsLong/cvdConfirmsShort/volumeTrendConfirms: pure ADDITIONAL
// gates, not folded into acceptScore -- keeps "is this regime tradeable"
// (the score) separate from "does order flow/participation confirm this"
// (CVD, volume trend), matching the architecture's existing when/where
// split rather than merging every signal into one number. A bearish CVD
// divergence (rally not backed by net buying) blocks TREND LONG; the
// mirror case blocks TREND SHORT. Falling volume trend blocks BOTH
// equally -- thinning participation is a red flag regardless of
// direction, not something to read as a directional tell the way CVD
// divergence is. With the respective toggles off, each gate passes
// unconditionally -- zero behavior change from before either module
// existed.
bool trendLongContext =
(regime == "TREND" or regime == "EXPANSION") and
bias == "BULL" and
close > rap and
plusDI > minusDI and
vwapSlopeNorm > trendSlopeMin and
acceptScore >= trendAcceptMinRouter and
cvdConfirmsLong and
volumeTrendConfirms
bool trendShortContext =
(regime == "TREND" or regime == "EXPANSION") and
bias == "BEAR" and
close < rap and
minusDI > plusDI and
vwapSlopeNorm < -trendSlopeMin and
acceptScore >= trendAcceptMinRouter and
cvdConfirmsShort and
volumeTrendConfirms
bool mrLongContext =
regime == "RANGE" and
cheapWeak and
bias != "BEAR"
bool mrShortContext =
regime == "RANGE" and
richWeak and
bias != "BULL"
string engine = "WAIT"
if blockChop and regime == "CHOP"
engine := "BLOCK"
else if waitSqueeze and regime == "SQZ"
engine := "WAIT BREAKOUT"
else if trendLongContext
engine := "TREND LONG"
else if trendShortContext
engine := "TREND SHORT"
else if mrLongContext
engine := "BB/MR LONG"
else if mrShortContext
engine := "BB/MR SHORT"
else if regime == "EXPANSION"
engine := "TREND WATCH"
color engineColor = colorGray
if engine == "BLOCK"
engineColor := colorRed
else if str.contains(engine, "TREND")
engineColor := colorBlue
else if str.contains(engine, "BB/MR")
engineColor := colorOrange
else if engine == "WAIT BREAKOUT"
engineColor := colorPurple
string riskMode = "NORMAL"
if regime == "CHOP"
riskMode := "BLOCKED"
else if regime == "SQZ"
riskMode := "REDUCED"
else if rvol < 0.70
riskMode := "REDUCED"
color riskColor = colorGreen
if riskMode == "BLOCKED"
riskColor := colorRed
else if riskMode == "REDUCED"
riskColor := colorOrange
// =====================================================
// 6. ENGINE PLOTS
// =====================================================
// FIX (chart clutter): mobile's Style tab only exposes one combined
// visibility toggle per plot (line + price-scale label together), so
// there's no way to keep the line but drop the redundant label from
// settings alone on mobile. display.all - display.price_scale removes
// just the price-scale label in code instead -- confirmed against
// TradingView's own docs, which give the identical pattern
// (display.all - display.pane) as their own example. All three plots
// (RAP, VWAP, EMA RAP) have their price-scale labels off -- the
// dashboard's MISPRICING/RAP Z rows already cover these values, so the
// axis labels were pure duplication. The lines themselves are unaffected.
plot(
series=rap,
title="RAP",
color=color.white,
linewidth=2,
display=display.all - display.price_scale
)
plot(
series=vwapVal,
title="VWAP",
color=color.aqua,
linewidth=1,
display=display.all - display.price_scale
)
plot(
series=emaRAP,
title="EMA RAP",
color=color.fuchsia,
linewidth=1,
display=display.all - display.price_scale
)
color backgroundColor = na
if showBG
backgroundColor := color.new(regimeColor, 88)
bgcolor(backgroundColor)
// =====================================================
// Optional regime-change labels
// =====================================================
bool regimeChanged = not na(regime[1]) and regime != regime[1]
if showLabels and regimeChanged
label.new(
x=bar_index,
y=high + atr * 0.30,
text=regime,
style=label.style_label_down,
color=regimeColor,
textcolor=color.white,
size=size.small
)
// =====================================================
// 7. ENGINE DASHBOARD
// =====================================================
string dashboardPosition = position.top_right
if dashboardPos == "Top Left"
dashboardPosition := position.top_left
else if dashboardPos == "Bottom Right"
dashboardPosition := position.bottom_right
else if dashboardPos == "Bottom Left"
dashboardPosition := position.bottom_left
var table dash = table.new(
position.top_right,
2,
19,
bgcolor=colorThemeLabel,
frame_color=colorThemeBorder,
frame_width=2,
border_color=colorThemeGrid,
border_width=1
)
f_dashboardRow(
table tableId,
int row,
string labelText,
string valueText,
color valueBackground
) =>
table.cell(
table_id=tableId,
column=0,
row=row,
text=labelText,
text_color=colorThemeText,
bgcolor=colorThemeLabel,
text_size=size.small,
text_halign=text.align_left
)
table.cell(
table_id=tableId,
column=1,
row=row,
text=valueText,
text_color=colorThemeText,
bgcolor=valueBackground,
text_size=size.small,
text_halign=text.align_right
)
if barstate.islast
table.set_position(dash, dashboardPosition)
if showTable
table.cell(
table_id=dash,
column=0,
row=0,
text="REGIME ENGINE",
text_color=colorThemeText,
bgcolor=colorThemeHeader,
text_size=size.small,
text_halign=text.align_left
)
string headerEngineText = engine
if engine == "BLOCK"
headerEngineText := "NO TRADE"
table.cell(
table_id=dash,
column=1,
row=0,
text=headerEngineText,
text_color=colorThemeText,
bgcolor=engineColor,
text_size=size.small,
text_halign=text.align_right
)
f_dashboardRow(
dash,
1,
"REGIME",
regime,
regimeColor
)
f_dashboardRow(
dash,
2,
"BIAS",
bias,
biasColor
)
f_dashboardRow(
dash,
3,
"ACCEPTANCE",
acceptLabel + " " + str.tostring(acceptScore),
acceptColor
)
f_dashboardRow(
dash,
4,
"VOLATILITY",
volState,
volColor
)
f_dashboardRow(
dash,
5,
"MISPRICING",
mispriceLabel,
mispriceColor
)
f_dashboardRow(
dash,
6,
"RAP Z",
str.tostring(rapZ, "#.00"),
mispriceColor
)
color adxColor = colorGray
if adx >= adxTrendMin
adxColor := colorBlue
else if adx <= adxChopMax
adxColor := colorRed
f_dashboardRow(
dash,
7,
"ADX",
str.tostring(adx, "#.0"),
adxColor
)
color diColor = colorGray
if plusDI > minusDI
diColor := colorBlue
else if minusDI > plusDI
diColor := colorRed
string diText =
str.tostring(plusDI, "#.0") +
" / " +
str.tostring(minusDI, "#.0")
f_dashboardRow(
dash,
8,
"DI + / -",
diText,
diColor
)
color rvolColor = colorThemeValue
if rvol >= rvolExpansion
rvolColor := colorBlue
else if rvol < 0.70
rvolColor := colorOrange
f_dashboardRow(
dash,
9,
"RVOL",
str.tostring(rvol, "#.00"),
rvolColor
)
color atrRatioColor = colorThemeValue
if atrRatio >= atrExpansion
atrRatioColor := colorRed
else if atrRatio <= atrSqzMult
atrRatioColor := colorOrange
f_dashboardRow(
dash,
10,
"ATR RATIO",
str.tostring(atrRatio, "#.00"),
atrRatioColor
)
color efficiencyColor = colorThemeValue
if rangeEff >= effTrendMin
efficiencyColor := colorBlue
else if rangeEff <= effChopMax
efficiencyColor := colorRed
f_dashboardRow(
dash,
11,
"EFFICIENCY",
str.tostring(rangeEff, "#.00"),
efficiencyColor
)
color slopeColor = colorThemeValue
if vwapSlopeNorm > trendSlopeMin
slopeColor := colorBlue
else if vwapSlopeNorm < -trendSlopeMin
slopeColor := colorRed
f_dashboardRow(
dash,
12,
"VWAP SLOPE",
str.tostring(vwapSlopeNorm, "#.00"),
slopeColor
)
f_dashboardRow(
dash,
13,
"RISK",
riskMode,
riskColor
)
f_dashboardRow(
dash,
14,
"CVD",
useCVDInput ? cvdState : "OFF",
useCVDInput ? cvdColor : colorGray
)
f_dashboardRow(
dash,
15,
"CVD DIVERGENCE",
useCVDInput ? cvdDivergenceLabel : "OFF",
useCVDInput ? cvdDivergenceColor : colorGray
)
string pullbackZoneText =
not showPullbackZoneInput
? "OFF"
: na(pullbackZoneTop) or na(pullbackZoneBottom)
? "N/A"
: str.tostring(pullbackZoneBottom, format.mintick) + "-" + str.tostring(pullbackZoneTop, format.mintick)
color pullbackZoneRowColor =
not showPullbackZoneInput
? colorGray
: inPullbackZone
? colorGreen
: colorThemeValue
f_dashboardRow(
dash,
16,
"PULLBACK ZONE",
pullbackZoneText,
pullbackZoneRowColor
)
string volumeTrendText =
not useVolumeTrendInput
? "OFF"
: volumeTrendState + " " + (volumeTrendChange >= 0 ? "+" : "") + str.tostring(volumeTrendChange * 100, "#.0") + "%"
f_dashboardRow(
dash,
17,
"VOLUME TREND",
volumeTrendText,
useVolumeTrendInput ? volumeTrendColor : colorGray
)
// NOTE (CE10013 fix): a single 4-level nested ternary here
// ("OFF" -> "N/A" -> above/below -> in value area) hit Pine's
// continuation-parsing limit on ternary depth -- confirmed by
// compiler error, not a false positive from the usual string-
// literal or "=>" scan classes. Rewritten as an if/else-if chain
// instead, matching how regime/bias/cvdState/mispriceLabel etc.
// are already built elsewhere in this file.
string vpDashText = "OFF"
color vpDashColor = colorGray
if useVolumeProfileInput
if na(vpVAH) or na(vpVAL)
vpDashText := "N/A"
vpDashColor := colorThemeValue
else if close > vpVAH
vpDashText := "ABOVE VAH " + str.tostring(vpVAH, format.mintick)
vpDashColor := colorThemeValue
else if close < vpVAL
vpDashText := "BELOW VAL " + str.tostring(vpVAL, format.mintick)
vpDashColor := colorThemeValue
else
vpDashText := "IN VALUE AREA"
vpDashColor := colorGreen
f_dashboardRow(
dash,
18,
"VOL PROFILE",
vpDashText,
vpDashColor
)
else
table.clear(
table_id=dash,
start_column=0,
start_row=0,
end_column=1,
end_row=18
)
// =====================================================
// 8. TCO VISUAL LAYER
// =====================================================
// Previous-day unfinished business
float pdh = request.security(
symbol=syminfo.tickerid,
timeframe="D",
expression=high[1],
gaps=barmerge.gaps_off,
lookahead=barmerge.lookahead_off
)
float pdl = request.security(
symbol=syminfo.tickerid,
timeframe="D",
expression=low[1],
gaps=barmerge.gaps_off,
lookahead=barmerge.lookahead_off
)
// Persistent PDH/PDL objects
var line linePDH = na
var line linePDL = na
var label labelPDH = na
var label labelPDL = na
if barstate.islast
if not na(linePDH)
line.delete(linePDH)
linePDH := na
if not na(linePDL)
line.delete(linePDL)
linePDL := na
if not na(labelPDH)
label.delete(labelPDH)
labelPDH := na
if not na(labelPDL)
label.delete(labelPDL)
labelPDL := na
if showUB and not na(pdh) and not na(pdl)
linePDH := line.new(
x1=bar_index - 10,
y1=pdh,
x2=bar_index + 10,
y2=pdh,
color=color.new(color.blue, 30),
style=line.style_dashed
)
linePDL := line.new(
x1=bar_index - 10,
y1=pdl,
x2=bar_index + 10,
y2=pdl,
color=color.new(color.blue, 30),
style=line.style_dashed
)
labelPDH := label.new(
x=bar_index + 10,
y=pdh,
text="PDH",
color=color.new(color.white, 100),
style=label.style_label_left,
textcolor=color.blue,
size=size.small
)
labelPDL := label.new(
x=bar_index + 10,
y=pdl,
text="PDL",
color=color.new(color.white, 100),
style=label.style_label_left,
textcolor=color.blue,
size=size.small
)
// =====================================================
// Market structure and must-break levels
// =====================================================
float pivotHigh = ta.pivothigh(
source=high,
leftbars=swingLen,
rightbars=swingLen
)
float pivotLow = ta.pivotlow(
source=low,
leftbars=swingLen,
rightbars=swingLen
)
var float lastPivotHigh = na
var float lastPivotLow = na
var int lastPivotHighIndex = na
var int lastPivotLowIndex = na
if not na(pivotHigh)
lastPivotHigh := pivotHigh
lastPivotHighIndex := bar_index - swingLen
if not na(pivotLow)
lastPivotLow := pivotLow
lastPivotLowIndex := bar_index - swingLen
var line mustBreakHighLine = na
var line mustBreakLowLine = na
var label mustBreakHighLabel = na
var label mustBreakLowLabel = na
if barstate.islast
if not na(mustBreakHighLine)
line.delete(mustBreakHighLine)
mustBreakHighLine := na
if not na(mustBreakLowLine)
line.delete(mustBreakLowLine)
mustBreakLowLine := na
if not na(mustBreakHighLabel)
label.delete(mustBreakHighLabel)
mustBreakHighLabel := na
if not na(mustBreakLowLabel)
label.delete(mustBreakLowLabel)
mustBreakLowLabel := na
bool validPivotHigh =
not na(lastPivotHigh) and
not na(lastPivotHighIndex)
bool validPivotLow =
not na(lastPivotLow) and
not na(lastPivotLowIndex)
bool drawMustBreakHigh =
showMB and
validPivotHigh and
(bias == "BEAR" or bias == "NEUTRAL")
bool drawMustBreakLow =
showMB and
validPivotLow and
(bias == "BULL" or bias == "NEUTRAL")
if drawMustBreakHigh
mustBreakHighLine := line.new(
x1=lastPivotHighIndex,
y1=lastPivotHigh,
x2=bar_index + 5,
y2=lastPivotHigh,
color=colorRed,
width=2
)
mustBreakHighLabel := label.new(
x=bar_index + 5,
y=lastPivotHigh,
text="MUST BREAK",
color=color.new(color.white, 100),
textcolor=colorRed,
style=label.style_label_left,
size=size.small
)
if drawMustBreakLow
mustBreakLowLine := line.new(
x1=lastPivotLowIndex,
y1=lastPivotLow,
x2=bar_index + 5,
y2=lastPivotLow,
color=colorGreen,
width=2
)
mustBreakLowLabel := label.new(
x=bar_index + 5,
y=lastPivotLow,
text="MUST BREAK",
color=color.new(color.white, 100),
textcolor=colorGreen,
style=label.style_label_left,
size=size.small
)
// =====================================================
// Retracement zones
// =====================================================
var box retracementBox = na
bool inRetracement = false
bool validSwing =
not na(lastPivotHigh) and
not na(lastPivotLow) and
not na(lastPivotHighIndex) and
not na(lastPivotLowIndex)
if validSwing
float fib382 = na
float fib618 = na
color retracementColor = na
float swingRange = math.abs(lastPivotHigh - lastPivotLow)
if lastPivotHighIndex > lastPivotLowIndex
fib382 := lastPivotHigh - swingRange * 0.382
fib618 := lastPivotHigh - swingRange * 0.618
retracementColor := color.new(colorBlue, 85)
inRetracement :=
close <= fib382 and
close >= fib618
else
fib382 := lastPivotLow + swingRange * 0.382
fib618 := lastPivotLow + swingRange * 0.618
retracementColor := color.new(colorRed, 85)
inRetracement :=
close >= fib382 and
close <= fib618
if barstate.islast
if not na(retracementBox)
box.delete(retracementBox)
retracementBox := na
if showRT
float zoneTop = math.max(fib382, fib618)
float zoneBottom = math.min(fib382, fib618)
int zoneLeft = math.min(
lastPivotHighIndex,
lastPivotLowIndex
)
retracementBox := box.new(
left=zoneLeft,
top=zoneTop,
right=bar_index + 5,
bottom=zoneBottom,
border_color=color.new(retracementColor, 100),
bgcolor=retracementColor
)
else
if barstate.islast and not na(retracementBox)
box.delete(retracementBox)
retracementBox := na
// =====================================================
// Pullback target zone box
// =====================================================
var box pullbackZoneBoxDrawn = na
var label pullbackZoneLabel = na
if barstate.islast
if not na(pullbackZoneBoxDrawn)
box.delete(pullbackZoneBoxDrawn)
pullbackZoneBoxDrawn := na
if not na(pullbackZoneLabel)
label.delete(pullbackZoneLabel)
pullbackZoneLabel := na
if showPullbackZoneInput and not na(pullbackZoneTop) and not na(pullbackZoneBottom) and not na(pullbackZoneAnchorBar)
color pullbackBoxColor = pullbackZoneBiasAtSet == "BULL" ? color.new(colorBlue, 82) : color.new(colorRed, 82)
pullbackZoneBoxDrawn := box.new(
left=pullbackZoneAnchorBar,
top=pullbackZoneTop,
right=bar_index + 5,
bottom=pullbackZoneBottom,
border_color=color.new(pullbackBoxColor, 40),
bgcolor=pullbackBoxColor
)
pullbackZoneLabel := label.new(
x=bar_index + 5,
y=(pullbackZoneTop + pullbackZoneBottom) / 2,
text="PULLBACK ZONE",
color=color.new(color.white, 100),
textcolor=pullbackZoneBiasAtSet == "BULL" ? colorBlue : colorRed,
style=label.style_label_left,
size=size.small
)
// Any of the three clearing counts as "price has come to you" -- Fibonacci
// (swing-based), the RAP pullback zone (mean-reversion-based), or being
// back inside the Volume Profile's Value Area (where volume actually
// transacted, not just where price happened to swing).
bool atGoodEntryZone = inRetracement or inPullbackZone or inValueArea
// =====================================================
// 9. PSYCHOLOGY GATEKEEPER
// =====================================================
string psychologyMessage = "EVALUATING..."
color psychologyColor = colorGray
bool isTrendEntry =
engine == "TREND LONG" or
engine == "TREND SHORT"
if engine == "BLOCK"
psychologyMessage := "WAIT\nNO EDGE / CHOP"
psychologyColor := colorRed
else if engine == "WAIT BREAKOUT"
psychologyMessage := "WAIT\nSQUEEZE COMPRESSION"
psychologyColor := colorOrange
else if engine == "TREND WATCH" and (bearishCvdDivergence or bullishCvdDivergence) and useVolumeTrendInput and volumeTrendState == "FALLING"
// Both gates vetoed this trend context -- name both causes rather than
// picking one, since either alone would already explain the block and
// seeing both together is a stronger signal to actually stay out.
psychologyMessage := "CAUTION\nCVD DIVERGENCE + THINNING VOLUME"
psychologyColor := colorOrange
else if engine == "TREND WATCH" and (bearishCvdDivergence or bullishCvdDivergence)
// TREND WATCH is exactly the state a divergence-blocked trend context
// falls through to (cvdConfirmsLong/Short already vetoed
// trendLongContext/trendShortContext upstream) -- so a divergence
// showing up here specifically means "this expansion isn't backed by
// order flow," worth a distinct message from generic "no commitment."
psychologyMessage := "CAUTION\nCVD DIVERGENCE -- POSSIBLE EXHAUSTION"
psychologyColor := colorOrange
else if engine == "TREND WATCH" and useVolumeTrendInput and volumeTrendState == "FALLING"
// CHANGE: volumeTrendConfirms is now baked into trendLongContext/
// trendShortContext the same way cvdConfirmsLong/Short are, so a
// falling-volume block lands here too instead of only reaching an
// already-live TREND LONG/SHORT the way the old soft-warning version did.
psychologyMessage := "CAUTION\nTHINNING VOLUME -- NO INSTITUTIONAL FOLLOW-THROUGH"
psychologyColor := colorOrange
else if engine == "WAIT" or engine == "TREND WATCH"
psychologyMessage := "WAIT\nNO COMMITMENT"
psychologyColor := colorGray
else if isTrendEntry and not atGoodEntryZone
// atGoodEntryZone = inRetracement (Fibonacci) OR inPullbackZone (RAP
// mean-reversion) OR inValueArea (Volume Profile) -- any one method
// clearing is enough to stop "chasing." When none have, show the
// RAP-based zone's actual price range if one exists (the Fibonacci and
// Value Area zones have no single frozen range to quote the same way,
// since both are recalculated live rather than frozen on a trigger
// bar).
string pullbackRangeText =
not na(pullbackZoneTop) and not na(pullbackZoneBottom)
? "\n~" + str.tostring(pullbackZoneBottom, format.mintick) + "-" + str.tostring(pullbackZoneTop, format.mintick)
: ""
psychologyMessage := "DON'T CHASE\nLET PRICE COME TO YOU" + pullbackRangeText
psychologyColor := color.yellow
else
// REMOVED (dead code after the CVD-match change above): a branch here
// checking "isTrendEntry and volumeTrendState == FALLING" can no
// longer fire -- volumeTrendConfirms is now baked into
// trendLongContext/trendShortContext, so engine can never equal TREND
// LONG/SHORT while volume trend reads FALLING; that case is caught by
// the TREND WATCH branches above instead.
psychologyMessage := "HIGH QUALITY SETUP\nREADY TO EVALUATE"
psychologyColor := colorGreen
var table psychologyTable = table.new(
position.bottom_center,
1,
1,
bgcolor=color.new(color.black, 20),
frame_color=colorThemeBorder,
frame_width=1,
border_color=colorThemeGrid,
border_width=1
)
if barstate.islast
if showPsy
table.cell(
table_id=psychologyTable,
column=0,
row=0,
text=psychologyMessage,
text_color=psychologyColor,
bgcolor=colorThemeHeader,
text_size=size.large,
text_font_family=font.family_monospace,
text_halign=text.align_center
)
else
table.clear(
table_id=psychologyTable,
start_column=0,
start_row=0,
end_column=0,
end_row=0
)
Overview
Regime Engine + TCO Gatekeeper also measures how far price is from VWAP and an EMA to flag rich, cheap or extreme readings, and scores acceptance to route between mean-reversion and trend conditions. Chop and squeeze periods can be blocked. It can colour the background by regime, mark previous day high and low levels, show retracement zones, and display a dashboard table.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
ATR Length
int
14
atrLen
ADX Length
int
14
adxLen
RVOL Length
int
50
rvolLen
Range Efficiency Length
int
20
effLen
VWAP Slope Length
int
10
slopeLen
ADX Trend Minimum
float
25.0
adxTrendMin
ADX Chop Maximum
float
18.0
adxChopMax
Trend Efficiency Minimum
float
0.55
effTrendMin
Chop Efficiency Maximum
float
0.30
effChopMax
RVOL Expansion Minimum
float
1.25
rvolExpansion
ATR Expansion Multiple
float
1.25
atrExpansion
ATR Squeeze Multiple
float
0.80
atrSqzMult
Show all 40 settings
Setting
Type
Default
Variable
Use VWAP In RAP
bool
true
useVWAP
Use EMA In RAP
bool
true
useEMA
EMA Length For RAP
int
50
emaLen
Rich/Cheap Threshold
float
1.50
misRich
Extreme Threshold
float
2.50
misExtreme
MR Max Acceptance Score
int
55
mrWeakAcceptMax
Trend Min Acceptance Score
int
55
trendAcceptMinRouter
Trend VWAP Slope Minimum
float
0.10
trendSlopeMin
Block In CHOP
bool
true
blockChop
Wait During SQZ
bool
true
waitSqueeze
Show Engine Dashboard
bool
true
showTable
Dashboard Position
string
Top Right
dashboardPos
Color Background By Regime
bool
true
showBG
Show Regime Change Labels
bool
false
showLabels
Show Unfinished Business (PDH/PDL)
bool
true
showUB
Show Must Break Levels
bool
true
showMB
Show Retracement Zones
bool
true
showRT
Show Psychology Banner
bool
true
showPsy
Swing Length (Structure)
int
10
swingLen
Use CVD (Order Flow Proxy)
bool
true
useCVDInput
CVD Sub-Timeframe
timeframe
1
cvdSourceTfInput
Reset CVD Daily
bool
true
resetCvdDailyInput
CVD Divergence Lookback (Bars)
int
20
cvdLookbackInput
Show CVD Divergence Markers
bool
true
showCvdDivergenceMarkersInput
Show Pullback Target Zone
bool
true
showPullbackZoneInput
Target Z (ATR From RAP)
float
1.0
pullbackTargetZInput
Zone Width (ATR)
float
0.5
pullbackZoneWidthAtrInput
Use Volume Trend
bool
true
useVolumeTrendInput
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.
This indicator identifies mean-reversion setups by combining Bollinger Bands with Fair Value Gaps (FVGs). It tracks FVGs that print outside the upper or lower Bollinger Bands and counts them toward an alert threshold. Signals trigger upon gap mitigation (by to
Leptokurtic Directional Bias is an overlay indicator that measures kurtosis on a chosen source and marks directional bias on the price chart. The source can be a moving average, ATR, volume, close, or a Bollinger-band trend stop. Bias mode switches among RSI m
Commodity Heat Reversal plots score-based mean-reversion reversal markers on the price chart for commodity futures, aimed at the four-hour timeframe. It builds separate long and short heat scores from ATR distance to a mean moving average, ATR expansion, RSI e
Dynamic Extreme Channels & Reversals tracks evolving price highs and lows to plot adaptive channel bands and a midline directly on the price chart. When no new extremes occur within the specified lookback window, the channel boundaries interpolate toward price
This indicator plots directional trade signals, ATR-based target and stop-loss boxes, and SuperSmoother reversal bands directly on the price chart. It evaluates market conditions across selectable technical strategies, displaying confidence scores alongside an
This overlay indicator recreates short-setup conditions and audits their later outcomes on the price chart. It scores optional percent-surge, RSI overbought rollover, Bollinger exhaustion, volume spike, and EMA trend filters, then marks BASE or STRONG signals