Overview
Retail liquidity does not sit randomly on the chart — it clusters. Stop-losses stack just beyond recognizable swing highs and lows. Breakout orders queue above session highs and below session lows, waiting for a level to break. That clustering is precisely what makes those levels attractive to market makers and institutional desks that need to execute size: engineering price just beyond a resting pool of orders — a "stop-hunt" or liquidity sweep — generates the fills and the volume required to move price in the opposite direction with minimal slippage.
The Liquidity Grab Engine isolates this behavior programmatically. It tracks confirmed swing structure and user-defined session ranges, flags the moment price wicks through one of these levels, and then checks whether that raid is followed by a close back on the original side — the distinction between a genuine liquidity grab and a confirmed breakout.
Features of This Indicator
-
Structural Stop-Hunt Detection Flags swing failure patterns: price wicks beyond a confirmed swing high or low, then closes back on the original side, marking a failed breakout attempt in real time.
-
Session-Based Liquidity Pool Mapping Automatically tracks the high and low of a user-defined session window (Asia, London, or a fully custom range) and keeps monitoring that level for a sweep long after the session itself has closed.
-
Real-Time, Non-Repainting Confirmation Every grab is confirmed strictly on bar close, so historical markers never repaint, shift, or redraw retroactively as new price data arrives.
-
Lightweight Visual Marking Uses efficient line and label objects rather than heavier drawing primitives, keeping the chart responsive even with grabs tracked across an extended lookback.
How to Use
- Copy the script into TradingView's Pine Editor and click Add to Chart.
- Set the Liquidity Session input to match the window you want mapped — Asia, London, New York, or a fully custom range in exchange time.
- Watch for a "Liquidity Grab" label forming at a prior swing high/low or a mapped session high/low.
- Wait for the close back inside the raided level before treating it as a potential reversal trigger — the wick alone is not confirmation.
The Pine Script V5 Code
//@version=5
indicator("Liquidity Grab Engine — Crypto Indicator Pro", shorttitle="Liq Grab [CIP]", overlay=true, max_lines_count=500, max_labels_count=500)
// ============================================================================
// LIQUIDITY GRAB ENGINE
// Detects structural stop-hunts (swing failure patterns) and session-based
// liquidity pool sweeps: instances where price raids a resting liquidity
// level -- a prior swing high/low or a session high/low -- before reversing
// and closing back on the opposite side. Confirms strictly on bar close.
// ============================================================================
// ---------------------------- INPUTS ----------------------------
swingLength = input.int(10, "Swing Detection Length", minval=2, group="Market Structure",
tooltip="Bars used on each side to confirm a valid swing high/low.")
sessionString = input.session("0000-0800", "Liquidity Session (Exchange Time)", group="Session Liquidity")
showSession = input.bool(true, "Map Session High/Low Liquidity Pools", group="Session Liquidity")
bullColor = color.new(color.teal, 0)
bearColor = color.new(color.red, 0)
// ---------------------------- SWING STRUCTURE ----------------------------
swingHigh = ta.pivothigh(high, swingLength, swingLength)
swingLow = ta.pivotlow(low, swingLength, swingLength)
var float lastSwingHigh = na
var float lastSwingLow = na
var bool swingHighTaken = false // tracks whether the current swing high has already been swept
var bool swingLowTaken = false
if not na(swingHigh)
lastSwingHigh := swingHigh
swingHighTaken := false
if not na(swingLow)
lastSwingLow := swingLow
swingLowTaken := false
// ---------------------------- STRUCTURAL LIQUIDITY GRAB (SWING FAILURE) ----------------------------
// Bearish grab: price wicks above the last swing high (liquidity raid) but closes back below it.
bearishGrab = not na(lastSwingHigh) and not swingHighTaken and high > lastSwingHigh and close < lastSwingHigh
// Bullish grab: price wicks below the last swing low (liquidity raid) but closes back above it.
bullishGrab = not na(lastSwingLow) and not swingLowTaken and low < lastSwingLow and close > lastSwingLow
if bearishGrab
swingHighTaken := true
line.new(bar_index[1], lastSwingHigh, bar_index, lastSwingHigh, color=bearColor, width=1, style=line.style_dashed)
label.new(bar_index, high, "Liquidity Grab\n(Sell-Side Raid)", style=label.style_label_down, color=bearColor, textcolor=color.white, size=size.small)
if bullishGrab
swingLowTaken := true
line.new(bar_index[1], lastSwingLow, bar_index, lastSwingLow, color=bullColor, width=1, style=line.style_dashed)
label.new(bar_index, low, "Liquidity Grab\n(Buy-Side Raid)", style=label.style_label_up, color=bullColor, textcolor=color.white, size=size.small)
// ---------------------------- SESSION LIQUIDITY POOL MAPPING ----------------------------
inSession = not na(time(timeframe.period, sessionString))
var float sessionHigh = na
var float sessionLow = na
var bool sessHighTaken = false
var bool sessLowTaken = false
if inSession and not inSession[1]
sessionHigh := high
sessionLow := low
sessHighTaken := false
sessLowTaken := false
else if inSession
sessionHigh := math.max(sessionHigh, high)
sessionLow := math.min(sessionLow, low)
// Draw the completed session range once the session closes
if showSession and not inSession and inSession[1] and not na(sessionHigh)
line.new(bar_index - 1, sessionHigh, bar_index + 20, sessionHigh, color=color.new(color.orange, 30), width=1, extend=extend.right)
line.new(bar_index - 1, sessionLow, bar_index + 20, sessionLow, color=color.new(color.orange, 30), width=1, extend=extend.right)
// Session liquidity sweeps are checked continuously, including outside the session window
if showSession and not na(sessionHigh) and not sessHighTaken and high > sessionHigh and close < sessionHigh
sessHighTaken := true
label.new(bar_index, high, "Session Liquidity Grab", style=label.style_label_down, color=color.orange, textcolor=color.white, size=size.small)
if showSession and not na(sessionLow) and not sessLowTaken and low < sessionLow and close > sessionLow
sessLowTaken := true
label.new(bar_index, low, "Session Liquidity Grab", style=label.style_label_up, color=color.orange, textcolor=color.white, size=size.small)
// ---------------------------- ALERTS ----------------------------
alertcondition(bearishGrab, title="Bearish Liquidity Grab", message="Sell-side liquidity swept — potential bearish reversal.")
alertcondition(bullishGrab, title="Bullish Liquidity Grab", message="Buy-side liquidity swept — potential bullish reversal.")