Strategy · Pine Script v6 · On the chart

SMC Confluence Engine Strategy 2

by shubhamtaywade82

A smart money concepts strategy drawn on the price chart.

View on GitHub

Repaints

GitHub stars1on the repository
Pine versionv6current
Lines of code1,481
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update3 days ago26 Oct 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 strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

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

smc_confluence.pine
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © shubhamtaywade82

// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © shubhamtaywade82

// ============================================================
// SMC Confluence Engine [nemesis] — Crypto Futures Edition
// Pine Script v6 | strategy() | overlay=true
// Alerts: use alert() + “Call alert() on events”; alertcondition() is ignored in strategies.
// Optional LuxAlgo-inspired modes (original implementation; reference only):
//   — Pivot vs swing-leg + IDM + trailing BOS
//   — MSB momentum Z-score, multi-OB pool + quality / HPZ
//   — Structure-bound liquidity sweeps, internal structure, EQH/EQL, FVG, premium/equilibrium/discount zones
//   — Optional KNN Supertrend (logic adapted from LuxAlgo “KNN Supertrend Horizon”, CC BY-NC-SA 4.0)
// ============================================================
// 5-LAYER STACK:
//   Layer 1 — Smart Money Concepts (BOS, CHoCH, OB, Liquidity)
//   Layer 2 — Market Structure (HH/HL/LH/LL swing labels)
//   Layer 3 — Auto Trendlines (break + retest detection)
//   Layer 4 — Session High/Low (PDH/PDL + session ranges)
//   Layer 5 — Volume Profile (POC, VAH, VAL per session)
//
// SIGNAL ENGINE:
//   LONG:  Sell-side liq sweep → CHoCH up → price returns to
//          bullish OB → near POC/VAL → trendline retest up
//   SHORT: Buy-side liq sweep → CHoCH down → price returns to
//          bearish OB → near POC/VAH → trendline retest down
//
// SIGNAL QUALITY SCORE (0–6):
//   +1  CHoCH confirmed
//   +1  OB tap (price inside order block zone)
//   +1  Liquidity sweep preceded CHoCH
//   +1  Near POC / Value Area edge
//   +1  Session level confluence (near PDH/PDL/session range)
//   +1  Trendline break + retest aligned
// ============================================================

//@version=6
strategy(
     title         = "SMC Confluence Engine [nemesis] STRATEGY 2",
     shorttitle    = "SMC Strat2",
     overlay       = true,
     initial_capital = 200000,
     default_qty_type = strategy.percent_of_equity,
     default_qty_value = 10,
     commission_type = strategy.commission.percent,
     commission_value = 0.04,
     calc_on_every_tick = true,
     max_boxes_count = 500,
     max_lines_count = 500,
     max_labels_count = 500,
     max_bars_back = 2001
)

// ════════════════════════════════════════════════════════════
// ░░  INPUTS
// ════════════════════════════════════════════════════════════

// ── Layer 1: SMC ──────────────────────────────────────────
grp_smc = "🧠 SMC Settings"
structure_mode = input.string("Pivot", "Structure mode", options=["Pivot", "LuxAlgo (IDM + trail)"], group=grp_smc, tooltip="Pivot: classic pivot BOS/CHoCH. LuxAlgo: swing-leg CHoCH, inducement (IDM), trailing extremes, then BOS.")
smc_swing    = input.int(10,   "Swing Length (BOS/CHoCH)", minval=3, maxval=50,  group=grp_smc)
idm_len      = input.int(3,    "IDM / internal swing (Lux mode)", minval=2, maxval=20, group=grp_smc, tooltip="Short swing length for inducement; used when Structure mode = LuxAlgo.")
use_msb_zscore = input.bool(false, "MSB / break Z-score filter", group=grp_smc, tooltip="When on, structure breaks require momentum Z-score vs 50-bar stats (Toolkit-style).")
msb_zscore   = input.float(0.5, "MSB Z-score threshold", minval=0.1, step=0.1, group=grp_smc)
ob_body_pct  = input.float(0.3,"OB Min Body %",            minval=0.05,step=0.05,group=grp_smc,     tooltip="Impulse candle that creates OB must have body >= this % of price.")
ob_expire    = input.int(50,   "OB Expires (bars)",        minval=5, maxval=300, group=grp_smc)
ob_scan_bars = input.int(10,   "OB scan bars (opposite candle)", minval=1, maxval=30, group=grp_smc, tooltip="Search backward up to N bars for last opposite-color candle before the break.")
liq_lookback = input.int(20,   "Liquidity Swing Lookback", minval=5, maxval=100, group=grp_smc)
liq_rolling  = input.bool(true, "Rolling high/low liquidity sweeps", group=grp_smc)
liq_struct   = input.bool(false, "Structure-bound sweeps (trailing max/min)", group=grp_smc, tooltip="After CHoCH, trail highs/lows and flag wick beyond trail + close back inside (LuxAlgo-style).")
liq_wick_pct = input.float(0.1,"Sweep Min Wick %",        minval=0.02,step=0.02,group=grp_smc,     tooltip="Wick must pierce beyond swing by this % to count as a liquidity sweep.")
show_ob      = input.bool(true,"Show Order Block Boxes",  group=grp_smc)
show_bos     = input.bool(true,"Show BOS/CHoCH Labels",   group=grp_smc)
show_liq     = input.bool(true,"Show Liquidity Sweeps",   group=grp_smc)

grp_obp = "🧱 OB pool (Toolkit-style)"
use_ob_pool  = input.bool(false, "Use multi-OB pool", group=grp_obp, tooltip="Track multiple order blocks with quality %, HPZ, mitigation, overlap filter.")
ob_max_n     = input.int(10, "Max active OBs", minval=1, maxval=50, group=grp_obp)
ob_hpz_at    = input.float(80, "HPZ quality ≥", minval=50, maxval=99, group=grp_obp)
ob_hide_ov   = input.bool(false, "Hide overlapping new OBs", group=grp_obp)
ob_extend_br = input.bool(false, "Extend mitigated OBs briefly", group=grp_obp)
ob_hist_mode = input.string("Historical", "OB display", options=["Historical", "Present"], group=grp_obp, tooltip="Present: delete graphics when mitigated.")
ob_mit_wick  = input.bool(true, "Mitigate OB on wick (Low/High)", group=grp_obp, tooltip="If off, mitigation uses close through the zone.")

grp_int = "🔹 Internal / SMC+"
show_internal = input.bool(false, "Show internal BOS/CHoCH", group=grp_int, tooltip="Faster pivot length; separate mini structure labels.")
internal_len  = input.int(5, "Internal pivot length", minval=2, maxval=25, group=grp_int)
show_eqhl     = input.bool(false, "Equal highs / lows (EQH/EQL)", group=grp_int)
eqhl_thresh   = input.float(0.15, "EQH/EQL threshold × ATR(200)", minval=0.02, step=0.02, group=grp_int)
show_fvg      = input.bool(false, "Fair value gaps (3-bar)", group=grp_int)
fvg_extend    = input.int(5, "FVG extend right (bars)", minval=0, maxval=50, group=grp_int)
show_pd_zone  = input.bool(false, "Premium / equilibrium / discount zones", group=grp_int, tooltip="LuxAlgo-style 3 bands on lookback high/low: top 5% premium, bottom 5% discount, middle equilibrium (same geometry as LuxAlgo Smart Money Concepts zones).")
pd_lookback   = input.int(50, "Zone range lookback (bars)", minval=20, maxval=200, group=grp_int)
pd_zone_labels= input.bool(true, "PD zone labels", group=grp_int)
pd_col_prem   = input.color(#f23645, "Premium zone color", group=grp_int)
pd_col_eq     = input.color(#787b86, "Equilibrium zone color", group=grp_int)
pd_col_disc   = input.color(#089981, "Discount zone color", group=grp_int)

// ── Layer 2: Market Structure ─────────────────────────────
grp_ms = "📐 Market Structure"
ms_swing     = input.int(10,   "Structure Swing Length",  minval=3, maxval=30, group=grp_ms)
show_ms      = input.bool(true,"Show HH/HL/LH/LL Labels", group=grp_ms)
show_ms_line = input.bool(true,"Show Structure Lines",    group=grp_ms)

// ── Layer 3: Trendlines ───────────────────────────────────
grp_tl = "📏 Trendlines"
tl_pivot_len = input.int(10,   "Trendline Pivot Length",  minval=3, maxval=30, group=grp_tl)
tl_max       = input.int(3,    "Max Active Trendlines",   minval=1, maxval=5,  group=grp_tl)
tl_retest_pct= input.float(0.15,"Retest Zone %",          minval=0.02,step=0.02,group=grp_tl,     tooltip="Price within this % of trendline counts as a retest.")
show_tl      = input.bool(true,"Show Trendlines",         group=grp_tl)
show_tl_break= input.bool(true,"Show Break/Retest Labels",group=grp_tl)

// ── Layer 4: Session Levels ───────────────────────────────
grp_sess = "🕐 Session Levels"
show_pdh_pdl = input.bool(true,"Show PDH / PDL",          group=grp_sess)
show_sess_hl = input.bool(true,"Show Session High/Low",   group=grp_sess)
sess_liq_pct = input.float(0.1,"Session Level Sweep %",   minval=0.02,step=0.02,group=grp_sess)
asia_sess    = input.session("0000-0800","Asia Session",   group=grp_sess)
london_sess  = input.session("0800-1600","London Session", group=grp_sess)
ny_sess      = input.session("1300-2100","NY Session",     group=grp_sess)

// ── Layer 5: Volume Profile ───────────────────────────────
grp_vp = "📊 Volume Profile"
vp_bars      = input.int(100,  "Volume Profile Lookback", minval=20,maxval=500, group=grp_vp)
vp_rows      = input.int(24,   "Profile Rows",            minval=10,maxval=50,  group=grp_vp)
poc_zone_pct = input.float(0.2,"POC Proximity %",         minval=0.05,step=0.05,group=grp_vp)
show_vp      = input.bool(true,"Show POC/VAH/VAL Lines",  group=grp_vp)

// ── Signal Engine ─────────────────────────────────────────
grp_sig = "🚀 Signal Engine"
min_score    = input.int(3,    "Min Score for Signal (0–6)",minval=0,maxval=6, group=grp_sig)
sig_cooldown = input.int(5,    "Signal Cooldown (bars)",   minval=1,maxval=30, group=grp_sig)
show_signals = input.bool(true,"Show Entry Signals",       group=grp_sig)
show_score   = input.bool(true,"Show Score on Label",      group=grp_sig)

grp_risk = "🛡 Risk (strategy)"
atr_mult_sl = input.float(1.5, "SL ATR Multiplier", group=grp_risk)
atr_mult_tp = input.float(3.0, "TP ATR Multiplier", group=grp_risk)

grp_alert = "🔔 Alerts (strategy)"
enable_runtime_alerts = input.bool(false, "Call alert() on events", group=grp_alert, tooltip="TradingView ignores alertcondition() in strategies. Turn this on, add a chart alert whose condition is “alert() function calls only”, then pick which calls to include in the alert dialog.")
alert_timing = input.string("Bar close", "alert() timing", options=["Bar close", "Once per bar (any tick)"], group=grp_alert)

// ── Visuals ───────────────────────────────────────────────
grp_vis = "🎨 Visuals"
bull_col     = input.color(#00c4ff,"Bullish Color", group=grp_vis)
bear_col     = input.color(#ff3d3d,"Bearish Color", group=grp_vis)
show_dash    = input.bool(true,"Show Dashboard",    group=grp_vis)

grp_st = "📈 Supertrend"
st_mode = input.string("Classic", "Mode", options=["Classic", "KNN (LuxAlgo-style)"], group=grp_st, tooltip="Classic: split bull/bear ST lines. KNN: filters ST direction with k-nearest neighbors on RSI + ATR% features (CPU-heavy).")
st_classic_factor = input.float(3.0, "Classic: ST factor", minval=0.01, step=0.1, group=grp_st)
st_classic_atr    = input.int(10, "Classic: ATR length", minval=1, group=grp_st)

grp_st_ml = "📈 KNN ST — ML"
knn_neighbors = input.int(10, "K-Neighbors", minval=1, maxval=50, group=grp_st_ml)
knn_window    = input.int(500, "Search window", minval=100, maxval=2000, group=grp_st_ml)
knn_st_atr    = input.int(10, "ST ATR length", minval=1, group=grp_st_ml)
knn_st_factor = input.float(3.0, "ST factor", minval=0.01, step=0.1, group=grp_st_ml)

grp_st_filt = "📈 KNN ST — filter"
knn_smooth_src  = input.bool(true, "Smooth price (HMA)", group=grp_st_filt)
knn_hma_len       = input.int(10, "Smoothing length", minval=1, group=grp_st_filt)
knn_ml_buffer_pct = input.float(5.0, "ML confidence buffer %", minval=0.0, maxval=20.0, step=0.5, group=grp_st_filt)
knn_prob_ema      = input.int(20, "Prob EMA (smoothness)", minval=1, group=grp_st_filt)
knn_vibrancy      = input.float(1.5, "Vibrancy", minval=1.0, maxval=3.0, step=0.1, group=grp_st_filt)

grp_st_sig = "📈 KNN ST — rejection orbs"
show_st_bubbles = input.bool(false, "Show rejection orbs", group=grp_st_sig, tooltip="Uses many labels per signal; keep off for performance.")
knn_rej_mult      = input.float(1.5, "Min wick vs body", minval=1.0, maxval=5.0, step=0.1, group=grp_st_sig)
knn_bubble_gap    = input.int(5, "Min gap (bars)", minval=1, maxval=20, group=grp_st_sig)

grp_st_vis = "📈 KNN ST — visuals"
show_st_glow_chars = input.bool(true, "Direction plot chars", group=grp_st_vis)
knn_grad_candles   = input.bool(false, "KNN gradient candles", group=grp_st_vis, tooltip="When on, replaces structure_bias bar colors with ML gradient.")
st_bull_knn_col = input.color(#089981, "KNN uptrend color", group=grp_st_vis)
st_bear_knn_col = input.color(#f23645, "KNN downtrend color", group=grp_st_vis)
string ST_BAR_CHAR = "▬▬▬▬▬▬▬▬▬▬"

// ════════════════════════════════════════════════════════════
// ░░  BASE INDICATORS
// ════════════════════════════════════════════════════════════
atr14    = ta.atr(14)
atr200   = ta.atr(200)
vol_sma  = ta.sma(volume, 20)
is_new_day = timeframe.change("1D")

price_change = ta.change(close)
avg_change   = ta.sma(price_change, 50)
std_change   = ta.stdev(price_change, 50)
momentum_z   = std_change > 0 ? (price_change - avg_change) / std_change : 0.0
vol_pct_rank = ta.percentrank(volume, 100)

use_lux = structure_mode == "LuxAlgo (IDM + trail)"
z_bull_ok = not use_msb_zscore or momentum_z > msb_zscore
z_bear_ok = not use_msb_zscore or momentum_z < -msb_zscore

// ════════════════════════════════════════════════════════════
// ░░  LUX SWING LEG (two functions — Pine var in UDF is one static per function)
// ════════════════════════════════════════════════════════════
swings_main_len() =>
    var int os_m = 0
    var int topx_m = na
    var int btmx_m = na
    int len = smc_swing
    upper = ta.highest(high, len)
    lower = ta.lowest(low, len)
    os_m := high[len] > upper ? 0 : low[len] < lower ? 1 : os_m[1]
    top = os_m == 0 and os_m[1] != 0 ? high[len] : na
    topx_m := os_m == 0 and os_m[1] != 0 ? bar_index[len] : topx_m
    btm = os_m == 1 and os_m[1] != 1 ? low[len] : na
    btmx_m := os_m == 1 and os_m[1] != 1 ? bar_index[len] : btmx_m
    [top, topx_m, btm, btmx_m]

swings_idm_len() =>
    var int os_i = 0
    var int topx_i = na
    var int btmx_i = na
    int len = idm_len
    upper = ta.highest(high, len)
    lower = ta.lowest(low, len)
    os_i := high[len] > upper ? 0 : low[len] < lower ? 1 : os_i[1]
    top = os_i == 0 and os_i[1] != 0 ? high[len] : na
    topx_i := os_i == 0 and os_i[1] != 0 ? bar_index[len] : topx_i
    btm = os_i == 1 and os_i[1] != 1 ? low[len] : na
    btmx_i := os_i == 1 and os_i[1] != 1 ? bar_index[len] : btmx_i
    [top, topx_i, btm, btmx_i]

// ════════════════════════════════════════════════════════════
// ░░  LAYER 1A — PIVOT SWING (pivot mode)
// ════════════════════════════════════════════════════════════
ph = ta.pivothigh(high, smc_swing, smc_swing)
pl = ta.pivotlow(low,   smc_swing, smc_swing)

var float last_ph = na
var float last_pl = na
var int   last_ph_bar = -999
var int   last_pl_bar = -999
var int   ph_age = 0
var int   pl_age = 0

ph_age := ph_age + 1
pl_age := pl_age + 1

if not na(ph)
    last_ph := ph
    last_ph_bar := bar_index - smc_swing
    ph_age := 0

if not na(pl)
    last_pl := pl
    last_pl_bar := bar_index - smc_swing
    pl_age := 0

ph_valid = not na(last_ph) and ph_age <= ob_expire
pl_valid = not na(last_pl) and pl_age <= ob_expire

// ════════════════════════════════════════════════════════════
// ░░  LAYER 1B — LUXALGO STATE (swing-leg + IDM + BOS)
// ════════════════════════════════════════════════════════════
[top_l, topx_l, btm_l, btmx_l] = swings_main_len()
[stop_l, stopx_l, sbtm_l, sbtmx_l] = swings_idm_len()

var int lux_os = 0
var bool lux_top_crossed = false
var bool lux_btm_crossed = false
var float lux_max = na
var float lux_min = na
var int lux_max_x1 = na
var int lux_min_x1 = na
var float lux_topy = na
var float lux_btmy = na
var bool lux_stop_crossed = false
var bool lux_sbtm_crossed = false
var float lux_stopy_hold = na
var float lux_sbtmy_hold = na

if not na(top_l)
    lux_topy := top_l
    lux_top_crossed := false
if not na(btm_l)
    lux_btmy := btm_l
    lux_btm_crossed := false

if not na(stop_l)
    lux_stopy_hold := stop_l
if not na(sbtm_l)
    lux_sbtmy_hold := sbtm_l

if close > lux_topy and not lux_top_crossed and not na(lux_topy)
    lux_os := 1
    lux_top_crossed := true
if close < lux_btmy and not lux_btm_crossed and not na(lux_btmy)
    lux_os := 0
    lux_btm_crossed := true

lux_choch_flip = lux_os != lux_os[1]

if lux_choch_flip
    lux_max := high
    lux_min := low
    lux_max_x1 := bar_index
    lux_min_x1 := bar_index
    lux_stop_crossed := false
    lux_sbtm_crossed := false

if low < lux_sbtmy_hold and not lux_sbtm_crossed and lux_os == 1 and not na(lux_sbtmy_hold) and lux_sbtmy_hold != lux_btmy
    lux_sbtm_crossed := true

lux_bos_bull_bar = close > lux_max and lux_sbtm_crossed and lux_os == 1 and not na(lux_max)
if lux_bos_bull_bar
    lux_sbtm_crossed := false

if high > lux_stopy_hold and not lux_stop_crossed and lux_os == 0 and not na(lux_stopy_hold) and lux_stopy_hold != lux_topy
    lux_stop_crossed := true

lux_bos_bear_bar = close < lux_min and lux_stop_crossed and lux_os == 0 and not na(lux_min)
if lux_bos_bear_bar
    lux_stop_crossed := false

lux_max := math.max(high, nz(lux_max, high))
lux_min := math.min(low, nz(lux_min, low))
if lux_max > nz(lux_max[1], lux_max)
    lux_max_x1 := bar_index
if lux_min < nz(lux_min[1], lux_min)
    lux_min_x1 := bar_index

lux_bos_bull_evt = lux_bos_bull_bar and not lux_bos_bull_bar[1]
lux_bos_bear_evt = lux_bos_bear_bar and not lux_bos_bear_bar[1]

lux_choch_bull_raw = lux_choch_flip and lux_os == 1
lux_choch_bear_raw = lux_choch_flip and lux_os == 0

// ════════════════════════════════════════════════════════════
// ░░  LAYER 1C — PIVOT BOS / CHoCH + MERGE
// ════════════════════════════════════════════════════════════
var int structure_bias = 0

bos_bull_pv = ph_valid and close > last_ph and close[1] <= last_ph and z_bull_ok
bos_bear_pv = pl_valid and close < last_pl and close[1] >= last_pl and z_bear_ok
choch_bull_pv = bos_bull_pv and structure_bias == -1
choch_bear_pv = bos_bear_pv and structure_bias == 1

if not use_lux
    if bos_bull_pv or choch_bull_pv
        structure_bias := 1
    if bos_bear_pv or choch_bear_pv
        structure_bias := -1
else
    structure_bias := lux_os == 1 ? 1 : -1

lux_choch_bull = lux_choch_bull_raw
lux_choch_bear = lux_choch_bear_raw
lux_bos_bull_f = lux_bos_bull_evt and z_bull_ok
lux_bos_bear_f = lux_bos_bear_evt and z_bear_ok

choch_bull = use_lux ? lux_choch_bull : choch_bull_pv
choch_bear = use_lux ? lux_choch_bear : choch_bear_pv
bos_bull   = use_lux ? (lux_bos_bull_f or lux_choch_bull) : bos_bull_pv
bos_bear   = use_lux ? (lux_bos_bear_f or lux_choch_bear) : bos_bear_pv

bos_bull_lbl = use_lux ? lux_bos_bull_f : (bos_bull_pv and not choch_bull_pv)
bos_bear_lbl = use_lux ? lux_bos_bear_f : (bos_bear_pv and not choch_bear_pv)

// ── BOS / CHoCH visuals: CHoCH solid, BOS dashed ──────────
var int last_bos_bull_lbl = -999
var int last_bos_bear_lbl = -999
int     bos_label_gap     = math.max(3, smc_swing)

if show_bos
    if choch_bull
        _b1   = use_lux ? math.max(nz(topx_l, bar_index - 100), bar_index - 100) : math.max(last_ph_bar, bar_index - 100)
        _y1   = use_lux ? nz(lux_topy, last_ph) : last_ph
        _mid1 = math.round((_b1 + bar_index) / 2)
        line.new(_b1, _y1, bar_index, _y1, color = color.new(bull_col, 0), width = 1, style = line.style_solid)
        label.new(_mid1, _y1, "CHoCH", style = label.style_none, textcolor = color.new(bull_col, 0), size = size.small, yloc = yloc.price)
        last_bos_bull_lbl := bar_index
    if choch_bear
        _b2   = use_lux ? math.max(nz(btmx_l, bar_index - 100), bar_index - 100) : math.max(last_pl_bar, bar_index - 100)
        _y2   = use_lux ? nz(lux_btmy, last_pl) : last_pl
        _mid2 = math.round((_b2 + bar_index) / 2)
        line.new(_b2, _y2, bar_index, _y2, color = color.new(bear_col, 0), width = 1, style = line.style_solid)
        label.new(_mid2, _y2, "CHoCH", style = label.style_none, textcolor = color.new(bear_col, 0), size = size.small, yloc = yloc.price)
        last_bos_bear_lbl := bar_index
    if bos_bull_lbl and (bar_index - last_bos_bull_lbl) >= bos_label_gap
        _b3   = use_lux ? math.max(nz(lux_max_x1, bar_index - 100), bar_index - 100) : math.max(last_ph_bar, bar_index - 100)
        _y3   = use_lux ? nz(lux_max, last_ph) : last_ph
        _mid3 = math.round((_b3 + bar_index) / 2)
        line.new(_b3, _y3, bar_index, _y3, color = color.new(bull_col, 50), width = 1, style = line.style_dashed)
        label.new(_mid3, _y3, "BOS", style = label.style_none, textcolor = color.new(bull_col, 20), size = size.small, yloc = yloc.price)
        last_bos_bull_lbl := bar_index
    if bos_bear_lbl and (bar_index - last_bos_bear_lbl) >= bos_label_gap
        _b4   = use_lux ? math.max(nz(lux_min_x1, bar_index - 100), bar_index - 100) : math.max(last_pl_bar, bar_index - 100)
        _y4   = use_lux ? nz(lux_min, last_pl) : last_pl
        _mid4 = math.round((_b4 + bar_index) / 2)
        line.new(_b4, _y4, bar_index, _y4, color = color.new(bear_col, 50), width = 1, style = line.style_dashed)
        label.new(_mid4, _y4, "BOS", style = label.style_none, textcolor = color.new(bear_col, 20), size = size.small, yloc = yloc.price)
        last_bos_bear_lbl := bar_index

// ════════════════════════════════════════════════════════════
// ░░  TRAILING EXTREMES (structure-bound liquidity)
// ════════════════════════════════════════════════════════════
var float sw_trail_max = na
var float sw_trail_min = na
var int   sw_trail_max_x = na
var int   sw_trail_min_x = na

if choch_bull or choch_bear
    sw_trail_max := high
    sw_trail_min := low
    sw_trail_max_x := bar_index
    sw_trail_min_x := bar_index
else
    sw_trail_max := math.max(high, nz(sw_trail_max, high))
    sw_trail_min := math.min(low, nz(sw_trail_min, low))
    if sw_trail_max > nz(sw_trail_max[1], sw_trail_max)
        sw_trail_max_x := bar_index
    if sw_trail_min < nz(sw_trail_min[1], sw_trail_min)
        sw_trail_min_x := bar_index

struct_liq_sweep_bull = liq_struct and structure_bias == 1 and not na(sw_trail_max) and high > sw_trail_max and close < sw_trail_max and bar_index - nz(sw_trail_max_x, bar_index) > 1
struct_liq_sweep_bear = liq_struct and structure_bias == -1 and not na(sw_trail_min) and low < sw_trail_min and close > sw_trail_min and bar_index - nz(sw_trail_min_x, bar_index) > 1

// ════════════════════════════════════════════════════════════
// ░░  OB SCAN + IMPULSE
// ════════════════════════════════════════════════════════════
impulse_body_pct = math.abs(close - open) / close * 100

scan_bear_candle_idx(int mx) =>
    int found = na
    for i = 1 to mx
        if close[i] < open[i]
            found := i
            break
    na(found) ? 1 : found

scan_bull_candle_idx(int mx) =>
    int found = na
    for i = 1 to mx
        if close[i] > open[i]
            found := i
            break
    na(found) ? 1 : found

bull_break_evt = choch_bull or (use_lux ? lux_bos_bull_f : (bos_bull_pv and not choch_bull_pv))
bear_break_evt = choch_bear or (use_lux ? lux_bos_bear_f : (bos_bear_pv and not choch_bear_pv))

// ════════════════════════════════════════════════════════════
// ░░  OB POOL (UDT)
// ════════════════════════════════════════════════════════════
type ConfluenceOb
    box   bx
    label qlab
    line  poc_line
    float top
    float bot
    float q
    bool  is_bull
    bool  mit
    int   mit_bar
    bool  hpz
    int   left_x

var array<ConfluenceOb> ob_pool = array.new<ConfluenceOb>()

ob_quality_now() =>
    math.min(100.0, math.abs(momentum_z) * 20.0 + vol_pct_rank * 0.5)

ob_overlaps(float t, float b) =>
    bool hit = false
    if ob_hide_ov and ob_pool.size() > 0
        for i = 0 to ob_pool.size() - 1
            o = ob_pool.get(i)
            if not o.mit and ((t <= o.top and t >= o.bot) or (b <= o.top and b >= o.bot))
                hit := true
                break
    hit

push_ob_pool(bool bull, int ob_idx, float qscr) =>
    float t = high[ob_idx]
    float b = low[ob_idx]
    if ob_overlaps(t, b)
        0
    else
        int lx = bar_index - ob_idx
        float poc = (t + b) / 2.0
        bool hpz = qscr >= ob_hpz_at
        color bc = bull ? bull_col : bear_col
        color edge = hpz ? color.new(bc, 30) : color.new(bc, 60)
        color fill = hpz ? color.new(bc, 75) : color.new(bc, 85)
        box nb = show_ob and use_ob_pool ? box.new(lx, t, bar_index + 15, b, border_color = edge, bgcolor = fill, border_width = 1, border_style = hpz ? line.style_dashed : line.style_solid) : na
        line pl = show_ob and use_ob_pool ? line.new(lx, poc, bar_index + 15, poc, color = color.new(bc, 50), width = 1, style = line.style_dashed) : na
        label lb = show_ob and use_ob_pool ? label.new(bar_index + 18, poc, str.tostring(qscr, "#") + "%", color = color.new(bc, 100), textcolor = hpz ? color.yellow : bc, style = label.style_none, size = size.tiny, textalign = text.align_left) : na
        ConfluenceOb rec = ConfluenceOb.new(nb, lb, pl, t, b, qscr, bull, false, 0, hpz, lx)
        ob_pool.push(rec)
        1

if use_ob_pool and bull_break_evt and impulse_body_pct >= ob_body_pct
    int ix = scan_bear_candle_idx(ob_scan_bars)
    push_ob_pool(true, ix, ob_quality_now())

if use_ob_pool and bear_break_evt and impulse_body_pct >= ob_body_pct
    int iy = scan_bull_candle_idx(ob_scan_bars)
    push_ob_pool(false, iy, ob_quality_now())

// Manage pool: extend, mitigate, trim
if use_ob_pool and ob_pool.size() > 0
    int mit_count = 0
    for j = ob_pool.size() - 1 to 0
        o = ob_pool.get(j)
        if not o.mit
            if not na(o.bx)
                o.bx.set_right(bar_index + 15)
            if not na(o.qlab)
                o.qlab.set_x(bar_index + 18)
            if not na(o.poc_line)
                o.poc_line.set_x2(bar_index + 15)
            bool crossed = o.is_bull ? (ob_mit_wick ? low < o.bot : close < o.bot) : (ob_mit_wick ? high > o.top : close > o.top)
            if crossed
                bool present = ob_hist_mode == "Present"
                if present
                    if not na(o.bx)
                        o.bx.delete()
                    if not na(o.qlab)
                        o.qlab.delete()
                    if not na(o.poc_line)
                        o.poc_line.delete()
                    ob_pool.set(j, ConfluenceOb.new(na, na, na, o.top, o.bot, o.q, o.is_bull, true, bar_index, o.hpz, o.left_x))
                else
                    if not na(o.bx)
                        o.bx.set_bgcolor(color.new(color.gray, 90))
                        o.bx.set_border_color(color.new(color.gray, 70))
                    if not na(o.qlab)
                        o.qlab.set_textcolor(color.new(color.gray, 50))
                    if not na(o.poc_line)
                        o.poc_line.set_color(color.new(color.gray, 80))
                    if not na(o.bx)
                        o.bx.set_right(bar_index)
                    if not na(o.qlab)
                        o.qlab.set_x(bar_index)
                    if not na(o.poc_line)
                        o.poc_line.set_x2(bar_index)
                    ob_pool.set(j, ConfluenceOb.new(o.bx, o.qlab, o.poc_line, o.top, o.bot, o.q, o.is_bull, true, bar_index, o.hpz, o.left_x))
        else
            mit_count += 1
            bool ext = ob_extend_br and mit_count <= 2 and (bar_index - o.mit_bar <= 50)
            if ob_hist_mode == "Present"
                if not na(o.bx)
                    o.bx.delete()
                if not na(o.qlab)
                    o.qlab.delete()
                if not na(o.poc_line)
                    o.poc_line.delete()
            else if ext
                if not na(o.bx)
                    o.bx.set_right(bar_index + 15)
                if not na(o.qlab)
                    o.qlab.set_x(bar_index + 18)
                if not na(o.poc_line)
                    o.poc_line.set_x2(bar_index + 15)
            else
                if not na(o.bx)
                    o.bx.set_right(o.mit_bar)
                if not na(o.qlab)
                    o.qlab.set_x(o.mit_bar)
                if not na(o.poc_line)
                    o.poc_line.set_x2(o.mit_bar)

if use_ob_pool
    int trim_n = ob_pool.size() - ob_max_n
    if trim_n > 0
        for _t = 0 to trim_n - 1
            old = ob_pool.shift()
            if not na(old.bx)
                old.bx.delete()
            if not na(old.qlab)
                old.qlab.delete()
            if not na(old.poc_line)
                old.poc_line.delete()

in_bull_ob_pool = false
in_bear_ob_pool = false
if use_ob_pool and ob_pool.size() > 0
    for k = 0 to ob_pool.size() - 1
        o2 = ob_pool.get(k)
        if not o2.mit
            if o2.is_bull and low <= o2.top and high >= o2.bot
                in_bull_ob_pool := true
            if not o2.is_bull and high >= o2.bot and low <= o2.top
                in_bear_ob_pool := true

// ════════════════════════════════════════════════════════════
// ░░  LAYER 1C — LEGACY SINGLE ORDER BLOCKS
// ════════════════════════════════════════════════════════════
var float bull_ob_hi  = na
var float bull_ob_lo  = na
var int   bull_ob_bar = -999
var int   bull_ob_age = 0
var float bear_ob_hi  = na
var float bear_ob_lo  = na
var int   bear_ob_bar = -999
var int   bear_ob_age = 0

bull_ob_age := bull_ob_age + 1
bear_ob_age := bear_ob_age + 1

if not use_ob_pool and bull_break_evt and impulse_body_pct >= ob_body_pct
    int bi = scan_bear_candle_idx(ob_scan_bars)
    bull_ob_hi  := high[bi]
    bull_ob_lo  := low[bi]
    bull_ob_bar := bar_index - bi
    bull_ob_age := 0

if not use_ob_pool and bear_break_evt and impulse_body_pct >= ob_body_pct
    int bj = scan_bull_candle_idx(ob_scan_bars)
    bear_ob_hi  := high[bj]
    bear_ob_lo  := low[bj]
    bear_ob_bar := bar_index - bj
    bear_ob_age := 0

bull_ob_valid = not use_ob_pool and not na(bull_ob_hi) and bull_ob_age <= ob_expire and close >= bull_ob_lo * 0.998
bear_ob_valid = not use_ob_pool and not na(bear_ob_hi) and bear_ob_age <= ob_expire and close <= bear_ob_hi * 1.002

in_bull_ob_legacy = bull_ob_valid and low  <= bull_ob_hi and high >= bull_ob_lo
in_bear_ob_legacy = bear_ob_valid and high >= bear_ob_lo and low  <= bear_ob_hi

in_bull_ob = use_ob_pool ? in_bull_ob_pool : in_bull_ob_legacy
in_bear_ob = use_ob_pool ? in_bear_ob_pool : in_bear_ob_legacy

var box bull_ob_box = na
var box bear_ob_box = na

if show_ob and not use_ob_pool and bull_ob_valid and not na(bull_ob_hi)
    box.delete(bull_ob_box)
    bull_ob_box := box.new(bull_ob_bar, bull_ob_hi, bar_index, bull_ob_lo, border_color=color.new(bull_col, 20), bgcolor=color.new(bull_col, 85), border_width=1)

if show_ob and not use_ob_pool and bear_ob_valid and not na(bear_ob_hi)
    box.delete(bear_ob_box)
    bear_ob_box := box.new(bear_ob_bar, bear_ob_hi, bar_index, bear_ob_lo, border_color=color.new(bear_col, 20), bgcolor=color.new(bear_col, 85), border_width=1)

// ════════════════════════════════════════════════════════════
// ░░  LAYER 1D — LIQUIDITY SWEEPS
// ════════════════════════════════════════════════════════════
liq_hi = ta.highest(high, liq_lookback)
liq_lo = ta.lowest(low,   liq_lookback)

liq_sweep_bull_roll = liq_rolling and low < liq_lo[1] - liq_lo[1] * (liq_wick_pct/100) and close > liq_lo[1]
liq_sweep_bear_roll = liq_rolling and high > liq_hi[1] + liq_hi[1] * (liq_wick_pct/100) and close < liq_hi[1]

liq_sweep_bull = liq_sweep_bull_roll or struct_liq_sweep_bull
liq_sweep_bear = liq_sweep_bear_roll or struct_liq_sweep_bear

var int last_bull_sweep_bar  = -999
var int last_bear_sweep_bar  = -999
var int last_liq_bull_lbl    = -999
var int last_liq_bear_lbl    = -999
int     liq_label_gap        = math.max(3, smc_swing)

if liq_sweep_bull
    last_bull_sweep_bar := bar_index
if liq_sweep_bear
    last_bear_sweep_bar := bar_index

recent_bull_sweep = (bar_index - last_bull_sweep_bar) <= smc_swing * 2
recent_bear_sweep = (bar_index - last_bear_sweep_bar) <= smc_swing * 2

if show_liq and liq_sweep_bull and (bar_index - last_liq_bull_lbl) >= liq_label_gap
    label.new(bar_index, low, "•", style=label.style_none, textcolor=color.new(color.purple, 0), size=size.normal)
    label.new(bar_index, low - atr14 * 0.3, struct_liq_sweep_bull ? "Sell-side (struct)" : "Sell-side", style=label.style_none, textcolor=color.new(color.purple, 30), size=size.small)
    last_liq_bull_lbl := bar_index

if show_liq and liq_sweep_bear and (bar_index - last_liq_bear_lbl) >= liq_label_gap
    label.new(bar_index, high, "•", style=label.style_none, textcolor=color.new(color.orange, 0), size=size.normal)
    label.new(bar_index, high + atr14 * 0.3, struct_liq_sweep_bear ? "Buy-side (struct)" : "Buy-side", style=label.style_none, textcolor=color.new(color.orange, 30), size=size.small)
    last_liq_bear_lbl := bar_index

// ════════════════════════════════════════════════════════════
// ░░  LAYER 2 — MARKET STRUCTURE (HH / HL / LH / LL)
// ════════════════════════════════════════════════════════════
ms_ph = ta.pivothigh(high, ms_swing, ms_swing)
ms_pl = ta.pivotlow(low,   ms_swing, ms_swing)

var float prev_ms_ph = na
var float prev_ms_pl = na

ms_hh = not na(ms_ph) and not na(prev_ms_ph) and ms_ph > prev_ms_ph
ms_lh = not na(ms_ph) and not na(prev_ms_ph) and ms_ph < prev_ms_ph
ms_hl = not na(ms_pl) and not na(prev_ms_pl) and ms_pl > prev_ms_pl
ms_ll = not na(ms_pl) and not na(prev_ms_pl) and ms_pl < prev_ms_pl

var float last_ms_ph_val = na
var float last_ms_pl_val = na
var int   last_ms_ph_bar = -999
var int   last_ms_pl_bar = -999

if not na(ms_ph)
    prev_ms_ph := last_ms_ph_val
    last_ms_ph_val := ms_ph
    last_ms_ph_bar := bar_index - ms_swing

if not na(ms_pl)
    prev_ms_pl := last_ms_pl_val
    last_ms_pl_val := ms_pl
    last_ms_pl_bar := bar_index - ms_swing

// Structure labels — bare text, no background box, minimum gap enforced
var int last_ms_lbl_bar = -999
int     ms_label_gap    = math.max(3, ms_swing)

if show_ms and (bar_index - ms_swing - last_ms_lbl_bar) >= ms_label_gap
    if ms_hh
        label.new(bar_index - ms_swing, ms_ph, "HH",
             style=label.style_none, textcolor=color.new(bull_col, 20),
             size=size.small, yloc=yloc.abovebar)
        last_ms_lbl_bar := bar_index - ms_swing
    if ms_lh
        label.new(bar_index - ms_swing, ms_ph, "LH",
             style=label.style_none, textcolor=color.new(bear_col, 20),
             size=size.small, yloc=yloc.abovebar)
        last_ms_lbl_bar := bar_index - ms_swing
    if ms_hl
        label.new(bar_index - ms_swing, ms_pl, "HL",
             style=label.style_none, textcolor=color.new(bull_col, 20),
             size=size.small, yloc=yloc.belowbar)
        last_ms_lbl_bar := bar_index - ms_swing
    if ms_ll
        label.new(bar_index - ms_swing, ms_pl, "LL",
             style=label.style_none, textcolor=color.new(bear_col, 20),
             size=size.small, yloc=yloc.belowbar)
        last_ms_lbl_bar := bar_index - ms_swing

// Structure bias from HH/HL vs LH/LL
var int ms_trend = 0   // 1 = uptrend, -1 = downtrend
if ms_hh or ms_hl
    ms_trend := 1
if ms_lh or ms_ll
    ms_trend := -1

// Structure lines (connect swing pivots)
var line ms_ph_line = na
var line ms_pl_line = na

if show_ms_line
    if not na(ms_ph)
        line.delete(ms_ph_line)
        ms_ph_line := line.new(bar_index - ms_swing - ms_swing, nz(prev_ms_ph, ms_ph),
             bar_index - ms_swing, ms_ph,
             color=color.new(bear_col, 50), width=1, style=line.style_dashed)
    if not na(ms_pl)
        line.delete(ms_pl_line)
        ms_pl_line := line.new(bar_index - ms_swing - ms_swing, nz(prev_ms_pl, ms_pl),
             bar_index - ms_swing, ms_pl,
             color=color.new(bull_col, 50), width=1, style=line.style_dashed)

// ════════════════════════════════════════════════════════════
// ░░  LAYER 2B — INTERNAL BOS / CHoCH (faster pivots)
// ════════════════════════════════════════════════════════════
iph = ta.pivothigh(high, internal_len, internal_len)
ipl = ta.pivotlow(low,   internal_len, internal_len)

var float ilast_ph = na
var float ilast_pl = na
var int   ilast_ph_bar = -999
var int   ilast_pl_bar = -999
var int   ibias = 0

if not na(iph)
    ilast_ph := iph
    ilast_ph_bar := bar_index - internal_len
if not na(ipl)
    ilast_pl := ipl
    ilast_pl_bar := bar_index - internal_len

iph_ok = not na(ilast_ph)
ipl_ok = not na(ilast_pl)

ibos_bull = iph_ok and close > ilast_ph and close[1] <= ilast_ph and z_bull_ok
ibos_bear = ipl_ok and close < ilast_pl and close[1] >= ilast_pl and z_bear_ok
ichoch_bull = ibos_bull and ibias == -1
ichoch_bear = ibos_bear and ibias == 1

if ibos_bull or ichoch_bull
    ibias := 1
if ibos_bear or ichoch_bear
    ibias := -1

var int last_ilbl = -999
int igap = math.max(2, internal_len)

if show_internal
    if ichoch_bull and (bar_index - last_ilbl) >= igap
        _ix = math.max(ilast_ph_bar, bar_index - 80)
        line.new(_ix, ilast_ph, bar_index, ilast_ph, color=color.new(bull_col, 40), width=1, style=line.style_solid)
        label.new(math.round((_ix + bar_index) / 2), ilast_ph, "iCHoCH", style=label.style_none, textcolor=color.new(bull_col, 0), size=size.tiny, yloc=yloc.price)
        last_ilbl := bar_index
    if ichoch_bear and (bar_index - last_ilbl) >= igap
        _iy = math.max(ilast_pl_bar, bar_index - 80)
        line.new(_iy, ilast_pl, bar_index, ilast_pl, color=color.new(bear_col, 40), width=1, style=line.style_solid)
        label.new(math.round((_iy + bar_index) / 2), ilast_pl, "iCHoCH", style=label.style_none, textcolor=color.new(bear_col, 0), size=size.tiny, yloc=yloc.price)
        last_ilbl := bar_index
    if ibos_bull and not ichoch_bull and (bar_index - last_ilbl) >= igap
        _iz = math.max(ilast_ph_bar, bar_index - 80)
        line.new(_iz, ilast_ph, bar_index, ilast_ph, color=color.new(bull_col, 60), width=1, style=line.style_dashed)
        label.new(math.round((_iz + bar_index) / 2), ilast_ph, "iBOS", style=label.style_none, textcolor=color.new(bull_col, 30), size=size.tiny, yloc=yloc.price)
        last_ilbl := bar_index
    if ibos_bear and not ichoch_bear and (bar_index - last_ilbl) >= igap
        _iw = math.max(ilast_pl_bar, bar_index - 80)
        line.new(_iw, ilast_pl, bar_index, ilast_pl, color=color.new(bear_col, 60), width=1, style=line.style_dashed)
        label.new(math.round((_iw + bar_index) / 2), ilast_pl, "iBOS", style=label.style_none, textcolor=color.new(bear_col, 30), size=size.tiny, yloc=yloc.price)
        last_ilbl := bar_index

// ════════════════════════════════════════════════════════════
// ░░  LAYER 2C — EQH / EQL (structure pivots)
// ════════════════════════════════════════════════════════════
if show_eqhl and not na(ms_ph) and not na(prev_ms_ph) and math.abs(ms_ph - prev_ms_ph) < eqhl_thresh * atr200
    label.new(bar_index - ms_swing, ms_ph, "EQH", style=label.style_none, textcolor=color.new(bear_col, 0), size=size.tiny, yloc=yloc.abovebar)

if show_eqhl and not na(ms_pl) and not na(prev_ms_pl) and math.abs(ms_pl - prev_ms_pl) < eqhl_thresh * atr200
    label.new(bar_index - ms_swing, ms_pl, "EQL", style=label.style_none, textcolor=color.new(bull_col, 0), size=size.tiny, yloc=yloc.belowbar)

// ════════════════════════════════════════════════════════════
// ░░  LAYER 2D — FAIR VALUE GAPS (simple 3-bar)
// ════════════════════════════════════════════════════════════
bull_fvg = low > high[2] and high[1] > low[1]
bear_fvg = high < low[2] and low[1] < high[1]

if show_fvg and bull_fvg
    box.new(bar_index - 2, low, bar_index + fvg_extend, high[2], bgcolor=color.new(bull_col, 88), border_color=color.new(bull_col, 50), border_width=1)

if show_fvg and bear_fvg
    box.new(bar_index - 2, high, bar_index + fvg_extend, low[2], bgcolor=color.new(bear_col, 88), border_color=color.new(bear_col, 50), border_width=1)

// ════════════════════════════════════════════════════════════
// ░░  LAYER 2E — PREMIUM / EQUILIBRIUM / DISCOUNT (LuxAlgo-style bands)
// ════════════════════════════════════════════════════════════
// Geometry from LuxAlgo drawPremiumDiscountZones(): range = lookback high/low vs their trailing swing.
pd_hi = ta.highest(high, pd_lookback)
pd_lo = ta.lowest(low, pd_lookback)
float pd_t = pd_hi
float pd_b = pd_lo
float pd_prem_top = pd_t
float pd_prem_bot = 0.95 * pd_t + 0.05 * pd_b
float pd_eq_top = 0.525 * pd_t + 0.475 * pd_b
float pd_eq_bot = 0.525 * pd_b + 0.475 * pd_t
float pd_disc_top = 0.95 * pd_b + 0.05 * pd_t
float pd_disc_bot = pd_b

// Disjoint classification by equilibrium band (matches ICT-style “above/below equilibrium”)
in_pd_equilibrium = close >= pd_eq_bot and close <= pd_eq_top
in_pd_premium = close > pd_eq_top
in_pd_discount = close < pd_eq_bot
in_premium = in_pd_premium
in_discount = in_pd_discount

var box pd_box_prem = na
var box pd_box_eq = na
var box pd_box_disc = na
var label pd_lbl_prem = na
var label pd_lbl_eq = na
var label pd_lbl_disc = na

if show_pd_zone and barstate.islast and pd_t != pd_b
    int pd_left = bar_index - pd_lookback
    int pd_right = bar_index + 10
    box.delete(pd_box_prem)
    box.delete(pd_box_eq)
    box.delete(pd_box_disc)
    label.delete(pd_lbl_prem)
    label.delete(pd_lbl_eq)
    label.delete(pd_lbl_disc)
    pd_box_prem := box.new(pd_left, pd_prem_top, pd_right, pd_prem_bot, bgcolor=color.new(pd_col_prem, 82), border_color=color.new(pd_col_prem, 55), border_width=1)
    pd_box_eq := box.new(pd_left, pd_eq_top, pd_right, pd_eq_bot, bgcolor=color.new(pd_col_eq, 82), border_color=color.new(pd_col_eq, 55), border_width=1)
    pd_box_disc := box.new(pd_left, pd_disc_top, pd_right, pd_disc_bot, bgcolor=color.new(pd_col_disc, 82), border_color=color.new(pd_col_disc, 55), border_width=1)
    if pd_zone_labels
        int mid_x = math.round(0.5 * (pd_left + pd_right))
        pd_lbl_prem := label.new(mid_x, pd_prem_top, "Premium", color=color(na), textcolor=pd_col_prem, style=label.style_label_down, size=size.tiny, yloc=yloc.price)
        pd_lbl_eq := label.new(pd_right, math.avg(pd_eq_top, pd_eq_bot), "Equilibrium", color=color(na), textcolor=pd_col_eq, style=label.style_label_left, size=size.tiny, yloc=yloc.price)
        pd_lbl_disc := label.new(mid_x, pd_disc_bot, "Discount", color=color(na), textcolor=pd_col_disc, style=label.style_label_up, size=size.tiny, yloc=yloc.price)

// ════════════════════════════════════════════════════════════
// ░░  LAYER 3 — AUTO TRENDLINES
// ════════════════════════════════════════════════════════════
tl_ph = ta.pivothigh(high, tl_pivot_len, tl_pivot_len)
tl_pl = ta.pivotlow(low,   tl_pivot_len, tl_pivot_len)

var float tl_ph1 = na
var int   tl_ph1_bar = -999
var float tl_ph2 = na
var int   tl_ph2_bar = -999
var float tl_pl1 = na
var int   tl_pl1_bar = -999
var float tl_pl2 = na
var int   tl_pl2_bar = -999

if not na(tl_ph)
    tl_ph2 := tl_ph1
    tl_ph2_bar := tl_ph1_bar
    tl_ph1 := tl_ph
    tl_ph1_bar := bar_index - tl_pivot_len

if not na(tl_pl)
    tl_pl2 := tl_pl1
    tl_pl2_bar := tl_pl1_bar
    tl_pl1 := tl_pl
    tl_pl1_bar := bar_index - tl_pivot_len

// Trendline projected value at current bar
tl_bear_slope = not na(tl_ph1) and not na(tl_ph2) and tl_ph1_bar != tl_ph2_bar ?
     (tl_ph1 - tl_ph2) / (tl_ph1_bar - tl_ph2_bar) : na
tl_bull_slope = not na(tl_pl1) and not na(tl_pl2) and tl_pl1_bar != tl_pl2_bar ?
     (tl_pl1 - tl_pl2) / (tl_pl1_bar - tl_pl2_bar) : na

tl_bear_val = not na(tl_bear_slope) ?
     tl_ph1 + tl_bear_slope * (bar_index - tl_ph1_bar) : na
tl_bull_val = not na(tl_bull_slope) ?
     tl_pl1 + tl_bull_slope * (bar_index - tl_pl1_bar) : na

// Trendline break detection
tl_bear_break = not na(tl_bear_val) and close > tl_bear_val and close[1] <= tl_bear_val[1]
tl_bull_break = not na(tl_bull_val) and close < tl_bull_val and close[1] >= tl_bull_val[1]

// Retest detection — one-shot per break
var bool tl_bear_broken   = false
var bool tl_bull_broken   = false
var bool tl_bear_retested = false
var bool tl_bull_retested = false
var int  tl_bear_break_bar = -999
var int  tl_bull_break_bar = -999

if not na(tl_ph)
    tl_bear_broken   := false
    tl_bear_retested := false
    tl_bear_break_bar := -999

if not na(tl_pl)
    tl_bull_broken   := false
    tl_bull_retested := false
    tl_bull_break_bar := -999

if tl_bear_break
    tl_bear_broken   := true
    tl_bear_retested := false
    tl_bear_break_bar := bar_index
if tl_bull_break
    tl_bull_broken   := true
    tl_bull_retested := false
    tl_bull_break_bar := bar_index

tl_bear_retest = tl_bear_broken and not tl_bear_retested and not na(tl_bear_val) and
                 (bar_index - tl_bear_break_bar) > 1 and
                 math.abs(close - tl_bear_val) / close * 100 <= tl_retest_pct and
                 close > tl_bear_val

tl_bull_retest = tl_bull_broken and not tl_bull_retested and not na(tl_bull_val) and
                 (bar_index - tl_bull_break_bar) > 1 and
                 math.abs(close - tl_bull_val) / close * 100 <= tl_retest_pct and
                 close < tl_bull_val

if tl_bear_retest
    tl_bear_retested := true
if tl_bull_retest
    tl_bull_retested := true

// Draw trendlines
var line tl_bear_line = na
var line tl_bull_line = na

if show_tl
    if not na(tl_ph1) and not na(tl_ph2)
        line.delete(tl_bear_line)
        tl_bear_line := line.new(tl_ph2_bar, tl_ph2, bar_index, tl_bear_val,
             color=color.new(bear_col, 30), width=1,
             style=tl_bear_broken ? line.style_dashed : line.style_solid,
             extend=extend.right)
    if not na(tl_pl1) and not na(tl_pl2)
        line.delete(tl_bull_line)
        tl_bull_line := line.new(tl_pl2_bar, tl_pl2, bar_index, tl_bull_val,
             color=color.new(bull_col, 30), width=1,
             style=tl_bull_broken ? line.style_dashed : line.style_solid,
             extend=extend.right)

// TL break/retest — dot + text anchored to TL value (no floating boxes)
if show_tl_break
    if tl_bear_break
        label.new(bar_index, tl_bear_val, "◆  Break",
             style=label.style_none, textcolor=color.new(bull_col, 0),
             size=size.small, yloc=yloc.belowbar)
    if tl_bull_break
        label.new(bar_index, tl_bull_val, "◆  Break",
             style=label.style_none, textcolor=color.new(bear_col, 0),
             size=size.small, yloc=yloc.abovebar)
    if tl_bear_retest
        label.new(bar_index, tl_bear_val, "●  Retest",
             style=label.style_none, textcolor=color.new(bull_col, 0),
             size=size.small, yloc=yloc.belowbar)
    if tl_bull_retest
        label.new(bar_index, tl_bull_val, "●  Retest",
             style=label.style_none, textcolor=color.new(bear_col, 0),
             size=size.small, yloc=yloc.abovebar)

// ════════════════════════════════════════════════════════════
// ░░  LAYER 4 — SESSION HIGH / LOW + PDH / PDL
// ════════════════════════════════════════════════════════════
var float day_high = high
var float day_low  = low
var float pdh      = na
var float pdl      = na

if is_new_day
    pdh := day_high
    pdl := day_low
    day_high := high
    day_low  := low
else
    day_high := math.max(day_high, high)
    day_low  := math.min(day_low,  low)

in_asia   = not na(time(timeframe.period, asia_sess))
in_london = not na(time(timeframe.period, london_sess))
in_ny     = not na(time(timeframe.period, ny_sess))

var float asia_hi   = na
var float asia_lo   = na
var float london_hi = na
var float london_lo = na
var float ny_hi     = na
var float ny_lo     = na
var bool  was_asia   = false
var bool  was_london = false
var bool  was_ny     = false

if in_asia
    asia_hi := na(asia_hi) or was_asia == false ? high : math.max(asia_hi, high)
    asia_lo := na(asia_lo) or was_asia == false ? low  : math.min(asia_lo, low)
    was_asia := true
else if was_asia
    was_asia := false

if in_london
    london_hi := na(london_hi) or was_london == false ? high : math.max(london_hi, high)
    london_lo := na(london_lo) or was_london == false ? low  : math.min(london_lo, low)
    was_london := true
else if was_london
    was_london := false

if in_ny
    ny_hi := na(ny_hi) or was_ny == false ? high : math.max(ny_hi, high)
    ny_lo := na(ny_lo) or was_ny == false ? low  : math.min(ny_lo, low)
    was_ny := true
else if was_ny
    was_ny := false

var line pdh_line = na
var line pdl_line = na

if show_pdh_pdl and not na(pdh) and barstate.islast
    line.delete(pdh_line)
    line.delete(pdl_line)
    pdh_line := line.new(bar_index - 50, pdh, bar_index + 20, pdh,
         color=color.new(bear_col, 20), width=1, style=line.style_dotted,
         extend=extend.right)
    pdl_line := line.new(bar_index - 50, pdl, bar_index + 20, pdl,
         color=color.new(bull_col, 20), width=1, style=line.style_dotted,
         extend=extend.right)

pdh_sweep     = not na(pdh) and high > pdh * (1 + sess_liq_pct/100) and close < pdh
pdl_sweep     = not na(pdl) and low  < pdl * (1 - sess_liq_pct/100) and close > pdl
near_pdh      = not na(pdh) and math.abs(close - pdh) / close * 100 <= sess_liq_pct
near_pdl      = not na(pdl) and math.abs(close - pdl) / close * 100 <= sess_liq_pct
near_sess_hi  = (not na(asia_hi) and math.abs(close - asia_hi) / close * 100 <= sess_liq_pct) or(not na(london_hi) and math.abs(close - london_hi) / close * 100 <= sess_liq_pct)
near_sess_lo  = (not na(asia_lo) and math.abs(close - asia_lo) / close * 100 <= sess_liq_pct) or(not na(london_lo) and math.abs(close - london_lo) / close * 100 <= sess_liq_pct)

sess_level_bull = near_pdl or near_sess_lo or pdl_sweep
sess_level_bear = near_pdh or near_sess_hi or pdh_sweep

// ════════════════════════════════════════════════════════════
// ░░  LAYER 5 — VOLUME PROFILE (POC / VAH / VAL)
// ════════════════════════════════════════════════════════════
vp_high  = ta.highest(high, vp_bars)
vp_low   = ta.lowest(low,   vp_bars)
vp_range = vp_high - vp_low
bucket_size = vp_range / vp_rows

var float poc      = na
var float vah      = na
var float val_line = na
var float max_vol_price = na
var float max_vol       = 0.0

if bar_index >= vp_bars
    max_vol       := 0.0
    max_vol_price := close
    for i = 0 to vp_bars - 1
        if volume[i] > max_vol
            max_vol       := volume[i]
            max_vol_price := (high[i] + low[i] + close[i]) / 3.0
    poc := max_vol_price

    float vwap_sum  = 0.0
    float vol_total = 0.0
    float vwsum2    = 0.0
    for i = 0 to vp_bars - 1
        float tp = (high[i] + low[i] + close[i]) / 3.0
        vwap_sum  += tp * volume[i]
        vol_total += volume[i]
        vwsum2    += tp * tp * volume[i]
    float vwap_val = vol_total > 0 ? vwap_sum / vol_total : close
    float variance = vol_total > 0 ? vwsum2 / vol_total - vwap_val * vwap_val : 0.0
    float sigma    = variance > 0 ? math.sqrt(variance) : atr14
    vah      := vwap_val + sigma
    val_line := vwap_val - sigma

near_poc = not na(poc)      and math.abs(close - poc)      / close * 100 <= poc_zone_pct
near_vah = not na(vah)      and math.abs(close - vah)      / close * 100 <= poc_zone_pct
near_val = not na(val_line) and math.abs(close - val_line) / close * 100 <= poc_zone_pct
vp_bull_conf = near_poc or near_val
vp_bear_conf = near_poc or near_vah

var line poc_line  = na
var line vah_line  = na
var line val_line2 = na

if show_vp and not na(poc) and barstate.islast
    line.delete(poc_line)
    line.delete(vah_line)
    line.delete(val_line2)
    poc_line  := line.new(bar_index - vp_bars, poc,      bar_index, poc,
         color=color.new(color.yellow, 20), width=2, style=line.style_solid)
    vah_line  := line.new(bar_index - vp_bars, vah,      bar_index, vah,
         color=color.new(bear_col, 40), width=1, style=line.style_dashed)
    val_line2 := line.new(bar_index - vp_bars, val_line, bar_index, val_line,
         color=color.new(bull_col, 40), width=1, style=line.style_dashed)

// ════════════════════════════════════════════════════════════
// ░░  SIGNAL ENGINE — 3-LAYER CONFLUENCE
// ════════════════════════════════════════════════════════════
long_s1 = choch_bull          ? 1 : 0   // CHoCH (mandatory trigger)
long_s2 = in_bull_ob          ? 1 : 0   // OB tap
long_s3 = recent_bull_sweep   ? 1 : 0   // Liq sweep
long_s4 = vp_bull_conf        ? 1 : 0   // Volume profile
long_s5 = sess_level_bull     ? 1 : 0   // Session level
long_s6 = tl_bear_retest      ? 1 : 0   // TL retest

short_s1 = choch_bear         ? 1 : 0
short_s2 = in_bear_ob         ? 1 : 0
short_s3 = recent_bear_sweep  ? 1 : 0
short_s4 = vp_bear_conf       ? 1 : 0
short_s5 = sess_level_bear    ? 1 : 0
short_s6 = tl_bull_retest     ? 1 : 0

long_score  = long_s1  + long_s2  + long_s3  + long_s4  + long_s5  + long_s6
short_score = short_s1 + short_s2 + short_s3 + short_s4 + short_s5 + short_s6

var int last_sig_bar = -999
cooldown_ok = (bar_index - last_sig_bar) >= sig_cooldown

long_signal  = long_s1  == 1 and long_score  >= min_score and cooldown_ok
short_signal = short_s1 == 1 and short_score >= min_score and cooldown_ok

if long_signal or short_signal
    last_sig_bar := bar_index

// ════════════════════════════════════════════════════════════
// ░░ STRATEGY ENTRIES
// ════════════════════════════════════════════════════════════

// Only trade high conviction
trade_long  = long_signal  and long_score  >= 1
trade_short = short_signal and short_score >= 1

// // Long Entry
// if long_signal
//     strategy.entry("LONG", strategy.long)

// // Short Entry
// if short_signal
//     strategy.entry("SHORT", strategy.short)

if trade_long and strategy.position_size <= 0
    strategy.entry("LONG", strategy.long)

if trade_short and strategy.position_size >= 0
    strategy.entry("SHORT", strategy.short)

// ════════════════════════════════════════════════════════════
// ░░  SIGNAL LABELS
// ════════════════════════════════════════════════════════════
score_breakdown_long() =>
    "CHoCH" + (long_s2==1?" OB":"") + (long_s3==1?" LIQ":"") +(long_s4==1?" VP":"") + (long_s5==1?" SESS":"") + (long_s6==1?" TL":"")

score_breakdown_short() =>
    "CHoCH" + (short_s2==1?" OB":"") + (short_s3==1?" LIQ":"") +(short_s4==1?" VP":"") + (short_s5==1?" SESS":"") + (short_s6==1?" TL":"")

if show_signals and long_signal
    _sz  = long_score >= 5 ? size.normal : size.normal
    _col = long_score >= 5 ? color.new(bull_col, 0) : color.new(bull_col, 25)
    _txt = "▲ LONG" +       (show_score ? " [" + str.tostring(long_score) + "/6]" : "") +       "\n" + score_breakdown_long()
    label.new(bar_index, low - atr14 * 1.2, _txt,
         style=label.style_label_up, color=_col,
         textcolor=color.white, size=_sz)

if show_signals and short_signal
    _sz  = short_score >= 5 ? size.normal : size.normal
    _col = short_score >= 5 ? color.new(bear_col, 0) : color.new(bear_col, 25)
    _txt = "▼ SHORT" +       (show_score ? " [" + str.tostring(short_score) + "/6]" : "") +       "\n" + score_breakdown_short()
    label.new(bar_index, high + atr14 * 1.2, _txt,
         style=label.style_label_down, color=_col,
         textcolor=color.white, size=_sz)

// ════════════════════════════════════════════════════════════
// ░░  KNN SUPERTREND (LuxAlgo-style, CC BY-NC-SA — adapted)
// ════════════════════════════════════════════════════════════
st_knn = st_mode == "KNN (LuxAlgo-style)"

format_dyn_vol(float vol) =>
    vol >= 1000000000 ? str.format("{0,number,#.##}B", vol / 1000000000) :
     vol >= 1000000 ? str.format("{0,number,#.##}M", vol / 1000000) :
     vol >= 1000 ? str.format("{0,number,#.#}K", vol / 1000) :
     str.tostring(vol, "#")

bubble_size_from_vol() =>
    float avg_v = ta.sma(volume, 100)
    float std_v = ta.stdev(volume, 100)
    float zv = (volume - avg_v) / nz(std_v, 1.0)
    int(math.max(8, math.min(30, 14 + (zv * 2))))

render_st_orb(int bar_x, float y_ctr, string txt, int base_sz, color theme_c, float gap, bool is_bull) =>
    label.new(bar_x, y_ctr - (gap * 1.5), style=label.style_circle, color=color.new(color.black, 80), size=base_sz + 2, yloc=yloc.price)
    label.new(bar_x, y_ctr, style=label.style_circle, color=color.new(theme_c, 70), size=base_sz + 1, yloc=yloc.price)
    label.new(bar_x, y_ctr, style=label.style_circle, color=color.new(theme_c, 15), size=base_sz, yloc=yloc.price)
    label.new(bar_x, y_ctr + (gap * 0.5), style=label.style_circle, color=color.new(color.white, 85), size=int(base_sz * 0.7), yloc=yloc.price)
    label.new(bar_x, y_ctr + gap, style=label.style_circle, color=color.new(color.white, 40), size=int(base_sz * 0.2), yloc=yloc.price)
    float ly = is_bull ? y_ctr - (gap * 8.0) : y_ctr + (gap * 8.0)
    label.new(bar_x, ly, text=txt, style=is_bull ? label.style_label_up : label.style_label_down, color=color.new(color.black, 40), textcolor=color.white, size=size.small, yloc=yloc.price)

float knn_src = knn_smooth_src ? ta.hma(close, knn_hma_len) : close
float knn_f1 = ta.rsi(knn_src, 14)
float knn_f2 = (ta.atr(14) / knn_src) * 100
[knn_st_val, knn_st_dir] = ta.supertrend(knn_st_factor, knn_st_atr)
int knn_target_trend = knn_st_dir < 0 ? 1 : -1

var float knn_ml_prob = 50.0
if st_knn and bar_index > knn_window
    float bull_votes = 0.0
    float bear_votes = 0.0
    array<float> dists = array.new_float(0)
    for i = 1 to knn_window
        float di = math.sqrt(math.pow(knn_f1 - knn_f1[i], 2) + math.pow(knn_f2 - knn_f2[i], 2))
        array.push(dists, di)
    array<float> sorted_d = array.copy(dists)
    array.sort(sorted_d)
    int th_i = math.min(knn_neighbors - 1, array.size(sorted_d) - 1)
    float dist_th = array.get(sorted_d, th_i)
    for j = 0 to array.size(dists) - 1
        if array.get(dists, j) <= dist_th
            if knn_target_trend[j + 1] > 0
                bull_votes += 1
            else
                bear_votes += 1
    float vote_sum = bull_votes + bear_votes
    knn_ml_prob := vote_sum > 0 ? (bull_votes / vote_sum) * 100 : nz(knn_ml_prob[1], 50.0)

float knn_smooth_prob = ta.ema(knn_ml_prob, knn_prob_ema)

var bool knn_ml_bull = false
if st_knn
    if knn_smooth_prob > 50 + knn_ml_buffer_pct
        knn_ml_bull := true
    else if knn_smooth_prob < 50 - knn_ml_buffer_pct
        knn_ml_bull := false

float knn_intensity = knn_ml_bull ? (knn_smooth_prob - 50) * 2 : (50 - knn_smooth_prob) * 2
float knn_glow_pow = math.pow(math.max(0, knn_intensity) / 100, knn_vibrancy) * 100
color knn_bull_glow = color.from_gradient(knn_glow_pow, 0, 100, color.new(st_bull_knn_col, 100), color.new(st_bull_knn_col, 75))
color knn_bear_glow = color.from_gradient(knn_glow_pow, 0, 100, color.new(st_bear_knn_col, 100), color.new(st_bear_knn_col, 75))
color knn_candle_bull = color.from_gradient(knn_glow_pow, 0, 100, color.new(st_bull_knn_col, 85), color.new(st_bull_knn_col, 20))
color knn_candle_bear = color.from_gradient(knn_glow_pow, 0, 100, color.new(st_bear_knn_col, 85), color.new(st_bear_knn_col, 20))

float st_atr_ref = ta.atr(14)
float st_body = math.abs(close - open)
float st_upper_wick = high - math.max(open, close)
float st_lower_wick = math.min(open, close) - low
var int last_st_bubble_bar = 0

bool knn_bear_rej = st_knn and not knn_ml_bull and high > knn_st_val and close < knn_st_val and st_upper_wick > st_body * knn_rej_mult and (bar_index - last_st_bubble_bar >= knn_bubble_gap)
bool knn_bull_rej = st_knn and knn_ml_bull and low < knn_st_val and close > knn_st_val and st_lower_wick > st_body * knn_rej_mult and (bar_index - last_st_bubble_bar >= knn_bubble_gap)

if show_st_bubbles and st_knn and knn_bull_rej
    float stem = st_atr_ref * 1.5
    float ogap = st_atr_ref * 0.05
    line.new(bar_index, low, bar_index, low - stem, color=color.new(st_bull_knn_col, 60), style=line.style_dashed)
    render_st_orb(bar_index, low - stem, format_dyn_vol(volume), bubble_size_from_vol(), st_bull_knn_col, ogap, true)
    last_st_bubble_bar := bar_index

if show_st_bubbles and st_knn and knn_bear_rej
    float stem2 = st_atr_ref * 1.5
    float ogap2 = st_atr_ref * 0.05
    line.new(bar_index, high, bar_index, high + stem2, color=color.new(st_bear_knn_col, 60), style=line.style_dashed)
    render_st_orb(bar_index, high + stem2, format_dyn_vol(volume), bubble_size_from_vol(), st_bear_knn_col, ogap2, false)
    last_st_bubble_bar := bar_index

// ════════════════════════════════════════════════════════════
// ░░  PLOTS
// ════════════════════════════════════════════════════════════
[st_cl, st_cd] = ta.supertrend(st_classic_factor, st_classic_atr)

color knn_st_line_col = st_knn ? (knn_ml_bull ? color.new(st_bull_knn_col, 60) : color.new(st_bear_knn_col, 60)) : color.new(color.gray, 100)
plot(st_knn ? knn_st_val : na, "ST KNN line", knn_st_line_col, 2, plot.style_linebr)

plot(not st_knn and st_cd < 0 ? st_cl : na, "ST Bull (classic)",
     color=color.new(bull_col, 50), linewidth=1, style=plot.style_linebr)
plot(not st_knn and st_cd > 0 ? st_cl : na, "ST Bear (classic)",
     color=color.new(bear_col, 50), linewidth=1, style=plot.style_linebr)

plotchar(show_st_glow_chars and st_knn and knn_ml_bull, "ST KNN bull char", ST_BAR_CHAR, location.bottom, knn_bull_glow, size=size.large)
plotchar(show_st_glow_chars and st_knn and not knn_ml_bull, "ST KNN bear char", ST_BAR_CHAR, location.top, knn_bear_glow, size=size.large)

bgcolor(structure_bias ==  1 ? color.new(bull_col, 97) :
        structure_bias == -1 ? color.new(bear_col, 97) : na,
        title="Structure Bias BG")

barcolor(knn_grad_candles and st_knn ? (knn_ml_bull ? knn_candle_bull : knn_candle_bear) :
         structure_bias ==  1 ? color.new(bull_col, 60) :
         structure_bias == -1 ? color.new(bear_col, 60) : na)

// ════════════════════════════════════════════════════════════
// ░░ RISK MANAGEMENT
// ════════════════════════════════════════════════════════════

long_sl = close - atr14 * atr_mult_sl
long_tp = close + atr14 * atr_mult_tp

short_sl = close + atr14 * atr_mult_sl
short_tp = close - atr14 * atr_mult_tp

// Apply exits
strategy.exit("LONG EXIT", from_entry="LONG", stop=long_sl, limit=long_tp)
strategy.exit("SHORT EXIT", from_entry="SHORT", stop=short_sl, limit=short_tp)

// ════════════════════════════════════════════════════════════
// ░░  DASHBOARD
// ════════════════════════════════════════════════════════════
var table dash = table.new(position.top_right, 3, 17,
     bgcolor=color.new(#0d0d0d, 5),
     border_color=color.new(#2a2a2a, 30), border_width=1,
     frame_color=color.new(#3a3a3a, 20), frame_width=1)

dc(c, r, t, bg, tc) =>
    table.cell(dash, c, r, t, bgcolor=bg, text_color=tc, text_size=size.small)

if show_dash and barstate.islast
    pos = color.new(#00c87a, 20)
    neg = color.new(#ff3d3d, 20)
    neu = color.new(#3a3a3a, 40)
    hd  = color.new(#1a1a2e, 10)
    wht = color.white

    dc(0, 0, "SMC CONFLUENCE ENGINE", hd,  wht)
    dc(1, 0, "BULL",                  hd,  wht)
    dc(2, 0, "BEAR",                  hd,  wht)

    dc(0, 1, "📐 SMC Bias",    neu, wht)
    dc(1, 1, structure_bias ==  1 ? "BULL ✓" : "—", structure_bias ==  1 ? pos : neu, wht)
    dc(2, 1, structure_bias == -1 ? "BEAR ✓" : "—", structure_bias == -1 ? neg : neu, wht)

    dc(0, 2, "🔄 CHoCH",       neu, wht)
    dc(1, 2, choch_bull ? "ACTIVE ✓" : "—", choch_bull ? pos : neu, wht)
    dc(2, 2, choch_bear ? "ACTIVE ✓" : "—", choch_bear ? neg : neu, wht)

    dc(0, 3, "🟦 Order Block", neu, wht)
    dc(1, 3, in_bull_ob ? "IN ZONE ✓" : "—", in_bull_ob ? pos : neu, wht)
    dc(2, 3, in_bear_ob ? "IN ZONE ✓" : "—", in_bear_ob ? neg : neu, wht)

    dc(0, 4, "💧 Liq Sweep",   neu, wht)
    dc(1, 4, recent_bull_sweep ? "RECENT ✓" : "—", recent_bull_sweep ? pos : neu, wht)
    dc(2, 4, recent_bear_sweep ? "RECENT ✓" : "—", recent_bear_sweep ? neg : neu, wht)

    dc(0, 5, "📊 Vol Profile", neu, wht)
    dc(1, 5, near_val ? "VAL ✓" : near_poc ? "POC ✓" : "—",
             (near_val or near_poc) ? pos : neu, wht)
    dc(2, 5, near_vah ? "VAH ✓" : near_poc ? "POC ✓" : "—",
             (near_vah or near_poc) ? neg : neu, wht)

    dc(0, 6, "🕐 Session Lvl", neu, wht)
    dc(1, 6, sess_level_bull ? "CONF ✓" : "—", sess_level_bull ? pos : neu, wht)
    dc(2, 6, sess_level_bear ? "CONF ✓" : "—", sess_level_bear ? neg : neu, wht)

    dc(0, 7, "📏 Trendline",   neu, wht)
    dc(1, 7, tl_bear_retest ? "RETEST ✓" : tl_bear_break ? "BREAK" : "—",
             tl_bear_retest ? pos : neu, wht)
    dc(2, 7, tl_bull_retest ? "RETEST ✓" : tl_bull_break ? "BREAK" : "—",
             tl_bull_retest ? neg : neu, wht)

    dc(0, 8, "📐 MS Trend",    neu, wht)
    dc(1, 8, ms_trend ==  1 ? "UP ✓" : "—",   ms_trend ==  1 ? pos : neu, wht)
    dc(2, 8, ms_trend == -1 ? "DOWN ✓" : "—", ms_trend == -1 ? neg : neu, wht)

    dc(0, 9, "⭐ Score",        hd,  wht)
    dc(1, 9, str.tostring(long_score)  + "/6",
             long_score  >= 4 ? pos : long_score  >= 2 ? color.new(color.orange, 30) : neu, wht)
    dc(2, 9, str.tostring(short_score) + "/6",
             short_score >= 4 ? neg : short_score >= 2 ? color.new(color.orange, 30) : neu, wht)

    dc(0,10, "🚀 SIGNAL",      hd,  wht)
    dc(1,10, long_signal  ? "▲ LONG"  : "—",
             long_signal  ? (long_score  >= 5 ? pos : color.new(bull_col, 40)) : neu, wht)
    dc(2,10, short_signal ? "▼ SHORT" : "—",
             short_signal ? (short_score >= 5 ? neg : color.new(bear_col, 40)) : neu, wht)

    dc(0,11, "📌 PDH / PDL",   neu, wht)
    dc(1,11, not na(pdh) ? str.tostring(math.round(pdh, 2)) : "—", neu, wht)
    dc(2,11, not na(pdl) ? str.tostring(math.round(pdl, 2)) : "—", neu, wht)

    dc(0,12, "📊 POC / VAH/VAL", neu, wht)
    dc(1,12, not na(poc) ? str.tostring(math.round(poc, 2)) : "—", neu, wht)
    dc(2,12, not na(vah) ? str.tostring(math.round(vah, 2)) + "/" +         (not na(val_line) ? str.tostring(math.round(val_line, 2)) : "—") : "—", neu, wht)

    dc(0,13, "📈 ATR14",       neu, wht)
    dc(1,13, str.tostring(math.round(atr14, 4)), neu, wht)
    dc(2,13, volume > vol_sma * 1.5 ? "HIGH VOL ⚡" : "Normal",
             volume > vol_sma * 1.5 ? pos : neu, wht)

    int hpz_n = 0
    if use_ob_pool and ob_pool.size() > 0
        for zi = 0 to ob_pool.size() - 1
            zn = ob_pool.get(zi)
            if zn.hpz and not zn.mit
                hpz_n += 1

    dc(0,14, "⚙ Mode / Z",     neu, wht)
    dc(1,14, use_lux ? "Lux IDM" : "Pivot", neu, wht)
    dc(2,14, use_msb_zscore ? str.tostring(momentum_z, "#.##") : "Z off",
             use_msb_zscore ? (momentum_z > msb_zscore ? pos : momentum_z < -msb_zscore ? neg : neu) : neu, wht)

    dc(0,15, "📦 HPZ / PD",    neu, wht)
    dc(1,15, use_ob_pool ? str.tostring(hpz_n) + " HPZ" : "OB pool off", hpz_n > 0 ? pos : neu, wht)
    string pd_dash_txt = not show_pd_zone ? "—" : in_pd_discount ? "Discount" : in_pd_equilibrium ? "Equilibrium" : in_pd_premium ? "Premium" : "—"
    color pd_dash_col = not show_pd_zone ? neu : in_pd_discount ? pos : in_pd_equilibrium ? color.new(color.gray, 20) : in_pd_premium ? neg : neu
    dc(2,15, pd_dash_txt, pd_dash_col, wht)

    dc(0,16, "📈 Supertrend", neu, wht)
    if st_mode == "KNN (LuxAlgo-style)"
        dc(1,16, knn_ml_bull ? "KNN bull" : "KNN bear", knn_ml_bull ? pos : neg, wht)
        dc(2,16, str.tostring(knn_smooth_prob, "#.#") + "% ML", neu, wht)
    else
        dc(1,16, "Classic", neu, wht)
        dc(2,16, str.tostring(st_classic_factor, "#.#") + " / " + str.tostring(st_classic_atr) + " ATR", neu, wht)

// ════════════════════════════════════════════════════════════
// ░░  RUNTIME ALERTS (strategies: alertcondition() has no effect)
// ════════════════════════════════════════════════════════════
alert_freq_rt = alert_timing == "Bar close" ? alert.freq_once_per_bar_close : alert.freq_once_per_bar

alert_msg_suffix() =>
    " | " + syminfo.ticker + " @ " + str.tostring(close, format.mintick)

if enable_runtime_alerts
    if long_signal
        if long_score >= 5
            alert("🔥 HIGH CONVICTION LONG (5+)" + alert_msg_suffix(), alert_freq_rt)
        else
            alert("▲ LONG Signal (SMC-CE)" + alert_msg_suffix(), alert_freq_rt)
    if short_signal
        if short_score >= 5
            alert("🔥 HIGH CONVICTION SHORT (5+)" + alert_msg_suffix(), alert_freq_rt)
        else
            alert("▼ SHORT Signal (SMC-CE)" + alert_msg_suffix(), alert_freq_rt)
    if liq_sweep_bull
        alert("💧 Sell-side liquidity swept" + alert_msg_suffix(), alert_freq_rt)
    if liq_sweep_bear
        alert("💧 Buy-side liquidity swept" + alert_msg_suffix(), alert_freq_rt)
    if choch_bull
        alert("🔄 CHoCH bullish" + alert_msg_suffix(), alert_freq_rt)
    if choch_bear
        alert("🔄 CHoCH bearish" + alert_msg_suffix(), alert_freq_rt)
    if pdh_sweep
        alert("⚠ PDH swept" + alert_msg_suffix(), alert_freq_rt)
    if pdl_sweep
        alert("⚠ PDL swept" + alert_msg_suffix(), alert_freq_rt)

Overview

SMC Confluence Engine Strategy 2 marks break of structure and change of character, order blocks, liquidity sweeps and optionally internal structure, equal highs and lows, fair value gaps and premium and discount zones. Long entries are described as a sell-side sweep, a bullish change of character and a return to a bullish order block, with shorts mirrored, scored by confluence. Orders use 10 percent of equity with a small commission. It calculates on every tick, so signals can change on the live bar.

Details

Published by

shubhamtaywade82 on GitHub

Draws

On the price chart

Markets

Crypto

Timeframes

M15-H1

Risk Notes

  • Signals can change on the live bar or after it closes (repainting)

Settings

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

SettingTypeDefaultVariable
Structure modestringPivotstructure_mode
Swing Length (BOS/CHoCH)int10smc_swing
IDM / internal swing (Lux mode)int3idm_len
MSB / break Z-score filterboolfalseuse_msb_zscore
MSB Z-score thresholdfloat0.5msb_zscore
OB Min Body %float0.3ob_body_pct
OB Expires (bars)int50ob_expire
OB scan bars (opposite candle)int10ob_scan_bars
Liquidity Swing Lookbackint20liq_lookback
Rolling high/low liquidity sweepsbooltrueliq_rolling
Structure-bound sweeps (trailing max/min)boolfalseliq_struct
Sweep Min Wick %float0.1liq_wick_pct
Show all 40 settings
SettingTypeDefaultVariable
Show Order Block Boxesbooltrueshow_ob
Show BOS/CHoCH Labelsbooltrueshow_bos
Show Liquidity Sweepsbooltrueshow_liq
Use multi-OB poolboolfalseuse_ob_pool
Max active OBsint10ob_max_n
HPZ quality ≥float80ob_hpz_at
Hide overlapping new OBsboolfalseob_hide_ov
Extend mitigated OBs brieflyboolfalseob_extend_br
OB displaystringHistoricalob_hist_mode
Mitigate OB on wick (Low/High)booltrueob_mit_wick
Show internal BOS/CHoCHboolfalseshow_internal
Internal pivot lengthint5internal_len
Equal highs / lows (EQH/EQL)boolfalseshow_eqhl
EQH/EQL threshold × ATR(200)float0.15eqhl_thresh
Fair value gaps (3-bar)boolfalseshow_fvg
FVG extend right (bars)int5fvg_extend
Premium / equilibrium / discount zonesboolfalseshow_pd_zone
Zone range lookback (bars)int50pd_lookback
PD zone labelsbooltruepd_zone_labels
Premium zone colorcolor#f23645pd_col_prem
Equilibrium zone colorcolor#787b86pd_col_eq
Discount zone colorcolor#089981pd_col_disc
Structure Swing Lengthint10ms_swing
Show HH/HL/LH/LL Labelsbooltrueshow_ms
Show Structure Linesbooltrueshow_ms_line
Trendline Pivot Lengthint10tl_pivot_len
Max Active Trendlinesint3tl_max
Retest Zone %float0.15tl_retest_pct
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

Daily Bias + FVG Strategy

by TradersPost
228 stars

This overlay strategy plots previous-day high and low, the daily open, and fair value gap zones on the price chart. It takes entries on a selectable timeframe when the FVG requirement and an optional session window allow them, then uses an ATR or fixed pip sto

Strategy v6 SMC / ICT / price actionSupport & resistance Repaints
Updated 3 months ago · None stated GitHub repository of Daily Bias + FVG Strategy
105 stars

Gold Scalping BOS & CHoCH is an overlay strategy designed for detecting market structure shifts. It calculates rolling swing highs and lows to identify Break of Structure (BOS) and Change of Character (CHoCH) events. Long signals trigger when bullish BOS and C

Strategy v6 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · None stated GitHub repository of Gold Scalping BOS & CHoCH

P404 Smoke

by Musyimi97
16 stars

P404 Smoke is a price-chart strategy that sequences an optional higher-timeframe EMA bias, a liquidity range from recent pivots, a sweep of buy-side or sell-side liquidity, and a market structure shift within a set bar window. Comments state it then looks for

Strategy v5 SMC / ICT / price actionSupport & resistance
Updated 6 months ago · MIT GitHub repository of P404 Smoke

FairValueGap C003

by ruckard
13 stars

FairValueGap C003 is an overlay strategy that identifies Fair Value Gaps (FVG) and opens trades expecting price to return and fill the gap. It calculates the distance between current price levels and recent gap boundaries to aim for partial or full gap fills.

Strategy v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · None stated GitHub repository of FairValueGap C003

SMC Strategy

by tsunafire
10 stars

SMC Strategy is a Pine Script v5 overlay strategy based on smart money concepts. It flags market structure shifts from ten-bar highs and lows, draws bullish and bearish order blocks as rectangles when that option is on, and marks liquidity sweeps with short li

Strategy v5 SMC / ICT / price actionSupport & resistance Repaints
Updated 2 years ago · None stated GitHub repository of SMC Strategy

This strategy identifies reversal opportunities based on liquidity sweeps and multi-gate confluence scoring. It evaluates setups using mandatory conditions such as swing sweeps, body and ATR filters, and dual EMA higher-timeframe trend alignment, alongside opt

Strategy v6 SMC / ICT / price actionTrend
Updated 2 days ago · MIT GitHub repository of Reversal Engine Score [WavesUnchained] Strategy

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