Liquidity Map — Microstructure Study For ThinkOrSwim

atcsam

Market Structure Expert
Plus
Liquidity Map
WB6ttCa.png


(Updated: V1.1)

Liquidity Map — Microstructure Study (WIP)

Ventured deep into the liquidity‑programming rabbit hole and built a microstructure engine to visualize how liquidity actually behaves on the chart. It maps 15 structural zones, RPM confluence, directional REV↑/REV↓ probability, volume pressure, and candle‑behavior footprints.

This is a new arena for me, so if you spot anything in the code that can be improved, tightened, or optimized — I’m all ears.

Surprisingly, it plays extremely well with the Climax Trap study and YTC lines.

Testing Chart with All studies
Liquidity Test Chart



Method to the Madness…..

🔧 Liquidity Map — Zone & Math Breakdown

ZONE 1 — Dark Pool Churn


High volume, low movement → hidden accumulation/distribution. Math: RVOL20 > 1.8 and body < 35% of range.

ZONES 2–3 — Sweeps (Up/Down)

Aggressive runs through highs/lows. Math: RVOL20 > 1.5, body > ATR20 * 0.65, directional close.

ZONES 4–5 — Stop‑Grabs (Up/Down)

Wick‑heavy stop runs that fail to continue. Math: Wick > 45% of range, RVOL20 > 1.5, close back inside mid‑range.

ZONE 6 — Hidden Absorption

Institutions absorbing flow without moving price. Math: RVOL20 ≥ 2.0, body ≤ ATR20 * 0.30, wick sum ≤ 50% of body.

ZONES 7–8 — Voids & Refills

True displacement beyond prior high/low → imbalance → correction. Void Math: Break prior high/low, body > ATR20 * 0.6, wick < 15%, RVOL20 > 1.5. Refill Math: Void[1] and price returns to prior boundary.

ZONE 9 — Volume Climax

Exhaustive push with extreme volume. Math: RVOL20 > 3.0, body > ATR20 * 0.8, wick sum < 30%.

ZONE 10 — Trend Exhaustion

Strong effort → weak result. Math: RVOL20 > 1.5, body > ATR20 * 0.5, opposite wick > 40%.

ZONE 11 — Liquidity Shelves (High/Medium/Institutional)

Multi‑bar liquidity storage zones. Math: Highest/lowest of last 5 bars + RVOL/range/wick filters depending on mode.

ZONE 12 — Pivot Cluster

Compression of highs/lows + range + volume. Math: Pivot band compression + range compression + volume compression + structural confluence.

ZONE 13 — Institutional Reversal Signature

Multi‑zone reversal confluence. Math: Exhaustion + refill, or climax + shelf, or stop‑grab + exhaustion.

ZONE 14 — Fair Value Gap (FVG)

Void without refill. Math: isVoid AND NOT isVoidRefill.

ZONE 15 — HFT VWAP Trap

VWAP deviation + structural trap. Math: High/low outside VWAP bands + stop‑grab/climax/exhaustion confluence.

The Liquidity Map started as a way to sanity‑check the math and ended up becoming a full microstructure engine. Every zone, footprint, and probability score is built from observable behavior — no indicators, no predictions, no shortcuts. Just structure, volume, displacement, exhaustion, and the footprints left behind when liquidity is taken, defended, or trapped.

If you dig into the code, focus on the zone logic. That’s the heart of the study. The visuals, RPM, REV↑/REV↓, volume pressure, and candle‑behavior engine all exist to make that math readable on the chart.

Still a work in progress, and still learning — so any technical feedback, corrections, or improvements are appreciated. The goal is simple: map liquidity honestly and let the market tell its own story.

Study Link
Study Liquidity_Map

Ruby:
############################################
# LIQUIDITY MAP
# Version: 1.1 (Work In Progress)
# atcsam — 09/07/26
#
# Description:
# A microstructure model that maps liquidity behavior
# across 15 structural zones, directional reversal probability (REV↑/REV↓),
# RPM confluence scoring, volume pressure, and candle‑behavior micro‑footprints.
# 1.1 — RTH‑aware, NaN‑safe zone engine with enhanced 5‑bar RPM (meter)_v2.

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

#-----------------------------------------
# USER TOGGLES
#-----------------------------------------
input useRTH = no;
input shelfMode = {default "High", "Medium", "Institutional"};
input showBubbles        = yes;   # Zone Bubbles
input showRPM            = yes;   # RPM confluence meter
input showREV            = yes;   # Directional REV↑ / REV↓ scoring + bubbles
input showrevlb          = yes;
input showVolumeEngine   = yes;   # Pulse, Spike, RVOL dots

input showFlowLabel      = yes;   # Flow label at top

input showCandleBubbles  = yes;   # Candle behavior bubbles
input showCandleLabels   = no;    # Candle behavior label


# 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);


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

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

# --- RTH-Filtered Volume ---
def rawVol = volume;
def vol    = if useRTH and !isRTH then 0 else rawVol;


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

# --- Volume Math ---
def avgVol20 = Average(vol, 20);
def RVOL20   = if avgVol20 != 0 then vol / avgVol20 else 1;

# --- 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–5 — 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 6 — HIDDEN ABSORPTION
# ====================================================
def _isHiddenAbsorb =
    RVOL20 >= 2.0 and
    candleBody <= atr20 * 0.30 and
    (upperWick + lowerWick) <= candleBody * 0.50;
def isHiddenAbsorb = if IsNaN(_isHiddenAbsorb) then 0 else _isHiddenAbsorb;

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

# --- VOID ---
def _voidUp =
    close > prevHigh and
    candleBody > atr20 * 0.6 and
    upperWick < candleRange * 0.15 and
    RVOL20 > 1.5;
def voidUp = if IsNaN(_voidUp) then 0 else _voidUp;

def _voidDown =
    close < prevLow and
    candleBody > atr20 * 0.6 and
    lowerWick < candleRange * 0.15 and
    RVOL20 > 1.5;
def voidDown = if IsNaN(_voidDown) then 0 else _voidDown;

def isVoid = voidUp or voidDown;

# --- 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;

# ====================================================
# ZONE 9 — 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 10 — 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 11 — 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 12 — 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;

# ====================================================
# ZONE 13 — INSTITUTIONAL REVERSAL SIGNATURE
# ====================================================
def _isInstReversal =
    (isTrendExhaustion and isVoidRefill) or
    (isVolClimax and isShelf) or
    (isUpStopGrab and isTrendExhaustion) or
    (isDnStopGrab and isTrendExhaustion);
def isInstReversal = if IsNaN(_isInstReversal) then 0 else _isInstReversal;

# ====================================================
# ZONE 14 — FAIR VALUE GAP (FVG)
# ====================================================
def _isFVG = isVoid and !isVoidRefill;
def isFVG = if IsNaN(_isFVG) then 0 else _isFVG;

# ====================================================
# ZONE 15 — 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;

# ====================================================
# FLOW LABEL
# ====================================================
AddLabel(showFlowLabel,
    "Flow: " +
    (if isHftVwapTrap then "⚠️ HFT VWAP TRAP"
     else if isInstReversal then "INST REVERSAL"
     else if pivotCluster then "PIVOT CLUSTER"
     else if isChurn then "DP CHURN"
     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 isVoid then "VOID"
     else if isVoidRefill then "REFILL"
     else if isVolClimax then "CLIMAX"
     else if isTrendExhaustion then "EXHAUSTION"
     else if isShelf then "SHELF"
     else if isFVG then "FVG"
     else "Standard Flow"),
    if isHftVwapTrap then Color.MAGENTA else Color.WHITE
);

# ====================================================
# CHART BUBBLES FOR ALL 15 ZONES
# ====================================================


AddChartBubble(showBubbles and isChurn, low, "DP CHURN", GlobalColor("MagLt"), no);
AddChartBubble(showBubbles and isUpStopGrab, high, "LIQ ↑", Color.YELLOW, yes);
AddChartBubble(showBubbles and isDnStopGrab, low, "LIQ ↓", Color.LIME, no);
AddChartBubble(showBubbles and isUpSweep, high, "SWEEP ↑", Color.CYAN, yes);
AddChartBubble(showBubbles and isDnSweep, low, "SWEEP ↓", Color.LIGHT_RED, no);
AddChartBubble(showBubbles and isHiddenAbsorb, low, "ABSORB", Color.CYAN, no);
AddChartBubble(showBubbles and isVoid, high, "VOID", Color.DARK_GREEN, yes);
AddChartBubble(showBubbles and isVoidRefill, low, "REFILL", Color.BLUE, no);
AddChartBubble(showBubbles and isVolClimax, high, "CLIMAX", GlobalColor("Amber"), yes);
AddChartBubble(showBubbles and isTrendExhaustion,
    if close > open then high else low,
    "EXHAUST",
    Color.RED,
    close > open
);
AddChartBubble(showBubbles and isShelf,
    if shelfHigh_H or shelfHigh_M or shelfHigh_I then high else low,
    "SHELF",
    Color.WHITE,
    shelfHigh_H or shelfHigh_M or shelfHigh_I
);
AddChartBubble(showBubbles and pivotCluster, close, "PIVOT CLUSTER", Color.ORANGE, yes);
AddChartBubble(showBubbles and isInstReversal, close, "INST REV", GlobalColor("Trap"), yes);
AddChartBubble(showBubbles and isFVG,
    if voidUp then high else low,
    "FVG",
    Color.DARK_GREEN,
    voidUp
);
AddChartBubble(showBubbles and isHftVwapTrap,
    if close > vwapValue then high else low,
    "⚠️ HFT TRAP",
    Color.MAGENTA,
    close > vwapValue
);


# ====================================================
# RPM v2 — Multi‑Bar, Pattern‑Aware Reversal Engine
# ====================================================

# 1) Multi‑bar memory (5‑bar window)
def _recentSweep    = Highest(isUpSweep or isDnSweep, 5);
def recentSweep     = if IsNaN(_recentSweep) then 0 else _recentSweep;

def _recentExhaust  = Highest(isTrendExhaustion, 5);
def recentExhaust   = if IsNaN(_recentExhaust) then 0 else _recentExhaust;

def _recentClimax   = Highest(isVolClimax, 5);
def recentClimax    = if IsNaN(_recentClimax) then 0 else _recentClimax;

def _recentTrap     = Highest(isHftVwapTrap, 5);
def recentTrap      = if IsNaN(_recentTrap) then 0 else _recentTrap;

def _recentShelf    = Highest(isShelf, 5);
def recentShelf     = if IsNaN(_recentShelf) then 0 else _recentShelf;

def _recentVoid     = Highest(isVoid, 5);
def recentVoid      = if IsNaN(_recentVoid) then 0 else _recentVoid;

def _recentRefill   = Highest(isVoidRefill, 5);
def recentRefill    = if IsNaN(_recentRefill) then 0 else _recentRefill;

def _recentStopGrab = Highest(isUpStopGrab or isDnStopGrab, 5);
def recentStopGrab  = if IsNaN(_recentStopGrab) then 0 else _recentStopGrab;

def _recentChurn    = Highest(isChurn, 5);
def recentChurn     = if IsNaN(_recentChurn) then 0 else _recentChurn;

# 2) Weighted RPM components (current vs recent)

def rpmSweep_v2   = (isUpSweep or isDnSweep) * 2 + recentSweep * 1;
def rpmExhaust_v2 = isTrendExhaustion * 2 + recentExhaust * 1;
def rpmClimax_v2  = isVolClimax * 3 + recentClimax * 1;
def rpmTrap_v2    = isHftVwapTrap * 3 + recentTrap * 1;
def rpmShelf_v2   = isShelf * 1 + recentShelf * 1;
def rpmVoid_v2    = isVoid * 2 + recentVoid * 1;
def rpmRefill_v2  = isVoidRefill * 2 + recentRefill * 1;
def rpmChurn_v2   = isChurn * 1 + recentChurn * 1;
def rpmStop_v2    = (isUpStopGrab or isDnStopGrab) * 3 + recentStopGrab * 1;

# 3) Swing‑within‑5‑bars
def recentSwingHigh = high == Highest(high, 5);
def recentSwingLow  = low  == Lowest(low, 5);

def swingBonus_v2 =
    (recentSwingHigh and recentExhaust) * 3 +
    (recentSwingLow  and recentExhaust) * 3 +
    (recentSwingHigh and recentTrap)    * 4 +
    (recentSwingLow  and recentTrap)    * 4 +
    (recentSwingHigh and voidDown)      * 2 +
    (recentSwingLow  and voidUp)        * 2;

# 4) Sequence bonuses (pattern detection)
def seq_Reversal_v2     = (recentSweep and recentShelf and recentExhaust);
def seq_Trap_v2         = (recentVoid and recentRefill and recentTrap);
def seq_SCBC_v2         = (recentClimax and recentExhaust and recentShelf);
def seq_Prop_v2         = (recentShelf and recentSweep and recentChurn);
def seq_VoidExhaust_v2  = (recentVoid and recentRefill and recentExhaust);
def seq_SweepExhaust_v2 = (recentSweep and recentExhaust);

def seqBonus_v2 =
    (seq_Reversal_v2     * 5) +
    (seq_Trap_v2         * 4) +
    (seq_SCBC_v2         * 6) +
    (seq_Prop_v2         * 3) +
    (seq_VoidExhaust_v2  * 4) +
    (seq_SweepExhaust_v2 * 3);


# 5) Enhanced RPM score
def rpmScore_v2 =
    rpmSweep_v2 +
    rpmExhaust_v2 +
    rpmClimax_v2 +
    rpmTrap_v2 +
    rpmShelf_v2 +
    rpmVoid_v2 +
    rpmRefill_v2 +
    rpmChurn_v2 +
    rpmStop_v2 +
    swingBonus_v2 +
    seqBonus_v2;

def rpmPercent_v2 = Min(100, (rpmScore_v2 / 18) * 100);

# 6) RPM v2 label

AddLabel(showRPM,
    "RPMv2: " + AsPercent(rpmPercent_v2 / 100) + " Raw:" + rpmScore_v2,
    if rpmPercent_v2 >= 85 then GlobalColor("RPM_DownStrong")
    else if rpmPercent_v2 >= 65 then GlobalColor("RPM_Down")
    else if rpmPercent_v2 >= 45 then GlobalColor("RPM_Neutral")
    else if rpmPercent_v2 >= 25 then GlobalColor("RPM_Up")
    else GlobalColor("RPM_UpStrong")
);

# ====================================================
# --- DIRECTIONAL REVERSAL PROBABILITY METER (REV↑ / REV↓)
# ====================================================

def revUpScore =
    (isDnStopGrab * 3) +
    (isHiddenAbsorb * 2) +
    (refillUp * 3) +
    (resultWeakDown * 3) +
    ((isVolClimax and close < open) * 3) +
    ((isInstReversal and close < open) * 3) +
    ((shelfLow_H or shelfLow_M or shelfLow_I) * 1) +
    ((pivotCluster and close < open) * 2) +
    ((voidDown and !refillDown) * 1);

def revDnScore =
    (isUpStopGrab * 3) +
    (isHiddenAbsorb * 2) +
    (refillDown * 3) +
    (resultWeakUp * 3) +
    ((isVolClimax and close > open) * 3) +
    ((isInstReversal and close > open) * 3) +
    ((shelfHigh_H or shelfHigh_M or shelfHigh_I) * 1) +
    ((pivotCluster and close > open) * 2) +
    ((voidUp and !refillUp) * 1);

def revUpLow      = revUpScore <= 4;
def revUpModerate = revUpScore > 4 and revUpScore <= 9;
def revUpHigh     = revUpScore > 9 and revUpScore <= 14;
def revUpExtreme  = revUpScore > 14;

def revDnLow      = revDnScore <= 4;
def revDnModerate = revDnScore > 4 and revDnScore <= 9;
def revDnHigh     = revDnScore > 9 and revDnScore <= 14;
def revDnExtreme  = revDnScore > 14;

# ====================================================
# REVv2 — Directional Boosters
# ====================================================

def revUpBoost =
    (rpmScore_v2 >= 4 and recentSwingLow) +
    (rpmScore_v2 >= 6 and recentRefill) +
    (rpmScore_v2 >= 8 and recentTrap) +
    (rpmScore_v2 >= 10 and recentExhaust);

def revDnBoost =
    (rpmScore_v2 >= 4 and recentSwingHigh) +
    (rpmScore_v2 >= 6 and recentVoid) +
    (rpmScore_v2 >= 8 and recentTrap) +
    (rpmScore_v2 >= 10 and recentExhaust);

def revUpScore_v2 = revUpScore + revUpBoost;
def revDnScore_v2 = revDnScore + revDnBoost;


AddChartBubble(
    showREV and revUpScore_v2 > 2,
    low,
    "REV↑ " + revUpScore_v2,
    if revUpScore_v2 > 14 then Color.GREEN
    else if revUpScore_v2 > 9 then Color.DARK_GREEN
    else if revUpScore_v2 > 4 then Color.YELLOW
    else Color.GRAY,
    no
);

AddChartBubble(
    showREV and revDnScore_v2 > 2,
    high,
    "REV↓ " + revDnScore_v2,
    if revDnScore_v2 > 14 then Color.RED
    else if revDnScore_v2 > 9 then Color.DARK_RED
    else if revDnScore_v2 > 4 then Color.ORANGE
    else Color.GRAY,
    yes
);

AddLabel(showREV,
    "REV↑ " + revUpScore_v2,
    if revUpScore_v2 > 14 then Color.GREEN
    else if revUpScore_v2 > 9 then Color.DARK_GREEN
    else if revUpScore_v2 > 4 then Color.YELLOW
    else Color.GRAY
);

AddLabel(showREV,
    "REV↓ " + revDnScore_v2,
    if revDnScore_v2 > 14 then Color.RED
    else if revDnScore_v2 > 9 then Color.DARK_RED
    else if revDnScore_v2 > 4 then Color.ORANGE
    else Color.GRAY
);

#-----------------------------------------
# Volume Engine V13
#-----------------------------------------

input ShowVlabel          = no;
input ShowVolumeMarkers  = yes;

input type        = { default SMP, EXP };
input length1     = 20;
input RelVLenght  = 50;

#-----------------------------------------
# 1) CORE VOLUME SERIES
#-----------------------------------------

def v13p_vol = volume;

# 10-bar average volume (for Pulse + Spike)
def v13p_volAvg10 = Average(v13p_vol, 10);

#-----------------------------------------
# 2) PULSE ENGINE (MACRO CONTEXT)
#-----------------------------------------

# 3-bar pulse (stable, minimal noise)
def v13p_pulse3 = (v13p_vol + v13p_vol[1] + v13p_vol[2]) / 3;

# Pulse ratio vs 10-bar average
def v13p_pulseRatio =
    if v13p_volAvg10 != 0 then v13p_pulse3 / v13p_volAvg10
    else 1;

# Pulse classification
def v13p_pulseStrong  = v13p_pulseRatio > 1.25;
def v13p_pulseNeutral = v13p_pulseRatio >= 0.75 and v13p_pulseRatio <= 1.25;
def v13p_pulseWeak    = v13p_pulseRatio < 0.75;

# Spike detection (expansion event)
def v13p_spike =
    v13p_vol > Highest(v13p_vol[1], 3) and
    v13p_vol > v13p_volAvg10 * 1.5;

#-----------------------------------------
# 3) RVOL DOT ENGINE (MICRO ACCELERATION)
#-----------------------------------------

def v13m_RelPrevVol = v13p_vol / v13p_vol[1];
def v13m_longAvg = Average(v13p_vol, RelVLenght);
def v13m_dotSignal = v13m_RelPrevVol >= 1.25;
def v13m_dotHigh = v13p_vol >= v13m_longAvg;
def v13m_dotLow  = v13p_vol <  v13m_longAvg;
def v13m_midBody = MidBodyVal();

#-----------------------------------------
# 4) DOT PLOT (MID-BODY
#-----------------------------------------

plot v13m_Dot =
    if ShowVolumeMarkers
       and v13m_dotSignal
       and !v13p_pulseWeak
    then v13m_midBody
    else Double.NaN;

v13m_Dot.SetPaintingStrategy(PaintingStrategy.POINTS);
v13m_Dot.SetLineWeight(1);
v13m_Dot.AssignValueColor(
    if v13m_dotHigh then Color.GREEN
    else Color.YELLOW
);

#-----------------------------------------
# 5) SPIKE TRIANGLE (MID-BODY, WEIGHT 2)
#-----------------------------------------

plot v13m_SpikePlot =
    if ShowVolumeMarkers and v13p_spike then v13m_midBody
    else Double.NaN;

v13m_SpikePlot.SetPaintingStrategy(PaintingStrategy.TRIANGLES);
v13m_SpikePlot.SetLineWeight(2);
v13m_SpikePlot.AssignValueColor(Color.CYAN);

#-----------------------------------------
# 6) VOLUME LABEL (MACRO NARRATIVE)
#-----------------------------------------

AddLabel(
   ShowVlabel,
    if v13p_spike then "Vol Spike"
    else if v13p_pulseStrong then "Vol ↑ Strong"
    else if v13p_pulseWeak then "Vol ↓ Weak"
    else "Vol Neutral",

    if v13p_spike then Color.CYAN
    else if v13p_pulseStrong then Color.GREEN
    else if v13p_pulseWeak then Color.RED
    else Color.LIGHT_GRAY
);

#-----------------------------------------
# Candle Behavior Engine (Micro Footprints)
#-----------------------------------------


# Core candle components
def body      = AbsValue(close - open);
def range     = high - low;
def prevBody  = AbsValue(close[1] - open[1]);
def prevRange = high[1] - low[1];

def bodyPctOfRange =
    if range != 0 then body / range else 0;

#def upperWick =
#    if close >= open then high - close else high - open;

#def lowerWick =
#    if close >= open then open - low else close - low;

def upperWickPct =
    if range != 0 then upperWick / range else 0;

def lowerWickPct =
    if range != 0 then lowerWick / range else 0;

#-----------------------------------------
# 1) DISPLACEMENT BARS (Engulfing)
#-----------------------------------------

def isDispUp =
    body > prevBody and
    range > prevRange and
    close > high[1];

def isDispDn =
    body > prevBody and
    range > prevRange and
    close < low[1];
#-----------------------------------------
# TR1 DIRECTION (2‑bar trend)
#-----------------------------------------

def tr1Up = close > close[1]; # and close[1] > close[2]
def tr1Dn = close < close[1] ; #and close[1] < close[2]

def tr1 =
    if tr1Up then 1
    else if tr1Dn then -1
    else 0;

#-----------------------------------------
# 2) REJECTION BARS (Liquidity Refusal)
#-----------------------------------------

def isRejectUp =
    lowerWickPct > 0.4 and
    close > open and
    close < high[1];   # closes inside / below prior high

def isRejectDn =
    upperWickPct > 0.4 and
    close < open and
    close > low[1];    # closes inside / above prior low

#-----------------------------------------
# 3) ABSORPTION BARS (Defense at Structure)
#-----------------------------------------

# Small body, decent range, volume present
def isAbsorb =
    bodyPctOfRange < 0.25 and
    range > prevRange * 0.75 and
    v13p_vol > v13p_volAvg10 and
    (isShelf or isVoid or isHiddenAbsorb or isChurn);

#-----------------------------------------
# 4) COMPRESSION (Coiling / Pressure Buildup)
#-----------------------------------------

def isCompression =
    range < prevRange * 0.7 and
    range < Average(range, 5) * 0.8 and
    !isVoid and !isUpSweep and !isDnSweep;

#-----------------------------------------
# Candle Footprints
#-----------------------------------------

#-----------------------------------------
# DISPLACEMENT ARROWS + Tr
#-----------------------------------------

plot cb_DispUp =
    if showCandleBubbles 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 showCandleBubbles 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 showCandleBubbles and isRejectUp then low else Double.NaN;
cb_RejectUp.SetPaintingStrategy(PaintingStrategy.POINTS);
cb_RejectUp.SetLineWeight(2);
cb_RejectUp.AssignValueColor(Color.CYAN);

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

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

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

#-----------------------------------------
# (Narrative)
#-----------------------------------------

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.DARK_GRAY
);

My latest snafu, GNRC walk down: D,30min/15min/5minEOD
Daily
16XHq5V.png

30min
heP1AZp.png

15min
jmvqvdO.png


5min
Q7JCfbh.png
 
Last edited:
V1.1 — RTH‑aware, NaN‑safe zone engine with enhanced 5‑bar pattern sequence RPM(meter)_v2. WIP: Live‑testing surfaced minor issues; tuning/ enhancements in progress. Interesting Bar by Bar observations.

Code, study and chart links updated in 1st post.

2YOa6RR.png
 
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:

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