Indicator · Pine Script v6 · On the chart

ICT SSL Premium & Discount

by hungpixi

Calculates and plots ICT concepts on the price chart, including Buy-Side Liquidity (BSL) and Sell-Side Liquidity (SSL) levels identified through fractal swings.

View on GitHub

GitHub stars3on the repository
Pine versionv6current
Lines of code668
Settings36inputs
LicenceNone statedfile: MPL-2.0
Last update7 months ago17 Mar 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

668 lines of Pine Script v6 from ICT_SSL_PremiumDiscount.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

ICT_SSL_PremiumDiscount.pine
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0
// https://mozilla.org/MPL/2.0/
// © hungpixi — Comarai (https://comarai.com)
// Reference: jbondata/pinescript-indicator-suite, FibAlgo ICT Premium & Discount
//@version=6
indicator("[hungpixi] ICT SSL Premium & Discount", overlay=true, max_lines_count=500, max_boxes_count=500, max_labels_count=500)

// ─── INPUTS ───────────────────────────────────────────────────────────────────
// Display Range
grpDisplay     = "📐 Display Range"
int lookbackBars = input.int(2000, "Show Last N Bars", minval=100, maxval=10000, step=100, group=grpDisplay, tooltip="Chỉ hiển thị indicator trong N bars gần nhất. Giảm xuống để chart sạch hơn.")

// Fractal / Swing Detection
grpSwing       = "🔍 Swing Detection"
int leftBars   = input.int(1, "Fractal Bars (each side)", minval=1, maxval=10, group=grpSwing, tooltip="Bars mỗi bên để xác nhận fractal. 3 = 7-bar fractal.")
int maxSwings  = input.int(5, "Max Swings to Show", minval=3, maxval=20, group=grpSwing)

// SSL / BSL Display
grpLiq         = "💧 Liquidity Levels"
bool showBSL   = input.bool(true, "Show BSL (Buy-Side Liquidity)", group=grpLiq)
bool showSSL   = input.bool(true, "Show SSL (Sell-Side Liquidity)", group=grpLiq)
color colBSL   = input.color(color.new(#089981, 0), "BSL Color", group=grpLiq)
color colSSL   = input.color(color.new(#F23645, 0), "SSL Color", group=grpLiq)
int lineWidth  = input.int(1, "Line Width", minval=1, maxval=4, group=grpLiq)

// Sweep Detection
grpSweep       = "⚡ Sweep Detection"
bool showSweep = input.bool(true, "Show Sweep Labels", group=grpSweep)
color colSweepBull = input.color(color.new(#089981, 0), "Bullish Sweep Color", group=grpSweep)
color colSweepBear = input.color(color.new(#F23645, 0), "Bearish Sweep Color", group=grpSweep)

// Premium / Discount Zones
grpPD          = "📊 Premium & Discount Zones"
bool showPD    = input.bool(true, "Show Premium / Discount", group=grpPD)
int pdLookback = input.int(50, "Dealing Range Lookback", minval=10, maxval=200, group=grpPD)
color colPrem  = input.color(color.new(#F23645, 88), "Premium Zone", group=grpPD)
color colDisc  = input.color(color.new(#089981, 88), "Discount Zone", group=grpPD)
color colEQ    = input.color(color.new(#787B86, 30), "Equilibrium Line", group=grpPD)

// OTE Zone
grpOTE         = "🎯 OTE Zone"
bool showOTE   = input.bool(true, "Show OTE (62%-79%)", group=grpOTE)
color colOTE   = input.color(color.new(#FF9800, 85), "OTE Zone Color", group=grpOTE)

// ─── ENTRY / SL / TP SETTINGS ─────────────────────────────────────────────────
grpEntry       = "🎯 Entry Signals (SL/TP)"
bool showEntry = input.bool(true, "Show Entry Signals", group=grpEntry)
bool requireOTE = input.bool(false, "Require OTE Zone for Entry", group=grpEntry, tooltip="Chỉ hiện signal khi sweep xảy ra trong OTE zone — setup chất lượng cao hơn nhưng ít tín hiệu hơn.")
float rrRatio  = input.float(3.0, "Risk:Reward Ratio", minval=1.0, maxval=10.0, step=0.5, group=grpEntry, tooltip="TP cố định theo R:R. 3.0 = TP gấp 3 lần SL. 3R là đã win.")
int maxTrades  = input.int(50, "Max Trades Shown", minval=5, maxval=100, group=grpEntry)
bool showTP2   = input.bool(true, "Show TP2 (at EQ)", group=grpEntry, tooltip="TP2 tại đường Equilibrium. Partial close tại đây.")
bool showTP3   = input.bool(false, "Show TP3 (opposite zone)", group=grpEntry, tooltip="TP3 tại vùng ngược lại (Premium→Discount hoặc ngược lại).")
int cooldownBars = input.int(5, "Cooldown (bars)", minval=1, maxval=30, group=grpEntry, tooltip="Chờ N bars sau mỗi signal trước khi cho signal tiếp.")
bool oneAtATime = input.bool(true, "One Trade at a Time", group=grpEntry, tooltip="Phải chạm SL hoặc TP mới cho signal mới.")
color colBuy   = input.color(color.new(#00E676, 0), "Buy Signal Color", group=grpEntry)
color colSell  = input.color(color.new(#FF1744, 0), "Sell Signal Color", group=grpEntry)
color colSL    = input.color(color.new(#FF5252, 0), "SL Line Color", group=grpEntry)
color colTP    = input.color(color.new(#69F0AE, 0), "TP Line Color", group=grpEntry)

// SL Structure Settings
grpSL          = "🛡️ SL Settings"
int slLookback = input.int(14, "SL Structure Lookback (bars)", minval=1, maxval=50, group=grpSL, tooltip="Tìm structural low/high trong N bars gần nhất để đặt SL. 14 = nhìn lại 14 cây nến.")
float minSLDist = input.float(5.0, "Min SL Distance (price)", minval=0.1, step=1.0, group=grpSL, tooltip="Khoảng cách SL tối thiểu tính theo giá. VD: 5.0 cho XAUUSD = ít nhất $5.")
float maxSLDist = input.float(30.0, "Max SL Distance (price)", minval=1.0, step=5.0, group=grpSL, tooltip="Khoảng cách SL tối đa. Nếu SL xa hơn → clamp về max. VD: 30.0 cho XAUUSD.")
string slMethod = input.string("Sweep Wick", "SL Method", options=["Sweep Wick", "Structure", "Both (farthest)"], group=grpSL, tooltip="Sweep Wick = wick của sweep candle (khuyên dùng). Structure = low/high N bars. Both = lấy cái xa nhất.")

// Dashboard
grpDash        = "📋 Dashboard"
bool showDash  = input.bool(true, "Show Dashboard", group=grpDash)
string dashPos = input.string("Top Right", "Position", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group=grpDash)

// ─── DISPLAY RANGE CHECK ──────────────────────────────────────────────────────
bool inRange = bar_index >= last_bar_index - lookbackBars

// ─── FRACTAL DETECTION ────────────────────────────────────────────────────────
float highestInRange = ta.highest(high, leftBars * 2 + 1)
float lowestInRange  = ta.lowest(low, leftBars * 2 + 1)
bool isFractalHigh   = high[leftBars] == highestInRange and high[leftBars] > high[leftBars - 1] and high[leftBars] > high[leftBars + 1]
bool isFractalLow    = low[leftBars] == lowestInRange and low[leftBars] < low[leftBars - 1] and low[leftBars] < low[leftBars + 1]

// ─── LIQUIDITY LEVEL ARRAYS ──────────────────────────────────────────────────
// BSL arrays (swing highs)
var array<line>  linesBSL    = array.new_line()
var array<int>   barsBSL     = array.new_int()
var array<float> pricesBSL   = array.new_float()
var array<bool>  brokenBSL   = array.new_bool()
var array<bool>  sweptBSL    = array.new_bool()

// SSL arrays (swing lows)
var array<line>  linesSSL    = array.new_line()
var array<int>   barsSSL     = array.new_int()
var array<float> pricesSSL   = array.new_float()
var array<bool>  brokenSSL   = array.new_bool()
var array<bool>  sweptSSL    = array.new_bool()

int fractalBar = bar_index - leftBars

// ─── PUSH NEW SWING HIGHS (BSL) ──────────────────────────────────────────────
if isFractalHigh and barstate.isconfirmed and showBSL and inRange
    float swingPrice = high[leftBars]
    line ln = line.new(fractalBar, swingPrice, fractalBar + 20, swingPrice, xloc.bar_index, color=colBSL, width=lineWidth, style=line.style_solid)
    array.push(linesBSL, ln)
    array.push(barsBSL, fractalBar)
    array.push(pricesBSL, swingPrice)
    array.push(brokenBSL, false)
    array.push(sweptBSL, false)
    // Trim oldest
    if array.size(linesBSL) > maxSwings
        line.delete(array.shift(linesBSL))
        array.shift(barsBSL)
        array.shift(pricesBSL)
        array.shift(brokenBSL)
        array.shift(sweptBSL)

// ─── PUSH NEW SWING LOWS (SSL) ──────────────────────────────────────────────
if isFractalLow and barstate.isconfirmed and showSSL and inRange
    float swingPrice = low[leftBars]
    line ln = line.new(fractalBar, swingPrice, fractalBar + 20, swingPrice, xloc.bar_index, color=colSSL, width=lineWidth, style=line.style_solid)
    array.push(linesSSL, ln)
    array.push(barsSSL, fractalBar)
    array.push(pricesSSL, swingPrice)
    array.push(brokenSSL, false)
    array.push(sweptSSL, false)
    // Trim oldest
    if array.size(linesSSL) > maxSwings
        line.delete(array.shift(linesSSL))
        array.shift(barsSSL)
        array.shift(pricesSSL)
        array.shift(brokenSSL)
        array.shift(sweptSSL)

// ─── PREMIUM & DISCOUNT ZONES (calculated before sweep detection) ────────────
float rangeHigh = ta.highest(high, pdLookback)
float rangeLow  = ta.lowest(low, pdLookback)
float eq        = (rangeHigh + rangeLow) / 2.0

// OTE zone
float rangeSize   = rangeHigh - rangeLow
float oteBuyHigh  = rangeLow + rangeSize * 0.382   // 38.2% from bottom = 61.8% retracement from top
float oteBuyLow   = rangeLow + rangeSize * 0.21    // 21% from bottom = 79% retracement from top
float oteSellHigh = rangeLow + rangeSize * 0.79    // 79% from bottom
float oteSellLow  = rangeLow + rangeSize * 0.618   // 61.8% from bottom

bool inPremium  = close > eq
bool inDiscount = close <= eq
bool inOTEBuy   = close >= oteBuyLow and close <= oteBuyHigh
bool inOTESell  = close >= oteSellLow and close <= oteSellHigh

// ─── TRADE STATE ─────────────────────────────────────────────────────────────
var array<line>  tradeSLLines  = array.new_line()
var array<line>  tradeTP1Lines = array.new_line()
var array<line>  tradeTP2Lines = array.new_line()
var array<line>  tradeTP3Lines = array.new_line()
var array<label> tradeLabels   = array.new_label()
var array<int>   tradeBars     = array.new_int()
var array<float> tradeEntries  = array.new_float()
var array<float> tradeSLs      = array.new_float()
var array<float> tradeTP1s     = array.new_float()
var array<int>   tradeDirs     = array.new_int() // 1=buy, -1=sell
var array<bool>  tradeActive   = array.new_bool()

var int totalBuys      = 0
var int totalSells     = 0
var int totalTPHits    = 0
var int totalSLHits    = 0
var int lastSignalBar  = 0
var bool hasActiveTrade = false
var float activeSL     = na
var float activeTP     = na
var int   activeDir    = 0

// ─── PENDING ENTRY STATE (retest model) ──────────────────────────────────────
// Khi sweep xảy ra → lưu "pending order" tại swept level
// Bar sau nếu giá chạm lại level → trigger entry (như limit order)
var bool  pendingBuy     = false
var float pendBuyEntry   = na   // entry = SSL level bị swept
var float pendBuySL      = na   // SL = wick low của sweep candle
var int   pendBuyBar     = 0    // bar khi sweep xảy ra
var int   pendBuyExpiry  = 10   // hết hạn sau N bars

var bool  pendingSell    = false
var float pendSellEntry  = na   // entry = BSL level bị swept
var float pendSellSL     = na   // SL = wick high của sweep candle
var int   pendSellBar    = 0
var int   pendSellExpiry = 10

// ─── HELPER: Create trade visualization ──────────────────────────────────────
f_drawTrade(int dir, float entry, float sl, float tp1, float tp2, float tp3) =>
    // Colors
    color sigCol = dir == 1 ? colBuy : colSell
    string sigIcon = dir == 1 ? "🟢" : "🔴"
    string sigDir = dir == 1 ? "BUY" : "SELL"
    float riskPips = math.abs(entry - sl)
    float rrActual = riskPips > 0 ? math.abs(tp1 - entry) / riskPips : 0

    // Entry label — compact
    label entryLbl = label.new(bar_index, dir == 1 ? low : high, 
         sigIcon + " " + sigDir + " @ " + str.tostring(entry, format.mintick),
         color=sigCol, textcolor=color.white, 
         style=dir == 1 ? label.style_label_up : label.style_label_down, 
         size=size.tiny,
         tooltip="Entry: " + str.tostring(entry, format.mintick) + "\nSL: " + str.tostring(sl, format.mintick) + "\nTP: " + str.tostring(tp1, format.mintick) + "\nR:R " + str.tostring(rrActual, "#.#"))

    // SL line + label
    line slLine = line.new(bar_index, sl, bar_index + 15, sl, 
         color=colSL, width=1, style=line.style_dashed)
    label.new(bar_index + 15, sl, "SL " + str.tostring(sl, format.mintick),
         color=color.new(colSL, 80), textcolor=colSL,
         style=label.style_label_left, size=size.tiny)

    // TP1 line + label — prominent target
    line tp1Line = line.new(bar_index, tp1, bar_index + 15, tp1, 
         color=colTP, width=2, style=line.style_dashed)
    label.new(bar_index + 15, tp1, "TP " + str.tostring(rrActual, "#.#") + "R  " + str.tostring(tp1, format.mintick),
         color=color.new(colTP, 80), textcolor=colTP,
         style=label.style_label_left, size=size.tiny)

    // TP2 line at EQ (if enabled)
    line tp2Line = na
    if showTP2 and not na(tp2)
        tp2Line := line.new(bar_index, tp2, bar_index + 10, tp2, 
             color=color.new(#42A5F5, 40), width=1, style=line.style_dotted)

    // TP3 line at opposite zone (if enabled)
    line tp3Line = na
    if showTP3 and not na(tp3)
        tp3Line := line.new(bar_index, tp3, bar_index + 10, tp3, 
             color=color.new(#AB47BC, 40), width=1, style=line.style_dotted)

    // Entry-to-SL zone fill (subtle red)
    box.new(bar_index, entry, bar_index + 15, sl, 
         border_color=color.new(colSL, 100), bgcolor=color.new(colSL, 94), 
         border_width=0)

    // Entry-to-TP1 zone fill (subtle green)
    box.new(bar_index, entry, bar_index + 15, tp1, 
         border_color=color.new(colTP, 100), bgcolor=color.new(colTP, 94), 
         border_width=0)

    // Store trade data
    array.push(tradeSLLines, slLine)
    array.push(tradeTP1Lines, tp1Line)
    array.push(tradeTP2Lines, tp2Line)
    array.push(tradeTP3Lines, tp3Line)
    array.push(tradeLabels, entryLbl)
    array.push(tradeBars, bar_index)
    array.push(tradeEntries, entry)
    array.push(tradeSLs, sl)
    array.push(tradeTP1s, tp1)
    array.push(tradeDirs, dir)
    array.push(tradeActive, true)

    // Trim oldest trades
    if array.size(tradeLabels) > maxTrades
        line.delete(array.shift(tradeSLLines))
        line.delete(array.shift(tradeTP1Lines))
        line.delete(array.shift(tradeTP2Lines))
        line.delete(array.shift(tradeTP3Lines))
        label.delete(array.shift(tradeLabels))
        array.shift(tradeBars)
        array.shift(tradeEntries)
        array.shift(tradeSLs)
        array.shift(tradeTP1s)
        array.shift(tradeDirs)
        array.shift(tradeActive)

// ─── UPDATE BSL LEVELS & SWEEP DETECTION ─────────────────────────────────────
var array<label> sweepLabels = array.new_label()  // managed sweep labels
var array<label> pendLabels  = array.new_label()  // managed pending labels
var array<line>  pendLines   = array.new_line()   // managed pending lines

var int bslSweepCount = 0
bool bslSweepNow = false
float bslSweepPrice = na

for i = 0 to array.size(linesBSL) - 1
    if i >= array.size(linesBSL)
        break
    line  ln       = array.get(linesBSL, i)
    float priceI   = array.get(pricesBSL, i)
    int   barI     = array.get(barsBSL, i)
    bool  wasBroken = array.get(brokenBSL, i)
    bool  wasSwept  = array.get(sweptBSL, i)

    if not wasBroken
        line.set_xy2(ln, bar_index, priceI)
        if high >= priceI and close < priceI and not wasSwept
            array.set(sweptBSL, i, true)
            bslSweepCount += 1
            bslSweepNow := true
            bslSweepPrice := priceI
            if showSweep
                label swLbl = label.new(bar_index, high, "▼", color=colSweepBear, textcolor=color.white, style=label.style_label_down, size=size.tiny, tooltip="BSL Sweep @ " + str.tostring(priceI, format.mintick))
                array.push(sweepLabels, swLbl)
                if array.size(sweepLabels) > maxTrades * 2
                    label.delete(array.shift(sweepLabels))
        if close > priceI
            array.set(brokenBSL, i, true)
            line.set_xy2(ln, bar_index, priceI)
            line.set_style(ln, line.style_dashed)
    if wasBroken
        line.set_style(ln, line.style_dashed)

// ─── UPDATE SSL LEVELS & SWEEP DETECTION ─────────────────────────────────────
var int sslSweepCount = 0
bool sslSweepNow = false
float sslSweepPrice = na

for i = 0 to array.size(linesSSL) - 1
    if i >= array.size(linesSSL)
        break
    line  ln       = array.get(linesSSL, i)
    float priceI   = array.get(pricesSSL, i)
    int   barI     = array.get(barsSSL, i)
    bool  wasBroken = array.get(brokenSSL, i)
    bool  wasSwept  = array.get(sweptSSL, i)

    if not wasBroken
        line.set_xy2(ln, bar_index, priceI)
        if low <= priceI and close > priceI and not wasSwept
            array.set(sweptSSL, i, true)
            sslSweepCount += 1
            sslSweepNow := true
            sslSweepPrice := priceI
            if showSweep
                label swLbl = label.new(bar_index, low, "▲", color=colSweepBull, textcolor=color.white, style=label.style_label_up, size=size.tiny, tooltip="SSL Sweep @ " + str.tostring(priceI, format.mintick))
                array.push(sweepLabels, swLbl)
                if array.size(sweepLabels) > maxTrades * 2
                    label.delete(array.shift(sweepLabels))
        if close < priceI
            array.set(brokenSSL, i, true)
            line.set_xy2(ln, bar_index, priceI)
            line.set_style(ln, line.style_dashed)
    if wasBroken
        line.set_style(ln, line.style_dashed)

// ─── CHECK ACTIVE TRADE: SL/TP HIT ──────────────────────────────────────────
var array<label> hitLabels = array.new_label()

if hasActiveTrade
    bool slHit = activeDir == 1 ? low <= activeSL : high >= activeSL
    bool tpHit = activeDir == 1 ? high >= activeTP : low <= activeTP
    if slHit
        hasActiveTrade := false
        totalSLHits += 1
        label hitLbl = label.new(bar_index, activeDir == 1 ? low : high, "✖",
             color=color.new(#FF5252, 0), textcolor=color.white,
             style=activeDir == 1 ? label.style_label_up : label.style_label_down,
             size=size.tiny)
        array.push(hitLabels, hitLbl)
        if array.size(hitLabels) > maxTrades
            label.delete(array.shift(hitLabels))
        activeSL := na
        activeTP := na
        activeDir := 0
    else if tpHit
        hasActiveTrade := false
        totalTPHits += 1
        label hitLbl = label.new(bar_index, activeDir == 1 ? high : low, "✔",
             color=color.new(#69F0AE, 0), textcolor=color.white,
             style=activeDir == 1 ? label.style_label_down : label.style_label_up,
             size=size.tiny)
        array.push(hitLabels, hitLbl)
        if array.size(hitLabels) > maxTrades
            label.delete(array.shift(hitLabels))
        activeSL := na
        activeTP := na
        activeDir := 0

// ─── HELPER: Find nearest opposite liquidity level ───────────────────────────
f_nearestBSL(float abovePrice) =>
    float nearest = na
    for i = 0 to array.size(pricesBSL) - 1
        if i >= array.size(pricesBSL)
            break
        float p = array.get(pricesBSL, i)
        bool broken = array.get(brokenBSL, i)
        if not broken and p > abovePrice
            if na(nearest) or p < nearest
                nearest := p
    nearest

f_nearestSSL(float belowPrice) =>
    float nearest = na
    for i = 0 to array.size(pricesSSL) - 1
        if i >= array.size(pricesSSL)
            break
        float p = array.get(pricesSSL, i)
        bool broken = array.get(brokenSSL, i)
        if not broken and p < belowPrice
            if na(nearest) or p > nearest
                nearest := p
    nearest

// ─── ENTRY SIGNAL: RETEST MODEL ─────────────────────────────────────────────
// ICT Retest Entry Model:
//
// 1. Sweep xảy ra → tạo "pending limit order" tại swept level
// 2. Chờ giá retest lại level đó trên bar sau → trigger entry
// 3. Nếu giá không retest trong N bars → pending hết hạn
//
// BUY:  SSL Sweep → pending buy tại SSL level
//       Khi giá pullback xuống chạm SSL level → Entry = SSL level
//       SL = wick low của sweep candle
//       TP = nearest BSL above
//
// SELL: BSL Sweep → pending sell tại BSL level
//       Khi giá pullback lên chạm BSL level → Entry = BSL level
//       SL = wick high của sweep candle
//       TP = nearest SSL below

bool cooldownOk = bar_index - lastSignalBar >= cooldownBars
bool tradeSlotOk = oneAtATime ? not hasActiveTrade : true

// ── Step 1: Create pending orders when sweep detected ──
if showEntry and barstate.isconfirmed and cooldownOk and tradeSlotOk and inRange

    // SSL Sweep in Discount → create pending BUY at SSL level
    if sslSweepNow and inDiscount
        bool oteOk = requireOTE ? inOTEBuy : true
        if oteOk and not na(sslSweepPrice)
            pendingBuy   := true
            pendBuyEntry := sslSweepPrice  // entry = tại swept SSL level
            // SL calculation based on method
            float wickSL = low  // sweep candle wick
            float structSL = ta.lowest(low, slLookback)  // structural low over N bars
            float rawSL = slMethod == "Structure" ? structSL : slMethod == "Sweep Wick" ? wickSL : math.min(wickSL, structSL)
            // Enforce min/max SL distance
            float distFromEntry = pendBuyEntry - rawSL
            float clampedSL = distFromEntry < minSLDist ? pendBuyEntry - minSLDist : distFromEntry > maxSLDist ? pendBuyEntry - maxSLDist : rawSL
            pendBuySL := clampedSL > 0 ? clampedSL : pendBuyEntry - minSLDist  // safety: SL must be > 0
            pendBuyBar := bar_index
            if showSweep
                line pLine = line.new(bar_index, sslSweepPrice, bar_index + pendBuyExpiry, sslSweepPrice,
                     color=color.new(colBuy, 50), width=1, style=line.style_dotted)
                label pLbl = label.new(bar_index, sslSweepPrice, "⏳ BUY @ " + str.tostring(sslSweepPrice, format.mintick),
                     color=color.new(colBuy, 70), textcolor=color.white,
                     style=label.style_label_right, size=size.tiny)
                array.push(pendLines, pLine)
                array.push(pendLabels, pLbl)
                if array.size(pendLabels) > maxTrades
                    line.delete(array.shift(pendLines))
                    label.delete(array.shift(pendLabels))

    // BSL Sweep in Premium → create pending SELL at BSL level
    if bslSweepNow and inPremium
        bool oteOk = requireOTE ? inOTESell : true
        if oteOk and not na(bslSweepPrice)
            pendingSell   := true
            pendSellEntry := bslSweepPrice  // entry = tại swept BSL level
            // SL calculation based on method
            float wickSL = high  // sweep candle wick
            float structSL = ta.highest(high, slLookback)  // structural high over N bars
            float rawSL = slMethod == "Structure" ? structSL : slMethod == "Sweep Wick" ? wickSL : math.max(wickSL, structSL)
            // Enforce min/max SL distance
            float distFromEntry = rawSL - pendSellEntry
            float clampedSL = distFromEntry < minSLDist ? pendSellEntry + minSLDist : distFromEntry > maxSLDist ? pendSellEntry + maxSLDist : rawSL
            pendSellSL := clampedSL
            pendSellBar := bar_index
            if showSweep
                line pLine = line.new(bar_index, bslSweepPrice, bar_index + pendSellExpiry, bslSweepPrice,
                     color=color.new(colSell, 50), width=1, style=line.style_dotted)
                label pLbl = label.new(bar_index, bslSweepPrice, "⏳ SELL @ " + str.tostring(bslSweepPrice, format.mintick),
                     color=color.new(colSell, 70), textcolor=color.white,
                     style=label.style_label_right, size=size.tiny)
                array.push(pendLines, pLine)
                array.push(pendLabels, pLbl)
                if array.size(pendLabels) > maxTrades
                    line.delete(array.shift(pendLines))
                    label.delete(array.shift(pendLabels))

// ── Step 2: Check pending orders for retest fill ──
// Pending BUY: giá pullback xuống chạm pendBuyEntry → fill
if pendingBuy and not hasActiveTrade
    // Expired?
    if bar_index - pendBuyBar > pendBuyExpiry
        pendingBuy := false
        pendBuyEntry := na
        pendBuySL := na
    // SL hit trước khi fill? (giá phá qua wick low)
    else if not na(pendBuySL) and low < pendBuySL
        pendingBuy := false
        pendBuyEntry := na
        pendBuySL := na
    // FILL: giá chạm lại SSL level (low <= entry level)
    else if inRange and low <= pendBuyEntry and not na(pendBuySL)
        float entryPrice = pendBuyEntry
        float slPrice = pendBuySL
        float risk = entryPrice - slPrice
        // Sanity: risk must be positive and SL must be reasonable
        bool slValid = risk > 0 and risk >= minSLDist and risk <= maxSLDist and slPrice > 0
        // TP = fixed R:R (3R default)
        float tp1Price = entryPrice + risk * rrRatio
        float tp2Price = showTP2 ? eq : na
        float tp3Price = showTP3 ? rangeHigh : na

        if slValid
            f_drawTrade(1, entryPrice, slPrice, tp1Price, tp2Price, tp3Price)
            totalBuys += 1
            lastSignalBar := bar_index
            hasActiveTrade := true
            activeSL := slPrice
            activeTP := tp1Price
            activeDir := 1
        pendingBuy := false
        pendBuyEntry := na
        pendBuySL := na

// Pending SELL: giá pullback lên chạm pendSellEntry → fill
if pendingSell and not hasActiveTrade
    // Expired?
    if bar_index - pendSellBar > pendSellExpiry
        pendingSell := false
        pendSellEntry := na
        pendSellSL := na
    // SL hit trước khi fill? (giá phá qua wick high)
    else if not na(pendSellSL) and high > pendSellSL
        pendingSell := false
        pendSellEntry := na
        pendSellSL := na
    // FILL: giá chạm lại BSL level (high >= entry level)
    else if inRange and high >= pendSellEntry and not na(pendSellSL)
        float entryPrice = pendSellEntry
        float slPrice = pendSellSL
        float risk = slPrice - entryPrice
        // Sanity: risk must be positive and SL must be reasonable
        bool slValid = risk > 0 and risk >= minSLDist and risk <= maxSLDist
        // TP = fixed R:R (3R default)
        float tp1Price = entryPrice - risk * rrRatio
        float tp2Price = showTP2 ? eq : na
        float tp3Price = showTP3 ? rangeLow : na

        if slValid
            f_drawTrade(-1, entryPrice, slPrice, tp1Price, tp2Price, tp3Price)
            totalSells += 1
            lastSignalBar := bar_index
            hasActiveTrade := true
            activeSL := slPrice
            activeTP := tp1Price
            activeDir := -1
        pendingSell := false
        pendSellEntry := na
        pendSellSL := na

// ─── ZONE VISUALIZATION ──────────────────────────────────────────────────────
// (bgcolor removed — using box zones only for cleaner display)

// EQ line
plot(showPD and inRange ? eq : na, "Equilibrium (50%)", color=colEQ, linewidth=1, style=plot.style_line)

// ─── PREMIUM & DISCOUNT ZONE BOXES ───────────────────────────────────────────
var box premBox  = na
var box discBox  = na
var box oteBoxBuy  = na
var box oteBoxSell = na

if showPD and inRange
    box.delete(premBox)
    box.delete(discBox)
    premBox := box.new(bar_index - pdLookback, rangeHigh, bar_index, eq, 
         border_color=color.new(#F23645, 80), bgcolor=color.new(#F23645, 96), 
         border_width=1, border_style=line.style_dotted)
    discBox := box.new(bar_index - pdLookback, eq, bar_index, rangeLow, 
         border_color=color.new(#089981, 80), bgcolor=color.new(#089981, 96), 
         border_width=1, border_style=line.style_dotted)

if showOTE and inRange
    box.delete(oteBoxBuy)
    box.delete(oteBoxSell)
    oteBoxBuy := box.new(bar_index - pdLookback, oteBuyHigh, bar_index, oteBuyLow, 
         border_color=color.new(#FF9800, 70), bgcolor=color.new(#FF9800, 94), 
         border_width=1, border_style=line.style_dashed)
    oteBoxSell := box.new(bar_index - pdLookback, oteSellHigh, bar_index, oteSellLow, 
         border_color=color.new(#FF9800, 70), bgcolor=color.new(#FF9800, 94), 
         border_width=1, border_style=line.style_dashed)

// ─── ZONE LABELS ─────────────────────────────────────────────────────────────
var label lblPrem = na
var label lblDisc = na
var label lblEQ   = na
var label lblOTEBuy  = na
var label lblOTESell = na

if showPD and inRange
    label.delete(lblPrem)
    label.delete(lblDisc)
    label.delete(lblEQ)
    lblPrem := label.new(bar_index + 10, (rangeHigh + eq) / 2, "PREMIUM", 
         color=color.new(color.white, 100), textcolor=color.new(#F23645, 50), 
         style=label.style_label_left, size=size.tiny)
    lblDisc := label.new(bar_index + 10, (rangeLow + eq) / 2, "DISCOUNT", 
         color=color.new(color.white, 100), textcolor=color.new(#089981, 50), 
         style=label.style_label_left, size=size.tiny)
    lblEQ := label.new(bar_index + 10, eq, "EQ", 
         color=color.new(color.white, 100), textcolor=color.new(colEQ, 30), 
         style=label.style_label_left, size=size.tiny)

if showOTE and inRange
    label.delete(lblOTEBuy)
    label.delete(lblOTESell)
    lblOTEBuy := label.new(bar_index + 10, (oteBuyHigh + oteBuyLow) / 2, "OTE Buy", 
         color=color.new(color.white, 100), textcolor=color.new(#FF9800, 40), 
         style=label.style_label_left, size=size.tiny)
    lblOTESell := label.new(bar_index + 10, (oteSellHigh + oteSellLow) / 2, "OTE Sell", 
         color=color.new(color.white, 100), textcolor=color.new(#FF9800, 40), 
         style=label.style_label_left, size=size.tiny)

// ─── DASHBOARD TABLE ─────────────────────────────────────────────────────────
if showDash
    var table dash = na
    tablePos = switch dashPos
        "Top Right"    => position.top_right
        "Top Left"     => position.top_left
        "Bottom Right"  => position.bottom_right
        "Bottom Left"   => position.bottom_left
        => position.top_right

    if barstate.islast
        int totalTrades = totalTPHits + totalSLHits
        float winRate = totalTrades > 0 ? totalTPHits / totalTrades * 100 : 0

        dash := table.new(tablePos, 2, 9, bgcolor=color.new(#1e222d, 10), border_color=color.new(#363a45, 0), border_width=1, frame_color=color.new(#363a45, 0), frame_width=2)

        // Header
        table.cell(dash, 0, 0, "ICT SSL P&D", text_color=color.white, text_size=size.small, bgcolor=color.new(#2962FF, 20))
        table.cell(dash, 1, 0, "Status", text_color=color.white, text_size=size.small, bgcolor=color.new(#2962FF, 20))

        // Zone
        string zoneText = inPremium ? "PREMIUM ⬆" : "DISCOUNT ⬇"
        color zoneColor = inPremium ? color.new(#F23645, 20) : color.new(#089981, 20)
        table.cell(dash, 0, 1, "Zone", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 1, zoneText, text_color=color.white, text_size=size.small, bgcolor=zoneColor)

        // OTE
        string oteText = inOTEBuy ? "OTE BUY 🎯" : inOTESell ? "OTE SELL 🎯" : "—"
        color oteColor = inOTEBuy ? color.new(#FF9800, 30) : inOTESell ? color.new(#FF9800, 30) : color.new(#363a45, 0)
        table.cell(dash, 0, 2, "OTE", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 2, oteText, text_color=color.white, text_size=size.small, bgcolor=oteColor)

        // EQ
        table.cell(dash, 0, 3, "EQ", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 3, str.tostring(eq, format.mintick), text_color=color.white, text_size=size.small)

        // Sweeps
        table.cell(dash, 0, 4, "Sweeps", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 4, "SSL:" + str.tostring(sslSweepCount) + " BSL:" + str.tostring(bslSweepCount), text_color=color.white, text_size=size.small)

        // Trade header
        table.cell(dash, 0, 5, "──────", text_color=color.new(#363a45, 0), text_size=size.small, bgcolor=color.new(#2962FF, 20))
        table.cell(dash, 1, 5, "Trades", text_color=color.white, text_size=size.small, bgcolor=color.new(#2962FF, 20))

        // Signals
        table.cell(dash, 0, 6, "Signals", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 6, "🟢" + str.tostring(totalBuys) + " 🔴" + str.tostring(totalSells), text_color=color.white, text_size=size.small)

        // Results
        table.cell(dash, 0, 7, "Results", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 7, "✔" + str.tostring(totalTPHits) + " ✖" + str.tostring(totalSLHits), text_color=color.white, text_size=size.small, bgcolor=winRate >= 50 ? color.new(#089981, 30) : totalTrades > 0 ? color.new(#F23645, 30) : color.new(#363a45, 0))

        // Win Rate
        table.cell(dash, 0, 8, "Win %", text_color=color.gray, text_size=size.small)
        table.cell(dash, 1, 8, totalTrades > 0 ? str.tostring(winRate, "#.#") + "%" : "—", text_color=color.white, text_size=size.small)

// ─── ALERTS ──────────────────────────────────────────────────────────────────
alertcondition(ta.crossover(close, eq), "Price crossed above EQ (entering Premium)", "Price entered PREMIUM zone — EQ crossover")
alertcondition(ta.crossunder(close, eq), "Price crossed below EQ (entering Discount)", "Price entered DISCOUNT zone — EQ crossunder")
alertcondition(sslSweepNow and inDiscount, "BUY Signal (SSL Sweep in Discount)", "🟢 BUY: SSL Sweep detected in Discount zone")
alertcondition(bslSweepNow and inPremium, "SELL Signal (BSL Sweep in Premium)", "🔴 SELL: BSL Sweep detected in Premium zone")

Overview

The indicator detects liquidity sweeps, identifies Premium and Discount valuation zones with an Equilibrium line, and highlights Optimal Trade Entry (OTE) zones between 62% and 79% Fibonacci retracements. It visualizes potential trade entry signals alongside fixed risk-to-reward stop loss and target levels. Main settings control swing fractal sensitivity, lookback periods, OTE entry filters, and risk-to-reward ratios.

Details

Published by

hungpixi on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance

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

algorithmic-tradingpinescriptsmart-money-conceptstradingview

Settings

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

SettingTypeDefaultVariable
Show Last N Barsint2000lookbackBars
Fractal Bars (each side)int1leftBars
Max Swings to Showint5maxSwings
Show BSL (Buy-Side Liquidity)booltrueshowBSL
Show SSL (Sell-Side Liquidity)booltrueshowSSL
BSL Colorcolorcolor.new(#089981, 0)colBSL
SSL Colorcolorcolor.new(#F23645, 0)colSSL
Line Widthint1lineWidth
Show Sweep LabelsbooltrueshowSweep
Bullish Sweep Colorcolorcolor.new(#089981, 0)colSweepBull
Bearish Sweep Colorcolorcolor.new(#F23645, 0)colSweepBear
Show Premium / DiscountbooltrueshowPD
Show all 36 settings
SettingTypeDefaultVariable
Dealing Range Lookbackint50pdLookback
Premium Zonecolorcolor.new(#F23645, 88)colPrem
Discount Zonecolorcolor.new(#089981, 88)colDisc
Equilibrium Linecolorcolor.new(#787B86, 30)colEQ
Show OTE (62%-79%)booltrueshowOTE
OTE Zone Colorcolorcolor.new(#FF9800, 85)colOTE
Show Entry SignalsbooltrueshowEntry
Require OTE Zone for EntryboolfalserequireOTE
Risk:Reward Ratiofloat3.0rrRatio
Max Trades Shownint50maxTrades
Show TP2 (at EQ)booltrueshowTP2
Show TP3 (opposite zone)boolfalseshowTP3
Cooldown (bars)int5cooldownBars
One Trade at a TimebooltrueoneAtATime
Buy Signal Colorcolorcolor.new(#00E676, 0)colBuy
Sell Signal Colorcolorcolor.new(#FF1744, 0)colSell
SL Line Colorcolorcolor.new(#FF5252, 0)colSL
TP Line Colorcolorcolor.new(#69F0AE, 0)colTP
SL Structure Lookback (bars)int14slLookback
Min SL Distance (price)float5.0minSLDist
Max SL Distance (price)float30.0maxSLDist
SL MethodstringSweep WickslMethod
Show DashboardbooltrueshowDash
PositionstringTop RightdashPos
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
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

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