Indicator · Pine Script v5 · On the chart

LHS v2 Liquidity Sweep Score

by AssetWhisper

An overlay indicator that tracks unswept pivot highs and lows as liquidity levels and detects sweeps, where a wick pierces a level and the bar closes back inside.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code211
Settings11inputs
LicenceMPL-2.0
Last update7 weeks ago10 Sep 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

211 lines of Pine Script v5 from indicators/LHS_Liquidity_Sweep_Score.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

LHS_Liquidity_Sweep_Score.pine
//@version=5
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © AssetWhisper — https://assetwhisper.com/indicators/
// ============================================================================
//  LHS v2 — Liquidity Sweep Score
//  Idea: la liquidez (stops) se acumula justo por debajo de mínimos pivote y por
//  encima de máximos pivote. Un "sweep" es una mecha que perfora uno de esos
//  niveles y cierra de vuelta dentro. Cada sweep recibe una puntuación 0-100 según
//  mecha, volumen relativo, posición del cierre y proximidad a los VWAP anclados a
//  los pivotes; solo se señaliza por encima de un umbral.
//  Mejoras v2: niveles reales (no rolling highest), cada nivel solo se barre una vez,
//  score continuo exportable a otros scripts, objetos gráficos gestionados y
//  alertas con texto dinámico.
// ============================================================================
indicator("LHS v2 — Liquidity Sweep Score", shorttitle="LHS v2", overlay=true, max_lines_count=300, max_labels_count=200)

// === INPUTS ===
grpL = "Niveles de liquidez"
pivotLen   = input.int(10, "Pivot length (left/right)", minval=3, maxval=50, group=grpL)
maxLevels  = input.int(12, "Max unswept levels per side", minval=3, maxval=40, group=grpL)
maxAgeBars = input.int(400, "Level max age (bars)", minval=50, maxval=2000, group=grpL)
grpS = "Sweep"
wickMin     = input.float(0.5, "Min wick / range", minval=0.2, maxval=0.9, step=0.05, group=grpS)
volLookback = input.int(100, "Volume rank lookback", minval=20, maxval=500, group=grpS)
volPctMin   = input.int(70, "Min volume percentile (0-100)", minval=0, maxval=99, group=grpS)
minScore    = input.int(60, "Min score to signal (0-100)", minval=0, maxval=100, group=grpS)
grpV = "Visual"
showLevels = input.bool(true, "Draw unswept levels", group=grpV)
showSweeps = input.bool(true, "Draw sweep lines + score", group=grpV)
showAVWAP  = input.bool(true, "Show pivot-anchored VWAPs", group=grpV)
showTable  = input.bool(true, "Show info table", group=grpV)

// === BASE ===
atr = ta.atr(14)
ph  = ta.pivothigh(high, pivotLen, pivotLen)
pl  = ta.pivotlow(low, pivotLen, pivotLen)

// === ALMACÉN DE NIVELES NO BARRIDOS ===
var array<float> hiLvl  = array.new<float>()
var array<int>   hiBar  = array.new<int>()
var array<line>  hiLine = array.new<line>()
var array<float> loLvl  = array.new<float>()
var array<int>   loBar  = array.new<int>()
var array<line>  loLine = array.new<line>()

deleteLine(ln) =>
    if not na(ln)
        line.delete(ln)

pushLevel(lvls, bars, lns, price, bIdx, col) =>
    array.push(lvls, price)
    array.push(bars, bIdx)
    line ln = na
    if showLevels
        ln := line.new(bIdx, price, bar_index, price, color=col, style=line.style_dotted, width=1)
    array.push(lns, ln)
    if array.size(lvls) > maxLevels
        array.shift(lvls)
        array.shift(bars)
        deleteLine(array.shift(lns))

removeLevel(lvls, bars, lns, i) =>
    array.remove(lvls, i)
    array.remove(bars, i)
    deleteLine(array.remove(lns, i))

purgeOld(lvls, bars, lns) =>
    n = array.size(lvls)
    if n > 0
        for i = n - 1 to 0
            if bar_index - array.get(bars, i) > maxAgeBars
                removeLevel(lvls, bars, lns, i)

extendLines(lns) =>
    n = array.size(lns)
    if n > 0
        for i = 0 to n - 1
            ln = array.get(lns, i)
            if not na(ln)
                line.set_x2(ln, bar_index)

// Registrar pivotes confirmados (el pivote ocurrió hace pivotLen barras)
if not na(ph)
    pushLevel(hiLvl, hiBar, hiLine, ph, bar_index - pivotLen, color.new(color.red, 40))
if not na(pl)
    pushLevel(loLvl, loBar, loLine, pl, bar_index - pivotLen, color.new(color.green, 40))
purgeOld(hiLvl, hiBar, hiLine)
purgeOld(loLvl, loBar, loLine)

// === VWAP ANCLADOS AL ÚLTIMO PIVOTE ALTO / BAJO ===
var float cumVolHi = 0.0
var float cumPVHi  = 0.0
var float cumVolLo = 0.0
var float cumPVLo  = 0.0
if not na(ph)
    cumVolHi := 0.0
    cumPVHi  := 0.0
    for i = pivotLen to 1
        cumVolHi += volume[i]
        cumPVHi  += hlc3[i] * volume[i]
if not na(pl)
    cumVolLo := 0.0
    cumPVLo  := 0.0
    for i = pivotLen to 1
        cumVolLo += volume[i]
        cumPVLo  += hlc3[i] * volume[i]
cumVolHi += volume
cumPVHi  += hlc3 * volume
cumVolLo += volume
cumPVLo  += hlc3 * volume
avwapHi = cumVolHi > 0 ? cumPVHi / cumVolHi : na
avwapLo = cumVolLo > 0 ? cumPVLo / cumVolLo : na

// === DETECCIÓN DE SWEEPS ===
bullSweep = false
bearSweep = false
sweptCntB = 0
sweptCntS = 0
float sweptLvlB = na
int   sweptBarB = na
float sweptLvlS = na
int   sweptBarS = na

// Bull: la mecha perfora un mínimo pivote y el cierre queda por encima.
// Si el cierre queda por debajo el nivel se ha roto: deja de ser liquidez pendiente.
nLo = array.size(loLvl)
if nLo > 0
    for i = nLo - 1 to 0
        lvl = array.get(loLvl, i)
        if low < lvl
            if close > lvl
                bullSweep := true
                sweptCntB += 1
                if na(sweptLvlB) or lvl < sweptLvlB
                    sweptLvlB := lvl
                    sweptBarB := array.get(loBar, i)
            removeLevel(loLvl, loBar, loLine, i)

nHi = array.size(hiLvl)
if nHi > 0
    for i = nHi - 1 to 0
        lvl = array.get(hiLvl, i)
        if high > lvl
            if close < lvl
                bearSweep := true
                sweptCntS += 1
                if na(sweptLvlS) or lvl > sweptLvlS
                    sweptLvlS := lvl
                    sweptBarS := array.get(hiBar, i)
            removeLevel(hiLvl, hiBar, hiLine, i)

extendLines(hiLine)
extendLines(loLine)

// === CALIDAD DEL SWEEP ===
rng        = high - low
lowerWickR = rng > 0 ? (math.min(open, close) - low) / rng : 0.0
upperWickR = rng > 0 ? (high - math.max(open, close)) / rng : 0.0
clv        = rng > 0 ? (close - low) / rng : 0.5           // 1 = cierre en el máximo
volRank    = ta.percentrank(volume, volLookback)           // 0-100

distHi = na(avwapHi) ? na : math.abs(close - avwapHi)
distLo = na(avwapLo) ? na : math.abs(close - avwapLo)
nearestDist = na(distHi) ? distLo : na(distLo) ? distHi : math.min(distHi, distLo)
vwapProx = na(nearestDist) or atr <= 0 ? 0.0 : math.max(0.0, 1 - nearestDist / (2 * atr))

scoreBull = math.min(100.0, 30 * math.min(lowerWickR / 0.7, 1.0) + 25 * volRank / 100 + 25 * clv + 20 * vwapProx + (sweptCntB >= 2 ? 10 : 0))
scoreBear = math.min(100.0, 30 * math.min(upperWickR / 0.7, 1.0) + 25 * volRank / 100 + 25 * (1 - clv) + 20 * vwapProx + (sweptCntS >= 2 ? 10 : 0))

bullSignal = bullSweep and lowerWickR >= wickMin and volRank >= volPctMin and scoreBull >= minScore
bearSignal = bearSweep and upperWickR >= wickMin and volRank >= volPctMin and scoreBear >= minScore

// === DIBUJO DE SWEEPS ===
if showSweeps and bullSignal
    line.new(sweptBarB, sweptLvlB, bar_index, sweptLvlB, color=color.new(color.green, 0), width=2)
    label.new(bar_index, low, "SWEEP " + str.tostring(scoreBull, "#"), style=label.style_label_up, color=color.new(color.green, 20), textcolor=color.white, size=size.small)
if showSweeps and bearSignal
    line.new(sweptBarS, sweptLvlS, bar_index, sweptLvlS, color=color.new(color.red, 0), width=2)
    label.new(bar_index, high, "SWEEP " + str.tostring(scoreBear, "#"), style=label.style_label_down, color=color.new(color.red, 20), textcolor=color.white, size=size.small)

// === VISUAL ===
plot(showAVWAP ? avwapHi : na, "AVWAP from pivot high", color=color.new(color.red, 60), linewidth=1)
plot(showAVWAP ? avwapLo : na, "AVWAP from pivot low", color=color.new(color.green, 60), linewidth=1)
plotshape(bullSignal, "Bull sweep", style=shape.triangleup, location=location.belowbar, color=color.green, size=size.small)
plotshape(bearSignal, "Bear sweep", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small)
plotshape(bullSweep and not bullSignal, "Weak bull sweep", style=shape.circle, location=location.belowbar, color=color.new(color.green, 70), size=size.tiny)
plotshape(bearSweep and not bearSignal, "Weak bear sweep", style=shape.circle, location=location.abovebar, color=color.new(color.red, 70), size=size.tiny)

// Serie exportable: +score en sweep alcista, -score en bajista, 0 el resto (usar como input.source en otros scripts)
plot(bullSignal ? scoreBull : bearSignal ? -scoreBear : 0, "LHS signal (export)", display=display.none)

if showTable
    var table t = table.new(position.top_right, 2, 5, bgcolor=color.new(color.black, 60))
    table.cell(t, 0, 0, "Levels above", text_color=color.white, text_size=size.small)
    table.cell(t, 1, 0, str.tostring(array.size(hiLvl)), text_color=color.red, text_size=size.small)
    table.cell(t, 0, 1, "Levels below", text_color=color.white, text_size=size.small)
    table.cell(t, 1, 1, str.tostring(array.size(loLvl)), text_color=color.green, text_size=size.small)
    table.cell(t, 0, 2, "Volume rank", text_color=color.white, text_size=size.small)
    table.cell(t, 1, 2, str.tostring(volRank, "#") + "%", text_color=volRank >= volPctMin ? color.lime : color.gray, text_size=size.small)
    table.cell(t, 0, 3, "Bull score", text_color=color.white, text_size=size.small)
    table.cell(t, 1, 3, bullSweep ? str.tostring(scoreBull, "#") : "-", text_color=color.green, text_size=size.small)
    table.cell(t, 0, 4, "Bear score", text_color=color.white, text_size=size.small)
    table.cell(t, 1, 4, bearSweep ? str.tostring(scoreBear, "#") : "-", text_color=color.red, text_size=size.small)

// === ALERTAS ===
alertcondition(bullSignal, "LHS Bull Sweep", "LHS v2: bullish liquidity sweep (score above threshold)")
alertcondition(bearSignal, "LHS Bear Sweep", "LHS v2: bearish liquidity sweep (score above threshold)")
if bullSignal
    alert("LHS v2 bull sweep " + syminfo.ticker + " @" + str.tostring(close, format.mintick) + " score " + str.tostring(scoreBull, "#"), alert.freq_once_per_bar_close)
if bearSignal
    alert("LHS v2 bear sweep " + syminfo.ticker + " @" + str.tostring(close, format.mintick) + " score " + str.tostring(scoreBear, "#"), alert.freq_once_per_bar_close)

Overview

Each sweep gets a 0 to 100 score from wick size, relative volume, close position and proximity to pivot-anchored VWAPs, and is signalled only above a minimum score. Each level is swept once. Pivot length, wick ratio, volume percentile and score threshold are inputs, and alerts and an info table are available.

Details

Published by

AssetWhisper on GitHub

Draws

On the price chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Pivot length (left/right)int10pivotLen
Max unswept levels per sideint12maxLevels
Level max age (bars)int400maxAgeBars
Min wick / rangefloat0.5wickMin
Volume rank lookbackint100volLookback
Min volume percentile (0-100)int70volPctMin
Min score to signal (0-100)int60minScore
Draw unswept levelsbooltrueshowLevels
Draw sweep lines + scorebooltrueshowSweeps
Show pivot-anchored VWAPsbooltrueshowAVWAP
Show info tablebooltrueshowTable
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

ICT Equal Highs and Lows

by ArunKBhaskar
575 stars

This overlay indicator finds pivot highs and lows on the price chart and compares the last two highs and the last two lows. When the percentage difference between a pair is below the Equal HL threshold, it treats them as equal levels and draws a box. Left and

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Equal Highs and Lows

DE-tracker

by Allen1212
303 stars

This indicator plots labels on the TradingView price chart to mark ARK Invest's buy and sell trades for the DE stock. Buy transactions appear in green with X1 or X2 multipliers showing the number of ARK ETFs involved on the same day; sells appear in red. The m

Indicator v4 Support & resistanceSMC / ICT / price action
Updated 2 years ago · None stated GitHub repository of DE-tracker

This Pine Script indicator plots a ZigZag on the price chart. It identifies reversal points when price moves more than a set percentage from the last pivot. The script calculates forecasts using zigzag data and labels detected geometric patterns including harm

Indicator v4 TrendSupport & resistance

Golden Bullet Strategy

by TradersPost
228 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

Open Source Fractal

by cantolab
183 stars

This indicator implements the Fractal Model concept to plot market structure shifts, liquidity sweeps, and Change in State of Delivery (CISD) levels. It overlays custom higher-timeframe candle previews, open prices, and time markers directly on the price chart

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · MIT GitHub repository of Open Source Fractal

Swing Failure Signals

by Ganador1
154 stars

This indicator calculates pivot highs and pivot lows to detect liquidity sweeps and Swing Failure Patterns. It identifies when price breaks past recent key swing highs or lows within a configurable historical window, then looks for a quick reversal or change i

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · Apache-2.0 GitHub repository of Swing Failure Signals

More SMC / ICT scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers