Liquidity Map — Microstructure Study For ThinkOrSwim

atcsam

Market Structure Expert
Plus
Liquidity Map V2


yhFSlWM.png

(Out of the Rabbit Hole)

Chart Link
Liquidity all studies

Liquidity Map V2 is a full microstructure engine built around 17 liquidity zones, three institutional patterns, and four core intents. The study uses user‑controlled inputs to toggle visibility of structural footprints, pattern bubbles, and labels. These inputs allow traders to customize the map for:
  • reversal detection
  • displacement quality
  • mitigation logic
  • institutional flow visualization
Default Settings
  • Labels: Top‑Right
  • REV↑/REV↓ bubbles: Off
  • Flow label: Off
  • S/R Line: Yes
  • HTF Mode: Yes (Quarterly aggregation)
  • Event Bubbles: ALL
  • User control: All / Off / Custom
User Inputs — Custom Bubble Toggles

Each bubble group corresponds to a specific microstructure footprint. Turning them on/off lets the operator isolate patterns, debug sequences, or focus on a single intent:

  • StopGrab / Sweep / ICT Sweep — Collection intent
  • FVG / ICT FVG / FVG Fill — Escape intent
  • BPR / ICT BPR / Mitigation Origin — Defense intent
  • Void / Refill — Imbalance structure
  • InstRev / Exhaust / Climax — Reversal signals
  • OB / BOS / CHoCH — Structural confirmation
  • HFT Trap / Shelf / Churn — Microstructure context
  • Clusters / Pivot Cluster — Institutional footprint density
Turning ALL bubbles on gives the full Liquidity Map V2 visualization. Turning Custom groups on allows targeted pattern analysis (Pattern A, B, or C).

Dynamic Support/Resistance

I decided to use Mitigation Origins for S/R levels. They behave more like dynamic VWAP bands than static S/R lines — adapting to institutional defense, displacement origin, and mitigation flow.

Operator Cheat Sheet — Liquidity Map V2

Pattern A — Collection (Stop Grab & Reversal)

Goal:
Harvest liquidity to enter positions Watch:

  • LIQ ↑/↓
  • SWEEP ↑/↓
  • ICT SWEEP
  • SHELF
  • MICROREJECT / MICROABSORB
  • INST REV ↑/↓
Sequence: Shelf → Sweep → Reject/Absorb → InstRev → Reversal

Pattern B — Escape (Displacement & Imbalance)

Goal:
Push price away from fill zone Watch:

  • FVG ↑/↓
  • ICT FVG ↑/↓
  • VOID
  • BPR ↑/↓
  • OB BULL/BEAR
  • BOS ↑/↓
Sequence: Sweep → OB → Displacement → HQFVG/Void → BPR → Continuation

Pattern C — Defense (Mitigation & Refill)

Goal:
Protect displacement origin Watch:

Study Link
Liquidity_Map_V2
Ruby:
############################################
# LIQUIDITY MAP
# Version: 2.0 (Work In Progress)
# atcsam — 09/28/26
# Playing it Forward
# Description:
# A microstructure engine that maps liquidity behavior
# across 17 structural zones, directional reversal probability
# (REV↑/REV↓), RPM confluence scoring, volume pressure,
# and candle‑behavior micro‑footprints.
#
# Includes full Pattern & Sequence Detection:
# Pattern A (Collection), Pattern B (Escape),
# Pattern C (Defense), and Abandonment Logic.
############################################

#-----------------------------------------
# USER INPUTS
#-----------------------------------------

input LabelPos = {default "Top Right", "Top Left", "Bottom Left"};
input useRTH = no;
input shelfMode = {default "High", "Medium", "Institutional"};

input SR_Lines = yes;
input HTF_SR = yes;
input htfTimeFrame = AggregationPeriod.quarter;

input showREV_Bubbles    = no;
input revBubbleThreshold = 2;

input EventBubbles = {default "All", "Off", "Custom"};

input BPR               = no;
input Climax_Exhaust    = no;
input Clusters          = no;
input DP_Churn          = yes;
input FVG               = no;
input HFT_Trap          = yes;
input Hidden_Absorption = yes;
input Inst_Rev          = yes;
input Shelf             = no;
input StopGrab          = yes;
input Sweep             = no;
input Voids             = no;
input ICT_Sweep         = yes;
input ICTFVG            = no;
input ICTBPR            = no;
input Mitigation        = no;
input OB_B              = no;
input BOS_B             = no;

input showCandleArrow   = no;
input showRPM           = yes;
input showRevLabel      = yes;
input showCandleLabels  = yes;
input showFlowLabel     = no;

input lookbackPeriod        = 10;
input bodyMultiplier        = 1.5;
input rvolExhaustThreshold  = 0.8;
input seqLookback           = 10;

#-----------------------------------------
# POSITION
#-----------------------------------------

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;

def showAllBubbles   = EventBubbles == EventBubbles.All;
def showNoBubbles    = EventBubbles == EventBubbles.Off;
def showCustomBubble = EventBubbles == EventBubbles.Custom;

#-----------------------------------------
# GLOBAL COLORS
#-----------------------------------------

DefineGlobalColor("MagLt", CreateColor(237, 74, 213));
DefineGlobalColor("Amber", CreateColor(220, 170, 45));
DefineGlobalColor("Trap", CreateColor(220, 65, 145));
DefineGlobalColor("RPM_DownStrong", Color.RED);
DefineGlobalColor("RPM_Down",CreateColor(205, 70, 78));
DefineGlobalColor("RPM_Neutral",CreateColor(237, 226, 19));
DefineGlobalColor("RPM_Up", CreateColor(70, 175, 135));
DefineGlobalColor("RPM_UpStrong", CreateColor(0, 210, 120));
DefineGlobalColor("Info", CreateColor(0, 190, 220));
DefineGlobalColor("Paper",      CreateColor(245, 240, 225));
DefineGlobalColor("Parchment",  CreateColor(221, 204, 178));
DefineGlobalColor("FiberP",     CreateColor(145, 121, 90));
DefineGlobalColor("DownTrend",  CreateColor(255, 0, 128));
DefineGlobalColor("Fill",       CreateColor(0, 128, 255));
DefineGlobalColor("UpTrend",    CreateColor(0, 128, 255));
DefineGlobalColor("BearStrong", CreateColor(230, 70, 78));

#-----------------------------------------
# CORE METRICS / RTH FILTER
#-----------------------------------------

def isRTH =
    SecondsFromTime(0930) >= 0 and
    SecondsTillTime(1600) >= 0;
def rawVol = volume;
def volRTH = if useRTH and !isRTH then Double.NaN else rawVol;
def avgVol20 = Average(volRTH, 20);
def RVOL20 = if avgVol20 > 0 then rawVol / avgVol20 else 1;
def isIntraday = GetAggregationPeriod() < AggregationPeriod.DAY;
def volAvg10 = Average(volume, 10);
def price       = close;
def candleRange = high - low;
def candleBody  = AbsValue(close - open);
def upperWick   = high - Max(open, close);
def lowerWick   = Min(open, close) - low;
def atr20 = ATR(20);
def bn   = BarNumber();

# ====================================================
# ZONE 1 — DARK POOL CHURN
# ====================================================

def _isChurn =
    RVOL20 > 1.8 and
    candleBody / candleRange < 0.35;
def isChurn = if IsNaN(_isChurn) then 0 else _isChurn;

# ====================================================
# ZONE 2 — SWEEPS & STOP GRABS
# ====================================================

def _isUpSweep = RVOL20 > 1.5 and candleBody > atr20 * 0.65 and close > open;
def isUpSweep = if IsNaN(_isUpSweep) then 0 else _isUpSweep;
def _isDnSweep = RVOL20 > 1.5 and candleBody > atr20 * 0.65 and close <= open;
def isDnSweep = if IsNaN(_isDnSweep) then 0 else _isDnSweep;
def _isUpStopGrab = RVOL20 > 1.5 and upperWick > candleRange * 0.45 and close < (high + low) / 2;
def isUpStopGrab = if IsNaN(_isUpStopGrab) then 0 else _isUpStopGrab;
def _isDnStopGrab = RVOL20 > 1.5 and lowerWick > candleRange * 0.45 and close > (high + low) / 2;
def isDnStopGrab = if IsNaN(_isDnStopGrab) then 0 else _isDnStopGrab;

# ====================================================
# ZONE 3 — VOIDS & REFILLS
# ====================================================

def prevHigh = high[1];
def prevLow  = low[1];
def strongBody      = candleBody > candleRange * 0.55;
def smallUpperWick  = upperWick < candleRange * 0.35;
def smallLowerWick  = lowerWick < candleRange * 0.35;
def normalVolume    = RVOL20 > 0.8;
def voidUp   = close > prevHigh and strongBody and smallUpperWick and normalVolume;
def voidDown = close < prevLow  and strongBody and smallLowerWick and normalVolume;
def _refillUp   = voidUp[1] and low <= prevHigh[1];
def refillUp    = if IsNaN(_refillUp) then 0 else _refillUp;
def _refillDown = voidDown[1] and high >= prevLow[1];
def refillDown  = if IsNaN(_refillDown) then 0 else _refillDown;
def isVoid       = voidUp or voidDown;
def isVoidRefill = refillUp or refillDown;
def voidBN1 = CompoundValue(1, if isVoid then BarNumber() else voidBN1[1], 0);

# ====================================================
# ZONE 4 — HIDDEN ABSORPTION
# ====================================================

def _isHiddenAbsorb =
    RVOL20 >= 1.80 and
    candleBody <= atr20 * 0.30 and
    candleRange >= atr20 * 0.40 and
    (upperWick + lowerWick) >= candleBody * 0.25 and
    (upperWick + lowerWick) <= candleBody * 0.75 and
    !isUpSweep and !isDnSweep and
    !isUpStopGrab and !isDnStopGrab and
    !voidUp and !voidDown;
def isHiddenAbsorb = if IsNaN(_isHiddenAbsorb) then 0 else _isHiddenAbsorb;

# ====================================================
# ZONE 5 — VOLUME CLIMAX
# ====================================================

def _isVolClimax =
    RVOL20 > 3.0 and
    candleBody > atr20 * 0.8 and
    (upperWick + lowerWick) < candleRange * 0.30;
def isVolClimax = if IsNaN(_isVolClimax) then 0 else _isVolClimax;

# ====================================================
# ZONE 6 — TREND EXHAUSTION
# ====================================================

def _effortHigh = RVOL20 > 1.5 and candleBody > atr20 * 0.5;
def effortHigh  = if IsNaN(_effortHigh) then 0 else _effortHigh;
def _resultWeakUp = close < open and upperWick > candleRange * 0.4;
def resultWeakUp  = if IsNaN(_resultWeakUp) then 0 else _resultWeakUp;
def _effortLow = RVOL20 > 1.5 and candleBody > atr20 * 0.5;
def effortLow  = if IsNaN(_effortLow) then 0 else _effortLow;
def _resultWeakDown = close > open and lowerWick > candleRange * 0.4;
def resultWeakDown  = if IsNaN(_resultWeakDown) then 0 else _resultWeakDown;
def _isTrendExhaustion = (effortHigh and resultWeakUp) or (effortLow and resultWeakDown);
def isTrendExhaustion  = if IsNaN(_isTrendExhaustion) then 0 else _isTrendExhaustion;

# ====================================================
# ZONE 7 — LIQUIDITY SHELVES
# ====================================================
def tick = TickSize();
def shelfHigh_H =
    high == Highest(high, 5) and
    AbsValue(high[1] - high) <= (tick * 2) and
    AbsValue(high[2] - high) <= (tick * 2);
def shelfLow_H =
    low == Lowest(low, 5) and
    low[1] == Lowest(low[1], 5) and
    low[2] == Lowest(low[2], 5);
def shelfHigh_M =
    shelfHigh_H and RVOL20 < 0.8 and candleRange < atr20 * 0.6;
def shelfLow_M =
    shelfLow_H and RVOL20 < 0.8 and candleRange < atr20 * 0.6;
def shelfHigh_I =
    shelfHigh_H and RVOL20 < 0.6 and candleBody < atr20 * 0.4 and upperWick < candleRange * 0.3;
def shelfLow_I =
    shelfLow_H and RVOL20 < 0.6 and candleBody < atr20 * 0.4 and lowerWick < candleRange * 0.3;
def _isShelf =
    if shelfMode == shelfMode."High" then (shelfHigh_H or shelfLow_H)
    else if shelfMode == shelfMode."Medium" then (shelfHigh_M or shelfLow_M)
    else (shelfHigh_I or shelfLow_I);
def isShelf = if IsNaN(_isShelf) then 0 else _isShelf;
def shelfLow  = shelfLow_H or shelfLow_M or shelfLow_I;
def shelfHigh = shelfHigh_H or shelfHigh_M or shelfHigh_I;

# ====================================================
# ZONE 8 — PIVOT CLUSTER
# ====================================================

def pivotHigh20 = Highest(high, 20);
def pivotLow20  = Lowest(low, 20);
def _pivotHighCompression =
    AbsValue(pivotHigh20 - pivotHigh20[5]) <= atr20 * 0.4;
def pivotHighCompression = if IsNaN(_pivotHighCompression) then 0 else _pivotHighCompression;
def _pivotLowCompression =
    AbsValue(pivotLow20 - pivotLow20[5]) <= atr20 * 0.4;
def pivotLowCompression = if IsNaN(_pivotLowCompression) then 0 else _pivotLowCompression;
def pivotBandCompression = pivotHighCompression and pivotLowCompression;
def _rangeCompression =
    candleRange < atr20 * 0.6 and candleRange < candleRange[1];
def rangeCompression = if IsNaN(_rangeCompression) then 0 else _rangeCompression;
def _volCompression =
    RVOL20 < 0.8 and RVOL20 < RVOL20[1];
def volCompression = if IsNaN(_volCompression) then 0 else _volCompression;
def _pivotCluster =
    pivotBandCompression and
    rangeCompression and
    volCompression and
    (isShelf or isHiddenAbsorb or isUpStopGrab or isDnStopGrab or isVoid or isVoidRefill);
def pivotCluster = if IsNaN(_pivotCluster) then 0 else _pivotCluster;
def pivotClusterUp   = pivotCluster and high == pivotHigh20;
def pivotClusterDown = pivotCluster and low  == pivotLow20;
def pivotClusterMid  = pivotCluster and !pivotClusterUp and !pivotClusterDown;

# ====================================================
# ZONE 9 — HFT VWAP TRAP
# ====================================================

def vwapValue = vwap;
def vwapDist  = AbsValue(close - vwapValue);
def vwapStDev = StDev(vwapDist, 50);
def upperVwapBand = vwapValue + (2.0 * vwapStDev);
def lowerVwapBand = vwapValue - (2.0 * vwapStDev);
def _isHftVwapTrap =
    (high > upperVwapBand and (isUpStopGrab or isVolClimax or isTrendExhaustion)) or
    (low < lowerVwapBand and (isDnStopGrab or isVolClimax or isTrendExhaustion));
def isHftVwapTrap = if IsNaN(_isHftVwapTrap) then 0 else _isHftVwapTrap;

# ====================================================
# ZONE 10 — ICT LIQUIDITY SWEEPS
# ====================================================

def swingHigh = Highest(high[1], 5);
def swingLow  = Lowest(low[1], 5);
def ictBuySideSweep =
    high > swingHigh and
    close < swingHigh and
    close < open and
    RVOL20 >= 1.25;
def ictSellSideSweep =
    low < swingLow and
    close > swingLow and
    close > open and
    RVOL20 >= 1.25;
def isICTSweep = ictBuySideSweep or ictSellSideSweep;

# ====================================================
# ZONE 11 — BPR
# ====================================================

def dispUp  = close[1] - open[1] > (high[1] - low[1]) * 0.35;
def dispDn  = open[1] - close[1] > (high[1] - low[1]) * 0.35;
def oppBull = dispDn[1] and dispUp;
def oppBear = dispUp[1] and dispDn;
def wickOverlap =
    low[1] < high[2] and
    high[1] > low[2];
def swingContext =
    high[1] == Highest(high, 5) or
    low[1]  == Lowest(low, 5);
def bullBPR =
    oppBull and wickOverlap and swingContext;
def bearBPR =
    oppBear and wickOverlap and swingContext;
def bullBPRValid =
    bullBPR and close > high[1];
def bearBPRValid =
    bearBPR and close < low[1];
def isBPR      = bullBPR or bearBPR;
def isBPRValid = bullBPRValid or bearBPRValid;

# ====================================================
# ZONE 12 — UNIFIED FVG ENGINE
# ====================================================

def body        = AbsValue(close - open);
def avgBodySize = Average(body[1], lookbackPeriod);
def middleBody  = AbsValue(close[1] - open[1]);
def bigMiddleBody = middleBody > avgBodySize * bodyMultiplier;
def prevBody  = AbsValue(close[1] - open[1]);
def prevRange = high[1] - low[1];
def ictClassicBullFVG = high[2] < low;
def ictClassicBearFVG = low[2] > high;
def isICTClassicFVG   = ictClassicBullFVG or ictClassicBearFVG;
def bullDispFVG =
    bigMiddleBody and
    high[2] < low;
def bearDispFVG =
    bigMiddleBody and
    low[2] > high;
def isDispFVG = bullDispFVG or bearDispFVG;
def isHQFVG   = isDispFVG and (BarNumber()[1] - voidBN1 <= 3);
def bullHQFVG = bullDispFVG and (BarNumber()[1] - voidBN1 <= 3);
def bearHQFVG = bearDispFVG and (BarNumber()[1] - voidBN1 <= 3);
def ictDispUp =
    isICTSweep and
    close > high[1] and
    body > prevBody and
    RVOL20 > 1.2;
def ictDispDn =
    isICTSweep and
    close < low[1] and
    body > prevBody and
    RVOL20 > 1.2;
def ictBullFVG =
    ictDispUp and
    high[2] < low and
    upperWick < candleRange * 0.35;
def ictBearFVG =
    ictDispDn and
    low[2] > high and
    lowerWick < candleRange * 0.35;
def isICTDispFVG = ictBullFVG or ictBearFVG;
def isFVG =
    isICTClassicFVG or
    isDispFVG or
    isICTDispFVG;
def middleBN =
    if isFVG
    then bn[1]
    else middleBN[1];
def hasMiddle = middleBN > 0;
def fvgForwardWindow =
    hasMiddle and
    bn > middleBN and
    bn <= middleBN + 5;
def bullFVGFill = fvgForwardWindow and bullDispFVG[1] and low <= high[3];
def bearFVGFill = fvgForwardWindow and bearDispFVG[1] and high >= low[3];
def isFVGFill = bullFVGFill or bearFVGFill;
def activeFVG     = isFVG and fvgForwardWindow;
def activeFVGFill = isFVGFill and fvgForwardWindow;
def voidOnlyRefill = isVoidRefill and !isFVG;

# ====================================================
# ZONE 13 — INSTITUTIONAL REVERSAL SIGNATURE
# ====================================================

def rvolExhaust = RVOL20 < rvolExhaustThreshold;
def anySweep     = isUpSweep or isDnSweep or isUpStopGrab or isDnStopGrab or isICTSweep;
def anyDispEvent = isVoid or isVoidRefill or isFVG or isFVGFill or isBPRValid;
def anyExhaust   = isTrendExhaustion or isVolClimax or rvolExhaust;
def anyHtf       = isChurn or isHiddenAbsorb or isShelf or pivotCluster;
def sweepCluster    = Sum(anySweep, 2) >= 1;
def dispCluster     = Highest(anyDispEvent, 2);
def exhaustCluster  = Highest(anyExhaust, 2);
def htfCluster      = Highest(anyHtf, 2);
def chainActive =
    activeFVG or activeFVGFill or isHQFVG or voidOnlyRefill or isBPR;
def clusterScore =
    (if sweepCluster then 1 else 0) +
    (if dispCluster then 1 else 0) +
    (if exhaustCluster then 1 else 0) +
    (if htfCluster then 1 else 0);
def isInstRevUp =
    clusterScore >= 3 and
    !chainActive and
    (low == Lowest(low, 5));
def isInstRevDn =
    clusterScore >= 3 and
    !chainActive and
    (high == Highest(high, 5));
def isInstReversal = isInstRevUp or isInstRevDn;

# ====================================================
# ZONE 14 — ICT BPR (Overlapping FVGs)
# ====================================================

# 1. Define Standard Gaps (Evaluating the Current Bar)
def fvgBullGapHigh = low;
def fvgBullGapLow  = high[2];
def fvgIsBull      = fvgBullGapLow < fvgBullGapHigh;
def fvgBearGapHigh = low[2];
def fvgBearGapLow  = high;
def fvgIsBear      = fvgBearGapHigh > fvgBearGapLow;

# 2. Track the boundaries and timing of the most recent opposing FVGs
def lastBearFvgHigh = if fvgIsBear then fvgBearGapHigh else lastBearFvgHigh[1];
def lastBearFvgLow  = if fvgIsBear then fvgBearGapLow else lastBearFvgLow[1];
def lastBearFvgBar  = if fvgIsBear then bn else lastBearFvgBar[1];
def lastBullFvgHigh = if fvgIsBull then fvgBullGapHigh else lastBullFvgHigh[1];
def lastBullFvgLow  = if fvgIsBull then fvgBullGapLow else lastBullFvgLow[1];
def lastBullFvgBar  = if fvgIsBull then bn else lastBullFvgBar[1];

# 3. Identify the BPR Overlap
def ictBullBPR = fvgIsBull and
                 (bn - lastBearFvgBar <= 8) and
                 (fvgBullGapHigh >= lastBearFvgLow) and
                 (fvgBullGapLow <= lastBearFvgHigh);
def ictBearBPR = fvgIsBear and
                 (bn - lastBullFvgBar <= 8) and
                 (fvgBearGapHigh >= lastBullFvgLow) and
                 (fvgBearGapLow <= lastBullFvgHigh);

# 4. Validity and Signal Execution
def ictBullBPRValid = ictBullBPR and RVOL20 > 1.2 and close > high[1];
def ictBearBPRValid = ictBearBPR and RVOL20 > 1.2 and close < low[1];
def isICTBPR      = ictBullBPR or ictBearBPR;
def isICTBPRValid = ictBullBPRValid or ictBearBPRValid;

# ====================================================
# Candle Behavior Engine
# ====================================================

def range = high - low;
def bodyPctOfRange =
    if range != 0 then body / range else 0;
def upperWickPct =
    if range != 0 then upperWick / range else 0;
def lowerWickPct =
    if range != 0 then lowerWick / range else 0;
def isDispUp1 =
    body > prevBody and
    range > prevRange and
    close > high[1];
def isDispDn1 =
    body > prevBody and
    range > prevRange and
    close < low[1];
def tr1Up = close > close[1];
def tr1Dn = close < close[1];
def tr1   = if tr1Up then 1 else if tr1Dn then -1 else 0;
def isRejectUp =
    lowerWickPct > 0.4 and
    close > open and
    close < high[1];
def isRejectDn =
    upperWickPct > 0.4 and
    close < open and
    close > low[1];
def isAbsorb =
    bodyPctOfRange < 0.25 and
    range > prevRange * 0.75 and
    RVOL20 > 1 and
    rawVol > avgVol20 and
    rawVol > volAvg10 and
    (isShelf or isVoid or isHiddenAbsorb or isChurn);
def isCompression =
    range < prevRange * 0.7 and
    range < Average(range, 5) * 0.8 and
    !isVoid and !isUpSweep and !isDnSweep;

# ====================================================
# ZONE 15 — BOS / CHoCH (Dynamic Swings)
# ====================================================
def localDispUp =
    (candleBody > candleBody[1]) and
    (candleRange > candleRange[1]) and
    (close > high[1]) and
    (RVOL20 > 1.0);

def localDispDn =
    (candleBody > candleBody[1]) and
    (candleRange > candleRange[1]) and
    (close < low[1]) and
    (RVOL20 > 1.0);

# --- Swing High / Low ---
input swingLength = 3;
def swingHigh1 = high[swingLength] == Highest(high, swingLength * 2 + 1);
def swingLow1  = low[swingLength]  == Lowest(low, swingLength * 2 + 1);

# --- Dynamic swing levels ---
def swingHighLevel = if swingHigh1 then high[swingLength] else swingHighLevel[1];
def swingLowLevel  = if swingLow1  then low[swingLength]  else swingLowLevel[1];

# --- CHoCH (Reversal + break of opposite swing) ---
def chochUp = swingLow1[1] and close > swingHighLevel;
def chochDn = swingHigh1[1] and close < swingLowLevel;
def isCHoCH = chochUp or chochDn;

# --- BOS (Break of dynamic swing levels) ---
def bosUp = localDispUp and close > swingHighLevel;
def bosDn = localDispDn and close < swingLowLevel;
def isBOS = bosUp or bosDn;

# ====================================================
# ZONE 16 — Institutional Order Block (Corrected)
# ====================================================
def isDispUp =
    body > prevBody and
    range > prevRange and
    close > high[1];
def isDispDn =
    body > prevBody and
    range > prevRange and
    close < low[1];
def volValid = volume > Average(volume, 20);

# 2. Bullish OB: Displacement + FVG + Volume, AND the preceding candle must be a down candle
def obBull =
    isDispUp and
    (bullDispFVG or isHQFVG) and
    volValid and
    close[1] < open[1];

# 3. Bearish OB: Displacement + FVG + Volume, AND the preceding candle must be an up candle
def obBear =
    isDispDn and
    (bearDispFVG or isHQFVG) and
    volValid and
    close[1] > open[1];
def isOB = obBull or obBear;

# ====================================================
# ZONE 17 — MITIGATION ENGINE
# ====================================================

# 1. Mitigation Origins
def mitigBeginUp =
    isUpSweep or
    isInstRevUp or
    bullHQFVG or
    obBull or
    bosUp or
    chochUp;

def mitigBeginDn =
    isDnSweep or
    isInstRevDn or
    bearHQFVG or
    obBear or
    bosDn or
    chochDn;

# 2. Mitigation Terminations
def mitigEndUp =
    refillUp or
    bullFVGFill or
    pivotClusterUp;
def mitigEndDn =
    refillDown or
    bearFVGFill or
    pivotClusterDown;
def oppositeResetUp = mitigBeginDn;
def oppositeResetDn = mitigBeginUp;

# 3. Active State Tracking
def mitigIsActiveUp = CompoundValue(1,
    if mitigBeginUp then 1
    else if mitigEndUp or oppositeResetUp then 0
    else mitigIsActiveUp[1], 0);
def mitigIsActiveDn = CompoundValue(1,
    if mitigBeginDn then 1
    else if mitigEndDn or oppositeResetDn then 0
    else mitigIsActiveDn[1], 0);
def isMitigOriginUp =
    mitigBeginUp and (mitigIsActiveUp[1] <= 0);
def isMitigOriginDn =
    mitigBeginDn and (mitigIsActiveDn[1] <= 0);

# 4. Anchor Bar Memory
def mitigAnchorUp = CompoundValue(1,
    if isMitigOriginUp then bn
    else if mitigIsActiveUp then mitigAnchorUp[1]
    else 0, 0);
def mitigAnchorDn = CompoundValue(1,
    if isMitigOriginDn then bn
    else if mitigIsActiveDn then mitigAnchorDn[1]
    else 0, 0);
def mitigAgeUp =
    if mitigAnchorUp > 0 then bn - mitigAnchorUp else 0;
def mitigAgeDn =
    if mitigAnchorDn > 0 then bn - mitigAnchorDn else 0;

# 5. Age-Out Mechanism
def ageOutUp = mitigAgeUp > 50 and !mitigBeginUp;
def ageOutDn = mitigAgeDn > 50 and !mitigBeginDn;
def mitigIsClosedUp = mitigEndUp or ageOutUp;
def mitigIsClosedDn = mitigEndDn or ageOutDn;

# 6. Price Level Tracking (Clears out to NaN when closed to prevent endless line plotting)
def originUpPrice =
    if isMitigOriginUp then low
    else if mitigIsClosedUp then Double.NaN
    else originUpPrice[1];
def originDnPrice =
    if isMitigOriginDn then high
    else if mitigIsClosedDn then Double.NaN
    else originDnPrice[1];

# ====================================================
# EVENT BN MEMORY
# ====================================================

# 1. State Trackers
def churnBN        = CompoundValue(1, if isChurn        then bn else churnBN[1], 0);
def sweepUpBN      = CompoundValue(1, if isUpSweep      then bn else sweepUpBN[1], 0);
def sweepDnBN      = CompoundValue(1, if isDnSweep      then bn else sweepDnBN[1], 0);
def stopGrabUpBN   = CompoundValue(1, if isUpStopGrab   then bn else stopGrabUpBN[1], 0);
def stopGrabDnBN   = CompoundValue(1, if isDnStopGrab   then bn else stopGrabDnBN[1], 0);
def voidUpBN       = CompoundValue(1, if voidUp         then bn else voidUpBN[1], 0);
def voidDnBN       = CompoundValue(1, if voidDown       then bn else voidDnBN[1], 0);
def refillUpBN     = CompoundValue(1, if refillUp       then bn else refillUpBN[1], 0);
def refillDnBN     = CompoundValue(1, if refillDown     then bn else refillDnBN[1], 0);
def fvgBullBN      = CompoundValue(1, if bullDispFVG    then bn else fvgBullBN[1], 0);
def fvgBearBN      = CompoundValue(1, if bearDispFVG    then bn else fvgBearBN[1], 0);
def fvgFillBullBN  = CompoundValue(1, if bullFVGFill    then bn else fvgFillBullBN[1], 0);
def fvgFillBearBN  = CompoundValue(1, if bearFVGFill    then bn else fvgFillBearBN[1], 0);
def hqFvgBN        = CompoundValue(1, if isHQFVG        then bn else hqFvgBN[1], 0);
def bprBullBN      = CompoundValue(1, if bullBPR        then bn else bprBullBN[1], 0);
def bprBearBN      = CompoundValue(1, if bearBPR        then bn else bprBearBN[1], 0);
def instRevUpBN    = CompoundValue(1, if isInstRevUp    then bn else instRevUpBN[1], 0);
def instRevDnBN    = CompoundValue(1, if isInstRevDn    then bn else instRevDnBN[1], 0);
def ictSweepUpBN   = CompoundValue(1, if ictBuySideSweep then bn else ictSweepUpBN[1], 0);
def ictSweepDnBN   = CompoundValue(1, if ictSellSideSweep then bn else ictSweepDnBN[1], 0);
def hiddenAbsorbBN = CompoundValue(1, if isHiddenAbsorb then bn else hiddenAbsorbBN[1], 0);
def pivotClusterBN = CompoundValue(1, if pivotCluster   then bn else pivotClusterBN[1], 0);
def shelfBN        = CompoundValue(1, if isShelf        then bn else shelfBN[1], 0);
def volClimaxBN    = CompoundValue(1, if isVolClimax    then bn else volClimaxBN[1], 0);
def hftTrapBN      = CompoundValue(1, if isHftVwapTrap  then bn else hftTrapBN[1], 0);
def bprValidBullBN = CompoundValue(1, if bullBPRValid   then bn else bprValidBullBN[1], 0);
def bprValidBearBN = CompoundValue(1, if bearBPRValid   then bn else bprValidBearBN[1], 0);
def trendExhaustUpBN = CompoundValue(1, if effortHigh and resultWeakUp then bn else trendExhaustUpBN[1], 0);
def trendExhaustDnBN = CompoundValue(1, if effortLow and resultWeakDown then bn else trendExhaustDnBN[1], 0);
def rejectUpBN  = CompoundValue(1, if isRejectUp  then bn else rejectUpBN[1], 0);
def rejectDnBN  = CompoundValue(1, if isRejectDn  then bn else rejectDnBN[1], 0);
def absorbBN    = CompoundValue(1, if isAbsorb    then bn else absorbBN[1], 0);
def compressBN  = CompoundValue(1, if isCompression then bn else compressBN[1], 0);

# 2. Lookback Queries: foundational structure, and seqLookback triggers
def contextLookback = 30;
def hasShelf        = bn - shelfBN <= contextLookback;
def hasChurn        = bn - churnBN <= contextLookback;
def hasPivot        = bn - pivotClusterBN <= contextLookback;
def hasClimax       = bn - volClimaxBN <= contextLookback;
def hasVoid         = (bn - voidUpBN <= contextLookback) or (bn - voidDnBN <= contextLookback);
def hasSweep        = (bn - sweepUpBN <= seqLookback) or (bn - sweepDnBN <= seqLookback);
def hasICTSweep     = (bn - ictSweepUpBN <= seqLookback) or (bn - ictSweepDnBN <= seqLookback);
def hasStopGrab     = (bn - stopGrabUpBN <= seqLookback) or (bn - stopGrabDnBN <= seqLookback);
def hasInstRev      = (bn - instRevUpBN <= seqLookback) or (bn - instRevDnBN <= seqLookback);
def hasTrendExhaust = (bn - trendExhaustUpBN <= seqLookback) or (bn - trendExhaustDnBN <= seqLookback);
def hasRefill       = (bn - refillUpBN <= seqLookback) or (bn - refillDnBN <= seqLookback);
def hasFVG          = (bn - fvgBullBN <= seqLookback) or (bn - fvgBearBN <= seqLookback);
def hasFVGFill      = (bn - fvgFillBullBN <= seqLookback) or (bn - fvgFillBearBN <= seqLookback);
def hasBPR          = (bn - bprBullBN <= seqLookback) or (bn - bprBearBN <= seqLookback);
def hasBPRValid     = (bn - bprValidBullBN <= seqLookback) or (bn - bprValidBearBN <= seqLookback);
def hasHftTrap      = bn - hftTrapBN <= seqLookback;
def hasHQFVG        = bn - hqFvgBN <= seqLookback;

# 3. Sequences
def seq_LRT = (hasSweep or hasICTSweep) and hasStopGrab and hasHftTrap and hasInstRev;
def seq_SFD = hasShelf and hasChurn and hasPivot and (hasClimax or hasTrendExhaust) and hasInstRev;
def seq_IMB = hasVoid and (hasFVG or hasHQFVG) and (hasFVGFill or hasRefill) and hasBPR and hasInstRev;
def seq_ICT_Mitig = hasICTSweep and hasFVGFill;
def seq_ICT_Cont = hasICTSweep and hasBPRValid;
def seq_VoidChain = hasVoid and hasFVG and hasFVGFill;
def seq_SweepExhaust = hasSweep and hasTrendExhaust and hasInstRev;
def hasReject      = (bn - rejectUpBN <= seqLookback) or (bn - rejectDnBN <= seqLookback);
def hasAbsorb = bn - absorbBN <= contextLookback;
def seq_MicroReject =
    hasReject and hasSweep and hasStopGrab;
def seq_MicroAbsorb =
    hasAbsorb and hasShelf and hasChurn;
def hasCompress = bn - compressBN <= contextLookback;

def seqID =
     if seq_LRT          then 1
else if seq_SFD          then 2
else if seq_IMB          then 3
else if seq_ICT_Mitig    then 4
else if seq_ICT_Cont     then 5
else if seq_VoidChain    then 6
else if seq_SweepExhaust then 7
else if seq_MicroReject  then 8
else if seq_MicroAbsorb  then 9
else 0;

AddLabel(yes,
    "SEQ " + seqID,
    if seqID == 1 then Color.RED
    else if seqID == 2 then Color.YELLOW
    else if seqID == 3 then Color.GREEN
    else if seqID == 4 then Color.CYAN
    else if seqID == 5 then Color.MAGENTA
    else if seqID == 6 then Color.ORANGE
    else if seqID == 7 then Color.DARK_RED
    else if seqID == 8 then Color.LIGHT_RED   
    else if seqID == 9 then Color.LIGHT_GREEN
    else Color.GRAY,
    pos);

# ====================================================
# FLOW LABEL
# ====================================================

AddLabel(
    showFlowLabel,
    "Flow: " +
    (if isHftVwapTrap then "⚠️ HFT VWAP TRAP"
     else if isInstReversal then "REVERSAL"
     else if isICTSweep then "ICT SWEEP"
     else if isMitigOriginUp then "MITIG ORIGIN ↑"
     else if isMitigOriginDn then "MITIG ORIGIN ↓"
     else if isBPRValid then "BPR✓"
     else if isFVGFill then "FVG FILL"
     else if isHQFVG then "FVG✓"
     else if isBPR then "BPR"
     else if isFVG then "FVG"
     else if isVoidRefill then "REFILL"
     else if isVoid then "VOID"
     else if isUpStopGrab then "LIQ GRAB ↑"
     else if isDnStopGrab then "LIQ GRAB ↓"
     else if isUpSweep then "SWEEP ↑"
     else if isDnSweep then "SWEEP ↓"
     else if isHiddenAbsorb then "HIDDEN ABSORB"
     else if isVolClimax then "CLIMAX"
     else if isTrendExhaustion then "EXHAUSTION"
     else if isShelf then "SHELF"
     else if pivotCluster then "PIVOT CLUSTER"
     else if isChurn then "DP CHURN"
     else "Standard Flow"),
    if isHftVwapTrap then Color.MAGENTA else Color.WHITE,
    pos
);

# ====================================================
# CHART BUBBLES (TIERS)
# ====================================================

AddChartBubble(
    (showAllBubbles or (showCustomBubble and DP_Churn))
     and isChurn,
    low,
    "DP CHURN",
    GlobalColor("DownTrend"),
    no
);

# --- Shelves ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Shelf))
    and isShelf,
    if shelfHigh then high else low,
    "SHELF",
    if shelfHigh_I or shelfLow_I then Color.WHITE
    else if shelfHigh_M or shelfLow_M then GlobalColor("Parchment")
    else GlobalColor("FiberP"),
    shelfHigh
);

# --- Absorption ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Hidden_Absorption))
    and isHiddenAbsorb,
    low,
    "ABSORB",
    Color.CYAN,
    no
);

# --- StopGrab Grab ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and StopGrab))
    and (isUpStopGrab or isDnStopGrab),
    if isUpStopGrab then high else low,
    "LIQ " + (if isUpStopGrab then "↑" else "↓"),
    if isUpStopGrab then Color.YELLOW else Color.LIME,
    if isUpStopGrab then yes else no
);

# --- Sweep ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Sweep))
    and (isUpSweep or isDnSweep),
    if isUpSweep then high else low,
    "SWEEP " + (if isUpSweep then "↑" else "↓"),
    if isUpSweep then Color.CYAN else Color.LIGHT_RED,
    if isUpSweep then yes else no
);

# --- Voids ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Voids))
    and isVoid,
    if close > open then high else low,
    "VOID",
    if !isVoidRefill and !isFVGFill and !isFVG
        then Color.DARK_GREEN
        else Color.LIGHT_GRAY,
    close > open
);

# --- Refill ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Voids))
    and isVoidRefill and !isFVGFill,
    low,
    "REFILL",
    GlobalColor("Fill"),
    no
);
# --- Climax Exhaustion ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Climax_Exhaust))
    and (isVolClimax or isTrendExhaustion),
    if upperWick > lowerWick then high else low,
    (if isVolClimax then "CLIMAX" else "EXHAUST"),
    if isVolClimax then GlobalColor("Amber") else Color.RED,
    if upperWick > lowerWick then yes else no
);

# --- Clusters ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Clusters))
    and (pivotClusterUp or pivotClusterDown or pivotClusterMid),
    if pivotClusterUp then high
    else if pivotClusterDown then low
    else close,
    "PIVOT CLUSTER" +
        (if pivotClusterUp then " ↑"
         else if pivotClusterDown then " ↓"
         else ""),
    if pivotClusterMid then Color.LIGHT_GRAY else Color.ORANGE,
    if pivotClusterUp then yes
    else if pivotClusterDown then no
    else yes
);

# --- ICT_Sweep FVG ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and ICTFVG))
    and isICTDispFVG,
    if ictBullFVG then low else high,
    "ICT FVG " + (if ictBullFVG then "↑" else "↓"),
    Color.CYAN,
    if ictBullFVG then no else yes
);

# --- ICT_Sweep BPR ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and ICTBPR))
    and (isICTBPR or isICTBPRValid),
    if ictBullBPR or ictBullBPRValid then low else high,
    "ICT BPR " +
    (if ictBullBPR or ictBullBPRValid then "↑" else "↓") +
    (if isICTBPRValid then "✓" else ""),
    if isICTBPRValid then
    (if ictBullBPRValid then Color.GREEN else Color.RED)
    else
    (if ictBullBPR then Color.MAGENTA else Color.DARK_ORANGE),
    if ictBullBPR or ictBullBPRValid then no else yes
);

# --- ICT_Sweep Sweep ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and ICT_Sweep))
    and (ictBuySideSweep or ictSellSideSweep),
    if ictSellSideSweep then low else high,
    "ICT SWEEP " + (if ictSellSideSweep then "↑" else "↓"),
    Color.MAGENTA,
    if ictSellSideSweep then no else yes
);

# --- HFT Trap ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and HFT_Trap))
    and isHftVwapTrap,
    if close > vwapValue then high else low,
    "⚠️ HFT TRAP",
    Color.MAGENTA,
    close > vwapValue
);
# --- Inst Rev ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and Inst_Rev))
    and (isInstRevUp or isInstRevDn),
    if isInstRevUp then low else high,
    "INST REV " + (if isInstRevUp then "↑" else "↓"),
    Color.WHITE,
    if isInstRevUp then no else yes
);

# --- FVG ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and FVG))
    and (bullDispFVG or bearDispFVG),
    if bullDispFVG then low else high,
    "FVG " +
        (if bullDispFVG then "↑" else "↓") +
        (if isHQFVG then "✓" else ""),
    if isHQFVG then
        (if bullDispFVG then Color.GREEN else Color.RED)
    else
        (if bullDispFVG then Color.CYAN else Color.ORANGE),
    if bullDispFVG then no else yes
);

# --- Fill ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and FVG))
    and isFVGFill,
    if bullFVGFill then low else high,
    "FVG FILL",
    GlobalColor("Info"),
    if bullFVGFill then no else yes
);

# --- BRP ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and BPR))
    and isBPR,
    if bullBPR then low else high,
    "BPR " + (if bullBPR then "↑" else "↓") +
        (if isBPRValid then "✓" else ""),
    if isBPRValid then (if bullBPR then Color.GREEN else Color.RED)
                   else (if bullBPR then Color.CYAN else Color.DARK_ORANGE),
    if bullBPR then no else yes
);

# --- CHoCH Bubbles ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and BOS_B))
    and (chochUp or chochDn),
    if chochUp then low else high,
    "CHoCH " + (if chochUp then "↑" else "↓"),
    if chochUp then Color.GREEN else Color.RED,
    if chochUp then no else yes
);

# --- BOS Bubbles ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and BOS_B))
    and (bosUp or bosDn),
    if bosUp then low else high,
    "BOS " + (if bosUp then "↑" else "↓"),
    if bosUp then Color.CYAN else Color.ORANGE,
    if bosUp then no else yes
);

# --- OB Bubbles ---
AddChartBubble(
    (showAllBubbles or (showCustomBubble and OB_B))
    and (obBull or obBear),
    if obBull then low else high,
    "OB " + (if obBull then "BULL" else "BEAR"),
    if obBull then Color.CYAN else Color.MAGENTA,
    if obBull then no else yes
);
 # --- Mitigation ---

AddChartBubble(
    (showAllBubbles or (showCustomBubble and Mitigation))
    and (isMitigOriginUp or isMitigOriginDn),
    if isMitigOriginUp then low else high,
    "MITIG ORIGIN " + (if isMitigOriginUp then "↑" else "↓"),
    if isMitigOriginUp then Color.GREEN else Color.RED,
    if isMitigOriginUp then no else yes
);

# ====================================================
# DECAY SETTINGS
# ====================================================

def dirDecay  = 0.50;
def seqDecay  = 0.70;

# ====================================================
# DIRECTIONAL STATE VARIABLES (1 = Bullish, -1 = Bearish, 0 = None)
# ====================================================
def sweepDir        = if isUpSweep then 1 else if isDnSweep then -1 else 0;
def ictSweepDir     = if ictSellSideSweep then 1 else if ictBuySideSweep then -1 else 0;
def stopGrabDir     = if isUpStopGrab then 1 else if isDnStopGrab then -1 else 0;
def voidDir         = if voidUp then 1 else if voidDown then -1 else 0;
def refillDir       = if refillUp then 1 else if refillDown then -1 else 0;
def fvgDir          = if bullDispFVG then 1 else if bearDispFVG then -1 else 0;
def fvgFillDir      = if bullFVGFill then 1 else if bearFVGFill then -1 else 0;
def bprDir          = if bullBPR then 1 else if bearBPR then -1 else 0;
def bprValidDir     = if bullBPRValid then 1 else if bearBPRValid then -1 else 0;
def trendExhaustDir = if (effortLow and resultWeakDown) then 1 else if (effortHigh and resultWeakUp) then -1 else 0;
def instRevDir      = if isInstRevUp then 1 else if isInstRevDn then -1 else 0;
def sweepDirMem = CompoundValue(1,
    if isUpSweep then 1
    else if isDnSweep then -1
    else sweepDirMem[1] * dirDecay, 0);

def ictSweepDirMem = CompoundValue(1,
    if ictSellSideSweep then 1       # Swept lows -> Bullish reaction
    else if ictBuySideSweep then -1  # Swept highs -> Bearish reaction
    else ictSweepDirMem[1] * dirDecay, 0);

def voidDirMem = CompoundValue(1,
    if voidUp then 1
    else if voidDown then -1
    else voidDirMem[1] * dirDecay, 0);

def refillDirMem = CompoundValue(1,
    if refillUp then 1
    else if refillDown then -1
    else refillDirMem[1] * dirDecay, 0);

def fvgDirMem = CompoundValue(1,
    if bullDispFVG then 1
    else if bearDispFVG then -1
    else fvgDirMem[1] * dirDecay, 0);

def fvgFillDirMem = CompoundValue(1,
    if bullFVGFill then 1
    else if bearFVGFill then -1
    else fvgFillDirMem[1] * dirDecay, 0);

def bprDirMem = CompoundValue(1,
    if bullBPR then 1
    else if bearBPR then -1
    else bprDirMem[1] * dirDecay, 0);

def bprValidDirMem = CompoundValue(1,
    if bullBPRValid then 1
    else if bearBPRValid then -1
    else bprValidDirMem[1] * dirDecay, 0);

def trendExhaustDirMem = CompoundValue(1,
    if effortLow and resultWeakDown then 1     # Sellers exhausted -> Bullish
    else if effortHigh and resultWeakUp then -1  # Buyers exhausted -> Bearish
    else trendExhaustDirMem[1] * dirDecay, 0);

def instRevDirMem = CompoundValue(1,
    if isInstRevUp then 1
    else if isInstRevDn then -1
    else instRevDirMem[1] * dirDecay, 0);

# ====================================================
# SEQUENCE BOOSTERS (Decay)
# ====================================================

def seq_LRT_mem =
    if seq_LRT then 1 else seq_LRT_mem[1] * seqDecay;

def seq_IMB_mem =
    if seq_IMB then 1 else seq_IMB_mem[1] * seqDecay;

def seq_ICT_Mitig_mem =
    if seq_ICT_Mitig then 1 else seq_ICT_Mitig_mem[1] * seqDecay;

def seq_SweepExhaust_mem =
    if seq_SweepExhaust then 1 else seq_SweepExhaust_mem[1] * seqDecay;

def seq_MicroAbsorb_mem =
    if seq_MicroAbsorb then 1 else seq_MicroAbsorb_mem[1] * seqDecay;

def seq_MicroReject_mem =
    if seq_MicroReject then 1 else seq_MicroReject_mem[1] * seqDecay;

# ====================================================
# TREND BOOSTERS
# ====================================================

def revTrendUpBoost = if tr1 == 1 then 3 else 0;
def revTrendDnBoost = if tr1 == -1 then 3 else 0;

# ====================================================
# RPM V2 — Reversal Pressure Meter
# ====================================================

def rpmEventCore =
      (hasSweep        * 2)
    + (hasICTSweep     * 3)
    + (hasStopGrab     * 3)
    + (hasVoid         * 2)
    + (hasRefill       * 2)
    + (hasFVG          * 1)
    + (hasHQFVG        * 2)
    + (hasFVGFill      * 3)
    + (hasBPR          * 2)
    + (hasBPRValid     * 4)
    + (hasTrendExhaust * 3)
    + (hasClimax       * 3)
    + (hasHftTrap      * 4)
    + (hasShelf        * 1)
    + (hasChurn        * 1)
    + (hasPivot        * 2)
    + (hasAbsorb       * 1)
    + (hasReject       * 1)
    + (hasCompress     * 1);

def rpmSeqBonus =
      (seq_LRT_mem          * 8)
    + (seq_SFD              * 7)
    + (seq_IMB_mem          * 9)
    + (seq_ICT_Mitig_mem    * 6)
    + (seq_ICT_Cont         * 6)
    + (seq_VoidChain        * 5)
    + (seq_SweepExhaust_mem * 6)
    + (seq_MicroReject_mem  * 3)
    + (seq_MicroAbsorb_mem  * 3);

def rpmBiasUp =
      ((sweepDirMem       == -1) * 3)
    + ((ictSweepDirMem    == -1) * 3)
    + ((voidDirMem        == -1) * 2)
    + ((refillDirMem      ==  1) * 2)
    + ((fvgDirMem         == -1) * 1)
    + ((fvgFillDirMem     ==  1) * 2)
    + ((bprDirMem         ==  1) * 2)
    + ((bprValidDirMem    ==  1) * 3)
    + ((trendExhaustDirMem == -1) * 3)
    + ((instRevDirMem     ==  1) * 4);

def rpmBiasDn =
      ((sweepDirMem       ==  1) * 3)
    + ((ictSweepDirMem    ==  1) * 3)
    + ((voidDirMem        ==  1) * 2)
    + ((refillDirMem      == -1) * 2)
    + ((fvgDirMem         ==  1) * 1)
    + ((fvgFillDirMem     == -1) * 2)
    + ((bprDirMem         == -1) * 2)
    + ((bprValidDirMem    == -1) * 3)
    + ((trendExhaustDirMem ==  1) * 3)
    + ((instRevDirMem     == -1) * 4);

def rpmScoreRaw =
      rpmEventCore
    + rpmSeqBonus
    + rpmBiasUp
    + rpmBiasDn;

def maxRpmRaw = 84;
def rpmPercent = (rpmScoreRaw / maxRpmRaw) * 100;

AddLabel(
    showRPM,
    "RPM " + Round(rpmPercent, 0) + "% " ,
    if rpmPercent >= 80 then GlobalColor("RPM_UpStrong")
    else if rpmPercent >= 60 then GlobalColor("RPM_Up")
    else if rpmPercent >= 39 then Color.Light_Gray
    else if rpmPercent >= 20 then GlobalColor("DownTrend")
    else GlobalColor("RPM_DownStrong"),
    pos
);

# ====================================================
# REV V2 — Directional Reversal Pressure
# ====================================================

def revUpScore =
      ((stopGrabDir == -1) * 3)
    + ((voidDirMem     == -1) * 2)
    + ((refillDirMem   ==  1) * 3)
    + ((trendExhaustDirMem == -1) * 3)
    + ((instRevDirMem  ==  1) * 5)
    + (hasAbsorb * 2)
    + (hasReject * 2)
    + (hasShelf  * 1)
    + (hasPivot  * 2)
    + ((hasICTSweep and ictSweepDirMem == -1) * 3)
    + ((hasFVG and fvgDirMem == -1) * 1)
    + ((hasHQFVG and fvgDirMem == -1) * 2)
    + ((hasFVGFill and fvgFillDirMem == 1) * 3)
    + ((hasBPR and bprDirMem == 1) * 2)
    + ((hasBPRValid and bprValidDirMem == 1) * 4);

def revDnScore =
      ((stopGrabDir ==  1) * 3)
    + ((voidDirMem     ==  1) * 2)
    + ((refillDirMem   == -1) * 3)
    + ((trendExhaustDirMem ==  1) * 3)
    + ((instRevDirMem  == -1) * 5)
    + (hasAbsorb * 2)
    + (hasReject * 2)
    + (hasShelf  * 1)
    + (hasPivot  * 2)
    + ((hasICTSweep and ictSweepDirMem == 1) * 3)
    + ((hasFVG and fvgDirMem == 1) * 1)
    + ((hasHQFVG and fvgDirMem == 1) * 2)
    + ((hasFVGFill and fvgFillDirMem == -1) * 3)
    + ((hasBPR and bprDirMem == -1) * 2)
    + ((hasBPRValid and bprValidDirMem == -1) * 4);

def revUpScore1 =
      revUpScore
    + (seq_LRT_mem          * 4)
    + (seq_IMB_mem          * 5)
    + (seq_ICT_Mitig_mem    * 3)
    + (seq_SweepExhaust_mem * 4)
    + (seq_MicroAbsorb_mem  * 2)
    + revTrendUpBoost
    - revTrendDnBoost;

def revDnScore1 =
      revDnScore
    + (seq_LRT_mem          * 4)
    + (seq_IMB_mem          * 5)
    + (seq_ICT_Mitig_mem    * 3)
    + (seq_SweepExhaust_mem * 4)
    + (seq_MicroReject_mem  * 2)
    + revTrendDnBoost
    - revTrendUpBoost;


AddLabel(
    showRevLabel,
    "REV↓ " + Round(revDnScore1,0),
    if revDnScore1 >= 24 then Color.RED
    else if revDnScore1 >= 18 then Color.Pink
    else if revDnScore1 >= 10 then Color.ORANGE
    else Color.GRAY,
    pos
);

AddLabel(
    showRevLabel,
    "REV↑ " + Round(revUpScore1,0),
    if revUpScore1 >= 24 then Color.GREEN
    else if revUpScore1 >= 18 then Color.Light_GREEN
    else if revUpScore1 >= 10 then Color.YELLOW
    else Color.GRAY,
    pos
);

AddChartBubble(
    showREV_Bubbles and Round(revUpScore1,0) >= revBubbleThreshold,
    low,
    "REV↑ " + Round(revUpScore1,0),
    Color.Light_GREEN,
    no
);

AddChartBubble(
    showREV_Bubbles and Round(revDnScore1,0) >= revBubbleThreshold,
    high,
    "REV↓ " + Round(revDnScore1,0),
   GlobalColor("RPM_Down"),
    yes
);

#-----------------------------------------
# DISPLACEMENT ARROWS
#-----------------------------------------

plot cb_DispUp =
    if showCandleArrow and isDispUp and tr1 == 1
    then high
    else Double.NaN;
cb_DispUp.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_UP);
cb_DispUp.SetLineWeight(1);
cb_DispUp.AssignValueColor(Color.GREEN);

plot cb_DispDn =
    if showCandleArrow and isDispDn and tr1 == -1
    then low
    else Double.NaN;
cb_DispDn.SetPaintingStrategy(PaintingStrategy.BOOLEAN_ARROW_DOWN);
cb_DispDn.SetLineWeight(1);
cb_DispDn.AssignValueColor(Color.RED);

plot cb_RejectUp =
    if isRejectUp then low else Double.NaN;
cb_RejectUp.SetPaintingStrategy(PaintingStrategy.POINTS);
cb_RejectUp.SetLineWeight(2);
cb_RejectUp.AssignValueColor(Color.CYAN);

plot cb_RejectDn =
    if isRejectDn then high else Double.NaN;
cb_RejectDn.SetPaintingStrategy(PaintingStrategy.POINTS);
cb_RejectDn.SetLineWeight(2);
cb_RejectDn.AssignValueColor(Color.CYAN);

plot cb_Absorb =
    if isAbsorb then MidBodyVal() else Double.NaN;
cb_Absorb.SetPaintingStrategy(PaintingStrategy.POINTS);
cb_Absorb.SetLineWeight(2);
cb_Absorb.AssignValueColor(Color.YELLOW);

plot cb_Compress =
    if isCompression then MidBodyVal() else Double.NaN;
cb_Compress.SetPaintingStrategy(PaintingStrategy.SQUARES);
cb_Compress.SetLineWeight(1);
cb_Compress.AssignValueColor(Color.LIGHT_GRAY);

#-----------------------------------------
# CANDLE NARRATIVE LABEL
#-----------------------------------------

AddLabel(
    showCandleLabels,
    if isDispUp then "Disp ↑"
    else if isDispDn then "Disp ↓"
    else if isRejectUp then "Reject ↑"
    else if isRejectDn then "Reject ↓"
    else if isAbsorb then "Absorb"
    else if isCompression then "Compress"
    else "Candle Neutral",
    if isDispUp then Color.GREEN
    else if isDispDn then Color.RED
    else if isRejectUp or isRejectDn then Color.CYAN
    else if isAbsorb then Color.YELLOW
    else if isCompression then Color.LIGHT_GRAY
    else Color.GRAY,
    pos
);

# ====================================================
# PATTERN ENGINE v4
# ====================================================

def patternA_up =
    isHiddenAbsorb or
    (hasShelf and shelfLow) or
    hasChurn or
    (hasVoid and voidDir == 1) or
    (hasHQFVG and fvgDir == 1) or
    isMitigOriginUp or
    chochUp or
    bosUp;

def patternA_dn =
    isHiddenAbsorb or
    (hasShelf and shelfHigh) or
    hasChurn or
    (hasVoid and voidDir == -1) or
    (hasHQFVG and fvgDir == -1) or
    isMitigOriginDn or
    chochDn or
    bosDn;

def patternA = patternA_up or patternA_dn;

def patternB_up =
    (hasFVG and fvgDir == 1) or
    (hasHQFVG and fvgDir == 1) or
    (hasVoid and voidDir == 1) or
    hasBPRValid or
    (isICTDispFVG and ictBullFVG) or
    (isICTBPRValid and ictBullBPRValid);

def patternB_dn =
    (hasFVG and fvgDir == -1) or
    (hasHQFVG and fvgDir == -1) or
    (hasVoid and voidDir == -1) or
    hasBPRValid or
    (isICTDispFVG and ictBearFVG) or
    (isICTBPRValid and ictBearBPRValid);

def patternB = patternB_up or patternB_dn;

def patternC_up =
    hasFVGFill or
    hasRefill or
    isMitigOriginUp or
    isInstRevUp or
    hasTrendExhaust or
    chochUp or
    bosUp;

def patternC_dn =
    hasFVGFill or
    hasRefill or
    isMitigOriginDn or
    isInstRevDn or
    hasTrendExhaust or
    chochDn or
    bosDn;

def patternC = patternC_up or patternC_dn;

def patternID =
     if patternA then 1
else if patternB then 2
else if patternC then 3
else 0;

AddLabel(
    yes,
    " "
    + (if patternID == 1 then
            (if patternA_up then "Pattern A ↑ (Accumulation)"
             else "Pattern A ↓ (Distribution)")
       else if patternID == 2 then
            (if patternB_up then "Pattern B ↑ (Bull Displacement)"
             else "Pattern B ↓ (Bear Displacement)")
       else if patternID == 3 then
            (if patternC_up then "Pattern C ↑ (Bull Defense)"
             else "Pattern C ↓ (Bear Defense)")
       else "None"),
    if patternID == 1 then
        (if patternA_up then Color.GREEN else Color.PINK)
    else if patternID == 2 then Color.CYAN
    else if patternID == 3 then Color.ORANGE
    else Color.GRAY,
    pos
);

# ====================================================
# DISPLACEMENT QUALITY V2
# ====================================================

def dispSponsored =
    hasHQFVG or
    (hasVoid and voidDir != 0) or
    hasBPRValid or
    isICTDispFVG or
    isICTBPRValid;

def dispModerate =
    hasFVG and !dispSponsored;

def dispWeak =
    !hasFVG and !hasHQFVG and !hasVoid;

def dispQualityCode =
     if dispSponsored then 1
else if dispModerate then 2
else 3;

AddLabel(
    yes,
    " "
    + (if dispQualityCode == 1 then "Sponsored"
       else if dispQualityCode == 2 then "Moderate"
       else "Weak"),
    if dispQualityCode == 1 then Color.GREEN
    else if dispQualityCode == 2 then Color.YELLOW
    else Color.GRAY,
    pos
);

# ====================================================
# FAILURE ENGINE V2
# ====================================================

def failMitigation =
    hasFVGFill and !hasInstRev;
def failDisplacement =
    (hasVoid and !hasHQFVG) or
    (hasFVG and fvgDir == 0);
def failDefense =
    hasTrendExhaust and !hasInstRev;
def failAbandon =
    failMitigation and failDisplacement and failDefense;
def failStateCode =
     if failAbandon then 4
else if failDefense then 3
else if failMitigation then 2
else if failDisplacement then 1
else 0;

AddLabel(
    yes,
    " "
    + (if failStateCode == 4 then "Full Abandonment"
       else if failStateCode == 3 then "Defense Failure"
       else if failStateCode == 2 then "Mitigation Failure"
       else if failStateCode == 1 then "Displacement Weakening"
       else "Stable"),
    if failStateCode == 4 then Color.RED
    else if failStateCode == 3 then Color.ORANGE
    else if failStateCode == 2 then Color.YELLOW
    else if failStateCode == 1 then Color.LIGHT_GRAY
    else Color.GRAY,
    pos
);

# ====================================================
# FLOW BIAS / PRESSURE
# ====================================================

def flowBiasCode =
     if revUpScore > revDnScore then 1
else if revDnScore > revUpScore then -1
else 0;

AddLabel(
    yes,
    " "
    + (if flowBiasCode == 1 then "Pressure Up"
       else if flowBiasCode == -1 then "Pressure Dn"
       else "Mixed Pressure"),
    if flowBiasCode == -1 then Color.RED
    else if flowBiasCode == 1 then Color.LIGHT_GREEN
    else Color.LIGHT_GRAY,
    pos
);

# ====================================================
# SEQUENCE LABEL
# ====================================================

def seqCode = seqID;

AddLabel(
    yes,
    " "
    + (if seqCode == 1 then "LRT"
       else if seqCode == 2 then "SFD"
       else if seqCode == 3 then "IMB"
       else if seqCode == 4 then "ICT-Mitig"
       else if seqCode == 5 then "ICT-Cont"
       else if seqCode == 6 then "VoidChain"
       else if seqCode == 7 then "SweepExhaust"
       else if seqCode == 8 then "MicroReject"
       else if seqCode == 9 then "MicroAbsorb"
       else "None"),
    Color.LIGHT_GRAY,
    pos
);

# ====================================================
# NEXT PHASE
# ====================================================

def nextPhaseCode =
     if failStateCode == 4 then -2
else if failStateCode == 3 then -1
else if failStateCode == 2 then 0
else if failStateCode == 1 then 2
else if patternID == 1 then 1
else if patternID == 2 then 2
else if patternID == 3 then 3
else 0;

AddLabel(
    yes,
    "NEXT: "
    + (if nextPhaseCode == 1 then " B (Displacement)"
       else if nextPhaseCode == 2 then " C (Defense)"
       else if nextPhaseCode == 3 then " B Continuation"
       else if nextPhaseCode == 0 then " A Continuation"
       else if nextPhaseCode == -1 then "Reversal Forming"
       else if nextPhaseCode == -2 then "Opposite Pattern A (New Collection)"
       else "Unknown"),
    if nextPhaseCode == -1 or nextPhaseCode == -2 then GlobalColor("DownTrend")
    else if nextPhaseCode == 1 then Color.GREEN
    else if nextPhaseCode == 2 then Color.YELLOW
    else Color.GRAY,
    pos
);


# ====================================================
# DYNAMIC SR LINES (Unmitigated vs Mitigated State)
# ====================================================

# 1. Bullish Price Tracking
def curOrigPriceUp = CompoundValue(1,
    if isMitigOriginUp then low
    else curOrigPriceUp[1],
    Double.NaN
);
def hasGreenLevel = if !IsNaN(curOrigPriceUp) then 1 else 0;

# Detect when price closes below the support level, officially mitigating it
def isMitigatedUp = close < curOrigPriceUp;

# 1 = Unmitigated (Solid), 0 = Mitigated (Dashed)
def greenRegime = CompoundValue(1,
    if isMitigOriginUp then 1
    else if isMitigatedUp then 0
    else greenRegime[1], 1
);

plot GreenSolid = if SR_Lines and hasGreenLevel and greenRegime == 1 then curOrigPriceUp else Double.NaN;
GreenSolid.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
GreenSolid.SetDefaultColor(Color.GREEN);
GreenSolid.SetLineWeight(2);

plot GreenDashed = if SR_Lines and hasGreenLevel and greenRegime == 0 then curOrigPriceUp else Double.NaN;
GreenDashed.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
GreenDashed.SetStyle(Curve.POINTS);
GreenDashed.SetDefaultColor(Color.DARK_GREEN); # Slightly darker to fade it to the background
GreenDashed.SetLineWeight(1);


# 2. Bearish Price Tracking
def curOrigPriceDn = CompoundValue(1,
    if isMitigOriginDn then high
    else curOrigPriceDn[1],
    Double.NaN
);
def hasRedLevel = if !IsNaN(curOrigPriceDn) then 1 else 0;

# Detect when price closes above the resistance level, officially mitigating it
def isMitigatedDn = close > curOrigPriceDn;

# 1 = Unmitigated (Solid), 0 = Mitigated (Dashed)
def redRegime = CompoundValue(1,
    if isMitigOriginDn then 1
    else if isMitigatedDn then 0
    else redRegime[1], 1
);

plot RedSolid = if SR_Lines and hasRedLevel and redRegime == 1 then curOrigPriceDn else Double.NaN;
RedSolid.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
RedSolid.SetDefaultColor(Color.RED);
RedSolid.SetLineWeight(2);

plot RedDashed = if SR_Lines and hasRedLevel and redRegime == 0 then curOrigPriceDn else Double.NaN;
RedDashed.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
RedDashed.SetStyle(Curve.POINTS);
RedDashed.SetDefaultColor(Color.DARK_RED);
RedDashed.SetLineWeight(1);

input enableAlerts = yes;
input alertType   = Alert.BAR;
input alertSound  = Sound.NoSound;
Alert(enableAlerts and isMitigOriginUp, "Bullish Mitigation Origin Active",alertType, alertSound);
Alert(enableAlerts and isMitigOriginDn, "Bearish Mitigation Origin Active",alertType, alertSound);

# ====================================================
# HTF MITIGATION / HTF S/R
# ====================================================

input HTF_rt = no;    # HTF real time. Shhh... Secret repainter switch (No Admin)
def localOriginUp =
    if isMitigOriginUp then low
    else if IsNaN(localOriginUp[1]) then low
    else localOriginUp[1];
def localOriginDn =
    if isMitigOriginDn then high
    else if IsNaN(localOriginDn[1]) then high
    else localOriginDn[1];
def isHigherTf = htfTimeFrame > GetAggregationPeriod();
def htfL_raw = low(period = htfTimeFrame);
def htfH_raw = high(period = htfTimeFrame);
def htfL = if HTF_rt then htfL_raw else htfL_raw[1];
def htfH = if HTF_rt then htfH_raw else htfH_raw[1];
def htfRawUp = CompoundValue(1,
    if IsNaN(htfRawUp[1]) then htfL
    else if HTF_rt then htfL_raw
    else if htfL != htfL[1] then htfL
    else htfRawUp[1],
    htfL
);
def htfRawDn = CompoundValue(1,
    if IsNaN(htfRawDn[1]) then htfH
    else if HTF_rt then htfH_raw
    else if htfH != htfH[1] then htfH
    else htfRawDn[1],
    htfH
);
def activeUp =
    if !isHigherTf then localOriginUp
    else if HTF_rt then htfL_raw
    else htfRawUp;
def activeDn =
    if !isHigherTf then localOriginDn
    else if HTF_rt then htfH_raw
    else htfRawDn;
def htfExtUp = CompoundValue(1,
    if IsNaN(close) then htfExtUp[1] else activeUp,
    activeUp
);
def htfExtDn = CompoundValue(1,
    if IsNaN(close) then htfExtDn[1] else activeDn,
    activeDn
);
plot HTFLineUp = if HTF_SR and !IsNaN(htfExtUp) then htfExtUp else Double.NaN;
HTFLineUp.SetPaintingStrategy(PaintingStrategy.DASHES);
HTFLineUp.SetDefaultColor(Color.CYAN);
HTFLineUp.SetLineWeight(1);

plot HTFLineDn = if HTF_SR and !IsNaN(htfExtDn) then htfExtDn else Double.NaN;
HTFLineDn.SetPaintingStrategy(PaintingStrategy.DASHES);
HTFLineDn.SetDefaultColor(Color.MAGENTA);
HTFLineDn.SetLineWeight(1);
 
Last edited:
yOud2Yk_d.png



Liquidity Map V2 — Full Pattern Engine Manual (Operator Edition)

I. The Institutional Core

Definition of Liquidity
Resting orders (stops, limits, shelf clusters) acting as fuel for large‑scale institutional entries and exits without slippage.

Core Axiom Institutions need liquidity. Retail provides it. Markets are engineered to move toward liquidity pools — never away from them.

The Four Institutional Intents

  1. Collection Intent — Harvest resting orders to fill positions (Pattern A).
  2. Escape Intent — Push price rapidly away from fill zones (Pattern B).
  3. Defense Intent — Test and protect the origin of the sponsored move (Pattern C).
  4. Abandonment Intent — Exit or reverse when a sponsored leg fails.
II. The Three Institutional Patterns (Operator Mode)

Pattern A — Stop Grab & Reversal (Trap Origin)

Institutional Intent: Collection
Large operators cannot enter size without slippage unless they locate dense pockets of opposing orders. Retail stop‑losses become institutional fuel.

Sequence Shelf → Sweep → MicroReject / MicroAbsorb → InstRev

Operator Tells

  • Structural shelf forms (Zone 7/8).
  • Price sweeps below the shelf, triggering stops and baiting breakout shorts.
  • MicroReject: Violent denial of the sweep — shorts trapped.
  • MicroAbsorb: Heavy post‑sweep churning — passive absorption of retail selling.
Key Confirmations

  • seq_MicroReject — Confirms the exact moment the sweep is rejected.
  • seq_MicroAbsorb — Confirms institutional absorption at the shelf.
Pattern B — Displacement & Imbalance (Sponsored Leg)

Institutional Intent: Escape
Institutions push price away from the fill zone, creating imbalance.

Sequence Sweep → Order Block Formation → Wide‑Body Displacement → Refined ICT FVG → Void → BPR

Operator Tells

  • Body expansion (bigMiddleBody, ratio > 1.5).
  • Shrinking wicks.
  • RVOL > 1.5.
  • HQFVG, valid Order Blocks, full voids, strong BPR.
Pattern C — Mitigation & Refill (Defense Test)

Institutional Intent: Defense
Institutions test and defend the displacement and Order Block origins.

Sequence Displacement → Exhaustion → Drift Retrace → FVGFill / Mitigation Start → InstRev / BPR Defense

Operator Tells

  • Momentum decay (TrendExhaustion).
  • Volume decay on retrace.
  • Clean drift retrace into imbalance or OB region.
  • InstRev or BPRValid at defended levels.
Visual Operator Tells (Dynamic S/R)

  • Solid Line (Mitigation Start — Unmitigated) Virgin defense level / Order Block full of resting liquidity. Highly actionable.
  • Dashed Line (Mitigation Stop — Mitigated) Candle body has closed through the origin. Liquidity spent. Zone transitions or fails.




III. Microstructure Engines & Architecture



Institutional Intent
Microstructure Engine​
Key Modules & Footprints​
Collection
Fuel Engine​
Zone7, Zone8, SG, SW, ICTSweep, RVOL, seq_Reversal_v2, seq_MicroReject​
Escape
Displacement Engine​
bigMiddleBody, Order Blocks, HQFVG, Void, BPR, seq_Displacement_v2​
Defense
Mitigation Engine​
TrendExhaustion, FVGFill, Refill, InstRev, seq_InstRev_v2, Mitigation Start/Stop​
Abandonment
Friction Engine​
CH, HA, CE, TrendExhaustion, InstRev, seq_MicroAbsorb​


IV. Displacement Quality & Scoring Matrix

High Quality (Sponsored)


  • Body/wick > 1.5
  • RVOL > 1.5
  • Clean OB
  • HQFVG
  • Full void
  • Strong BPR → Strong continuation
Moderate Quality

  • Body/wick 1.0–1.5
  • RVOL 1.0–1.5
  • Standard FVG
  • Partial void → Range or chop
Low Quality (Unsponsored)

  • Body/wick < 1.0
  • RVOL < 1.0
  • Micro‑FVG
  • Pseudo‑void
  • Weak BPR → Impending reversal → Pattern A focus
V. Mitigation Failure & Reversal Logic

When defense fails, the prior leg is abandoned.

Failed Mitigation

  • Deep FVG fill violating the OB origin
  • Refill retrace breaking structural pivots
  • Candle body close through Mitigation Start → Mitigation Stop
Failed Displacement

  • Shrinking bodies
  • Expanding wicks
  • RVOL < 1.0
Failed Defense

  • TrendExhaustion + InstRev against trend
Full Abandonment All three failures → New Pattern A cycle in opposite direction.

VI. Trigger Chain Logic

Trigger Chain A — Collection


Shelf (Zone 7/8) → Sweep (SG/SW/ICTSweep) → RVOL Spike → Rejection Close → seq_Reversal_v2

Trigger Chain B — Escape

Sweep Origin → Order Block Origin → Body Expansion (>1.5 ATR) → RVOL Expansion → HQFVG / Void / BPR → seq_Displacement_v2

Trigger Chain C — Defense

Displacement → TrendExhaustion → Volume Decay → Drift Retrace → FVGFill / Mitigation Start → seq_ICTFVGFill_v2

VII. Execution Playbook

1. Pattern A Execution — Stop Grab & Reversal

Entry Logic
Never buy the initial sweep. Wait for wick extension → MicroReject / MicroAbsorb → snap back inside shelf.

Risk Logic Hard stop beneath sweep wick.

Continuation Filter Sponsored displacement → reversal holds. No displacement → expect chop.

2. Pattern B Execution — Displacement & Imbalance

Entry Logic
Enter on first refined ICT FVG retest, OB test, or void edge retest.

Risk Logic Stops inside imbalance or behind OB origin.

Continuation Filter Body/wick > 1.5 + RVOL > 1.5 → sponsored.

3. Pattern C Execution — Mitigation & Refill

Entry Logic
Execute on first retest of Solid Line (Mitigation Start). Dashed Line → defense broken → step aside.

Risk Logic Stops below Solid Line / OB origin. Candle body close through → instant exit.

Continuation Filter Volume decay + respect of midpoint (BPR/FVG/OB) → trend continues. Slice through or InstRev against trend → mitigation failed.

APPENDIX — Liquidity Map V2 Glossary

  • BPR — Balanced Price Range midpoint zone.
  • CE — Collapse Event; friction signal indicating abandonment.
  • CH — Churn; post‑sweep absorption.
  • Deep FVG Violation — Full fill invalidating displacement origin.
  • Displacement — Sponsored wide‑body candle.
  • Drift Retrace — Controlled low‑volume retrace.
  • FVG — Fair Value Gap.
  • HQFVG — High‑Quality ICT FVG.
  • ICT Sweep — Swing sweep with rejection + RVOL.
  • InstRev — Institutional reversal print.
  • Mitigation Start — Solid Line; active defense.
  • Mitigation Stop — Dashed Line; defense broken.
  • MicroAbsorb — Shelf + churn absorption.
  • MicroReject — Sweep rejection.
  • Order Block — Opposing candle(s) before displacement.
  • Refill — Controlled void fill.
  • RVOL Spike — Institutional participation.
  • Shelf (Zone 7/8) — Multi‑touch stop cluster.
  • TrendExhaustion — Momentum decay.
 
Last edited:
Update: V2 is underway. The rabbit hole got deeper than expected — integrating ICT logic, reorganizing the script, and hammering through debugging/testing. o_O

Standby to standby…
 
Welcome to the rabbit hole!
Standing by to standby! Huge thanks for putting in the hard work on this update—we know how deep that rabbit hole gets
That graphic was killer — 🏆 I’m still laughing at the rabbit‑hole lab setup. Thanks for putting that together and for the boost while I’m buried in this update.
 
Precursor Post — Liquidity Map V2 (Events‑Only Preview)

avSHFYy_d.png


Before the full Liquidity Map V2 release, I’m putting out an Events‑Only version as a preview while I continue test‑driving the complete system. This module isolates the Liquidity Events layer from the full V2 architecture, allowing early feedback on event detection, bubble clarity, and structural consistency.

This preview includes:


  • All Liquidity Events (sweeps, voids, refills, shelves, traps, pivots, etc.)
  • Stacked event bubbles for clean visual hierarchy
  • Updated logic paths from the full V2 rewrite
  • User controls for toggling event bubbles and labels
  • Compatibility with all chart types (time, tick, HA, range) using raw OHLC
  • No RPM or Sequencer modules — those remain in the full version
This Events‑Only release is meant to serve as a lightweight test bed while I finish validating the complete V2 system.

Developer Notes

This module is extracted directly from the full Liquidity Map V2 rewrite. Event logic, bubble hierarchy, and structural detection are identical to the full version. RPM scoring and Micro/Macro sequencing are disabled in this preview. Feedback on event accuracy, bubble clarity, and edge‑case behavior is welcomed. Full V2 release will follow once testing is complete, I will update the 1st post.

📌 GLOSSARY

⭐ Liquidity Events


  • SWEEP ↑ / ↓ — Price grabbed liquidity above/below recent highs or lows.
  • LIQ GRAB ↑ / ↓ — Stop‑run wick; market hunted resting orders.
  • VOID — Fast displacement created an imbalance zone.
  • REFILL — Price traded back into the imbalance.
  • FVG — Fair Value Gap; a three‑bar imbalance.
  • FVG✓ — High‑quality displacement gap.
  • FVG FILL — Price filled the gap.
  • BPR — Break–Pullback–Retest; structural retest.
  • BPR✓ — Confirmed retest with rejection.
  • HFT TRAP — Fast wick trap; liquidity taken then reversed.
  • ICT SWEEP — Classic liquidity sweep of a prior high/low.
  • INST REV — Institutional reversal; trend shift signature.
  • CLIMAX — Range spike; exhaustion candle.
  • EXHAUSTION — Momentum stall after a strong push.
  • SHELF — Sideways base; compression zone.
  • DP CHURN — Overlap and friction; no directional commitment.
  • PIVOT CLUSTER — Multiple pivots forming a reaction zone.
  • HIDDEN ABSORB — Absorption candle; buyers/sellers soaking orders.

Study Link
Liquidity_Map_V2_Events


Ruby:
############################################
# LIQUIDITY MAP — Events Only
# Version: 2.0
# Author: atcsam — 09/14/26
#
# Description:
# Microstructure model that maps institutional liquidity behavior.
# This module extracts the Liquidity Events layer from the full
# Liquidity Map V2 system (Events, Reversal Probability Meter,
# Micro/Macro Event Sequencer). Only event detection and event
# bubbles are included in this version.

############################################

#-----------------------------------------
# USER TOGGLES
#-----------------------------------------
input useRTH = no;
input shelfMode = {default "High", "Medium", "Institutional"};


input showEvent_Bubbles = yes;   # Zone Bubbles master switch

#group "Event Visual Toggles"

input BPR               = yes;  # Balance Price Range markers
input Climax_Exhaust    = yes;  # Volume Climaxes & Trend Exhaustions
input Clusters          = yes;  # Pivot Clusters
input DP_Churn          = yes;  # Churn / Effort vs. Result friction bubbles
input FVG               = yes;  # FVG and HQ-FVG markers/bubbles
input HFT_Trap          = yes;  # HFT Trap triggers
input Hidden_Absorption = yes;  # Hidden Absorption
input ICT_Sweep         = yes;  # ICT Liquidity Sweeps
input Inst_Rev          = yes;  # Institutional Reversal footprints (Zone 13)
input Shelf             = yes;  # Support/Resistance Shelves
input StopGrab          = yes;  # Stop Grab triggers
input Sweep             = yes;  # Swing Sweep
input Voids             = yes;  # Void and Refill




input showFlowLabel      = no;   # Flow label at top

# GlobalColors
DefineGlobalColor("MagLt", CreateColor(237, 74, 213));
DefineGlobalColor("Amber", CreateColor(220, 170, 45));
DefineGlobalColor("Trap", CreateColor(220, 65, 145));
DefineGlobalColor("RPM_DownStrong", Color.RED);
DefineGlobalColor("RPM_Down", Color.DARK_RED);
DefineGlobalColor("RPM_Neutral", Color.YELLOW);
DefineGlobalColor("RPM_Up", Color.GREEN);
DefineGlobalColor("RPM_UpStrong", Color.DARK_GREEN);
DefineGlobalColor("Info", CreateColor(0, 190, 220));
DefineGlobalColor("Paper",          CreateColor(245, 240, 225));
DefineGlobalColor("Parchment",      CreateColor(221, 204, 178));
DefineGlobalColor("FiberP",         CreateColor(145, 121, 90));
DefineGlobalColor("DownTrend",      CreateColor(255, 0, 128));
DefineGlobalColor("Fill",        CreateColor(0, 128, 255));  
DefineGlobalColor("UpTrend",        CreateColor(0, 128, 255));  

# ====================================================
# RTH FILTER (Optional)
# ====================================================

def isRTH =
    SecondsFromTime(0930) >= 0 and
    SecondsTillTime(1600) >= 0;

# --- RTH-Filtered Volume (Corrected) ---
# Instead of zeroing overnight bars (which corrupts averages),
# we exclude them from the average using Double.NaN.
def rawVol = volume;

def volRTH = if useRTH and !isRTH then Double.NaN else rawVol;

# Use volRTH for averages, but use rawVol for actual bar volume.
# This keeps RVOL honest without breaking your zone logic.
def avgVol20 = Average(volRTH, 20);

def RVOL20 = if avgVol20 > 0 then rawVol / avgVol20 else 1;

# --- Core Price Data ---
def price       = close;
def candleRange = high - low;
def candleBody  = AbsValue(close - open);
def upperWick   = high - Max(open, close);
def lowerWick   = Min(open, close) - low;

# --- ATR ---
def atr20 = ATR(20);


# ====================================================
# ZONE 1 — DARK POOL CHURN
# ====================================================
def _isChurn =
    RVOL20 > 1.8 and
    candleBody / candleRange < 0.35;
def isChurn = if IsNaN(_isChurn) then 0 else _isChurn;

# ====================================================
# ZONES 2 — SWEEPS & STOP GRABS
# ====================================================
def _isUpSweep = RVOL20 > 1.5 and candleBody > atr20 * 0.65 and close > open;
def isUpSweep = if IsNaN(_isUpSweep) then 0 else _isUpSweep;

def _isDnSweep = RVOL20 > 1.5 and candleBody > atr20 * 0.65 and close <= open;
def isDnSweep = if IsNaN(_isDnSweep) then 0 else _isDnSweep;

def _isUpStopGrab =
    RVOL20 > 1.5 and
    upperWick > candleRange * 0.45 and
    close < (high + low) / 2;
def isUpStopGrab = if IsNaN(_isUpStopGrab) then 0 else _isUpStopGrab;

def _isDnStopGrab =
    RVOL20 > 1.5 and
    lowerWick > candleRange * 0.45 and
    close > (high + low) / 2;
def isDnStopGrab = if IsNaN(_isDnStopGrab) then 0 else _isDnStopGrab;



# ====================================================
# ZONES 3 — VOIDS & REFILLS
# ====================================================
def prevHigh = high[1];
def prevLow  = low[1];


# --- VOID (Displacement Gap) ---

def strongBody = candleBody > candleRange * 0.55;
def smallUpperWick = upperWick < candleRange * 0.35;
def smallLowerWick = lowerWick < candleRange * 0.35;
def normalVolume = RVOL20 > 0.8;

def voidUp =
    close > prevHigh and
    strongBody and
    smallUpperWick and
    normalVolume;

def voidDown =
    close < prevLow and
    strongBody and
    smallLowerWick and
    normalVolume;
# --- REFILL ---
def _refillUp   = voidUp[1] and low <= prevHigh[1];
def refillUp    = if IsNaN(_refillUp) then 0 else _refillUp;

def _refillDown = voidDown[1] and high >= prevLow[1];
def refillDown  = if IsNaN(_refillDown) then 0 else _refillDown;

def isVoidRefill = refillUp or refillDown;


def isVoid = voidUp or voidDown;
def voidBN = CompoundValue(1,
    if isVoid then BarNumber() else voidBN[1],
    0
);

# ====================================================
# ZONE 4 — HIDDEN Absorption (1.2)
# ====================================================
def _isHiddenAbsorb =
    RVOL20 >= 1.80 and
    candleBody <= atr20 * 0.30 and
    candleRange >= atr20 * 0.40 and
    (upperWick + lowerWick) >= candleBody * 0.25 and
    (upperWick + lowerWick) <= candleBody * 0.75 and
    !isUpSweep and !isDnSweep and
    !isUpStopGrab and !isDnStopGrab and
    !voidUp and !voidDown;

def isHiddenAbsorb = if IsNaN(_isHiddenAbsorb) then 0 else _isHiddenAbsorb;


# ====================================================
# ZONE 5 — VOLUME CLIMAX
# ====================================================
def _isVolClimax =
    RVOL20 > 3.0 and
    candleBody > atr20 * 0.8 and
    (upperWick + lowerWick) < candleRange * 0.30;
def isVolClimax = if IsNaN(_isVolClimax) then 0 else _isVolClimax;

# ====================================================
# ZONE 6 — TREND EXHAUSTION
# ====================================================
def _effortHigh = RVOL20 > 1.5 and candleBody > atr20 * 0.5;
def effortHigh  = if IsNaN(_effortHigh) then 0 else _effortHigh;

def _resultWeakUp = close < open and upperWick > candleRange * 0.4;
def resultWeakUp  = if IsNaN(_resultWeakUp) then 0 else _resultWeakUp;

def _effortLow = RVOL20 > 1.5 and candleBody > atr20 * 0.5;
def effortLow  = if IsNaN(_effortLow) then 0 else _effortLow;

def _resultWeakDown = close > open and lowerWick > candleRange * 0.4;
def resultWeakDown  = if IsNaN(_resultWeakDown) then 0 else _resultWeakDown;

def _isTrendExhaustion = (effortHigh and resultWeakUp) or (effortLow and resultWeakDown);
def isTrendExhaustion  = if IsNaN(_isTrendExhaustion) then 0 else _isTrendExhaustion;

# ====================================================
# ZONE 7 — LIQUIDITY SHELVES
# ====================================================
def shelfHigh_H =
    high == Highest(high, 5) and
    high[1] == Highest(high[1], 5) and
    high[2] == Highest(high[2], 5);

def shelfLow_H =
    low == Lowest(low, 5) and
    low[1] == Lowest(low[1], 5) and
    low[2] == Lowest(low[2], 5);

def shelfHigh_M =
    shelfHigh_H and RVOL20 < 0.8 and candleRange < atr20 * 0.6;

def shelfLow_M =
    shelfLow_H and RVOL20 < 0.8 and candleRange < atr20 * 0.6;

def shelfHigh_I =
    shelfHigh_H and RVOL20 < 0.6 and candleBody < atr20 * 0.4 and upperWick < candleRange * 0.3;

def shelfLow_I =
    shelfLow_H and RVOL20 < 0.6 and candleBody < atr20 * 0.4 and lowerWick < candleRange * 0.3;

def _isShelf =
    if shelfMode == shelfMode."High" then (shelfHigh_H or shelfLow_H)
    else if shelfMode == shelfMode."Medium" then (shelfHigh_M or shelfLow_M)
    else (shelfHigh_I or shelfLow_I);
def isShelf = if IsNaN(_isShelf) then 0 else _isShelf;

def shelfLow  = shelfLow_H or shelfLow_M or shelfLow_I;
def shelfHigh = shelfHigh_H or shelfHigh_M or shelfHigh_I;

# ====================================================
# ZONE 8 — PIVOT CLUSTER (Compression)
# ====================================================
def pivotHigh20 = Highest(high, 20);
def pivotLow20  = Lowest(low, 20);

def _pivotHighCompression =
    AbsValue(pivotHigh20 - pivotHigh20[5]) <= atr20 * 0.4;
def pivotHighCompression = if IsNaN(_pivotHighCompression) then 0 else _pivotHighCompression;

def _pivotLowCompression =
    AbsValue(pivotLow20 - pivotLow20[5]) <= atr20 * 0.4;
def pivotLowCompression = if IsNaN(_pivotLowCompression) then 0 else _pivotLowCompression;

def pivotBandCompression = pivotHighCompression and pivotLowCompression;

def _rangeCompression =
    candleRange < atr20 * 0.6 and candleRange < candleRange[1];
def rangeCompression = if IsNaN(_rangeCompression) then 0 else _rangeCompression;

def _volCompression =
    RVOL20 < 0.8 and RVOL20 < RVOL20[1];
def volCompression = if IsNaN(_volCompression) then 0 else _volCompression;

def _pivotCluster =
    pivotBandCompression and
    rangeCompression and
    volCompression and
    (isShelf or isHiddenAbsorb or isUpStopGrab or isDnStopGrab or isVoid or isVoidRefill);
def pivotCluster = if IsNaN(_pivotCluster) then 0 else _pivotCluster;

# --- Directional classification
def pivotClusterUp   = pivotCluster and high == pivotHigh20;
def pivotClusterDown = pivotCluster and low  == pivotLow20;
def pivotClusterMid  = pivotCluster and !pivotClusterUp and !pivotClusterDown;


# ====================================================
# ZONE 9 — FVG + FVG FILL
# ====================================================

input lookbackPeriod = 10;
input bodyMultiplier = 1.5;

# --- Body calculations ---
def body = AbsValue(close - open);
def avgBodySize = Average(body[1], lookbackPeriod);
def middleBody = AbsValue(close[1] - open[1]);
def bigMiddleBody = middleBody > avgBodySize * bodyMultiplier;

# ====================================================
# FVG DETECTION
# ====================================================
# A = 2 bars ago
# B = 1 bar ago (middle candle)
# C = current bar

def bullFVG =
    bigMiddleBody and
    high[2] < low;

def bearFVG =
    bigMiddleBody and
    low[2] > high;

def isFVG = bullFVG or bearFVG;
def isHQFVG = isFVG and (BarNumber()[1] - voidBN <= 3);

# ====================================================
# MIDDLE CANDLE BN (Anchor)
# ====================================================
def middleBN = if isFVG then BarNumber()[1] else middleBN[1];

def hasMiddle = middleBN > 0;

# ====================================================
# FORWARD WINDOW (5 Bars After Middle Candle)
# ====================================================
def fvgForwardWindow =
    hasMiddle and
    BarNumber() > middleBN and
    BarNumber() <= middleBN + 5;

# ====================================================
# FVG FILL (Mitigation)
# ====================================================

def bullFVGFill =
    fvgForwardWindow and
    bullFVG[1] and
    low <= high[2];

def bearFVGFill =
    fvgForwardWindow and
    bearFVG[1] and
    high >= low[2];

def isFVGFill = bullFVGFill or bearFVGFill;

# ====================================================
# ACTIVE FLAGS
# ====================================================

def activeFVG =
    isFVG and fvgForwardWindow;

def activeFVGFill =
    isFVGFill and fvgForwardWindow;

# VOID-only refill remains untouched
def voidOnlyRefill =
    isVoidRefill and !isFVG;



# ====================================================
# ZONE 10 — HFT VWAP TRAP
# ====================================================
def vwapValue = vwap;
def vwapDist = AbsValue(close - vwapValue);
def vwapStDev = StDev(vwapDist, 50);

def upperVwapBand = vwapValue + (2.0 * vwapStDev);
def lowerVwapBand = vwapValue - (2.0 * vwapStDev);

def _isHftVwapTrap =
    (high > upperVwapBand and (isUpStopGrab or isVolClimax or isTrendExhaustion)) or
    (low < lowerVwapBand and (isDnStopGrab or isVolClimax or isTrendExhaustion));
def isHftVwapTrap = if IsNaN(_isHftVwapTrap) then 0 else _isHftVwapTrap;

# ====================================================
# ZONE 11 — LIQUIDITY SWEEPS
# ====================================================

# Swing references (you already use ATR-scaled displacement sweeps,
# so this is a separate liquidity-grab model)
def swingHigh = Highest(high[1], 5);
def swingLow  = Lowest(low[1], 5);

# Buy-side liquidity sweep (take previous high, reject)
def ictBuySideSweep =
    high > swingHigh and
    close < swingHigh and
    close < open and
    RVOL20 >= 1.25;

# Sell-side liquidity sweep (take previous low, reject)
def ictSellSideSweep =
    low < swingLow and
    close > swingLow and
    close > open and
    RVOL20 >= 1.25;

def isICTSweep = ictBuySideSweep or ictSellSideSweep;


# ====================================================
# ZONE 12 — BALANCED PRICE RANGE (BPR)
# VOID-ANCHORED, 5-BAR FORWARD VALIDATION
# ====================================================

# --- BPR EVENT DETECTION ---

# Minimum displacement threshold
def dispUp  = close[1] - open[1] > (high[1] - low[1]) * 0.35;
def dispDn  = open[1] - close[1] > (high[1] - low[1]) * 0.35;

# Opposing displacement
def oppBull = dispDn[1] and dispUp;
def oppBear = dispUp[1] and dispDn;

# Wick overlap (required)
def wickOverlap =
    low[1] < high[2] and
    high[1] > low[2];

# Swing filter (optional but recommended)
def swingContext =
    high[1] == Highest(high, 5) or
    low[1]  == Lowest(low, 5);

# Final BPR event
def bullBPR =
    oppBull and wickOverlap and swingContext;

def bearBPR =
    oppBear and wickOverlap and swingContext;

def bullBPRValid =
    bullBPR and close > high[1];

# Bearish continuation: price closes BELOW the BPR low
def bearBPRValid =
    bearBPR and close < low[1];

# Combined continuation flag
def isBPRValid = bullBPRValid or bearBPRValid;

def isBPR = bullBPR or bearBPR;

# ====================================================
# ZONE 13 — INSTITUTIONAL REVERSAL SIGNATURE (Directional)
# ====================================================

# --- Bullish Reversal Signatures (Defense at Lows / Sweeping Sell-Side) ---
def _isInstReversalUp =
    # Exhaustion + Bullish Void Refill
    (isTrendExhaustion and refillUp) or

    # Volume Climax + Support Shelf
    (isVolClimax and shelfLow) or

    # Down Stop Grab + Exhaustion
    (isDnStopGrab and isTrendExhaustion) or

    # ICT Sell-Side Liquidity Sweep (Sweeping lows, rejecting up)
    (ictSellSideSweep) or

    # Down Sweep + Mitigation + Exhaustion (Failed downside displacement)
    (isDnSweep and isFVGFill and isTrendExhaustion) or

    # Bullish BPR + FVG Fill (Void-anchored bullish reversal chain)
    (bullBPR and isFVGFill);

def isInstReversalUp = if IsNaN(_isInstReversalUp) then 0 else _isInstReversalUp;


# --- Bearish Reversal Signatures (Defense at Highs / Sweeping Buy-Side) ---
def _isInstReversalDn =
    # Exhaustion + Bearish Void Refill
    (isTrendExhaustion and refillDown) or

    # Volume Climax + Resistance Shelf
    (isVolClimax and shelfHigh) or

    # Up Stop Grab + Exhaustion
    (isUpStopGrab and isTrendExhaustion) or

    # ICT Buy-Side Liquidity Sweep (Sweeping highs, rejecting down)
    (ictBuySideSweep) or

    # Up Sweep + Mitigation + Exhaustion (Failed upside displacement)
    (isUpSweep and isFVGFill and isTrendExhaustion) or

    # Bearish BPR + FVG Fill (Void-anchored bearish reversal chain)
    (bearBPR and isFVGFill);

def isInstReversalDn = if IsNaN(_isInstReversalDn) then 0 else _isInstReversalDn;


# --- Master Flag (For existing generic labels/bubbles) ---
def isInstReversal = isInstReversalUp or isInstReversalDn;
# ====================================================
# FLOW LABEL — FULLY PRIORITIZED (ICT + DISPLACEMENT)
# ====================================================

AddLabel(showFlowLabel,
    "Flow: " +
    (if isHftVwapTrap then "⚠️ HFT VWAP TRAP"
     else if isInstReversal then "REVERSAL"
     else if isICTSweep then "ICT SWEEP"
     else if isBPRValid then "BPR✓"
     else if isFVGFill then "FVG FILL"
     else if isHQFVG then "FVG✓"  
     else if isBPR then "BPR"
     else if isFVG then "FVG"
     else if isVoidRefill then "REFILL"
     else if isVoid then "VOID"
     else if isUpStopGrab then "LIQ GRAB ↑"
     else if isDnStopGrab then "LIQ GRAB ↓"
     else if isUpSweep then "SWEEP ↑"
     else if isDnSweep then "SWEEP ↓"
     else if isHiddenAbsorb then "HIDDEN ABSORB"
     else if isVolClimax then "CLIMAX"
     else if isTrendExhaustion then "EXHAUSTION"
     else if isShelf then "SHELF"
     else if pivotCluster then "PIVOT CLUSTER"
     else if isChurn then "DP CHURN"
     else "Standard Flow"),
    if isHftVwapTrap then Color.MAGENTA else Color.WHITE
);




# ====================================================
# CHART BUBBLES — Bottom → Top PRIORITY STACK
# ====================================================

# --- Tier 1: Weak ---
AddChartBubble(showEvent_Bubbles and dp_churn and isChurn, low, "DP CHURN", GlobalColor("DownTrend"), no);

AddChartBubble(
    showEvent_Bubbles and shelf and isShelf,
    if shelfHigh then high else low,
    "SHELF",
    if shelfHigh_I or shelfLow_I then Color.WHITE
    else if shelfHigh_M or shelfLow_M then GlobalColor("Parchment")
    else GlobalColor("FiberP"),
    shelfHigh
);

# --- Tier 9: Institutional Reversal ---
# ====================================================
# INSTITUTIONAL REVERSAL BUBBLES (Directional)
# ====================================================

# --- Bullish Institutional Reversal ---
AddChartBubble(
   showEvent_Bubbles and Inst_Rev and Inst_Rev and isInstReversalUp,
    low,
    "INST REV ↑",
    Color.WHITE,
    no  # Placed below the low
);

# --- Bearish Institutional Reversal ---
AddChartBubble(
    showEvent_Bubbles and Inst_Rev and isInstReversalDn,
    high,
    "INST REV ↓",
    Color.WHITE,
    yes # Placed above the high
);
#GlobalColor("Trap")

AddChartBubble(showEvent_Bubbles and Hidden_Absorption and isHiddenAbsorb, low, "ABSORB", Color.CYAN, no);


# --- Tier 2: Liquidity ---
AddChartBubble(showEvent_Bubbles and stopgrab and isUpStopGrab, high, "LIQ ↑", Color.YELLOW, yes);
AddChartBubble(showEvent_Bubbles and stopgrab and isDnStopGrab, low, "LIQ ↓", Color.LIME, no);

AddChartBubble(showEvent_Bubbles and sweep and isUpSweep, high, "SWEEP ↑", Color.CYAN, yes);
AddChartBubble(showEvent_Bubbles and sweep and isDnSweep, low, "SWEEP ↓", Color.LIGHT_RED, no);


# --- Tier 3: Displacement ---

AddChartBubble(
    showEvent_Bubbles and voids and isVoid,
    high,
    "VOID",
    if !isVoidRefill and !isFVGfill and !isfvg
        then Color.DARK_GREEN
        else Color.LIGHT_GRAY,
    yes
);



AddChartBubble(showEvent_Bubbles and voids and isVoidRefill and !isFVGfill, low, "REFILL",GlobalColor("fill"), no);


# --- Tier 4: Volume / Exhaustion ---
AddChartBubble(
    showEvent_Bubbles and Climax_Exhaust and isVolClimax,
    if upperWick > lowerWick then high else low,
    "CLIMAX",
    GlobalColor("Amber"),
    upperWick > lowerWick
);


AddChartBubble(
    showEvent_Bubbles and Climax_Exhaust and isTrendExhaustion,
    if upperWick > lowerWick then high else low,
    "EXHAUST",
    Color.RED,
    upperWick > lowerWick
);



# --- Tier 5: Structure ---
# Up pivot cluster (swing high)
AddChartBubble(
    showEvent_Bubbles and Clusters and pivotClusterUp,
    high,
    "PIVOT CLUSTER ↑",
    Color.ORANGE,
    yes
);

# Down pivot cluster (swing low)
AddChartBubble(
    showEvent_Bubbles and Clusters and pivotClusterDown,
    low,
    "PIVOT CLUSTER ↓",
    Color.ORANGE,
    no
);

# Mid pivot cluster (internal)
AddChartBubble(
    showEvent_Bubbles and Clusters and pivotClusterMid,
    close,
    "PIVOT CLUSTER",
    Color.Light_gray,
    yes
);


# --- Tier 6: ICT Auction Imbalance ---

AddChartBubble(
    showEvent_Bubbles and fvg and bullFVG,
    low,
    "FVG " + (if isHQFVG then "↑✓" else "↑"),
    Color.CYAN,
    no
);

AddChartBubble(
    showEvent_Bubbles and fvg and bearFVG,
    high,
    "FVG " + (if isHQFVG then "↓✓" else "↓"),
    Color.ORANGE,
    yes
);

AddChartBubble(showEvent_Bubbles and fvg and isFVGFill,
    if bullFVGFill then low else high,
    "FVG FILL",
    globalColor("Info"),
    if bullFVGFill then no else yes
);


# ====================================================
# BPR & BPR VALID BUBBLES (EXCLUDE BPR WHEN VALID)
# ====================================================

AddChartBubble(
    showEvent_Bubbles and bpr and isBPR,
    if bullBPR then high else low,
    "BPR" + (if isBPRValid then "✓" else ""),
    if isBPRValid then (if bullBPR then Color.GREEN else Color.RED)
                   else (if bullBPR then Color.CYAN else Color.DARK_ORANGE),
    bullBPR
);


# --- Tier 7: ICT Liquidity ---
AddChartBubble(showEvent_Bubbles and ICT_Sweep and ictBuySideSweep, high, "ICT SWEEP ↓", Color.MAGENTA, yes);
AddChartBubble(showEvent_Bubbles and ICT_Sweep and ictSellSideSweep, low, "ICT SWEEP ↑", Color.MAGENTA, no);


# --- Tier 8: Institutional ---
AddChartBubble(showEvent_Bubbles and HFT_Trap and isHftVwapTrap,
    if close > vwapValue then high else low,
    "⚠️ HFT TRAP",
    Color.MAGENTA,
    close > vwapValue
);

# ---- Event Bubbles and Flow Label Only ---
# ✂️────────── CUT ALONG THIS LINE ───────────✂️


1RgQ2ah.png
 
Last edited:
Update & Request for Ideas
rEsceb0_d.png


Still test‑driving the Reversal Probability Meter (probability ≠ prediction) and working through some UI tweaks.

Where I could use some help is in turning these event signals into meaningful support/resistance levels. I’ve been experimenting with shelves, InstRev, sweeps, etc., but I’d like to hear how others approach this or whether anyone has cracked a reliable method.

If you’ve got thoughts, recommendations, or even “quit wasting your time,” I’m open to it.

For reference, the 1‑hour LMT DP Churn is was looking at — curious how others would classify or use that in level construction.

uDLhUDd.png
 
My personal trading style tends toward:

HTF Absorption and then LTF Execution

High-volume churn on a higher timeframe shows where smart money is stepping in to absorb order flow over time. This isn't an execution signal; it's a zone builder. Drawing support/resistance around HTF Churn bars gives you macro levels where major inventory is changing hands.

Sweeps on lower timeframes show the precise moment liquidity is engine-purged (stop runs). Searching for a sweep inside or at the boundary of an HTF Absorption Zone gives you a tight, defined invalidation level without cluttering your higher-timeframe view with bubbles.
 
My personal trading style tends toward:

HTF Absorption and then LTF Execution

High-volume churn on a higher timeframe shows where smart money is stepping in to absorb order flow over time. This isn't an execution signal; it's a zone builder. Drawing support/resistance around HTF Churn bars gives you macro levels where major inventory is changing hands.

Sweeps on lower timeframes show the precise moment liquidity is engine-purged (stop runs). Searching for a sweep inside or at the boundary of an HTF Absorption Zone gives you a tight, defined invalidation level without cluttering your higher-timeframe view with bubbles.
Thanks — that makes sense. So I’ll use the higher‑aggregation bars to build the HTF absorption/zone structure, and then rely on sweeps and microstructure on the intraday charts for the actual triggers. Keeps the macro levels clean and lets the LTF handle the precision. Definitely sounds like an interesting challenge.
 
Hi. Does REV3 signel repaint ?
No, it will repaint while candle is forming but stable once closed. Currently hard coded in the addchartbubble for reversal score >2: " showREV and revDnScore_v2 > 2,". New V2, hopefully out this week, allows user control of the threshold.
 
Liquidity — My Personal Rabbit Hole

Liquidity and market mechanics sit somewhere between Quantum Physics, Dark Matter, and the Occult. Sometimes it feels like all three at once.

This started with one question: “Can I detect liquidity events?” Seventeen zones later, the rabbit hole opened.

From there, the project grew:

• Build a Reversal Probability Meter

• Define pattern sequences

• Map institutional trigger chains

• Merge everything into a unified operator framework

That became Liquidity Map V2 — a full microstructure visualization engine.

Posts 1 and 2 are updated with the latest code, studies, and charts. Still refining. Still exploring. Still falling deeper into the rabbit hole.

Feedback, corrections, and improvements are always appreciated. V2 is iterative by design — every insight strengthens the engine.

Note:
This is a heavy study — there’s a lot happening under the hood. I do run it live with all bubbles enabled when trading bar‑by‑bar, paired with a 15‑minute HTF on 5‑minute or lower charts. The full visualization helps expose sweeps, displacement, mitigation, and reversal pressure in real time.

Confirmed Rabbit Hole:

I asked Gemini for a Graphic of my Liquidity Map V2 Rabbit Hole
2WmKiT3.png
 
Mitigation‑Only Version

This stripped version of the Liquidity Map contains only the mitigation engine. It identifies mitigation origins, tracks active mitigation states, and plots dynamic support/resistance levels derived from those origins. All other microstructure zones, pattern logic, volume engines, and candle‑behavior systems have been removed. The study now draws only two elements:

  1. Dynamic Mitigation S/R Lines
    • Solid = unmitigated origin
    • Dashed = mitigated origin
    • Auto‑clears when aged out or terminated
  2. HTF Support/Resistance Lines
    • Cyan = HTF low anchor
    • Magenta = HTF high anchor
    • Updated only when higher‑timeframe structure shifts
No other zones, traps, shelves, absorption, FVG engines, VWAP logic, BOS/CHoCH extras, RPM scoring, or labels are included. This is a pure mitigation‑tracking tool.

5min chart/ HTF Daily

1790964436080.png


Ruby:
############################################
# MITIGATION ENGINE — Liquidity Map Extract
# Only: Origins, Terminations, SR Lines, HTF Lines
# atcsam — 10/02/26
#
# Description:
# This extract contains ONLY the mitigation system from Liquidity Map V2.

input SR_Lines = yes;
input HTF_SR = yes;
input htfTimeFrame = AggregationPeriod.quarter;

def bn = BarNumber();
def price = close;

#-----------------------------------------
# BASIC METRICS NEEDED FOR SWEEPS / DISP
#-----------------------------------------
def candleRange = high - low;
def candleBody  = AbsValue(close - open);
def prevRange   = high[1] - low[1];
def prevBody    = AbsValue(close[1] - open[1]);
def RVOL20      = if Average(volume, 20) > 0 then volume / Average(volume, 20) else 1;

#-----------------------------------------
# MINIMAL SWEEP DETECTION (Up/Dn)
#-----------------------------------------
def isUpSweep = RVOL20 > 1.5 and candleBody > prevRange * 0.65 and close > open;
def isDnSweep = RVOL20 > 1.5 and candleBody > prevRange * 0.65 and close <= open;

#-----------------------------------------
# MINIMAL FVG HQ DETECTION (Up/Dn)
#-----------------------------------------
def bullHQFVG = high[2] < low and candleBody > prevBody;
def bearHQFVG = low[2] > high and candleBody > prevBody;

#-----------------------------------------
# MINIMAL FVG FILL DETECTION (Up/Dn)
#-----------------------------------------
def bullFVGFill = bullHQFVG[1] and low <= high[3];
def bearFVGFill = bearHQFVG[1] and high >= low[3];

#-----------------------------------------
# MINIMAL BOS / CHoCH (Up/Dn)
#-----------------------------------------
input swingLength = 3;
def swingHigh1 = high[swingLength] == Highest(high, swingLength * 2 + 1);
def swingLow1  = low[swingLength]  == Lowest(low, swingLength * 2 + 1);

def swingHighLevel = if swingHigh1 then high[swingLength] else swingHighLevel[1];
def swingLowLevel  = if swingLow1  then low[swingLength]  else swingLowLevel[1];

def bosUp   = candleBody > candleBody[1] and close > swingHighLevel;
def bosDn   = candleBody > candleBody[1] and close < swingLowLevel;
def chochUp = swingLow1[1] and close > swingHighLevel;
def chochDn = swingHigh1[1] and close < swingLowLevel;

#-----------------------------------------
# MINIMAL ORDER BLOCK (Up/Dn)
#-----------------------------------------
def obBull = candleBody > prevBody and close > high[1] and close[1] < open[1];
def obBear = candleBody > prevBody and close < low[1]  and close[1] > open[1];

#-----------------------------------------
# MINIMAL PIVOT TERMINATION (Up/Dn)
#-----------------------------------------
def pivotClusterUp   = high == Highest(high, 5);
def pivotClusterDown = low  == Lowest(low, 5);

#-----------------------------------------
# MITIGATION ORIGINS (Up/Dn)
#-----------------------------------------
def mitigBeginUp =
    isUpSweep or
    bullHQFVG or
    obBull or
    bosUp or
    chochUp;

def mitigBeginDn =
    isDnSweep or
    bearHQFVG or
    obBear or
    bosDn or
    chochDn;

#-----------------------------------------
# MITIGATION TERMINATIONS (Up/Dn)
#-----------------------------------------
def mitigEndUp =
    bullFVGFill or
    pivotClusterUp;

def mitigEndDn =
    bearFVGFill or
    pivotClusterDown;

def oppositeResetUp = mitigBeginDn;
def oppositeResetDn = mitigBeginUp;

#-----------------------------------------
# ACTIVE STATE TRACKING
#-----------------------------------------
def mitigIsActiveUp = CompoundValue(1,
    if mitigBeginUp then 1
    else if mitigEndUp or oppositeResetUp then 0
    else mitigIsActiveUp[1], 0);

def mitigIsActiveDn = CompoundValue(1,
    if mitigBeginDn then 1
    else if mitigEndDn or oppositeResetDn then 0
    else mitigIsActiveDn[1], 0);

def isMitigOriginUp = mitigBeginUp and (mitigIsActiveUp[1] <= 0);
def isMitigOriginDn = mitigBeginDn and (mitigIsActiveDn[1] <= 0);

#-----------------------------------------
# ANCHOR MEMORY
#-----------------------------------------
def mitigAnchorUp = CompoundValue(1,
    if isMitigOriginUp then bn
    else if mitigIsActiveUp then mitigAnchorUp[1]
    else 0, 0);

def mitigAnchorDn = CompoundValue(1,
    if isMitigOriginDn then bn
    else if mitigIsActiveDn then mitigAnchorDn[1]
    else 0, 0);

def mitigAgeUp = if mitigAnchorUp > 0 then bn - mitigAnchorUp else 0;
def mitigAgeDn = if mitigAnchorDn > 0 then bn - mitigAnchorDn else 0;

def ageOutUp = mitigAgeUp > 50 and !mitigBeginUp;
def ageOutDn = mitigAgeDn > 50 and !mitigBeginDn;

def mitigIsClosedUp = mitigEndUp or ageOutUp;
def mitigIsClosedDn = mitigEndDn or ageOutDn;

#-----------------------------------------
# PRICE LEVEL TRACKING
#-----------------------------------------
def originUpPrice =
    if isMitigOriginUp then low
    else if mitigIsClosedUp then Double.NaN
    else originUpPrice[1];

def originDnPrice =
    if isMitigOriginDn then high
    else if mitigIsClosedDn then Double.NaN
    else originDnPrice[1];

#-----------------------------------------
# DYNAMIC SR LINES
#-----------------------------------------
def curOrigPriceUp = CompoundValue(1,
    if isMitigOriginUp then low
    else curOrigPriceUp[1],
    Double.NaN);

def hasGreenLevel = !IsNaN(curOrigPriceUp);
def isMitigatedUp = close < curOrigPriceUp;

def greenRegime = CompoundValue(1,
    if isMitigOriginUp then 1
    else if isMitigatedUp then 0
    else greenRegime[1], 1);

plot GreenSolid = if SR_Lines and hasGreenLevel and greenRegime == 1 then curOrigPriceUp else Double.NaN;
GreenSolid.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
GreenSolid.SetDefaultColor(Color.GREEN);
GreenSolid.SetLineWeight(2);

plot GreenDashed = if SR_Lines and hasGreenLevel and greenRegime == 0 then curOrigPriceUp else Double.NaN;
GreenDashed.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
GreenDashed.SetStyle(Curve.POINTS);
GreenDashed.SetDefaultColor(Color.DARK_GREEN);
GreenDashed.SetLineWeight(1);

def curOrigPriceDn = CompoundValue(1,
    if isMitigOriginDn then high
    else curOrigPriceDn[1],
    Double.NaN);

def hasRedLevel = !IsNaN(curOrigPriceDn);
def isMitigatedDn = close > curOrigPriceDn;

def redRegime = CompoundValue(1,
    if isMitigOriginDn then 1
    else if isMitigatedDn then 0
    else redRegime[1], 1);

plot RedSolid = if SR_Lines and hasRedLevel and redRegime == 1 then curOrigPriceDn else Double.NaN;
RedSolid.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
RedSolid.SetDefaultColor(Color.RED);
RedSolid.SetLineWeight(2);

plot RedDashed = if SR_Lines and hasRedLevel and redRegime == 0 then curOrigPriceDn else Double.NaN;
RedDashed.SetPaintingStrategy(PaintingStrategy.HORIZONTAL);
RedDashed.SetStyle(Curve.POINTS);
RedDashed.SetDefaultColor(Color.DARK_RED);
RedDashed.SetLineWeight(1);

#-----------------------------------------
# HTF SR LINES (with Secret Real-Time Switch)
#-----------------------------------------

input HTF_rt = no;    # Secret HTF repainter switch

def htfL_raw = low(period = htfTimeFrame);
def htfH_raw = high(period = htfTimeFrame);

# Real-time vs stable HTF values
def htfL = if HTF_rt then htfL_raw else htfL_raw[1];
def htfH = if HTF_rt then htfH_raw else htfH_raw[1];

# Stable memory (non-repainting unless HTF_rt = yes)
def htfRawUp = CompoundValue(1,
    if IsNaN(htfRawUp[1]) then htfL
    else if HTF_rt then htfL_raw
    else if htfL != htfL[1] then htfL
    else htfRawUp[1],
    htfL
);

def htfRawDn = CompoundValue(1,
    if IsNaN(htfRawDn[1]) then htfH
    else if HTF_rt then htfH_raw
    else if htfH != htfH[1] then htfH
    else htfRawDn[1],
    htfH
);

plot HTFLineUp = if HTF_SR then htfRawUp else Double.NaN;
HTFLineUp.SetPaintingStrategy(PaintingStrategy.DASHES);
HTFLineUp.SetDefaultColor(Color.CYAN);
HTFLineUp.SetLineWeight(1);

plot HTFLineDn = if HTF_SR then htfRawDn else Double.NaN;
HTFLineDn.SetPaintingStrategy(PaintingStrategy.DASHES);
HTFLineDn.SetDefaultColor(Color.MAGENTA);
HTFLineDn.SetLineWeight(1);
 
Last edited:

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
1519 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top