XAULearn

Liq Heat Map + Vol Prof v20 · ซอร์สโค้ด

Pine Script ต้นฉบับ

ไฟล์นี้ถูกคัดลอกจากต้นฉบับใน G:\Trader\PineSCript\ ตอนสร้างเว็บ จึงเป็นโค้ดชุดเดียวกับที่ทำงานอยู่บนชาร์ตจริง ณ วันที่ปรับปรุง

LiqHM_VolProf_HTFRev_v20_0.pine 2,334 บรรทัด 157 KB ต้นฉบับแก้ไขล่าสุด 11 สิงหาคม 2569 ดาวน์โหลด .pine

Pine Script ยังไม่มีไวยากรณ์เฉพาะสำหรับตัวไฮไลต์ที่ใช้ จึงไฮไลต์ด้วยไวยากรณ์ TypeScript ซึ่งใกล้เคียงกันพอที่จะอ่านง่าย ตัวเลขบรรทัดตรงกับไฟล์จริง ใช้อ้างอิงในบทเรียนได้ เช่น liqhm-v20.pine:120

สารบัญส่วนของโค้ด (48)
  1. 138 §1 UDT
  2. 206 §2 INPUTS
  3. 508 §3 COLORS
  4. 520 §4 FUNCTIONS (HTF + Kalman + RQ-Kernel + Shannon Entropy + Monte Carlo)
  5. 695 §5 GLOBAL TA PRECOMPUTE
  6. 800 §5b NEW v4 COMPUTATIONS
  7. 874 §5c ⑭ MONTE CARLO — CORE LOGIC (LuxAlgo, namespaced)
  8. 884 ⑮ Persistent MC band bounds (updated whenever MC recomputes; used by signal §19)
  9. 918 §5d ⑰ ML YZ — GLOBAL PRECOMPUTES (must run every bar)
  10. 1000 §6 AMT REJECTION SIGNALS
  11. 1009 §7 HEATMAP PIVOT ENGINE
  12. 1019 §7: ALWAYS use full lookBack_hm for h_l + pivot stats (preserves v3 display)
  13. 1056 §8 PO3 TRACKING
  14. 1082 §9 HTF PATTERN LOGIC
  15. 1092 §10 RSI DIVERGENCE LOGIC (price-scale lines only — oscillator removed)
  16. 1122 §11 FIX WINDOW BGCOLOR
  17. 1129 §12 PLOTSHAPES — AMT Rejection
  18. 1136 §13 HTF PATTERN DRAWINGS (fires on each new HTF bar)
  19. 1149 §14 BARSTATE.ISLAST — Volume Profile + Heatmap + Info Label
  20. 1187 // ══════════════════════════════════════════════════════════════════════
  21. 1189 // ══════════════════════════════════════════════════════════════════════
  22. 1281 // ══════════════════════════════════════════════════════════════════════
  23. 1283 // ══════════════════════════════════════════════════════════════════════
  24. 1380 // ══════════════════════════════════════════════════════════════════════
  25. 1381 // §14b MARKET QUALITY INFO LABEL (DC + Entropy + VPIN)
  26. 1382 // ══════════════════════════════════════════════════════════════════════
  27. 1397 §15 BARSTATE.ISLAST — PO3 UI
  28. 1474 §16 ALERTS
  29. 1499 §17 KALMAN D7 PLOTS (et20tradeview — MPL 2.0)
  30. 1520 §18 ⑭ PREDICTIVE MONTE CARLO ENGINE — VISUALIZATION (LuxAlgo, namespaced)
  31. 1625 // ⑮ persist band bounds for the confluence signal
  32. 1793 §18b ⑱ CVD-CONFLUENCE GATE — booleans consumed by ⑮/⑯ (v18, default OFF)
  33. 1824 §19 ⑮ CONFLUENCE BUY / SELL SIGNAL (v8)
  34. 1826 1) Price INSIDE Monte Carlo band (between S1 and R1)
  35. 1827 2) Near a significant level (AMT VWMA value band / RQ-Kernel)
  36. 1828 3) Multi-TF OB/OS : current(M5) + (H1 or H4) agree
  37. 1829 4) Order-flow confirm : rejection / iceberg / delta (>= i_sig_need_of)
  38. 1830 5) (optional) Kalman turn + (optional) Entropy quality
  39. 1833 1) Price INSIDE the Monte Carlo band (between S1 and R1)
  40. 1840 2) Significant-level proximity (AMT VWMA value band + RQ-Kernel)
  41. 1847 3) Multi-TF OB/OS — current(M5) AND (H1 or H4)
  42. 1851 4) Order-flow confirm (rejection / iceberg / delta direction)
  43. 1856 5) Kalman turn + Entropy quality (optional)
  44. 1894 §20 ⑯ TRIPLE MTF CONF SIGNAL (v11)
  45. 1970 §21 ⑰ ML YZ — PLOTS + SIGNALS + ALERTS
  46. 2046 §22 ⑱ HV-PIVOT LEVELS + CLV-DELTA (display-only, additive — v17)
  47. 2117 ⑲ VWAP STDEV BANDS (session-anchored · tuned for M5) ── DISPLAY-ONLY
  48. 2177 ⑳ TREND BREAKOUT + CORRECTION METER (M5) ── DISPLAY-ONLY [v20]
//@version=6
// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║  Liq Heat Map and Vol Prof  +  HTF Reversal Divergences  |  Pine v6     ║
// ║  + Kalman D7  |  Version 16                                              ║
// ║  AMT Rejection  → QC_SovereignMatrix v2.1  §5 global + §14 (verbatim)  ║
// ║  Volume Profile → QC_SovereignMatrix v2.1  §25 (verbatim)              ║
// ║  Liq Heatmap    → EA Quantum FundOS v2.1   §7 + §9 (verbatim)          ║
// ║  HTF Reversal   → LuxAlgo  (CC BY-NC-SA 4.0)                           ║
// ║  Kalman D7      → et20tradeview  (MPL 2.0)                              ║
// ║  Monte Carlo    → LuxAlgo Predictive Monte Carlo Engine (CC BY-NC-SA)  ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v3 → v4 Changelog:                                                      ║
// ║   ⑩ Hilbert Transform Dominant Cycle (Adaptive HM/VP Lookback)          ║
// ║   ⑪ RQ-Kernel Rational Quadratic Regression Lines + Uncertainty Band    ║
// ║   ⑫ Shannon Entropy Market Quality Filter                                ║
// ║   ⑬ VPIN Proxy Informed Flow Detector                                   ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v4 → v5 Changelog (nothing from v4 removed — pure addition):            ║
// ║   ⑭ Predictive Monte Carlo Engine (GBM / SRW / Historical Shuffle)      ║
// ║      • Regime filter (Trend SMA / Momentum RSI), anchor mode            ║
// ║      • Percentile path coloring, envelope, average path                 ║
// ║      • S/R projection zones + Monte Carlo dashboard + forecast candle   ║
// ║      All MC identifiers namespaced (mc_/MC_) to avoid any collision.     ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v5 → v8 Changelog (pure addition, nothing removed):                     ║
// ║   ⑮ Confluence BUY/SELL Signal — fires when ALL align:                   ║
// ║      • Price INSIDE Monte Carlo band (between S1 and R1)                 ║
// ║      • Near a significant level (AMT VWMA value band / RQ-Kernel)        ║
// ║      • Multi-TF OB/OS : current(M5) + (H1 or H4) agree                   ║
// ║      • Order-flow confirm: rejection / iceberg / delta (≥ N of 3)        ║
// ║      • Kalman turn + Shannon-Entropy quality (optional confluence)       ║
// ║      Independent toggles: Labels on/off · Arrows(plotshape) on/off       ║
// ║      + 2 alerts (Confluence BUY / SELL)                                  ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v8 → v9 Changelog (pure default tuning, nothing removed or added):      ║
// ║   INPUT DEFAULT CHANGES:                                                  ║
// ║   ⑭ MC Regime Filter: None → Trend (SMA)                                ║
// ║   ⑭ MC Historical Lookback: 100 → 200                                   ║
// ║   ⑭ MC Volatility Multiplier: 1.0 → 1.2                                 ║
// ║   ⑮ Confluence MC band zone: Inside (S1-R1) → Lower/Upper half          ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v9 → v10 Changelog (pure addition, nothing from v9 removed):            ║
// ║   ⑯ Triple MTF Conf Signal — overlay ★ CONF BUY / ★ CONF SELL labels   ║
// ║      Condition: M5 + H1 + H4 ALL show WMA-RSI OS (BUY) / OB (SELL)      ║
// ║               + AMT Rejection on the same bar (_rej_bot / _rej_top)      ║
// ║      OS = WMA(RSI(5),mw) < WMA(RSI(20),mw) AND slow < 50  at ALL 3 TFs  ║
// ║      OB = WMA(RSI(5),mw) > WMA(RSI(20),mw) AND slow > 50  at ALL 3 TFs  ║
// ║      mw=20 matches MoConf v11 momentumWindow (same fast/slow RSI WMAs    ║
// ║      as the MLMI kNN features — no state divergence, no init artefacts)  ║
// ║      Independent toggles: Labels on/off · Arrows(plotshape) on/off       ║
// ║      + 2 alertconditions                                                  ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v10 → v11 Changelog (targeted fix, nothing from v10 removed):           ║
// ║   ⑯ Fixed false positives — added absolute fast WMA RSI threshold        ║
// ║      Root cause: "fast<slow AND slow<50" fires when fast=41–49 (mildly   ║
// ║      bearish) but MLMI kNN OS zone requires fast < 40 (genuine OS).      ║
// ║      v11 OS = fast<slow AND fast<i_tri_os(40) AND slow<50  — ALL 3 TFs  ║
// ║      v11 OB = fast>slow AND fast>i_tri_ob(60) AND slow>50  — ALL 3 TFs  ║
// ║      2 new inputs: i_tri_os (default 40) · i_tri_ob (default 60)        ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v11 → v12 Changelog (pure addition, nothing from v11 removed):          ║
// ║   ⑰ ML YZ — Adaptive Yang-Zhang Volatility Bands (ML YZ v1)             ║
// ║      • Pure Yang-Zhang 3-component estimator (OC + RS + CO)             ║
// ║      • kNN regime classifier: 3 features (BW-ROC / RelVol / PriceDist)  ║
// ║        → Balance(1) / Neutral(0) / Imbalance(-1)  via YZ vol rank label ║
// ║      • Adaptive band width: tighter on Balance, wider on Imbalance       ║
// ║      • KAMA middle band (or EMA/SMA/WMA/HMA — selectable)               ║
// ║      • AMT signals: Initiative BUY/SELL + Responsive BUY/SELL            ║
// ║      • Value Area inner bands + VWAP-proxy POC line                      ║
// ║      • Regime label on last bar + 4 alertconditions                      ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v12 → v16 Changelog (bug fix only — no logic/inputs/signals changed):    ║
// ║   ⑰ FIX: ML YZ Regime label stacked up on every bar close (see report).  ║
// ║      Cause: the last-bar label used a bare label.new() with no var/delete ║
// ║      → each closed bar committed its label permanently while the next     ║
// ║      bar drew another, piling up IMBALANCE/BALANCE/NEUTRAL · YZ=x.xx.     ║
// ║      Fix: var label _yz_lbl + label.delete() before redraw (same idiom    ║
// ║      as the §14b market-quality label) → exactly ONE YZ label persists.   ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v16 → v17 Changelog (pure addition, nothing removed — display-only):      ║
// ║   ⑱ HV-Pivot Levels + CLV-Delta   (default OFF — levels-eyes, no gate)    ║
// ║      • Volume-gated pivots (BigBeluga concept, ported as a FILTER not the  ║
// ║        full zone system): ta.pivothigh/low kept only if the pivot bar's    ║
// ║        volume[len] > SMA(volume, maLen) × mult.                            ║
// ║      • CVD via CLV×vol :  ((C−L)−(H−C))/(H−L) × volume  — replaces the     ║
// ║        weak candle-direction proxy (close≥open?+v:−v). Same principle as   ║
// ║        KV-MACD v8 ㉓ leg-delta → one delta definition across the suite.    ║
// ║      • Draws ONE horizontal ray per level (extend right) + a CVD label.    ║
// ║        NO boxes · NO polylines · capped last N per side (default 3) →      ║
// ║        ≤ 6 lines + ≤ 6 labels total. Anti-bloat by construction.           ║
// ║      • Does NOT touch ⑮/⑯/⑰ signals. Additive; delete §22 to revert.      ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v17 → v18 Changelog (OPT-IN entry-logic — ALL gates default OFF):         ║
// ║   ⑱ CVD-Confluence GATE — wire the HV-Pivot levels into ⑮/⑯ as an extra   ║
// ║      directional confluence (KV-MACD i_ladGate discipline: OFF by default).║
// ║      • BUY passes only if price is within Xatr of an HV-SUPPORT whose      ║
// ║        CLV-CVD ≥ min (accumulation-confirmed floor).                       ║
// ║      • SELL passes only if price is within Xatr of an HV-RESISTANCE whose  ║
// ║        CLV-CVD ≤ −min (distribution-confirmed ceiling).                    ║
// ║      • Independent toggles i_hv_gate_sig (⑮) / i_hv_gate_tri (⑯); both     ║
// ║        auto-relax to no-op unless ⑱ levels are enabled. When OFF the ⑮/⑯   ║
// ║        signals are byte-for-byte the validated v11 behaviour.              ║
// ║      • §18b computes the gate booleans BEFORE §19 (reads prior-bar levels, ║
// ║        no lookahead). §22 detection/labels unchanged.                      ║
// ║   ⚠ CHANGES ENTRY LOGIC when enabled → must pass walk-forward OOS before   ║
// ║     any live use (gospel: no un-OOS'd entry change reaches capital).        ║
// ╚═══════════════════════════════════════════════════════════════════════════╝
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v19 → v20 Changelog (pure addition, nothing removed):                    ║
// ║   ⑳ Trend Breakout + Correction Meter (M5) — DISPLAY-ONLY.               ║
// ║      (A) Donchian N-bar high/low breakout lines (100 + 200) = trend-      ║
// ║          follow trigger, with BO up/down markers. (B) Correction Meter    ║
// ║          table = MR-into-trend read: after a breakout, measures pullback  ║
// ║          depth as a Fib retrace of the impulse (deeper = higher-prob      ║
// ║          continuation), plus a dip-buy zone box (Fib .5–.618). Uses only  ║
// ║          line/label/box/table → 0 plot-budget (no RE10140 risk). Nothing  ║
// ║          feeds any signal / gate / score — pure visual reference.         ║
// ╠═══════════════════════════════════════════════════════════════════════════╣
// ║  v18 → v19 Changelog (pure addition, nothing removed):                    ║
// ║   ⑲ VWAP Stdev Bands (session-anchored, tuned for M5) — DISPLAY-ONLY.     ║
// ║      Ported from "VWAP Stdev Bands v2 Mod" (Pine v2 → v6). Session VWAP    ║
// ║      + up to 5 stdev band groups + over/under fills + optional VWAP-zone  ║
// ║      bar color (default OFF so it never fights existing bar coloring).    ║
// ║      Daily anchor = correct intraday VWAP on M5. Nothing fed to any       ║
// ║      signal / gate / score — pure visual reference.                       ║
// ╚═══════════════════════════════════════════════════════════════════════════╝
indicator(
     title              = "Liq Heat Map and Vol Prof v20",
     shorttitle         = "LiqHM+VP+Kalman+MC v20",
     overlay            = true,
     max_lines_count    = 500,
     max_labels_count   = 500,
     max_boxes_count    = 500,
     max_polylines_count= 100,
     max_bars_back      = 500)

// ═══════════════════════════════════════════════════════════════════════════
// §1  UDT
// ═══════════════════════════════════════════════════════════════════════════
type pivot
    float value
    int   index
    float volume_
    float vol
    bool  isLower

// ── ⑱ HV-Pivot level (one persistent ray + its snapshot CLV-CVD) ───────────
type hvLevel
    line  ln
    float price
    float cvd

type HTFData
    float o
    float h
    float l
    float c
    int   oIdx
    int   hIdx
    int   lIdx
    float delta
    int   startTime

type HTFCandleUI
    box   body
    line  wick
    line  oM
    line  hM
    line  lM
    line  cM
    label oL
    label hL
    label lL
    label cL
    label dL
    label tL

// ── ⑭ Monte Carlo UDT (LuxAlgo) ───────────────────────────────────────────
type LCG
    int seed

method next(LCG this) =>
    this.seed := (1103515245 * this.seed + 12345) % 2147483647
    if this.seed < 0
        this.seed += 2147483647
    float(this.seed) / 2147483647.0

method nextInt(LCG this, int min, int max) =>
    this.seed := (1103515245 * this.seed + 12345) % 2147483647
    if this.seed < 0
        this.seed += 2147483647
    min + (this.seed % (max - min + 1))

method nextGaussian(LCG this) =>
    float u1 = this.next()
    if u1 <= 0.00001
        u1 := 0.00001
    float u2 = this.next()
    math.sqrt(-2.0 * math.log(u1)) * math.cos(2.0 * math.pi * u2)

type Path
    array<chart.point> pts
    float endPrice

// ═══════════════════════════════════════════════════════════════════════════
// §2  INPUTS
// ═══════════════════════════════════════════════════════════════════════════

// ── ① AMT Rejection ────────────────────────────────────────────────────────
const string G_AMT = "① AMT Rejection  (VWMA50 ± StdDev50 × 1.5  — Fixed)"
bool   i_show_amt = input.bool(true, "Show Iceberg / Rejection Signals", group=G_AMT)

// ── ② Volume Profile ───────────────────────────────────────────────────────
const string G_VP = "② Volume Profile  (QC_SovereignMatrix v2.1)"
bool   i_show_vp         = input.bool(true,  "Show Volume Profile",              group=G_VP)
int    i_vp_bars         = input.int(500,    "VP Lookback Bars",                 group=G_VP, minval=20, maxval=500)
int    i_vp_rows         = input.int(100,    "VP Row Count",                     group=G_VP, minval=5,  maxval=100)
float  i_vp_pct          = input.float(90.0, "Value Area %",                     group=G_VP, minval=0,  maxval=100)
bool   i_vp_show_boxes   = input.bool(false, "Show VP Boxes (Volume Histogram)", group=G_VP)
bool   i_vp_show_poc     = input.bool(false, "Show POC Line",                    group=G_VP)
color  i_c_poc_line      = input.color(color.new(#FF1744, 0), "POC Color",       group=G_VP)
string i_poc_style       = input.string("Dotted", "POC Style",                   group=G_VP, options=["Solid","Dashed","Dotted"])
int    i_poc_width       = input.int(2, "POC Width",                             group=G_VP, minval=1, maxval=5)
bool   i_vp_show_poc_lbl = input.bool(true, "Show POC Label",                    group=G_VP)
bool   i_vp_show_vah     = input.bool(true,  "Show VAH Line",                    group=G_VP)
color  i_c_vah_line      = input.color(color.new(#00BCD4, 0), "VAH Color",       group=G_VP)
string i_vah_style       = input.string("Dotted", "VAH Style",                   group=G_VP, options=["Solid","Dashed","Dotted"])
int    i_vah_width       = input.int(2, "VAH Width",                             group=G_VP, minval=1, maxval=5)
bool   i_vp_show_vah_lbl = input.bool(true, "Show VAH Label",                    group=G_VP)
bool   i_vp_show_val     = input.bool(true,  "Show VAL Line",                    group=G_VP)
color  i_c_val_line      = input.color(color.new(#00BCD4, 0), "VAL Color",       group=G_VP)
string i_val_style       = input.string("Dotted", "VAL Style",                   group=G_VP, options=["Solid","Dashed","Dotted"])
int    i_val_width       = input.int(2, "VAL Width",                             group=G_VP, minval=1, maxval=5)
bool   i_vp_show_val_lbl = input.bool(true, "Show VAL Label",                    group=G_VP)

// ── ③ Liquidity Heatmap ────────────────────────────────────────────────────
const string grp_hm    = "③ Liquidity Heatmap  (EA Quantum FundOS v2.1)"
int    lookBack_hm     = input.int(377,  "Calculated Bars",                      group=grp_hm, minval=10, maxval=500)
int    hm_offset       = input.int(30,   "➡️ Profile X-Axis Offset",              group=grp_hm, minval=0,  maxval=150)
bool   displPf         = input.bool(true, "Show Profile",                        group=grp_hm)
bool   i_hm_show_boxes = input.bool(true, "Show Heatmap Boxes",                  group=grp_hm)
bool   i_hm_show_lines = input.bool(true, "Show Heatmap Lines",                  group=grp_hm)
int    bins            = input.int(85,   "Resolution (Bins)",                     group=grp_hm, minval=5, maxval=200)
color  sellColor       = input.color(color.new(#FF6D00, 0), "Sell Liquidity Color", group=grp_hm)
color  buyColor        = input.color(color.new(#00E676, 0), "Buy Liquidity Color",  group=grp_hm)
int    i_sell_tr       = input.int(19, "Sell Line Transparency (0=opaque)",      group=grp_hm, minval=0, maxval=100)
int    i_buy_tr        = input.int(19, "Buy Line Transparency (0=opaque)",       group=grp_hm, minval=0, maxval=100)
bool   poc_show        = input.bool(true, "Show POC",                            group=grp_hm)
color  maxColor        = input.color(color.new(#6A1E9A, 0), "Max Point (POC) Color", group=grp_hm)

// ── ④ LBMA / CME Fix Windows ───────────────────────────────────────────────
const string G_FIX = "④ LBMA / CME Fix Windows"
bool  i_show_fix    = input.bool(true,  "Show Fix Window Backgrounds",           group=G_FIX,
     tooltip="Highlights LBMA AM, LBMA PM, and CME NY fix windows on the price chart.\nTimes are based on UTC+0.")
bool  i_fix_am_en   = input.bool(true,  "LBMA AM Fix  (10:00 – 10:30 UTC)",      group=G_FIX)
bool  i_fix_pm_en   = input.bool(true,  "LBMA PM Fix  (15:00 – 15:15 UTC)",      group=G_FIX)
bool  i_fix_cme_en  = input.bool(true,  "CME NY Close (13:20 – 13:30 UTC)",      group=G_FIX)
int   i_fix_utc     = input.int(0,      "UTC Offset (hours)",                    group=G_FIX, minval=-12, maxval=14)
color i_col_fix_am  = input.color(color.new(#4CAF50, 88), "LBMA AM Color",      group=G_FIX)
color i_col_fix_pm  = input.color(color.new(#4CAF50, 88), "LBMA PM Color",      group=G_FIX)
color i_col_fix_cme = input.color(color.new(#4CAF50, 88), "CME NY Color",       group=G_FIX)

// ── ⑤ HTF Reversal Patterns ────────────────────────────────────────────────
const string G_HTF = "⑤ HTF Reversal Patterns  (LuxAlgo)"
string htfInput           = input.timeframe("60", "High Timeframe",             group=G_HTF, tooltip="Timeframe to check for reversal patterns.")
bool   showEngulfingInput = input.bool(true,      "Show Engulfing Patterns",    group=G_HTF)
bool   showPinBarsInput   = input.bool(true,      "Show Pin Bars",              group=G_HTF, tooltip="Includes Hammer and Shooting Star patterns.")
color  bullPatternColInput = input.color(#00E676, "Bullish Pattern Color",      group=G_HTF, inline="HTFcol")
color  bearPatternColInput = input.color(#FF6D00, "Bearish Pattern Color",      group=G_HTF, inline="HTFcol")

// ── ⑥ RSI Divergence ───────────────────────────────────────────────────────
const string G_RSI = "⑥ RSI Divergence  (LuxAlgo)"
bool   showDivInput    = input.bool(true, "Show RSI Divergences",               group=G_RSI)
int    rsiLenInput     = input.int(14,    "RSI Length",                         group=G_RSI, minval=1)
int    lbRInput        = input.int(5,     "Pivot Right Lookback",               group=G_RSI)
int    lbLInput        = input.int(5,     "Pivot Left Lookback",                group=G_RSI)
color  bullDivColInput = input.color(#089981, "Bullish Div Color",              group=G_RSI, inline="DIVcol")
color  bearDivColInput = input.color(#f23645, "Bearish Div Color",              group=G_RSI, inline="DIVcol")

// ── ⑦ HTF PO3 ──────────────────────────────────────────────────────────────
const string G_PO3 = "⑦ HTF PO3  (LuxAlgo)"
bool   showPo3Input       = input.bool(true,  "Show HTF PO3",                   group=G_PO3, tooltip="Projects HTF candles to the right of price.")
int    candleCountInput   = input.int(3,      "Candles to Show",                group=G_PO3, minval=1, maxval=10)
int    po3OffsetInput     = input.int(70,     "Right Offset (Bars)",            group=G_PO3, minval=5, tooltip="If Heatmap Profile is also enabled, increase this to avoid overlap.")
bool   showPo3LabelsInput = input.bool(true,  "Show PO3 Price Labels",          group=G_PO3)
bool   showPo3DeltaInput  = input.bool(true,  "Show PO3 Running Delta",         group=G_PO3)

// ── ⑧ Alerts ───────────────────────────────────────────────────────────────
const string G_ALERT = "⑧ Alerts"
bool   alertBullDivInput = input.bool(true, "Bullish Divergence",               group=G_ALERT)
bool   alertBearDivInput = input.bool(true, "Bearish Divergence",               group=G_ALERT)
bool   alertBullEngInput = input.bool(true, "Bullish Engulfing",                group=G_ALERT)
bool   alertBearEngInput = input.bool(true, "Bearish Engulfing",                group=G_ALERT)
bool   alertHammerInput  = input.bool(true, "Hammer",                           group=G_ALERT)
bool   alertStarInput    = input.bool(true, "Shooting Star",                    group=G_ALERT)

// ── ⑨ Kalman D7  (et20tradeview — MPL 2.0) ────────────────────────────────
const string G_KLM = "⑨ Kalman D7  (et20tradeview)"
bool   show_pul    = input.bool(false,  "Show EMA",                             group=G_KLM)
source             = input(close,       "Source",                                group=G_KLM)
float  factor      = input.float(0.5,   "EMA Length 1",   minval=0.1, step=0.1, group=G_KLM)
float  factor2     = input.float(0.5,   "EMA Length 2",   minval=0.1, step=0.1, group=G_KLM)
int    r_ratiob    = input.int(200,     "R base",         step=5,               group=G_KLM, tooltip="Uncertainty measurement lookback")
float  q_ratiob    = input.float(0.002, "q multiplier base", step=0.001,        group=G_KLM, tooltip="Process noise variance, higher = more responsive")
float  r_gate_multb = input.float(1.5, "R Gate Multiplier base", step=0.1,     group=G_KLM, tooltip="R sensitivity baseline")
int    r_ratios    = input.int(20,      "R fast",         step=5,               group=G_KLM, tooltip="Uncertainty measurement lookback (fast)")
float  q_ratios    = input.float(0.005, "q multiplier fast", step=0.001,        group=G_KLM, tooltip="Process noise variance (fast), higher = more responsive")
float  r_gate_mults = input.float(1.2, "R Gate Multiplier fast", step=0.1,     group=G_KLM, tooltip="R sensitivity baseline (fast)")

// ── ⑩ Hilbert DC — Adaptive Line Reach ────────────────────────────────────
// DC adaptive controls HM LINE REACH (how far back lines extend on chart).
// Pivot stats (h_l array, vb_max, step_bin) always use full lookBack_hm input
// to keep box text identical to v3. VP always uses full i_vp_bars input.
const string G_DC = "⑩ Hilbert DC — Adaptive HM Line Reach"
bool  i_dc_en        = input.bool(true,  "Enable Adaptive DC Line Reach (HM)", group=G_DC, tooltip="Adjusts how far Heatmap lines extend back based on dominant cycle.\nPivot stats and box text are unchanged (identical to v3).\nVP lookback is also unaffected.")
bool  i_dc_show_lbl  = input.bool(true,  "Show DC Info Label",                  group=G_DC)
int   i_dc_hm_mult   = input.int(13,     "HM Line Reach Multiplier (×DC)",      group=G_DC, minval=4, maxval=30, tooltip="Heatmap line reach = DC × this bars. Default 13 (Fibonacci). Fast market → shorter reach. Slow market → longer reach.")

// ── ⑪ RQ-Kernel (Rational Quadratic Regression) ───────────────────────────
const string G_RQ = "⑪ RQ-Kernel (Rational Quadratic Regression)"
bool  i_rq_en        = input.bool(true,  "Enable RQ-Kernel Lines",              group=G_RQ)
int   i_rq_h_fast    = input.int(8,      "Fast Bandwidth (h)",                  group=G_RQ, minval=2, maxval=50)
int   i_rq_h_slow    = input.int(25,     "Slow Bandwidth (h)",                  group=G_RQ, minval=5, maxval=100)
float i_rq_alpha     = input.float(1.0,  "Alpha (Multi-Scale)",                 group=G_RQ, step=0.25, minval=0.25, maxval=10.0)
int   i_rq_len       = input.int(50,     "Max Lookback (bars)",                 group=G_RQ, minval=10, maxval=200)
bool  i_rq_show_ub   = input.bool(true,  "Show Uncertainty Band",               group=G_RQ)
color i_rq_fast_col  = input.color(color.new(#00E5FF, 10), "RQ Fast Color",     group=G_RQ)
color i_rq_slow_col  = input.color(color.new(#FF6F00, 10), "RQ Slow Color",     group=G_RQ)
color i_rq_ub_col    = input.color(color.new(#607D8B, 88), "Uncertainty Band Color", group=G_RQ)

// ── ⑫ Shannon Entropy (Market Quality Filter) ─────────────────────────────
const string G_ENT = "⑫ Shannon Entropy (Market Quality Filter)"
bool  i_ent_en       = input.bool(true,  "Enable Entropy Filter",               group=G_ENT)
int   i_ent_len      = input.int(50,     "Lookback (bars)",                     group=G_ENT, minval=20, maxval=200)
int   i_ent_bins     = input.int(8,      "Discretization Bins",                 group=G_ENT, minval=4, maxval=16)
bool  i_ent_show_lbl = input.bool(true,  "Show Entropy Label",                  group=G_ENT)
float i_ent_str_thr  = input.float(0.35, "Structured Threshold (<)",            group=G_ENT, step=0.05, minval=0.10, maxval=0.60)
float i_ent_ran_thr  = input.float(0.65, "Random Threshold (>)",                group=G_ENT, step=0.05, minval=0.40, maxval=0.90)

// ── ⑬ VPIN — Informed Flow Detector ───────────────────────────────────────
const string G_VPIN = "⑬ VPIN — Informed Flow Detector"
bool  i_vpin_en       = input.bool(true,  "Enable VPIN Detection",              group=G_VPIN)
int   i_vpin_len      = input.int(20,     "Smoothing Length (bars)",            group=G_VPIN, minval=5, maxval=100)
float i_vpin_tox      = input.float(0.35, "Toxic Flow Threshold",               group=G_VPIN, step=0.05, minval=0.15, maxval=0.70)
float i_vpin_bal      = input.float(0.15, "Balanced Flow Threshold",            group=G_VPIN, step=0.05, minval=0.05, maxval=0.40)
bool  i_vpin_show_lbl = input.bool(true,  "Show VPIN Label",                    group=G_VPIN)
color i_vpin_tox_col  = input.color(color.new(#FF1744, 0), "Toxic Color",       group=G_VPIN)
color i_vpin_bal_col  = input.color(color.new(#00E676, 0), "Balanced Color",    group=G_VPIN)

// ── ⑭ Predictive Monte Carlo Engine (LuxAlgo) ─────────────────────────────
// Verbatim LuxAlgo logic; identifiers namespaced mc_/MC_ to coexist with v4.
const string MC_METHOD_GBM = "Geometric Brownian Motion"
const string MC_METHOD_SRW = "Simple Random Walk"
const string MC_METHOD_HS  = "Historical Shuffle"

const string MC_REGIME_NONE     = "None"
const string MC_REGIME_TREND    = "Trend (SMA)"
const string MC_REGIME_MOMENTUM = "Momentum (RSI)"

const color MC_DATA       = #DBDBDB
const color MC_HEADERS    = #808080
const color MC_BACKGROUND = #161616
const color MC_BORDERS    = #2E2E2E

const string MC_TOP_RIGHT    = 'Top Right'
const string MC_BOTTOM_RIGHT = 'Bottom Right'
const string MC_BOTTOM_LEFT  = 'Bottom Left'

const string MC_TINY   = 'Tiny'
const string MC_SMALL  = 'Small'
const string MC_NORMAL = 'Normal'
const string MC_LARGE  = 'Large'
const string MC_HUGE   = 'Huge'

const string G_MC      = "⑭ Monte Carlo — Settings  (LuxAlgo)"
const string G_MC_STY  = "⑭ Monte Carlo — Style"
const string G_MC_DASH = "⑭ Monte Carlo — Dashboard"

bool   mc_enableInput = input.bool(true, "Enable Monte Carlo Engine", group=G_MC, tooltip="Master switch for the Predictive Monte Carlo module. When off, no MC drawings or dashboard are rendered.")
string mc_methodInput = input.string(MC_METHOD_GBM, "Simulation Method", options = [MC_METHOD_GBM, MC_METHOD_SRW, MC_METHOD_HS], group = G_MC, tooltip = "Select the mathematical method used to project the future price paths.")
string mc_regimeInput = input.string(MC_REGIME_TREND, "Regime Filter", options = [MC_REGIME_NONE, MC_REGIME_TREND, MC_REGIME_MOMENTUM], group = G_MC, tooltip = "Only sample past returns and volatilities that match the current market regime. Works for all methods.")
int    mc_lookbackInput = input.int(200, "Historical Lookback", minval = 10, maxval = 1000, group = G_MC, tooltip = "Number of historical bars used to calculate drift and volatility.")
int    mc_projInput     = input.int(50, "Projection Length", minval = 5, maxval = 200, group = G_MC, tooltip = "Number of bars to project into the future.")
int    mc_simInput      = input.int(200, "Simulation Count", minval = 2, maxval = 200, group = G_MC, tooltip = "Number of paths to simulate. Max 200.")
float  mc_volMulti      = input.float(1.2, "Volatility Multiplier", minval = 0.1, maxval = 5.0, step = 0.1, group = G_MC, tooltip = "Multiply the historical volatility to stress-test projections. Higher values make GBM and SRW deviate more.")
int    mc_seedInput     = input.int(12345, "Random Seed", group = G_MC, tooltip = "Change this value to generate an entirely new set of random paths.")
bool   mc_anchorInput   = input.bool(true, "Enable Anchor Mode", group = G_MC, tooltip = "Locks the projection and only updates every X bars, leaving historical S/R zones on the chart to see how price reacted.")
int    mc_anchorStep    = input.int(100, "Anchor Update Step (Bars)", minval = 10, group = G_MC, tooltip = "How often to cast a new historical projection when Anchor Mode is enabled.")
bool   mc_anchorPaths   = input.bool(false, "Draw Paths in Anchor Mode", group = G_MC, tooltip = "TradingView only allows 100 polylines per chart. Enabling this in Anchor Mode will cause older envelopes to disappear.")

float  mc_topPercentile = input.float(90.0, "Top Path Percentile", minval = 50.0, maxval = 100.0, group = G_MC_STY, tooltip = "Paths ending above this percentile are colored with the Top Color.")
float  mc_botPercentile = input.float(10.0, "Bottom Path Percentile", minval = 0.0, maxval = 50.0, group = G_MC_STY, tooltip = "Paths ending below this percentile are colored with the Bottom Color.")
color  mc_topColor      = input.color(#089981, "Top Color", group = G_MC_STY, inline = "mccolors")
color  mc_midColor      = input.color(color.gray, "Middle Color", group = G_MC_STY, inline = "mccolors")
color  mc_bottomColor   = input.color(#f23645, "Bottom Color", group = G_MC_STY, inline = "mccolors")
int    mc_transpInput   = input.int(60, "Path Transparency", minval = 0, maxval = 100, group = G_MC_STY)
color  mc_avgColor      = input.color(color.new(color.white, 0), "Average Line Color", group = G_MC_STY)
color  mc_envColor      = input.color(color.new(color.gray, 90), "Envelope Fill Color", group = G_MC_STY)
bool   mc_srInput       = input.bool(true, "Show S/R Levels", group = G_MC_STY, tooltip = "Display horizontal support and resistance lines based on the projection limits.")

bool   mc_dashboardInput         = input.bool(true, 'Show Dashboard', group = G_MC_DASH, tooltip = "Enable or disable the Monte Carlo dashboard.")
string mc_dashboardPositionInput = input.string(MC_TOP_RIGHT, 'Position', group = G_MC_DASH, tooltip = "Select the dashboard location.", options = [MC_TOP_RIGHT, MC_BOTTOM_RIGHT, MC_BOTTOM_LEFT])
string mc_dashboardSizeInput     = input.string(MC_NORMAL, 'Size', group = G_MC_DASH, tooltip = "Select the dashboard size.", options = [MC_TINY, MC_SMALL, MC_NORMAL, MC_LARGE, MC_HUGE])
bool   mc_dash2Input             = input.bool(true, 'Show Next Candle Prediction', group = G_MC_DASH)
color  mc_bullColor              = input.color(#089981, "Bullish Color", group = G_MC_DASH, inline = "mcc1")
color  mc_bearColor              = input.color(#f23645, "Bearish Color", group = G_MC_DASH, inline = "mcc1")

// ── ⑮ Confluence Buy/Sell Signal ───────────────────────────────────────────
const string G_SIG = "⑮ Confluence Buy/Sell Signal"
bool   i_sig_en      = input.bool(true,  "Enable Confluence Signal (master)", group=G_SIG, tooltip="Master switch. Fires only when ALL conditions align:\n• Price INSIDE Monte Carlo band (between S1 and R1)\n• Near a significant level (AMT value band / RQ-Kernel)\n• Multi-TF OB/OS : current(M5) + (H1 or H4)\n• Order-flow confirm: rejection / iceberg / delta\n• (optional) Kalman turn + Entropy quality")
bool   i_sig_show_lbl= input.bool(true,  "  Show LABELS (▲BUY / ▼SELL)",  group=G_SIG)
bool   i_sig_show_shp= input.bool(true,  "  Show ARROWS (plotshape)",      group=G_SIG)
bool   i_sig_confirm = input.bool(true,  "Signal on bar close only (less repaint)", group=G_SIG)
string i_sig_band    = input.string("Lower/Upper half", "MC band zone", group=G_SIG, options=["Inside (S1-R1)","Lower/Upper half"], tooltip="Inside = price anywhere between S1 and R1.\nLower/Upper half = BUY only below mean, SELL only above mean.")
bool   i_sig_use_kal = input.bool(true,  "Require Kalman turn confirm",   group=G_SIG, tooltip="BUY: Kalman fast ticking up. SELL: ticking down.")
bool   i_sig_use_ent = input.bool(true,  "Require Entropy quality",       group=G_SIG, tooltip="Skip signals when market is RANDOM (low quality).")
bool   i_sig_ent_str = input.bool(false, "  Entropy must be STRUCTURED",  group=G_SIG, tooltip="Stricter: require STRUCTURED (not just not-random).")
int    i_sig_rsi_len = input.int(14,     "RSI Length (OB/OS)",            group=G_SIG, minval=2)
float  i_sig_ob      = input.float(70.0, "Overbought level (≥)",          group=G_SIG, step=1.0)
float  i_sig_os      = input.float(30.0, "Oversold level (≤)",            group=G_SIG, step=1.0)
string i_sig_htf1    = input.timeframe("60",  "HTF 1 (H1)",               group=G_SIG)
string i_sig_htf2    = input.timeframe("240", "HTF 2 (H4)",               group=G_SIG)
float  i_sig_mc_tol  = input.float(0.0,  "MC band edge tolerance ($)",    group=G_SIG, step=0.1, minval=0.0, tooltip="How far inside/outside the MC R1/S1 band still counts as 'at edge'. 0 = exact edge.")
float  i_sig_lvl_atr = input.float(0.5,  "Significant-level proximity (xATR14)", group=G_SIG, step=0.05, tooltip="Price must be within this × ATR of a significant level (AMT VWMA value band / RQ-Kernel).")
int    i_sig_need_of = input.int(1,      "Min order-flow confirms (1-3)", group=G_SIG, minval=1, maxval=3, tooltip="Need at least this many of: rejection · iceberg · delta direction.")
int    i_sig_gap     = input.int(5,      "Min bars between signals",      group=G_SIG, minval=1, maxval=50)
bool   i_sig_use_rq  = input.bool(true,  "Include RQ-Kernel as a level",  group=G_SIG)
color  i_sig_buy_col = input.color(#00E676, "BUY Label Color",            group=G_SIG)
color  i_sig_sell_col= input.color(#FF1744, "SELL Label Color",           group=G_SIG)
string i_sig_size    = input.string("Normal", "Label Size",               group=G_SIG, options=["Tiny","Small","Normal","Large"])

// ── ⑯ Triple MTF Conf Signal ──────────────────────────────────────────────
const string G_TRI = "⑯ Triple MTF Conf Signal  (M5+H1+H4 WMA-RSI + AMT Rejection)"
bool   i_tri_en       = input.bool(true,  "Enable Triple MTF Conf Signal",         group=G_TRI,
     tooltip="★ CONF BUY : WMA(RSI) bearish on ALL 3 TFs + AMT rejection ↑ (wick below VAL, close above).\n★ CONF SELL: WMA(RSI) bullish on ALL 3 TFs + AMT rejection ↓ (wick above VAH, close below).\nOS : fast WMA(RSI(5),mw) < slow WMA(RSI(20),mw)  AND  slow < 50  at all 3 TFs.\nOB : fast > slow  AND  slow > 50.\nMatches MoConf v11 MLMI momentum direction (same RSI features, no state divergence).")
bool   i_tri_show_lbl = input.bool(true,  "  Show Labels (★ CONF BUY / SELL)",    group=G_TRI)
bool   i_tri_show_shp = input.bool(true,  "  Show Arrows (plotshape)",             group=G_TRI)
string i_tri_m5_tf    = input.timeframe("5",   "Base TF (M5)",                     group=G_TRI,
     tooltip="Timeframe for the shortest momentum check. Default M5 — explicit, not chart-TF dependent.")
string i_tri_htf1     = input.timeframe("60",  "HTF 1 (H1)",                       group=G_TRI)
string i_tri_htf2     = input.timeframe("240", "HTF 2 (H4)",                       group=G_TRI)
int    i_tri_mw       = input.int(20,  "Momentum Window (WMA length of RSI)",      group=G_TRI, minval=5, maxval=100,
     tooltip="WMA length applied to both RSI(5) and RSI(20).\nDefault 20 = MoConf v11 momentumWindow.\nLarger → smoother, fewer signals. Smaller → faster, more noise.")
float  i_tri_os       = input.float(40.0, "OS threshold (fast WMA RSI ≤)",         group=G_TRI, step=1.0, minval=20.0, maxval=49.0,
     tooltip="Fast WMA(RSI(5),mw) must be below this at ALL 3 TFs for OS (BUY setup).\nDefault 40 — matches genuine MLMI kNN OS zone.\nRejects mildly bearish fast=41–49 that MLMI does NOT classify as OS.\nExample: fast=18 passes, fast=41 is rejected.")
float  i_tri_ob       = input.float(60.0, "OB threshold (fast WMA RSI ≥)",         group=G_TRI, step=1.0, minval=51.0, maxval=80.0,
     tooltip="Fast WMA(RSI(5),mw) must be above this at ALL 3 TFs for OB (SELL setup).\nDefault 60 — matches genuine MLMI kNN OB zone.\nRejects mildly bullish fast=51–59 that MLMI does NOT classify as OB.")
int    i_tri_gap      = input.int(3,   "Min bars between signals",                 group=G_TRI, minval=1, maxval=50)
color  i_tri_buy_col  = input.color(color.new(#00E676, 0), "BUY Label Color",      group=G_TRI)
color  i_tri_sell_col = input.color(color.new(#FF1744, 0), "SELL Label Color",     group=G_TRI)

// ── ⑰ ML YZ — Adaptive Volatility Bands ──────────────────────────────────
const string G_YZ1 = "⑰ ML YZ — Yang-Zhang"
const string G_YZ2 = "⑰ ML YZ — ML / kNN"
const string G_YZ3 = "⑰ ML YZ — AMT Zones"
const string G_YZ4 = "⑰ ML YZ — Display"
bool   i_yz_en        = input.bool  (true,   "Enable ML YZ Bands",              group=G_YZ1)
int    i_yz_len       = input.int   (20,     "YZ Period",      minval=5, maxval=300, group=G_YZ1)
float  i_yz_mult      = input.float (1.5,    "Band Multiplier",minval=0.1, step=0.1, group=G_YZ1)
string i_yz_ma_type   = input.string("KAMA", "Middle Band MA",
     options=["EMA","HMA","SMA","WMA","KAMA"],                                    group=G_YZ1)
int    i_yz_ma_len    = input.int   (20,     "MA Period",      minval=5, maxval=200, group=G_YZ1)
int    i_yz_knn_k     = input.int   (5,      "kNN — K Neighbors", minval=3, maxval=20, group=G_YZ2)
int    i_yz_knn_lb    = input.int   (100,    "kNN — Lookback",    minval=50,maxval=300,group=G_YZ2)
bool   i_yz_adapt     = input.bool  (true,   "Adaptive Band Width",              group=G_YZ2)
float  i_yz_mul_low   = input.float (0.8,    "Low-Vol Multiplier", minval=0.5,maxval=1.0,step=0.1,group=G_YZ2)
float  i_yz_mul_high  = input.float (1.4,    "High-Vol Multiplier",minval=1.0,maxval=2.0,step=0.1,group=G_YZ2)
int    i_yz_poc_len   = input.int   (50,     "VA / POC Lookback", minval=10,maxval=200, group=G_YZ3)
float  i_yz_va_pct    = input.float (70.0,   "Value Area %",   minval=50.0,maxval=95.0, group=G_YZ3)
bool   i_yz_show_va   = input.bool  (true,   "Show Value Area",                  group=G_YZ3)
bool   i_yz_show_poc  = input.bool  (true,   "Show POC (VWAP proxy)",            group=G_YZ3)
bool   i_yz_col_bars  = input.bool  (false,  "Color Bars by YZ State",           group=G_YZ4,
     tooltip="Bar color override: Bull=green / Bear=red / Neutral=grey.\nDefault OFF — LiqHM already colours bars via Kalman direction.")
bool   i_yz_show_sig  = input.bool  (true,   "Show YZ AMT Signals",             group=G_YZ4)
bool   i_yz_show_lbl  = input.bool  (true,   "Show YZ Regime Label",            group=G_YZ4)

// ── ⑱ HV-Pivot Levels + CLV-Delta  (BigBeluga concept, display-only) ───────
const string G_HV = "⑱ HV-Pivot Levels  (volume-gated S/R + CLV-CVD)"
bool  i_hv_en       = input.bool (false, "Enable HV-Pivot Levels",              group=G_HV,
     tooltip="Ports ONLY the volume-gated-pivot concept from BigBeluga's High-Volume Pivot S/R (not the box/CVD-polyline system).\nA pivot high/low is kept as an S/R level ONLY if the pivot bar traded on high volume.\nDisplay-only: draws one ray + a CVD label per level. Does not feed the ⑮/⑯/⑰ signals.\nDefault OFF.")
int   i_hv_res_len  = input.int  (144,  "Resistance Pivot Length",  minval=2, maxval=240, group=G_HV,
     tooltip="Bars required before AND after a swing high to confirm a resistance pivot (BigBeluga default 144). Larger = fewer, more structural levels. Confirms len bars late (non-repainting once confirmed).\nCapped at 240: the pivot needs 2×len bars of history and the indicator declares max_bars_back=500.")
int   i_hv_sup_len  = input.int  (144,  "Support Pivot Length",     minval=2, maxval=240, group=G_HV,
     tooltip="Bars required before AND after a swing low to confirm a support pivot.\nCapped at 240 (2×len must fit max_bars_back=500).")
int   i_hv_vol_ma   = input.int  (89,   "Volume MA Length",         minval=1,   group=G_HV,
     tooltip="SMA length of volume used as the baseline for the high-volume test (BigBeluga default 89 in your setup).")
float i_hv_vol_mult = input.float(1.5,  "Volume Threshold Multiplier", minval=0.1, step=0.1, group=G_HV,
     tooltip="Pivot bar's volume must exceed SMA(volume, maLen) × this to qualify (your setup = 1.5). Higher = stricter = fewer levels.")
int   i_hv_keep     = input.int  (3,    "Levels Kept Per Side",     minval=1, maxval=15, group=G_HV,
     tooltip="Only the most-recent N resistance and N support levels are drawn; older ones are deleted. Caps object count (≤ 2×N lines + 2×N labels). Anti-bloat control.")
bool  i_hv_show_cvd = input.bool (true, "Show CVD Label (CLV×vol)",             group=G_HV,
     tooltip="Label each level with its formation-window CVD, computed as CLV×volume ( ((C−L)−(H−C))/(H−L) × vol ) summed over the pivot window.\nPositive = accumulation into the level, negative = distribution. This is the CLV×vol fix vs the raw candle-direction proxy.")
color i_hv_res_col  = input.color(color.new(#2196F3, 0), "Resistance Color",    group=G_HV)
color i_hv_sup_col  = input.color(color.new(#FFC13B, 0), "Support Color",       group=G_HV)

// ── ⑱ CVD-Confluence GATE  (v18 — OPT-IN, changes entry logic, default OFF) ─
const string G_HVG = "⑱ HV-Pivot — CVD Confluence Gate  (v18, default OFF)"
bool  i_hv_gate_sig = input.bool (false, "Gate ⑮ Confluence by CVD-level",      group=G_HVG,
     tooltip="When ON, the ⑮ Confluence BUY/SELL fires ONLY if price is also at a CVD-confirmed HV-Pivot level (BUY→accumulation support, SELL→distribution resistance).\nWhen OFF (default) ⑮ is byte-for-byte the validated behaviour.\nAuto-relaxes to no-op unless ⑱ Enable HV-Pivot Levels is also ON.\n⚠ Changes entry logic — walk-forward OOS before any live use.")
bool  i_hv_gate_tri = input.bool (false, "Gate ⑯ Triple MTF by CVD-level",      group=G_HVG,
     tooltip="Same CVD-level requirement applied to the ⑯ Triple MTF Conf signal. Default OFF.\n⚠ Changes entry logic — OOS before live.")
float i_hv_gate_atr = input.float(0.5,   "Level proximity (×ATR14)",  minval=0.05, step=0.05, group=G_HVG,
     tooltip="Price must be within this × ATR(14) of the HV-level for the gate to allow the signal. Same units as the ⑮ significant-level proximity.")
float i_hv_gate_cvd = input.float(0.0,   "Min |CVD| to confirm level",  minval=0.0, step=100.0, group=G_HVG,
     tooltip="Minimum formation CVD magnitude for a level to count: a support needs CVD ≥ +this, a resistance needs CVD ≤ −this.\n0 = sign only (any accumulation counts for support, any distribution for resistance). Raise to demand stronger accumulation/distribution.")

// ═══════════════════════════════════════════════════════════════════════════
// §3  COLORS
// ═══════════════════════════════════════════════════════════════════════════
color C_CYAN          = color.new(#00BCD4, 0)
color C_GOLD          = color.new(#FFD700, 0)
const color NEUTRAL_COLOR = #787b86
// ── ⑰ ML YZ colors ────────────────────────────────────────────────────────
const color YZ_BULL   = #2DD204
const color YZ_BEAR   = #D2042D
const color YZ_NEUT   = #B0B0B0
const color YZ_BLUE   = #00BFFF

// ═══════════════════════════════════════════════════════════════════════════
// §4  FUNCTIONS (HTF + Kalman + RQ-Kernel + Shannon Entropy + Monte Carlo)
// ═══════════════════════════════════════════════════════════════════════════
formatDelta(float val) =>
    string sign  = val > 0 ? "+" : ""
    float absVal = math.abs(val)
    absVal >= 1000000 ? sign + str.format("{0,number,#.#}M", val / 1000000) :
     absVal >= 1000   ? sign + str.format("{0,number,#.#}K", val / 1000)    :
     sign + str.tostring(val)

drawHtfPattern(string name, float h, float l, float o, float c, int tStart, int tEnd, color col, bool isBullish) =>
    string timeStr  = str.format_time(tStart, "HH:mm", syminfo.timezone)
    string labelTxt = name + "\n" + timeStr
    int    midTime  = math.round((tStart + tEnd) / 2)
    int    timeInset = math.round((tEnd - tStart) * 0.1)
    box.new(tStart, h, tEnd, l,
         xloc=xloc.bar_time, bgcolor=color.new(col, 90), border_color=color.new(col, 50), border_width=1)
    box.new(tStart + timeInset, math.max(o, c), tEnd - timeInset, math.min(o, c),
         xloc=xloc.bar_time, bgcolor=color.new(col, 60), border_color=color.new(col, 20), border_width=1)
    line.new(midTime, h, midTime, l,
         xloc=xloc.bar_time, color=color.new(col, 20), width=1)
    label.new(midTime, isBullish ? l : h, labelTxt,
         xloc=xloc.bar_time, yloc=yloc.price, color=#00000000, textcolor=col,
         style=isBullish ? label.style_label_up : label.style_label_down, size=size.small)

// Kalman helper: custom EMA
pine_ema(float src, float length) =>
    float alpha = 2 / (length + 1)
    float sum   = src
    sum := alpha * src + (1 - alpha) * nz(sum[1])

// Kalman helper: clamp
klm_clamp(float x, float a, float b) =>
    math.min(math.max(x, a), b)

// Kalman core filter function
calc_kalman(float src, float p_state, float p_cv, float R_raw, float Q_raw, float res_var, float q_ratio, float gate_mult) =>
    float q_process       = klm_clamp(nz(Q_raw, 0.1), 0.001, 10.0) * q_ratio
    float r_dynamic_floor = res_var * gate_mult
    float r_adaptive      = math.max(nz(R_raw, 1.0), r_dynamic_floor)
    float current_p_cov   = p_cv + q_process
    float k_gain          = current_p_cov / (current_p_cov + r_adaptive + 1e-10)
    float next_state      = nz(p_state, src)
    float next_cov        = current_p_cov
    if not na(k_gain)
        next_state := next_state + k_gain * (src - next_state)
        next_cov   := (1 - k_gain) * current_p_cov
    [next_state, next_cov]

// RQ-Kernel function
f_rq_kernel(float src, int h, float alpha, int len) =>
    float _ws = 0.0
    float _wv = 0.0
    float _2ah2 = 2.0 * alpha * float(h) * float(h)
    for _i = 0 to len - 1
        float _w = math.pow(1.0 + float(_i * _i) / _2ah2, -alpha)
        _wv += nz(src[_i]) * _w
        _ws += _w
    _ws > 0.0 ? _wv / _ws : src

// Shannon Entropy function
f_shannon_entropy(float src, int len, int n_bins) =>
    float _hi = src
    float _lo = src
    for _k = 1 to len - 1
        _hi := math.max(_hi, nz(src[_k], src))
        _lo := math.min(_lo, nz(src[_k], src))
    float _rng  = math.max(_hi - _lo, syminfo.mintick * 10.0)
    float _step = _rng / float(n_bins)
    float _ent  = 0.0
    for _b = 0 to n_bins - 1
        float _blo = _lo + _step * float(_b)
        float _bhi = _blo + _step
        int   _cnt = 0
        for _i = 0 to len - 1
            float _v = nz(src[_i], src)
            if _v >= _blo and _v < _bhi
                _cnt += 1
        float _p = float(_cnt) / float(len)
        if _p > 0.0
            _ent -= _p * math.log(_p) / math.log(2.0)
    float _max = math.log(float(n_bins)) / math.log(2.0)
    _max > 0.0 ? _ent / _max : 0.5

// ── ⑭ Monte Carlo dashboard helpers (LuxAlgo) ─────────────────────────────
var string mc_parsedDashboardPosition = switch mc_dashboardPositionInput
    MC_TOP_RIGHT    => position.top_right
    MC_BOTTOM_RIGHT => position.bottom_right
    MC_BOTTOM_LEFT  => position.bottom_left
    =>                 position.top_right

var string mc_parsedDashboardSize = switch mc_dashboardSizeInput
    MC_TINY   => size.tiny
    MC_SMALL  => size.small
    MC_NORMAL => size.normal
    MC_LARGE  => size.large
    MC_HUGE   => size.huge
    =>           size.normal

mc_cell(table t_able, int column, int row, string data, color text_col = #FFFFFF, string align = text.align_right, color background = na, float height = 0) =>
    t_able.cell(column, row, data, text_color = text_col, text_size = mc_parsedDashboardSize, text_halign = align, bgcolor = background, height = height)

mc_divider(table t_able, int row, int lastColumn) =>
    string rowDivider = '━━━━━━━━━━━━━━'
    t_able.merge_cells(0, row, lastColumn, row)
    mc_cell(t_able, 0, row, rowDivider, text_col = MC_BORDERS, align = text.align_center, height = 0.5)

// ── ⑯ TF name helper ──────────────────────────────────────────────────────
f_tri_tf_name(string tf) =>
    switch tf
        "1"   => "M1"
        "5"   => "M5"
        "15"  => "M15"
        "30"  => "M30"
        "60"  => "H1"
        "120" => "H2"
        "240" => "H4"
        "D"   => "D1"
        "W"   => "W1"
        =>        tf

// ── ⑰ Yang-Zhang pure volatility estimator ────────────────────────────────
// σ²_YZ = σ²_OC + k·σ²_CO + (1-k)·σ²_RS
// OC = overnight, CO = open-to-close, RS = Rogers-Satchell
yzVol(int per) =>
    float k    = 0.34 / (1.34 + (per + 1.0) / math.max(per - 1.0, 1.0))
    float lnOC = math.log(open  / nz(close[1], close))
    float lnCO = math.log(close / open)
    float lnHO = math.log(high  / open)
    float lnLO = math.log(low   / open)
    float rs   = lnHO * (lnHO - lnCO) + lnLO * (lnLO - lnCO)
    float vOC  = ta.variance(lnOC, per)
    float vCO  = ta.variance(lnCO, per)
    float vRS  = ta.sma(rs, per)
    math.sqrt(math.max(vOC + k * vCO + (1.0 - k) * vRS, 0.0))

// ── ⑰ Kaufman Adaptive MA ─────────────────────────────────────────────────
// ER = net direction / path noise; SC = (ER*(fast-slow)+slow)²
kamaCalc(float src, int len) =>
    float fast  = 2.0 / 3.0
    float slow  = 2.0 / 31.0
    float dir   = math.abs(src - nz(src[len], src))
    float noise = math.sum(math.abs(src - nz(src[1], src)), len)
    float er    = noise > 0.0 ? dir / noise : 0.0
    float sc    = math.pow(er * (fast - slow) + slow, 2.0)
    var float _kv = na
    _kv          := na(_kv) ? src : _kv + sc * (src - _kv)
    _kv

// ── ⑰ kNN regime classifier ───────────────────────────────────────────────
// 3-feature Euclidean kNN → Balance(1) / Neutral(0) / Imbalance(-1)
knnRegime(float f1, float f2, float f3, float lbl_s, int k_n, int lb) =>
    float[] dists = array.new_float(lb, 0.0)
    for i = 1 to lb
        float d = math.sqrt(
             math.pow(f1 - f1[i], 2.0) +
             math.pow(f2 - f2[i], 2.0) +
             math.pow(f3 - f3[i], 2.0))
        array.set(dists, i - 1, d)
    float[] srt = array.copy(dists)
    array.sort(srt, order.ascending)
    float thr = array.get(srt, math.min(k_n - 1, array.size(srt) - 1))
    float vote = 0.0
    int   cnt  = 0
    for i = 0 to lb - 1
        if array.get(dists, i) <= thr and cnt < k_n
            float lbl = 0.0
            if lbl_s[i + 1] > 0.65
                lbl := -1.0
            else if lbl_s[i + 1] < 0.35
                lbl := 1.0
            vote += lbl
            cnt  += 1
    vote > 0.0 ? 1 : vote < 0.0 ? -1 : 0

// ═══════════════════════════════════════════════════════════════════════════
// §5  GLOBAL TA PRECOMPUTE
// ═══════════════════════════════════════════════════════════════════════════

// ── AMT ───────────────────────────────────────────────────────────────────
float _vol_ma50 = ta.sma(volume, 50)
float _mass     = _vol_ma50 > 0.0 ? volume / _vol_ma50 : 1.0
float _m_mean   = ta.vwma(close, 50)
float _m_dev    = ta.stdev(close, 50) * 1.5
float _m_vah    = _m_mean + _m_dev
float _m_val    = _m_mean - _m_dev
float _tol_z    = _m_dev * 0.1

// ── VP range ──────────────────────────────────────────────────────────────
float _vp_top = ta.highest(i_vp_bars)
float _vp_bot = ta.lowest(i_vp_bars)

// ── Heatmap ───────────────────────────────────────────────────────────────
float _hm_vol     = math.sum(volume, 10)
float _hm_vol_max = ta.highest(_hm_vol, lookBack_hm)
float _nVol       = _hm_vol_max > 0 ? (_hm_vol / _hm_vol_max * 100) : 0.0
float _atr_hm     = ta.atr(5) / 50
float _hm_atr_off = ta.highest(_atr_hm * _nVol, lookBack_hm)

// ── 2-bar pivot detection ─────────────────────────────────────────────────
float _h2 = ta.highest(2)
float _l2  = ta.lowest(2)

// ── Fix Window Detection ──────────────────────────────────────────────────
int  _fx_hm  = (hour(time, "UTC+0") + i_fix_utc) * 100 + minute(time, "UTC+0")
bool _fx_am  = _fx_hm >= 1000 and _fx_hm < 1030
bool _fx_pm  = _fx_hm >= 1500 and _fx_hm < 1515
bool _fx_cme = _fx_hm >= 1320 and _fx_hm < 1330

// ── RSI + divergence pivots ───────────────────────────────────────────────
float rsiValue = ta.rsi(close, rsiLenInput)
float phRsi    = ta.pivothigh(rsiValue, lbLInput, lbRInput)
float plRsi    = ta.pivotlow(rsiValue,  lbLInput, lbRInput)

// ── ⑮ Multi-TF RSI for confluence signal (current TF + H1 + H4) ───────────
float sig_rsi_cur = ta.rsi(close, i_sig_rsi_len)
float sig_rsi_h1  = request.security(syminfo.tickerid, i_sig_htf1, ta.rsi(close, i_sig_rsi_len), barmerge.gaps_off, barmerge.lookahead_off)
float sig_rsi_h4  = request.security(syminfo.tickerid, i_sig_htf2, ta.rsi(close, i_sig_rsi_len), barmerge.gaps_off, barmerge.lookahead_off)

// ── ⑯ Triple MTF WMA-RSI momentum ─────────────────────────────────────────
// rq = fast = WMA(RSI(5), mw)   rs = slow = WMA(RSI(20), mw)
// OS: fast < slow AND slow < 50  (bearish momentum in bearish regime)
// OB: fast > slow AND slow > 50  (bullish momentum in bullish regime)
// Using same RSI features as MoConf v11 MLMI kNN (rsi_quick / rsi_slow),
// computed directly — no kNN state divergence, no initialisation artefacts.
[_tri_rq_m5, _tri_rs_m5] = request.security(syminfo.tickerid, i_tri_m5_tf,
     [ta.wma(ta.rsi(close, 5), i_tri_mw), ta.wma(ta.rsi(close, 20), i_tri_mw)],
     barmerge.gaps_off, barmerge.lookahead_off)
[_tri_rq_h1, _tri_rs_h1] = request.security(syminfo.tickerid, i_tri_htf1,
     [ta.wma(ta.rsi(close, 5), i_tri_mw), ta.wma(ta.rsi(close, 20), i_tri_mw)],
     barmerge.gaps_off, barmerge.lookahead_off)
[_tri_rq_h4, _tri_rs_h4] = request.security(syminfo.tickerid, i_tri_htf2,
     [ta.wma(ta.rsi(close, 5), i_tri_mw), ta.wma(ta.rsi(close, 20), i_tri_mw)],
     barmerge.gaps_off, barmerge.lookahead_off)
string _tri_tf0n = f_tri_tf_name(i_tri_m5_tf)
string _tri_tf1n = f_tri_tf_name(i_tri_htf1)
string _tri_tf2n = f_tri_tf_name(i_tri_htf2)

// ── HTF bar detection + data fetch ───────────────────────────────────────
bool htfNewBar = nz(ta.change(time(htfInput))) != 0

[hOpen, hHigh, hLow, hClose, hPrevOpen, hPrevClose, hTimeStart, hTimeEnd] =
     request.security(syminfo.tickerid, htfInput,
     [open[1], high[1], low[1], close[1], open[2], close[2], time[1], time_close[1]])

// ── Kalman EMA state measurement ──────────────────────────────────────────
int   qlen   = 5
float ema1   = pine_ema(source, factor)
float ema2   = pine_ema(ema1, factor2)

// ── Kalman base filter state ──────────────────────────────────────────────
var float kf_stateb = na
var float p_covb    = 1.0

float residualb          = ema2 - nz(kf_stateb[1], ema2)
float vol_baseb          = math.max(ta.atr(14), 1e-6)
float residual_varianceb = math.pow(residualb, 2) / math.pow(vol_baseb, 2)
float R_rawb             = nz(ta.rma(residual_varianceb, r_ratiob), 1.0)
float Q_rawb             = nz(ta.rma(residual_varianceb, qlen),     0.1)

[new_valb, new_covb] = calc_kalman(ema2, kf_stateb, p_covb, R_rawb, Q_rawb, residual_varianceb, q_ratiob, r_gate_multb)
kf_stateb := new_valb
p_covb    := new_covb
float smoothb = pine_ema(kf_stateb, 3)

// ── Kalman fast filter state ──────────────────────────────────────────────
var float kf_states = na
var float p_covs    = 1.0

float residuals          = ema2 - nz(kf_states[1], ema2)
float vol_bases          = math.max(ta.atr(14), 1e-6)
float residual_variances = math.pow(residuals, 2) / math.pow(vol_bases, 2)
float R_raws             = nz(ta.rma(residual_variances, r_ratios), 1.0)
float Q_raws             = nz(ta.rma(residual_variances, qlen),     0.1)

[new_vals, new_covs] = calc_kalman(ema2, kf_states, p_covs, R_raws, Q_raws, residual_variances, q_ratios, r_gate_mults)
kf_states := new_vals
p_covs    := new_covs
float smooths = pine_ema(kf_states, 5)

// ═══════════════════════════════════════════════════════════════════════════
// §5b  NEW v4 COMPUTATIONS
// ═══════════════════════════════════════════════════════════════════════════

// ── Hilbert Transform Dominant Cycle (Ehlers Homodyne Discriminator) ──────
var float _ht_smooth = 0.0
var float _ht_dtr    = 0.0
var float _ht_I1     = 0.0
var float _ht_Q1     = 0.0
var float _ht_jI     = 0.0
var float _ht_jQ     = 0.0
var float _ht_I2     = 0.0
var float _ht_Q2     = 0.0
var float _ht_Re     = 0.0
var float _ht_Im     = 0.0
var float _ht_per    = 20.0
var float _ht_dc     = 20.0

float _ht_coef = 0.075 * nz(_ht_dc, 20.0) + 0.54
_ht_smooth := (4.0*close + 3.0*nz(close[1]) + 2.0*nz(close[2]) + nz(close[3])) / 10.0
_ht_dtr    := (0.0962*_ht_smooth + 0.5769*nz(_ht_smooth[2]) - 0.5769*nz(_ht_smooth[4]) - 0.0962*nz(_ht_smooth[6])) * _ht_coef
_ht_I1     := nz(_ht_dtr[3])
_ht_Q1     := (0.0962*_ht_dtr + 0.5769*nz(_ht_dtr[2]) - 0.5769*nz(_ht_dtr[4]) - 0.0962*nz(_ht_dtr[6])) * _ht_coef
_ht_jI     := (0.0962*_ht_I1 + 0.5769*nz(_ht_I1[2]) - 0.5769*nz(_ht_I1[4]) - 0.0962*nz(_ht_I1[6])) * _ht_coef
_ht_jQ     := (0.0962*_ht_Q1 + 0.5769*nz(_ht_Q1[2]) - 0.5769*nz(_ht_Q1[4]) - 0.0962*nz(_ht_Q1[6])) * _ht_coef
_ht_I2     := 0.2*(_ht_I1 - _ht_jQ) + 0.8*nz(_ht_I2[1])
_ht_Q2     := 0.2*(_ht_Q1 + _ht_jI) + 0.8*nz(_ht_Q2[1])
_ht_Re     := 0.2*(_ht_I2*nz(_ht_I2[1]) + _ht_Q2*nz(_ht_Q2[1])) + 0.8*nz(_ht_Re[1])
_ht_Im     := 0.2*(_ht_I2*nz(_ht_Q2[1]) - _ht_Q2*nz(_ht_I2[1])) + 0.8*nz(_ht_Im[1])
if _ht_Im != 0.0 and _ht_Re != 0.0
    _ht_per := 2.0 * math.acos(-1.0) / math.atan(_ht_Im / _ht_Re)
if _ht_per > 1.5 * nz(_ht_per[1], 20.0)
    _ht_per := 1.5 * nz(_ht_per[1], 20.0)
if _ht_per < 0.67 * nz(_ht_per[1], 20.0)
    _ht_per := 0.67 * nz(_ht_per[1], 20.0)
_ht_per := math.max(6.0, math.min(50.0, _ht_per))
_ht_dc  := 0.2*_ht_per + 0.8*nz(_ht_dc[1])

int dc_int      = math.max(6, math.min(50, math.round(_ht_dc)))
// DC adaptive: HM lookback only — VP always uses user's full i_vp_bars setting
// VP needs maximum history for accurate POC/VAH/VAL; shrinking it via DC would
// cause the exact v3→v4 regression visible in the before/after screenshots.
int hm_lb_adapt = math.max(30, math.min(lookBack_hm, dc_int * i_dc_hm_mult))
int hm_lb_eff   = i_dc_en ? hm_lb_adapt : lookBack_hm
int vp_lb_eff   = i_vp_bars  // always full user input — never DC-reduced

// ── RQ-Kernel ─────────────────────────────────────────────────────────────
float rq_fast = i_rq_en ? f_rq_kernel(close, i_rq_h_fast, i_rq_alpha, i_rq_len) : na
float rq_slow = i_rq_en ? f_rq_kernel(close, i_rq_h_slow, i_rq_alpha, i_rq_len) : na

// ── Shannon Entropy ───────────────────────────────────────────────────────
float _ent_norm   = i_ent_en ? f_shannon_entropy(close, i_ent_len, i_ent_bins) : 0.5
bool  _ent_struct = _ent_norm < i_ent_str_thr
bool  _ent_random = _ent_norm > i_ent_ran_thr
string _ent_str   = _ent_struct ? "STRUCTURED" : _ent_random ? "RANDOM" : "TRANSITIONAL"
color  _ent_col   = _ent_struct ? color.new(#00E676,0) : _ent_random ? color.new(#FF1744,0) : color.new(#FFD700,0)
int _ent_tr_add   = i_ent_en ? int(_ent_norm * 60.0) : 0

// ── VPIN Proxy ────────────────────────────────────────────────────────────
float _vp_rng_b  = math.max(high - low, syminfo.mintick)
float _vpin_bvol = volume * ((close - low)  / _vp_rng_b)
float _vpin_svol = volume * ((high - close) / _vp_rng_b)
float _vpin_imb  = math.abs(_vpin_bvol - _vpin_svol)
float _vpin_smi  = ta.sma(_vpin_imb, i_vpin_len)
float _vpin_smt  = math.max(ta.sma(volume, i_vpin_len), 1.0)
float _vpin_val  = math.min(_vpin_smi / _vpin_smt, 1.0)
bool  _vpin_toxic = i_vpin_en and _vpin_val >= i_vpin_tox
bool  _vpin_blncd = i_vpin_en and _vpin_val <= i_vpin_bal
float _vpin_net   = ta.sma(_vpin_bvol - _vpin_svol, i_vpin_len)
bool  _vpin_tox_bull = _vpin_toxic and _vpin_net > 0.0
bool  _vpin_tox_bear = _vpin_toxic and _vpin_net < 0.0
string _vpin_str  = _vpin_toxic ? (_vpin_tox_bull ? "TOXIC-BUY" : "TOXIC-SELL") : _vpin_blncd ? "BALANCED" : "NEUTRAL"
color  _vpin_col  = _vpin_tox_bull ? i_vpin_tox_col : _vpin_tox_bear ? i_vpin_tox_col : _vpin_blncd ? i_vpin_bal_col : color.new(#607D8B,0)

// ═══════════════════════════════════════════════════════════════════════════
// §5c  ⑭ MONTE CARLO — CORE LOGIC (LuxAlgo, namespaced)
// ═══════════════════════════════════════════════════════════════════════════
var array<float>    mc_logReturns = array.new<float>()
var array<float>    mc_simReturns = array.new<float>()
var array<int>      mc_regimes    = array.new<int>()
var array<polyline> mc_polylines  = array.new<polyline>()
var array<line>     mc_srLines    = array.new<line>()
var array<label>    mc_srLabels   = array.new<label>()
var array<box>      mc_srBoxes    = array.new<box>()

// ⑮ Persistent MC band bounds (updated whenever MC recomputes; used by signal §19)
var float mc_bandR1  = na
var float mc_bandS1  = na
var float mc_bandMax = na
var float mc_bandMin = na
var float mc_bandMean= na

float mc_lr = math.log(close / nz(close[1], close))
float mc_sr = close - nz(close[1], close)

float mc_sma50 = ta.sma(close, 50)
float mc_rsi14 = ta.rsi(close, 14)

int mc_currentRegime = 0
if mc_regimeInput == MC_REGIME_TREND
    mc_currentRegime := close > nz(mc_sma50, close) ? 1 : -1
else if mc_regimeInput == MC_REGIME_MOMENTUM
    mc_currentRegime := nz(mc_rsi14, 50) > 50 ? 1 : -1

if mc_enableInput
    if barstate.isnew
        mc_logReturns.push(mc_lr)
        mc_simReturns.push(mc_sr)
        mc_regimes.push(mc_currentRegime)
        if mc_logReturns.size() > mc_lookbackInput
            mc_logReturns.shift()
            mc_simReturns.shift()
            mc_regimes.shift()
    else if mc_logReturns.size() > 0
        mc_logReturns.set(mc_logReturns.size() - 1, mc_lr)
        mc_simReturns.set(mc_simReturns.size() - 1, mc_sr)
        mc_regimes.set(mc_regimes.size() - 1, mc_currentRegime)

// ═══════════════════════════════════════════════════════════════════════════
// §5d  ⑰ ML YZ — GLOBAL PRECOMPUTES  (must run every bar)
// ═══════════════════════════════════════════════════════════════════════════

// ── All MA variants computed unconditionally (CW10003 guard) ──────────────
float _yz_ma_ema = ta.ema(close, i_yz_ma_len)
float _yz_ma_sma = ta.sma(close, i_yz_ma_len)
float _yz_ma_wma = ta.wma(close, i_yz_ma_len)
int   _yz_hma_s  = math.max(math.round(math.sqrt(float(i_yz_ma_len))), 1)
float _yz_hma_w1 = ta.wma(close, math.max(i_yz_ma_len / 2, 1))
float _yz_hma_w2 = ta.wma(close, i_yz_ma_len)
float _yz_ma_hma = ta.wma(2.0 * _yz_hma_w1 - _yz_hma_w2, _yz_hma_s)
float _yz_ma_kama= kamaCalc(close, i_yz_ma_len)

// ── Core YZ computation ────────────────────────────────────────────────────
float yz_vol  = i_yz_en ? yzVol(i_yz_len) : na
float yz_mid  = switch i_yz_ma_type
    "EMA" => _yz_ma_ema
    "SMA" => _yz_ma_sma
    "WMA" => _yz_ma_wma
    "HMA" => _yz_ma_hma
    =>       _yz_ma_kama

// Band width in price units
float yz_bw_px  = nz(yz_vol) * close
float yz_bw_sma = ta.sma(yz_bw_px, i_yz_len)
float yz_bw_roc = yz_bw_sma > 0.0 ? (yz_bw_px - nz(yz_bw_px[i_yz_len], yz_bw_px)) / yz_bw_sma : 0.0

// Volume activity (relative to historical average)
float yz_vol_sma = ta.sma(volume, i_yz_knn_lb)
float yz_rel_vol = yz_vol_sma > 0.0 ? float(volume) / yz_vol_sma : 1.0

// Price distance from midline (normalised by band width)
float yz_pd_raw  = yz_bw_px > 0.0 ? math.abs(close - yz_mid) / yz_bw_px : 0.5

// ── Normalised features (percentile rank 0–1) ─────────────────────────────
float yz_feat1  = ta.percentrank(yz_bw_roc,  i_yz_knn_lb) / 100.0
float yz_feat2  = ta.percentrank(yz_rel_vol, i_yz_knn_lb) / 100.0
float yz_feat3  = ta.percentrank(yz_pd_raw,  i_yz_knn_lb) / 100.0
float yz_vrank  = ta.percentrank(nz(yz_vol), i_yz_knn_lb) / 100.0

// ── ML kNN regime classification ──────────────────────────────────────────
int yz_regime = i_yz_en ? knnRegime(yz_feat1, yz_feat2, yz_feat3, yz_vrank, i_yz_knn_k, i_yz_knn_lb) : 0

// ── Adaptive multiplier ────────────────────────────────────────────────────
float yz_eff_mult = i_yz_adapt
     ? (yz_regime ==  1 ? i_yz_mult * i_yz_mul_low
     :  yz_regime == -1 ? i_yz_mult * i_yz_mul_high
     :  i_yz_mult)
     : i_yz_mult

// ── Outer bands ────────────────────────────────────────────────────────────
float yz_upper = yz_mid + yz_bw_px * yz_eff_mult
float yz_lower = yz_mid - yz_bw_px * yz_eff_mult

// ── Band direction ─────────────────────────────────────────────────────────
bool  yz_expanding   = yz_bw_px > nz(yz_bw_px[1], yz_bw_px) and yz_bw_px > yz_bw_sma
bool  yz_contracting = yz_bw_px < nz(yz_bw_px[1], yz_bw_px) and yz_bw_px < yz_bw_sma

// ── AMT Value Area + POC ──────────────────────────────────────────────────
float yz_va_half  = yz_bw_px * yz_eff_mult * (i_yz_va_pct / 100.0)
float yz_va_upper = yz_mid + yz_va_half
float yz_va_lower = yz_mid - yz_va_half

float _yz_poc_num = math.sum(close * float(volume), i_yz_poc_len)
float _yz_poc_den = math.sum(float(volume), i_yz_poc_len)
float yz_poc      = _yz_poc_den > 0.0 ? _yz_poc_num / _yz_poc_den : close

// ── AMT Signals ───────────────────────────────────────────────────────────
bool yz_init_buy  = i_yz_en and ta.crossover (close, yz_upper)    and (yz_expanding  or yz_regime == -1)
bool yz_init_sell = i_yz_en and ta.crossunder(close, yz_lower)    and (yz_expanding  or yz_regime == -1)
bool yz_resp_buy  = i_yz_en and ta.crossover (close, yz_va_lower) and yz_contracting
bool yz_resp_sell = i_yz_en and ta.crossunder(close, yz_va_upper) and yz_contracting

// Directional state memory
var int yz_state = 0
yz_state := yz_init_buy ? 1 : yz_init_sell ? -1 : nz(yz_state[1])

// ── Regime / state colors ─────────────────────────────────────────────────
color yz_c_st  = yz_state ==  1 ? YZ_BULL : yz_state == -1 ? YZ_BEAR : YZ_NEUT
color yz_c_reg = yz_regime ==  1 ? C_GOLD : yz_regime == -1 ? YZ_BEAR : YZ_NEUT

// ═══════════════════════════════════════════════════════════════════════════
// §6  AMT REJECTION SIGNALS
// ═══════════════════════════════════════════════════════════════════════════
bool _ice_vah = math.abs(close - _m_vah) <= _tol_z and _mass > 1.8 and close <= _m_vah
bool _ice_val = math.abs(close - _m_val) <= _tol_z and _mass > 1.8 and close >= _m_val
bool _rej_top = high > _m_vah and close < _m_vah and not _ice_vah
bool _rej_bot = low  < _m_val and close > _m_val and not _ice_val
bool _is_ice  = _ice_vah or _ice_val

// ═══════════════════════════════════════════════════════════════════════════
// §7  HEATMAP PIVOT ENGINE
// ═══════════════════════════════════════════════════════════════════════════
int resolution = 100

var array<box>   hm_boxes    = array.new<box>()
var array<label> hm_labels   = array.new<label>()
var array<line>  hm_lines    = array.new<line>()
var array<float> volume_bins = array.new<float>(bins, 0.)
var array<pivot> pivots      = array.new<pivot>()

// §7: ALWAYS use full lookBack_hm for h_l + pivot stats (preserves v3 display)
// DC adaptive (hm_lb_eff) applies ONLY to the line-reach in §14 hm_start loop.
// Changing this to hm_lb_eff shifts vb_max/step_bin → breaks box text format.
array<float> h_l = array.new<float>()
for i = 0 to lookBack_hm - 1
    h_l.push(high[i] + _hm_atr_off[i])
    h_l.push(low[i]  - _hm_atr_off[i])

if last_bar_index - bar_index < lookBack_hm
    float top  = h_l.max()
    float bot  = h_l.min()
    float step = (top - bot) / resolution
    float level1 = high + _atr_hm * _nVol
    float level2 = low  - _atr_hm * _nVol
    if _h2 == high
        for i = 0 to resolution - 1
            float lower = bot + step * i
            float mid   = lower + step / 2
            if math.abs(level1 - mid) <= step
                pivots.push(pivot.new(mid, bar_index, _nVol, _hm_vol, false))
    if _l2 == low
        for i = 0 to resolution - 1
            float lower = bot + step * i
            float mid   = lower + step / 2
            if math.abs(level2 - mid) <= step
                pivots.push(pivot.new(mid - _atr_hm * _nVol, bar_index, _nVol, _hm_vol, true))
    if pivots.size() > 0
        for i = pivots.size() - 1 to 0
            pivot p    = pivots.get(i)
            float y    = p.value
            bool  isLw = p.isLower
            if isLw and low < y
                pivots.remove(i)
            else if not isLw and high > y
                pivots.remove(i)

// ═══════════════════════════════════════════════════════════════════════════
// §8  PO3 TRACKING
// ═══════════════════════════════════════════════════════════════════════════
var htfHistory = array.new<HTFData>()
var float curO = na, var float curH = na, var float curL = na
var int   curOIdx = na, var int curHIdx = na, var int curLIdx = na
var float curDelta = 0.0
var int   curStartTime = na

if showPo3Input
    if htfNewBar
        if not na(curO)
            htfHistory.unshift(HTFData.new(curO, curH, curL, close[1], curOIdx, curHIdx, curLIdx, curDelta, curStartTime))
            if htfHistory.size() > 10
                htfHistory.pop()
        curO := open, curH := high, curL := low
        curOIdx := bar_index, curHIdx := bar_index, curLIdx := bar_index
        curDelta := (close > open ? volume : close < open ? -volume : 0)
        curStartTime := time
    else
        if high > curH or na(curH)
            curH := high, curHIdx := bar_index
        if low < curL or na(curL)
            curL := low, curLIdx := bar_index
        curDelta += (close > open ? volume : close < open ? -volume : 0)

// ═══════════════════════════════════════════════════════════════════════════
// §9  HTF PATTERN LOGIC
// ═══════════════════════════════════════════════════════════════════════════
bool isBullEngulfing = showEngulfingInput and hClose > hOpen and hPrevClose < hPrevOpen and hClose >= hPrevOpen and hOpen <= hPrevClose
bool isBearEngulfing = showEngulfingInput and hClose < hOpen and hPrevClose > hPrevOpen and hClose <= hPrevOpen and hOpen >= hPrevClose
bool isHammer        = showPinBarsInput   and (math.min(hOpen, hClose) - hLow) > (hHigh - hLow) * 0.6 and math.abs(hClose - hOpen) < (hHigh - hLow) * 0.3
bool isShootingStar  = showPinBarsInput   and (hHigh - math.max(hOpen, hClose)) > (hHigh - hLow) * 0.6 and math.abs(hClose - hOpen) < (hHigh - hLow) * 0.3
bool isBullPattern   = isBullEngulfing or isHammer
bool isBearPattern   = isBearEngulfing or isShootingStar

// ═══════════════════════════════════════════════════════════════════════════
// §10  RSI DIVERGENCE LOGIC  (price-scale lines only — oscillator removed)
// ═══════════════════════════════════════════════════════════════════════════
bool bullDivConfirmed = false
bool bearDivConfirmed = false

var float plPriceMem = na, var float plRsiMem = na, var int plIndexMem = na
if not na(plRsi)
    if not na(plPriceMem) and rsiValue[lbRInput] > plRsiMem and low[lbRInput] < plPriceMem
        bullDivConfirmed := true
        if showDivInput
            line.new(plIndexMem, plPriceMem, bar_index - lbRInput, low[lbRInput],
                 color=bullDivColInput, width=2)
            label.new(bar_index - lbRInput, low[lbRInput], "Bull Div",
                 yloc=yloc.belowbar, textcolor=bullDivColInput, color=#00000000,
                 size=size.small, style=label.style_label_up)
    plPriceMem := low[lbRInput], plRsiMem := rsiValue[lbRInput], plIndexMem := bar_index - lbRInput

var float phPriceMem = na, var float phRsiMem = na, var int phIndexMem = na
if not na(phRsi)
    if not na(phPriceMem) and rsiValue[lbRInput] < phRsiMem and high[lbRInput] > phPriceMem
        bearDivConfirmed := true
        if showDivInput
            line.new(phIndexMem, phPriceMem, bar_index - lbRInput, high[lbRInput],
                 color=bearDivColInput, width=2)
            label.new(bar_index - lbRInput, high[lbRInput], "Bear Div",
                 yloc=yloc.abovebar, textcolor=bearDivColInput, color=#00000000,
                 size=size.small, style=label.style_label_down)
    phPriceMem := high[lbRInput], phRsiMem := rsiValue[lbRInput], phIndexMem := bar_index - lbRInput

// ═══════════════════════════════════════════════════════════════════════════
// §11  FIX WINDOW BGCOLOR
// ═══════════════════════════════════════════════════════════════════════════
bgcolor(i_show_fix and i_fix_am_en  and _fx_am  ? i_col_fix_am  : na, title="LBMA AM Fix BG")
bgcolor(i_show_fix and i_fix_pm_en  and _fx_pm  ? i_col_fix_pm  : na, title="LBMA PM Fix BG")
bgcolor(i_show_fix and i_fix_cme_en and _fx_cme ? i_col_fix_cme : na, title="CME NY Fix BG")

// ═══════════════════════════════════════════════════════════════════════════
// §12  PLOTSHAPES — AMT Rejection
// ═══════════════════════════════════════════════════════════════════════════
plotshape(i_show_amt and _is_ice,  "Iceberg", shape.labelup,   location.belowbar, color.new(color.white, 100), size=size.small, text="🧊", textcolor=C_CYAN)
plotshape(i_show_amt and _rej_top, "Rej Top", shape.labeldown, location.abovebar, color.new(color.white, 100), size=size.small, text="⏹",  textcolor=C_GOLD)
plotshape(i_show_amt and _rej_bot, "Rej Bot", shape.labelup,   location.belowbar, color.new(color.white, 100), size=size.small, text="⏹",  textcolor=C_GOLD)

// ═══════════════════════════════════════════════════════════════════════════
// §13  HTF PATTERN DRAWINGS (fires on each new HTF bar)
// ═══════════════════════════════════════════════════════════════════════════
if htfNewBar and not na(hTimeStart)
    if isBullPattern
        drawHtfPattern(
             isBullEngulfing ? "Bull Engulfing" : "Hammer",
             hHigh, hLow, hOpen, hClose, hTimeStart, hTimeEnd, bullPatternColInput, true)
    if isBearPattern
        drawHtfPattern(
             isBearEngulfing ? "Bear Engulfing" : "Shooting Star",
             hHigh, hLow, hOpen, hClose, hTimeStart, hTimeEnd, bearPatternColInput, false)

// ═══════════════════════════════════════════════════════════════════════════
// §14  BARSTATE.ISLAST — Volume Profile + Heatmap + Info Label
// ═══════════════════════════════════════════════════════════════════════════
var array<box>   _vp_boxes = array.new<box>()
var array<line>  _vp_lines = array.new<line>()
var array<label> _vp_lbls  = array.new<label>()
var int hm_start = 0
var label _mq_lbl = na

if barstate.islast

    // ── Line style helpers ────────────────────────────────────────────────
    string _poc_ls = switch i_poc_style
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        =>          line.style_solid
    string _vah_ls = switch i_vah_style
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        =>          line.style_solid
    string _val_ls = switch i_val_style
        "Dashed" => line.style_dashed
        "Dotted" => line.style_dotted
        =>          line.style_solid

    // ── Clear VP objects ──────────────────────────────────────────────────
    if array.size(_vp_boxes) > 0
        for _vpb in _vp_boxes
            box.delete(_vpb)
        array.clear(_vp_boxes)
    if array.size(_vp_lines) > 0
        for _vpln in _vp_lines
            line.delete(_vpln)
        array.clear(_vp_lines)
    if array.size(_vp_lbls) > 0
        for _vplb in _vp_lbls
            label.delete(_vplb)
        array.clear(_vp_lbls)

    // ══════════════════════════════════════════════════════════════════════
    // VOLUME PROFILE  (QC_SovereignMatrix v2.1 §25 — verbatim, adaptive range)
    // ══════════════════════════════════════════════════════════════════════
    if i_show_vp
        // Adaptive VP range using vp_lb_eff
        int   _vp_bars_act = vp_lb_eff
        float _vp_hi_act   = close
        float _vp_lo_act   = close
        for _ri = 1 to _vp_bars_act - 1
            _vp_hi_act := math.max(_vp_hi_act, nz(high[_ri],  high))
            _vp_lo_act := math.min(_vp_lo_act, nz(low[_ri],   low))

        float _vp_step = math.max((_vp_hi_act - _vp_lo_act) / i_vp_rows, syminfo.mintick)
        float[] _vp_lv = array.new_float(i_vp_rows + 1)
        for _i = 0 to i_vp_rows
            array.set(_vp_lv, _i, _vp_lo_act + float(_i) * _vp_step)
        float[] _vp_bu = array.new_float(i_vp_rows, 0.0)
        float[] _vp_be = array.new_float(i_vp_rows, 0.0)
        for _b = 0 to _vp_bars_act - 1
            int _ix = math.max(0, math.min(i_vp_rows - 1, int((close[_b] - _vp_lo_act) / _vp_step)))
            if close[_b] >= open[_b]
                array.set(_vp_bu, _ix, array.get(_vp_bu, _ix) + volume[_b])
            else
                array.set(_vp_be, _ix, array.get(_vp_be, _ix) + volume[_b])
        float[] _tv      = array.new_float(i_vp_rows)
        float   _mx_v    = 0.0
        int     _poc_idx = 0
        for _i = 0 to i_vp_rows - 1
            float _vp_t = array.get(_vp_bu, _i) + array.get(_vp_be, _i)
            array.set(_tv, _i, _vp_t)
            if _vp_t > _mx_v
                _mx_v    := _vp_t
                _poc_idx := _i
        float _va_total  = array.get(_tv, _poc_idx)
        float _va_target = array.sum(_tv) * i_vp_pct / 100.0
        int   _va_up     = _poc_idx
        int   _va_dn     = _poc_idx
        for _x = 0 to i_vp_rows - 1
            if _va_total >= _va_target
                break
            float _uv = _va_up < i_vp_rows - 1 ? array.get(_tv, _va_up + 1) : 0.0
            float _dv = _va_dn > 0             ? array.get(_tv, _va_dn - 1) : 0.0
            if _uv >= _dv
                _va_total += _uv
                _va_up    += 1
            else
                _va_total += _dv
                _va_dn    -= 1
        float _poc_lvl = (array.get(_vp_lv, _poc_idx) + array.get(_vp_lv, _poc_idx + 1)) / 2.0
        float _vah_lvl = array.get(_vp_lv, math.min(_va_up + 1, i_vp_rows))
        float _val_lvl = array.get(_vp_lv, _va_dn)
        float _dist_vp = (_vp_hi_act - _vp_lo_act) / 500.0
        int   _vp_left = bar_index - _vp_bars_act + 1
        int   _vp_rght = bar_index + 10
        if i_vp_show_boxes and _mx_v > 0
            for _i = 0 to i_vp_rows - 1
                float _bu2 = array.get(_vp_bu, _i)
                float _be2 = array.get(_vp_be, _i)
                float _tot = _bu2 + _be2
                if _tot > 0
                    int   _nv  = int(math.round((_tot / _mx_v) * _vp_bars_act))
                    int   _nb  = int(math.round((_bu2 / _tot) * _nv))
                    int   _xl  = bar_index + _vp_bars_act - _nv
                    float _lo  = array.get(_vp_lv, _i)
                    float _hi  = array.get(_vp_lv, _i + 1)
                    bool  _inva = _i >= _va_dn and _i <= _va_up
                    if _nb > 0
                        array.push(_vp_boxes, box.new(_xl, _hi - _dist_vp, _xl + _nb, _lo + _dist_vp,
                             border_color=na, bgcolor=_inva ? color.new(#2962FF, 30) : color.new(#2962FF, 75)))
                    int _nbe = _nv - _nb
                    if _nbe > 0
                        array.push(_vp_boxes, box.new(_xl + _nb, _hi - _dist_vp, _xl + _nv, _lo + _dist_vp,
                             border_color=na, bgcolor=_inva ? color.new(#FF6F00, 30) : color.new(#FF6F00, 75)))
        if i_vp_show_poc
            array.push(_vp_lines, line.new(_vp_left, _poc_lvl, _vp_rght, _poc_lvl,
                 color=i_c_poc_line, width=i_poc_width, style=_poc_ls))
            if i_vp_show_poc_lbl
                array.push(_vp_lbls, label.new(_vp_rght, _poc_lvl,
                     "POC $" + str.tostring(math.round_to_mintick(_poc_lvl)),
                     style=label.style_label_left,
                     color=color.new(i_c_poc_line, 80), textcolor=i_c_poc_line, size=size.tiny))
        if i_vp_show_vah
            array.push(_vp_lines, line.new(_vp_left, _vah_lvl, _vp_rght, _vah_lvl,
                 color=i_c_vah_line, width=i_vah_width, style=_vah_ls))
            if i_vp_show_vah_lbl
                array.push(_vp_lbls, label.new(_vp_rght, _vah_lvl, "VAH",
                     style=label.style_none, textcolor=i_c_vah_line, size=size.tiny))
        if i_vp_show_val
            array.push(_vp_lines, line.new(_vp_left, _val_lvl, _vp_rght, _val_lvl,
                 color=i_c_val_line, width=i_val_width, style=_val_ls))
            if i_vp_show_val_lbl
                array.push(_vp_lbls, label.new(_vp_rght, _val_lvl, "VAL",
                     style=label.style_none, textcolor=i_c_val_line, size=size.tiny))

    // ══════════════════════════════════════════════════════════════════════
    // HEATMAP VISUALIZATION  (EA FundOS v2.1 §9 — verbatim, entropy-adjusted)
    // ══════════════════════════════════════════════════════════════════════
    if hm_lines.size() > 0
        for _hln in hm_lines
            _hln.delete()
        hm_lines.clear()
    for b in hm_boxes
        b.delete()
    hm_boxes.clear()
    for lbl in hm_labels
        lbl.delete()
    hm_labels.clear()

    // Entropy-adjusted transparency
    int _adj_tr_buy  = math.min(95, i_buy_tr  + _ent_tr_add)
    int _adj_tr_sell = math.min(95, i_sell_tr + _ent_tr_add)

    if h_l.size() >= 2
        float _hl_max = h_l.max()
        float _hl_min = h_l.min()
        if not na(_hl_max) and not na(_hl_min) and _hl_max > _hl_min
            float step_bin = (_hl_max - _hl_min) / bins
            for j = 0 to bins - 1
                volume_bins.set(j, 0)
            if pivots.size() > 0
                for i = 0 to pivots.size() - 1
                    pivot lvl    = pivots.get(i)
                    float vol_   = lvl.vol
                    float line_y = lvl.value
                    for j = 0 to bins - 1
                        float lower_b = _hl_min + step_bin * j
                        float mid_b   = lower_b + step_bin / 2
                        if math.abs(line_y - mid_b) < step_bin
                            volume_bins.set(j, volume_bins.get(j) + vol_)
            float vb_max = volume_bins.max()
            float vb_min = volume_bins.min()
            float vb_avg = volume_bins.avg()
            if vb_max > 0
                for j = 0 to bins - 1
                    float lower_b  = _hl_min + step_bin * j
                    float upper_b  = lower_b + step_bin
                    float mid_b    = lower_b + step_bin / 2
                    float voll     = volume_bins.get(j)
                    float valueVol = voll / vb_max * 50
                    color col_bin  = close > mid_b ? buyColor : sellColor
                    color m_col    = color.from_gradient(voll, vb_min, vb_max,
                                         color.new(col_bin, 80), color.new(col_bin, 0))
                    color m_col1   = color.from_gradient(voll, vb_min, vb_max,
                                         color.new(col_bin, 50), color.new(col_bin, 0))
                    if not (close < upper_b and close > lower_b)
                        if displPf and valueVol != 0 and i_hm_show_boxes
                            hm_boxes.push(box.new(
                                 bar_index + hm_offset,
                                 upper_b,
                                 bar_index + hm_offset + int(valueVol),
                                 lower_b,
                                 bgcolor      = voll == vb_max and poc_show ? maxColor : m_col,
                                 border_color = chart.bg_color,
                                 text         = voll > vb_avg ? str.tostring(voll, format.volume) : "",
                                 text_halign  = text.align_left))
                            hm_boxes.push(box.new(
                                 bar_index + hm_offset,
                                 upper_b,
                                 bar_index + hm_offset + 5,
                                 lower_b,
                                 text         = str.tostring(valueVol * 2, format.percent),
                                 bgcolor      = color(na),
                                 border_color = color(na),
                                 text_color   = voll == vb_max ? maxColor : m_col1))
                        if i_hm_show_lines
                            bool isLower_cond = close > mid_b
                            for i = 0 to hm_lb_eff - 1
                                if isLower_cond
                                    if low[i] < mid_b
                                        hm_start := bar_index - i
                                        break
                                    if i == hm_lb_eff - 1
                                        hm_start := bar_index - i
                                        break
                                else
                                    if high[i] > mid_b
                                        hm_start := bar_index - i
                                        break
                                    if i == hm_lb_eff - 1
                                        hm_start := bar_index - i
                                        break
                            color color_ln = voll == vb_max and poc_show
                                 ? maxColor
                                 : color.from_gradient(valueVol, 0, 50, color(na),
                                       isLower_cond
                                       ? color.new(buyColor,  _adj_tr_buy)
                                       : color.new(sellColor, _adj_tr_sell))
                            hm_lines.push(line.new(
                                 hm_start + 3, mid_b,
                                 bar_index + hm_offset, mid_b,
                                 width = math.max(1, int(valueVol / 5)),
                                 color = color_ln))

    // ══════════════════════════════════════════════════════════════════════
    // §14b  MARKET QUALITY INFO LABEL (DC + Entropy + VPIN)
    // ══════════════════════════════════════════════════════════════════════
    if i_dc_show_lbl or i_ent_show_lbl or i_vpin_show_lbl
        label.delete(_mq_lbl)
        string _s1 = i_dc_en   ? "DC:" + str.tostring(dc_int) + "b  HM:" + str.tostring(hm_lb_eff) + "  VP:" + str.tostring(i_vp_bars) + "(fixed)" : ""
        string _s2 = i_ent_en  ? "H:" + str.tostring(math.round(_ent_norm*100.0)) + "%  " + _ent_str : ""
        string _s3 = i_vpin_en ? "VPIN:" + str.tostring(math.round(_vpin_val*100.0)) + "%  " + _vpin_str : ""
        string _sep1 = _s1 != "" and _s2 != "" ? "\n" : ""
        string _sep2 = (_s1 != "" or _s2 != "") and _s3 != "" ? "\n" : ""
        string _full = _s1 + _sep1 + _s2 + _sep2 + _s3
        if _full != ""
            _mq_lbl := label.new(bar_index + 4, high + ta.atr(14)*0.8, _full,
                 style=label.style_label_left, color=color.new(#0D1117,75),
                 textcolor=_ent_col, size=size.small)

// ═══════════════════════════════════════════════════════════════════════════
// §15  BARSTATE.ISLAST — PO3 UI
// ═══════════════════════════════════════════════════════════════════════════
var uiElements = array.new<HTFCandleUI>()

if barstate.islast and showPo3Input
    if uiElements.size() > 0
        for i = 0 to uiElements.size() - 1
            HTFCandleUI ui = uiElements.get(i)
            ui.body.delete()
            ui.wick.delete()
            if not na(ui.oM)
                ui.oM.delete()
            if not na(ui.hM)
                ui.hM.delete()
            if not na(ui.lM)
                ui.lM.delete()
            if not na(ui.cM)
                ui.cM.delete()
            if not na(ui.oL)
                ui.oL.delete()
            if not na(ui.hL)
                ui.hL.delete()
            if not na(ui.lL)
                ui.lL.delete()
            if not na(ui.cL)
                ui.cL.delete()
            if not na(ui.dL)
                ui.dL.delete()
            if not na(ui.tL)
                ui.tL.delete()
    uiElements.clear()

    int candleWidth = 6
    int candleGap   = 10
    for p = 0 to candleCountInput - 1
        bool isLive   = (p == candleCountInput - 1)
        HTFData data  = isLive
             ? HTFData.new(curO, curH, curL, close, curOIdx, curHIdx, curLIdx, curDelta, curStartTime)
             : (htfHistory.size() > (candleCountInput - 2) - p ? htfHistory.get((candleCountInput - 2) - p) : na)
        if not na(data)
            int   startIdx  = last_bar_index + po3OffsetInput + (p * (candleWidth + candleGap))
            int   endIdx    = startIdx + candleWidth
            int   midIdx    = (startIdx + endIdx) / 2
            color baseColor = data.c >= data.o ? bullPatternColInput : bearPatternColInput
            color wickColor = isLive ? baseColor : color.new(baseColor, 70)
            line  wLine     = line.new(midIdx, data.h, midIdx, data.l, color=wickColor, width=2)
            box   bBox      = box.new(startIdx, math.max(data.o, data.c), endIdx, math.min(data.o, data.c),
                 border_color=wickColor, bgcolor=color.new(baseColor, isLive ? 20 : 85))
            line oM = na, line hM = na, line lM = na, line cM = na
            label oL = na, label hL = na, label lL = na, label cL = na, label dL = na
            if isLive
                oM := line.new(data.oIdx, data.o, startIdx, data.o, color=NEUTRAL_COLOR,       style=line.style_dashed)
                hM := line.new(data.hIdx, data.h, midIdx,   data.h, color=bullPatternColInput, style=line.style_dashed)
                lM := line.new(data.lIdx, data.l, midIdx,   data.l, color=bearPatternColInput, style=line.style_dashed)
                cM := line.new(bar_index, data.c, endIdx,   data.c, color=baseColor,           style=line.style_dashed)
                if showPo3LabelsInput
                    oL := label.new(endIdx + 1, data.o, "Open: "  + str.tostring(data.o, format.mintick), color=#00000000, textcolor=NEUTRAL_COLOR,       style=label.style_label_left, size=size.small)
                    hL := label.new(endIdx + 1, data.h, "High: "  + str.tostring(data.h, format.mintick), color=#00000000, textcolor=bullPatternColInput, style=label.style_label_left, size=size.small)
                    lL := label.new(endIdx + 1, data.l, "Low: "   + str.tostring(data.l, format.mintick), color=#00000000, textcolor=bearPatternColInput, style=label.style_label_left, size=size.small)
                    cL := label.new(endIdx + 1, data.c, "Close: " + str.tostring(data.c, format.mintick), color=#00000000, textcolor=baseColor,            style=label.style_label_left, size=size.small)
            int    totalMins = timeframe.in_seconds(htfInput) / 60
            string tfStr     = totalMins >= 1440 ? str.tostring(totalMins / 1440) + "D" :
                               totalMins >= 60   ? str.tostring(totalMins / 60)   + "H" :
                               str.tostring(totalMins) + "m"
            label tL = label.new(midIdx, data.h,
                 tfStr + (not isLive ? "\n" + str.format_time(data.startTime, "HH:mm", syminfo.timezone) : ""),
                 color=#00000000,
                 textcolor=isLive ? NEUTRAL_COLOR : color.new(NEUTRAL_COLOR, 60),
                 style=label.style_label_down, size=size.normal)
            if showPo3DeltaInput
                dL := label.new(midIdx, data.l, "Delta: " + formatDelta(data.delta),
                     color=#00000000,
                     textcolor=isLive ? (data.delta >= 0 ? bullPatternColInput : bearPatternColInput) : color.new(NEUTRAL_COLOR, 60),
                     style=label.style_label_up, size=size.normal)
            uiElements.push(HTFCandleUI.new(bBox, wLine, oM, hM, lM, cM, oL, hL, lL, cL, dL, tL))

// ═══════════════════════════════════════════════════════════════════════════
// §16  ALERTS
// ═══════════════════════════════════════════════════════════════════════════
if alertBullDivInput and bullDivConfirmed
    alert("Bullish RSI Divergence on " + syminfo.ticker, alert.freq_once_per_bar_close)
if alertBearDivInput and bearDivConfirmed
    alert("Bearish RSI Divergence on " + syminfo.ticker, alert.freq_once_per_bar_close)
if alertBullEngInput and htfNewBar and isBullEngulfing
    alert("HTF Bullish Engulfing on " + syminfo.ticker, alert.freq_once_per_bar_close)
if alertBearEngInput and htfNewBar and isBearEngulfing
    alert("HTF Bearish Engulfing on " + syminfo.ticker, alert.freq_once_per_bar_close)
if alertHammerInput and htfNewBar and isHammer
    alert("HTF Hammer on " + syminfo.ticker, alert.freq_once_per_bar_close)
if alertStarInput and htfNewBar and isShootingStar
    alert("HTF Shooting Star on " + syminfo.ticker, alert.freq_once_per_bar_close)
// ── v4 Alerts ─────────────────────────────────────────────────────────────
if _vpin_tox_bull and not _vpin_tox_bull[1]
    alert("VPIN Toxic Bull Flow on " + syminfo.ticker, alert.freq_once_per_bar_close)
if _vpin_tox_bear and not _vpin_tox_bear[1]
    alert("VPIN Toxic Bear Flow on " + syminfo.ticker, alert.freq_once_per_bar_close)
if _ent_struct and not _ent_struct[1]
    alert("Entropy: Market turned STRUCTURED on " + syminfo.ticker, alert.freq_once_per_bar_close)
if _ent_random and not _ent_random[1]
    alert("Entropy: Market turned RANDOM on " + syminfo.ticker, alert.freq_once_per_bar_close)

// ═══════════════════════════════════════════════════════════════════════════
// §17  KALMAN D7 PLOTS  (et20tradeview — MPL 2.0)
// ═══════════════════════════════════════════════════════════════════════════
p2 = plot(show_pul ? ema2 : na, "Pumori",        color=color.new(color.purple, 70), force_overlay=true)
p3 = plot(show_pul ? ema2 : na, offset=-1,        color=color.new(color.purple, 70), force_overlay=true)
fill(p2, p3, color=color.new(#69359C, 70), title="Lag Shading")

p4 = plot(smoothb, "Kalman Base", color=color.rgb(230, 65, 195), linewidth=2)
p5 = plot(smooths, "Kalman Fast", color=color.rgb(80, 100, 240), linewidth=2)

fill(p4, p5, color=smooths > smoothb ? color.new(color.aqua, 80) : color.new(color.red, 80), title="Kalman Fill")

// ── RQ-Kernel Plots ───────────────────────────────────────────────────────
p_rq_fast = plot(i_rq_en ? rq_fast : na, "RQ-Kernel Fast", color=i_rq_fast_col, linewidth=2)
p_rq_slow = plot(i_rq_en ? rq_slow : na, "RQ-Kernel Slow", color=i_rq_slow_col, linewidth=2)
fill(p_rq_fast, p5, i_rq_en and i_rq_show_ub ? i_rq_ub_col : na, title="RQ-Kalman Uncertainty Band")

// ── VPIN Plotshapes ───────────────────────────────────────────────────────
plotshape(i_vpin_en and _vpin_tox_bull and not _vpin_tox_bull[1], "VPIN Toxic Bull", shape.labeldown, location.abovebar, color.new(i_vpin_tox_col,20), size=size.tiny, text="⚡", textcolor=color.white)
plotshape(i_vpin_en and _vpin_tox_bear and not _vpin_tox_bear[1], "VPIN Toxic Bear", shape.labelup,   location.belowbar, color.new(i_vpin_tox_col,20), size=size.tiny, text="⚡", textcolor=color.white)

// ═══════════════════════════════════════════════════════════════════════════
// §18  ⑭ PREDICTIVE MONTE CARLO ENGINE — VISUALIZATION (LuxAlgo, namespaced)
// ═══════════════════════════════════════════════════════════════════════════
bool mc_isTrigger = barstate.islast
if mc_anchorInput
    mc_isTrigger := (bar_index % mc_anchorStep == 0)

if mc_enableInput and mc_isTrigger
    if not mc_anchorInput
        for pl in mc_polylines
            pl.delete()
        mc_polylines.clear()
        for l in mc_srLines
            l.delete()
        mc_srLines.clear()
        for lbl in mc_srLabels
            lbl.delete()
        mc_srLabels.clear()
        for b in mc_srBoxes
            b.delete()
        mc_srBoxes.clear()

    if mc_logReturns.size() >= 2
        // Filter valid indices for Regime
        array<int>   mc_validIndices    = array.new<int>()
        array<float> mc_validLogReturns = array.new<float>()
        array<float> mc_validSimReturns = array.new<float>()

        for k = 0 to mc_simReturns.size() - 1
            if mc_regimeInput == MC_REGIME_NONE or mc_regimes.get(k) == mc_currentRegime
                mc_validIndices.push(k)
                mc_validLogReturns.push(mc_logReturns.get(k))
                mc_validSimReturns.push(mc_simReturns.get(k))

        if mc_validIndices.size() == 0 // Fallback if no matching regime found
            for k = 0 to mc_simReturns.size() - 1
                mc_validIndices.push(k)
                mc_validLogReturns.push(mc_logReturns.get(k))
                mc_validSimReturns.push(mc_simReturns.get(k))

        float mc_muLog = mc_validLogReturns.avg()
        float mc_sigmaLog = nz(mc_validLogReturns.stdev() * mc_volMulti)

        float mc_muSimp = mc_validSimReturns.avg()
        float mc_sigmaSimp = nz(mc_validSimReturns.stdev() * mc_volMulti)

        int mc_seedSalt = 0
        if mc_methodInput == MC_METHOD_GBM
            mc_seedSalt := 1000
        else if mc_methodInput == MC_METHOD_SRW
            mc_seedSalt := 2000

        LCG mc_rng = LCG.new(int(time) + mc_seedInput + mc_seedSalt)
        array<Path> mc_paths = array.new<Path>()

        array<float> mc_avgPathPoints = array.new<float>(mc_projInput + 1, 0.0)

        for i = 0 to mc_simInput - 1
            array<chart.point> pts = array.new<chart.point>()
            pts.push(chart.point.now(close))
            mc_avgPathPoints.set(0, mc_avgPathPoints.get(0) + close)

            float currentPrice = close

            for j = 1 to mc_projInput
                float stepReturn = 0.0

                if mc_methodInput == MC_METHOD_HS
                    int idx = mc_rng.nextInt(0, mc_validIndices.size() - 1)
                    stepReturn := mc_simReturns.get(mc_validIndices.get(idx))
                    currentPrice += stepReturn

                else if mc_methodInput == MC_METHOD_SRW
                    float z = mc_rng.nextGaussian()
                    stepReturn := mc_muSimp + mc_sigmaSimp * z
                    currentPrice += stepReturn

                else if mc_methodInput == MC_METHOD_GBM
                    float z = mc_rng.nextGaussian()
                    stepReturn := math.exp((mc_muLog - math.pow(mc_sigmaLog, 2) / 2.0) + mc_sigmaLog * z)
                    currentPrice *= stepReturn

                pts.push(chart.point.from_index(bar_index + j, currentPrice))
                mc_avgPathPoints.set(j, mc_avgPathPoints.get(j) + currentPrice)

            mc_paths.push(Path.new(pts, currentPrice))

        // Get Percentiles for Path Coloring
        array<float> mc_endPrices = array.new<float>()
        for p in mc_paths
            mc_endPrices.push(p.endPrice)

        mc_endPrices.sort()
        float mc_pTopRaw = mc_endPrices.percentile_nearest_rank(mc_topPercentile)
        float mc_pBotRaw = mc_endPrices.percentile_nearest_rank(mc_botPercentile)

        float mc_pMean = mc_endPrices.avg()
        float mc_pMedian = mc_endPrices.percentile_nearest_rank(50.0)
        float mc_pMaxRaw = mc_endPrices.max()
        float mc_pMinRaw = mc_endPrices.min()

        float mc_pTop = math.max(mc_pTopRaw, close)
        float mc_pBot = math.min(mc_pBotRaw, close)
        float mc_pMax = math.max(mc_pMaxRaw, mc_pTop + syminfo.mintick)
        float mc_pMin = math.min(mc_pMinRaw, mc_pBot - syminfo.mintick)

        // ⑮ persist band bounds for the confluence signal
        mc_bandR1   := mc_pTop
        mc_bandS1   := mc_pBot
        mc_bandMax  := mc_pMax
        mc_bandMin  := mc_pMin
        mc_bandMean := mc_pMean

        if mc_srInput
            int endIdx = bar_index + mc_projInput

            float zHeight = (mc_pMax - mc_pMin) * 0.015
            int hSteps = math.min(50, mc_projInput)
            float stepSize = float(mc_projInput) / hSteps

            for i = 0 to hSteps - 1
                int leftBar = bar_index + math.round(i * stepSize)
                int rightBar = bar_index + math.round((i + 1) * stepSize)
                int transp = 30 + int(70 * (float(i) / (hSteps - 1)))

                mc_srBoxes.push(box.new(leftBar, mc_pMax + zHeight, rightBar, mc_pMax - zHeight, border_color = na, bgcolor = color.new(mc_topColor, transp)))
                mc_srBoxes.push(box.new(leftBar, mc_pTop + zHeight, rightBar, mc_pTop - zHeight, border_color = na, bgcolor = color.new(mc_topColor, transp)))
                mc_srBoxes.push(box.new(leftBar, mc_pBot + zHeight, rightBar, mc_pBot - zHeight, border_color = na, bgcolor = color.new(mc_bottomColor, transp)))
                mc_srBoxes.push(box.new(leftBar, mc_pMin + zHeight, rightBar, mc_pMin - zHeight, border_color = na, bgcolor = color.new(mc_bottomColor, transp)))

            mc_srLabels.push(label.new(endIdx, mc_pMax, "Max", color = #00000000, textcolor = mc_topColor, style = label.style_label_left, size = size.small))
            mc_srLabels.push(label.new(endIdx, mc_pTop, "R1", color = #00000000, textcolor = mc_topColor, style = label.style_label_left, size = size.small))
            mc_srLabels.push(label.new(endIdx, mc_pBot, "S1", color = #00000000, textcolor = mc_bottomColor, style = label.style_label_left, size = size.small))
            mc_srLabels.push(label.new(endIdx, mc_pMin, "Min", color = #00000000, textcolor = mc_bottomColor, style = label.style_label_left, size = size.small))

        // Draw the simulation polylines
        if not mc_anchorInput or mc_anchorPaths
            for p in mc_paths
                color pathColor = mc_midColor
                if p.endPrice >= mc_pTop
                    pathColor := mc_topColor
                else if p.endPrice <= mc_pBot
                    pathColor := mc_bottomColor

                mc_polylines.push(polyline.new(p.pts, line_color = color.new(pathColor, mc_transpInput), line_width = 1))

        // Calculate Envelope Coordinates
        array<chart.point> mc_maxPts = array.new<chart.point>()
        array<chart.point> mc_minPts = array.new<chart.point>()

        for j = 0 to mc_projInput
            float maxAtJ = na
            float minAtJ = na
            for i = 0 to mc_simInput - 1
                float val = mc_paths.get(i).pts.get(j).price
                if na(maxAtJ) or val > maxAtJ
                    maxAtJ := val
                if na(minAtJ) or val < minAtJ
                    minAtJ := val

            mc_maxPts.push(chart.point.from_index(bar_index + j, maxAtJ))
            mc_minPts.unshift(chart.point.from_index(bar_index + j, minAtJ))

        array<chart.point> mc_envPts = array.copy(mc_maxPts)
        for pt in mc_minPts
            mc_envPts.push(pt)
        mc_envPts.push(mc_maxPts.get(0))

        // Draw the envelope and average
        mc_polylines.push(polyline.new(mc_envPts, line_color = na, fill_color = mc_envColor))

        array<chart.point> mc_avgPts = array.new<chart.point>()
        for j = 0 to mc_projInput
            mc_avgPts.push(chart.point.from_index(bar_index + j, mc_avgPathPoints.get(j) / mc_simInput))

        mc_polylines.push(polyline.new(mc_avgPts, line_color = mc_avgColor, line_width = 2))

        // Combine Dashboard
        if mc_dashboardInput
            var table t_able = table.new(mc_parsedDashboardPosition, mc_dash2Input ? 5 : 2, 9, bgcolor = MC_BACKGROUND, border_width = 0, frame_color = MC_BORDERS, frame_width = 1, force_overlay = false)

            t_able.merge_cells(0, 0, 1, 0)
            mc_cell(t_able, 0, 0, 'Monte Carlo Metrics', text_col = MC_DATA, align = text.align_center)

            mc_divider(t_able, 1, mc_dash2Input ? 4 : 1)

            mc_cell(t_able, 0, 2, 'Mean Expected', text_col = MC_HEADERS, align = text.align_left)
            mc_cell(t_able, 1, 2, str.tostring(mc_pMean, format.mintick), text_col = MC_DATA)

            mc_divider(t_able, 3, mc_dash2Input ? 4 : 1)

            mc_cell(t_able, 0, 4, 'Median Expected', text_col = MC_HEADERS, align = text.align_left)
            mc_cell(t_able, 1, 4, str.tostring(mc_pMedian, format.mintick), text_col = MC_DATA)

            mc_divider(t_able, 5, mc_dash2Input ? 4 : 1)

            mc_cell(t_able, 0, 6, 'Max Projection', text_col = MC_HEADERS, align = text.align_left)
            mc_cell(t_able, 1, 6, str.tostring(mc_pMax, format.mintick), text_col = MC_DATA)

            mc_divider(t_able, 7, mc_dash2Input ? 4 : 1)

            mc_cell(t_able, 0, 8, 'Min Projection', text_col = MC_HEADERS, align = text.align_left)
            mc_cell(t_able, 1, 8, str.tostring(mc_pMin, format.mintick), text_col = MC_DATA)

            // Render the candle next to the metrics if dashed input is on
            if mc_dash2Input
                if mc_endPrices.size() > 0
                    float pred_open = close
                    float pred_close = mc_pMean
                    float p90 = mc_endPrices.percentile_nearest_rank(90.0)
                    float p10 = mc_endPrices.percentile_nearest_rank(10.0)

                    float pred_high = math.max(p90, math.max(pred_open, pred_close))
                    float pred_low = math.min(p10, math.min(pred_open, pred_close))

                    color pred_color = pred_close >= pred_open ? mc_bullColor : mc_bearColor

                    t_able.merge_cells(2, 0, 4, 0)
                    mc_cell(t_able, 2, 0, 'Forecasted Candle', text_col = MC_DATA, align = text.align_center)

                    mc_cell(t_able, 2, 2, 'Direction', text_col = MC_HEADERS, align = text.align_left)
                    mc_cell(t_able, 3, 2, pred_close >= pred_open ? "Bullish" : "Bearish", text_col = pred_color)

                    mc_cell(t_able, 2, 4, 'Expected High', text_col = MC_HEADERS, align = text.align_left)
                    mc_cell(t_able, 3, 4, str.tostring(pred_high, format.mintick), text_col = MC_DATA)

                    mc_cell(t_able, 2, 6, 'Expected Close', text_col = MC_HEADERS, align = text.align_left)
                    mc_cell(t_able, 3, 6, str.tostring(pred_close, format.mintick), text_col = MC_DATA)

                    mc_cell(t_able, 2, 8, 'Expected Low', text_col = MC_HEADERS, align = text.align_left)
                    mc_cell(t_able, 3, 8, str.tostring(pred_low, format.mintick), text_col = MC_DATA)

                    t_able.merge_cells(4, 2, 4, 8)

                    float pRange = math.max(pred_high - pred_low, syminfo.mintick)
                    float bodyTop = math.max(pred_open, pred_close)
                    float bodyBot = math.min(pred_open, pred_close)

                    int totalLevels = 9
                    int topWickLevels = math.max(0, math.round((pred_high - bodyTop) / pRange * totalLevels))
                    int botWickLevels = math.max(0, math.round((bodyBot - pred_low) / pRange * totalLevels))
                    int bodyLevels = math.max(1, math.round((bodyTop - bodyBot) / pRange * totalLevels))

                    int currentTotal = topWickLevels + botWickLevels + bodyLevels
                    if currentTotal > totalLevels
                        int diff = currentTotal - totalLevels
                        while diff > 0
                            if topWickLevels >= botWickLevels and topWickLevels > 0
                                topWickLevels -= 1
                            else if botWickLevels > 0
                                botWickLevels -= 1
                            else
                                bodyLevels -= 1
                            diff -= 1
                    else if currentTotal < totalLevels
                        int diff = totalLevels - currentTotal
                        bodyLevels += diff

                    string visualCandle = ""
                    if topWickLevels > 0
                        for i = 1 to topWickLevels
                            visualCandle += "│\n"
                    if bodyLevels > 0
                        for i = 1 to bodyLevels
                            visualCandle += "██\n"
                    if botWickLevels > 0
                        for i = 1 to botWickLevels
                            visualCandle += "│\n"

                    visualCandle := str.substring(visualCandle, 0, math.max(0, str.length(visualCandle) - 1))

                    mc_cell(t_able, 4, 2, visualCandle, text_col = pred_color, align = text.align_center)

// ═══════════════════════════════════════════════════════════════════════════
// §18b  ⑱ CVD-CONFLUENCE GATE — booleans consumed by ⑮/⑯  (v18, default OFF)
//   Declared here (before §19) so the signals can read the HV-Pivot level
//   arrays. Arrays are var + maintained in §22, which runs LATER in the bar, so
//   here we see levels confirmed up to the PREVIOUS bar → no lookahead. A gate
//   only bites when its toggle AND ⑱ levels are both ON; otherwise it is a
//   transparent no-op and ⑮/⑯ keep their exact validated behaviour.
// ═══════════════════════════════════════════════════════════════════════════
var array<hvLevel> _hv_res = array.new<hvLevel>()   // maintained in §22
var array<hvLevel> _hv_sup = array.new<hvLevel>()   // maintained in §22

bool  _hv_gate_sig_on = i_hv_gate_sig and i_hv_en
bool  _hv_gate_tri_on = i_hv_gate_tri and i_hv_en

float _hv_g_atr = ta.atr(14)
float _hv_g_tol = nz(_hv_g_atr, syminfo.mintick * 50.0) * i_hv_gate_atr

// BUY needs an accumulation-confirmed support at price; SELL a distribution-
// confirmed resistance. Proximity already implies price is AT the level, so no
// separate above/below check is needed — the CVD sign does the quality filter.
bool _hv_buy_ok = false
if i_hv_en
    for _lv in _hv_sup
        if math.abs(close - _lv.price) <= _hv_g_tol and _lv.cvd >= i_hv_gate_cvd
            _hv_buy_ok := true
bool _hv_sell_ok = false
if i_hv_en
    for _lv in _hv_res
        if math.abs(close - _lv.price) <= _hv_g_tol and _lv.cvd <= -i_hv_gate_cvd
            _hv_sell_ok := true

// ═══════════════════════════════════════════════════════════════════════════
// §19  ⑮ CONFLUENCE BUY / SELL SIGNAL  (v8)
//      ALL must align:
//        1) Price INSIDE Monte Carlo band (between S1 and R1)
//        2) Near a significant level (AMT VWMA value band / RQ-Kernel)
//        3) Multi-TF OB/OS : current(M5) + (H1 or H4) agree
//        4) Order-flow confirm : rejection / iceberg / delta  (>= i_sig_need_of)
//        5) (optional) Kalman turn  +  (optional) Entropy quality
//      Labels and Arrows(plotshape) toggle independently.
// ═══════════════════════════════════════════════════════════════════════════
// 1) Price INSIDE the Monte Carlo band (between S1 and R1)
bool  _sig_ready = i_sig_en and not na(mc_bandS1) and not na(mc_bandR1)
float _mc_mid    = nz(mc_bandMean, (nz(mc_bandS1) + nz(mc_bandR1)) / 2.0)
bool  _in_band   = _sig_ready and close >= mc_bandS1 - i_sig_mc_tol and close <= mc_bandR1 + i_sig_mc_tol
bool  _buy_band  = _in_band and (i_sig_band == "Lower/Upper half" ? close <= _mc_mid : true)
bool  _sell_band = _in_band and (i_sig_band == "Lower/Upper half" ? close >= _mc_mid : true)

// 2) Significant-level proximity (AMT VWMA value band + RQ-Kernel)
float _sig_atr  = ta.atr(14)
float _sig_ltol = nz(_sig_atr, syminfo.mintick * 50.0) * i_sig_lvl_atr
bool _near_rq      = i_sig_use_rq and i_rq_en and (math.abs(close - nz(rq_slow, close)) <= _sig_ltol or math.abs(close - nz(rq_fast, close)) <= _sig_ltol)
bool _near_lvl_buy  = math.abs(close - _m_val) <= _sig_ltol or math.abs(close - _m_mean) <= _sig_ltol or _near_rq
bool _near_lvl_sell = math.abs(close - _m_vah) <= _sig_ltol or math.abs(close - _m_mean) <= _sig_ltol or _near_rq

// 3) Multi-TF OB/OS — current(M5) AND (H1 or H4)
bool _os_2tf = (sig_rsi_cur <= i_sig_os) and ((sig_rsi_h1 <= i_sig_os) or (sig_rsi_h4 <= i_sig_os))
bool _ob_2tf = (sig_rsi_cur >= i_sig_ob) and ((sig_rsi_h1 >= i_sig_ob) or (sig_rsi_h4 >= i_sig_ob))

// 4) Order-flow confirm (rejection / iceberg / delta direction)
float _sig_dlt = _vpin_bvol - _vpin_svol
int _of_buy  = (_rej_bot ? 1 : 0) + (_is_ice ? 1 : 0) + (_sig_dlt > 0.0 ? 1 : 0)
int _of_sell = (_rej_top ? 1 : 0) + (_is_ice ? 1 : 0) + (_sig_dlt < 0.0 ? 1 : 0)

// 5) Kalman turn + Entropy quality (optional)
bool _kal_bull = (not i_sig_use_kal) or (smooths > smooths[1])
bool _kal_bear = (not i_sig_use_kal) or (smooths < smooths[1])
bool _ent_ok   = (not i_sig_use_ent) or (i_sig_ent_str ? _ent_struct : (not _ent_random))

bool _sig_ok_bar = (not i_sig_confirm) or barstate.isconfirmed
bool _raw_buy  = i_sig_en and _sig_ok_bar and _buy_band  and _near_lvl_buy  and _os_2tf and _of_buy  >= i_sig_need_of and _kal_bull and _ent_ok and (not _hv_gate_sig_on or _hv_buy_ok)
bool _raw_sell = i_sig_en and _sig_ok_bar and _sell_band and _near_lvl_sell and _ob_2tf and _of_sell >= i_sig_need_of and _kal_bear and _ent_ok and (not _hv_gate_sig_on or _hv_sell_ok)

// dedup spacing
var int _sig_last_bar = -100000
bool sigBuy  = _raw_buy  and (bar_index - _sig_last_bar) >= i_sig_gap
bool sigSell = _raw_sell and (bar_index - _sig_last_bar) >= i_sig_gap
if sigBuy or sigSell
    _sig_last_bar := bar_index

string _sig_sz = switch i_sig_size
    "Tiny"   => size.tiny
    "Small"  => size.small
    "Large"  => size.large
    =>          size.normal

// ── LABELS (toggle: i_sig_show_lbl) ──
if i_sig_en and i_sig_show_lbl and sigBuy
    string _tipB = "CONFLUENCE BUY ▲\n──────────────\nInside MC band  S1 " + str.tostring(mc_bandS1, format.mintick) + " — R1 " + str.tostring(mc_bandR1, format.mintick) + "\nLevel: near AMT/RQ value\nMTF OS: M5 " + str.tostring(sig_rsi_cur,"#.0") + " · H1 " + str.tostring(sig_rsi_h1,"#.0") + " · H4 " + str.tostring(sig_rsi_h4,"#.0") + "\nOrder-flow: " + (_rej_bot?"Rej ":"") + (_is_ice?"Ice ":"") + (_sig_dlt>0?"Δ+ ":"") + "(" + str.tostring(_of_buy) + "/3)\nKalman " + (smooths>smooths[1]?"↑":"↓") + "  Entropy " + _ent_str
    label.new(bar_index, low, "▲ BUY", yloc=yloc.belowbar, style=label.style_label_up, color=color.new(i_sig_buy_col, 0), textcolor=color.white, size=_sig_sz, tooltip=_tipB)
if i_sig_en and i_sig_show_lbl and sigSell
    string _tipS = "CONFLUENCE SELL ▼\n──────────────\nInside MC band  S1 " + str.tostring(mc_bandS1, format.mintick) + " — R1 " + str.tostring(mc_bandR1, format.mintick) + "\nLevel: near AMT/RQ value\nMTF OB: M5 " + str.tostring(sig_rsi_cur,"#.0") + " · H1 " + str.tostring(sig_rsi_h1,"#.0") + " · H4 " + str.tostring(sig_rsi_h4,"#.0") + "\nOrder-flow: " + (_rej_top?"Rej ":"") + (_is_ice?"Ice ":"") + (_sig_dlt<0?"Δ- ":"") + "(" + str.tostring(_of_sell) + "/3)\nKalman " + (smooths<smooths[1]?"↓":"↑") + "  Entropy " + _ent_str
    label.new(bar_index, high, "▼ SELL", yloc=yloc.abovebar, style=label.style_label_down, color=color.new(i_sig_sell_col, 0), textcolor=color.white, size=_sig_sz, tooltip=_tipS)

// ── ARROWS / plotshape (toggle: i_sig_show_shp) ──
plotshape(i_sig_en and i_sig_show_shp and sigBuy,  title="Confluence BUY ▲",  style=shape.triangleup,   location=location.belowbar, color=i_sig_buy_col,  size=size.small, text="BUY",  textcolor=color.new(i_sig_buy_col,0))
plotshape(i_sig_en and i_sig_show_shp and sigSell, title="Confluence SELL ▼", style=shape.triangledown, location=location.abovebar, color=i_sig_sell_col, size=size.small, text="SELL", textcolor=color.new(i_sig_sell_col,0))

alertcondition(sigBuy,  title="[LiqHM v16] CONFLUENCE BUY",  message="LiqHM v16 — Confluence BUY: price inside MC band + value level + MTF OS (M5+H1/H4) + order-flow + Kalman/Entropy.")
alertcondition(sigSell, title="[LiqHM v16] CONFLUENCE SELL", message="LiqHM v16 — Confluence SELL: price inside MC band + value level + MTF OB (M5+H1/H4) + order-flow + Kalman/Entropy.")

// ═══════════════════════════════════════════════════════════════════════════
// §20  ⑯ TRIPLE MTF CONF SIGNAL  (v11)
//      Condition: ALL 3 TFs show WMA-RSI OS (BUY) or OB (SELL)
//                + AMT Rejection on the same bar
//      OS: fast < slow  AND  fast < i_tri_os(40)  AND  slow < 50   ← v11 fix
//      OB: fast > slow  AND  fast > i_tri_ob(60)  AND  slow > 50   ← v11 fix
//      v10 bug: "fast<slow AND slow<50" fired with fast=41–49 (mildly bearish)
//      which MLMI kNN does NOT classify as OS (requires genuine lower range).
//      v11 adds i_tri_os/i_tri_ob absolute gate so fast must be genuinely
//      oversold/overbought at ALL 3 TFs — matches MLMI OS/OB zone threshold.
//      Labels and Arrows toggle independently. Dedup gap = i_tri_gap bars.
// ═══════════════════════════════════════════════════════════════════════════

// OS: fast < slow  AND  fast < i_tri_os  AND  slow < 50
// Requires fast WMA RSI to be genuinely in OS territory (< 40 default),
// not just "barely below slow" (gap=3, fast=41). Filters false positives.
bool _tri_os_m5 = i_tri_en and not na(_tri_rq_m5) and _tri_rq_m5 < _tri_rs_m5 and _tri_rq_m5 < i_tri_os and _tri_rs_m5 < 50.0
bool _tri_os_h1 = i_tri_en and not na(_tri_rq_h1) and _tri_rq_h1 < _tri_rs_h1 and _tri_rq_h1 < i_tri_os and _tri_rs_h1 < 50.0
bool _tri_os_h4 = i_tri_en and not na(_tri_rq_h4) and _tri_rq_h4 < _tri_rs_h4 and _tri_rq_h4 < i_tri_os and _tri_rs_h4 < 50.0

// OB: fast > slow  AND  fast > i_tri_ob  AND  slow > 50
bool _tri_ob_m5 = i_tri_en and not na(_tri_rq_m5) and _tri_rq_m5 > _tri_rs_m5 and _tri_rq_m5 > i_tri_ob and _tri_rs_m5 > 50.0
bool _tri_ob_h1 = i_tri_en and not na(_tri_rq_h1) and _tri_rq_h1 > _tri_rs_h1 and _tri_rq_h1 > i_tri_ob and _tri_rs_h1 > 50.0
bool _tri_ob_h4 = i_tri_en and not na(_tri_rq_h4) and _tri_rq_h4 > _tri_rs_h4 and _tri_rq_h4 > i_tri_ob and _tri_rs_h4 > 50.0

bool _tri_os_triple = _tri_os_m5 and _tri_os_h1 and _tri_os_h4
bool _tri_ob_triple = _tri_ob_m5 and _tri_ob_h1 and _tri_ob_h4

bool _tri_raw_buy  = _tri_os_triple and _rej_bot and (not _hv_gate_tri_on or _hv_buy_ok)
bool _tri_raw_sell = _tri_ob_triple and _rej_top and (not _hv_gate_tri_on or _hv_sell_ok)

var int _tri_last_bar = -100000
bool _tri_buy  = _tri_raw_buy  and (bar_index - _tri_last_bar) >= i_tri_gap
bool _tri_sell = _tri_raw_sell and (bar_index - _tri_last_bar) >= i_tri_gap
if _tri_buy or _tri_sell
    _tri_last_bar := bar_index

// ── LABELS (toggle: i_tri_show_lbl) ──
if i_tri_en and i_tri_show_lbl and _tri_buy
    string _tip_buy = "★ TRIPLE CONF BUY\n─────────────────────\n" +
         "Triple WMA-RSI OS (fast<slow AND fast<" + str.tostring(i_tri_os, "#") + " AND slow<50):\n" +
         "  " + _tri_tf0n + ": fast=" + str.tostring(_tri_rq_m5, "#.1") + "  slow=" + str.tostring(_tri_rs_m5, "#.1") + "\n" +
         "  " + _tri_tf1n + ": fast=" + str.tostring(_tri_rq_h1, "#.1") + "  slow=" + str.tostring(_tri_rs_h1, "#.1") + "\n" +
         "  " + _tri_tf2n + ": fast=" + str.tostring(_tri_rq_h4, "#.1") + "  slow=" + str.tostring(_tri_rs_h4, "#.1") + "\n" +
         "Rejection ↑: wick below VAL (" + str.tostring(_m_val, format.mintick) + "), close above"
    label.new(bar_index, low, "★ CONF BUY",
         yloc=yloc.belowbar, style=label.style_label_up,
         color=color.new(i_tri_buy_col, 0), textcolor=color.white,
         size=size.small, tooltip=_tip_buy)

if i_tri_en and i_tri_show_lbl and _tri_sell
    string _tip_sell = "★ TRIPLE CONF SELL\n─────────────────────\n" +
         "Triple WMA-RSI OB (fast>slow AND fast>" + str.tostring(i_tri_ob, "#") + " AND slow>50):\n" +
         "  " + _tri_tf0n + ": fast=" + str.tostring(_tri_rq_m5, "#.1") + "  slow=" + str.tostring(_tri_rs_m5, "#.1") + "\n" +
         "  " + _tri_tf1n + ": fast=" + str.tostring(_tri_rq_h1, "#.1") + "  slow=" + str.tostring(_tri_rs_h1, "#.1") + "\n" +
         "  " + _tri_tf2n + ": fast=" + str.tostring(_tri_rq_h4, "#.1") + "  slow=" + str.tostring(_tri_rs_h4, "#.1") + "\n" +
         "Rejection ↓: wick above VAH (" + str.tostring(_m_vah, format.mintick) + "), close below"
    label.new(bar_index, high, "★ CONF SELL",
         yloc=yloc.abovebar, style=label.style_label_down,
         color=color.new(i_tri_sell_col, 0), textcolor=color.white,
         size=size.small, tooltip=_tip_sell)

// ── ARROWS / plotshape (toggle: i_tri_show_shp) ──
plotshape(i_tri_en and i_tri_show_shp and _tri_buy,  title="★ Triple CONF BUY",
     style=shape.triangleup,   location=location.belowbar,
     color=i_tri_buy_col,  size=size.tiny)
plotshape(i_tri_en and i_tri_show_shp and _tri_sell, title="★ Triple CONF SELL",
     style=shape.triangledown, location=location.abovebar,
     color=i_tri_sell_col, size=size.tiny)

// ── ALERTS ──
alertcondition(_tri_buy,  title="[LiqHM v16] ★ TRIPLE CONF BUY",
     message="LiqHM v16 ★ Triple CONF BUY: WMA-RSI OS on all 3 TFs (fast<slow AND fast<40 AND slow<50) + AMT rejection ↑ at value band.")
alertcondition(_tri_sell, title="[LiqHM v16] ★ TRIPLE CONF SELL",
     message="LiqHM v16 ★ Triple CONF SELL: WMA-RSI OB on all 3 TFs (fast>slow AND fast>60 AND slow>50) + AMT rejection ↓ at value band.")

// ═══════════════════════════════════════════════════════════════════════════
// §21  ⑰ ML YZ — PLOTS + SIGNALS + ALERTS
// ═══════════════════════════════════════════════════════════════════════════

// ── Outer bands ────────────────────────────────────────────────────────────
p_yz_up = plot(i_yz_en ? yz_upper : na, "YZ Outer Upper",
     color=color.new(yz_c_st, 20), linewidth=1, force_overlay=true)
p_yz_dn = plot(i_yz_en ? yz_lower : na, "YZ Outer Lower",
     color=color.new(yz_c_st, 20), linewidth=1, force_overlay=true)
plot(i_yz_en ? yz_mid : na, "YZ Middle",
     color=yz_c_st, linewidth=2, force_overlay=true)
fill(p_yz_up, p_yz_dn, color=color.new(yz_c_reg, 90))

// ── Value Area bands ───────────────────────────────────────────────────────
p_yz_va_u = plot(i_yz_en and i_yz_show_va ? yz_va_upper : na, "YZ VA Upper",
     color=color.new(C_GOLD, 40), linewidth=1, style=plot.style_stepline, force_overlay=true)
p_yz_va_d = plot(i_yz_en and i_yz_show_va ? yz_va_lower : na, "YZ VA Lower",
     color=color.new(C_GOLD, 40), linewidth=1, style=plot.style_stepline, force_overlay=true)
fill(p_yz_va_u, p_yz_va_d, color=color.new(C_GOLD, 93))

// ── POC (VWAP proxy) ───────────────────────────────────────────────────────
plot(i_yz_en and i_yz_show_poc ? yz_poc : na, "YZ POC",
     color=YZ_BLUE, linewidth=1, style=plot.style_circles, force_overlay=true)

// ── AMT Initiative signals (outer-band break, Imbalance regime) ────────────
plotshape(i_yz_en and i_yz_show_sig and yz_init_buy,
     "YZ Initiative BUY",  shape.triangleup,   location.belowbar,
     YZ_BULL, text="IB", textcolor=YZ_BULL, size=size.small)
plotshape(i_yz_en and i_yz_show_sig and yz_init_sell,
     "YZ Initiative SELL", shape.triangledown, location.abovebar,
     YZ_BEAR, text="IS", textcolor=YZ_BEAR, size=size.small)

// ── AMT Responsive signals (VA re-entry, Balance regime) ──────────────────
plotshape(i_yz_en and i_yz_show_sig and yz_resp_buy,
     "YZ Responsive BUY",  shape.circle, location.belowbar,
     color.new(YZ_BULL, 40), text="RB", textcolor=YZ_BULL, size=size.tiny)
plotshape(i_yz_en and i_yz_show_sig and yz_resp_sell,
     "YZ Responsive SELL", shape.circle, location.abovebar,
     color.new(YZ_BEAR, 40), text="RS", textcolor=YZ_BEAR, size=size.tiny)

// ── Optional bar color override ────────────────────────────────────────────
barcolor(i_yz_en and i_yz_col_bars ? yz_c_st : na)

// ── Regime label (last bar only) ──────────────────────────────────────────
// v16 FIX (object lifecycle): keep ONE persistent label handle and delete it
// before each redraw. The v12 code called a bare label.new() under
// barstate.islast, so every bar that closed committed its own regime label
// permanently while the new last bar drew another → the labels stacked up
// ("IMBALANCE/BALANCE/NEUTRAL · YZ=x.xx" piling on top of each other).
// var label + label.delete() mirrors the §14b market-quality label lifecycle,
// so exactly one ML YZ regime label ever exists on the chart.
var label _yz_lbl = na
if i_yz_en and i_yz_show_lbl and barstate.islast
    label.delete(_yz_lbl)
    string _yr = yz_regime ==  1 ? "⚖ BALANCE"
               : yz_regime == -1 ? "⚡ IMBALANCE"
               : "◈ NEUTRAL"
    string _ys = yz_state ==  1 ? " · BULL" : yz_state == -1 ? " · BEAR" : ""
    string _yv = " · YZ=" + str.tostring(math.round(nz(yz_vol) * close * 100.0) / 100.0, "#.##")
    _yz_lbl := label.new(bar_index, yz_upper,
         _yr + _ys + _yv,
         color     = color.new(yz_c_reg, 20),
         textcolor = color.white,
         style     = label.style_label_lower_left,
         size      = size.small)

// ── ML YZ Alerts ──────────────────────────────────────────────────────────
alertcondition(yz_init_buy,  title="[LiqHM v16] ML YZ Initiative BUY",
     message="LiqHM v16 ML YZ Initiative BUY  | {{ticker}} | {{close}}")
alertcondition(yz_init_sell, title="[LiqHM v16] ML YZ Initiative SELL",
     message="LiqHM v16 ML YZ Initiative SELL | {{ticker}} | {{close}}")
alertcondition(yz_resp_buy,  title="[LiqHM v16] ML YZ Responsive BUY",
     message="LiqHM v16 ML YZ Responsive BUY  | {{ticker}} | {{close}}")
alertcondition(yz_resp_sell, title="[LiqHM v16] ML YZ Responsive SELL",
     message="LiqHM v16 ML YZ Responsive SELL | {{ticker}} | {{close}}")

// ═══════════════════════════════════════════════════════════════════════════
// §22  ⑱ HV-PIVOT LEVELS + CLV-DELTA  (display-only, additive — v17)
//   Volume-gated pivot S/R (BigBeluga concept ported as a FILTER, not the box
//   system). CVD = CLV×vol, NOT the candle-direction proxy. One ray + one CVD
//   label per level; capped last N per side. NO boxes, NO polylines. Purely a
//   pair of eyes for LOCATION — does not gate ⑮/⑯/⑰.
// ═══════════════════════════════════════════════════════════════════════════
// ── Precompute (must run every bar: ta.* keep state) ──────────────────────
float _hv_vsma  = ta.sma(volume, i_hv_vol_ma)
bool  _hv_hivol = _hv_vsma > 0.0 and volume > _hv_vsma * i_hv_vol_mult
float _hv_ph    = ta.pivothigh(high, i_hv_res_len, i_hv_res_len)
float _hv_pl    = ta.pivotlow (low,  i_hv_sup_len, i_hv_sup_len)

// CLV×vol delta (the fix): ((C−L)−(H−C))/(H−L) × volume. Guard H==L bars.
float _hv_rng   = high - low
float _hv_clv   = _hv_rng > 0.0 ? ((close - low) - (high - close)) / _hv_rng : 0.0
float _hv_delta = _hv_clv * volume

// Formation-window CVD — hoisted to global scope so math.sum runs EVERY bar
// (stateful rolling sum; calling it only inside an if-branch = consistency error).
float _hv_cvd_res = math.sum(_hv_delta, i_hv_res_len + 1)
float _hv_cvd_sup = math.sum(_hv_delta, i_hv_sup_len + 1)

// Valid pivot = pivot confirmed AND the pivot bar (len bars back) was high-vol
bool  _hv_valid_res = i_hv_en and not na(_hv_ph) and _hv_hivol[i_hv_res_len]
bool  _hv_valid_sup = i_hv_en and not na(_hv_pl) and _hv_hivol[i_hv_sup_len]

// ── Persistent level stores (capped per side) ──────────────────────────────
// _hv_res / _hv_sup are declared in §18b (so ⑮/⑯ can read them); maintained here.
var array<label>   _hv_labels = array.new<label>()

// ── Create ray + snapshot CVD on confirmation; evict oldest beyond N ────────
if _hv_valid_res
    float _p   = high[i_hv_res_len]
    line  _ln  = line.new(bar_index - i_hv_res_len, _p, bar_index, _p,
         color=color.new(i_hv_res_col, 0), width=1, extend=extend.right)
    _hv_res.push(hvLevel.new(_ln, _p, _hv_cvd_res))
    if _hv_res.size() > i_hv_keep
        hvLevel _old = _hv_res.shift()
        line.delete(_old.ln)

if _hv_valid_sup
    float _p   = low[i_hv_sup_len]
    line  _ln  = line.new(bar_index - i_hv_sup_len, _p, bar_index, _p,
         color=color.new(i_hv_sup_col, 0), width=1, extend=extend.right)
    _hv_sup.push(hvLevel.new(_ln, _p, _hv_cvd_sup))
    if _hv_sup.size() > i_hv_keep
        hvLevel _old = _hv_sup.shift()
        line.delete(_old.ln)

// ── Refresh CVD labels at the right edge (last bar only — cheap, house style) ─
if i_hv_en and barstate.islast
    for _lb in _hv_labels
        label.delete(_lb)
    _hv_labels.clear()
    if i_hv_show_cvd
        // Anchor a few bars into the empty right void; R box sits ABOVE the line
        // (style_label_down = box above anchor), S box BELOW (style_label_up) so
        // the ray never runs through the text.
        int _hv_lx = bar_index + 3
        for _lv in _hv_res
            _hv_labels.push(label.new(_hv_lx, _lv.price,
                 "R " + str.tostring(_lv.price, format.mintick) + "  CVD " + formatDelta(_lv.cvd),
                 style=label.style_label_down, color=color.new(i_hv_res_col, 80),
                 textcolor=i_hv_res_col, size=size.tiny))
        for _lv in _hv_sup
            _hv_labels.push(label.new(_hv_lx, _lv.price,
                 "S " + str.tostring(_lv.price, format.mintick) + "  CVD " + formatDelta(_lv.cvd),
                 style=label.style_label_up, color=color.new(i_hv_sup_col, 80),
                 textcolor=i_hv_sup_col, size=size.tiny))

// ============================================================================
//  ⑲ VWAP STDEV BANDS  (session-anchored · tuned for M5)  ──  DISPLAY-ONLY
//  Ported from "VWAP Stdev Bands v2 Mod" (Pine v2 → v6). Session VWAP + 3 stdev
//  band groups + inner over/under fills. (Outer band groups 4-5 and the VWAP-zone
//  bar-color feature were dropped to stay under Pine's shared 64-plot-per-script
//  cap — RE10140.) Anchor resets each new period (default Daily → true intraday
//  VWAP on M5). NOTHING here feeds any signal / gate / score — pure reference.
// ============================================================================
g_vwb = "⑲ VWAP Stdev Bands (M5)"
i_vwb_on   = input.bool(true,  "Enable VWAP Stdev Bands", group=g_vwb)
i_vwb_anch = input.timeframe("D", "Anchor / reset period", group=g_vwb, tooltip="Session anchor. 'D' = daily reset = standard intraday VWAP (correct for M5). 'W' weekly, 'M' monthly.")
i_vwb_src  = input.source(hl2,  "VWAP price source", group=g_vwb)

i_vwb_u1 = input.float(1.28, "Stdev above (1)", group=g_vwb, inline="v1")
i_vwb_d1 = input.float(1.28, "below (1)",       group=g_vwb, inline="v1")
i_vwb_u2 = input.float(2.01, "Stdev above (2)", group=g_vwb, inline="v2")
i_vwb_d2 = input.float(2.01, "below (2)",       group=g_vwb, inline="v2")
i_vwb_u3 = input.float(2.51, "Stdev above (3)", group=g_vwb, inline="v3")
i_vwb_d3 = input.float(2.51, "below (3)",       group=g_vwb, inline="v3")

i_vwb_g2 = input.bool(true, "Show band group 2", group=g_vwb)
i_vwb_g3 = input.bool(true, "Show band group 3", group=g_vwb)

// ── session reset + cumulative sums (var + nz self-reference = v6 idiom) ──────
int   vwb_anchT = request.security(syminfo.tickerid, i_vwb_anch, time, lookahead=barmerge.lookahead_off)
int   vwb_anchC = ta.change(vwb_anchT)   // computed unconditionally (avoids ta.* inside conditional warning)
bool  vwb_new   = i_vwb_on and vwb_anchC != 0
float vwb_sr    = i_vwb_src
var float vwb_pv  = na
var float vwb_vv  = na
var float vwb_pv2 = na
vwb_pv  := vwb_new ? vwb_sr * volume          : nz(vwb_pv[1])  + vwb_sr * volume
vwb_vv  := vwb_new ? volume                   : nz(vwb_vv[1])  + volume
vwb_pv2 := vwb_new ? vwb_sr * vwb_sr * volume : nz(vwb_pv2[1]) + vwb_sr * vwb_sr * volume
float vwb_vwap = vwb_vv != 0 ? vwb_pv / vwb_vv : na
float vwb_dev  = vwb_vv != 0 ? math.sqrt(math.max(vwb_pv2 / vwb_vv - vwb_vwap * vwb_vwap, 0)) : na

// ── band levels (3 groups) ───────────────────────────────────────────────────
float vwb_U1 = vwb_vwap + i_vwb_u1 * vwb_dev
float vwb_D1 = vwb_vwap - i_vwb_d1 * vwb_dev
float vwb_U2 = vwb_vwap + i_vwb_u2 * vwb_dev
float vwb_D2 = vwb_vwap - i_vwb_d2 * vwb_dev
float vwb_U3 = vwb_vwap + i_vwb_u3 * vwb_dev
float vwb_D3 = vwb_vwap - i_vwb_d3 * vwb_dev

// ── plots — VWAP + 3 band groups (7 plots; groups 2-3 by their toggles) ──────
vwb_pA  = plot(i_vwb_on              ? vwb_vwap : na, "VWAP",           color=color.black, style=plot.style_circles)
vwb_pU1 = plot(i_vwb_on              ? vwb_U1   : na, "VWAP Upper (1)", color=color.gray,  style=plot.style_circles)
vwb_pD1 = plot(i_vwb_on              ? vwb_D1   : na, "VWAP Lower (1)", color=color.gray,  style=plot.style_circles)
vwb_pU2 = plot(i_vwb_on and i_vwb_g2 ? vwb_U2   : na, "VWAP Upper (2)", color=color.red)
vwb_pD2 = plot(i_vwb_on and i_vwb_g2 ? vwb_D2   : na, "VWAP Lower (2)", color=color.green)
vwb_pU3 = plot(i_vwb_on and i_vwb_g3 ? vwb_U3   : na, "VWAP Upper (3)", color=color.red)
vwb_pD3 = plot(i_vwb_on and i_vwb_g3 ? vwb_D3   : na, "VWAP Lower (3)", color=color.green)

// ── inner over/under fills (4; each also counts toward the 64-plot cap) ───────
fill(vwb_pU1, vwb_pU2, color=color.new(color.red,   90), title="OB Fill 1")
fill(vwb_pD1, vwb_pD2, color=color.new(color.green, 90), title="OS Fill 1")
fill(vwb_pU2, vwb_pU3, color=color.new(color.red,   90), title="OB Fill 2")
fill(vwb_pD2, vwb_pD3, color=color.new(color.green, 90), title="OS Fill 2")

// ============================================================================
//  ⑳ TREND BREAKOUT + CORRECTION METER  (M5)  ──  DISPLAY-ONLY   [v20]
//  Two complementary tools from the "3 primitives" framework:
//   (A) Donchian N-bar high/low = the TREND-FOLLOW trigger (break of the
//       highest-high / lowest-low over N bars; longer N = stronger edge).
//   (B) Correction Meter = the MR-INTO-TREND read: after a breakout it
//       measures how deep the pullback into the trend is (Fib retrace of the
//       impulse). Deeper pullback = higher-probability continuation entry.
//  Uses ONLY line / label / box / table  → 0 plot-budget (safe vs the shared
//  64-plot cap / RE10140). NOTHING feeds any signal / gate / score.
// ============================================================================
g_tbk = "⑳ Trend Breakout + Correction Meter (M5)"
i_tbk_on    = input.bool(true,  "Enable Trend Breakout module", group=g_tbk)
i_tbk_lines = input.bool(true,  "  Show Donchian breakout lines", group=g_tbk)
i_tbk_len1  = input.int(100,    "  Primary length (bars)", group=g_tbk, minval=10, tooltip="M5 default 100 ≈ 1 session of structure. Longer = stronger trend edge (transcript: 100/200/ATH).")
i_tbk_l2on  = input.bool(true,  "  Show secondary length", group=g_tbk, inline="tbk2")
i_tbk_len2  = input.int(200,    "", group=g_tbk, inline="tbk2", minval=10)
i_tbk_mark  = input.bool(true,  "  Show BO up/down breakout markers", group=g_tbk)
i_tbk_upc   = input.color(color.new(#D85A30,0), "  DCH (resistance) color", group=g_tbk)
i_tbk_dnc   = input.color(color.new(#1D9E75,0), "  DCL (support) color", group=g_tbk)
i_tbk_lsty  = input.string("Dashed", "  Line style", group=g_tbk, options=["Solid","Dashed","Dotted"])
i_tbk_ext   = input.int(6, "  Line extend (bars right)", group=g_tbk, minval=0, maxval=100)
i_tbk_lblsz = input.string("Tiny", "  Line label size", group=g_tbk, options=["Tiny","Small","Normal"])
i_tbk_meter = input.bool(true, "  Show Correction Meter", group=g_tbk)
i_tbk_mpos  = input.string("Top Left", "  Meter position", group=g_tbk, options=["Top Left","Top Right","Bottom Left","Bottom Right"])
i_tbk_msz   = input.string("Small", "  Meter text size", group=g_tbk, options=["Tiny","Small","Normal"])
i_tbk_zone  = input.bool(true, "  Show dip-buy zone (Fib)", group=g_tbk)
i_tbk_znc   = input.color(color.new(#1D9E75,86), "  Dip-buy zone color", group=g_tbk)
i_tbk_flo   = input.float(0.5,   "  Zone Fib low",  group=g_tbk, inline="tbkfib", step=0.01, minval=0.1, maxval=0.9)
i_tbk_fhi   = input.float(0.618, "  Zone Fib high", group=g_tbk, inline="tbkfib", step=0.01, minval=0.2, maxval=1.0)

color TBK_BULL = #97C459
color TBK_BEAR = #EF5350

// ── Donchian levels + breakout events ────────────────────────────────────────
float tbk_dch1 = ta.highest(high, i_tbk_len1)
float tbk_dcl1 = ta.lowest(low,  i_tbk_len1)
float tbk_dch2 = ta.highest(high, i_tbk_len2)
float tbk_dcl2 = ta.lowest(low,  i_tbk_len2)
bool  tbk_boU  = i_tbk_on and close > tbk_dch1[1] and close[1] <= tbk_dch1[1]
bool  tbk_boD  = i_tbk_on and close < tbk_dcl1[1] and close[1] >= tbk_dcl1[1]

// ── trend + impulse/pullback state (for the Correction Meter) ────────────────
var int   tbk_dir = 0
var float tbk_org = na
var float tbk_ext = na
var int   tbk_bar = na
if tbk_boU
    tbk_dir := 1
    tbk_org := tbk_dch1[1]
    tbk_ext := high
    tbk_bar := bar_index
else if tbk_boD
    tbk_dir := -1
    tbk_org := tbk_dcl1[1]
    tbk_ext := low
    tbk_bar := bar_index
else if tbk_dir == 1
    tbk_ext := math.max(nz(tbk_ext, high), high)
else if tbk_dir == -1
    tbk_ext := math.min(nz(tbk_ext, low), low)

float tbk_imp   = tbk_dir == 1 ? tbk_ext - tbk_org : tbk_dir == -1 ? tbk_org - tbk_ext : na
float tbk_pb    = tbk_dir == 1 ? tbk_ext - low     : tbk_dir == -1 ? high - tbk_ext    : na
float tbk_depth = not na(tbk_imp) and tbk_imp > 0 ? tbk_pb / tbk_imp : na
float tbk_zt    = tbk_dir == 1 ? tbk_ext - i_tbk_flo * tbk_imp : tbk_dir == -1 ? tbk_ext + i_tbk_fhi * tbk_imp : na
float tbk_zb    = tbk_dir == 1 ? tbk_ext - i_tbk_fhi * tbk_imp : tbk_dir == -1 ? tbk_ext + i_tbk_flo * tbk_imp : na

// ── breakout markers (label = 0 plot-budget) ─────────────────────────────────
var array<label> tbk_mk = array.new_label()
if i_tbk_on and i_tbk_mark and tbk_boU
    array.push(tbk_mk, label.new(bar_index, low, "BO▲" + str.tostring(i_tbk_len1), style=label.style_label_up, color=color.new(TBK_BULL, 15), textcolor=color.white, size=size.small, tooltip="Donchian " + str.tostring(i_tbk_len1) + "-bar HIGH broken → trend-long trigger"))
    if array.size(tbk_mk) > 120
        label.delete(array.shift(tbk_mk))
if i_tbk_on and i_tbk_mark and tbk_boD
    array.push(tbk_mk, label.new(bar_index, high, "BO▼" + str.tostring(i_tbk_len1), style=label.style_label_down, color=color.new(TBK_BEAR, 15), textcolor=color.white, size=size.small, tooltip="Donchian " + str.tostring(i_tbk_len1) + "-bar LOW broken → trend-short trigger"))
    if array.size(tbk_mk) > 120
        label.delete(array.shift(tbk_mk))

// ── persistent drawables (redrawn on last bar only = clean, cheap) ───────────
tbk_lsty_r  = switch i_tbk_lsty
    "Solid"  => line.style_solid
    "Dotted" => line.style_dotted
    =>          line.style_dashed
tbk_lblsz_r = switch i_tbk_lblsz
    "Small"  => size.small
    "Normal" => size.normal
    =>          size.tiny
tbk_msz_r   = switch i_tbk_msz
    "Tiny"   => size.tiny
    "Normal" => size.normal
    =>          size.small
tbk_mpos_r  = switch i_tbk_mpos
    "Top Right"    => position.top_right
    "Bottom Left"  => position.bottom_left
    "Bottom Right" => position.bottom_right
    =>                position.top_left

var line  tbk_l_dch1 = na
var line  tbk_l_dcl1 = na
var line  tbk_l_dch2 = na
var line  tbk_l_dcl2 = na
var label tbk_lb_dch = na
var label tbk_lb_dcl = na
var box   tbk_zbx    = na
var table tbk_tbl    = table.new(tbk_mpos_r, 2, 6, border_width=1, frame_color=color.new(#2c3440, 0), frame_width=1)

if barstate.islast
    line.delete(tbk_l_dch1)
    line.delete(tbk_l_dcl1)
    line.delete(tbk_l_dch2)
    line.delete(tbk_l_dcl2)
    label.delete(tbk_lb_dch)
    label.delete(tbk_lb_dcl)
    box.delete(tbk_zbx)
    table.clear(tbk_tbl, 0, 0, 1, 5)
    if i_tbk_on
        int _lx = bar_index - math.min(i_tbk_len1, 300)
        int _rx = bar_index + i_tbk_ext
        if i_tbk_lines
            tbk_l_dch1 := line.new(_lx, tbk_dch1, _rx, tbk_dch1, color=i_tbk_upc, style=tbk_lsty_r, width=1)
            tbk_l_dcl1 := line.new(_lx, tbk_dcl1, _rx, tbk_dcl1, color=i_tbk_dnc, style=tbk_lsty_r, width=1)
            tbk_lb_dch := label.new(_rx, tbk_dch1, "DCH " + str.tostring(i_tbk_len1) + "  " + str.tostring(tbk_dch1, format.mintick) + "  BO→long", style=label.style_label_left, color=color.new(i_tbk_upc, 82), textcolor=i_tbk_upc, size=tbk_lblsz_r)
            tbk_lb_dcl := label.new(_rx, tbk_dcl1, "DCL " + str.tostring(i_tbk_len1) + "  " + str.tostring(tbk_dcl1, format.mintick) + "  BO→short", style=label.style_label_left, color=color.new(i_tbk_dnc, 82), textcolor=i_tbk_dnc, size=tbk_lblsz_r)
            if i_tbk_l2on
                tbk_l_dch2 := line.new(_lx, tbk_dch2, _rx, tbk_dch2, color=color.new(i_tbk_upc, 55), style=tbk_lsty_r, width=1)
                tbk_l_dcl2 := line.new(_lx, tbk_dcl2, _rx, tbk_dcl2, color=color.new(i_tbk_dnc, 55), style=tbk_lsty_r, width=1)
        if i_tbk_zone and tbk_dir != 0 and not na(tbk_zt) and not na(tbk_zb) and nz(tbk_imp) > 0
            int _zl = math.max(nz(tbk_bar, bar_index - 30), bar_index - 100)
            tbk_zbx := box.new(_zl, tbk_zt, _rx, tbk_zb, border_color=color.new(i_tbk_znc, 40), border_style=line.style_dotted, border_width=1, bgcolor=i_tbk_znc, text="dip-buy zone", text_color=color.new(i_tbk_dnc, 0), text_size=size.tiny, text_halign=text.align_left, text_valign=text.align_bottom)
        if i_tbk_meter
            color _bg  = color.new(#161B22, 0)
            color _lbc = #6b7686
            table.cell(tbk_tbl, 0, 0, "correction meter", text_color=#8b98a8, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
            table.cell(tbk_tbl, 1, 0, "", bgcolor=_bg)
            if tbk_dir == 0
                table.cell(tbk_tbl, 0, 1, "trend",  text_color=_lbc, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 1, 1, "waiting for breakout", text_color=#c9d1d9, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
            else
                string _tdir = tbk_dir == 1 ? "UP" : "DN"
                color  _tcol = tbk_dir == 1 ? TBK_BULL : TBK_BEAR
                int    _age  = bar_index - nz(tbk_bar, bar_index)
                float  _d    = nz(tbk_depth, 0.0)
                int    _ns   = _d < 0.382 ? 1 : _d < 0.5 ? 2 : _d < 0.618 ? 3 : _d < 0.786 ? 4 : 5
                string _star = _d > 1.0 ? "⚠ broke origin" : _ns == 1 ? "★☆☆☆☆" : _ns == 2 ? "★★☆☆☆" : _ns == 3 ? "★★★☆☆" : _ns == 4 ? "★★★★☆" : "★★★★★"
                string _fib  = _d < 0.382 ? "shallow" : _d < 0.5 ? "Fib .382" : _d < 0.618 ? "Fib .5" : _d < 0.786 ? "Fib .618" : _d <= 1.0 ? "Fib .786" : "≥1.0"
                table.cell(tbk_tbl, 0, 1, "trend",    text_color=_lbc, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 1, 1, _tdir + " · DCH" + str.tostring(i_tbk_len1) + " broke " + str.tostring(_age) + " bars ago", text_color=_tcol, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 0, 2, "impulse",  text_color=_lbc, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 1, 2, str.tostring(tbk_org, format.mintick) + " → " + str.tostring(tbk_ext, format.mintick) + "  (+" + str.tostring(nz(tbk_imp), format.mintick) + ")", text_color=#c9d1d9, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 0, 3, "pullback", text_color=_lbc, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 1, 3, str.tostring(tbk_ext, format.mintick) + " → " + str.tostring(tbk_dir == 1 ? low : high, format.mintick) + "  (−" + str.tostring(nz(tbk_pb), format.mintick) + ")", text_color=#c9d1d9, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 0, 4, "depth",    text_color=_lbc, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 1, 4, str.tostring(_d * 100.0, "#") + "%   " + _fib, text_color=#EF9F27, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 0, 5, "dip-buy",  text_color=_lbc, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)
                table.cell(tbk_tbl, 1, 5, _star + "  (deeper = higher prob)", text_color=_d > 1.0 ? TBK_BEAR : TBK_BULL, text_size=tbk_msz_r, bgcolor=_bg, text_halign=text.align_left)

// == END  LiqHM+VP + HTF Reversal + Kalman D7 + Monte Carlo + Confluence Signal + Triple MTF Conf + ML YZ + HV-Pivot + CVD-Gate + VWAP-Bands + TrendBreakout+CorrMeter  |  Pine v6  |  v20  overlay=true ==