Indicator · Pine Script v6 · On the chart

Liquidity Magnet

by VolodymyrFilias

Liquidity Magnet scores active liquidity pools on the price chart and highlights the pool most likely to be reached.

View on GitHub

GitHub stars4on the repository
Pine versionv6current
Lines of code547
Settings40inputs
LicenceMIT
Last update4 months ago11 Jun 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

547 lines of Pine Script v6 from indicators/liquidity-magnet.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

liquidity-magnet.pine
//@version=6
// =============================================================================
// LIQUIDITY MAGNET v1.6.4
// =============================================================================
// v1.6.4 fixes:
//   + Historical markers now placed at recordedBar (where the magnet actually
//     was), not at resolvedBar (where price arrived later). No more ghost
//     markers in empty chart areas.
//   + If a former top was swept the same bar it stopped being top, it gets
//     status=touched immediately, not pending → expired. This eliminates
//     fake ✗ markers on pools that were actually swept.
//   + Top selection robust even when scores are tiny: picks highest-scoring
//     pool regardless of total score magnitude. Fixes NEUTRAL/0% in Replay.
// =============================================================================

indicator("Liquidity Magnet", "LM", overlay=true, max_boxes_count=500, max_lines_count=500, max_labels_count=500, max_bars_back=5000)

// ============================== INPUTS =======================================
gDet = "Liquidity Detection"
pivotLen     = input.int(10, "Pivot Lookback", minval=2, group=gDet)
maxPools     = input.int(25, "Max Active Pools", minval=5, maxval=50, group=gDet)
minAge       = input.int(3, "Min Pool Age (bars)", minval=1, group=gDet)
maxAge       = input.int(500, "Max Pool Age (bars)", minval=50, group=gDet)

gEq = "Equal H/L Clustering"
autoEqTol    = input.bool(true, "Auto-Scale Tolerance by Timeframe", group=gEq)
manualEqTol  = input.float(0.15, "Manual Tolerance %", minval=0.0, step=0.05, group=gEq)
eqBonus      = input.float(1.7, "Equal H/L Bonus Mult", minval=1.0, step=0.1, group=gEq)

gDist = "Distance Filter (Log-Scale)"
autoScale    = input.bool(true, "Auto-Scale by Timeframe", group=gDist)
manualLogD   = input.float(0.45, "Manual Log-Distance", minval=0.05, maxval=2.0, step=0.05, group=gDist)

gScore = "Probability Scoring (Weights)"
wStrength    = input.float(1.0, "Strength Weight", minval=0.0, step=0.1, group=gScore)
wProximity   = input.float(1.2, "Proximity Weight", minval=0.0, step=0.1, group=gScore)
wAge         = input.float(0.6, "Age Decay Weight", minval=0.0, step=0.1, group=gScore)
wMomentum    = input.float(0.9, "Momentum Bias Weight", minval=0.0, step=0.1, group=gScore)

gVis = "Visualization"
showZones    = input.bool(true, "Show Liquidity Zones", group=gVis)
showTop      = input.bool(true, "Highlight TOP MAGNET", group=gVis)
showLabels   = input.bool(true, "Show Probability Labels", group=gVis)
showDash     = input.bool(true, "Show Dashboard", group=gVis)
showStats    = input.bool(true, "Show Touch Stats in Dashboard", group=gVis)
showLegend   = input.bool(true, "Show Marker Legend in Dashboard", group=gVis)
zoneExt      = input.int(20, "Zone Extension (bars right)", minval=0, group=gVis)

gHist = "Historical Track (Bar Replay)"
showHistory      = input.bool(false, "Show Historical Top Magnets", group=gHist)
trackingWindow   = input.int(100, "Tracking Window (bars)", minval=20, maxval=500, group=gHist)
maxHistory       = input.int(100, "Max Historical Markers", minval=10, maxval=300, group=gHist)
histMarkerSize   = input.string("Tiny", "Historical Marker Size", options=["Tiny", "Small", "Normal", "Large", "Huge"], group=gHist)

gMag = "Magnet Marker & Field Line"
showMagnet   = input.bool(true, "Show Magnet Marker on TOP zone", group=gMag)
magnetSize   = input.string("Large", "Magnet Marker Size", options=["Tiny", "Small", "Normal", "Large", "Huge"], group=gMag)
showField    = input.bool(true, "Show Magnet Field Line", group=gMag)
fieldStyle   = input.string("Diagonal", "Field Line Style", options=["Diagonal", "Vertical"], group=gMag)
fieldOffset  = input.int(20, "Diagonal Offset (bars right)", minval=10, maxval=100, group=gMag)

gSize = "UI Sizing"
dashPosStr    = input.string("Top Right", "Dashboard Position", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left", "Middle Right", "Middle Left"], group=gSize)
dashSizeStr   = input.string("Small",     "Dashboard Text Size", options=["Tiny", "Small", "Normal", "Large", "Huge"], group=gSize)
lblSizeStr    = input.string("Small",     "Zone Label Text Size", options=["Tiny", "Small", "Normal", "Large"], group=gSize)
topLblSizeStr = input.string("Normal",    "TOP Label Text Size", options=["Small", "Normal", "Large", "Huge"], group=gSize)

gCol = "Colors"
colHigh     = input.color(#ef4444, "Upside Liquidity", group=gCol)
colLow      = input.color(#22c55e, "Downside Liquidity", group=gCol)
colTop      = input.color(#f59e0b, "TOP MAGNET", group=gCol)
colField    = input.color(#f59e0b, "Magnet Field Line", group=gCol)
colHistOK   = input.color(#22c55e, "Historical: Touched", group=gCol)
colHistFail = input.color(#94a3b8, "Historical: Expired", group=gCol)
colDashBg   = input.color(#0F172A, "Dashboard BG", group=gCol)
colDashBdr  = input.color(#1e293b, "Dashboard Border", group=gCol)

// ============================== TYPES ========================================
type Pool
    float price
    int   barIdx
    bool  isHigh
    bool  isEqual
    float volAt
    float score
    float probPct
    box   bx
    line  ln
    label lbl

type HistMagnet
    float price
    int   recordedBar
    bool  isHigh
    int   status         // 0 pending, 1 touched, 2 expired
    int   resolvedBar

// ============================== STATE ========================================
var array<Pool>       pools         = array.new<Pool>()
var array<HistMagnet> history       = array.new<HistMagnet>()
var array<label>      histLabels    = array.new<label>()
var line              fieldLn       = na
var label             fieldArrow    = na
var label             magnetMarker  = na
var int               touchCount    = 0
var int               expiredCount  = 0

var float prevTopPrice   = na
var bool  prevTopIsHigh  = false

// ============================== TA HOISTED ===================================
atrV   = ta.atr(14)
momRaw = ta.ema(close, 8) - ta.ema(close, 21)
volMa  = ta.sma(volume, 50)

// ============================== AUTO-SCALE BY TF =============================
tfSec = timeframe.in_seconds(timeframe.period)

autoLogD = 0.30
if tfSec >= 604800
    autoLogD := 0.95
else if tfSec >= 86400
    autoLogD := 0.65
else if tfSec >= 14400
    autoLogD := 0.45
else if tfSec >= 3600
    autoLogD := 0.30
else if tfSec >= 900
    autoLogD := 0.18
else
    autoLogD := 0.10

logDistMax = autoScale ? autoLogD : manualLogD

autoEqPct = 0.15
if tfSec >= 604800
    autoEqPct := 10
else if tfSec >= 86400
    autoEqPct := 8
else if tfSec >= 14400
    autoEqPct := 5
else if tfSec >= 3600
    autoEqPct := 0.8
else if tfSec >= 900
    autoEqPct := 0.5
else
    autoEqPct := 0.15

eqTolPct = (autoEqTol ? autoEqPct : manualEqTol) / 100.0

// ============================== HELPERS ======================================
logDist(float a, float b) =>
    (a > 0 and b > 0) ? math.abs(math.log(a / b)) : 1e10

findClosestPool(float newPrice, bool isHigh) =>
    idx     = -1
    minDist = 1e10
    sz      = array.size(pools)
    if sz > 0
        for i = 0 to sz - 1
            p = array.get(pools, i)
            if p.isHigh == isHigh
                diff = math.abs(p.price - newPrice) / newPrice
                if diff <= eqTolPct and diff < minDist
                    minDist := diff
                    idx     := i
    idx

clearPoolDraws(Pool p) =>
    if not na(p.bx)
        box.delete(p.bx)
    if not na(p.ln)
        line.delete(p.ln)
    if not na(p.lbl)
        label.delete(p.lbl)

strToSize(string s) =>
    result = size.small
    if s == "Tiny"
        result := size.tiny
    else if s == "Small"
        result := size.small
    else if s == "Normal"
        result := size.normal
    else if s == "Large"
        result := size.large
    else if s == "Huge"
        result := size.huge
    result

strToPos(string s) =>
    result = position.top_right
    if s == "Top Right"
        result := position.top_right
    else if s == "Top Left"
        result := position.top_left
    else if s == "Bottom Right"
        result := position.bottom_right
    else if s == "Bottom Left"
        result := position.bottom_left
    else if s == "Middle Right"
        result := position.middle_right
    else if s == "Middle Left"
        result := position.middle_left
    result

dashSize   = strToSize(dashSizeStr)
lblSize    = strToSize(lblSizeStr)
topLblSize = strToSize(topLblSizeStr)
magSize    = strToSize(magnetSize)
histSize   = strToSize(histMarkerSize)
dashPos    = strToPos(dashPosStr)

// ============================== PIVOT DETECT + MERGE =========================
ph = ta.pivothigh(high, pivotLen, pivotLen)
pl = ta.pivotlow(low,  pivotLen, pivotLen)

if not na(ph)
    mIdx = findClosestPool(ph, true)
    if mIdx >= 0
        existing = array.get(pools, mIdx)
        existing.isEqual := true
        existing.price   := math.max(existing.price, ph)
        existing.volAt   := existing.volAt + volume[pivotLen]
        array.set(pools, mIdx, existing)
    else
        array.push(pools, Pool.new(ph, bar_index - pivotLen, true, false, volume[pivotLen], 0.0, 0.0, na, na, na))

if not na(pl)
    mIdx = findClosestPool(pl, false)
    if mIdx >= 0
        existing = array.get(pools, mIdx)
        existing.isEqual := true
        existing.price   := math.min(existing.price, pl)
        existing.volAt   := existing.volAt + volume[pivotLen]
        array.set(pools, mIdx, existing)
    else
        array.push(pools, Pool.new(pl, bar_index - pivotLen, false, false, volume[pivotLen], 0.0, 0.0, na, na, na))

// ============================== PRUNE — track if prev top was swept ==========
// We need to know if previous top got SWEPT specifically (not just removed)
// to properly mark history.
prevTopWasSweptThisBar = false

ii = array.size(pools) - 1
while ii >= 0
    p      = array.get(pools, ii)
    age    = bar_index - p.barIdx
    swept  = (p.isHigh and high > p.price) or (not p.isHigh and low < p.price)
    tooOld = age > maxAge
    lDist  = logDist(p.price, close)
    tooFar = lDist > logDistMax

    // Was this the previous top magnet?
    isPrevTop = not na(prevTopPrice) and math.abs(p.price - prevTopPrice) < syminfo.mintick * 5 and p.isHigh == prevTopIsHigh

    if swept
        if isPrevTop
            prevTopWasSweptThisBar := true
        clearPoolDraws(p)
        array.remove(pools, ii)
    else if tooOld
        clearPoolDraws(p)
        array.remove(pools, ii)
    else if tooFar
        clearPoolDraws(p)
        array.remove(pools, ii)
    ii -= 1

while array.size(pools) > maxPools
    pShift = array.shift(pools)
    clearPoolDraws(pShift)

// ============================== SCORING ======================================
calcScore(Pool p) =>
    age   = bar_index - p.barIdx
    lDist = logDist(p.price, close)

    volFactor = (na(p.volAt) or volMa <= 0) ? 1.0 : math.min(p.volAt / volMa, 3.0)
    eqMult    = p.isEqual ? eqBonus : 1.0
    strength  = volFactor * eqMult

    prox = 0.0
    if lDist < 0.002
        prox := 0.3
    else if lDist < 0.05
        prox := math.max(0.5, 1.0 - math.abs(lDist - 0.02) * 10.0)
    else
        prox := math.max(0.05, 1.0 - (lDist - 0.05) * 3.0)  // floor at 0.05 instead of 0

    ageScore = 0.0
    if age < minAge
        ageScore := 0.5
    else if age < 50
        ageScore := 1.0
    else
        ageScore := math.max(0.1, 1.0 - (age - 50) / 450.0)

    dirToPool = p.price > close ? 1.0 : -1.0
    momNorm   = close > 0 ? momRaw / close : 0.0
    momAlign  = dirToPool * momNorm * 100.0
    momScore  = 1.0 + math.max(-0.5, math.min(0.5, momAlign))

    rawScore = strength * wStrength + prox * wProximity + ageScore * wAge + momScore * wMomentum
    math.max(rawScore, 0.01)  // floor so scoring never collapses to 0

totalScore = 0.0
scoreUp    = 0.0
scoreDn    = 0.0
topIdx     = -1
topScore   = -1.0

if array.size(pools) > 0
    for i = 0 to array.size(pools) - 1
        p = array.get(pools, i)
        p.score := calcScore(p)
        array.set(pools, i, p)
        totalScore += p.score
        if p.score > topScore
            topScore := p.score
            topIdx   := i

    // Probabilities + bias breakdown
    if totalScore > 0
        for i = 0 to array.size(pools) - 1
            p = array.get(pools, i)
            p.probPct := (p.score / totalScore) * 100.0
            array.set(pools, i, p)
            if p.isHigh
                scoreUp += p.score
            else
                scoreDn += p.score

topPrice   = (topIdx >= 0 and array.size(pools) > 0) ? array.get(pools, topIdx).price   : na
topProb    = (topIdx >= 0 and array.size(pools) > 0) ? array.get(pools, topIdx).probPct : na
topIsHigh  = (topIdx >= 0 and array.size(pools) > 0) ? array.get(pools, topIdx).isHigh  : false

// ============================== MAGNET PULL ==================================
biasMag = 0.0
if not na(topPrice) and totalScore > 0
    sameSidePct = topIsHigh ? scoreUp / totalScore * 100.0 : scoreDn / totalScore * 100.0
    biasMag := sameSidePct
biasDir = topIsHigh ? 1 : -1
bias = biasMag * biasDir

distToTopPct = na(topPrice) ? na : (topPrice - close) / close * 100.0

// ============================== HISTORICAL TRACK =============================
topActuallyChanged = not na(prevTopPrice) and not na(topPrice) and (math.abs(prevTopPrice - topPrice) > syminfo.mintick * 5 or prevTopIsHigh != topIsHigh)
topDisappeared     = not na(prevTopPrice) and na(topPrice)

if topActuallyChanged or topDisappeared
    // If prev top was swept THIS bar — record as immediately touched
    initialStatus = prevTopWasSweptThisBar ? 1 : 0
    resBar        = prevTopWasSweptThisBar ? bar_index : na
    array.push(history, HistMagnet.new(prevTopPrice, bar_index, prevTopIsHigh, initialStatus, resBar))
    if prevTopWasSweptThisBar
        touchCount += 1

// Resolve still-pending entries
if array.size(history) > 0
    for i = 0 to array.size(history) - 1
        h = array.get(history, i)
        if h.status == 0
            barsElapsed = bar_index - h.recordedBar
            touched = (h.isHigh and high >= h.price) or (not h.isHigh and low <= h.price)
            if touched
                h.status      := 1
                h.resolvedBar := bar_index
                touchCount    += 1
                array.set(history, i, h)
            else if barsElapsed >= trackingWindow
                h.status      := 2
                h.resolvedBar := bar_index
                expiredCount  += 1
                array.set(history, i, h)

while array.size(history) > maxHistory
    array.shift(history)

prevTopPrice  := topPrice
prevTopIsHigh := topIsHigh

// ============================== VISUALIZATION ================================
intensityTransp(float pct) =>
    int(math.max(10, 85 - pct * 3.5))

if barstate.islast and showZones and array.size(pools) > 0
    atrH = atrV * 0.04
    for i = 0 to array.size(pools) - 1
        p = array.get(pools, i)
        clearPoolDraws(p)
        isTop = (i == topIdx) and showTop
        baseC = p.isHigh ? colHigh : colLow
        zoneC = isTop ? colTop : color.new(baseC, intensityTransp(p.probPct))
        lineC = isTop ? colTop : color.new(baseC, math.max(0, intensityTransp(p.probPct) - 30))
        lineW = isTop ? 3 : 1
        lineStyle = isTop ? line.style_solid : line.style_dotted

        p.bx := box.new(p.barIdx, p.price + atrH, bar_index + zoneExt, p.price - atrH, border_color=color.new(zoneC, 80), bgcolor=color.new(zoneC, 70))
        p.ln := line.new(p.barIdx, p.price, bar_index + zoneExt, p.price, color=lineC, width=lineW, style=lineStyle)

        if showLabels
            eqMark  = p.isEqual ? " ⊜" : ""
            topMark = isTop ? " ◆" : ""
            lblTxt  = str.tostring(p.probPct, "#.0") + "%" + eqMark + topMark
            lblTxtCol = isTop ? color.white : color.new(color.white, 10)
            curLblSize = isTop ? topLblSize : lblSize
            p.lbl := label.new(bar_index + zoneExt, p.price, lblTxt, style=label.style_label_left, color=color.new(zoneC, 40), textcolor=lblTxtCol, size=curLblSize)
        array.set(pools, i, p)

// ============================== MAGNET MARKER + FIELD LINE ===================
if not na(magnetMarker)
    label.delete(magnetMarker)
    magnetMarker := na
if not na(fieldLn)
    line.delete(fieldLn)
    fieldLn := na
if not na(fieldArrow)
    label.delete(fieldArrow)
    fieldArrow := na

if barstate.islast and not na(topPrice)
    if showMagnet
        magnetMarker := label.new(bar_index + zoneExt - 2, topPrice, "🧲", style=label.style_none, textcolor=colTop, size=magSize)

    if showField
        if fieldStyle == "Diagonal"
            endX = bar_index + fieldOffset
            fieldLn := line.new(bar_index, close, endX, topPrice, color=color.new(colField, 20), width=2, style=line.style_dashed)
        else
            fieldLn := line.new(bar_index, close, bar_index, topPrice, color=color.new(colField, 20), width=2, style=line.style_dashed)

// ============================== HISTORICAL MARKERS ===========================
// Place markers at recordedBar (where the magnet actually was), not where
// price arrived. This keeps markers anchored to real price locations.
if barstate.isconfirmed or barstate.islast
    if array.size(histLabels) > 0
        for i = 0 to array.size(histLabels) - 1
            label.delete(array.get(histLabels, i))
        array.clear(histLabels)

    if showHistory and array.size(history) > 0
        for i = 0 to array.size(history) - 1
            h = array.get(history, i)
            if h.status > 0
                mark = h.status == 1 ? "✓" : "✗"
                col  = h.status == 1 ? colHistOK : colHistFail
                lbl  = label.new(h.recordedBar, h.price, mark, style=label.style_none, textcolor=col, size=histSize)
                array.push(histLabels, lbl)

// ============================== DASHBOARD ====================================
var table dash = table.new(dashPos, 2, 14, bgcolor=colDashBg, border_color=colDashBdr, border_width=1, frame_color=colDashBdr, frame_width=1)

table.clear(dash, 0, 0, 1, 13)

if (barstate.isconfirmed or barstate.islast) and showDash
    biasAbs = math.abs(bias)

    biasLabel = "NEUTRAL"
    if biasAbs > 60
        biasLabel := bias > 0 ? "STRONG BULL PULL" : "STRONG BEAR PULL"
    else if biasAbs > 40
        biasLabel := bias > 0 ? "BULL PULL" : "BEAR PULL"

    biasCol = color.rgb(148, 163, 184)
    if bias > 40
        biasCol := colHigh
    else if bias < -40
        biasCol := colLow

    totalRes  = touchCount + expiredCount
    touchRate = totalRes > 0 ? float(touchCount) / float(totalRes) * 100.0 : na

    pendingCount = 0
    if array.size(history) > 0
        for i = 0 to array.size(history) - 1
            h = array.get(history, i)
            if h.status == 0
                pendingCount += 1

    topPriceStr   = na(topPrice) ? "—" : str.tostring(topPrice, format.mintick)
    topProbStr    = na(topProb)  ? "—" : str.tostring(topProb, "#.0") + "%"
    distStr       = na(distToTopPct) ? "—" : str.tostring(distToTopPct, "+#.00") + "%"
    intensityStr  = str.tostring(biasAbs, "#.0") + "%"
    poolsStr      = str.tostring(array.size(pools))
    touchRateStr  = na(touchRate) ? "—" : str.tostring(touchRate, "#.0") + "% (" + str.tostring(touchCount) + "/" + str.tostring(totalRes) + ")"
    logDistStr    = str.tostring(logDistMax, "#.00") + (autoScale ? " (auto)" : " (manual)")
    eqTolStr      = str.tostring(eqTolPct * 100.0, "#.00") + "%" + (autoEqTol ? " (auto)" : " (manual)")

    labelCol = color.rgb(148, 163, 184)
    grayCol  = color.rgb(100, 116, 139)

    row = 0
    table.cell(dash, 0, row, "LIQUIDITY MAGNET", text_color=colTop, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, "v1.6.4", text_color=grayCol, bgcolor=colDashBg, text_size=size.tiny)
    row += 1
    table.cell(dash, 0, row, "Magnet Pull", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, biasLabel, text_color=biasCol, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Pull Intensity", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, intensityStr, text_color=biasCol, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Top Target", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, topPriceStr, text_color=colTop, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Probability", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, topProbStr, text_color=colTop, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Distance", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, distStr, text_color=color.white, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Active Pools", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, poolsStr, text_color=color.white, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Log-Dist Cap", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, logDistStr, text_color=color.white, bgcolor=colDashBg, text_size=dashSize)
    row += 1
    table.cell(dash, 0, row, "Equal Tol", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
    table.cell(dash, 1, row, eqTolStr, text_color=color.white, bgcolor=colDashBg, text_size=dashSize)
    row += 1

    if showStats
        table.cell(dash, 0, row, "Historical Touch %", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
        table.cell(dash, 1, row, touchRateStr, text_color=color.white, bgcolor=colDashBg, text_size=dashSize)
        row += 1
        if pendingCount > 0
            table.cell(dash, 0, row, "Pending", text_color=labelCol, bgcolor=colDashBg, text_size=dashSize)
            table.cell(dash, 1, row, str.tostring(pendingCount), text_color=grayCol, bgcolor=colDashBg, text_size=dashSize)
            row += 1

    if showLegend
        table.cell(dash, 0, row, "Legend", text_color=grayCol, bgcolor=colDashBg, text_size=size.tiny)
        table.cell(dash, 1, row, "◆ Top  ⊜ Equal  ✓ Touched  ✗ Expired", text_color=grayCol, bgcolor=colDashBg, text_size=size.tiny)

// ============================== ALERTS =======================================
newTopMagnet = ta.change(topPrice) != 0
nearTop      = not na(topPrice) and math.abs(close - topPrice) / close < 0.005
sweptTop     = not na(topPrice[1]) and ((topIsHigh[1] and high > topPrice[1]) or (not topIsHigh[1] and low < topPrice[1]))
biasFlipBull = bias > 50 and bias[1] <= 50
biasFlipBear = bias < -50 and bias[1] >= -50

alertcondition(newTopMagnet, "New Top Magnet",        "Liquidity Magnet: new TOP MAGNET identified")
alertcondition(nearTop,      "Price Approaching Top", "Liquidity Magnet: price within 0.5% of TOP MAGNET")
alertcondition(sweptTop,     "Top Magnet Touched",    "Liquidity Magnet: TOP MAGNET touched by price")
alertcondition(biasFlipBull, "Strong Bull Pull",      "Liquidity Magnet: magnet pull turned strongly bullish")
alertcondition(biasFlipBear, "Strong Bear Pull",      "Liquidity Magnet: magnet pull turned strongly bearish")

Overview

Liquidity Magnet detects swing highs, swing lows, and equal high or low clusters, then ranks them using strength, proximity, age decay, and momentum bias. Equal-level clusters receive a bonus, and distance filters can auto-scale by timeframe. Zones, probability labels, a top-magnet marker, a field line, and a dashboard with touch statistics are optional. Historical top magnets can be tracked, but the script does not place trades or define exits.

Details

Published by

VolodymyrFilias on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance, Screeners & dashboards

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.

Topics

indicatorsliquiditymarket-structurepine-script-v6pinescriptsmart-money-conceptstechnical-analysistradingtradingviewvolume-profile

Settings

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

SettingTypeDefaultVariable
Pivot Lookbackint10pivotLen
Max Active Poolsint25maxPools
Min Pool Age (bars)int3minAge
Max Pool Age (bars)int500maxAge
Auto-Scale Tolerance by TimeframebooltrueautoEqTol
Manual Tolerance %float0.15manualEqTol
Equal H/L Bonus Multfloat1.7eqBonus
Auto-Scale by TimeframebooltrueautoScale
Manual Log-Distancefloat0.45manualLogD
Strength Weightfloat1.0wStrength
Proximity Weightfloat1.2wProximity
Age Decay Weightfloat0.6wAge
Show all 40 settings
SettingTypeDefaultVariable
Momentum Bias Weightfloat0.9wMomentum
Show Liquidity ZonesbooltrueshowZones
Highlight TOP MAGNETbooltrueshowTop
Show Probability LabelsbooltrueshowLabels
Show DashboardbooltrueshowDash
Show Touch Stats in DashboardbooltrueshowStats
Show Marker Legend in DashboardbooltrueshowLegend
Zone Extension (bars right)int20zoneExt
Show Historical Top MagnetsboolfalseshowHistory
Tracking Window (bars)int100trackingWindow
Max Historical Markersint100maxHistory
Historical Marker SizestringTinyhistMarkerSize
Show Magnet Marker on TOP zonebooltrueshowMagnet
Magnet Marker SizestringLargemagnetSize
Show Magnet Field LinebooltrueshowField
Field Line StylestringDiagonalfieldStyle
Diagonal Offset (bars right)int20fieldOffset
Dashboard PositionstringTop RightdashPosStr
Dashboard Text SizestringSmalldashSizeStr
Zone Label Text SizestringSmalllblSizeStr
TOP Label Text SizestringNormaltopLblSizeStr
Upside Liquiditycolor#ef4444colHigh
Downside Liquiditycolor#22c55ecolLow
TOP MAGNETcolor#f59e0bcolTop
Magnet Field Linecolor#f59e0bcolField
Historical: Touchedcolor#22c55ecolHistOK
Historical: Expiredcolor#94a3b8colHistFail
Dashboard BGcolor#0F172AcolDashBg
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

575 stars

This overlay indicator scans a pasted list of symbols for ICT liquidity sweep patterns using prior swing sweeps. The screening method looks back one, two, or three swings and can list long and short signals in a results table with adjustable rows, position, an

Indicator v5 SMC / ICT / price actionScreeners & dashboards
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Liquidity Sweep Pattern Scanner

Golden Bullet Strategy

by TradersPost
218 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
56 stars

This indicator identifies fractal-based pivot highs and lows to map out market structure directly on the price chart. It detects bullish and bearish structure breaks, automatically labeling Break of Structure (BOS) and Change of Character (ChoCH) events when p

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · None stated GitHub repository of Market Structure (Fractal)
53 stars

Apex Trend & Liquidity Master calculates a trend cloud using customizable moving averages expanded by a volatility multiplier directly on the main price chart. It identifies dynamic support and resistance liquidity zones from swing high and low pivots, extendi

Indicator v6 TrendSupport & resistance
Updated 2 months ago · MPL-2.0 GitHub repository of Apex Trend & Liquidity Master
23 stars

MTF State of Delivery tracks lower timeframe and higher timeframe orderflow delivery states by identifying bullish and bearish delivery levels across timeframes. It plots confirmed and pending orderflow lines directly on the price chart and provides an optiona

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 3 weeks ago · Custom licence GitHub repository of MTF State of Delivery by traderprimez

More indicators

Trade Your Charts With a Regulated Broker

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

Compare brokers