XAULearn

Gold Session Framework v11 · ซอร์สโค้ด

Pine Script ต้นฉบับ

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

Gold_Session_Framework_v11.pine 1,826 บรรทัด 99 KB ต้นฉบับแก้ไขล่าสุด 17 กันยายน 2569 ดาวน์โหลด .pine ไฟล์ H1 (TFH1) — โค้ดเดียวกัน ค่าเริ่มต้น H1

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

สารบัญส่วนของโค้ด (31)
  1. 3 GOLD SESSION FRAMEWORK v11 (XAUUSD)
  2. 43 1. SESSION
  3. 52 2. RANGE BASIS + AVR MULTIPLIERS
  4. 73 3. DISPLAY TOGGLES (AVR)
  5. 90 3b. STYLE — AVR lines & boxes
  6. 119 3c. CHANDELIER (multi-TF) — 3 selectable-TF instances (USER-TUNED values)
  7. 170 3d. VOLUME PROFILE (single, chart-TF, KV-MACD ㉕ principle)
  8. 202 3e. VALUE-AREA REVERSION SIGNALS (VARS) — ported from LuxAlgo (Fabio strat)
  9. 221 3f. CVD DIVERGENCE — ported from TradingFinder CVD Divergence [Periodic/EMA]
  10. 237 3f-2. CVD CONFLUENCE (v9) — same ±RD on a higher TF (default M10) → ±CRD
  11. 255 3f-3. DELTA SOURCE (v10) — how buy/sell volume is decided for CVD ±RD and ±CRD
  12. 271 3f-4. DELTA PROFILE (v10) — net delta per price row since the session anchor
  13. 285 3g. ANCHORED VWAP (AVWAP) — Grateful-Futures technique (developing polylines)
  14. 305 3h. P/B/D ZONE DETECTOR — Patrick Nill range/breakout model (visual)
  15. 330 4. HELPER FUNCTIONS
  16. 508 5. DAILY RANGE (non-repaint: previous COMPLETED daily bar)
  17. 515 6. ASIA SESSION STATE
  18. 550 7. AVR LEVELS
  19. 578 8. AVR PERSISTENT OBJECTS
  20. 646 9. AVR UPDATE HELPERS
  21. 676 10. AVR RENDER
  22. 711 11. AVR ZONE CLASSIFICATION + TABLE
  23. 753 12. CHANDELIER — stateless ingredients via security, recursion at global scope
  24. 790 13. VOLUME PROFILE — OBJECT POOL (create-once, update-coords: realtime-safe)
  25. 989 13b. VALUE-AREA REVERSION (VARS) — per-bar session profile + reclaim engine
  26. 991 current-session profile state
  27. 1225 13c. CVD DIVERGENCE — CLV×vol proxy + fractal regular divergence (on price)
  28. 1443 13c-3. DELTA PROFILE (v10) — net delta per price row since the session anchor
  29. 1562 13d. ANCHORED VWAP (AVWAP) — developing polyline curves (dB from §13)
  30. 1670 13e. P/B/D ZONE DETECTOR — consolidation range → 3-touch validate → breakout
  31. 1808 14. ALERTS
//@version=6
// ============================================================================
// GOLD SESSION FRAMEWORK v11  (XAUUSD)
//   v1 AVR · v2/v3 Chandelier + VP · v4 chandelier-out-of-security · v5 buffer
//   fix · v6 tuned defaults + VARS + CVD · v7 Anchored VWAP.
//   v8 = NEW module: P/B/D ZONE DETECTOR — formalizes Patrick Nill's discretionary
//        range/breakout model. Auto-detects consolidation ranges (Efficiency-Ratio
//        box/path filter), VALIDATES by ≥3 touches, freezes the box on breakout,
//        classifies SMALL→breakout / BIG→range (height ÷ ATR), and flags ALIGNED
//        (confluent) zones. Developing zone shown live.
//   v9 = NEW: CVD CONFLUENCE with a higher timeframe (default M10). When a ±RD
//        fires on the chart TF and the SAME-direction ±RD exists on the HTF inside
//        a tolerance window, the label is upgraded to ±CRD. rev2: the two frames are
//        matched by PIVOT TIME (they confirm at different lags: M3 +6 min, M10 +20 min),
//        so the match is judged by WHERE the divergence happened, not when it arrived.
//        HTF is read from CLOSED
//        bars only (non-repainting). Every CVD parameter is shared with §3f — only
//        the timeframe differs. Toggle + tolerance + late-upgrade in the inputs.
//   v10 = (a) DELTA SOURCE for CVD ±RD and ±CRD: candle CLV (full history) or LTF tick rule
//         (lower-TF close vs previous close × volume) with CLV fallback.
//         ⚑ rev2: bid/ask tick mode REMOVED — tick (1T) requests need an Expert+ TradingView plan
//           (runtime error RE10065 on Premium). Seconds LTF (e.g. 1S) may work on Premium.
//         ⚑ rev3: default delta source = TradingView FOOTPRINT (request.footprint, Premium feature:
//           buy/sell volume classified by TradingView per trade) → falls back to 1-min tick rule
//           → candle CLV. Delta profile uses footprint rows (delta per price row) directly.
//         ⚑ rev4: only ONE request.footprint() call is allowed per script (RE10047) → footprint
//           feeds the chart TF (RD + delta profile); the confluence TF (CRD) uses the 1-min tick rule.
//         (b) DELTA PROFILE: net delta per price row since the session anchor + ±DPOC lines.
//         (c) defaults = the user's live M3 settings (read from the chart 16 Sep 2026).
//         Credit: delta classification and the delta-per-price profile idea are adapted from
//         "Footprint IQ Pro [TradingIQ]" and "CVD Profiles [TradingIQ]" (© Trading-IQ,
//         TradingView open-source). Logic re-written for this framework; please keep this credit.
//   v11 (17 Sep 2026, level-1 fixes): tick rule carries the last sign on unchanged-close minutes (their volume was
//        dropped) · ±RD EMA gate compares the PIVOT bar's close with the EMA of that bar (was the current EMA) ·
//        Chandelier sets whose TF is FASTER than the chart are disabled (request.security of a lower TF returns
//        only the last intrabar) · VARS alerts honour the master toggle · debug text uses the chart TF ·
//        defaults = user's live M3 settings (read off the chart 17 Sep 2026).
//   VISUAL-only — location + confirmation map, NO trade engine. Walk-forward first.
// ============================================================================
indicator("Gold Session Framework v11", overlay=true, max_lines_count=500, max_boxes_count=500, max_labels_count=500, max_polylines_count=100, max_bars_back=2000)

// ===========================================================================
// 1. SESSION
// ===========================================================================
gS = "Session"
asia_session = input.session("1400-2300", "Asia Session (Exchange Time)", group=gS)
i_tzsel = input.string("Exchange", "Session Timezone", options=["Exchange","America/New_York","UTC","Asia/Tokyo","Asia/Bangkok","Asia/Shanghai"], group=gS, tooltip="Clip anchors ~18:00 ET. 'Exchange' = the chart symbol's own timezone.")
i_minbars = input.int(3, "Min Asia bars to validate", minval=1, group=gS)
tz = i_tzsel == "Exchange" ? syminfo.timezone : i_tzsel

// ===========================================================================
// 2. RANGE BASIS + AVR MULTIPLIERS
// ===========================================================================
gA = "Range Basis"
atr_length = input.int(20, "Daily ATR Length", minval=1, group=gA)
use_adr    = input.bool(false, "Use ADR (High-Low) instead of ATR", group=gA)

gU = "Upward Multipliers (x range)"
up_lvl1 = input.float(0.326, "Level 1 Up",  step=0.001, group=gU)
up_avrm = input.float(0.600, "AVR- Up",     step=0.001, group=gU)
up_avr  = input.float(0.850, "AVR Up",      step=0.001, group=gU)
up_avrp = input.float(1.100, "AVR+ Up",     step=0.001, group=gU)
up_max  = input.float(1.350, "Max Up",      step=0.001, group=gU)

gD = "Downward Multipliers (x range)"
dn_lvl1 = input.float(0.326, "Level 1 Down", step=0.001, group=gD)
dn_avrm = input.float(0.600, "AVR- Down",    step=0.001, group=gD)
dn_avr  = input.float(0.850, "AVR Down",     step=0.001, group=gD)
dn_avrp = input.float(1.100, "AVR+ Down",    step=0.001, group=gD)
dn_max  = input.float(1.350, "Max Down",     step=0.001, group=gD)

// ===========================================================================
// 3. DISPLAY TOGGLES (AVR)
// ===========================================================================
gV = "Display — AVR"
show_asia    = input.bool(true,  "Asia box (H/L/EQ)",           group=gV)
show_quarts  = input.bool(true,  "Quarters Q1/Q3 (25/75%)",     group=gV)
show_l1      = input.bool(true,  "Level 1 zone + red OTE",      group=gV)
show_consol  = input.bool(true,  "Consolidation band (AVR-)",   group=gV)
show_up      = input.bool(true,  "Up projections",              group=gV)
show_dn      = input.bool(true,  "Down projections",            group=gV)
show_labels  = input.bool(true,  "Right-edge labels",           group=gV)
show_table   = input.bool(true,  "Info table",                  group=gV)
show_warn    = input.bool(true,  "Discretion warning label",    group=gV)
ote_lo       = input.float(0.50, "OTE inner from", step=0.01, minval=0, maxval=1, group=gV)
ote_hi       = input.float(0.79, "OTE inner to",   step=0.01, minval=0, maxval=1, group=gV)
ext_bars     = input.int(6, "Extend right (bars)", minval=0, group=gV)

// ===========================================================================
// 3b. STYLE — AVR lines & boxes
// ===========================================================================
gST = "Style — AVR Lines"
c_asia = input.color(color.white, "Asia H/L",             group=gST, inline="s_asia")
w_asia = input.int(2, "w", minval=1, maxval=4,            group=gST, inline="s_asia")
c_eq   = input.color(color.white, "EQ",                   group=gST, inline="s_eq")
w_eq   = input.int(2, "w", minval=1, maxval=4,            group=gST, inline="s_eq")
c_q    = input.color(color.new(color.gray,60), "Quarters",group=gST, inline="s_q")
w_q    = input.int(1, "w", minval=1, maxval=4,            group=gST, inline="s_q")
c_am   = input.color(color.rgb(200,255,0), "AVR-",        group=gST, inline="s_am")
w_am   = input.int(1, "w", minval=1, maxval=4,            group=gST, inline="s_am")
c_a    = input.color(color.rgb(200,255,0), "AVR",         group=gST, inline="s_a")
w_a    = input.int(1, "w", minval=1, maxval=4,            group=gST, inline="s_a")
c_ap   = input.color(color.rgb(200,255,0), "AVR+",        group=gST, inline="s_ap")
w_ap   = input.int(1, "w", minval=1, maxval=4,            group=gST, inline="s_ap")
c_mx   = input.color(color.rgb(200,255,0), "Max",         group=gST, inline="s_mx")
w_mx   = input.int(1, "w", minval=1, maxval=4,            group=gST, inline="s_mx")
sty_line = input.string("Solid", "Level line style", options=["Solid","Dashed","Dotted"], group=gST)
sty_band = input.string("Dashed", "EQ / Quarter style",  options=["Solid","Dashed","Dotted"], group=gST)
gSB = "Style — AVR Zone fills"
c_l1   = input.color(color.new(color.gray,88), "Level-1 box fill", group=gSB, inline="s_l1")
cb_l1  = input.color(color.new(color.gray,70), "border",           group=gSB, inline="s_l1")
c_ote  = input.color(color.rgb(166,58,30,58), "OTE box fill", group=gSB, inline="s_ote")
cb_ote = input.color(color.new(color.rgb(166,58,30),10), "border",       group=gSB, inline="s_ote")
c_con  = input.color(color.new(color.rgb(41,128,185),92), "Consol fill", group=gSB, inline="s_con")
cb_con = input.color(color.new(color.rgb(41,128,185),90), "border", group=gSB, inline="s_con")
bw_box = input.int(1, "Box border width", minval=1, maxval=4, group=gSB)

// ===========================================================================
// 3c. CHANDELIER (multi-TF) — 3 selectable-TF instances  (USER-TUNED values)
// ===========================================================================
gCH = "Chandelier — Shared"
ch_useclose = input.bool(true, "Use close for extremum", group=gCH)
ch_hlen = input.int(50, "Hurst lookback (Enhanced)", minval=10, group=gCH)
ch_trth = input.float(0.60, "Hurst TREND >", step=0.05, group=gCH)
ch_mrth = input.float(0.40, "Hurst MR <",    step=0.05, group=gCH)

g1 = "Chandelier 1"
ch1_on  = input.bool(true,  "Enable",  group=g1, inline="a")
ch1_tf  = input.timeframe("3", "TF",   group=g1, inline="a")
ch1_len = input.int(20, "len", minval=2, group=g1, inline="b")
ch1_eng = input.string("Enhanced", "Engine", options=["Enhanced","Simple ATR"], group=g1, inline="b")
ch1_mtr = input.float(5.618, "mTREND", step=0.1, group=g1, inline="c")
ch1_mmr = input.float(3.236, "mMR", step=0.1, group=g1, inline="c")
ch1_mb  = input.float(6.1278, "mult base / Simple", step=0.1, group=g1)
ch1_sty = input.string("Line", "Type", options=["Line","Stepline","Circles"], group=g1, inline="d")
ch1_w   = input.int(1, "w", minval=1, maxval=4, group=g1, inline="d")
ch1_cu  = input.color(color.white, "Up", group=g1, inline="e")
ch1_cd  = input.color(color.rgb(0,224,192), "Dn", group=g1, inline="e")
ch1_sh  = input.bool(false, "Flip markers", group=g1)

g2 = "Chandelier 2"
ch2_on  = input.bool(true,  "Enable",  group=g2, inline="a")
ch2_tf  = input.timeframe("60", "TF",  group=g2, inline="a")
ch2_len = input.int(14, "len", minval=2, group=g2, inline="b")
ch2_eng = input.string("Enhanced", "Engine", options=["Enhanced","Simple ATR"], group=g2, inline="b")
ch2_mtr = input.float(5.618, "mTREND", step=0.1, group=g2, inline="c")
ch2_mmr = input.float(3.236, "mMR", step=0.1, group=g2, inline="c")
ch2_mb  = input.float(5.0, "mult base / Simple", step=0.1, group=g2)
ch2_sty = input.string("Line", "Type", options=["Line","Stepline","Circles"], group=g2, inline="d")
ch2_w   = input.int(1, "w", minval=1, maxval=4, group=g2, inline="d")
ch2_cu  = input.color(color.rgb(89,255,0),   "Up", group=g2, inline="e")
ch2_cd  = input.color(color.rgb(255,77,0),  "Dn", group=g2, inline="e")
ch2_sh  = input.bool(false, "Flip markers", group=g2)

g3 = "Chandelier 3"
ch3_on  = input.bool(true,  "Enable",  group=g3, inline="a")
ch3_tf  = input.timeframe("240", "TF", group=g3, inline="a")
ch3_len = input.int(14, "len", minval=2, group=g3, inline="b")
ch3_eng = input.string("Enhanced", "Engine", options=["Enhanced","Simple ATR"], group=g3, inline="b")
ch3_mtr = input.float(5.618, "mTREND", step=0.1, group=g3, inline="c")
ch3_mmr = input.float(3.236, "mMR", step=0.1, group=g3, inline="c")
ch3_mb  = input.float(5.618, "mult base / Simple", step=0.1, group=g3)
ch3_sty = input.string("Line", "Type", options=["Line","Stepline","Circles"], group=g3, inline="d")
ch3_w   = input.int(1, "w", minval=1, maxval=4, group=g3, inline="d")
ch3_cu  = input.color(color.rgb(89,255,0),    "Up", group=g3, inline="e")
ch3_cd  = input.color(color.rgb(255,77,0),  "Dn", group=g3, inline="e")
ch3_sh  = input.bool(false, "Flip markers", group=g3)

// ===========================================================================
// 3d. VOLUME PROFILE (single, chart-TF, KV-MACD ㉕ principle)
// ===========================================================================
gVP = "Volume Profile"
vp_on       = input.bool(true, "Enable Volume Profile", group=gVP)
vp_mode     = input.string("Session", "Mode", options=["Session","Rolling","Composite"], group=gVP, tooltip="Session=current day (D bar). Rolling=last N bars. Composite=last N days.")
vp_roll     = input.int(610, "Rolling window (bars)", minval=10, maxval=1990, group=gVP)
vp_days     = input.int(3,   "Composite sessions (days)", minval=1, group=gVP)
vp_maxbars  = input.int(987, "Max bars profiled (cap)", minval=10, maxval=1990, group=gVP)
vp_bins     = input.int(60,  "Bins (rows)", minval=5, maxval=200, group=gVP)
vp_va       = input.float(84, "Value area %", minval=1, maxval=100, group=gVP)
vp_maxwidth = input.int(60, "Profile width (bars)", minval=5, group=gVP)
vp_offset   = input.int(5,  "Profile offset (bars right)", minval=0, group=gVP)
vp_showhist = input.bool(true, "Show histogram", group=gVP)
vp_shownodes= input.bool(false, "Show HVN / LVN nodes", group=gVP)
vp_hvn      = input.float(1.3, "HVN ≥ ×avg-bin", step=0.1, group=gVP)
vp_lvn      = input.float(0.5, "LVN ≤ ×avg-bin", step=0.1, group=gVP)
vp_maxnodes = input.int(6, "Max nodes / side", minval=1, maxval=20, group=gVP)

gVP2 = "Volume Profile — Style"
vp_fill   = input.color(color.new(color.gray,90),  "Histogram fill",   group=gVP2)
vp_vafill = input.color(color.new(color.rgb(140,90,20),45),  "Value-area fill",  group=gVP2)
vp_cpoc = input.color(color.rgb(255,0,255), "POC", group=gVP2, inline="p")
vp_wpoc = input.int(1, "w", minval=1, maxval=4, group=gVP2, inline="p")
vp_spoc = input.string("Dotted", "", options=["Solid","Dashed","Dotted"], group=gVP2, inline="p")
vp_cva  = input.color(color.rgb(0,224,192), "VAH/VAL", group=gVP2, inline="v")
vp_wva  = input.int(2, "w", minval=1, maxval=4, group=gVP2, inline="v")
vp_sva  = input.string("Solid", "", options=["Solid","Dashed","Dotted"], group=gVP2, inline="v")
vp_chvn = input.color(color.new(color.green,0), "HVN", group=gVP2, inline="n")
vp_clvn = input.color(color.new(color.red,0),   "LVN", group=gVP2, inline="n")
vp_wnode= input.int(1, "w", minval=1, maxval=4, group=gVP2, inline="n")

// ===========================================================================
// 3e. VALUE-AREA REVERSION SIGNALS (VARS) — ported from LuxAlgo (Fabio strat)
//   Session-anchored VA (own per-bar profile). Signal = break VAL/VAH →
//   volume slowing (absorption) → engulfing candle closes back inside VA
//   within N bars, with growing dominant volume. VISUAL/location only.
// ===========================================================================
gVR = "Value-Area Reversion (VARS)"
vars_on    = input.bool(true,  "Enable VARS reclaim signals", group=gVR)
vars_prev  = input.bool(true,  "  • signals on PREVIOUS session VA", group=gVR)
vars_curr  = input.bool(true, "  • signals on CURRENT session VA",  group=gVR)
vars_showprevva = input.bool(false, "Show previous-session VA lines", group=gVR)
vars_colorcandles = input.bool(true, "Color candles on reclaim (barcolor)", group=gVR)
vars_anchorHour = input.int(18, "Session anchor hour (America/New_York)", minval=0, maxval=23, group=gVR, tooltip="Fabio anchors at 18:00 ET (futures open). VARS uses its OWN session profile independent of the display VP above.")
vars_maxbars = input.int(10, "Max bars to signal after breakout", minval=1, maxval=100, group=gVR)
vars_rows  = input.int(60, "VARS profile rows", minval=10, maxval=150, group=gVR)
vars_va    = input.float(70.0, "VARS value area %", minval=50.0, maxval=99.0, step=0.5, group=gVR, tooltip="Fabio/LuxAlgo default 70 (separate from the display-VP value area).")
vars_cbull = input.color(color.rgb(89,255,0),  "Bullish reclaim", group=gVR, inline="vc")
vars_cbear = input.color(color.rgb(255,77,0),   "Bearish reclaim", group=gVR, inline="vc")

// ===========================================================================
// 3f. CVD DIVERGENCE — ported from TradingFinder CVD Divergence [Periodic/EMA]
//   CVD = CLV×vol proxy. Regular divergence via fractal pivots + EMA50 gate.
//   Draws divergence lines + ±RD labels on price (bounded pool). VISUAL-only.
// ===========================================================================
gCV = "CVD Divergence"
cvd_on     = input.bool(true, "Enable CVD divergence", group=gCV)
cvd_mode   = input.string("Periodic", "Cumulative mode", options=["Periodic","EMA"], group=gCV)
cvd_period = input.int(20, "CVD period", minval=1, group=gCV)
cvd_n      = input.int(2, "Fractal periods (pivot L/R)", minval=1, maxval=20, group=gCV)
cvd_maxlen = input.int(30, "Max bars between pivots", minval=2, maxval=200, group=gCV)
cvd_showlabels = input.bool(true, "Show ±RD labels", group=gCV)
cvd_maxdraw = input.int(50, "Max divergences drawn (pool)", minval=5, maxval=200, group=gCV)
cvd_cbull  = input.color(color.rgb(21,151,25), "Bullish +RD", group=gCV, inline="cc")
cvd_cbear  = input.color(color.rgb(212,44,44), "Bearish -RD", group=gCV, inline="cc")

// ===========================================================================
// 3f-2. CVD CONFLUENCE (v9) — same ±RD on a higher TF (default M10) → ±CRD
//   Uses the SAME CVD settings as §3f (mode / period / fractal / max bars /
//   EMA50 gate); only the timeframe differs. The HTF value comes from the last
//   CLOSED HTF bar → no repaint, at most one HTF bar of extra lag.
// ===========================================================================
gCC = "CVD Confluence (v9)"
cvdC_on    = input.bool(true, "Enable ±CRD confluence", group=gCC)
cvdC_tf    = input.timeframe("5", "Confluence timeframe", group=gCC, tooltip="ค่าคำนวณ CVD ทุกตัวใช้ชุดเดียวกับกลุ่ม CVD Divergence ด้านบน เปลี่ยนแค่กรอบเวลา")
cvdC_tolmin = input.int(20, "Pivot time tolerance (minutes)", minval=0, maxval=600, group=gCC, tooltip="จุดกลับตัวของ ±RD สองกรอบเวลาต้องห่างกันไม่เกินกี่นาที จึงนับเป็นเหตุการณ์เดียวกัน · M10 ควรอย่างน้อย 20 นาที (1–2 แท่ง M10)")
cvdC_arrive = input.int(30, "Late-confirm window (chart bars)", minval=1, maxval=300, group=gCC, tooltip="ยอมรอสัญญาณกรอบใหญ่ได้กี่แท่งชาร์ต หลังจาก ±RD ของชาร์ตวาดไปแล้ว · M10 ยืนยันช้ากว่า M3 ได้ถึง 30 นาที = 10 แท่ง M3 · ตั้ง 30 แท่ง = เผื่อ 90 นาที")
cvdC_dbg    = input.bool(true, "Show CRD debug table (why it did / did not match)", group=gCC)
cvdC_conf  = input.bool(true, "Closed HTF bars only (no repaint)", group=gCC)
cvdC_up    = input.bool(true, "Upgrade an existing ±RD label when the HTF confirms later", group=gCC, tooltip="กรอบใหญ่ยืนยันช้ากว่า (M10 ช้ากว่า M3 ประมาณ 20 นาที) ถ้าปิดตัวเลือกนี้ CRD จะเกิดน้อยมาก")
cvdC_cbull = input.color(color.rgb(0,230,60),  "+CRD", group=gCC, inline="cc2")
cvdC_cbear = input.color(color.rgb(255,82,82), "-CRD", group=gCC, inline="cc2")
cvdC_big   = input.bool(true, "Emphasize ±CRD (bigger label, thicker line)", group=gCC)

// ===========================================================================
// 3f-3. DELTA SOURCE (v10) — how buy/sell volume is decided for CVD ±RD and ±CRD
//   Classification adapted from Footprint IQ Pro / CVD Profiles [TradingIQ] (© Trading-IQ).
//   ⚠ Lower-TF history on TradingView is limited → older bars fall back to candle CLV
//     (toggle below). Coverage is shown in the CRD debug table.
//   ⚠ Tick data is NOT used: it requires an Expert+ plan (RE10065 on Premium).
// ===========================================================================
gDS = "Delta Source (v10)"
SRC_CLV  = "Candle CLV (history)"
SRC_TICK = "LTF tick rule"
SRC_FP   = "Footprint (Premium)"
cvd_srcMode  = input.string("Candle CLV (history)", "Delta source", options=[SRC_FP, SRC_TICK, SRC_CLV], group=gDS, tooltip="Footprint = ปริมาณซื้อ/ขายที่ TradingView จัดฝั่งจากการเทรดจริง (ฟีเจอร์ Volume footprint ของแพ็กเกจ Premium) · LTF tick rule = ทิศจาก close แท่งย่อยเทียบแท่งย่อยก่อนหน้า × volume · Candle CLV = สูตรเดิม v8/v9")
cvd_fpTicks  = input.int(100, "Footprint ticks per row", minval=1, group=gDS, tooltip="ความสูงแถวของ footprint เป็นจำนวน tick · OANDA XAUUSD tick = 0.001 → 100 tick = 0.10$ · มีผลกับ Delta Profile (ต้องเล็กกว่าแถวของโปรไฟล์)")
cvd_ltfTf    = input.timeframe("1", "LTF for tick rule", group=gDS, tooltip="1 นาที = ประวัติยาว ครอบคลุมดี · 1S (วินาที) ละเอียดกว่าแต่ประวัติสั้น และต้องเป็นแพ็กเกจที่รองรับ")
cvd_fallback = input.bool(true, "Fall back (footprint → 1-min tick rule → candle CLV) where data is missing", group=gDS)

// ===========================================================================
// 3f-4. DELTA PROFILE (v10) — net delta per price row since the session anchor
//   Idea adapted from CVD Profiles [TradingIQ] (© Trading-IQ). Anchor = VARS session anchor.
// ===========================================================================
gDP = "Delta Profile (v10)"
dp_on      = input.bool(false, "Enable delta profile", group=gDP)
dp_rowAtr  = input.float(0.5, "Row height (× ATR14 at session start)", minval=0.05, step=0.05, group=gDP)
dp_maxRows = input.int(120, "Max rows drawn (largest |delta| first)", minval=10, maxval=250, group=gDP)
dp_width   = input.int(40, "Profile width (bars)", minval=5, group=gDP)
dp_offset  = input.int(70, "Profile offset (bars right)", minval=0, group=gDP, tooltip="VP ใช้ offset 5 + width 60 → ตั้ง 70 เพื่อไม่ทับกัน")
dp_showPoc = input.bool(false, "Show +DPOC / −DPOC (largest buy / sell delta rows)", group=gDP)
dp_cPos    = input.color(color.new(color.rgb(0,230,118),45), "Buy delta", group=gDP, inline="dpc")
dp_cNeg    = input.color(color.new(color.rgb(255,82,82),45),  "Sell delta", group=gDP, inline="dpc")

// ===========================================================================
// 3g. ANCHORED VWAP (AVWAP) — Grateful-Futures technique (developing polylines)
//   Session (day open) + optional prev-day + AUTO swing-pivot anchors.
//   Reads trend control / dynamic S/R to pair with GEX levels. VISUAL-only.
// ===========================================================================
gAV = "Anchored VWAP (AVWAP)"
avw_on   = input.bool(true, "Enable Anchored VWAP", group=gAV)
avw_src  = input.source(hlc3, "Price source", group=gAV)
avw_sess = input.bool(true,  "Session AVWAP (from day open)", group=gAV)
avw_prev = input.bool(false, "Previous-day AVWAP", group=gAV)
avw_swh  = input.bool(false,  "Swing-High AVWAP (sell-trend anchor)", group=gAV)
avw_swl  = input.bool(false,  "Swing-Low AVWAP (buy-trend anchor)",  group=gAV)
avw_piv  = input.int(20, "Swing pivot length (L/R)", minval=2, maxval=100, group=gAV, tooltip="Bigger = only more significant swings re-anchor the swing AVWAPs.")
avw_lbl  = input.bool(true, "Curve labels", group=gAV)
avw_w    = input.int(2, "Curve width", minval=1, maxval=4, group=gAV)
avw_c_sess = input.color(color.rgb(255,255,255),           "Session",   group=gAV, inline="ac")
avw_c_prev = input.color(color.new(color.gray,30),"Prev-day",  group=gAV, inline="ac")
avw_c_swh  = input.color(color.red,               "Swing-High",group=gAV, inline="ac2")
avw_c_swl  = input.color(color.green,             "Swing-Low", group=gAV, inline="ac2")

// ===========================================================================
// 3h. P/B/D ZONE DETECTOR — Patrick Nill range/breakout model (visual)
//   Range = low-efficiency (choppy) window; VALID after ≥3 touches; freeze on
//   breakout; SMALL(→breakout) vs BIG(→range) by height/ATR; flag ALIGNED zones.
// ===========================================================================
gPD = "P/B/D Zones (Patrick model)"
pd_on     = input.bool(true, "Enable P/B/D zone detector", group=gPD)
pd_win    = input.int(45, "Range window (bars)", minval=5, maxval=200, group=gPD)
pd_atrlen = input.int(20, "ATR length", minval=1, group=gPD)
pd_er     = input.float(0.35, "Range if box/path ratio <", step=0.05, minval=0.1, maxval=0.9, group=gPD, tooltip="Efficiency ratio = window height ÷ Σ true-range. Low = choppy/range, high = trending. Lower value = stricter (only very choppy windows qualify).")
pd_tol    = input.float(0.3, "Touch tolerance (×ATR)", step=0.05, minval=0.0, group=gPD)
pd_brk    = input.float(0.4, "Breakout beyond edge (×ATR)", step=0.05, minval=0.0, group=gPD)
pd_mintou = input.int(4, "Min touches to validate", minval=2, maxval=12, group=gPD)
pd_maxage = input.int(240, "Max range age (bars)", minval=20, maxval=1000, group=gPD)
pd_smallK = input.float(2.5, "SMALL zone height ≤ ×ATR (→breakout)", step=0.1, group=gPD)
pd_bigK   = input.float(4.5, "BIG zone height ≥ ×ATR (→range)", step=0.1, group=gPD)
pd_align  = input.bool(true, "Flag aligned/confluent zones", group=gPD)
pd_atol   = input.float(0.5, "Alignment tolerance (×ATR)", step=0.1, group=gPD)
pd_maxz   = input.int(16, "Max zones drawn (pool)", minval=2, maxval=40, group=gPD)
pd_showdev= input.bool(true, "Show developing (forming) zone", group=gPD)
pd_c_sm   = input.color(color.new(color.aqua,80),    "Small(BO) fill",  group=gPD, inline="pc")
pd_c_bg   = input.color(color.new(color.orange,80),  "Big(range) fill", group=gPD, inline="pc")
pd_c_al   = input.color(color.rgb(224, 64, 251, 85), "Aligned fill", group=gPD, inline="pc2")
pd_c_bd   = input.color(color.new(color.gray,40),    "Border",          group=gPD, inline="pc2")

// ===========================================================================
// 4. HELPER FUNCTIONS
// ===========================================================================
f_linestyle(s) => s == "Dashed" ? line.style_dashed : s == "Dotted" ? line.style_dotted : line.style_solid

// Yang-Zhang per-bar return sigma (KV-MACD price-σ vol unit)
f_yzsigma(n) =>
    float k  = 0.34 / (1.34 + (n + 1.0) / (n - 1.0))
    float lo_ = math.log(open / nz(close[1], open))
    float lc_ = math.log(close / open)
    float rs_ = math.log(high / close) * math.log(high / open) + math.log(low / close) * math.log(low / open)
    float vo = ta.variance(lo_, n)
    float vc = ta.variance(lc_, n)
    float vr = ta.sma(rs_, n)
    math.sqrt(math.max(vo + k * vc + vr, 0.0))

// Rescaled-range Hurst estimate over n bars
f_hurst(n) =>
    float mean = 0.0
    for i = 0 to n - 1
        mean += nz(math.log(close[i] / close[i + 1]))
    mean := mean / n
    float cum = 0.0
    float mx = -1e20
    float mn = 1e20
    float ss = 0.0
    for i = 0 to n - 1
        float dev = nz(math.log(close[i] / close[i + 1])) - mean
        cum += dev
        mx := math.max(mx, cum)
        mn := math.min(mn, cum)
        ss += dev * dev
    float R = mx - mn
    float S = math.sqrt(ss / n)
    float rs = S > 0 ? R / S : na
    (na(rs) or rs <= 0) ? 0.5 : math.log(rs) / math.log(n)

// STATELESS chandelier ingredients — safe to run inside request.security
// (no var, no self-history). Returns [highest, lowest, band(=vol×mult)].
f_chanIng(len, useClose, enhanced, hlen, mTr, mMr, mB, trTh, mrTh) =>
    float atrv = ta.atr(len)
    float volu = atrv
    if enhanced
        float sig = f_yzsigma(len)
        float vpx = close * sig
        volu := (na(vpx) or vpx <= 0) ? atrv : vpx
    float mult = mB
    if enhanced
        float h = f_hurst(hlen)
        mult := h > trTh ? mTr : (h < mrTh ? mMr : mB)
    float band = volu * mult
    float hh = useClose ? ta.highest(close, len) : ta.highest(high, len)
    float ll = useClose ? ta.lowest(close, len)  : ta.lowest(low, len)
    [hh, ll, band]

// STATEFUL everget recursion — runs at GLOBAL scope (var on chart timeline).
// Each of the 3 call-sites gets its own persistent state. Returns [stop, dir].
f_trail(hh, ll, band) =>
    var float ls = na
    var float ss = na
    var int   dir = 1
    float lb = hh - band
    float sb = ll + band
    ls := na(ls[1]) ? lb : (close[1] > ls[1] ? math.max(lb, ls[1]) : lb)
    ss := na(ss[1]) ? sb : (close[1] < ss[1] ? math.min(sb, ss[1]) : sb)
    dir := (dir == -1 and close > ss[1]) ? 1 : (dir == 1 and close < ls[1]) ? -1 : dir
    float st = dir > 0 ? ls : ss
    [st, dir]

// v10 — sign of one lower-TF sample (adapted from Footprint IQ Pro [TradingIQ], © Trading-IQ)
f_tickSign(float c, float b, float a, float p) =>
    (not na(b) and c <= b) ? -1.0 : (not na(a) and c >= a) ? 1.0 : (not na(p) and c > p) ? 1.0 : (not na(p) and c < p) ? -1.0 : 0.0
// v11 — standard tick rule: an unchanged close inherits the previous sign (v10 returned 0 and dropped that minute's volume)
f_tickCarry(float c, float p, float prev) =>
    (not na(p) and c > p) ? 1.0 : (not na(p) and c < p) ? -1.0 : prev

// v10 rev4 — net delta of the current HTF bar · returns [delta, code] code 1 = tick rule · 0 = none
//   TradingView allows only ONE request.footprint() call per script (runtime error RE10047).
//   That call is used by the chart TF (§13c-3). The confluence TF therefore uses the 1-min
//   tick rule when the source is Footprint (fpTicks kept in the signature, unused).
f_ltfDelta(string srcMode, simple string ltf, simple int fpTicks, bool fallback) =>
    float acc  = na
    int   code = 0
    if srcMode == "LTF tick rule" or srcMode == "Footprint (Premium)"
        [lV, lC, lP] = request.security_lower_tf(syminfo.tickerid, ltf, [volume, close, close[1]], ignore_invalid_timeframe = true)
        int ns = array.size(lV)
        if code == 0 and ns > 0
            acc := 0.0
            float sg_ = 0.0
            for i = 0 to ns - 1
                sg_ := f_tickCarry(array.get(lC, i), array.get(lP, i), sg_)
                acc += nz(array.get(lV, i)) * sg_
            code := 1
    [acc, code]

// CVD ±RD for ANY timeframe (v9) — identical formula to §13c, callable from
// request.security so the confluence TF uses exactly the same settings.
f_cvdDiv(string mode, simple int per, simple int n, simple int maxlen, string srcMode, simple string ltf, bool fallback, simple int fpTicks) =>
    float rng_  = high - low
    float clv_  = rng_ > 0 ? ((close - low) - (high - close)) / rng_ : 0.0
    [ld_, lc_] = f_ltfDelta(srcMode, ltf, fpTicks, fallback)
    float dbar_ = lc_ > 0 ? ld_ : (srcMode == "Candle CLV (history)" or fallback ? nz(volume, 0.0) * clv_ : 0.0)
    float hist_ = mode == "Periodic" ? math.sum(dbar_, per) : ta.ema(dbar_, per)
    float e50_  = ta.ema(close, 50)
    bool upF_ = not na(ta.pivothigh(n, n)) and close[n] > e50_[n]   // v11: EMA of the pivot bar
    bool dnF_ = not na(ta.pivotlow(n, n))  and close[n] < e50_[n]
    float hiLastP_ = ta.valuewhen(upF_, high[n], 0)
    float hiPerP_  = ta.valuewhen(upF_, high[n], 1)
    float hiLastH_ = ta.valuewhen(upF_, hist_[n], 0)
    float hiPerH_  = ta.valuewhen(upF_, hist_[n], 1)
    int   hiLastB_ = int(ta.valuewhen(upF_, bar_index[n], 0))
    int   hiPerB_  = int(ta.valuewhen(upF_, bar_index[n], 1))
    bool bear_ = upF_ and hiLastH_ > 0 and hiPerH_ > 0 and (hiLastB_ - hiPerB_) < maxlen and hiLastP_ > hiPerP_ and hiLastH_ < hiPerH_
    float loLastP_ = ta.valuewhen(dnF_, low[n], 0)
    float loPerP_  = ta.valuewhen(dnF_, low[n], 1)
    float loLastH_ = ta.valuewhen(dnF_, hist_[n], 0)
    float loPerH_  = ta.valuewhen(dnF_, hist_[n], 1)
    int   loLastB_ = int(ta.valuewhen(dnF_, bar_index[n], 0))
    int   loPerB_  = int(ta.valuewhen(dnF_, bar_index[n], 1))
    bool bull_ = dnF_ and loLastH_ < 0 and loPerH_ < 0 and (loLastB_ - loPerB_) < maxlen and loLastP_ < loPerP_ and loLastH_ > loPerH_
    // เวลาของแท่งจุดกลับตัวที่ใช้ยืนยัน divergence (ใช้จับคู่ข้ามกรอบเวลา)
    float bullT_ = ta.valuewhen(dnF_, time[n], 0)
    float bearT_ = ta.valuewhen(upF_, time[n], 0)
    // คืนค่าเป็น 1.0/0.0 เพื่อให้ nz() ใช้ได้ (bool ที่เป็น na ข้าม TF มีพฤติกรรมไม่แน่นอน)
    [bull_ ? 1.0 : 0.0, bear_ ? 1.0 : 0.0, bullT_, bearT_]

// --- VARS profile helpers (array-based → no historical-buffer issue) ---
va_reset(arr, rows) =>
    array.clear(arr)
    for i = 0 to rows - 1
        array.push(arr, 0.0)

va_add(bins, bLow, bHigh, bVol, rLow, bSize, rows) =>
    int bc = array.size(bins)
    if bVol > 0 and bSize > 0 and bc > 0
        int fi = math.max(0, math.min(bc - 1, int(math.floor((bLow  - rLow) / bSize))))
        int li = math.max(0, math.min(bc - 1, int(math.floor((bHigh - rLow) / bSize))))
        if fi <= li
            float vpb = bVol / (li - fi + 1)
            for k = fi to li
                array.set(bins, k, array.get(bins, k) + vpb)

va_levels(bins, rLow, bSize, vaPct) =>
    int bc = array.size(bins)
    float tot = 0.0
    float mx  = 0.0
    int   poc = 0
    if bc > 0
        for k = 0 to bc - 1
            float bv = array.get(bins, k)
            tot += bv
            if bv > mx
                mx := bv
                poc := k
    float tgt = tot * vaPct / 100.0
    float acc = mx
    int loI = poc
    int hiI = poc
    if tot > 0
        for e = 1 to bc
            if acc >= tgt
                break
            int li = poc - e
            int ui = poc + e
            bool cl = li >= 0 and li < bc
            bool cu = ui >= 0 and ui < bc
            float lv = cl ? array.get(bins, li) : 0.0
            float uv = cu ? array.get(bins, ui) : 0.0
            if cl and (not cu or lv >= uv)
                acc += lv
                loI := li
            else if cu
                acc += uv
                hiI := ui
    float val = (tot > 0 and not na(bSize)) ? rLow + loI * bSize : na
    float vah = (tot > 0 and not na(bSize)) ? rLow + (hiI + 1) * bSize : na
    [vah, val]

// ===========================================================================
// 5. DAILY RANGE (non-repaint: previous COMPLETED daily bar)
// ===========================================================================
_atrD = request.security(syminfo.tickerid, "D", ta.atr(atr_length)[1],           lookahead=barmerge.lookahead_on)
_adrD = request.security(syminfo.tickerid, "D", ta.sma(high - low, atr_length)[1], lookahead=barmerge.lookahead_on)
dRange = use_adr ? _adrD : _atrD

// ===========================================================================
// 6. ASIA SESSION STATE
// ===========================================================================
in_asia = not na(time(timeframe.period, asia_session, tz))
is_new  = in_asia and not in_asia[1]
is_end  = not in_asia and in_asia[1]

var float a_h = na
var float a_l = na
var int   a_n = 0
var float L_h   = na
var float L_l   = na
var float L_rng = na
var int   day_bi = na

if is_new
    a_h := high
    a_l := low
    a_n := 1
    L_h := na
    L_l := na
    L_rng := na
else if in_asia
    a_h := math.max(a_h, high)
    a_l := math.min(a_l, low)
    a_n += 1

if is_end and a_n >= i_minbars
    L_h   := a_h
    L_l   := a_l
    L_rng := dRange
    day_bi := bar_index

show = not in_asia and not na(L_h) and not na(L_rng)

// ===========================================================================
// 7. AVR LEVELS
// ===========================================================================
eq   = show ? (L_h + L_l) / 2.0 : na
arng = L_h - L_l
q1   = show ? L_l + arng * 0.25 : na
q3   = show ? L_l + arng * 0.75 : na
u_l1 = show ? L_h + L_rng * up_lvl1 : na
u_am = show ? L_h + L_rng * up_avrm : na
u_a  = show ? L_h + L_rng * up_avr  : na
u_ap = show ? L_h + L_rng * up_avrp : na
u_mx = show ? L_h + L_rng * up_max  : na
d_l1 = show ? L_l - L_rng * dn_lvl1 : na
d_am = show ? L_l - L_rng * dn_avrm : na
d_a  = show ? L_l - L_rng * dn_avr  : na
d_ap = show ? L_l - L_rng * dn_avrp : na
d_mx = show ? L_l - L_rng * dn_max  : na
u_band = L_rng * up_lvl1
d_band = L_rng * dn_lvl1
u_ote_a = show ? L_h + u_band * ote_lo : na
u_ote_b = show ? L_h + u_band * ote_hi : na
d_ote_a = show ? L_l - d_band * ote_lo : na
d_ote_b = show ? L_l - d_band * ote_hi : na

cAsia  = c_asia
stLine = f_linestyle(sty_line)
stBand = f_linestyle(sty_band)

// ===========================================================================
// 8. AVR PERSISTENT OBJECTS
// ===========================================================================
var line ln_h  = na
var line ln_l  = na
var line ln_eq = na
var line ln_q1 = na
var line ln_q3 = na
var line ln_uam = na
var line ln_ua  = na
var line ln_uap = na
var line ln_umx = na
var line ln_dam = na
var line ln_da  = na
var line ln_dap = na
var line ln_dmx = na
var box bx_l1u  = na
var box bx_oteu = na
var box bx_l1d  = na
var box bx_oted = na
var box bx_con  = na
var label lb_uam = na
var label lb_ua  = na
var label lb_uap = na
var label lb_umx = na
var label lb_dam = na
var label lb_da  = na
var label lb_dap = na
var label lb_dmx = na
var label lb_h    = na
var label lb_l    = na
var label lb_zone = na
var label lb_warn = na
var table tb = na

if barstate.isfirst
    ln_h  := line.new(bar_index,na,bar_index,na, color=c_asia, width=w_asia)
    ln_l  := line.new(bar_index,na,bar_index,na, color=c_asia, width=w_asia)
    ln_eq := line.new(bar_index,na,bar_index,na, color=c_eq, width=w_eq, style=stBand)
    ln_q1 := line.new(bar_index,na,bar_index,na, color=c_q, width=w_q, style=stBand)
    ln_q3 := line.new(bar_index,na,bar_index,na, color=c_q, width=w_q, style=stBand)
    ln_uam:= line.new(bar_index,na,bar_index,na, color=c_am, width=w_am, style=stLine)
    ln_ua := line.new(bar_index,na,bar_index,na, color=c_a,  width=w_a,  style=stLine)
    ln_uap:= line.new(bar_index,na,bar_index,na, color=c_ap, width=w_ap, style=stLine)
    ln_umx:= line.new(bar_index,na,bar_index,na, color=c_mx, width=w_mx, style=stLine)
    ln_dam:= line.new(bar_index,na,bar_index,na, color=c_am, width=w_am, style=stLine)
    ln_da := line.new(bar_index,na,bar_index,na, color=c_a,  width=w_a,  style=stLine)
    ln_dap:= line.new(bar_index,na,bar_index,na, color=c_ap, width=w_ap, style=stLine)
    ln_dmx:= line.new(bar_index,na,bar_index,na, color=c_mx, width=w_mx, style=stLine)
    bx_l1u := box.new(bar_index,na,bar_index,na, border_color=cb_l1,  bgcolor=c_l1,  border_width=bw_box)
    bx_oteu:= box.new(bar_index,na,bar_index,na, border_color=cb_ote, bgcolor=c_ote, border_width=bw_box)
    bx_l1d := box.new(bar_index,na,bar_index,na, border_color=cb_l1,  bgcolor=c_l1,  border_width=bw_box)
    bx_oted:= box.new(bar_index,na,bar_index,na, border_color=cb_ote, bgcolor=c_ote, border_width=bw_box)
    bx_con := box.new(bar_index,na,bar_index,na, border_color=cb_con, bgcolor=c_con, border_width=bw_box)
    lb_uam:=label.new(bar_index,na,"", style=label.style_none, textcolor=c_am, size=size.small)
    lb_ua :=label.new(bar_index,na,"", style=label.style_none, textcolor=c_a,  size=size.small)
    lb_uap:=label.new(bar_index,na,"", style=label.style_none, textcolor=c_ap, size=size.small)
    lb_umx:=label.new(bar_index,na,"", style=label.style_none, textcolor=c_mx, size=size.small)
    lb_dam:=label.new(bar_index,na,"", style=label.style_none, textcolor=c_am, size=size.small)
    lb_da :=label.new(bar_index,na,"", style=label.style_none, textcolor=c_a,  size=size.small)
    lb_dap:=label.new(bar_index,na,"", style=label.style_none, textcolor=c_ap, size=size.small)
    lb_dmx:=label.new(bar_index,na,"", style=label.style_none, textcolor=c_mx, size=size.small)
    lb_h  :=label.new(bar_index,na,"", style=label.style_none, textcolor=c_asia, size=size.small)
    lb_l  :=label.new(bar_index,na,"", style=label.style_none, textcolor=c_asia, size=size.small)
    lb_zone:=label.new(bar_index,na,"", style=label.style_label_left, color=color.new(color.black,20), textcolor=color.white, size=size.small)
    lb_warn:=label.new(bar_index,na,"", style=label.style_label_down, color=color.new(color.orange,25), textcolor=color.black, size=size.tiny)
    tb := table.new(position.top_right, 2, 8, border_width=1, frame_color=color.new(color.gray,60), frame_width=1)

// ===========================================================================
// 9. AVR UPDATE HELPERS
// ===========================================================================
uH(l, x1, x2, y, vis) =>
    yy = vis ? y : na
    line.set_xy1(l, x1, yy)
    line.set_xy2(l, x2, yy)

uB(b, x1, x2, top, bot, vis) =>
    if vis and not na(top) and not na(bot)
        box.set_left(b, x1)
        box.set_right(b, x2)
        box.set_top(b, top)
        box.set_bottom(b, bot)
    else
        box.set_top(b, na)
        box.set_bottom(b, na)

uL(lb, x, y, txt, vis) =>
    if vis and not na(y)
        label.set_xy(lb, x, y)
        label.set_text(lb, txt)
    else
        label.set_xy(lb, x, na)
        label.set_text(lb, "")

f_row(r, k, v, kc, vc) =>
    table.cell(tb, 0, r, k, text_color=color.white, text_size=size.small, bgcolor=kc)
    table.cell(tb, 1, r, v, text_color=color.white, text_size=size.small, bgcolor=vc)

// ===========================================================================
// 10. AVR RENDER
// ===========================================================================
if barstate.islast
    x1 = na(day_bi) ? bar_index : day_bi
    x2 = bar_index + ext_bars
    uH(ln_h,  x1, x2, L_h, show and show_asia)
    uH(ln_l,  x1, x2, L_l, show and show_asia)
    uH(ln_eq, x1, x2, eq,  show and show_asia)
    uH(ln_q1, x1, x2, q1,  show and show_quarts)
    uH(ln_q3, x1, x2, q3,  show and show_quarts)
    uH(ln_uam, x1, x2, u_am, show and show_up)
    uH(ln_ua,  x1, x2, u_a,  show and show_up)
    uH(ln_uap, x1, x2, u_ap, show and show_up)
    uH(ln_umx, x1, x2, u_mx, show and show_up)
    uH(ln_dam, x1, x2, d_am, show and show_dn)
    uH(ln_da,  x1, x2, d_a,  show and show_dn)
    uH(ln_dap, x1, x2, d_ap, show and show_dn)
    uH(ln_dmx, x1, x2, d_mx, show and show_dn)
    uB(bx_l1u,  x1, x2, u_l1, L_h,        show and show_l1 and show_up)
    uB(bx_oteu, x1, x2, u_ote_b, u_ote_a, show and show_l1 and show_up)
    uB(bx_l1d,  x1, x2, L_l, d_l1,        show and show_l1 and show_dn)
    uB(bx_oted, x1, x2, d_ote_a, d_ote_b, show and show_l1 and show_dn)
    uB(bx_con,  x1, x2, u_am, d_am,       show and show_consol)
    uL(lb_h,   x2, L_h,  "Asia H",  show and show_labels and show_asia)
    uL(lb_l,   x2, L_l,  "Asia L",  show and show_labels and show_asia)
    uL(lb_uam, x2, u_am, "AVR- ↑",  show and show_labels and show_up)
    uL(lb_ua,  x2, u_a,  "AVR ↑",   show and show_labels and show_up)
    uL(lb_uap, x2, u_ap, "AVR+ ↑",  show and show_labels and show_up)
    uL(lb_umx, x2, u_mx, "MAX ↑",   show and show_labels and show_up)
    uL(lb_dam, x2, d_am, "AVR- ↓",  show and show_labels and show_dn)
    uL(lb_da,  x2, d_a,  "AVR ↓",   show and show_labels and show_dn)
    uL(lb_dap, x2, d_ap, "AVR+ ↓",  show and show_labels and show_dn)
    uL(lb_dmx, x2, d_mx, "MAX ↓",   show and show_labels and show_dn)

// ===========================================================================
// 11. AVR ZONE CLASSIFICATION + TABLE
// ===========================================================================
zoneTxt = "—"
if show
    c = close
    zoneTxt := c > u_mx  ? "▲ > MAX  (OUTLIER — rare)" :
               c > u_ap  ? "▲ AVR+ → MAX  (exhaustion ↑)" :
               c > u_a   ? "▲ AVR → AVR+  (avg expansion ↑)" :
               c > u_am  ? "▲ AVR- → AVR  ↑" :
               c > L_h   ? "▲ Level 1 ↑  (first expansion)" :
               c > eq    ? "◦ Upper Asia (balance)" :
               c > L_l   ? "◦ Lower Asia (balance)" :
               c > d_am  ? "▼ Level 1 ↓  (first expansion)" :
               c > d_a   ? "▼ AVR- → AVR ↓" :
               c > d_ap  ? "▼ AVR → AVR+  (avg expansion ↓)" :
               c > d_mx  ? "▼ AVR+ → MAX  (exhaustion ↓)" :
                           "▼ < MAX  (OUTLIER — rare)"

if barstate.islast
    uL(lb_zone, bar_index + ext_bars, close, "  " + zoneTxt, show and show_labels)
    if show_warn and show
        label.set_xy(lb_warn, bar_index, na(u_mx) ? high : u_mx)
        label.set_text(lb_warn, "ZONES = LOCATION / EXHAUSTION — NOT ENTRY")
    else
        label.set_text(lb_warn, "")
        label.set_xy(lb_warn, bar_index, na)

if barstate.islast and show_table
    bg = color.new(color.black, 15)
    hd = color.new(cAsia, 10)
    table.cell(tb, 0, 0, "GOLD SESSION FRAMEWORK v11", text_color=color.white, text_size=size.small, bgcolor=hd)
    table.cell(tb, 1, 0, use_adr ? "ADR" : "ATR", text_color=color.white, text_size=size.small, bgcolor=hd)
    f_row(1, (use_adr?"ADR(":"ATR(")+str.tostring(atr_length)+")", show?str.tostring(L_rng, format.mintick):"—", bg, bg)
    f_row(2, "Asia H / L", show?str.tostring(L_h, format.mintick)+" / "+str.tostring(L_l, format.mintick):"—", bg, bg)
    f_row(3, "Asia range", show?str.tostring(arng, format.mintick):"—", bg, bg)
    f_row(4, "Up  AVR / MAX", show?str.tostring(u_a, format.mintick)+" / "+str.tostring(u_mx, format.mintick):"—", bg, color.new(color.red,80))
    f_row(5, "Dn  AVR / MAX", show?str.tostring(d_a, format.mintick)+" / "+str.tostring(d_mx, format.mintick):"—", bg, color.new(color.red,80))
    f_row(6, "Current zone", show?zoneTxt:"waiting Asia close", bg, color.new(color.orange,70))
    table.cell(tb, 0, 7, "LOCATION/EXHAUSTION — NOT ENTRY", text_color=color.yellow, text_size=size.tiny, bgcolor=bg)
    table.cell(tb, 1, 7, "", bgcolor=bg)

// ===========================================================================
// 12. CHANDELIER — stateless ingredients via security, recursion at global scope
// ===========================================================================
[hh1, ll1, bd1] = request.security(syminfo.tickerid, ch1_tf, f_chanIng(ch1_len, ch_useclose, ch1_eng=="Enhanced", ch_hlen, ch1_mtr, ch1_mmr, ch1_mb, ch_trth, ch_mrth), lookahead=barmerge.lookahead_off)
[hh2, ll2, bd2] = request.security(syminfo.tickerid, ch2_tf, f_chanIng(ch2_len, ch_useclose, ch2_eng=="Enhanced", ch_hlen, ch2_mtr, ch2_mmr, ch2_mb, ch_trth, ch_mrth), lookahead=barmerge.lookahead_off)
[hh3, ll3, bd3] = request.security(syminfo.tickerid, ch3_tf, f_chanIng(ch3_len, ch_useclose, ch3_eng=="Enhanced", ch_hlen, ch3_mtr, ch3_mmr, ch3_mb, ch_trth, ch_mrth), lookahead=barmerge.lookahead_off)

// v11: a Chandelier whose TF is faster than the chart cannot be computed here (request.security returns only the
//      last intrabar of the lower TF) → treated as off on that chart (e.g. Ch1 'TF 3' on the H1 pane).
bool ch1_ok = ch1_on and timeframe.in_seconds(ch1_tf) >= timeframe.in_seconds()
bool ch2_ok = ch2_on and timeframe.in_seconds(ch2_tf) >= timeframe.in_seconds()
bool ch3_ok = ch3_on and timeframe.in_seconds(ch3_tf) >= timeframe.in_seconds()
[st1, dr1] = f_trail(ch1_ok ? hh1 : na, ch1_ok ? ll1 : na, ch1_ok ? bd1 : na)
[st2, dr2] = f_trail(ch2_ok ? hh2 : na, ch2_ok ? ll2 : na, ch2_ok ? bd2 : na)
[st3, dr3] = f_trail(ch3_ok ? hh3 : na, ch3_ok ? ll3 : na, ch3_ok ? bd3 : na)

c1 = dr1 > 0 ? ch1_cu : ch1_cd
c2 = dr2 > 0 ? ch2_cu : ch2_cd
c3 = dr3 > 0 ? ch3_cu : ch3_cd

plot(ch1_on and ch1_sty=="Line"     ? st1 : na, "Ch1 line", color=c1, linewidth=ch1_w, style=plot.style_steplinebr)
plot(ch1_on and ch1_sty=="Stepline" ? st1 : na, "Ch1 step", color=c1, linewidth=ch1_w, style=plot.style_stepline, display=display.none)
plot(ch1_on and ch1_sty=="Circles"  ? st1 : na, "Ch1 dots", color=c1, linewidth=ch1_w, style=plot.style_circles, display=display.none)
plot(ch2_on and ch2_sty=="Line"     ? st2 : na, "Ch2 line", color=c2, linewidth=ch2_w, style=plot.style_steplinebr)
plot(ch2_on and ch2_sty=="Stepline" ? st2 : na, "Ch2 step", color=c2, linewidth=ch2_w, style=plot.style_stepline, display=display.none)
plot(ch2_on and ch2_sty=="Circles"  ? st2 : na, "Ch2 dots", color=c2, linewidth=ch2_w, style=plot.style_circles, display=display.none)
plot(ch3_on and ch3_sty=="Line"     ? st3 : na, "Ch3 line", color=c3, linewidth=ch3_w, style=plot.style_steplinebr)
plot(ch3_on and ch3_sty=="Stepline" ? st3 : na, "Ch3 step", color=c3, linewidth=ch3_w, style=plot.style_stepline, display=display.none)
plot(ch3_on and ch3_sty=="Circles"  ? st3 : na, "Ch3 dots", color=c3, linewidth=ch3_w, style=plot.style_circles, display=display.none)

plotshape(ch1_on and ch1_sh and dr1>0 and dr1[1]<0, "Ch1 ↑", shape.triangleup,   location.belowbar, ch1_cu, size=size.tiny)
plotshape(ch1_on and ch1_sh and dr1<0 and dr1[1]>0, "Ch1 ↓", shape.triangledown, location.abovebar, ch1_cd, size=size.tiny)
plotshape(ch2_on and ch2_sh and dr2>0 and dr2[1]<0, "Ch2 ↑", shape.triangleup,   location.belowbar, ch2_cu, size=size.tiny)
plotshape(ch2_on and ch2_sh and dr2<0 and dr2[1]>0, "Ch2 ↓", shape.triangledown, location.abovebar, ch2_cd, size=size.tiny)
plotshape(ch3_on and ch3_sh and dr3>0 and dr3[1]<0, "Ch3 ↑", shape.triangleup,   location.belowbar, ch3_cu, size=size.tiny)
plotshape(ch3_on and ch3_sh and dr3<0 and dr3[1]>0, "Ch3 ↓", shape.triangledown, location.abovebar, ch3_cd, size=size.tiny)

// ===========================================================================
// 13. VOLUME PROFILE — OBJECT POOL (create-once, update-coords: realtime-safe)
// ===========================================================================
bool dB = ta.change(time("D")) != 0
var int sessN = 1
sessN := dB ? 1 : sessN + 1

stPoc = f_linestyle(vp_spoc)
stVa  = f_linestyle(vp_sva)

var array<box>  vpBox  = array.new<box>()
var array<line> vpNode = array.new<line>()
var line  vpLpoc = na
var line  vpLvah = na
var line  vpLval = na
var label vpLbP  = na
var label vpLbH  = na
var label vpLbV  = na

if barstate.isfirst
    for i = 0 to vp_bins - 1
        array.push(vpBox, box.new(bar_index, na, bar_index, na, border_width=1))
    for i = 0 to (vp_maxnodes * 2) - 1
        array.push(vpNode, line.new(bar_index, na, bar_index, na))
    vpLpoc := line.new(bar_index, na, bar_index, na)
    vpLvah := line.new(bar_index, na, bar_index, na)
    vpLval := line.new(bar_index, na, bar_index, na)
    vpLbP  := label.new(bar_index, na, "", style=label.style_label_left, size=size.tiny)
    vpLbH  := label.new(bar_index, na, "", style=label.style_label_left, size=size.tiny)
    vpLbV  := label.new(bar_index, na, "", style=label.style_label_left, size=size.tiny)

if barstate.islast
    // --- hide the whole pool first ---
    for b in vpBox
        box.set_top(b, na)
        box.set_bottom(b, na)
    for l in vpNode
        line.set_xy1(l, bar_index, na)
        line.set_xy2(l, bar_index, na)
    line.set_xy1(vpLpoc, bar_index, na)
    line.set_xy2(vpLpoc, bar_index, na)
    line.set_xy1(vpLvah, bar_index, na)
    line.set_xy2(vpLvah, bar_index, na)
    line.set_xy1(vpLval, bar_index, na)
    line.set_xy2(vpLval, bar_index, na)
    label.set_xy(vpLbP, bar_index, na)
    label.set_xy(vpLbH, bar_index, na)
    label.set_xy(vpLbV, bar_index, na)

    if vp_on
        // --- window length (clamped to available history) ---
        int Nbars = vp_roll
        if vp_mode == "Session"
            Nbars := sessN
        else if vp_mode == "Composite"
            int seen = 0
            int nb = 1
            int scan = math.min(vp_maxbars, bar_index + 1) - 1
            for i = 0 to scan
                if dB[i]
                    seen += 1
                    if seen > vp_days
                        break
                nb := i + 1
            Nbars := nb
        Nbars := math.min(Nbars, vp_maxbars)
        Nbars := math.min(Nbars, bar_index + 1)
        Nbars := math.max(Nbars, 2)

        // --- price range ---
        float hi = high
        float lo = low
        for i = 0 to Nbars - 1
            if not na(high[i]) and not na(low[i])
                hi := math.max(hi, high[i])
                lo := math.min(lo, low[i])

        float binSize = (hi - lo) / vp_bins
        if binSize > 0
            // --- accumulate volume into bins ---
            array<float> binVol = array.new_float(vp_bins, 0.0)
            for i = 0 to Nbars - 1
                float v = volume[i]
                if not na(v) and v > 0 and not na(high[i]) and not na(low[i])
                    int b0 = math.max(0, int(math.floor((low[i]  - lo) / binSize)))
                    int b1 = math.min(vp_bins - 1, int(math.floor((high[i] - lo) / binSize)))
                    float vPer = v / math.max(1, b1 - b0 + 1)
                    for b = b0 to b1
                        array.set(binVol, b, array.get(binVol, b) + vPer)

            // --- POC + totals ---
            int   pocIdx = 0
            float pocVol = 0.0
            float total  = 0.0
            for b = 0 to vp_bins - 1
                float bv = array.get(binVol, b)
                total += bv
                if bv > pocVol
                    pocVol := bv
                    pocIdx := b

            if total > 0 and pocVol > 0
                float avgBin = total / vp_bins
                float pocPrice = lo + (pocIdx + 0.5) * binSize

                // --- value area expansion (larger-neighbor) ---
                float target = total * vp_va / 100.0
                float cum = pocVol
                int lo_i = pocIdx
                int hi_i = pocIdx
                while cum < target and (lo_i > 0 or hi_i < vp_bins - 1)
                    float upV = hi_i < vp_bins - 1 ? array.get(binVol, hi_i + 1) : -1.0
                    float dnV = lo_i > 0           ? array.get(binVol, lo_i - 1) : -1.0
                    if upV >= dnV
                        hi_i += 1
                        cum  += upV
                    else
                        lo_i -= 1
                        cum  += dnV
                float vah = lo + (hi_i + 1) * binSize
                float val = lo + lo_i * binSize

                int rX = bar_index + vp_offset
                int lX = bar_index - math.min(Nbars, 300)

                // --- histogram → update pool boxes ---
                if vp_showhist
                    for b = 0 to vp_bins - 1
                        box bx = array.get(vpBox, b)
                        float bv = array.get(binVol, b)
                        int wBars = int(math.round(bv / pocVol * vp_maxwidth))
                        if bv > 0 and wBars >= 1
                            bool inVA = b >= lo_i and b <= hi_i
                            color fc = b == pocIdx ? color.new(vp_cpoc, 55) : inVA ? vp_vafill : vp_fill
                            box.set_left(bx, rX)
                            box.set_right(bx, rX + wBars)
                            box.set_top(bx, lo + (b + 1) * binSize)
                            box.set_bottom(bx, lo + b * binSize)
                            box.set_bgcolor(bx, fc)
                            box.set_border_color(bx, color.new(color.gray, 90))

                // --- POC / VAH / VAL lines + labels ---
                line.set_xy1(vpLpoc, lX, pocPrice)
                line.set_xy2(vpLpoc, rX + vp_maxwidth, pocPrice)
                line.set_color(vpLpoc, vp_cpoc)
                line.set_width(vpLpoc, vp_wpoc)
                line.set_style(vpLpoc, stPoc)
                label.set_xy(vpLbP, rX + vp_maxwidth, pocPrice)
                label.set_text(vpLbP, "POC " + str.tostring(pocPrice, format.mintick))
                label.set_textcolor(vpLbP, vp_cpoc)
                label.set_color(vpLbP, color.new(color.black, 30))

                line.set_xy1(vpLvah, lX, vah)
                line.set_xy2(vpLvah, rX + vp_maxwidth, vah)
                line.set_color(vpLvah, vp_cva)
                line.set_width(vpLvah, vp_wva)
                line.set_style(vpLvah, stVa)
                label.set_xy(vpLbH, rX + vp_maxwidth, vah)
                label.set_text(vpLbH, "VAH")
                label.set_textcolor(vpLbH, vp_cva)
                label.set_color(vpLbH, color.new(color.black, 40))

                line.set_xy1(vpLval, lX, val)
                line.set_xy2(vpLval, rX + vp_maxwidth, val)
                line.set_color(vpLval, vp_cva)
                line.set_width(vpLval, vp_wva)
                line.set_style(vpLval, stVa)
                label.set_xy(vpLbV, rX + vp_maxwidth, val)
                label.set_text(vpLbV, "VAL")
                label.set_textcolor(vpLbV, vp_cva)
                label.set_color(vpLbV, color.new(color.black, 40))

                // --- HVN / LVN nodes → update pool lines ---
                if vp_shownodes
                    int used = 0
                    int nH = 0
                    int nL = 0
                    int cap = array.size(vpNode)
                    for b = 1 to vp_bins - 2
                        if used < cap
                            float bv = array.get(binVol, b)
                            float bp = array.get(binVol, b - 1)
                            float bn = array.get(binVol, b + 1)
                            float ny = lo + (b + 0.5) * binSize
                            bool isH = nH < vp_maxnodes and bv >= vp_hvn * avgBin and bv >= bp and bv >= bn
                            bool isL = nL < vp_maxnodes and bv <= vp_lvn * avgBin and bv <= bp and bv <= bn and bv > 0
                            if isH or isL
                                line ln = array.get(vpNode, used)
                                line.set_xy1(ln, lX, ny)
                                line.set_xy2(ln, rX, ny)
                                line.set_color(ln, isH ? vp_chvn : vp_clvn)
                                line.set_width(ln, vp_wnode)
                                line.set_style(ln, isH ? line.style_solid : line.style_dotted)
                                used += 1
                                if isH
                                    nH += 1
                                else
                                    nL += 1

// ===========================================================================
// 13b. VALUE-AREA REVERSION (VARS) — per-bar session profile + reclaim engine
// ===========================================================================
// current-session profile state
var array<float> va_barHigh = array.new_float()
var array<float> va_barLow  = array.new_float()
var array<float> va_barUp   = array.new_float()
var array<float> va_barDn   = array.new_float()
var array<float> va_pVol    = array.new_float()
var array<float> va_prevVol = array.new_float()
var float va_low = na
var float va_high = na
var float va_bin = na
var float va_prevLow = na
var float va_prevBin = na
var bool  va_hasPrev = false
var int   va_activeAnchorTs = na
var int   va_anchorBI = na
var int   va_prevAnchorBI = na
// current state machine
var bool  va_bearBO = false
var bool  va_bullBO = false
var bool  va_bearSlow = false
var bool  va_bullSlow = false
var float va_lastBearVol = na
var float va_lastBullVol = na
var int   va_bearBars = 0
var int   va_bullBars = 0
// previous-session state machine
var bool  va_pBearBO = false
var bool  va_pBullBO = false
var bool  va_pBearSlow = false
var bool  va_pBullSlow = false
var float va_pLastBearVol = na
var float va_pLastBullVol = na
var int   va_pBearBars = 0
var int   va_pBullBars = 0

// anchor detection (Fabio 18:00 ET style)
int va_ly = year(time, "America/New_York")
int va_lm = month(time, "America/New_York")
int va_ld = dayofmonth(time, "America/New_York")
int va_todayTs = timestamp("America/New_York", va_ly, va_lm, va_ld, vars_anchorHour, 0)
int va_anchorTs = time >= va_todayTs ? va_todayTs : timestamp("America/New_York", va_ly, va_lm, va_ld - 1, vars_anchorHour, 0)
bool va_newAnchor = na(va_activeAnchorTs) or va_anchorTs != va_activeAnchorTs

if va_newAnchor
    if not na(va_activeAnchorTs) and not na(va_low) and array.size(va_pVol) > 0
        va_prevVol := array.copy(va_pVol)
        va_prevAnchorBI := va_anchorBI
        va_prevLow := va_low
        va_prevBin := va_bin
        va_hasPrev := true
    va_activeAnchorTs := va_anchorTs
    va_anchorBI := bar_index
    array.clear(va_barHigh)
    array.clear(va_barLow)
    array.clear(va_barUp)
    array.clear(va_barDn)
    va_bearBO := false
    va_bullBO := false
    va_bearSlow := false
    va_bullSlow := false
    va_lastBearVol := na
    va_lastBullVol := na
    va_bearBars := 0
    va_bullBars := 0
    va_pBearBO := false
    va_pBullBO := false
    va_pBearSlow := false
    va_pBullSlow := false
    va_pLastBearVol := na
    va_pLastBullVol := na
    va_pBearBars := 0
    va_pBullBars := 0
    va_low := na
    va_high := na
    va_bin := na
    va_reset(va_pVol, vars_rows)

float va_vol = nz(volume, 0.0)
float va_up  = close >= open ? va_vol : 0.0
float va_dn  = close <  open ? va_vol : 0.0
array.push(va_barHigh, high)
array.push(va_barLow,  low)
array.push(va_barUp,   va_up)
array.push(va_barDn,   va_dn)

bool va_rangeCh = na(va_low) or low < va_low or high > va_high
if va_rangeCh
    va_low  := na(va_low)  ? low  : math.min(va_low, low)
    va_high := na(va_high) ? high : math.max(va_high, high)
    va_bin  := math.max((va_high - va_low) / vars_rows, syminfo.mintick)
    va_reset(va_pVol, vars_rows)
    int sb = array.size(va_barHigh)
    for si = 0 to sb - 1
        va_add(va_pVol, array.get(va_barLow, si), array.get(va_barHigh, si), array.get(va_barUp, si) + array.get(va_barDn, si), va_low, va_bin, vars_rows)
else
    va_add(va_pVol, low, high, va_vol, va_low, va_bin, vars_rows)

[va_vah, va_val]   = va_levels(va_pVol,    va_low,     va_bin,     vars_va)
[va_pvah, va_pval] = va_levels(va_prevVol, va_prevLow, va_prevBin, vars_va)

bool va_bullEng = close > open and close[1] < open[1] and open <= close[1] and close >= open[1]
bool va_bearEng = close < open and close[1] > open[1] and open >= close[1] and close <= open[1]

// current-session reclaim state machine
bool va_bullReclaim = false
bool va_bearReclaim = false
bool va_hasVA = not na(va_val) and not na(va_vah)
if va_hasVA
    if close < va_val
        va_bullBO := false
        va_bullBars := 0
        va_bullSlow := false
        va_lastBullVol := na
        if not va_bearBO
            va_bearSlow := false
            va_lastBearVol := na
            va_bearBars := 1
        else
            va_bearBars += 1
        va_bearBO := true
        if va_dn > 0
            if not na(va_lastBearVol) and va_dn < va_lastBearVol
                va_bearSlow := true
            va_lastBearVol := va_dn
    else if close > va_vah
        va_bearBO := false
        va_bearBars := 0
        va_bearSlow := false
        va_lastBearVol := na
        if not va_bullBO
            va_bullSlow := false
            va_lastBullVol := na
            va_bullBars := 1
        else
            va_bullBars += 1
        va_bullBO := true
        if va_up > 0
            if not na(va_lastBullVol) and va_up < va_lastBullVol
                va_bullSlow := true
            va_lastBullVol := va_up
    else
        va_bullReclaim := va_bullEng and va_bearBO and va_bearBars <= vars_maxbars and va_bearSlow and va_up > va_dn and va_up > nz(va_lastBearVol, 0.0)
        va_bearReclaim := va_bearEng and va_bullBO and va_bullBars <= vars_maxbars and va_bullSlow and va_dn > va_up and va_dn > nz(va_lastBullVol, 0.0)
        va_bearBO := false
        va_bullBO := false
        va_bearBars := 0
        va_bullBars := 0
        va_bearSlow := false
        va_bullSlow := false
        va_lastBearVol := na
        va_lastBullVol := na

// previous-session reclaim state machine
bool va_pBullReclaim = false
bool va_pBearReclaim = false
bool va_hasPVA = va_hasPrev and not na(va_pval) and not na(va_pvah)
if va_hasPVA
    if close < va_pval
        va_pBullBO := false
        va_pBullBars := 0
        va_pBullSlow := false
        va_pLastBullVol := na
        if not va_pBearBO
            va_pBearSlow := false
            va_pLastBearVol := na
            va_pBearBars := 1
        else
            va_pBearBars += 1
        va_pBearBO := true
        if va_dn > 0
            if not na(va_pLastBearVol) and va_dn < va_pLastBearVol
                va_pBearSlow := true
            va_pLastBearVol := va_dn
    else if close > va_pvah
        va_pBearBO := false
        va_pBearBars := 0
        va_pBearSlow := false
        va_pLastBearVol := na
        if not va_pBullBO
            va_pBullSlow := false
            va_pLastBullVol := na
            va_pBullBars := 1
        else
            va_pBullBars += 1
        va_pBullBO := true
        if va_up > 0
            if not na(va_pLastBullVol) and va_up < va_pLastBullVol
                va_pBullSlow := true
            va_pLastBullVol := va_up
    else
        va_pBullReclaim := va_bullEng and va_pBearBO and va_pBearBars <= vars_maxbars and va_pBearSlow and va_up > va_dn and va_up > nz(va_pLastBearVol, 0.0)
        va_pBearReclaim := va_bearEng and va_pBullBO and va_pBullBars <= vars_maxbars and va_pBullSlow and va_dn > va_up and va_dn > nz(va_pLastBullVol, 0.0)
        va_pBearBO := false
        va_pBullBO := false
        va_pBearBars := 0
        va_pBullBars := 0
        va_pBearSlow := false
        va_pBullSlow := false
        va_pLastBearVol := na
        va_pLastBullVol := na

// signals
plotshape(vars_on and vars_prev and va_pBullReclaim, "PVA reclaim ↑", shape.triangleup,   location.belowbar, vars_cbull, text="PVA", textcolor=color.white, size=size.small)
plotshape(vars_on and vars_prev and va_pBearReclaim, "PVA reclaim ↓", shape.triangledown, location.abovebar, vars_cbear, text="PVA", textcolor=color.white, size=size.small)
plotshape(vars_on and vars_curr and va_bullReclaim,  "CVA reclaim ↑", shape.triangleup,   location.belowbar, vars_cbull, text="CVA", textcolor=color.white, size=size.small)
plotshape(vars_on and vars_curr and va_bearReclaim,  "CVA reclaim ↓", shape.triangledown, location.abovebar, vars_cbear, text="CVA", textcolor=color.white, size=size.small)

bool va_sigBull = (vars_prev and va_pBullReclaim) or (vars_curr and va_bullReclaim)
bool va_sigBear = (vars_prev and va_pBearReclaim) or (vars_curr and va_bearReclaim)
barcolor(vars_on and vars_colorcandles ? (va_sigBull ? vars_cbull : va_sigBear ? vars_cbear : na) : na, title="VARS reclaim candles")

// previous-session VA lines
var line va_lnPVAH = na
var line va_lnPVAL = na
if barstate.isfirst
    va_lnPVAH := line.new(bar_index, na, bar_index, na, xloc=xloc.bar_index, extend=extend.right, style=line.style_dashed, width=1)
    va_lnPVAL := line.new(bar_index, na, bar_index, na, xloc=xloc.bar_index, extend=extend.right, style=line.style_dashed, width=1)
if barstate.islast
    bool va_showP = vars_on and vars_showprevva and va_hasPrev and not na(va_pvah) and not na(va_pval)
    int va_px1 = na(va_prevAnchorBI) ? bar_index : va_prevAnchorBI
    if va_showP
        line.set_xy1(va_lnPVAH, va_px1, va_pvah)
        line.set_xy2(va_lnPVAH, bar_index, va_pvah)
        line.set_color(va_lnPVAH, color.new(vars_cbull, 25))
        line.set_xy1(va_lnPVAL, va_px1, va_pval)
        line.set_xy2(va_lnPVAL, bar_index, va_pval)
        line.set_color(va_lnPVAL, color.new(vars_cbear, 25))
    else
        line.set_xy1(va_lnPVAH, bar_index, na)
        line.set_xy2(va_lnPVAH, bar_index, na)
        line.set_xy1(va_lnPVAL, bar_index, na)
        line.set_xy2(va_lnPVAL, bar_index, na)

// ===========================================================================
// 13c. CVD DIVERGENCE — CLV×vol proxy + fractal regular divergence (on price)
// ===========================================================================
float cvd_rng  = high - low
float cvd_clv  = cvd_rng > 0 ? ((close - low) - (high - close)) / cvd_rng : 0.0
// v10 rev3 — delta source (see §3f-3): footprint → 1-min tick rule → candle CLV
footprint cvd_fp = na
if cvd_srcMode == SRC_FP
    cvd_fp := request.footprint(cvd_fpTicks, 70)
bool  cvd_fpOk = cvd_srcMode == SRC_FP and not na(cvd_fp)
float cvd_fpD = na
if cvd_fpOk
    cvd_fpD := footprint.delta(cvd_fp)
array<float> dp_sV = na
array<float> dp_sC = na
array<float> dp_sP = na
if cvd_srcMode == SRC_TICK or (cvd_srcMode == SRC_FP and cvd_fallback)
    [b1, b2, b5] = request.security_lower_tf(syminfo.tickerid, cvd_ltfTf, [volume, close, close[1]], ignore_invalid_timeframe = true)
    dp_sV := b1
    dp_sC := b2
    dp_sP := b5
int   cvd_ltfN = na(dp_sV) ? 0 : array.size(dp_sV)
float cvd_ltfD = na
if cvd_ltfN > 0
    cvd_ltfD := 0.0
    float cvd_sg = 0.0
    for i = 0 to cvd_ltfN - 1
        cvd_sg := f_tickCarry(array.get(dp_sC, i), array.get(dp_sP, i), cvd_sg)
        cvd_ltfD += nz(array.get(dp_sV, i)) * cvd_sg
float cvd_clvDelta = nz(volume, 0.0) * cvd_clv
int   cvd_code = cvd_fpOk and not na(cvd_fpD) ? 2 : cvd_ltfN > 0 ? 1 : 0
float cvd_dbar = cvd_code == 2 ? cvd_fpD : cvd_code == 1 ? cvd_ltfD : (cvd_srcMode == SRC_CLV or cvd_fallback ? cvd_clvDelta : 0.0)
float cvd_nCov  = math.min(300, bar_index + 1)
float cvd_covFp300  = math.sum(cvd_code == 2 ? 1.0 : 0.0, 300) / cvd_nCov
float cvd_covAny300 = math.sum(cvd_code > 0 ? 1.0 : 0.0, 300) / cvd_nCov
float cvdHist  = cvd_mode == "Periodic" ? math.sum(cvd_dbar, cvd_period) : ta.ema(cvd_dbar, cvd_period)
float cvdEMA   = ta.ema(close, 50)
bool  cvd_up   = close[cvd_n] > cvdEMA[cvd_n]   // v11: EMA of the pivot bar (was the current EMA)
bool  cvd_dn   = close[cvd_n] < cvdEMA[cvd_n]
float cvd_upPiv = ta.pivothigh(cvd_n, cvd_n)
float cvd_dnPiv = ta.pivotlow(cvd_n, cvd_n)
bool  cvd_upFrac = not na(cvd_upPiv) and cvd_up
bool  cvd_dnFrac = not na(cvd_dnPiv) and cvd_dn

// bearish regular divergence (price HH + CVD LH), on up-fractal bars
float cvd_hiLastP = ta.valuewhen(cvd_upFrac, high[cvd_n], 0)
float cvd_hiPerP  = ta.valuewhen(cvd_upFrac, high[cvd_n], 1)
float cvd_hiLastH = ta.valuewhen(cvd_upFrac, cvdHist[cvd_n], 0)
float cvd_hiPerH  = ta.valuewhen(cvd_upFrac, cvdHist[cvd_n], 1)
int   cvd_hiLastB = int(ta.valuewhen(cvd_upFrac, bar_index[cvd_n], 0))
int   cvd_hiPerB  = int(ta.valuewhen(cvd_upFrac, bar_index[cvd_n], 1))
float cvd_hiLastT = ta.valuewhen(cvd_upFrac, time[cvd_n], 0)
bool  cvd_bearDiv = cvd_on and cvd_upFrac and cvd_hiLastH > 0 and cvd_hiPerH > 0 and (cvd_hiLastB - cvd_hiPerB) < cvd_maxlen and cvd_hiLastP > cvd_hiPerP and cvd_hiLastH < cvd_hiPerH

// bullish regular divergence (price LL + CVD HL), on down-fractal bars
float cvd_loLastP = ta.valuewhen(cvd_dnFrac, low[cvd_n], 0)
float cvd_loPerP  = ta.valuewhen(cvd_dnFrac, low[cvd_n], 1)
float cvd_loLastH = ta.valuewhen(cvd_dnFrac, cvdHist[cvd_n], 0)
float cvd_loPerH  = ta.valuewhen(cvd_dnFrac, cvdHist[cvd_n], 1)
int   cvd_loLastB = int(ta.valuewhen(cvd_dnFrac, bar_index[cvd_n], 0))
int   cvd_loPerB  = int(ta.valuewhen(cvd_dnFrac, bar_index[cvd_n], 1))
float cvd_loLastT = ta.valuewhen(cvd_dnFrac, time[cvd_n], 0)
bool  cvd_bullDiv = cvd_on and cvd_dnFrac and cvd_loLastH < 0 and cvd_loPerH < 0 and (cvd_loLastB - cvd_loPerB) < cvd_maxlen and cvd_loLastP < cvd_loPerP and cvd_loLastH > cvd_loPerH

// --- v9: the same ±RD on the confluence timeframe (default M10) -----------
// Confluence only means anything when the chart TF is FASTER than the HTF.
bool cvdC_valid = cvdC_on and cvd_on and timeframe.in_seconds() < timeframe.in_seconds(cvdC_tf)
[cvdC_bullRaw, cvdC_bearRaw, cvdC_bullTRaw, cvdC_bearTRaw] = request.security(syminfo.tickerid, cvdC_tf, f_cvdDiv(cvd_mode, cvd_period, cvd_n, cvd_maxlen, cvd_srcMode, cvd_ltfTf, cvd_fallback, cvd_fpTicks), lookahead=barmerge.lookahead_off)
// hold the value of the LAST CLOSED HTF bar (read on the first chart bar of a new HTF bar)
bool cvdC_newHTF = ta.change(time(cvdC_tf)) != 0
var bool  cvdC_bullHeld = false
var bool  cvdC_bearHeld = false
var float cvdC_bullTHeld = na
var float cvdC_bearTHeld = na
if cvdC_newHTF
    cvdC_bullHeld  := nz(cvdC_bullRaw[1]) > 0.5
    cvdC_bearHeld  := nz(cvdC_bearRaw[1]) > 0.5
    cvdC_bullTHeld := cvdC_bullTRaw[1]
    cvdC_bearTHeld := cvdC_bearTRaw[1]
bool  cvdC_bull  = cvdC_valid and (cvdC_conf ? cvdC_bullHeld : nz(cvdC_bullRaw) > 0.5)
bool  cvdC_bear  = cvdC_valid and (cvdC_conf ? cvdC_bearHeld : nz(cvdC_bearRaw) > 0.5)
float cvdC_bullT = cvdC_conf ? cvdC_bullTHeld : cvdC_bullTRaw
float cvdC_bearT = cvdC_conf ? cvdC_bearTHeld : cvdC_bearTRaw
// rising edge = this HTF divergence just became visible on the chart
bool cvdC_bullEdge = cvdC_bull and not cvdC_bull[1]
bool cvdC_bearEdge = cvdC_bear and not cvdC_bear[1]
// latest HTF divergence pivot time seen so far (kept until a newer one appears)
var float cvdC_bullPivT = na
var float cvdC_bearPivT = na
if cvdC_bullEdge and not na(cvdC_bullT)
    cvdC_bullPivT := cvdC_bullT
if cvdC_bearEdge and not na(cvdC_bearT)
    cvdC_bearPivT := cvdC_bearT
// ⚑ rev2: the two frames confirm at different lags (M3 +2 bars = 6 min, M10 +2 bars =
// 20 min, plus up to one closed HTF bar). Matching by ARRIVAL time therefore misses
// real pairs — match by the PIVOT time instead, and allow a long arrival window.
int  cvdC_tolms = cvdC_tolmin * 60000
bool crdBullNow = cvd_bullDiv and not na(cvdC_bullPivT) and not na(cvd_loLastT) and math.abs(cvd_loLastT - cvdC_bullPivT) <= cvdC_tolms
bool crdBearNow = cvd_bearDiv and not na(cvdC_bearPivT) and not na(cvd_hiLastT) and math.abs(cvd_hiLastT - cvdC_bearPivT) <= cvdC_tolms

// bounded round-robin pool (protects the persistent AVR/VP objects from culling)
var array<line>  cvdLn = array.new<line>()
var array<label> cvdLb = array.new<label>()
var int cvdIdx = 0
if barstate.isfirst
    for i = 0 to cvd_maxdraw - 1
        array.push(cvdLn, line.new(bar_index, na, bar_index, na, xloc=xloc.bar_index, width=2))
        array.push(cvdLb, label.new(bar_index, na, "", xloc=xloc.bar_index, size=size.tiny, textcolor=color.white))

// v9 — remember the last drawn ±RD so a late HTF confirmation can upgrade it
var int   crdBullSlot = na
var int   crdBullX    = na
var int   crdBullBar  = na
var float crdBullPivT = na
var bool  crdBullDone = false
var int   crdBearSlot = na
var int   crdBearX    = na
var int   crdBearBar  = na
var float crdBearPivT = na
var bool  crdBearDone = false

if cvd_bearDiv or cvd_bullDiv
    line  cvd_ln = array.get(cvdLn, cvdIdx)
    label cvd_lb = array.get(cvdLb, cvdIdx)
    if cvd_bearDiv
        bool isC = crdBearNow
        line.set_xy1(cvd_ln, cvd_hiPerB, cvd_hiPerP)
        line.set_xy2(cvd_ln, cvd_hiLastB, cvd_hiLastP)
        line.set_color(cvd_ln, isC ? cvdC_cbear : cvd_cbear)
        line.set_width(cvd_ln, isC and cvdC_big ? 3 : 2)
        label.set_xy(cvd_lb, cvd_hiLastB, cvd_hiLastP)
        label.set_text(cvd_lb, cvd_showlabels ? (isC ? "-CRD" : "-RD") : "")
        label.set_style(cvd_lb, label.style_label_down)
        label.set_color(cvd_lb, isC ? cvdC_cbear : cvd_cbear)
        label.set_size(cvd_lb, isC and cvdC_big ? size.small : size.tiny)
        crdBearSlot := cvdIdx
        crdBearX    := cvd_hiLastB
        crdBearBar  := bar_index
        crdBearPivT := cvd_hiLastT
        crdBearDone := isC
    else
        bool isC = crdBullNow
        line.set_xy1(cvd_ln, cvd_loPerB, cvd_loPerP)
        line.set_xy2(cvd_ln, cvd_loLastB, cvd_loLastP)
        line.set_color(cvd_ln, isC ? cvdC_cbull : cvd_cbull)
        line.set_width(cvd_ln, isC and cvdC_big ? 3 : 2)
        label.set_xy(cvd_lb, cvd_loLastB, cvd_loLastP)
        label.set_text(cvd_lb, cvd_showlabels ? (isC ? "+CRD" : "+RD") : "")
        label.set_style(cvd_lb, label.style_label_up)
        label.set_color(cvd_lb, isC ? cvdC_cbull : cvd_cbull)
        label.set_size(cvd_lb, isC and cvdC_big ? size.small : size.tiny)
        crdBullSlot := cvdIdx
        crdBullX    := cvd_loLastB
        crdBullBar  := bar_index
        crdBullPivT := cvd_loLastT
        crdBullDone := isC
    cvdIdx := (cvdIdx + 1) % cvd_maxdraw

// v9 rev2 — late HTF confirmation: upgrade a ±RD drawn up to cvdC_arrive bars ago,
// label.get_x() guard = that pool slot has not been recycled by a newer signal.
bool crdBullLate = false
bool crdBearLate = false
if cvdC_valid and cvdC_up
    if cvdC_bullEdge and not crdBullDone and not na(crdBullSlot) and (bar_index - crdBullBar) <= cvdC_arrive and not na(cvdC_bullT) and not na(crdBullPivT) and math.abs(crdBullPivT - cvdC_bullT) <= cvdC_tolms
        label lbU = array.get(cvdLb, crdBullSlot)
        line  lnU = array.get(cvdLn, crdBullSlot)
        if label.get_x(lbU) == crdBullX
            label.set_text(lbU, cvd_showlabels ? "+CRD" : "")
            label.set_color(lbU, cvdC_cbull)
            label.set_size(lbU, cvdC_big ? size.small : size.tiny)
            line.set_color(lnU, cvdC_cbull)
            line.set_width(lnU, cvdC_big ? 3 : 2)
            crdBullDone := true
            crdBullLate := true
    if cvdC_bearEdge and not crdBearDone and not na(crdBearSlot) and (bar_index - crdBearBar) <= cvdC_arrive and not na(cvdC_bearT) and not na(crdBearPivT) and math.abs(crdBearPivT - cvdC_bearT) <= cvdC_tolms
        label lbD = array.get(cvdLb, crdBearSlot)
        line  lnD = array.get(cvdLn, crdBearSlot)
        if label.get_x(lbD) == crdBearX
            label.set_text(lbD, cvd_showlabels ? "-CRD" : "")
            label.set_color(lbD, cvdC_cbear)
            label.set_size(lbD, cvdC_big ? size.small : size.tiny)
            line.set_color(lnD, cvdC_cbear)
            line.set_width(lnD, cvdC_big ? 3 : 2)
            crdBearDone := true
            crdBearLate := true

bool crdBullSig = cvdC_valid and (crdBullNow or crdBullLate)
bool crdBearSig = cvdC_valid and (crdBearNow or crdBearLate)

// --- v9 rev2 debug: show exactly why the last pair did or did not match ----
var table crdTb = na
if barstate.isfirst
    crdTb := table.new(position.bottom_right, 2, 7, border_width=1, frame_color=color.new(color.gray,60), frame_width=1)
f_tmin(float a, float b) => (na(a) or na(b)) ? na : math.round(math.abs(a - b) / 60000.0)
if barstate.islast and cvdC_dbg
    color bgd = color.new(color.black, 20)
    string tfTxt = cvdC_valid ? cvdC_tf : "ปิด (ชาร์ตไม่เร็วกว่ากรอบยืนยัน)"
    float gapBull = f_tmin(crdBullPivT, cvdC_bullPivT)
    float gapBear = f_tmin(crdBearPivT, cvdC_bearPivT)
    table.cell(crdTb, 0, 0, "±CRD confluence", text_color=color.white, text_size=size.tiny, bgcolor=color.new(color.teal, 20))
    table.cell(crdTb, 1, 0, tfTxt, text_color=color.white, text_size=size.tiny, bgcolor=color.new(color.teal, 20))
    table.cell(crdTb, 0, 1, "เกณฑ์ (นาที / แท่ง)", text_color=color.silver, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 1, 1, str.tostring(cvdC_tolmin) + " / " + str.tostring(cvdC_arrive), text_color=color.white, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 0, 2, "+RD chart / HTF", text_color=color.silver, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 1, 2, (na(crdBullPivT) ? "—" : str.format_time(int(crdBullPivT), "dd HH:mm", syminfo.timezone)) + "  /  " + (na(cvdC_bullPivT) ? "—" : str.format_time(int(cvdC_bullPivT), "dd HH:mm", syminfo.timezone)), text_color=cvdC_cbull, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 0, 3, "  ห่างกัน (นาที)", text_color=color.silver, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 1, 3, (na(gapBull) ? "—" : str.tostring(gapBull, "#")) + (na(gapBull) ? "" : (gapBull <= cvdC_tolmin ? "  ✔ CRD" : "  ✘ เกินเกณฑ์")), text_color=color.white, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 0, 4, "-RD chart / HTF", text_color=color.silver, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 1, 4, (na(crdBearPivT) ? "—" : str.format_time(int(crdBearPivT), "dd HH:mm", syminfo.timezone)) + "  /  " + (na(cvdC_bearPivT) ? "—" : str.format_time(int(cvdC_bearPivT), "dd HH:mm", syminfo.timezone)), text_color=cvdC_cbear, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 0, 5, "  ห่างกัน (นาที)", text_color=color.silver, text_size=size.tiny, bgcolor=bgd)
    table.cell(crdTb, 1, 5, (na(gapBear) ? "—" : str.tostring(gapBear, "#")) + (na(gapBear) ? "" : (gapBear <= cvdC_tolmin ? "  ✔ CRD" : "  ✘ เกินเกณฑ์")), text_color=color.white, text_size=size.tiny, bgcolor=bgd)

if barstate.islast and cvdC_dbg
    string srcTxt = cvd_srcMode == SRC_CLV ? "CLV (ประวัติเต็ม)" : cvd_srcMode == SRC_FP ? timeframe.period + " footprint · " + cvdC_tf + " tick rule " + cvd_ltfTf : "tick rule " + cvd_ltfTf
    string covTxt = cvd_srcMode == SRC_CLV ? "—" : cvd_srcMode == SRC_FP ? "footprint " + str.tostring(math.round(cvd_covFp300 * 100)) + "% · รวมสำรอง " + str.tostring(math.round(cvd_covAny300 * 100)) + "% (300 แท่ง)" : str.tostring(math.round(cvd_covAny300 * 100)) + "% ของ 300 แท่งล่าสุด"
    table.cell(crdTb, 0, 6, "แหล่ง delta", text_color=color.silver, text_size=size.tiny, bgcolor=color.new(color.black, 20))
    table.cell(crdTb, 1, 6, srcTxt + " · " + covTxt, text_color=color.white, text_size=size.tiny, bgcolor=color.new(color.black, 20))

// ===========================================================================
// 13c-3. DELTA PROFILE (v10) — net delta per price row since the session anchor
//   Idea adapted from CVD Profiles [TradingIQ] (© Trading-IQ, TradingView open-source).
//   Rows are a fixed grid (height = ATR14 × dp_rowAtr at the anchor) stored in a map.
//   rev3: footprint rows add their own delta at the row mid price (exact per price row);
//   otherwise each 1-min sample adds its tick-rule signed volume at its close; bars without
//   either add the candle-CLV delta at hlc3 (only if fallback is on or source = CLV).
// ===========================================================================
var map<int, float> dp_map = map.new<int, float>()
var float dp_base = na
var float dp_step = na
var int   dp_anchorBI = na
float dp_atr = ta.atr(14)
if dp_on
    if va_newAnchor or na(dp_base)
        map.clear(dp_map)
        dp_base := open
        dp_step := math.max(syminfo.mintick, nz(dp_atr, high - low) * dp_rowAtr)
        dp_anchorBI := bar_index
    if cvd_code == 2
        array<volume_row> fpRows = footprint.rows(cvd_fp)
        int nR = array.size(fpRows)
        if nR > 0
            for i = 0 to nR - 1
                volume_row vr = array.get(fpRows, i)
                float rd = volume_row.delta(vr)
                if not na(rd) and rd != 0
                    float mid = (volume_row.up_price(vr) + volume_row.down_price(vr)) / 2.0
                    int key = int(math.floor((mid - dp_base) / dp_step))
                    map.put(dp_map, key, nz(map.get(dp_map, key)) + rd)
    else if cvd_code == 1
        float dp_sg = 0.0
        for i = 0 to cvd_ltfN - 1
            float sc = array.get(dp_sC, i)
            dp_sg := f_tickCarry(sc, array.get(dp_sP, i), dp_sg)
            float sv = nz(array.get(dp_sV, i)) * dp_sg
            if not na(sc) and sv != 0
                int key = int(math.floor((sc - dp_base) / dp_step))
                map.put(dp_map, key, nz(map.get(dp_map, key)) + sv)
    else if cvd_srcMode == SRC_CLV or cvd_fallback
        if cvd_clvDelta != 0
            int key = int(math.floor((hlc3 - dp_base) / dp_step))
            map.put(dp_map, key, nz(map.get(dp_map, key)) + cvd_clvDelta)

var array<box> dp_boxes = array.new<box>()
var line  dp_lnBuy  = na
var line  dp_lnSell = na
var label dp_lbBuy  = na
var label dp_lbSell = na
if barstate.isfirst
    for i = 0 to dp_maxRows - 1
        array.push(dp_boxes, box.new(bar_index, na, bar_index, na, border_width=0))
    dp_lnBuy  := line.new(bar_index, na, bar_index, na, width=2, style=line.style_dashed)
    dp_lnSell := line.new(bar_index, na, bar_index, na, width=2, style=line.style_dashed)
    dp_lbBuy  := label.new(bar_index, na, "", style=label.style_label_left, size=size.tiny)
    dp_lbSell := label.new(bar_index, na, "", style=label.style_label_left, size=size.tiny)
if barstate.islast
    for b in dp_boxes
        box.set_top(b, na)
        box.set_bottom(b, na)
    line.set_xy1(dp_lnBuy, bar_index, na)
    line.set_xy2(dp_lnBuy, bar_index, na)
    line.set_xy1(dp_lnSell, bar_index, na)
    line.set_xy2(dp_lnSell, bar_index, na)
    label.set_xy(dp_lbBuy, bar_index, na)
    label.set_xy(dp_lbSell, bar_index, na)
    int dpN = map.size(dp_map)
    if dp_on and dpN > 0 and not na(dp_step)
        array<int>   ks = map.keys(dp_map)
        array<float> vs = map.values(dp_map)
        array<float> av = array.new<float>()
        float maxAbs = 0.0
        int   iBuy = -1
        int   iSell = -1
        for i = 0 to dpN - 1
            float v = array.get(vs, i)
            array.push(av, math.abs(v))
            maxAbs := math.max(maxAbs, math.abs(v))
            if v > 0 and (iBuy < 0 or v > array.get(vs, iBuy))
                iBuy := i
            if v < 0 and (iSell < 0 or v < array.get(vs, iSell))
                iSell := i
        if maxAbs > 0
            array<int> dpOrd = array.sort_indices(av, order.descending)
            int nDraw = math.min(dpN, dp_maxRows)
            int x1 = bar_index + dp_offset
            for j = 0 to nDraw - 1
                int   idx = array.get(dpOrd, j)
                float v   = array.get(vs, idx)
                float lo  = dp_base + array.get(ks, idx) * dp_step
                int   w   = math.max(1, int(math.round(math.abs(v) / maxAbs * dp_width)))
                box bx = array.get(dp_boxes, j)
                box.set_left(bx, x1)
                box.set_right(bx, x1 + w)
                box.set_top(bx, lo + dp_step)
                box.set_bottom(bx, lo)
                box.set_bgcolor(bx, v > 0 ? dp_cPos : dp_cNeg)
                box.set_border_color(bx, color.new(color.gray, 100))
            if dp_showPoc
                int xL = na(dp_anchorBI) ? bar_index : dp_anchorBI
                if iBuy >= 0
                    float yB = dp_base + (array.get(ks, iBuy) + 0.5) * dp_step
                    line.set_xy1(dp_lnBuy, xL, yB)
                    line.set_xy2(dp_lnBuy, x1 + dp_width, yB)
                    line.set_color(dp_lnBuy, color.new(dp_cPos, 0))
                    label.set_xy(dp_lbBuy, x1 + dp_width, yB)
                    label.set_text(dp_lbBuy, "+DPOC " + str.tostring(yB, format.mintick))
                    label.set_color(dp_lbBuy, color.new(color.black, 30))
                    label.set_textcolor(dp_lbBuy, color.new(dp_cPos, 0))
                if iSell >= 0
                    float yS = dp_base + (array.get(ks, iSell) + 0.5) * dp_step
                    line.set_xy1(dp_lnSell, xL, yS)
                    line.set_xy2(dp_lnSell, x1 + dp_width, yS)
                    line.set_color(dp_lnSell, color.new(dp_cNeg, 0))
                    label.set_xy(dp_lbSell, x1 + dp_width, yS)
                    label.set_text(dp_lbSell, "−DPOC " + str.tostring(yS, format.mintick))
                    label.set_color(dp_lbSell, color.new(color.black, 30))
                    label.set_textcolor(dp_lbSell, color.new(dp_cNeg, 0))

// ===========================================================================
// 13d. ANCHORED VWAP (AVWAP) — developing polyline curves (dB from §13)
// ===========================================================================
float avw_price = avw_src
float avw_v = nz(volume, 0.0)

// --- Session AVWAP (anchored at day open); prev-day = last completed session ---
var float sess_pv = 0.0
var float sess_v  = 0.0
var array<chart.point> sess_pts = array.new<chart.point>()
var array<chart.point> prev_pts = array.new<chart.point>()
if dB
    if array.size(sess_pts) > 0
        prev_pts := array.copy(sess_pts)
    sess_pv := 0.0
    sess_v  := 0.0
    array.clear(sess_pts)
sess_pv += avw_price * avw_v
sess_v  += avw_v
float sess_vwap = sess_v > 0 ? sess_pv / sess_v : na
if not na(sess_vwap)
    array.push(sess_pts, chart.point.from_index(bar_index, sess_vwap))

// --- Swing-High AVWAP (re-anchor at each confirmed pivot high) ---
float avw_pivH = ta.pivothigh(avw_piv, avw_piv)
var float swh_pv = 0.0
var float swh_v  = 0.0
var array<chart.point> swh_pts = array.new<chart.point>()
if not na(avw_pivH)
    swh_pv := 0.0
    swh_v  := 0.0
    array.clear(swh_pts)
    for i = avw_piv to 0
        float vv = nz(volume[i], 0.0)
        swh_pv += avw_price[i] * vv
        swh_v  += vv
        float vw = swh_v > 0 ? swh_pv / swh_v : na
        if not na(vw)
            array.push(swh_pts, chart.point.from_index(bar_index - i, vw))
else
    swh_pv += avw_price * avw_v
    swh_v  += avw_v
    float vw = swh_v > 0 ? swh_pv / swh_v : na
    if not na(vw)
        array.push(swh_pts, chart.point.from_index(bar_index, vw))
float swh_vwap = swh_v > 0 ? swh_pv / swh_v : na

// --- Swing-Low AVWAP (re-anchor at each confirmed pivot low) ---
float avw_pivL = ta.pivotlow(avw_piv, avw_piv)
var float swl_pv = 0.0
var float swl_v  = 0.0
var array<chart.point> swl_pts = array.new<chart.point>()
if not na(avw_pivL)
    swl_pv := 0.0
    swl_v  := 0.0
    array.clear(swl_pts)
    for i = avw_piv to 0
        float vv = nz(volume[i], 0.0)
        swl_pv += avw_price[i] * vv
        swl_v  += vv
        float vw = swl_v > 0 ? swl_pv / swl_v : na
        if not na(vw)
            array.push(swl_pts, chart.point.from_index(bar_index - i, vw))
else
    swl_pv += avw_price * avw_v
    swl_v  += avw_v
    float vw = swl_v > 0 ? swl_pv / swl_v : na
    if not na(vw)
        array.push(swl_pts, chart.point.from_index(bar_index, vw))
float swl_vwap = swl_v > 0 ? swl_pv / swl_v : na

// --- draw polylines + labels ---
var polyline pl_sess = na
var polyline pl_prev = na
var polyline pl_swh  = na
var polyline pl_swl  = na
var label avw_lb_sess = na
var label avw_lb_swh  = na
var label avw_lb_swl  = na
if barstate.isfirst
    avw_lb_sess := label.new(bar_index, na, "", style=label.style_none, textcolor=avw_c_sess, size=size.tiny)
    avw_lb_swh  := label.new(bar_index, na, "", style=label.style_none, textcolor=avw_c_swh,  size=size.tiny)
    avw_lb_swl  := label.new(bar_index, na, "", style=label.style_none, textcolor=avw_c_swl,  size=size.tiny)
if barstate.islast
    if not na(pl_sess)
        polyline.delete(pl_sess)
        pl_sess := na
    if avw_on and avw_sess and array.size(sess_pts) > 1
        pl_sess := polyline.new(array.copy(sess_pts), xloc=xloc.bar_index, line_color=avw_c_sess, line_width=avw_w)
    if not na(pl_prev)
        polyline.delete(pl_prev)
        pl_prev := na
    if avw_on and avw_prev and array.size(prev_pts) > 1
        pl_prev := polyline.new(array.copy(prev_pts), xloc=xloc.bar_index, line_color=avw_c_prev, line_width=avw_w)
    if not na(pl_swh)
        polyline.delete(pl_swh)
        pl_swh := na
    if avw_on and avw_swh and array.size(swh_pts) > 1
        pl_swh := polyline.new(array.copy(swh_pts), xloc=xloc.bar_index, line_color=avw_c_swh, line_width=avw_w)
    if not na(pl_swl)
        polyline.delete(pl_swl)
        pl_swl := na
    if avw_on and avw_swl and array.size(swl_pts) > 1
        pl_swl := polyline.new(array.copy(swl_pts), xloc=xloc.bar_index, line_color=avw_c_swl, line_width=avw_w)
    uL(avw_lb_sess, bar_index + 3, sess_vwap, "D-VWAP",   avw_on and avw_sess and avw_lbl)
    uL(avw_lb_swh,  bar_index + 3, swh_vwap,  "SwH-VWAP", avw_on and avw_swh and avw_lbl)
    uL(avw_lb_swl,  bar_index + 3, swl_vwap,  "SwL-VWAP", avw_on and avw_swl and avw_lbl)

// ===========================================================================
// 13e. P/B/D ZONE DETECTOR — consolidation range → 3-touch validate → breakout
// ===========================================================================
float pd_atr  = ta.atr(pd_atrlen)
float pd_hh   = ta.highest(high, pd_win)
float pd_ll   = ta.lowest(low,  pd_win)
float pd_path = math.sum(ta.tr(true), pd_win)
float pd_ratio = pd_path > 0 ? (pd_hh - pd_ll) / pd_path : 1.0
bool  pd_consol = pd_atr > 0 and pd_ratio < pd_er

var int   pd_state = 0
var float pd_top = na
var float pd_bot = na
var int   pd_start = na
var int   pd_tt = 0
var int   pd_tb = 0
var bool  pd_wasTop = false
var bool  pd_wasBot = false
var array<float> pd_zTop = array.new_float()
var array<float> pd_zBot = array.new_float()
var array<box>   pd_boxes  = array.new<box>()
var array<label> pd_labels = array.new<label>()
var int  pd_idx = 0
var box   pd_dev   = na
var label pd_devlb = na

if barstate.isfirst
    for i = 0 to pd_maxz - 1
        array.push(pd_boxes,  box.new(bar_index, na, bar_index, na, border_width=1))
        array.push(pd_labels, label.new(bar_index, na, "", style=label.style_none, size=size.tiny))
    pd_dev   := box.new(bar_index, na, bar_index, na, border_width=1)
    pd_devlb := label.new(bar_index, na, "", style=label.style_none, size=size.tiny)

bool pd_newBOup = false
bool pd_newBOdn = false
if pd_on
    if pd_state == 0
        if pd_consol
            pd_state := 1
            pd_top := pd_hh
            pd_bot := pd_ll
            pd_start := bar_index - pd_win + 1
            // seed touch counts by scanning the detection window (debounced)
            float tol0 = pd_atr * pd_tol
            int tt = 0
            int tb = 0
            bool wt = false
            bool wb = false
            for i = pd_win - 1 to 0
                bool cT = high[i] >= pd_top - tol0
                bool cB = low[i]  <= pd_bot + tol0
                if cT and not wt
                    tt += 1
                if cB and not wb
                    tb += 1
                wt := cT
                wb := cB
            pd_tt := tt
            pd_tb := tb
            pd_wasTop := wt
            pd_wasBot := wb
    else
        float tol = pd_atr * pd_tol
        bool cT = high >= pd_top - tol
        bool cB = low  <= pd_bot + tol
        if cT and not pd_wasTop
            pd_tt += 1
        if cB and not pd_wasBot
            pd_tb += 1
        pd_wasTop := cT
        pd_wasBot := cB
        float brk = pd_atr * pd_brk
        bool boUp = close > pd_top + brk
        bool boDn = close < pd_bot - brk
        bool tooOld = (bar_index - pd_start) > pd_maxage
        if boUp or boDn or tooOld
            bool valid = (pd_tt + pd_tb) >= pd_mintou and pd_tt >= 1 and pd_tb >= 1
            if valid
                float h = pd_top - pd_bot
                float k = pd_atr > 0 ? h / pd_atr : na
                bool aligned = false
                if pd_align
                    float atol = pd_atr * pd_atol
                    int nz = array.size(pd_zTop)
                    if nz > 0
                        for zi = 0 to nz - 1
                            float zt = array.get(pd_zTop, zi)
                            float zb = array.get(pd_zBot, zi)
                            if (pd_bot - atol) <= zt and (pd_top + atol) >= zb
                                aligned := true
                array.push(pd_zTop, pd_top)
                array.push(pd_zBot, pd_bot)
                if array.size(pd_zTop) > pd_maxz
                    array.shift(pd_zTop)
                    array.shift(pd_zBot)
                box   zbx = array.get(pd_boxes, pd_idx)
                label zlb = array.get(pd_labels, pd_idx)
                color fc = aligned ? pd_c_al : (k <= pd_smallK ? pd_c_sm : pd_c_bg)
                string cls = k <= pd_smallK ? "SMALL→BO" : (k >= pd_bigK ? "BIG→RANGE" : "MID")
                color tc = aligned ? color.fuchsia : (k <= pd_smallK ? color.aqua : color.orange)
                box.set_left(zbx, pd_start)
                box.set_right(zbx, bar_index)
                box.set_top(zbx, pd_top)
                box.set_bottom(zbx, pd_bot)
                box.set_bgcolor(zbx, fc)
                box.set_border_color(zbx, pd_c_bd)
                label.set_xy(zlb, bar_index, pd_top)
                label.set_text(zlb, (aligned ? "★ALIGN " : "") + cls + (boUp ? " ▲" : boDn ? " ▼" : ""))
                label.set_style(zlb, label.style_label_down)
                label.set_color(zlb, color.new(color.black, 30))
                label.set_textcolor(zlb, tc)
                pd_idx := (pd_idx + 1) % pd_maxz
                pd_newBOup := boUp
                pd_newBOdn := boDn
            pd_state := 0
            pd_top := na
            pd_bot := na

// developing (forming) zone on last bar
if barstate.islast
    bool showDev = pd_on and pd_showdev and pd_state == 1 and (pd_tt + pd_tb) >= pd_mintou and not na(pd_top)
    if showDev
        box.set_left(pd_dev, pd_start)
        box.set_right(pd_dev, bar_index + 3)
        box.set_top(pd_dev, pd_top)
        box.set_bottom(pd_dev, pd_bot)
        box.set_bgcolor(pd_dev, color.new(color.gray, 90))
        box.set_border_color(pd_dev, pd_c_bd)
        label.set_xy(pd_devlb, bar_index + 3, pd_bot)
        label.set_text(pd_devlb, "forming " + str.tostring(pd_tt + pd_tb) + "t")
        label.set_style(pd_devlb, label.style_label_up)
        label.set_color(pd_devlb, color.new(color.black, 40))
        label.set_textcolor(pd_devlb, color.gray)
    else
        box.set_top(pd_dev, na)
        box.set_bottom(pd_dev, na)
        label.set_xy(pd_devlb, bar_index, na)

// ===========================================================================
// 14. ALERTS
// ===========================================================================
alertcondition(show and ta.cross(close, u_a),  "Reached AVR Up",   "Gold: reached AVR Up (avg expansion / exhaustion watch)")
alertcondition(show and ta.cross(close, d_a),  "Reached AVR Down", "Gold: reached AVR Down (avg expansion / exhaustion watch)")
alertcondition(show and ta.cross(close, u_mx), "Reached MAX Up",   "Gold: reached MAX Up (statistical outlier)")
alertcondition(show and ta.cross(close, d_mx), "Reached MAX Down", "Gold: reached MAX Down (statistical outlier)")
alertcondition(ch1_ok and dr1>0 and dr1[1]<0, "Ch1 flip UP",   "Chandelier 1 flipped long")
alertcondition(ch1_ok and dr1<0 and dr1[1]>0, "Ch1 flip DOWN", "Chandelier 1 flipped short")
alertcondition(vars_on and vars_prev and va_pBullReclaim, "VARS prev-VA bullish reclaim", "Gold VARS: bullish reclaim of previous-session value area")
alertcondition(vars_on and vars_prev and va_pBearReclaim, "VARS prev-VA bearish reclaim", "Gold VARS: bearish reclaim of previous-session value area")
alertcondition(vars_on and vars_curr and va_bullReclaim,  "VARS curr-VA bullish reclaim", "Gold VARS: bullish reclaim of current-session value area")
alertcondition(vars_on and vars_curr and va_bearReclaim,  "VARS curr-VA bearish reclaim", "Gold VARS: bearish reclaim of current-session value area")
alertcondition(cvd_bullDiv, "CVD bullish divergence (+RD)", "Gold CVD: bullish regular divergence (price LL / CVD HL)")
alertcondition(cvd_bearDiv, "CVD bearish divergence (-RD)", "Gold CVD: bearish regular divergence (price HH / CVD LH)")
alertcondition(crdBullSig, "CVD CONFLUENT bullish (+CRD)", "Gold CVD: +RD on chart TF and confluence TF together (+CRD)")
alertcondition(crdBearSig, "CVD CONFLUENT bearish (-CRD)", "Gold CVD: -RD on chart TF and confluence TF together (-CRD)")
alertcondition(pd_newBOup, "P/B/D breakout UP",   "Gold P/B/D: validated (3-touch) zone broke UP")
alertcondition(pd_newBOdn, "P/B/D breakout DOWN", "Gold P/B/D: validated (3-touch) zone broke DOWN")