Climax Detector w/ Traps
A unified climax, exhaustion, and trap detection framework for Thinkorswim
This study is the modern evolution of something I built years ago called A Better Better Volume with VSA. That original project started as a simple attempt to improve the classic Better Volume indicator by adding relative volume, wick behavior, and basic YTC‑style reference levels. Over time, as I kept layering in VSA concepts, range/spread context, anomaly handling, and early trap logic, the tool gradually shifted from “better volume bars” into a full microstructure framework.
The Climax Detector w/ Traps is the result of that evolution. It takes the core ideas from the original study — volume context, climax footprints, structural reference lines — and expands them into a unified engine that reads exhaustion, pressure, trap formation, and structural turning points with far more precision. If you used the old version, this is the same philosophy, just taken much further and built on a cleaner, more deterministic foundation.
Overview
This release introduces the Climax Detector w/ Traps, a consolidated microstructure engine designed to identify:
1. What This Script Does
The Climax Detector w/ Traps is a hybrid microstructure model that reads:
A. Climax Events
The engine detects climaxes using multiple independent subsystems:
YTC Lines (Expanded Explanation)
YTC Lines are one of the most important components of this script. Every time a Buying Climax (BC) or Selling Climax (SC) is detected, the engine automatically generates a set of YTC reference lines:
What YTC Lines Represent
YTC lines represent the same structural reference levels used by professional traders — the high, low, and midpoint of a climax bar. These levels are widely used in institutional trading to identify:
Why They Matter
Climax‑anchored levels are often used by professionals because they:
How I Use Them
1. Entry
The script uses YTC high/low overlap to determine:
YTC lines turn climax detection into a complete trading framework, not just a signal.
B. Trap Detection
The trap engine uses:
C. Tape Pressure
Tape pressure is derived from:
D. Special Candle States
The engine classifies:
E. Chop Zone Detection
A structural overlap model using YTC high/low bands:
This engine combines:
3. Key Features
✔ Unified Climax Engine
✔ Dual‑Fire Trap Engine
✔ Micro Traps
✔ Climax Traps
✔ Tape Pressure
✔ Special Candle Labels
✔ Chop Zone Logic
Chart w Studies
Climax Detector w Traps study
A unified climax, exhaustion, and trap detection framework for Thinkorswim
This study is the modern evolution of something I built years ago called A Better Better Volume with VSA. That original project started as a simple attempt to improve the classic Better Volume indicator by adding relative volume, wick behavior, and basic YTC‑style reference levels. Over time, as I kept layering in VSA concepts, range/spread context, anomaly handling, and early trap logic, the tool gradually shifted from “better volume bars” into a full microstructure framework.
The Climax Detector w/ Traps is the result of that evolution. It takes the core ideas from the original study — volume context, climax footprints, structural reference lines — and expands them into a unified engine that reads exhaustion, pressure, trap formation, and structural turning points with far more precision. If you used the old version, this is the same philosophy, just taken much further and built on a cleaner, more deterministic foundation.
Overview
This release introduces the Climax Detector w/ Traps, a consolidated microstructure engine designed to identify:
- Buying climaxes
- Selling climaxes
- Bull traps
- Bear traps
- Micro traps
- Climax traps
- Chop zones
- Pressure direction
1. What This Script Does
The Climax Detector w/ Traps is a hybrid microstructure model that reads:
A. Climax Events
The engine detects climaxes using multiple independent subsystems:
- Terminal Wyckoff BC / SC
- Strict wick climax
- Loose wick climax
- Gap‑failure exhaustion
- Shape‑based climaxes
- Two‑bar YTC climaxes
- Hybrid VSA climaxes
- ClimaxBC (Buying Climax → Weakness)
- ClimaxSC (Selling Climax → Strength)
YTC Lines are one of the most important components of this script. Every time a Buying Climax (BC) or Selling Climax (SC) is detected, the engine automatically generates a set of YTC reference lines:
- YTC High Line
- YTC Low Line
- YTC Mid Line (1/3 retracement)
What YTC Lines Represent
YTC lines represent the same structural reference levels used by professional traders — the high, low, and midpoint of a climax bar. These levels are widely used in institutional trading to identify:
- absorption
- continuation
- failure points
- trap behavior
Why They Matter
Climax‑anchored levels are often used by professionals because they:
- define the true pivot of the climax
- show where trapped traders must exit
- reveal where continuation trades trigger
- identify fade zones
- provide natural stop‑loss locations
- act as breakout confirmation levels
- help determine whether a trap is forming or failing
How I Use Them
1. Entry
- Break above SC → long continuation
- Break below BC → short continuation
- Rejection at the mid‑line → fade entry
- Profit targets at the opposite YTC band
- Exiting when price returns to the climax origin
- Stops placed beyond the YTC high/low
- Stops tightened when price re‑enters the YTC zone
- Bull Trap → price fails at BC YTC lines
- Bear Trap → price fails at SC YTC lines
The script uses YTC high/low overlap to determine:
- true chop
- breakout from chop
- suppression of false signals
YTC lines turn climax detection into a complete trading framework, not just a signal.
B. Trap Detection
The trap engine uses:
- exhaustion polarity
- microstate collapse
- wick‑climax alignment
- YTC exhaustion
- micro‑trap patterns
- climax‑trap overrides
- Bull Trap
- Bear Trap
- Micro Bull Trap
- Micro Bear Trap
- Climax Trap
C. Tape Pressure
Tape pressure is derived from:
- climaxes
- exhaustion
- microstate flips
- wick‑climax direction
- YTC polarity
- trend context
- tapeUpPressure
- tapeDnPressure
D. Special Candle States
The engine classifies:
- BuyClimax
- SellClimax
- SpinTop
- BullPin
- BearPin
- LV Up
- LV Down
E. Chop Zone Detection
A structural overlap model using YTC high/low bands:
- Detects true chop
- Identifies breakout from chop
- Suppresses false signals
- Provides clean regime context
- Chop
- Bull Breakout (Clears Upper Band)
- Bear Breakout (Clears Lower Band)
This engine combines:
- volume
- spread
- wick dominance
- displacement
- volatility
- microstate behavior
- YTC exhaustion
- shape analysis
- gap enhancers
- trend context
- climax memory
3. Key Features
✔ Unified Climax Engine
✔ Dual‑Fire Trap Engine
✔ Micro Traps
✔ Climax Traps
✔ Tape Pressure
✔ Special Candle Labels
✔ Chop Zone Logic
Example 1 — Bull Trap (Severity 3 of 5)
This example shows a Bull Trap with a severity rating of 3 out of 5. Notice how price continues to rise while volume decreases, signaling weakening participation behind the move. The trap forms when buyers chase the rising price into fading volume and diminishing pressure, creating the conditions for a reversal once the structure collapses back inside the prior range.
Chart w Studies
Example 2 — Bear Trap Setup (Early Warning Signal)
This chart shows a Bear Trap Setup, which acts as an early warning or “heads‑up” that downside pressure is weakening. Even though price pushes lower, the internal conditions don’t support continuation — volume behavior, wick structure, and microstate compression all point to sellers losing commitment. A Bear Trap Setup does not confirm a trap by itself, but it highlights the exact moment when the market becomes vulnerable to a bullish reversal if collapse conditions flip.
Climax Detector w Traps study
Ruby:
# Climax Detector w/ Traps
# atcsam — 04/20/2026
# Playing it forward
# Real Credit: thinkScript community
# Core inspirations and source references:
# - Verbatim translation of the TradeStation code from:
# http://emini-watch.com/free-stuff/volume-indicator/ (Better Volume)
#
# - YTC (Lance Beggs) — Converted from:
# https://www.tradingview.com/script/5fSgjYoM-YTC-Candlestick-Sentiment/
#
# - hiVolume indicator:
# source: http://tinboot.blogspot.com
# author: Allen Everhart
#
# - Advanced Volume Study for Volume Spread Analysis:
# [email protected]
############################################
# INPUTS
############################################
input proMode = {default Moderate, Aggressive, Conservative};
input showYTCLines = yes;
input currentOnlyYTC = yes;
input ytcLookback = 20;
input choplabel = yes;
input tape = yes;
input pro = yes;
input ShowArrows = no;
##########
# Label Position
#################
input LabelPos = {default "Bottom Left", "Top Left", "Top Right"};
def pos =
if LabelPos == LabelPos."Top Left" then 0 else
if LabelPos == LabelPos."Top Right" then 1 else
if LabelPos == LabelPos."Bottom Left" then 2 else
3;
###############
# CORE SERIES
###############
def o = open;
def h = high;
def l = low;
def c = close;
def v = volume;
def spread = h - l;
def body = AbsValue(c - o);
def range = spread;
def isBull = c >= o;
def isBear = c < o;
def upperWick = h - Max(o, c);
def lowerWick = Min(o, c) - l;
def bodyPct = if range != 0 then 100 * body / range else 0;
def upWickPct = if range != 0 then 100 * upperWick / range else 0;
def lowWickPct = if range != 0 then 100 * lowerWick / range else 0;
############################################
# MICROSTATE ENGINE (OVERWATCH CORE ONLY)
############################################
input zCloudLevel = 1.0;
input atrLength = 15;
input dsLen = 5;
def price = close;
def atr = Average(TrueRange(high, close, low), atrLength);
# Z-score on price
def zMean50 = Average(price, 50);
def zDev50 = StDev(price, 50);
def z50 = if zDev50 != 0 then (price - zMean50) / zDev50 else 0;
def zMean30 = Average(price, 30);
def zDev30 = StDev(price, 30);
def z30 = if zDev30 != 0 then (price - zMean30) / zDev30 else 0;
def zMean20 = Average(price, 20);
def zDev20 = StDev(price, 20);
def z20 = if zDev20 != 0 then (price - zMean20) / zDev20 else 0;
def zMean10 = Average(price, 10);
def zDev10 = StDev(price, 10);
def z10 = if zDev10 != 0 then (price - zMean10) / zDev10 else 0;
def zScore_ms =
if AbsValue(z50) > 3 then z10 else
if AbsValue(z50) > 2 then z20 else
if AbsValue(z50) > 1.5 then z30 else
z50;
# Curved dome
def zStretch = Max(0, AbsValue(zScore_ms) - zCloudLevel);
def zCurve = Power(zStretch, 1.5);
def atrMean = Average(atr, 20);
def volWeight = if atrMean != 0 then atr / atrMean else 1;
def zExpandRaw = zCurve * atr * volWeight;
def zExpand = ExpAverage(zExpandRaw, 3);
# DOOM core
def domeThickness = zExpand;
def domeSmooth = ExpAverage(domeThickness, 5);
def domeROC = domeSmooth - domeSmooth[3];
def domeNorm =
if HighestAll(domeSmooth) == LowestAll(domeSmooth)
then 0
else 100 * (domeSmooth - LowestAll(domeSmooth)) /
(HighestAll(domeSmooth) - LowestAll(domeSmooth));
def domeCompression = domeROC < 0;
def domeExpansion = domeROC > 0;
# Vol burst
def volBurst = domeROC > domeSmooth * 0.15;
# Directional commitment & stability (dsNorm)
def barRange_ms = high - low;
def bodySize_ms = AbsValue(close - open);
def upperWick_ms = high - Max(open, close);
def lowerWick_ms = Min(open, close) - low;
def safeRange_ms = if barRange_ms == 0 then 0.0001 else barRange_ms;
def bodyFrac_ms = bodySize_ms / safeRange_ms;
def bullBar_ms = close > open;
def bearBar_ms = close < open;
def followThroughUp_ms = bullBar_ms and close > close[1];
def followThroughDown_ms = bearBar_ms and close < close[1];
def overlap_ms =
(high <= high[1] and low >= low[1]) or
(high[1] <= high and low[1] >= low);
def wickAlignedBull_ms = bullBar_ms and lowerWick_ms > upperWick_ms;
def wickAlignedBear_ms = bearBar_ms and upperWick_ms > lowerWick_ms;
def dcRaw_ms =
(if bodyFrac_ms > 0.5 then 1 else 0) +
(if followThroughUp_ms or followThroughDown_ms then 1 else 0) +
(if wickAlignedBull_ms or wickAlignedBear_ms then 1 else 0) +
(if !overlap_ms then 1 else 0);
def dcNorm_ms = dcRaw_ms / 4;
def ema3_ms = ExpAverage(close, 3);
def ema3Slope_ms = ema3_ms - ema3_ms[1];
def mom3_ms = close - close[3];
def dirBar_ms = if close > open then 1 else if close < open then -1 else 0;
def dirSlope_ms = if ema3Slope_ms > 0 then 1 else if ema3Slope_ms < 0 then -1 else 0;
def dirMom_ms = if mom3_ms > 0 then 1 else if mom3_ms < 0 then -1 else 0;
def dirBarSum_ms = Sum(dirBar_ms, dsLen);
def dirSlopeSum_ms = Sum(dirSlope_ms, dsLen);
def dirMomSum_ms = Sum(dirMom_ms, dsLen);
def dsBar_ms = AbsValue(dirBarSum_ms) / dsLen;
def dsSlope_ms = AbsValue(dirSlopeSum_ms) / dsLen;
def dsMom_ms = AbsValue(dirMomSum_ms) / dsLen;
def dsNormRaw_ms = (dsBar_ms + dsSlope_ms + dsMom_ms) / 3;
rec dcSmooth_ms =
if IsNaN(dcSmooth_ms[1]) then dcNorm_ms
else dcSmooth_ms[1] + 0.2 * (dcNorm_ms - dcSmooth_ms[1]);
rec dsSmooth_ms =
if IsNaN(dsSmooth_ms[1]) then dsNormRaw_ms
else dsSmooth_ms[1] + 0.2 * (dsNormRaw_ms - dsSmooth_ms[1]);
def dsNorm = Max(0, Min(1, dsSmooth_ms));
# DOOM-L → microState
def doomL_norm = domeNorm;
def doomL_compress = domeCompression;
def doomL_expand = domeExpansion;
def doomL_roc = domeROC;
def doomL_smooth = domeSmooth;
def doomL_state =
if doomL_compress and !volBurst and dsNorm >= 0.5 then 0 else
if doomL_compress and volBurst and dsNorm >= 0.5 then 1 else
if doomL_expand and !volBurst and dsNorm >= 0.5 then 2 else
if doomL_expand and volBurst and dsNorm >= 0.5 then 3 else
0;
def microState = doomL_state;
# MicroState helpers for traps
def msContracting = microState == 0 or microState == 1;
def msExpanding = microState == 2 or microState == 3;
def msFlipDown =
(microState == 0 or microState == 1) and
(microState[1] == 2 or microState[1] == 3);
def msFlipUp =
(microState == 2 or microState == 3) and
(microState[1] == 0 or microState[1] == 1);
############################################
# GEOMETRY / DOJI / SPINNER (UNIFIED)
############################################
def isDoji = bodyPct <= 10;
def isMicroBody = bodyPct > 10 and bodyPct < 35;
def isWickBalanced =
upWickPct > 20 and
lowWickPct > 20 and
AbsValue(upWickPct - lowWickPct) <= 20;
def isSpinner = isMicroBody and isWickBalanced;
def upperW = high - Max(open, close);
def lowerW = Min(open, close) - low;
def upperPct = if spread > 0 then upperW / spread else 0;
def lowerPct = if spread > 0 then lowerW / spread else 0;
def isHighWave =
bodyPct <= 20 and
upperPct >= 0.35 and
lowerPct >= 0.35;
def isDojiFamily =
isDoji or
isSpinner or
isHighWave;
############################################
# ADAPTIVE LENGTHS
############################################
def vsaLen =
if proMode == proMode.Aggressive then 10
else if proMode == proMode.Conservative then 60
else 20;
def volLen = vsaLen;
def sprLen = vsaLen;
def rngLen = vsaLen;
############################################
# RELATIVE VOLUME (RVOL 3, 5, 10)
############################################
def avgV3 = Average(v, 3);
def avgV5 = Average(v, 5);
def avgV10 = Average(v, 10);
def rv3 = if avgV3 != 0 then v / avgV3 else 1;
def rv5 = if avgV5 != 0 then v / avgV5 else 1;
def rv10 = if avgV10 != 0 then v / avgV10 else 1;
############################################
# MODE SENSITIVITY MULTIPLIERS
############################################
def zMult =
if proMode == proMode.Aggressive then 1.4
else if proMode == proMode.Conservative then 0.7
else 1.0;
def vrocMult =
if proMode == proMode.Aggressive then 1.5
else if proMode == proMode.Conservative then 0.75
else 1.0;
############################################
# VOLUME ROC
############################################
def vroc = if v[1] != 0 then (v - v[1]) / v[1] else 0;
def vrocAdj = vroc * vrocMult;
############################################
# Z-SCORE + EULER NORMALIZATION
############################################
def volMean = Average(v, volLen);
def volStd = StDev(v, volLen);
def sprMean = Average(spread, sprLen);
def sprStd = StDev(spread, sprLen);
def rngMean = Average(range, rngLen);
def rngStd = StDev(range, rngLen);
def volNorm = (1 - Exp(-AbsValue((v - volMean) / Max(volStd, 0.0001)))) * zMult;
def sprNorm = (1 - Exp(-AbsValue((spread - sprMean) / Max(sprStd, 0.0001)))) * zMult;
def rngNorm = (1 - Exp(-AbsValue((range - rngMean) / Max(rngStd, 0.0001)))) * zMult;
############################################
# PRICE Z-SCORE (FOR GAP ENHANCERS)
############################################
def priceMean = Average(c, vsaLen);
def priceStd = StDev(c, vsaLen);
def priceZ = if priceStd != 0 then (c - priceMean) / priceStd else 0;
############################################
# BASIC PATTERNS
############################################
def isBullPin = isBull and bodyPct <= 33 and lowWickPct >= 60;
def isBearPin = isBear and bodyPct <= 33 and upWickPct >= 60;
############################################
# VOLUME CLASSIFICATION (USING RVOL + Z)
############################################
def isLowVol = rv3 < 0.95 and rv5 < 0.95 and volNorm < 0.65;
def isHighVol = rv3 > 1.8 or rv5 > 1.7 or volNorm > 0.7;
def isUltraVol = rv3 > 2.3 and rv5 > 2.0 and rv10 > 1.7 and volNorm > 0.75;
############################################
# GAPS (ENHANCERS)
############################################
def gapUp = o > h[1];
def gapDown = o < l[1];
############################################
# SMART PIN BARS
############################################
def bullPinShape = isBull and bodyPct <= 33 and lowWickPct >= 60;
def bearPinShape = isBear and bodyPct <= 33 and upWickPct >= 60;
def LV_BullPin = bullPinShape and volNorm <= 0.25;
def LV_BearPin = bearPinShape and volNorm <= 0.25;
############################################
# FAT-TAIL EVENT ENGINE (CONTEXT)
############################################
input kurtLen = 20;
input kurtSensitivity = 2.5;
def volMeanK = Average(v, kurtLen);
def volStDevK = StDev(v, kurtLen);
def volKurtosis = if volStDevK != 0 then (v - volMeanK) / volStDevK else 0;
def sprSafeK = if spread == 0 then 0.0001 else spread;
def churnK = v / sprSafeK;
def churnMeanK = Average(churnK, kurtLen);
def churnStDevK = StDev(churnK, kurtLen);
def churnZK = if churnStDevK != 0 then (churnK - churnMeanK) / churnStDevK else 0;
def priceVelocityK = c - c[5];
def priceVolatilityK = Average(TrueRange(h, c, l), 5);
def sharpeMomentumK = if priceVolatilityK != 0 then priceVelocityK / priceVolatilityK else 0;
def isFatTailEvent =
volKurtosis > kurtSensitivity and
churnZK > 1.0;
############################################
# CLASSIC VSA CONTEXT (NOT TRAPS)
############################################
def StoppingVol =
isBear and
isHighVol and
rngNorm > 0.6 and
c > c[1];
def NoDemand =
isBull and
isLowVol and
spread < sprMean and
c <= c[1];
def NoSupply =
isBear and
isLowVol and
spread < sprMean and
c >= c[1];
############################################
# TREND CONTEXT
############################################
def trendMA = ExpAverage(c, vsaLen);
def inUpTrend = c > trendMA;
def inDnTrend = c < trendMA;
############################################
# VSA ENGINE — HYBRID (STRICT + LOOSE)
############################################
def sprAvg = Average(spread, 20);
def sprATR = ATR(14);
def volAvg20 = Average(v, 20);
def volAvg50 = Average(v, 50);
def volStDev50 = StDev(v, 50);
def volZ = if volStDev50 != 0 then (v - volAvg50) / volStDev50 else 0;
# STRICT VOLUME SPIKE
def strictVol =
v > volAvg20 * 1.8 or
v > volAvg50 * 2.2 or
volZ > 2.0;
# STRICT SPREAD SPIKE
def strictSpread =
spread > sprAvg * 1.5 and
spread > sprATR * 1.2;
# LOOSE VOLUME + SPREAD
def looseVol = rv3 > 1.6 and rv5 > 1.4;
def looseSpread = spread > sprAvg * 1.1;
# STRICT REJECTION
def rejectUp_strict = c < h - (spread * 0.25);
def rejectDown_strict = c > l + (spread * 0.25);
# LOOSE REJECTION
def rejectUp_loose = c < h - (spread * 0.15);
def rejectDown_loose = c > l + (spread * 0.15);
# HYBRID REJECTION
def rejectUp = rejectUp_strict or rejectUp_loose;
def rejectDown = rejectDown_strict or rejectDown_loose;
# TREND ACCELERATION FILTER
def mom = c - c[1];
def momUp = mom > 0 and mom > mom[1];
def momDn = mom < 0 and mom < mom[1];
def trendAccelUp = momUp and spread > sprAvg;
def trendAccelDn = momDn and spread > sprAvg;
# ============================================================
# TERMINAL WYCKOFF BC / SC
# ============================================================
def volOK = isHighVol or isUltraVol;
def upTrend =
c > c[1] and
c[1] > c[2] and
c[2] > c[3];
def downTrend =
c < c[1] and
c[1] < c[2] and
c[2] < c[3];
def displacementUp = c >= h - 0.25 * spread;
def displacementDown = c <= l + 0.25 * spread;
def absorptionUp = upWickPct >= 40;
def absorptionDown = lowWickPct >= 40;
def wideSpread = spread >= 1.75 * sprATR and spread >= 1.5 * sprAvg;
def vsa_SC =
wideSpread and
displacementDown and
volOK and
downTrend and
!isDojiFamily and
!absorptionDown;
def vsa_BC =
wideSpread and
displacementUp and
volOK and
upTrend and
!isDojiFamily and
!absorptionUp;
def BC = vsa_BC;
def SC = vsa_SC;
#===============================================
# NON‑TERMINAL WICK‑CLIMAX ENGINE
# ================================================
def confirmUp =
c[1] < c or
v[1] < v or
h[1] < h;
def confirmDown =
c[1] > c or
v[1] < v or
l < l[1];
# STRICT WICK‑CLIMAX
def WCUp_strict =
isBear and
volOK and
strictSpread and
lowWickPct >= 40 and
rejectDown_strict and
!trendAccelDn and
confirmDown and
!isDojiFamily and
!vsa_SC and
!vsa_BC;
def WCDown_strict =
isBull and
volOK and
strictSpread and
upWickPct >= 40 and
rejectUp_strict and
!trendAccelUp and
confirmUp and
!isDojiFamily and
!vsa_SC and
!vsa_BC;
# LOOSE WICK‑CLIMAX
def WCUp_loose =
isBear and
volOK and
looseSpread and
lowWickPct >= 40 and
rejectDown and
!isDojiFamily and
!vsa_SC and
!vsa_BC;
def WCDown_loose =
isBull and
volOK and
looseSpread and
upWickPct >= 40 and
rejectUp and
!isDojiFamily and
!vsa_SC and
!vsa_BC;
# GAP FAILURE ENHANCERS
def gapFailUp = gapUp and rejectDown;
def gapFailDown = gapDown and rejectUp;
# FINAL HYBRID WICK‑CLIMAX OUTPUTS
def WCDown =
if WCDown_strict then 1
else if WCDown_loose then 1
else if (isBull and gapFailUp and isFatTailEvent and upWickPct >= 40 and !isDojiFamily and !vsa_BC and !vsa_SC) then 1
else 0;
def WCUp =
if WCUp_strict then 1
else if WCUp_loose then 1
else if (isBear and gapFailDown and isFatTailEvent and lowWickPct >= 40 and !isDojiFamily and !vsa_BC and !vsa_SC) then 1
else 0;
def GapExhaustUp = WCUp and (isBear and gapFailDown and isFatTailEvent);
def GapExhaustDown = WCDown and (isBull and gapFailUp and isFatTailEvent);
######################
# TWO-BAR YTC ENGINE
######################
def useYTC = yes;
def vRange = Max(h - l, TickSize());
def vValue1;
def vValue2;
if c > o then {
vValue1 = (vRange / (2 * vRange + o - c)) * v;
vValue2 = v - vValue1;
} else if c < o then {
vValue1 = ((vRange + c - o) / (2 * vRange + c - o)) * v;
vValue2 = v - vValue1;
} else {
vValue1 = 0.5 * v;
vValue2 = v - vValue1;
}
def vValue3 = AbsValue(vValue1 + vValue2);
def hi2 = Highest(h, 2);
def lo2 = Lowest(l, 2);
def range2 = Max(hi2 - lo2, TickSize());
def vValue14 = if useYTC then (vValue1 + vValue1[1]) * range2 else 0;
def vValue15 = if useYTC then (vValue1 + vValue1[1] - vValue2 - vValue2[1]) * range2 else 0;
def vValue16 = if useYTC then (vValue2 + vValue2[1]) * range2 else 0;
def vValue17 = if useYTC then (vValue2 + vValue2[1] - vValue1 - vValue1[1]) * range2 else 0;
def vValue18 = if useYTC then (vValue1 + vValue1[1]) / range2 else 0;
def vValue19 = if useYTC then (vValue1 + vValue1[1] - vValue2 - vValue2[1]) / range2 else 0;
def vValue20 = if useYTC then (vValue2 + vValue2[1]) / range2 else 0;
def vValue21 = if useYTC then (vValue2 + vValue2[1] - vValue1 - vValue1[1]) / range2 else 0;
def vCondition13 = useYTC and vValue15 == Highest(vValue15, ytcLookback) and c > o and c[1] > o[1];
def vCondition18 = useYTC and vValue20 == Lowest(vValue20, ytcLookback) and c > o and c[1] > o[1];
def vCondition19 = useYTC and vValue21 == Lowest(vValue21, ytcLookback) and c > o and c[1] > o[1];
def vCondition14 = useYTC and vValue16 == Highest(vValue16, ytcLookback) and c < o and c[1] < o[1];
def vCondition15 = useYTC and vValue17 == Highest(vValue17, ytcLookback) and c < o and c[1] < o[1];
def vCondition16 = useYTC and vValue18 == Lowest(vValue18, ytcLookback) and c < o and c[1] < o[1];
def vCondition17 = useYTC and vValue19 == Lowest(vValue19, ytcLookback) and c < o and c[1] < o[1];
def TwoBarClimaxUp = vCondition13 or vCondition18 or vCondition19;
def TwoBarClimaxDown = vCondition14 or vCondition15 or vCondition16 or vCondition17;
#########################
# SHAPE-BASED CLIMAX
#########################
def strongUpperWick1 = upperWick > range * 0.40;
def strongLowerWick1 = lowerWick > range * 0.40;
def upperWickDominant = upperWick > lowerWick * 1.3;
def lowerWickDominant = lowerWick > upperWick * 1.3;
def avgRange1 = Average(range, 20);
def smallBody1 = body < range * 0.30;
def rangeExpanded1 = range > avgRange1 * 1.30;
def trendUp1 = c > trendMA;
def trendDown1 = c < trendMA;
def bearShapeClimax_clean =
strongUpperWick1 and
smallBody1 and
rangeExpanded1 and
trendUp1;
def bullShapeClimax_clean =
strongLowerWick1 and
smallBody1 and
rangeExpanded1 and
trendDown1;
def ShapeBC =
isBull and
upperWickDominant and
smallBody1 and
wideSpread and
trendUp1;
def ShapeSC =
isBear and
lowerWickDominant and
smallBody1 and
wideSpread and
trendDown1;
################################
# TRAP DUAL-FIRE LOGIC
################################
def TrapUp =
(WCUp and TwoBarClimaxDown) or
(TwoBarClimaxDown and WCUp) or
WCUp or
TwoBarClimaxDown;
def TrapDown =
(WCDown and TwoBarClimaxUp) or
(TwoBarClimaxUp and WCDown) or
WCDown or
TwoBarClimaxUp;
###########################
# FINAL YTC EVENT STACK
###########################
def YTC_SC_Event =
if SC then 1
else if TwoBarClimaxDown then 1
else if ShapeSC then 1
else 0;
def YTC_BC_Event =
if BC then 1
else if TwoBarClimaxUp then 1
else if ShapeBC then 1
else 0;
def YTC_SC = YTC_SC_Event;
def YTC_BC = YTC_BC_Event;
def ClimaxSC = YTC_SC;
def ClimaxBC = YTC_BC;
##################
# YTC LINES
##################
def bn = BarNumber();
def NA = Double.NaN;
def cdbn = CompoundValue(1, if YTC_SC then bn else cdbn[1], 0);
def WhiteH = CompoundValue(1,
if YTC_SC and showYTCLines and useYTC then h
else WhiteH[1],
NA);
plot WhiteHL =
if useYTC and showYTCLines and currentOnlyYTC and bn >= HighestAll(cdbn) then WhiteH
else if useYTC and showYTCLines and !currentOnlyYTC then WhiteH
else NA;
WhiteHL.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
WhiteHL.SetDefaultColor(Color.WHITE);
def WhiteYTC = CompoundValue(1,
if YTC_SC then (h - (h - l) / 3)
else WhiteYTC[1],
NA);
plot WhiteYTCL =
if useYTC and showYTCLines and currentOnlyYTC and bn >= HighestAll(cdbn) then WhiteYTC
else if useYTC and showYTCLines and !currentOnlyYTC then WhiteYTC
else NA;
WhiteYTCL.SetPaintingStrategy(PaintingStrategy.DASHES);
WhiteYTCL.SetDefaultColor(Color.WHITE);
def WhiteL = CompoundValue(1,
if YTC_SC then l
else WhiteL[1],
NA);
plot WhiteLL =
if useYTC and showYTCLines and currentOnlyYTC and bn >= HighestAll(cdbn) then WhiteL
else NA;
WhiteLL.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
WhiteLL.SetDefaultColor(Color.MAGENTA);
def rlbn = CompoundValue(1, if YTC_BC then bn else rlbn[1], 0);
def RedYTC = CompoundValue(1,
if YTC_BC and showYTCLines and useYTC then ((h - l) / 3 + l)
else RedYTC[1],
NA);
plot RedYTCL =
if useYTC and showYTCLines and currentOnlyYTC and bn >= HighestAll(rlbn) then RedYTC
else if useYTC and showYTCLines and !currentOnlyYTC then RedYTC
else NA;
RedYTCL.SetPaintingStrategy(PaintingStrategy.DASHES);
RedYTCL.SetDefaultColor(Color.LIGHT_RED);
def RedYTCLow = CompoundValue(1,
if YTC_BC then l
else RedYTCLow[1],
NA);
def RedH = CompoundValue(1,
if YTC_BC and showYTCLines and useYTC then h
else RedH[1],
NA);
plot RedYTCLowL =
if useYTC and showYTCLines and currentOnlyYTC and bn >= HighestAll(rlbn) then RedYTCLow
else if useYTC and showYTCLines and !currentOnlyYTC then RedYTCLow
else NA;
RedYTCLowL.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
RedYTCLowL.SetDefaultColor(Color.RED);
plot RedHL =
if useYTC and showYTCLines and currentOnlyYTC and bn >= HighestAll(rlbn) then RedH
else if useYTC and showYTCLines and !currentOnlyYTC and (RedYTCLow < WhiteH) then RedH
else NA;
RedHL.SetPaintingStrategy(PaintingStrategy.DASHES);
RedHL.SetDefaultColor(Color.YELLOW);
#################
# CHOP ZONE
#################
# True structural overlap (must match YTC logic)
def overlap = RedYTCLow <= WhiteHL and RedHL >= WhiteLL;
# Chop zone boundaries
def chopZoneHigh = Max(RedHL, WhiteHL);
def chopZoneLow = Min(RedYTCLow, WhiteLL);
# Price inside the overlap zone
def insideOverlap = close <= chopZoneHigh and close >= chopZoneLow;
# Core chop state
def chopCore = overlap and insideOverlap;
# Hard breakout logic (candle must CLEAR the zone)
def bullBreakout = low > chopZoneHigh; # full clearance above
def bearBreakout = high < chopZoneLow; # full clearance below
def chopBreakout = bullBreakout or bearBreakout;
# Final chop state
def chop = chopCore and !chopBreakout;
AddLabel(choplabel and chop, "Chop", Color.YELLOW, pos);
############################################
# MARKER TOGGLES
############################################
input showVSAClimax = yes;
input showNoDemand = yes;
input showNoSupply = yes;
input showStoppingVol = yes;
input showGapExhaust = yes;
input showWeaknessBar = yes;
############################################
# GAP EXHAUST FLAG
############################################
def GapExhaust = GapExhaustUp or GapExhaustDown;
def weaknessBar = NoDemand;
plot VSAClimaxDownArrow = ShowArrows and showVSAClimax and BC;
VSAClimaxDownArrow.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
VSAClimaxDownArrow.SetLineWeight(3);
VSAClimaxDownArrow.AssignValueColor(Color.RED);
VSAClimaxDownArrow.HideBubble();
VSAClimaxDownArrow.HideTitle();
plot VSAClimaxUpArrow = ShowArrows and showVSAClimax and SC;
VSAClimaxUpArrow.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
VSAClimaxUpArrow.SetLineWeight(3);
VSAClimaxUpArrow.AssignValueColor(Color.WHITE);
VSAClimaxUpArrow.HideBubble();
VSAClimaxUpArrow.HideTitle();
####### ad on_
plot WickClimaxDownArrow = ShowArrows and showVSAClimax and WCDown;
WickClimaxDownArrow.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
WickClimaxDownArrow.AssignValueColor(Color.YELLOW);
WickClimaxDownArrow.SetLineWeight(2);
plot WickClimaxUpArrow = ShowArrows and showVSAClimax and WCUp;
WickClimaxUpArrow.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
WickClimaxUpArrow.AssignValueColor(Color.YELLOW);
WickClimaxUpArrow.SetLineWeight(2);
####################
# MAP TO TAPE TERMS
####################
def isBuyClimax = ClimaxBC; # Buying Climax (weakness)
def isSellClimax = ClimaxSC; # Selling Climax (strength)
def anyStrength =
ClimaxSC or
(NoSupply and inUpTrend) or
(isSpinner and inUpTrend);
def anyWeakness =
ClimaxBC or
(NoDemand and inDnTrend) or
(isSpinner and inDnTrend);
############################################
# PRESSURE DIRECTION
############################################
def tapeUpPressure =
ClimaxSC or
YTC_SC or
WcUp or
msFlipUp or
(inUpTrend and anyStrength);
def tapeDnPressure =
ClimaxBC or
YTC_BC or
wcDown or
msFlipDown or
(inDnTrend and anyWeakness);
############################################
# LOW VOLUME LABELS
############################################
def LV_Up = isLowVol and isBull;
def LV_Down = isLowVol and isBear;
############################################
# SHORT NAMES FOR LAST-3-BAR LABELS
############################################
def txtCode =
if isBuyClimax then 1 else
if isSellClimax then 2 else
if isSpinner then 3 else
if isBullPin then 4 else
if isBearPin then 5 else
if LV_Up then 6 else
if LV_Down then 7 else
0;
def txtCode1 = txtCode[1];
def txtCode2 = txtCode[2];
############################################
# COLOR MAP (TAPE LABELS)
############################################
AddLabel(
tape, "[2]" +
if txtCode2 == 1 then "BuyClimax"
else if txtCode2 == 2 then "SellClimax"
else if txtCode2 == 3 then "SpinTop"
else if txtCode2 == 4 then "BullPin"
else if txtCode2 == 5 then "BearPin"
else if txtCode2 == 6 then "LV Up"
else if txtCode2 == 7 then "LV Down"
else " … ",
if txtCode2 == 1 then Color.RED
else if txtCode2 == 2 then Color.WHITE
else if txtCode2 == 3 then Color.MAGENTA
else if txtCode2 == 4 then Color.GREEN
else if txtCode2 == 5 then Color.RED
else if txtCode2 == 6 then Color.LIGHT_RED
else if txtCode2 == 7 then Color.LIGHT_GREEN
else Color.LIGHT_GRAY, pos
);
AddLabel(
tape, "[1]" +
if txtCode1 == 1 then "BuyClimax"
else if txtCode1 == 2 then "SellClimax"
else if txtCode1 == 3 then "SpinTop"
else if txtCode1 == 4 then "BullPin"
else if txtCode1 == 5 then "BearPin"
else if txtCode1 == 6 then "LV Up"
else if txtCode1 == 7 then "LV Down"
else " … ",
if txtCode1 == 1 then Color.RED
else if txtCode1 == 2 then Color.WHITE
else if txtCode1 == 3 then Color.MAGENTA
else if txtCode1 == 4 then Color.GREEN
else if txtCode1 == 5 then Color.RED
else if txtCode1 == 6 then Color.LIGHT_RED
else if txtCode1 == 7 then Color.LIGHT_GREEN
else Color.LIGHT_GRAY, pos
);
AddLabel(
tape,
"[0]" +
(if txtCode == 1 then "BuyClimax"
else if txtCode == 2 then "SellClimax"
else if txtCode == 3 then "SpinTop"
else if txtCode == 4 then "BullPin"
else if txtCode == 5 then "BearPin"
else if txtCode == 6 then "LV Up"
else if txtCode == 7 then "LV Down"
else " … "),
(if txtCode == 1 then Color.RED
else if txtCode == 2 then Color.WHITE
else if txtCode == 3 then Color.MAGENTA
else if txtCode == 4 then Color.GREEN
else if txtCode == 5 then Color.RED
else if txtCode == 6 then Color.LIGHT_RED
else if txtCode == 7 then Color.LIGHT_GREEN
else Color.LIGHT_GRAY), pos
);
############################################
# MICRO CHURN / COMPRESSION
############################################
def isChurn = isSpinner and isLowVol;
def anyCompression = isChurn;
########################
# EXHAUSTION POLARITY
########################
def bullExhaust = (WcDown or YTC_BC); # weakness → bull trap path
def bearExhaust = (WcUp or YTC_SC); # strength → bear trap path
#################
# TRAP CORE
#################
def bullTrapCore = bullExhaust;
def bearTrapCore = bearExhaust;
def anyTrapCore = bullTrapCore or bearTrapCore;
######################
# MICRO COLLAPSE
######################
def microCollapse = msContracting or msFlipDown or msFlipUp;
#####################
# REAL TRAPS
#####################
def bullTrap = bullTrapCore and microCollapse;
def bearTrap = bearTrapCore and microCollapse;
def anyTrap = bullTrap or bearTrap;
####################
# MICRO TRAPS
####################
def microBullTrap =
bullTrapCore and microCollapse and
(
isBearPin or
(isSpinner and isBull[1])
);
def microBearTrap =
bearTrapCore and microCollapse and
(
isBullPin or
(isSpinner and isBear[1])
);
def trapTypeMicro =
if microBullTrap then 1
else if microBearTrap then 2
else 0;
############################################
# CLIMAX TRAPS
############################################
def isClimaxTrapBull = bullTrapCore and ClimaxBC;
def isClimaxTrapBear = bearTrapCore and ClimaxSC;
################
# TRAP TYPE
################
def trapType =
if bullTrap then 1
else if bearTrap then 2
else if trapTypeMicro == 1 then 3
else if trapTypeMicro == 2 then 4
else if isClimaxTrapBull or isClimaxTrapBear then 5
else 0;
####################
# CLIMAX MEMORY
####################
input climaxLookback = 6;
def lastBullExhaust = Highest(bearExhaust, climaxLookback) > 0; # SC = strength
def lastBearExhaust = Highest(bullExhaust, climaxLookback) > 0; # BC = weakness
############################################
# TRAP SETUP (DIRECTIONAL — BEST GUESS)
############################################
# Raw directional setups from exhaustion memory
def rawSetupBull = bullTrapCore and !anyTrap and lastBearExhaust; # BC → weakness → bull trap path
def rawSetupBear = bearTrapCore and !anyTrap and lastBullExhaust; # SC → strength → bear trap path
# Ambiguity conditions
def bothPossible = rawSetupBull and rawSetupBear;
def nonePossible = !rawSetupBull and !rawSetupBear;
# Trend bias for tie‑breaking
def trendBull = inUpTrend;
def trendBear = inDnTrend;
# Candle bias fallback
def candleBull = isBull;
def candleBear = isBear;
# Final directional trap setup (best guess)
def trapSetupBull =
!anyTrap and (
rawSetupBull
or (bothPossible and trendBull)
or (bothPossible and !trendBull and !trendBear and candleBull)
or (nonePossible and trendBull)
or (nonePossible and !trendBull and !trendBear and candleBull)
);
def trapSetupBear =
!anyTrap and (
rawSetupBear
or (bothPossible and trendBear)
or (bothPossible and !trendBull and !trendBear and candleBear)
or (nonePossible and trendBear)
or (nonePossible and !trendBull and !trendBear and candleBear)
);
############################################
# TRAP SEVERITY
############################################
def trapSeverity =
(if anyTrap then 1 else 0) +
(if trapTypeMicro > 0 then 1 else 0) +
(if isClimaxTrapBull or isClimaxTrapBear then 1 else 0) +
(if anyTrapCore then 1 else 0) +
(if microBullTrap or microBearTrap then 1 else 0) +
(if isFatTailEvent then 1 else 0);
############################################
# NARRATIVE WINDOWS
############################################
input narrLen = 4;
def strengthWindow = Highest(anyStrength, narrLen) > 0;
def weaknessWindow = Highest(anyWeakness, narrLen) > 0;
def compressionWindow = Highest(anyCompression, narrLen) > 0;
def trapWindow = Highest(anyTrap, narrLen) > 0;
############################################
# PRO CLASS
############################################
def proClass =
if trapWindow then 4
else if strengthWindow and !weaknessWindow then 1
else if weaknessWindow and !strengthWindow then 2
else if compressionWindow then 3
else 0;
#########################
# PRO SUBCATEGORY
#########################
def proSubCat =
if proClass == 1 and StoppingVol then 11
else if proClass == 1 and NoSupply then 12
else if proClass == 2 and bullExhaust then 21
else if proClass == 2 and NoDemand then 22
else if proClass == 3 and anyCompression then 31
else if proClass == 4 and trapType == 1 then 41 # Bull Trap
else if proClass == 4 and trapType == 2 then 42 # Bear Trap
else if proClass == 4 and trapType == 3 then 43 # Micro Bull Trap
else if proClass == 4 and trapType == 4 then 44 # Micro Bear Trap
else if proClass == 4 and trapType == 5 then 45 # Climax Trap
else if proClass == 4 and trapSetupBull then 46 # Trap Setup Bull
else if proClass == 4 and trapSetupBear then 47 # Trap Setup Bear
else if proClass == 4 and anyTrap then 40 # Generic Trap Pattern
else 0;
############################################
# TRAP FOOTPRINT
############################################
def isTrapFootprint = anyTrapCore and !anyTrap;
############################################
# PRO LABEL
############################################
AddLabel(
pro,
(if proClass == 1 then "Strength Building"
else if proClass == 2 then "Weakness Building"
else if proClass == 3 then "Compression"
# Trap Setup header ONLY when it's actually a setup
else if proClass == 4 and (proSubCat == 0 or proSubCat == 46 or proSubCat == 47) then "Trap Setup"
# Confirmed traps get NO prefix
else if proClass == 4 and (proSubCat == 41 or proSubCat == 42 or proSubCat == 43 or proSubCat == 44 or proSubCat == 45) then ""
else "Neutral")
+
(if proSubCat == 11 then " - Stopping Volume"
else if proSubCat == 12 then " - No Supply"
else if proSubCat == 21 then " - Buying Climax"
else if proSubCat == 22 then " - No Demand"
else if proSubCat == 31 then " - Low RVOL Coil"
else if proSubCat == 40 then " - Trap Pattern"
# Confirmed traps
else if proSubCat == 41 then "Bull Trap"
else if proSubCat == 42 then "Bear Trap"
else if proSubCat == 43 then "Micro Bull Trap"
else if proSubCat == 44 then "Micro Bear Trap"
else if proSubCat == 45 then "Climax Trap"
# Setups
else if proSubCat == 46 then " - Bull"
else if proSubCat == 47 then " - Bear"
else "")
+
(if trapType > 0 then " Sev: " + trapSeverity else "")
+
(if isTrapFootprint and trapType == 1 then " - Trap Footprint Bull"
else if isTrapFootprint and trapType == 2 then " - Trap Footprint Bear"
else ""),
(
if proClass == 4 and proSubCat == 0 then Color.ORANGE
else if proClass == 4 and (proSubCat == 46 or proSubCat == 47) then Color.CYAN
else if proClass == 4 then Color.MAGENTA
else if proClass == 1 then CreateColor(140, 220, 140)
else if proClass == 2 then CreateColor(255, 140, 140)
else if proClass == 3 then Color.YELLOW
else Color.LIGHT_GRAY), pos
);
###########################
# CLIMAX DEBUG BUBBLES
###########################
input showClimaxBubble = no;
AddChartBubble(
showClimaxBubble and ClimaxBC, # Buying Climax (weakness)
high,
"BC",
Color.GREEN,
yes
);
AddChartBubble(
showClimaxBubble and ClimaxSC, # Selling Climax (strength)
low,
"SC",
Color.RED,
no
);