XAULearn

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

Pine Script ต้นฉบับ

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

Gold_Session_Framework_v8.pine 1,413 บรรทัด 69 KB ต้นฉบับแก้ไขล่าสุด 29 สิงหาคม 2569 ดาวน์โหลด .pine

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

สารบัญส่วนของโค้ด (25)
  1. 16 1. SESSION
  2. 25 2. RANGE BASIS + AVR MULTIPLIERS
  3. 46 3. DISPLAY TOGGLES (AVR)
  4. 63 3b. STYLE — AVR lines & boxes
  5. 92 3c. CHANDELIER (multi-TF) — 3 selectable-TF instances (USER-TUNED values)
  6. 143 3d. VOLUME PROFILE (single, chart-TF, KV-MACD ㉕ principle)
  7. 175 3e. VALUE-AREA REVERSION SIGNALS (VARS) — ported from LuxAlgo (Fabio strat)
  8. 194 3f. CVD DIVERGENCE — ported from TradingFinder CVD Divergence [Periodic/EMA]
  9. 210 3g. ANCHORED VWAP (AVWAP) — Grateful-Futures technique (developing polylines)
  10. 230 3h. P/B/D ZONE DETECTOR — Patrick Nill range/breakout model (visual)
  11. 255 4. HELPER FUNCTIONS
  12. 376 5. DAILY RANGE (non-repaint: previous COMPLETED daily bar)
  13. 383 6. ASIA SESSION STATE
  14. 418 7. AVR LEVELS
  15. 446 8. AVR PERSISTENT OBJECTS
  16. 514 9. AVR UPDATE HELPERS
  17. 544 10. AVR RENDER
  18. 579 11. AVR ZONE CLASSIFICATION + TABLE
  19. 621 12. CHANDELIER — stateless ingredients via security, recursion at global scope
  20. 653 13. VOLUME PROFILE — OBJECT POOL (create-once, update-coords: realtime-safe)
  21. 852 13b. VALUE-AREA REVERSION (VARS) — per-bar session profile + reclaim engine
  22. 1088 13c. CVD DIVERGENCE — CLV×vol proxy + fractal regular divergence (on price)
  23. 1151 13d. ANCHORED VWAP (AVWAP) — developing polyline curves (dB from §13)
  24. 1259 13e. P/B/D ZONE DETECTOR — consolidation range → 3-touch validate → breakout
  25. 1397 14. ALERTS
//@version=6
// ============================================================================
// GOLD SESSION FRAMEWORK v8  (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.
//   VISUAL-only — location + confirmation map, NO trade engine. Walk-forward first.
// ============================================================================
indicator("Gold Session Framework v8", 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.new(color.rgb(166,58,30),20), "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(27,94,32),   "Up", group=g2, inline="e")
ch2_cd  = input.color(color.rgb(140,30,30),  "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(0,230,0),    "Up", group=g3, inline="e")
ch3_cd  = input.color(color.rgb(255,120,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(true, "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(false, "  • signals on CURRENT session VA",  group=gVR)
vars_showprevva = input.bool(true, "Show previous-session VA lines", group=gVR)
vars_colorcandles = input.bool(false, "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(8,153,129),  "Bullish reclaim", group=gVR, inline="vc")
vars_cbear = input.color(color.rgb(242,54,69),   "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(21, "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")

// ===========================================================================
// 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(true,  "Swing-High AVWAP (sell-trend anchor)", group=gAV)
avw_swl  = input.bool(true,  "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.orange,           "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(20, "Range window (bars)", minval=5, maxval=200, group=gPD)
pd_atrlen = input.int(14, "ATR length", minval=1, group=gPD)
pd_er     = input.float(0.40, "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.20, "Touch tolerance (×ATR)", step=0.05, minval=0.0, group=gPD)
pd_brk    = input.float(0.25, "Breakout beyond edge (×ATR)", step=0.05, minval=0.0, group=gPD)
pd_mintou = input.int(3, "Min touches to validate", minval=2, maxval=12, group=gPD)
pd_maxage = input.int(200, "Max range age (bars)", minval=20, maxval=1000, group=gPD)
pd_smallK = input.float(2.0, "SMALL zone height ≤ ×ATR (→breakout)", step=0.1, group=gPD)
pd_bigK   = input.float(4.0, "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(12, "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.new(color.fuchsia,70), "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]

// --- 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 v8", 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)

[st1, dr1] = f_trail(hh1, ll1, bd1)
[st2, dr2] = f_trail(hh2, ll2, bd2)
[st3, dr3] = f_trail(hh3, ll3, bd3)

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_linebr)
plot(ch1_on and ch1_sty=="Stepline" ? st1 : na, "Ch1 step", color=c1, linewidth=ch1_w, style=plot.style_stepline)
plot(ch1_on and ch1_sty=="Circles"  ? st1 : na, "Ch1 dots", color=c1, linewidth=ch1_w, style=plot.style_circles)
plot(ch2_on and ch2_sty=="Line"     ? st2 : na, "Ch2 line", color=c2, linewidth=ch2_w, style=plot.style_linebr)
plot(ch2_on and ch2_sty=="Stepline" ? st2 : na, "Ch2 step", color=c2, linewidth=ch2_w, style=plot.style_stepline)
plot(ch2_on and ch2_sty=="Circles"  ? st2 : na, "Ch2 dots", color=c2, linewidth=ch2_w, style=plot.style_circles)
plot(ch3_on and ch3_sty=="Line"     ? st3 : na, "Ch3 line", color=c3, linewidth=ch3_w, style=plot.style_linebr)
plot(ch3_on and ch3_sty=="Stepline" ? st3 : na, "Ch3 step", color=c3, linewidth=ch3_w, style=plot.style_stepline)
plot(ch3_on and ch3_sty=="Circles"  ? st3 : na, "Ch3 dots", color=c3, linewidth=ch3_w, style=plot.style_circles)

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
float cvd_dbar = nz(volume, 0.0) * cvd_clv
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
bool  cvd_dn   = close[cvd_n] < cvdEMA
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))
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))
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

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

if cvd_bearDiv or cvd_bullDiv
    line  cvd_ln = array.get(cvdLn, cvdIdx)
    label cvd_lb = array.get(cvdLb, cvdIdx)
    if cvd_bearDiv
        line.set_xy1(cvd_ln, cvd_hiPerB, cvd_hiPerP)
        line.set_xy2(cvd_ln, cvd_hiLastB, cvd_hiLastP)
        line.set_color(cvd_ln, cvd_cbear)
        label.set_xy(cvd_lb, cvd_hiLastB, cvd_hiLastP)
        label.set_text(cvd_lb, cvd_showlabels ? "-RD" : "")
        label.set_style(cvd_lb, label.style_label_down)
        label.set_color(cvd_lb, cvd_cbear)
    else
        line.set_xy1(cvd_ln, cvd_loPerB, cvd_loPerP)
        line.set_xy2(cvd_ln, cvd_loLastB, cvd_loLastP)
        line.set_color(cvd_ln, cvd_cbull)
        label.set_xy(cvd_lb, cvd_loLastB, cvd_loLastP)
        label.set_text(cvd_lb, cvd_showlabels ? "+RD" : "")
        label.set_style(cvd_lb, label.style_label_up)
        label.set_color(cvd_lb, cvd_cbull)
    cvdIdx := (cvdIdx + 1) % cvd_maxdraw

// ===========================================================================
// 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(dr1>0 and dr1[1]<0, "Ch1 flip UP",   "Chandelier 1 flipped long")
alertcondition(dr1<0 and dr1[1]>0, "Ch1 flip DOWN", "Chandelier 1 flipped short")
alertcondition(vars_prev and va_pBullReclaim, "VARS prev-VA bullish reclaim", "Gold VARS: bullish reclaim of previous-session value area")
alertcondition(vars_prev and va_pBearReclaim, "VARS prev-VA bearish reclaim", "Gold VARS: bearish reclaim of previous-session value area")
alertcondition(vars_curr and va_bullReclaim,  "VARS curr-VA bullish reclaim", "Gold VARS: bullish reclaim of current-session value area")
alertcondition(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(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")