Iván González

Liquidity Sweep & Absorption Reversal Engine

Category: Indicators By: Iván González Created: October 30, 2026, 11:01 AM
October 30, 2026, 11:01 AM
Indicators
0 Comments
Liquidity Sweep & Absorption Reversal Engine

Introduction

 

Above every obvious swing high sit buy stops, and below every swing low sit sell stops. Price often runs those pools of liquidity and then turns: the classic stop-run or liquidity sweep. The problem is that many sweeps are not reversals at all, just the start of a breakout.

The Liquidity Sweep & Absorption Reversal Engine by PhenLabs is built to separate the two. It tracks the pools of resting liquidity at recent swing highs and lows, detects the bar that sweeps one of them, and only flags a reversal when that bar also shows absorption: heavy volume traded with little price progress, the footprint of large players taking the other side. Each confirmed signal comes with a projected target.

Theory Behind the Indicator

 

The engine works in four steps:

  • Liquidity pools: every confirmed pivot high (8 bars on each side by default) becomes buy-side liquidity (BSL), and every pivot low becomes sell-side liquidity (SSL). Each side keeps the 6 most recent pools; when a new one arrives the oldest leaves.
  • Sweep: on each bar the engine takes the nearest BSL above the bar’s low and the nearest SSL below its high. A sweep is a wick beyond that level. Once swept, the pool is removed so it cannot fire again.
  • Trigger: in the default “Close Back Inside” mode, the sweep triggers when the bar closes back on the other side of the level (a rejection). In “Displacement Break” mode it triggers when the bar closes beyond the level by more than 1.2 ATR.
  • Absorption: volume at least 1.6 times its 20-bar average with a body no larger than 45% of the bar’s range. With the absorption filter on, only triggers on absorption bars become signals.

A bullish signal comes from a swept SSL, a bearish one from a swept BSL. The risk is the larger of the ATR and the wick beyond the level, and the target is the nearest pool on the opposite side or, if there is none or the R-multiple mode is chosen, the close plus or minus the risk times the R-multiple.

Key Features at a Glance

 

  • Automatic map of the most recent buy-side and sell-side liquidity pools.
  • Two sweep triggers: rejection back inside the level or displacement break.
  • Volume absorption filter that can be switched off.
  • “Bull Sweep” and “Bear Sweep” signals with triangles.
  • Projected draw from the swept level to the target, with the target price.
  • Dashboard with the nearest BSL and SSL, volume spike, absorption and the active mode.

How to Read the Indicator

 

  • Dashed teal lines: active buy-side liquidity pools (swing highs not yet swept).
  • Dashed red lines: active sell-side liquidity pools (swing lows not yet swept).
  • Red triangle with “Bear Sweep”: a buy-side pool was swept and rejected on an absorption bar, a potential top.
  • Teal triangle with “Bull Sweep”: a sell-side pool was swept and rejected on an absorption bar, a potential bottom.
  • Dotted line and price: the projected draw from the swept level to the target.
  • Dashboard: the levels currently in play and whether the last bar shows a volume spike and absorption.

Practical Applications

 

  • Reversal entries: enter after a confirmed sweep with the stop beyond the sweep’s extreme and the projected target as the first objective.
  • Filtering stop-runs: compare the signals with and without the absorption filter to see how many sweeps the volume condition removes.
  • Liquidity map: the pool lines alone show where the stops are likely resting, useful for placing targets and for not placing stops in the obvious places.
  • Breakout mode: switch to “Displacement Break” to study sweeps that turn into strong breakouts instead of reversals.

Indicator Configuration

 

  • pivotLen (default 8): bars on each side of a pivot. Higher values give fewer, more significant pools.
  • maxPools (default 6, 2 to 20): active pools kept on each side.
  • sweepMode (default 0): 0 = close back inside the level, 1 = displacement break.
  • dispMult (default 1.2): displacement in ATR, only used in displacement mode.
  • useAbs (default 1): require absorption to confirm a sweep.
  • volMult (default 1.6): volume spike threshold, as a multiple of the average volume.
  • bodyThresh (default 0.45): maximum body / range of an absorption bar. Lower is stricter.
  • volLen (default 20): length of the volume average.
  • rMult (default 2.0): R-multiple for the target.
  • showProj (default 1): show the projected draw.
  • projStyle (default 0): target type, 0 = nearest opposing pool, 1 = R-multiple.
  • showDash (default 1): show the dashboard.

On instruments without volume data the absorption filter never confirms: switch it off (useAbs = 0) to work with the sweeps alone.

The Code

 

//---------------------------------------------------------------
// PRC_LSA Reversal Engine
// version = 0
// 28.10.2026
// Iván González @ www.prorealcode.com
// Author: PhenLabs
// Sharing ProRealTime knowledge
//--------------------------------------------------------------------//
DEFPARAM DRAWONLASTBARONLY = true

// ===================== STRUCTURE & LIQUIDITY =====================
pivotLen = 8          // pivot lookback (bars on each side, min 2)
maxPools = 6          // max active pools per side (2 to 20)
sweepMode = 0         // sweep trigger: 0 = close back inside, 1 = displacement break
dispMult = 1.2        // displacement (x ATR), only used when sweepMode = 1

// ===================== ABSORPTION FILTER =====================
useAbs = 1            // 1 = require absorption confirmation
volMult = 1.6         // volume spike (x average volume)
bodyThresh = 0.45     // max body / range (lower = stricter absorption)
volLen = 20           // volume SMA length

// ===================== TARGETS & PROJECTION =====================
rMult = 2.0           // target R-multiple
showProj = 1          // 1 = show the projected draw
projStyle = 0         // target type: 0 = opposing pool, 1 = R-multiple

// ===================== VISUALS =====================
showDash = 1          // 1 = show the dashboard
// bullish signal / BSL line: 38,166,154 - bearish signal / SSL line: 239,83,80

prd = max(2, round(pivotLen))
mxP = min(20, max(2, round(maxPools)))
stride = 21

// ===================== CORE CALCULATIONS =====================
atrOk = 0
IF barindex >= 13 THEN
   atrOk = 1
   atrV = averagetruerange[14](close)
ENDIF
volAvg = average[volLen](volume)
volSpike = 0
IF barindex >= volLen - 1 AND volAvg > 0 AND volume >= volAvg * volMult THEN
   volSpike = 1
ENDIF
rng = high - low
IF rng > 0 THEN
   bodyR = abs(close - open) / rng
ELSE
   bodyR = 0
ENDIF
absorbed = 0
IF volSpike = 1 AND bodyR <= bodyThresh THEN
   absorbed = 1
ENDIF

// ===================== PIVOTS =====================
// confirmed prd bars later: strictly above the prd bars after it,
// at least as high as the prd bars before it
hhA = highest[prd](high)
llA = lowest[prd](low)
phOk = 0
plOk = 0
IF barindex >= 2 * prd THEN
   IF high[prd] > hhA AND high[prd] >= hhA[prd + 1] THEN
      phOk = 1
      phV = high[prd]
   ENDIF
   IF low[prd] < llA AND low[prd] <= llA[prd + 1] THEN
      plOk = 1
      plV = low[prd]
   ENDIF
ENDIF

// ===================== LIQUIDITY POOL STORE =====================
// Two buffers that alternate bar by bar: this bar starts from a copy of the
// pools as they were when the previous bar closed. Arrays are NOT rewound
// between ticks the way scalars are, so every tick of the live bar rebuilds
// its own copy and the confirmed pools are never touched twice.
pb = barindex MOD 2
qb = 1 - pb
ob = pb * stride
oq = qb * stride
IF barindex = 0 THEN
   nB = 0
   nS = 0
ENDIF
IF nB > 0 THEN
   FOR i = 0 TO nB - 1 DO
      $bL[ob + i] = $bL[oq + i]
   NEXT
ENDIF
IF nS > 0 THEN
   FOR i = 0 TO nS - 1 DO
      $sL[ob + i] = $sL[oq + i]
   NEXT
ENDIF

// new pivots join their pool; the oldest one leaves when the pool is full
IF phOk = 1 THEN
   $bL[ob + nB] = phV
   nB = nB + 1
   IF nB > mxP THEN
      FOR i = 0 TO nB - 2 DO
         $bL[ob + i] = $bL[ob + i + 1]
      NEXT
      nB = nB - 1
   ENDIF
ENDIF
IF plOk = 1 THEN
   $sL[ob + nS] = plV
   nS = nS + 1
   IF nS > mxP THEN
      FOR i = 0 TO nS - 2 DO
         $sL[ob + i] = $sL[ob + i + 1]
      NEXT
      nS = nS - 1
   ENDIF
ENDIF

// nearest active BSL above the low, nearest SSL below the high
bslIdx = -1
bslV = 0
IF nB > 0 THEN
   FOR i = nB - 1 DOWNTO 0 DO
      vv = $bL[ob + i]
      IF low < vv AND (bslIdx = -1 OR vv < bslV) THEN
         bslV = vv
         bslIdx = i
      ENDIF
   NEXT
ENDIF
sslIdx = -1
sslV = 0
IF nS > 0 THEN
   FOR i = nS - 1 DOWNTO 0 DO
      vv = $sL[ob + i]
      IF high > vv AND (sslIdx = -1 OR vv > sslV) THEN
         sslV = vv
         sslIdx = i
      ENDIF
   NEXT
ENDIF

// ===================== SWEEP ENGINE =====================
// bearish reversal: BSL swept (wick above) then rejected / absorbed
// bullish reversal: SSL swept (wick below) then rejected / absorbed
bslSwept = 0
IF bslIdx >= 0 AND high > bslV THEN
   bslSwept = 1
ENDIF
sslSwept = 0
IF sslIdx >= 0 AND low < sslV THEN
   sslSwept = 1
ENDIF
bslTrig = 0
sslTrig = 0
IF sweepMode = 0 THEN
   IF bslSwept = 1 AND close < bslV THEN
      bslTrig = 1
   ENDIF
   IF sslSwept = 1 AND close > sslV THEN
      sslTrig = 1
   ENDIF
ELSE
   IF bslSwept = 1 AND atrOk = 1 AND close > bslV + atrV * dispMult THEN
      bslTrig = 1
   ENDIF
   IF sslSwept = 1 AND atrOk = 1 AND close < sslV - atrV * dispMult THEN
      sslTrig = 1
   ENDIF
ENDIF
bslConfirm = 0
IF bslTrig = 1 AND (useAbs = 0 OR absorbed = 1) THEN
   bslConfirm = 1
ENDIF
sslConfirm = 0
IF sslTrig = 1 AND (useAbs = 0 OR absorbed = 1) THEN
   sslConfirm = 1
ENDIF

// swept pools are removed so they do not fire again
IF bslSwept = 1 THEN
   IF bslIdx <= nB - 2 THEN
      FOR i = bslIdx TO nB - 2 DO
         $bL[ob + i] = $bL[ob + i + 1]
      NEXT
   ENDIF
   nB = nB - 1
ENDIF
IF sslSwept = 1 THEN
   IF sslIdx <= nS - 2 THEN
      FOR i = sslIdx TO nS - 2 DO
         $sL[ob + i] = $sL[ob + i + 1]
      NEXT
   ENDIF
   nS = nS - 1
ENDIF

// ===================== SIGNALS AND PROJECTED DRAW =====================
// stored with a pure assignment indexed by a scalar counter (the counter
// rewinds between ticks, so a signal that disappears on the live bar leaves
// nothing behind) and redrawn on the last bar
IF atrOk = 1 THEN
   sigOff = atrV
ELSE
   sigOff = rng
ENDIF
once nSig = 0
once nPrj = 0
IF sslConfirm = 1 THEN
   $sgX[nSig] = barindex
   $sgY1[nSig] = low - 0.5 * sigOff
   $sgY2[nSig] = low - 1.2 * sigOff
   $sgY3[nSig] = low - 1.8 * sigOff
   $sgD[nSig] = 1
   nSig = nSig + 1
   IF showProj = 1 AND atrOk = 1 THEN
      risk = max(atrV, sslV - low)
      IF projStyle = 0 AND bslIdx >= 0 THEN
         tgt = bslV
      ELSE
         tgt = close + risk * rMult
      ENDIF
      $pjX[nPrj] = barindex
      $pjY1[nPrj] = sslV
      $pjY2[nPrj] = tgt
      $pjD[nPrj] = 1
      nPrj = nPrj + 1
   ENDIF
ENDIF
IF bslConfirm = 1 THEN
   $sgX[nSig] = barindex
   $sgY1[nSig] = high + 0.5 * sigOff
   $sgY2[nSig] = high + 1.8 * sigOff
   $sgY3[nSig] = high + 1.2 * sigOff
   $sgD[nSig] = -1
   nSig = nSig + 1
   IF showProj = 1 AND atrOk = 1 THEN
      risk = max(atrV, high - bslV)
      IF projStyle = 0 AND sslIdx >= 0 THEN
         tgt = sslV
      ELSE
         tgt = close - risk * rMult
      ENDIF
      $pjX[nPrj] = barindex
      $pjY1[nPrj] = bslV
      $pjY2[nPrj] = tgt
      $pjD[nPrj] = -1
      nPrj = nPrj + 1
   ENDIF
ENDIF

// ===================== DRAWING ON THE LAST BAR =====================
IF islastbarupdate THEN
   tk = ticksize
   IF tk <= 0 THEN
      tk = 0.01
   ENDIF
   // active pools
   IF nB > 0 THEN
      FOR i = nB - 1 DOWNTO 0 DO
         vv = $bL[ob + i]
         DRAWSEGMENT(barindex - 60, vv, barindex + 8, vv) COLOURED(38, 166, 154, 255) STYLE(dottedline2, 1)
      NEXT
   ENDIF
   IF nS > 0 THEN
      FOR i = nS - 1 DOWNTO 0 DO
         vv = $sL[ob + i]
         DRAWSEGMENT(barindex - 60, vv, barindex + 8, vv) COLOURED(239, 83, 80, 255) STYLE(dottedline2, 1)
      NEXT
   ENDIF
   // signals: triangle in the signal colour, "Bull / Sweep" text in blue
   IF nSig > 0 THEN
      FOR k = 0 TO nSig - 1 DO
         sx = $sgX[k]
         sy1 = $sgY1[k]
         sy2 = $sgY2[k]
         sy3 = $sgY3[k]
         IF $sgD[k] = 1 THEN
            DRAWTEXT("▲", sx, sy1, dialog, bold, 14) COLOURED(38, 166, 154, 255)
            DRAWTEXT("Bull", sx, sy2, dialog, standard, 10) COLOURED(41, 98, 255, 255)
            DRAWTEXT("Sweep", sx, sy3, dialog, standard, 10) COLOURED(41, 98, 255, 255)
         ELSE
            DRAWTEXT("▼", sx, sy1, dialog, bold, 14) COLOURED(239, 83, 80, 255)
            DRAWTEXT("Bear", sx, sy2, dialog, standard, 10) COLOURED(41, 98, 255, 255)
            DRAWTEXT("Sweep", sx, sy3, dialog, standard, 10) COLOURED(41, 98, 255, 255)
         ENDIF
      NEXT
   ENDIF
   // projected draws: the original keeps its 500 most recent lines (pool lines
   // included) and its 500 most recent labels
   firstPrj = max(0, nPrj - (500 - nB - nS))
   IF nPrj > 0 THEN
      FOR k = firstPrj TO nPrj - 1 DO
         px = $pjX[k]
         py1 = $pjY1[k]
         py2 = $pjY2[k]
         tgtP = round(py2 / tk) * tk
         IF $pjD[k] = 1 THEN
            DRAWSEGMENT(px, py1, px + 20, py2) COLOURED(38, 166, 154, 255) STYLE(dottedline, 2)
            DRAWTEXT("#tgtP#", px + 24, py2, dialog, bold, 10) COLOURED(38, 166, 154, 255)
         ELSE
            DRAWSEGMENT(px, py1, px + 20, py2) COLOURED(239, 83, 80, 255) STYLE(dottedline, 2)
            DRAWTEXT("#tgtP#", px + 24, py2, dialog, bold, 10) COLOURED(239, 83, 80, 255)
         ENDIF
      NEXT
   ENDIF

   // ---------- dashboard (top right) ----------
   IF showDash = 1 THEN
      pz = 10
      chW = pz * 0.62
      rowH = pz + 8
      padX = 6
      col0W = 12 * chW + 2 * padX
      col1W = 19 * chW + 2 * padX
      pW = col0W + col1W
      pH = 6 * rowH
      pL = 0 - 85 - pW
      pT = 0 - 12
      xC0 = pL + col0W / 2
      xC1 = pL + col0W + col1W / 2
      y0 = pT - rowH * 0.5
      y1 = pT - rowH * 1.5
      y2 = pT - rowH * 2.5
      y3 = pT - rowH * 3.5
      y4 = pT - rowH * 4.5
      y5 = pT - rowH * 5.5
      bslP = round(bslV / tk) * tk
      sslP = round(sslV / tk) * tk
      // opaque background: a transparent dark table turns grey on a light chart
      DRAWRECTANGLE(pL, pT, pL + pW, pT - pH) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(48, 54, 61, 255) FILLCOLOR(13, 17, 23, 255)
      DRAWTEXT("LSA Engine", xC0, y0, monospaced, bold, 11) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(201, 209, 217, 255)
      DRAWTEXT("PhenLabs", xC1, y0, monospaced, bold, 11) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(126, 138, 154, 255)
      DRAWTEXT("Nearest BSL", xC0, y1, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      DRAWTEXT("Nearest SSL", xC0, y2, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      DRAWTEXT("Vol Spike", xC0, y3, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      DRAWTEXT("Absorption", xC0, y4, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      DRAWTEXT("Mode", xC0, y5, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      IF bslIdx >= 0 THEN
         DRAWTEXT("#bslP#", xC1, y1, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(38, 166, 154, 255)
      ELSE
         DRAWTEXT("—", xC1, y1, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(38, 166, 154, 255)
      ENDIF
      IF sslIdx >= 0 THEN
         DRAWTEXT("#sslP#", xC1, y2, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(239, 83, 80, 255)
      ELSE
         DRAWTEXT("—", xC1, y2, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(239, 83, 80, 255)
      ENDIF
      IF volSpike = 1 THEN
         DRAWTEXT("YES", xC1, y3, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(241, 196, 15, 255)
      ELSE
         DRAWTEXT("no", xC1, y3, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      ENDIF
      IF absorbed = 1 THEN
         DRAWTEXT("YES", xC1, y4, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(241, 196, 15, 255)
      ELSE
         DRAWTEXT("no", xC1, y4, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(139, 148, 158, 255)
      ENDIF
      IF sweepMode = 0 THEN
         DRAWTEXT("Close Back Inside", xC1, y5, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(201, 209, 217, 255)
      ELSE
         DRAWTEXT("Displacement Break", xC1, y5, monospaced, bold, pz) ANCHOR(TOPRIGHT, XSHIFT, YSHIFT) COLOURED(201, 209, 217, 255)
      ENDIF
   ENDIF
ENDIF

RETURN

Download
Filename: PRC_LSA-Reversal-Engine.itf
Downloads: 17
Iván González
Iván González Legend
This author is like an anonymous function, present but not directly identifiable. More details on this code architect as soon as they exit 'incognito' mode.
Author’s Profile

Comments

ProRealCode ProRealCode
Loading...