پرش به محتوا
کریپکس

ICT · اندیکاتور

Crypex ICT Time: کیل‌زون، CBDR، Judas و نقدینگی با وقت تهران

اندیکاتور زمان و نقدینگی سبک ICT برای TradingView، متاتریدر ۵ و متاتریدر ۴: کیل‌زون‌های آسیا، لندن و نیویورک، Silver Bullet، CBDR با پروجکشن، Midnight Open و 08:30، PDH/PDL و PWH/PWL، تشخیص Raid و Run، Judas Swing، Power of 3 و ساعت زنده به وقت تهران.

پیش‌نمایش

اندیکاتور Crypex ICT Time روی طلا با رنج آسیا، لندن، نیویورک، CBDR و Raid PDL
طلا ۱۵ دقیقه‌ای: رنج آسیا، کیل‌زون لندن و نیویورک AM با ساعت تهران، CBDR با پروجکشن ±۱× و ±۲×، Midnight Open و 08:30، PDH/PDL و برداشت نقدینگی PDL (Raid) و پنل وضعیت.رندر از خروجی کدطلا (آتی GC=F)، ۱۵ دقیقه، ۲۸ تا ۳۰ اکتبر ۲۰۲۶
اندیکاتور Crypex ICT Time با Raid PDL و Raid PDH روی طلا
روز دیگر روی طلا: جارو شدن PDL در لندن و PDH در نیویورک، رنج‌های آسیا و CBDR، نوارهای Silver Bullet و پروجکشن CBDR تا هدف ‎+2×.رندر از خروجی کدطلا (آتی GC=F)، ۱۵ دقیقه، ۲۳ تا ۲۷ اکتبر ۲۰۲۶

این تصویرها اسکرین‌شات از TradingView یا متاتریدر نیستند؛ با اجرای همین کد روی داده‌ی واقعی بازار ساخته و رسم شده‌اند. ظاهر دقیق در پلتفرم شما (فونت، رنگ‌ها و اندازه) کمی متفاوت است.

ict-time-crypex.pine · Pine Script v6 · ۹۸۰ خط
// Crypex ICT Time — اندیکاتور زمان و نقدینگی سبک ICT (کیل‌زون، سشن‌ها، CBDR، Judas، PO3، نقدینگی، IPDA، SMT) با وقت تهران
// © Crypex.org — All rights reserved. | حق نشر محفوظ برای Crypex.org
// License: personal, non-commercial use only. Redistribution or resale without written permission is prohibited.
// مجوز: استفاده شخصی و غیرتجاری. بازنشر یا فروش مجدد بدون اجازه‌ی کتبی ممنوع است.
// Educational tool, not investment advice. | ابزار آموزشی است، توصیه‌ی سرمایه‌گذاری نیست.

//@version=6
// Crypex ICT Time (1 of 2) — sessions & liquidity, light on the chart:
//  TIME      Killzones (Asia/London/NY AM/London Close/NY Lunch/NY PM), Silver Bullet windows, Macro times, CBDR / Flout / Asian range + SD projections,
//            Midnight / 08:30 / 09:30 / Weekly open, Power of 3 (AMD), Judas Swing, live clock New York + Tehran.
//  LIQUIDITY PDH/PDL, PWH/PWL, PMH/PML, session highs/lows, EQH/EQL, raids (turtle soup) vs runs, IPDA 20/40/60, SMT divergence.
// Pair it with "Crypex ICT Models" (structure, FVG, blocks, entry models). All state changes happen on CONFIRMED bars only.
// Session times are New York time by default (DST handled by TradingView). Best on intraday charts 1m–1H.
indicator('Crypex ICT Time', 'Crypex ICT Time', overlay = true, max_bars_back = 500, max_labels_count = 500, max_lines_count = 500, max_boxes_count = 500)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// CONSTANTS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BRAND_GREEN = #0BA31C
BRAND_DARK  = #1E2229
C_BULL      = #089981
C_BEAR      = #F23645
C_BLUE      = #2962FF
C_ORANGE    = #FF9800
C_PURPLE    = #9C27B0
C_YELLOW    = #FFD600
C_GRAY      = #787B86
C_CYAN      = #00BCD4
C_PINK      = #E91E63
C_LIME      = #8BC34A

G1 = '1 · General & time zones'
G2 = '2 · Killzones / sessions'
G3 = '3 · Silver Bullet & Macros'
G4 = '4 · CBDR / Flout / Asian range + SD projections'
G5 = '5 · Time anchors'
G6 = '6 · Liquidity & raids'
G7 = '7 · Judas Swing & Power of 3'
G12 = '8 · IPDA & SMT'
G14 = '9 · Panel & watermark'

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// INPUTS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
tzIn      = 'America/New_York'   // ICT sessions are always defined in New York time (daylight saving handled by TradingView)
tzDisp    = input.string('Asia/Tehran', 'Show times on chart in', options = ['America/New_York', 'Asia/Tehran', 'UTC', 'Europe/London'], group = G1, tooltip = 'ساعت نمایش داده‌شده روی برچسب‌ها و پنل. پیش‌فرض: وقت تهران.')
tzAnc     = 'America/New_York'   // day / week / month boundary and anchors (Midnight Open, 08:30, 09:30)
histDays  = input.int(2, 'Days of sessions to keep on chart', minval = 1, maxval = 10, group = G1)
lsz       = input.string(size.small, 'Label size', options = [size.tiny, size.small, size.normal, size.large], group = G1)
showSessLbl = input.bool(true, 'Session name + time labels', group = G1)
lblGap   = input.float(3.0, 'Right-side labels: min vertical gap (% of recent range)', minval = 0.5, maxval = 15, step = 0.5, group = G1, tooltip = 'برچسب‌های سمت راست (PDH، پروجکشن‌ها، Midnight Open و ...) به‌صورت خودکار از هم فاصله می‌گیرند تا روی هم نیفتند. عدد بزرگ‌تر = فاصله‌ی بیشتر.')
lblPad   = input.int(3, 'Right-side labels: offset from last bar (bars)', minval = 1, maxval = 30, group = G1, tooltip = 'برای دیده‌شدن برچسب‌ها، حاشیه‌ی راست چارت را بازتر کنید (Settings > Scales > Right margin حدود ۱۵ تا ۲۰).')

sessStyle = input.string('Border + light fill', 'Session box style', options = ['Filled', 'Border + light fill', 'Border only', 'Off'], group = G2, tooltip = 'سبک باکس سشن‌ها. «Border only» تمیزترین حالت است و کندل‌ها را نمی‌پوشاند. «Off» فقط برچسب را نگه می‌دارد.')
asiaOn  = input.bool(true, 'Asian range', group = G2, inline = 'as')
asiaR   = input.session('2000-0000', '', group = G2, inline = 'as')
asiaC   = input.color(#9C27B0, '', group = G2, inline = 'as')
lonOn   = input.bool(true, 'London killzone', group = G2, inline = 'lo')
lonR    = input.session('0200-0500', '', group = G2, inline = 'lo')
lonC    = input.color(#2962FF, '', group = G2, inline = 'lo')
nyaOn   = input.bool(true, 'New York AM killzone', group = G2, inline = 'na')
nyaR    = input.session('0700-1000', '', group = G2, inline = 'na')
nyaC    = input.color(#089981, '', group = G2, inline = 'na')
lcOn    = input.bool(false, 'London Close killzone', group = G2, inline = 'lc')
lcR     = input.session('1000-1200', '', group = G2, inline = 'lc')
lcC     = input.color(#FF9800, '', group = G2, inline = 'lc')
nylOn   = input.bool(false, 'New York Lunch', group = G2, inline = 'nl')
nylR    = input.session('1200-1300', '', group = G2, inline = 'nl')
nylC    = input.color(#787B86, '', group = G2, inline = 'nl')
nypOn   = input.bool(false, 'New York PM killzone', group = G2, inline = 'np')
nypR    = input.session('1330-1600', '', group = G2, inline = 'np')
nypC    = input.color(#E91E63, '', group = G2, inline = 'np')
sessLvlOn = input.bool(false, 'Turn finished session highs/lows into liquidity levels', group = G2)

sbOn   = input.bool(true, 'Silver Bullet windows', group = G3, inline = 'sb')
sbC    = input.color(#FFD600, '', group = G3, inline = 'sb')
sb1R   = input.session('0300-0400', 'SB 1 (London)', group = G3)
sb2R   = input.session('1000-1100', 'SB 2 (NY AM)', group = G3)
sb3R   = input.session('1400-1500', 'SB 3 (NY PM)', group = G3)
mcOn   = input.bool(false, 'Macro windows (ICT macro times)', group = G3, inline = 'mc', tooltip = 'پنجره‌های کوتاه ICT مثل 02:33-03:00، 04:03-04:30، 08:50-09:10، 09:50-10:10، 10:50-11:10، 11:50-12:10، 13:10-13:40، 14:50-15:10، 15:15-15:45')
mcC    = input.color(#FF6D00, '', group = G3, inline = 'mc')

cbdrOn   = input.bool(true, 'CBDR', group = G4, inline = 'cb')
cbdrR    = input.session('1400-2000', '', group = G4, inline = 'cb')
cbdrC    = input.color(#00BCD4, '', group = G4, inline = 'cb')
floutOn  = input.bool(false, 'Flout', group = G4, inline = 'fl', tooltip = 'Flout: برداشت رایج = رنج تجمیع‌شده‌ی بعدازظهر + آسیا (قابل تنظیم). وقتی CBDR خیلی بزرگ باشد پایه‌ی پروجکشن می‌شود.')
floutR   = input.session('1400-0000', '', group = G4, inline = 'fl')
floutC   = input.color(#8BC34A, '', group = G4, inline = 'fl')
projCbdr = input.bool(true, 'Project CBDR deviations', group = G4)
projAsia = input.bool(false, 'Project Asian range deviations', group = G4)
projFlout = input.bool(false, 'Project Flout deviations', group = G4)
sdCount  = input.int(2, 'Number of deviations each side', minval = 1, maxval = 4, group = G4)
sdStep   = input.float(1.0, 'Deviation step (× range)', minval = 0.25, step = 0.25, group = G4, tooltip = 'انحراف‌ها ضرایب ساده‌ی ارتفاع رنج‌اند (۱×، ۲×، ۳×، ۴×) نه انحراف معیار آماری.')
filtPips = input.float(40.0, 'Forex: skip projection if range wider than (pips)', minval = 0, group = G4, tooltip = '۰ = خاموش. فقط روی نمادهای فارکس اعمال می‌شود. ۱ پیپ = ۱۰ تیک.')
projUpC  = input.color(#089981, 'Projection up colour', group = G4)
projDnC  = input.color(#F23645, 'Projection down colour', group = G4)

ancMidOn = input.bool(true, 'Midnight Open (00:00)', group = G5, inline = 'a1')
ancMidC  = input.color(#FFD600, '', group = G5, inline = 'a1')
anc830On = input.bool(true, '08:30 Open', group = G5, inline = 'a2')
anc830C  = input.color(#FF9800, '', group = G5, inline = 'a2')
anc930On = input.bool(false, '09:30 Open', group = G5, inline = 'a3')
anc930C  = input.color(#E91E63, '', group = G5, inline = 'a3')
ancWkOn  = input.bool(false, 'Weekly Open (Sunday evening for forex/gold, Monday 00:00 for crypto)', group = G5, inline = 'a4')
ancWkC   = input.color(#00BCD4, '', group = G5, inline = 'a4')

pdOn     = input.bool(true, 'PDH / PDL', group = G6, inline = 'l1')
pdC      = input.color(#2962FF, '', group = G6, inline = 'l1')
pwOn     = input.bool(true, 'PWH / PWL', group = G6, inline = 'l2', tooltip = 'هفته از یکشنبه‌شب (بازشدن بازار) شروع می‌شود و کندل‌های یکشنبه را شامل می‌شود. روزِ PDH/PDL کندل‌های یکشنبه‌ی فارکس و طلا را شامل نمی‌شود.')
pwC      = input.color(#9C27B0, '', group = G6, inline = 'l2')
pmOn     = input.bool(false, 'PMH / PML', group = G6, inline = 'l3')
pmC      = input.color(#E91E63, '', group = G6, inline = 'l3')
sessLC   = input.color(#787B86, 'Session levels colour', group = G6)
eqOn     = input.bool(true, 'EQH / EQL (equal highs / lows)', group = G6, inline = 'l4')
eqC      = input.color(#FF6D00, '', group = G6, inline = 'l4')
eqTol    = input.float(0.10, 'EQ tolerance (× ATR)', minval = 0.01, step = 0.01, group = G6)
raidLbl  = input.bool(true, 'Mark raids (sweep + close back = turtle soup) and runs', group = G6)
pendBars = input.int(3, 'Bars to wait before a break counts as a Run', minval = 1, maxval = 20, group = G6, tooltip = 'مهلت تکلیف شکست سطح نقدینگی بر حسب کندل: اگر در این مدت قیمت به داخل سطح برنگردد، «Run» ثبت می‌شود. روی ۱ دقیقه یعنی ۳ دقیقه و روی ۱۵ دقیقه یعنی ۴۵ دقیقه؛ متناسب با تایم‌فریم تنظیم کنید.')
keepSwept = input.int(30, 'Keep swept levels for (bars)', minval = 0, maxval = 500, group = G6)
maxLiq   = input.int(20, 'Max liquidity levels on chart', minval = 10, maxval = 120, group = G6)

jdOn     = input.bool(true, 'Judas Swing (Asian range sweep + close back)', group = G7, inline = 'jd')
jdR      = input.session('0000-0500', '', group = G7, inline = 'jd', tooltip = 'پنجره‌ی زمانی جست‌وجوی جاداس: جارو شدن سقف یا کف رنج آسیا و بستن کندل برگشت به داخل رنج.')
po3On    = input.bool(true, 'Power of 3 (AMD): bias only after a sweep of Midnight Open and a close back through it', group = G7)
po3Fresh = input.int(12, 'PO3: reclaim of the Midnight Open must follow the sweep within (bars)', minval = 2, maxval = 100, group = G7)
po3AR    = input.session('1900-0100', 'Accumulation', group = G7)
po3MR    = input.session('0100-0700', 'Manipulation', group = G7)
po3DR    = input.session('0700-1300', 'Distribution', group = G7)

swLen    = input.int(5, 'Swing length', minval = 2, maxval = 30, group = G12)
ipdaOn   = input.bool(false, 'IPDA look-back highs / lows', group = G12)
ipda1    = input.int(20, 'IPDA 1 (days)', minval = 5, maxval = 120, group = G12)
ipda2    = input.int(40, 'IPDA 2 (days)', minval = 5, maxval = 200, group = G12)
ipda3    = input.int(60, 'IPDA 3 (days)', minval = 5, maxval = 250, group = G12, tooltip = 'روزهای معاملاتی (کندل روزانه). منابع در واحد اختلاف دارند؛ این‌جا روز کاری.')
smtOn    = input.bool(false, 'SMT divergence (experimental)', group = G12, tooltip = 'سیگنال SMT بعد از تأیید پیوت (swLen کندل دیرتر) صادر می‌شود، ولی برچسب روی همان کندل پیوت می‌نشیند. این تأخیر ذاتی روش پیوت است.')
smtSym   = input.symbol('TVC:DXY', 'Correlated symbol', group = G12, tooltip = 'طلا/فارکس: TVC:DXY (معکوس). بیت‌کوین: BINANCE:ETHUSDT (مستقیم). شاخص‌ها: نماد هم‌بسته.')
smtInv   = input.bool(true, 'Inverse correlation (DXY vs gold / EURUSD)', group = G12)

showPanel = input.bool(true, 'Show info panel', group = G14)
panelPos  = input.string(position.top_right, 'Panel position', options = [position.top_right, position.top_left, position.bottom_right, position.middle_right], group = G14)
showWatermark = input.bool(true, 'Show crypex.org watermark', group = G14)
wmPos     = input.string(position.bottom_center, 'Watermark position', options = [position.bottom_left, position.bottom_center, position.bottom_right, position.middle_center, position.top_center, position.top_left], group = G14)
wmGap     = input.float(5.0, 'Gap from left (%)', minval = 0, maxval = 30, step = 0.5, group = G14, tooltip = 'Bottom-left only: empty space so the watermark sits beside the TradingView logo.')
wmSizeStr = input.string('normal', 'Watermark size', options = ['small', 'normal', 'large', 'huge'], group = G14)
wmTransp  = input.int(0, 'Watermark transparency', minval = 0, maxval = 95, group = G14)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// TYPES
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
type Sess
    string name
    string rng
    bool   on
    color  col
    bool   proj    = false
    bool   lvl     = false
    bool   kz      = false
    bool   active  = false
    int    sBar    = 0
    int    sTime   = 0
    float  hi      = na
    float  lo      = na
    int    hiBar   = 0
    int    loBar   = 0
    box    bx      = na
    label  lb      = na
    float  fHi     = na
    float  fLo     = na
    int    fHiBar  = 0
    int    fLoBar  = 0
    array<line>  pl
    array<label> pll
    array<float> plp
type Liq
    float  price
    string name
    int    dir          // +1 buy-side (above price), -1 sell-side (below price)
    int    kind         // 1 session, 2 PD, 3 PW, 4 PM, 5 EQ
    int    bar
    bool   swept   = false
    int    sweptBar = 0
    bool   pend    = false
    int    hitBar  = 0
    float  hitExt  = na
    label  mk      = na
    line   ln
    label  lb
type St
    // swings (EQH / EQL, SMT)
    float  swHi     = na
    int    swHiBar  = 0
    float  pSwHi    = na
    float  swLo     = na
    int    swLoBar  = 0
    float  pSwLo    = na
    // raids
    int    raidDir  = 0
    int    raidBar  = -9999
    float  raidPx   = na
    string raidName = ''
    bool   raidRej  = false
    int    raidSc   = 0
    label  raidMk   = na
    float  raidPen  = 0.0
    int    raidScBar = -1
    // day / week / month
    float  curDH    = na
    float  curDL    = na
    int    curDHB   = 0
    int    curDLB   = 0
    float  curWH    = na
    float  curWL    = na
    int    curWHB   = 0
    int    curWLB   = 0
    float  curMH    = na
    float  curML    = na
    int    curMHB   = 0
    int    curMLB   = 0
    // Judas / anchors / PO3
    bool   jdDone   = false
    int    jdPot    = 0
    float  moPx     = na
    int    po3DnBar = -9999
    int    po3UpBar = -9999
    float  po3DnExt = na
    float  po3UpExt = na
    float  po3Ext   = na
    float  jdExt    = na
    int    po3Bias  = 0
    // SMT
    float  phM      = na
    float  phS      = na
    float  plM      = na
    float  plS      = na
    int    smtDir   = 0
    int    smtBar   = -9999
    // events of this bar
    int    evRaid   = 0
    int    evRun    = 0
    int    evJudas  = 0
    int    evSmt    = 0

type Anc
    string name
    bool   on
    color  col
    line   ln = na
    label  lb = na
    float  px = na

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// GLOBAL STATE
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var St S = St.new()
var Sess sAsia  = Sess.new(name = 'Asia',      rng = asiaR,  on = asiaOn, col = asiaC,  proj = projAsia,  lvl = true,  kz = false, pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sLon   = Sess.new(name = 'London KZ', rng = lonR,   on = lonOn,   col = lonC,   proj = false, lvl = true, kz = true,  pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sNya   = Sess.new(name = 'NY AM KZ',  rng = nyaR,   on = nyaOn,   col = nyaC,   proj = false, lvl = true, kz = true,  pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sLc    = Sess.new(name = 'London Close KZ', rng = lcR, on = lcOn, col = lcC,   proj = false, lvl = true, kz = true,  pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sNyl   = Sess.new(name = 'NY Lunch',  rng = nylR,   on = nylOn,   col = nylC,   proj = false, lvl = false, kz = false, pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sNyp   = Sess.new(name = 'NY PM KZ',  rng = nypR,   on = nypOn,   col = nypC,   proj = false, lvl = true, kz = true,  pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sCbdr  = Sess.new(name = 'CBDR',      rng = cbdrR,  on = cbdrOn,  col = cbdrC,  proj = projCbdr, lvl = false, kz = false, pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var Sess sFlout = Sess.new(name = 'Flout',     rng = floutR, on = floutOn, col = floutC, proj = projFlout, lvl = false, kz = false, pl = array.new<line>(), pll = array.new<label>(), plp = array.new<float>())
var array<Sess>  allS     = array.from(sAsia, sLon, sNya, sLc, sNyl, sNyp, sCbdr, sFlout)
var array<box>   sessBx   = array.new<box>()
var array<label> sessLb   = array.new<label>()
var array<Liq>   liqs     = array.new<Liq>()
var array<label> evLbls   = array.new<label>()   // raid / judas / smt markers
var array<label> mkLbls   = array.new<label>()   // Break? / Run / Raid markers
var array<line>  ancHist  = array.new<line>()
var array<string> macroList = array.from('0233-0300', '0403-0430', '0850-0910', '0950-1010', '1050-1110', '1150-1210', '1310-1340', '1450-1510', '1515-1545')
var Anc aMid = Anc.new(name = 'Midnight Open', on = ancMidOn, col = ancMidC)
var Anc a830 = Anc.new(name = '08:30 Open', on = anc830On, col = anc830C)
var Anc a930 = Anc.new(name = '09:30 Open', on = anc930On, col = anc930C)
var Anc aWk  = Anc.new(name = 'Weekly Open', on = ancWkOn, col = ancWkC)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// BASIC SERIES
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
float atr   = ta.atr(14)
float pivH  = ta.pivothigh(high, swLen, swLen)
float pivL  = ta.pivotlow(low, swLen, swLen)
bool  intra = timeframe.isintraday and timeframe.in_seconds() <= 14400
float rng150 = ta.highest(high, 150) - ta.lowest(low, 150)
bool  drawBox = sessStyle != 'Off'
int   fillT   = sessStyle == 'Filled' ? 85 : sessStyle == 'Border only' ? 100 : 94
int   lineT   = sessStyle == 'Border only' ? 10 : 35

[x2h, x2l] = request.security(smtSym, timeframe.period, [smtInv ? -low : high, smtInv ? -high : low], ignore_invalid_symbol = true)
[ip1h, ip1l, ip2h, ip2l, ip3h, ip3l] = request.security(syminfo.tickerid, 'D', [ta.highest(high, ipda1)[1], ta.lowest(low, ipda1)[1], ta.highest(high, ipda2)[1], ta.lowest(low, ipda2)[1], ta.highest(high, ipda3)[1], ta.lowest(low, ipda3)[1]], lookahead = barmerge.lookahead_on, ignore_invalid_symbol = true)
f_in(string r) => not na(time(timeframe.period, r, tzIn))

bool inAsia = intra and f_in(asiaR)
bool inLon  = intra and f_in(lonR)
bool inNya  = intra and f_in(nyaR)
bool inLc   = intra and f_in(lcR)
bool inNyl  = intra and f_in(nylR)
bool inNyp  = intra and f_in(nypR)
bool inCbdr = intra and f_in(cbdrR)
bool inFlout = intra and f_in(floutR)
bool inSb1  = intra and f_in(sb1R)
bool inSb2  = intra and f_in(sb2R)
bool inSb3  = intra and f_in(sb3R)
bool inJd   = intra and f_in(jdR)
bool inPoA  = intra and f_in(po3AR)
bool inPoM  = intra and f_in(po3MR)
bool inPoD  = intra and f_in(po3DR)
f_inMacro() =>
    bool res = false
    for r in macroList
        if f_in(r)
            res := true
    res
bool inMacro = intra and f_inMacro()
bool sbAny   = sbOn and (inSb1 or inSb2 or inSb3)
bool kzAny   = (lonOn and inLon) or (nyaOn and inNya) or (lcOn and inLc) or (nypOn and inNyp) or sbAny

bgcolor(sbAny ? color.new(sbC, 94) : na, title = 'Silver Bullet window')
bgcolor(mcOn and inMacro ? color.new(mcC, 92) : na, title = 'Macro window')

bool newDay = bar_index > 0 and dayofmonth(time, tzAnc) != dayofmonth(time[1], tzAnc) and not (syminfo.type != 'crypto' and dayofweek(time, tzAnc) == dayofweek.sunday)
int  wdNow  = syminfo.type == 'crypto' ? (dayofweek(time, tzAnc) + 5) % 7 : dayofweek(time, tzAnc) - 1
int  wdPrev = syminfo.type == 'crypto' ? (dayofweek(time[1], tzAnc) + 5) % 7 : dayofweek(time[1], tzAnc) - 1
// trading week: starts Sunday evening for forex / gold / indices (Sun = 0 ... Sat = 6), Monday 00:00 for crypto (Mon = 0 ... Sun = 6)
bool newWk  = bar_index > 0 and wdNow < wdPrev
bool newMth = bar_index > 0 and month(time, tzAnc) != month(time[1], tzAnc)
int  minDay = hour(time, tzAnc) * 60 + minute(time, tzAnc)
int  minDay1 = hour(time[1], tzAnc) * 60 + minute(time[1], tzAnc)
bool dayChg = bar_index > 0 and dayofmonth(time, tzAnc) != dayofmonth(time[1], tzAnc)
bool sunSkip = syminfo.type != 'crypto' and dayofweek(time, tzAnc) == dayofweek.sunday

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// HELPERS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_hm(int t, string tz) => str.format_time(t, 'HH:mm', tz)
f_startMin(string r) => int(str.tonumber(str.substring(r, 0, 2))) * 60 + int(str.tonumber(str.substring(r, 2, 4)))
f_range(int t, string r, string tz) =>
    int a = f_startMin(r)
    int b = int(str.tonumber(str.substring(r, 5, 7))) * 60 + int(str.tonumber(str.substring(r, 7, 9)))
    int d = b - a
    if d <= 0
        d += 1440
    f_hm(t, tz) + '–' + f_hm(t + d * 60000, tz)
f_px(float p) => str.tostring(p, format.mintick)
f_trimBox(array<box> a, int cap) =>
    while array.size(a) > cap
        box.delete(array.shift(a))
f_trimLbl(array<label> a, int cap) =>
    while array.size(a) > cap
        label.delete(array.shift(a))
f_trimLine(array<line> a, int cap) =>
    while array.size(a) > cap
        line.delete(array.shift(a))
f_mark(int x, float y, string txt, color c, string st) =>
    // stack markers that sit close together so they never overlap
    int lvl = 0
    for l in evLbls
        if math.abs(label.get_x(l) - x) <= 8
            lvl += 1
    float off = nz(atr, 0) * 0.5 * math.min(lvl, 1) * (st == label.style_label_down ? 1 : -1)
    label m = label.new(x, y + off, txt, style = st, color = color.new(c, 100), textcolor = c, size = lsz)
    array.push(evLbls, m)
    f_trimLbl(evLbls, 24)
f_markL(int x, float y, string txt, color c, string st) =>
    label m = label.new(x, y, txt, style = st, color = color.new(c, 100), textcolor = c, size = lsz)
    array.push(mkLbls, m)
    f_trimLbl(mkLbls, maxLiq + 10)
    m

f_isPip() => syminfo.type == 'forex'
f_cl(int x) => math.max(x, bar_index - 1400)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// LIQUIDITY LEVELS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_addLiq(float px, string nm, int d, int kind, int ob, color c) =>
    if not na(px)
        int i = array.size(liqs)
        while i > 0
            i -= 1
            Liq q = array.get(liqs, i)
            if q.name == nm
                array.remove(liqs, i)
                line.delete(q.ln)
                label.delete(q.lb)
                if q.pend and not na(q.mk)
                    label.delete(q.mk)
        // make room: the oldest level that is NOT waiting for its verdict goes first
        while array.size(liqs) >= maxLiq
            int idx = -1
            int j = 0
            while j < array.size(liqs) and idx < 0
                Liq cq = array.get(liqs, j)
                if not cq.pend
                    idx := j
                j += 1
            if idx < 0
                break
            Liq q0 = array.get(liqs, idx)
            array.remove(liqs, idx)
            line.delete(q0.ln)
            label.delete(q0.lb)
        line   l = line.new(f_cl(ob), px, bar_index + lblPad - 1, px, color = c, style = line.style_dashed, width = 1)
        label  t = label.new(bar_index + 2, px, nm + ' ' + f_px(px), style = label.style_label_left, color = color.new(c, 100), textcolor = c, size = lsz)
        array.push(liqs, Liq.new(price = px, name = nm, dir = d, kind = kind, bar = bar_index, ln = l, lb = t))

f_kindScore(int k) => k == 3 or k == 4 ? 4 : k == 2 ? 3 : k == 1 ? 2 : 1

f_confirmRaid(Liq q, float ext) =>
    int sc = f_kindScore(q.kind)
    float pen = math.abs(ext - q.price)
    // one raid per bar: the highest-ranked level wins; equal rank -> the deeper penetration wins
    bool sameBar = S.raidScBar == bar_index
    bool better = not sameBar or sc > S.raidSc or (sc == S.raidSc and pen > S.raidPen)
    if not na(q.mk)
        label.delete(q.mk)
        q.mk := na
    if better
        // the marker of the level that just lost is removed, so only the winning raid stays on the chart
        if sameBar and not na(S.raidMk)
            label.delete(S.raidMk)
            S.raidMk := na
        S.raidDir := q.dir == 1 ? -1 : 1
        S.raidBar := bar_index
        S.raidPx := ext
        S.raidName := q.name
        S.raidRej := true
        S.raidSc := sc
        S.raidPen := pen
        S.raidScBar := bar_index
        S.evRaid := S.raidDir
        if raidLbl
            S.raidMk := f_markL(bar_index, q.dir == 1 ? high : low, 'Raid ' + q.name, C_ORANGE, q.dir == 1 ? label.style_label_down : label.style_label_up)

f_chkLiq() =>
    int i = array.size(liqs)
    while i > 0
        i -= 1
        Liq q = array.get(liqs, i)
        if not q.swept
            if bar_index > q.bar
                bool hit = q.dir == 1 ? high > q.price : low < q.price
                if hit
                    q.swept := true
                    q.sweptBar := bar_index
                    line.set_x2(q.ln, bar_index)
                    line.set_style(q.ln, line.style_dotted)
                    label.set_text(q.lb, '✓')
                    label.set_x(q.lb, bar_index)
                    bool rej = q.dir == 1 ? close < q.price : close > q.price
                    float ext = q.dir == 1 ? high : low
                    if rej
                        f_confirmRaid(q, ext)
                    else
                        // closed beyond the level: PENDING for pendBars bars. Back through the level = RAID, still beyond after that = confirmed RUN
                        q.pend := true
                        q.hitBar := bar_index
                        q.hitExt := ext
                        if raidLbl
                            q.mk := f_markL(bar_index, ext, 'Break? ' + q.name, C_GRAY, q.dir == 1 ? label.style_label_down : label.style_label_up)
        else
            if q.pend
                q.hitExt := q.dir == 1 ? math.max(q.hitExt, high) : math.min(q.hitExt, low)
                bool back = q.dir == 1 ? close < q.price : close > q.price
                if back
                    q.pend := false
                    f_confirmRaid(q, q.hitExt)
                else if bar_index - q.hitBar >= pendBars
                    q.pend := false
                    S.evRun := q.dir
                    if not na(q.mk)
                        label.set_text(q.mk, 'Run ' + q.name)
            if not q.pend and bar_index - q.sweptBar > keepSwept
                array.remove(liqs, i)
                line.delete(q.ln)
                label.delete(q.lb)
    // stop-loss extreme of a confirmed raid keeps updating only for a limited window
    if S.raidRej and bar_index - S.raidBar <= 60
        if S.raidDir == 1
            S.raidPx := math.min(nz(S.raidPx, low), low)
        if S.raidDir == -1
            S.raidPx := math.max(nz(S.raidPx, high), high)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SESSIONS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
method upd(Sess s, bool inS) =>
    bool ended = false
    if s.on
        if inS
            if not s.active
                s.active := true
                s.sBar := bar_index
                s.sTime := time
                s.hi := high
                s.lo := low
                s.hiBar := bar_index
                s.loBar := bar_index
                if drawBox
                    s.bx := box.new(left = bar_index, top = high, right = bar_index, bottom = low, border_color = color.new(s.col, lineT), border_width = 1, bgcolor = color.new(s.col, fillT))
                    array.push(sessBx, s.bx)
                    f_trimBox(sessBx, histDays * 8)
                if showSessLbl
                    s.lb := label.new(bar_index, high, s.name + '\n' + f_range(s.sTime, s.rng, tzDisp), style = label.style_label_down, color = color.new(s.col, 100), textcolor = s.col, size = lsz)
                    array.push(sessLb, s.lb)
                    f_trimLbl(sessLb, histDays * 8)
            else
                if high > s.hi
                    s.hi := high
                    s.hiBar := bar_index
                if low < s.lo
                    s.lo := low
                    s.loBar := bar_index
                if drawBox
                    box.set_top(s.bx, s.hi)
                    box.set_bottom(s.bx, s.lo)
                    box.set_right(s.bx, bar_index)
                if showSessLbl
                    label.set_xy(s.lb, math.round((s.sBar + bar_index) / 2), s.hi)
        else
            if s.active
                s.active := false
                ended := true
                s.fHi := s.hi
                s.fLo := s.lo
                s.fHiBar := s.hiBar
                s.fLoBar := s.loBar
                if drawBox
                    box.set_right(s.bx, bar_index - 1)
    ended
f_projLbl(Sess s, float p, string txt, color c) =>
    array.push(s.pll, label.new(bar_index + 3, p, txt + ' ' + f_px(p), style = label.style_label_left, color = color.new(c, 100), textcolor = c, size = lsz))
    array.push(s.plp, p)

f_proj(Sess s) =>
    // old projection lines of this range
    while array.size(s.pl) > 0
        line.delete(array.shift(s.pl))
    while array.size(s.pll) > 0
        label.delete(array.shift(s.pll))
    array.clear(s.plp)
    float r = s.fHi - s.fLo
    float pips = r / (syminfo.mintick * 10)
    bool tooWide = f_isPip() and filtPips > 0 and pips > filtPips
    if r > 0
        int t0 = f_cl(s.sBar)
        int t1 = bar_index + lblPad - 1
        array.push(s.pl, line.new(t0, s.fHi, t1, s.fHi, color = color.new(s.col, 20), style = line.style_solid, width = 1))
        array.push(s.pl, line.new(t0, s.fLo, t1, s.fLo, color = color.new(s.col, 20), style = line.style_solid, width = 1))
        f_projLbl(s, s.fHi, s.name + ' H', s.col)
        f_projLbl(s, s.fLo, s.name + ' L' + (tooWide ? ' (wide ' + str.tostring(pips, '#') + 'p)' : ''), s.col)
        if not tooWide
            for k = 1 to sdCount
                float m = k * sdStep
                float up = s.fHi + m * r
                float dn = s.fLo - m * r
                array.push(s.pl, line.new(t0, up, t1, up, color = projUpC, style = line.style_dashed, width = 1))
                array.push(s.pl, line.new(t0, dn, t1, dn, color = projDnC, style = line.style_dashed, width = 1))
                f_projLbl(s, up, s.name + ' +' + str.tostring(m, '#.##') + 'x', projUpC)
                f_projLbl(s, dn, s.name + ' -' + str.tostring(m, '#.##') + 'x', projDnC)

f_sessEnd(Sess s) =>
    if s.lvl and sessLvlOn
        f_addLiq(s.fHi, s.name + ' H', 1, 1, s.fHiBar, sessLC)
        f_addLiq(s.fLo, s.name + ' L', -1, 1, s.fLoBar, sessLC)
    if s.proj
        f_proj(s)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// DAY / WEEK / MONTH LEVELS & ANCHORS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_periods() =>
    if newDay
        if not na(S.curDH)
            if pdOn
                f_addLiq(S.curDH, 'PDH', 1, 2, S.curDHB, pdC)
                f_addLiq(S.curDL, 'PDL', -1, 2, S.curDLB, pdC)
        S.curDH := high
        S.curDL := low
        S.curDHB := bar_index
        S.curDLB := bar_index
        S.jdDone := false
        S.jdPot := 0
        S.po3DnBar := -9999
        S.po3UpBar := -9999
        S.po3DnExt := na
        S.po3UpExt := na
        S.po3Bias := 0
    else if not sunSkip
        if na(S.curDH) or high > S.curDH
            S.curDH := high
            S.curDHB := bar_index
        if na(S.curDL) or low < S.curDL
            S.curDL := low
            S.curDLB := bar_index
    if newWk
        if not na(S.curWH)
            if pwOn
                f_addLiq(S.curWH, 'PWH', 1, 3, S.curWHB, pwC)
                f_addLiq(S.curWL, 'PWL', -1, 3, S.curWLB, pwC)
        S.curWH := high
        S.curWL := low
        S.curWHB := bar_index
        S.curWLB := bar_index
    else
        if na(S.curWH) or high > S.curWH
            S.curWH := high
            S.curWHB := bar_index
        if na(S.curWL) or low < S.curWL
            S.curWL := low
            S.curWLB := bar_index
    if newMth
        if not na(S.curMH)
            if pmOn
                f_addLiq(S.curMH, 'PMH', 1, 4, S.curMHB, pmC)
                f_addLiq(S.curML, 'PML', -1, 4, S.curMLB, pmC)
        S.curMH := high
        S.curML := low
        S.curMHB := bar_index
        S.curMLB := bar_index
    else
        if na(S.curMH) or high > S.curMH
            S.curMH := high
            S.curMHB := bar_index
        if na(S.curML) or low < S.curML
            S.curML := low
            S.curMLB := bar_index
method fire(Anc a, bool exact) =>
    if a.on
        if not na(a.ln)
            line.set_extend(a.ln, extend.none)
            line.set_x2(a.ln, bar_index)
            array.push(ancHist, a.ln)
            f_trimLine(ancHist, histDays * 4 + 4)
        a.px := open
        a.ln := line.new(bar_index, open, bar_index + lblPad - 1, open, color = a.col, style = line.style_solid, width = 1)
        if showSessLbl
            if not na(a.lb)
                label.delete(a.lb)
            a.lb := label.new(bar_index + 1, open, a.name + (exact ? '' : ' ≈') + ' ' + f_px(open), style = label.style_label_left, color = color.new(a.col, 100), textcolor = a.col, size = lsz)
f_anchors() =>
    if intra
        if dayChg and not (syminfo.type != 'crypto' and dayofweek(time, tzAnc) == dayofweek.sunday)
            aMid.fire(minDay == 0)
            S.moPx := open
        if minDay >= 510 and (minDay1 < 510 or dayChg)
            a830.fire(minDay == 510)
        if minDay >= 570 and (minDay1 < 570 or dayChg)
            a930.fire(minDay == 570)
        if newWk
            aWk.fire(syminfo.type == 'crypto' ? minDay == 0 : minDay == 1020)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// JUDAS, PO3, SMT, EQH/EQL
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_judas() =>
    // a pending Judas expires when the search window ends
    if S.jdPot != 0 and not inJd
        S.jdPot := 0
    if jdOn and inJd and not S.jdDone and not na(sAsia.fHi)
        if S.jdPot == 0
            if high > sAsia.fHi and close < sAsia.fHi
                S.jdPot := -1
                S.jdExt := high
                f_mark(bar_index, high, 'Judas? ▼', C_YELLOW, label.style_label_down)
            else if low < sAsia.fLo and close > sAsia.fLo
                S.jdPot := 1
                S.jdExt := low
                f_mark(bar_index, low, 'Judas? ▲', C_YELLOW, label.style_label_up)
        else
            // cancelled when price closes beyond the sweep extreme (a real breakout, not a Judas)
            if S.jdPot == -1 and close > S.jdExt
                S.jdPot := 0
            if S.jdPot == 1 and close < S.jdExt
                S.jdPot := 0
            if S.jdPot == -1 and not na(S.moPx) and close < S.moPx
                S.jdDone := true
                S.evJudas := -1
                f_mark(bar_index, high, 'Judas ▼ ✔', C_YELLOW, label.style_label_down)
            if S.jdPot == 1 and not na(S.moPx) and close > S.moPx
                S.jdDone := true
                S.evJudas := 1
                f_mark(bar_index, low, 'Judas ▲ ✔', C_YELLOW, label.style_label_up)

f_po3() =>
    if po3On and (inPoM or inPoD) and not na(S.moPx)
        float thr = 0.25 * nz(atr, 0)
        bool dnX = low < S.moPx - thr
        bool upX = high > S.moPx + thr
        // 0) invalidation: a close beyond the manipulation extreme withdraws the bias; a NEW sweep + reclaim is then required
        if S.po3Bias == 1 and close < S.po3Ext
            S.po3Bias := 0
            S.po3DnBar := -9999
        if S.po3Bias == -1 and close > S.po3Ext
            S.po3Bias := 0
            S.po3UpBar := -9999
        // 1) confirmation uses only the state of PREVIOUS candles: the reclaim candle itself is never the sweep candle.
        //    A candle that crossed both sides gives no usable order inside the bar, so it can neither sweep nor confirm.
        if S.po3Bias == 0 and not (dnX and upX)
            if close > S.moPx and S.po3DnBar > S.po3UpBar and bar_index - S.po3DnBar <= po3Fresh
                S.po3Bias := 1
                S.po3Ext := S.po3DnExt
            else if close < S.moPx and S.po3UpBar > S.po3DnBar and bar_index - S.po3UpBar <= po3Fresh
                S.po3Bias := -1
                S.po3Ext := S.po3UpExt
        // 2) then this candle's own excursion is recorded
        if dnX and not upX
            S.po3DnBar := bar_index
            S.po3DnExt := math.min(nz(S.po3DnExt, low), low)
        if upX and not dnX
            S.po3UpBar := bar_index
            S.po3UpExt := math.max(nz(S.po3UpExt, high), high)

f_smtCheck(bool isHigh, bool go) =>
    // pivot candle is `swLen` bars back; secondary values taken around the same bar
    if go and smtOn and not na(x2h) and not na(x2l)
        if isHigh
            float m = high[swLen]
            float s2 = math.max(x2h[swLen - 1], math.max(x2h[swLen], x2h[swLen + 1]))
            if not na(S.phM) and not na(S.phS)
                bool mainHH = m > S.phM
                bool secHH = s2 > S.phS
                if (mainHH and not secHH) or (not mainHH and secHH and m < S.phM)
                    S.smtDir := -1
                    S.smtBar := bar_index
                    S.evSmt := -1
                    f_mark(bar_index - swLen, m, 'SMT ▼', C_PINK, label.style_label_down)
            S.phM := m
            S.phS := s2
        else
            float m = low[swLen]
            // x2l holds the "lowness" of the secondary (already inverted if needed): smaller = lower
            float s2l = math.min(x2l[swLen - 1], math.min(x2l[swLen], x2l[swLen + 1]))
            if not na(S.plM) and not na(S.plS)
                bool mainLL = m < S.plM
                bool secLL = s2l < S.plS
                if (mainLL and not secLL) or (not mainLL and secLL and m > S.plM)
                    S.smtDir := 1
                    S.smtBar := bar_index
                    S.evSmt := 1
                    f_mark(bar_index - swLen, m, 'SMT ▲', C_PINK, label.style_label_up)
            S.plM := m
            S.plS := s2l

f_pivots() =>
    float ph = pivH
    float pl = pivL
    if not na(ph)
        S.pSwHi := S.swHi
        S.swHi := ph
        S.swHiBar := bar_index - swLen
        if eqOn and not na(S.pSwHi) and math.abs(ph - S.pSwHi) <= eqTol * atr
            f_addLiq(math.max(ph, S.pSwHi), 'EQH', 1, 5, S.swHiBar, eqC)
    if not na(pl)
        S.pSwLo := S.swLo
        S.swLo := pl
        S.swLoBar := bar_index - swLen
        if eqOn and not na(S.pSwLo) and math.abs(pl - S.pSwLo) <= eqTol * atr
            f_addLiq(math.min(pl, S.pSwLo), 'EQL', -1, 5, S.swLoBar, eqC)
    f_smtCheck(true, not na(ph))
    f_smtCheck(false, not na(pl))

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// MAIN (confirmed bars only)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
S.evRaid := 0
S.evRun := 0
S.evJudas := 0
S.evSmt := 0

if barstate.isconfirmed
    if intra
        bool e1 = sAsia.upd(inAsia)
        bool e2 = sLon.upd(inLon)
        bool e3 = sNya.upd(inNya)
        bool e4 = sLc.upd(inLc)
        bool e5 = sNyl.upd(inNyl)
        bool e6 = sNyp.upd(inNyp)
        bool e7 = sCbdr.upd(inCbdr)
        bool e8 = sFlout.upd(inFlout)
        if e1
            f_sessEnd(sAsia)
        if e2
            f_sessEnd(sLon)
        if e3
            f_sessEnd(sNya)
        if e4
            f_sessEnd(sLc)
        if e5
            f_sessEnd(sNyl)
        if e6
            f_sessEnd(sNyp)
        if e7
            f_sessEnd(sCbdr)
        if e8
            f_sessEnd(sFlout)
    f_periods()
    f_anchors()
    f_pivots()
    f_chkLiq()
    f_judas()
    f_po3()

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// IPDA LOOK-BACK LINES + LIQUIDITY LABELS (last bar)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var array<line> ipL = array.new<line>()
var array<label> ipT = array.new<label>()
var array<float> ipP = array.new<float>()
f_ipda(float p, string t, color c) =>
    if not na(p)
        array.push(ipL, line.new(bar_index - 60, p, bar_index + lblPad - 1, p, color = color.new(c, 30), style = line.style_dotted, width = 1))
        array.push(ipT, label.new(bar_index + 8, p, t + ' ' + f_px(p), style = label.style_label_left, color = color.new(c, 100), textcolor = c, size = lsz))
        array.push(ipP, p)

f_place(array<label> ls, array<float> ps) =>
    int n = array.size(ps)
    if n > 0
        float gap = nz(rng150, 0) * lblGap / 100
        array<int> ord = array.sort_indices(ps, order.descending)
        float prevY = na
        for k = 0 to n - 1
            int ix = array.get(ord, k)
            float y = array.get(ps, ix)
            if not na(prevY) and prevY - y < gap
                y := prevY - gap
            label.set_xy(array.get(ls, ix), bar_index + lblPad, y)
            prevY := y

// every right-side label is re-stacked on the last bar: sorted by price, never closer than the minimum gap
f_spread() =>
    array<label> ls = array.new<label>()
    array<float> ps = array.new<float>()
    int xe = bar_index + lblPad - 1
    for q in liqs
        if not q.swept
            line.set_x2(q.ln, xe)
            array.push(ls, q.lb)
            array.push(ps, q.price)
    for a in array.from(aMid, a830, a930, aWk)
        if a.on and not na(a.lb) and not na(a.px)
            line.set_x2(a.ln, xe)
            array.push(ls, a.lb)
            array.push(ps, a.px)
    for sx in allS
        for ln in sx.pl
            line.set_x2(ln, xe)
        int m = array.size(sx.pll)
        if m > 0
            for k = 0 to m - 1
                array.push(ls, array.get(sx.pll, k))
                array.push(ps, array.get(sx.plp, k))
    int m2 = array.size(ipT)
    if m2 > 0
        for k = 0 to m2 - 1
            array.push(ls, array.get(ipT, k))
            array.push(ps, array.get(ipP, k))
    f_place(ls, ps)


if barstate.islast
    for q in liqs
        if q.swept
            label.set_x(q.lb, q.sweptBar)
    while array.size(ipL) > 0
        line.delete(array.shift(ipL))
    while array.size(ipT) > 0
        label.delete(array.shift(ipT))
    array.clear(ipP)
    if ipdaOn
        f_ipda(ip1h, str.tostring(ipda1) + 'D H', C_BLUE)
        f_ipda(ip1l, str.tostring(ipda1) + 'D L', C_BLUE)
        f_ipda(ip2h, str.tostring(ipda2) + 'D H', C_PURPLE)
        f_ipda(ip2l, str.tostring(ipda2) + 'D L', C_PURPLE)
        f_ipda(ip3h, str.tostring(ipda3) + 'D H', C_ORANGE)
        f_ipda(ip3l, str.tostring(ipda3) + 'D L', C_ORANGE)
        if not na(ip3h) and not na(ip3l)
            f_ipda((ip3h + ip3l) / 2, str.tostring(ipda3) + 'D EQ', C_GRAY)
    f_spread()

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// PANEL (clock New York + Tehran, active session, next session, day bias)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var table pnl = na

f_nextStart(string r) =>
    int sm = f_startMin(r)
    int hh = math.floor(sm / 60)
    // today's date in New York at the session start; later days are base + 24 h, then the hour is corrected if a daylight-saving change moved it
    int base = timestamp(tzIn, year(timenow, tzIn), month(timenow, tzIn), dayofmonth(timenow, tzIn), hh, sm % 60)
    int res = na
    bool found = false
    for d = 0 to 8
        if not found
            int cand = base + d * 86400000
            int dh = hh - hour(cand, tzIn)
            if dh > 12
                dh -= 24
            if dh < -12
                dh += 24
            cand += dh * 3600000
            int dow = dayofweek(cand, tzIn)
            bool closed = syminfo.type != 'crypto' and (dow == dayofweek.saturday or (dow == dayofweek.sunday and sm < 1020) or (dow == dayofweek.friday and sm >= 1020))
            if cand > timenow and not closed
                res := cand
                found := true
    res

f_row(table t, int r, string k, string v, color vc) =>
    table.cell(t, 0, r, k, text_color = color.new(color.gray, 0), text_size = size.small, text_halign = text.align_left, bgcolor = color.new(BRAND_DARK, 8))
    table.cell(t, 1, r, v, text_color = vc, text_size = size.small, text_halign = text.align_left, bgcolor = color.new(BRAND_DARK, 8))
f_dirTxt(int d) => d == 1 ? 'Bullish ▲' : d == -1 ? 'Bearish ▼' : '—'
f_dirCol(int d) => d == 1 ? C_BULL : d == -1 ? C_BEAR : color.gray

if barstate.islast and showPanel
    if na(pnl)
        pnl := table.new(panelPos, 2, 10, border_width = 1, border_color = color.new(color.gray, 70), frame_width = 1, frame_color = color.new(BRAND_GREEN, 20))
    string actNm = 'Outside sessions'
    color  actCol = color.gray
    for s in allS
        if s.on and s.active and s.name != 'CBDR' and s.name != 'Flout'
            actNm := s.name
            actCol := s.col
    if sbAny
        actNm += ' + Silver Bullet'
    if mcOn and inMacro
        actNm += ' + Macro'
    int bestT = na
    string bestN = '—'
    for s in allS
        if s.on and s.name != 'CBDR' and s.name != 'Flout'
            int c = f_nextStart(s.rng)
            if not na(c) and (na(bestT) or c < bestT)
                bestT := c
                bestN := s.name
    int bestD = na(bestT) ? 0 : math.round((bestT - timenow) / 60000)
    string nextTxt = na(bestT) ? '—' : bestN + ' in ' + str.tostring(math.floor(bestD / 60)) + 'h ' + str.tostring(bestD % 60) + 'm (' + f_hm(bestT, tzDisp) + ')'
    string tehr = str.format_time(timenow, 'HH:mm', 'Asia/Tehran')
    string nyc  = str.format_time(timenow, 'HH:mm', 'America/New_York')
    table.cell(pnl, 0, 0, 'Crypex ICT Time', text_color = BRAND_GREEN, text_size = size.normal, text_halign = text.align_left, text_formatting = text.format_bold, bgcolor = color.new(BRAND_DARK, 8))
    table.cell(pnl, 1, 0, intra ? '' : '⚠ use ≤ 4H', text_color = C_ORANGE, text_size = size.small, text_halign = text.align_left, bgcolor = color.new(BRAND_DARK, 8))
    f_row(pnl, 1, 'Tehran / New York', tehr + '  |  ' + nyc, color.white)
    f_row(pnl, 2, 'Now', actNm, actCol)
    f_row(pnl, 3, 'Next', nextTxt, color.white)
    string moTxt = na(S.moPx) ? '—' : (close > S.moPx ? 'above ' : 'below ') + f_px(S.moPx)
    f_row(pnl, 4, 'Midnight Open', moTxt, na(S.moPx) ? color.gray : (close > S.moPx ? C_BULL : C_BEAR))
    string po3Ph = inPoA ? 'Accumulation' : inPoM ? 'Manipulation' : inPoD ? 'Distribution' : '—'
    f_row(pnl, 5, 'Power of 3', po3Ph + (S.po3Bias != 0 ? '  → ' + (S.po3Bias == 1 ? 'buy' : 'sell') + ' day' : ''), S.po3Bias == 0 ? color.white : f_dirCol(S.po3Bias))
    string rdTxt = S.raidDir == 0 ? '—' : S.raidName + (S.raidRej ? ' (raid)' : ' (run)') + ' · ' + str.tostring(bar_index - S.raidBar) + ' bars ago'
    f_row(pnl, 6, 'Last liquidity taken', rdTxt, f_dirCol(S.raidDir))
    f_row(pnl, 7, 'SMT', S.smtDir == 0 ? '—' : f_dirTxt(S.smtDir) + ' · ' + str.tostring(bar_index - S.smtBar) + ' bars ago', f_dirCol(S.smtDir))
    f_row(pnl, 8, 'Asia range', na(sAsia.fHi) ? '—' : f_px(sAsia.fLo) + ' – ' + f_px(sAsia.fHi), color.white)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ALERTS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alertcondition(lonOn and inLon and not inLon[1], 'London killzone started', 'Crypex ICT: London killzone started')
alertcondition(nyaOn and inNya and not inNya[1], 'New York AM killzone started', 'Crypex ICT: New York AM killzone started')
alertcondition(nypOn and inNyp and not inNyp[1], 'New York PM killzone started', 'Crypex ICT: New York PM killzone started')
alertcondition(sbAny and not sbAny[1], 'Silver Bullet window started', 'Crypex ICT: Silver Bullet window started')
alertcondition(S.evRaid == -1, 'Buy-side liquidity raided', 'Crypex ICT: buy-side liquidity swept (bearish expectation)')
alertcondition(S.evRaid == 1, 'Sell-side liquidity raided', 'Crypex ICT: sell-side liquidity swept (bullish expectation)')
alertcondition(S.evRun != 0, 'Liquidity run (continuation)', 'Crypex ICT: liquidity level broken and held (run, not a raid)')
alertcondition(S.evJudas != 0, 'Judas Swing (confirmed)', 'Crypex ICT: Judas swing confirmed (close through Midnight Open)')
alertcondition(S.evSmt != 0, 'SMT divergence', 'Crypex ICT: SMT divergence')

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// WATERMARK
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var table wm = na
if showWatermark and barstate.islast
    if na(wm)
        wm := table.new(wmPos, 3, 1, border_width = 0, frame_width = 0)
    string wmSize = wmSizeStr == 'small' ? size.small : wmSizeStr == 'normal' ? size.normal : wmSizeStr == 'large' ? size.large : size.huge
    float spacerW = wmPos == position.bottom_left ? wmGap : 0.0
    bool bgLight = (color.r(chart.bg_color) * 299 + color.g(chart.bg_color) * 587 + color.b(chart.bg_color) * 114) / 1000 > 128
    color orgCol = bgLight ? BRAND_DARK : color.white
    table.cell(wm, 0, 0, '', width = spacerW, text_size = wmSize)
    table.cell(wm, 1, 0, 'crypex', text_color = color.new(BRAND_GREEN, wmTransp), text_size = wmSize, text_halign = text.align_right, text_valign = text.align_bottom, text_formatting = text.format_bold)
    table.cell(wm, 2, 0, '.org', text_color = color.new(orgCol, wmTransp), text_size = wmSize, text_halign = text.align_left, text_valign = text.align_bottom, text_formatting = text.format_bold)
ict-time-crypex.mq5 · MQL5 · ۱٬۵۹۴ خط
//+------------------------------------------------------------------+
//| Crypex ICT Time (MT5)                                          |
//| Sessions & killzones, Silver Bullet, CBDR + projections,         |
//| Midnight / 08:30 / 09:30 / Weekly open, PDH/PDL/PWH/PWL, EQH/EQL,|
//| Raid / Break / Run, Judas Swing, Power of 3, IPDA, SMT, panel    |
//| with Tehran + New York clocks                                    |
//| © Crypex.org — All rights reserved. | حق نشر محفوظ برای Crypex.org |
//| License: personal, non-commercial use only. Redistribution or    |
//| resale without written permission is prohibited.                 |
//| مجوز: استفاده شخصی و غیرتجاری. بازنشر یا فروش مجدد بدون          |
//| اجازه‌ی کتبی ممنوع است.                                           |
//| Educational tool, not investment advice.                         |
//+------------------------------------------------------------------+
#property copyright "Crypex.org"
#property link      "https://crypex.org"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

enum ENUM_TM_DST    { TM_DST_US, TM_DST_EU, TM_DST_NONE };
enum ENUM_TM_ZONE   { TM_Z_TEHRAN, TM_Z_NY, TM_Z_UTC, TM_Z_LONDON };
enum ENUM_TM_MARKET { TM_M_AUTO, TM_M_FOREX, TM_M_METALS, TM_M_CRYPTO };
enum ENUM_TM_STYLE  { TM_ST_FILLED, TM_ST_LIGHT, TM_ST_BORDER, TM_ST_OFF };
enum ENUM_TM_CORNER { TM_C_AUTO, TM_C_TOPRIGHT, TM_C_TOPLEFT, TM_C_BOTTOMRIGHT, TM_C_BOTTOMLEFT };

input group "General & time zones"
input int             InpBrokerGmt    = 2;               // Broker server time: GMT offset in WINTER (e.g. 2 for GMT+2 / GMT+3 brokers)
input ENUM_TM_DST     InpBrokerDst    = TM_DST_US;       // Broker daylight-saving rule (US = GMT+2/+3 New-York-close brokers)
input ENUM_TM_ZONE    InpTzDisp       = TM_Z_TEHRAN;     // Show times on chart in
input ENUM_TM_MARKET  InpMarket       = TM_M_AUTO;       // Market type (Auto = detect from the symbol name)
input int             InpHistDays     = 2;               // Days of sessions to keep on chart
input int             InpFont         = 9;               // Label font size
input bool            InpShowSessLbl  = true;            // Session name + time labels
input double          InpLblGap       = 3.0;             // Right-side labels: min vertical gap (% of recent range)
input int             InpLblPad       = 3;               // Right-side labels: offset from last bar (bars)
input int             InpRightMargin  = 15;              // Chart right margin (% of the chart, 0 = leave the chart alone)
input int             InpShadeDays    = 10;              // Days of Silver Bullet / Macro shading to draw
input group "Killzones / sessions"
input ENUM_TM_STYLE   InpSessStyle    = TM_ST_LIGHT;     // Session box style
input bool            InpAsiaOn       = true;            // Asian range
input string          InpAsiaR        = "2000-0000";     //   Asian range (New York time)
input color           InpAsiaC        = C'156,39,176';
input bool            InpLonOn        = true;            // London killzone
input string          InpLonR         = "0200-0500";     //   London (New York time)
input color           InpLonC         = C'41,98,255';
input bool            InpNyaOn        = true;            // New York AM killzone
input string          InpNyaR         = "0700-1000";     //   NY AM (New York time)
input color           InpNyaC         = C'8,153,129';
input bool            InpLcOn         = false;           // London Close killzone
input string          InpLcR          = "1000-1200";     //   London Close
input color           InpLcC          = C'255,152,0';
input bool            InpNylOn        = false;           // New York Lunch
input string          InpNylR         = "1200-1300";     //   NY Lunch
input color           InpNylC         = C'120,123,134';
input bool            InpNypOn        = false;           // New York PM killzone
input string          InpNypR         = "1330-1600";     //   NY PM
input color           InpNypC         = C'233,30,99';
input bool            InpSessLvlOn    = false;           // Turn finished session highs/lows into liquidity levels
input group "Silver Bullet & Macros"
input bool            InpSbOn         = true;            // Silver Bullet windows
input color           InpSbC          = C'255,214,0';
input string          InpSb1R         = "0300-0400";     //   SB 1 (London)
input string          InpSb2R         = "1000-1100";     //   SB 2 (NY AM)
input string          InpSb3R         = "1400-1500";     //   SB 3 (NY PM)
input bool            InpMcOn         = false;           // Macro windows (ICT macro times)
input color           InpMcC          = C'255,109,0';
input group "CBDR / Flout / Asian range + SD projections"
input bool            InpCbdrOn       = true;            // CBDR
input string          InpCbdrR        = "1400-2000";     //   CBDR (New York time)
input color           InpCbdrC        = C'0,188,212';
input bool            InpFloutOn      = false;           // Flout
input string          InpFloutR       = "1400-0000";     //   Flout
input color           InpFloutC       = C'139,195,74';
input bool            InpProjCbdr     = true;            // Project CBDR deviations
input bool            InpProjAsia     = false;           // Project Asian range deviations
input bool            InpProjFlout    = false;           // Project Flout deviations
input int             InpSdCount      = 2;               // Number of deviations each side (1-4)
input double          InpSdStep       = 1.0;             // Deviation step (x range)
input double          InpFiltPips     = 40.0;            // Forex: skip projection if range wider than (pips, 0 = off)
input color           InpProjUpC      = C'8,153,129';
input color           InpProjDnC      = C'242,54,69';
input group "Time anchors"
input bool            InpAncMidOn     = true;            // Midnight Open (00:00)
input color           InpAncMidC      = C'255,214,0';
input bool            InpAnc830On     = true;            // 08:30 Open
input color           InpAnc830C      = C'255,152,0';
input bool            InpAnc930On     = false;           // 09:30 Open
input color           InpAnc930C      = C'233,30,99';
input bool            InpAncWkOn      = false;           // Weekly Open
input color           InpAncWkC       = C'0,188,212';
input group "Liquidity & raids"
input bool            InpPdOn         = true;            // PDH / PDL
input color           InpPdC          = C'41,98,255';
input bool            InpPwOn         = true;            // PWH / PWL
input color           InpPwC          = C'156,39,176';
input bool            InpPmOn         = false;           // PMH / PML
input color           InpPmC          = C'233,30,99';
input color           InpSessLC       = C'120,123,134';
input bool            InpEqOn         = true;            // EQH / EQL (equal highs / lows)
input color           InpEqC          = C'255,109,0';
input double          InpEqTol        = 0.10;            // EQ tolerance (x ATR)
input bool            InpRaidLbl      = true;            // Mark raids and runs
input int             InpPendBars     = 3;               // Bars to wait before a break counts as a Run
input int             InpKeepSwept    = 30;              // Keep swept levels for (bars)
input int             InpMaxLiq       = 20;              // Max liquidity levels on chart
input group "Judas Swing & Power of 3"
input bool            InpJdOn         = true;            // Judas Swing
input string          InpJdR          = "0000-0500";     //   Judas search window (New York time)
input bool            InpPo3On        = true;            // Power of 3 (AMD)
input int             InpPo3Fresh     = 12;              // PO3: reclaim must follow the sweep within (bars)
input string          InpPo3AR        = "1900-0100";     //   Accumulation
input string          InpPo3MR        = "0100-0700";     //   Manipulation
input string          InpPo3DR        = "0700-1300";     //   Distribution
input group "IPDA & SMT"
input int             InpSwLen        = 5;               // Swing length
input bool            InpIpdaOn       = false;           // IPDA look-back highs / lows
input int             InpIpda1        = 20;              // IPDA 1 (days)
input int             InpIpda2        = 40;              // IPDA 2 (days)
input int             InpIpda3        = 60;              // IPDA 3 (days)
input bool            InpSmtOn        = false;           // SMT divergence (experimental)
input string          InpSmtSym       = "DXY";           // Correlated symbol (as named by your broker)
input bool            InpSmtInv       = true;            // Inverse correlation (DXY vs gold / EURUSD)
input group "Panel & watermark"
input bool            InpShowPanel    = true;            // Show info panel
input ENUM_TM_CORNER  InpPanelPos     = TM_C_AUTO;       // Panel position (Auto = the emptiest corner of the visible chart)
input bool            InpShowWm       = true;            // Show crypex.org watermark
input int             InpWmSize       = 18;
input int             InpWmGap        = 60;              // Gap from left (px), clears the chart logo
input int             InpWmBottom     = 14;              // Gap from bottom (px)
input group "Alerts"
input bool            InpAlertPopup   = true;
input bool            InpAlertPush    = false;
input bool            InpAlertMail    = false;
input group "Calculation"
input int             InpMaxBars      = 6000;            // Bars used for calculation

const string PFX = "CRX_TIME_";
const double TM_NA = DBL_MAX;
const color  C_BULL = C'8,153,129', C_BEAR = C'242,54,69', C_BLUE = C'41,98,255', C_ORANGE = C'255,152,0', C_PURPLE = C'156,39,176';
const color  C_YELLOW = C'255,214,0', C_GRAY = C'120,123,134', C_CYAN = C'0,188,212', C_PINK = C'233,30,99';
const color  BRAND_GREEN = C'11,163,28', BRAND_DARK = C'30,34,41';

struct TmSess { string name; int a; int b; bool on; color col; bool proj; bool lvl; bool kz; bool active; int sBar; datetime sTime;
                double hi; double lo; int hiBar; int loBar; double fHi; double fLo; int fHiBar; int fLoBar; int boxId; };
struct TmBox  { int id; int s; int i0; int i1; double hi; double lo; datetime sTime; };
struct TmLiq  { double price; string name; int dir; int kind; int bar; int ob; bool swept; int sweptBar; bool pend; int hitBar; double hitExt; int mk; };
struct TmMark { int id; int bar; double price; string text; color col; int dir; int kind; bool alive; };
struct TmProj { bool ok; int i0; double hi; double lo; };
struct TmAnc  { string name; bool on; color col; bool has; int bar; double px; bool exact; };
struct TmAncH { int x1; int x2; double px; color col; };
struct TmItem { string text; color col; double price; };

// ---- chart copy (oldest first) and per-bar derived series
datetime g_t[];
double   g_o[], g_h[], g_l[], g_c[];
double   g_atr[], g_x2h[], g_x2l[];
datetime g_ny[];                       // New York wall clock of every bar (as a plain datetime)
int      g_mn[], g_dow[], g_day[], g_mon[], g_fl[];
int      g_n = 0;

TmSess   g_s[8];
TmBox    g_box[];   int g_nBox = 0;   int g_boxId = 0;
TmLiq    g_lq[];    int g_nLq = 0;
TmMark   g_mk[];    int g_nMk = 0;    int g_mkId = 0;
TmProj   g_pj[8];
TmAnc    g_an[4];
TmAncH   g_ah[];    int g_nAh = 0;

bool     g_intra = true, g_isCrypto = false, g_isForex = false;
// state (St object of the Pine version)
double   g_swHi = TM_NA, g_pSwHi = TM_NA, g_swLo = TM_NA, g_pSwLo = TM_NA;
int      g_swHiBar = 0, g_swLoBar = 0;
int      g_raidDir = 0, g_raidBar = -9999, g_raidSc = 0, g_raidScBar = -1, g_raidMk = -1;
double   g_raidPx = TM_NA, g_raidPen = 0.0;
string   g_raidName = "";
bool     g_raidRej = false;
double   g_curDH = TM_NA, g_curDL = TM_NA, g_curWH = TM_NA, g_curWL = TM_NA, g_curMH = TM_NA, g_curML = TM_NA;
int      g_curDHB = 0, g_curDLB = 0, g_curWHB = 0, g_curWLB = 0, g_curMHB = 0, g_curMLB = 0;
bool     g_jdDone = false;
int      g_jdPot = 0;
double   g_jdExt = TM_NA, g_moPx = TM_NA;
int      g_po3DnBar = -9999, g_po3UpBar = -9999, g_po3Bias = 0;
double   g_po3DnExt = TM_NA, g_po3UpExt = TM_NA, g_po3Ext = TM_NA;
double   g_phM = TM_NA, g_phS = TM_NA, g_plM = TM_NA, g_plS = TM_NA;
int      g_smtDir = 0, g_smtBar = -9999;
int      g_evRaid = 0, g_evRun = 0, g_evJudas = 0, g_evSmt = 0;
bool     g_al[9];
bool     g_alLast[9];
color    g_bg = clrBlack;
datetime g_lastBar = 0, g_lastAlertBar = 0;
bool     g_firstDone = false;
int      g_cnt = 0;
string   g_macroR[9] = {"0233-0300", "0403-0430", "0850-0910", "0950-1010", "1050-1110", "1150-1210", "1310-1340", "1450-1510", "1515-1545"};
int      g_macroA[9], g_macroB[9];
int      g_sb1A, g_sb1B, g_sb2A, g_sb2B, g_sb3A, g_sb3B, g_jdA, g_jdB, g_poAA, g_poAB, g_poMA, g_poMB, g_poDA, g_poDB;

//+------------------------------------------------------------------+
//| time zones (MT has no time-zone database, so the rules are coded) |
//+------------------------------------------------------------------+
datetime NthSunday(const int y, const int m, const int n)
  {
   MqlDateTime d; d.year = y; d.mon = m; d.day = 1; d.hour = 0; d.min = 0; d.sec = 0;
   datetime first = StructToTime(d);
   MqlDateTime f; TimeToStruct(first, f);
   int add = (7 - f.day_of_week) % 7;
   return first + (datetime)((add + 7 * (n - 1)) * 86400);
  }

datetime LastSunday(const int y, const int m)
  {
   MqlDateTime d; d.year = (m == 12 ? y + 1 : y); d.mon = (m == 12 ? 1 : m + 1); d.day = 1; d.hour = 0; d.min = 0; d.sec = 0;
   datetime lastDay = StructToTime(d) - 86400;
   MqlDateTime f; TimeToStruct(lastDay, f);
   return lastDay - (datetime)(f.day_of_week * 86400);
  }

bool UsDstUtc(const datetime u)               // 2nd Sunday of March 02:00 EST  ->  1st Sunday of November 02:00 EDT
  {
   MqlDateTime d; TimeToStruct(u, d);
   datetime s = NthSunday(d.year, 3, 2) + 7 * 3600;
   datetime e = NthSunday(d.year, 11, 1) + 6 * 3600;
   return u >= s && u < e;
  }

bool EuDstUtc(const datetime u)               // last Sunday of March 01:00 UTC  ->  last Sunday of October 01:00 UTC
  {
   MqlDateTime d; TimeToStruct(u, d);
   return u >= LastSunday(d.year, 3) + 3600 && u < LastSunday(d.year, 10) + 3600;
  }

bool BrokerDstAt(const datetime u)
  {
   if(InpBrokerDst == TM_DST_US) return UsDstUtc(u);
   if(InpBrokerDst == TM_DST_EU) return EuDstUtc(u);
   return false;
  }

datetime ServerToUtc(const datetime ts)
  {
   datetime summer = ts - (datetime)((InpBrokerGmt + 1) * 3600);
   if(BrokerDstAt(summer)) return summer;
   return ts - (datetime)(InpBrokerGmt * 3600);
  }

datetime ToNY(const datetime u)  { return u + (datetime)(UsDstUtc(u) ? -4 * 3600 : -5 * 3600); }

datetime NyToUtc(const datetime ny)
  {
   datetime u = ny + 5 * 3600;
   if(ToNY(u) != ny) u = ny + 4 * 3600;
   return u;
  }

datetime ToZone(const datetime u, const ENUM_TM_ZONE z)
  {
   if(z == TM_Z_NY) return ToNY(u);
   if(z == TM_Z_UTC) return u;
   if(z == TM_Z_LONDON) return u + (datetime)(EuDstUtc(u) ? 3600 : 0);
   return u + 12600;                           // Tehran: UTC+3:30, no daylight saving
  }

datetime NowUtc()
  {
   datetime g = TimeGMT();
   if(g > 0) return g;
   return ServerToUtc(TimeCurrent());
  }

string Dn() { return ShortToString(0x25BC); }
string Up() { return ShortToString(0x25B2); }
string Ck() { return ShortToString(0x2713); }

string Num2(const double v)
  {
   string t = DoubleToString(v, 2);
   while(StringLen(t) > 0 && StringSubstr(t, StringLen(t) - 1, 1) == "0") t = StringSubstr(t, 0, StringLen(t) - 1);
   if(StringLen(t) > 0 && StringSubstr(t, StringLen(t) - 1, 1) == ".") t = StringSubstr(t, 0, StringLen(t) - 1);
   return t;
  }

string Two(const int v) { return (v < 10 ? "0" : "") + IntegerToString(v); }

string Hm(const datetime clock)
  {
   MqlDateTime d; TimeToStruct(clock, d);
   return Two(d.hour) + ":" + Two(d.min);
  }

string ZoneTag() { return InpTzDisp == TM_Z_NY ? "NY" : (InpTzDisp == TM_Z_UTC ? "UTC" : (InpTzDisp == TM_Z_LONDON ? "LON" : "")); }

//+------------------------------------------------------------------+
//| generic helpers                                                  |
//+------------------------------------------------------------------+
bool IsNa(const double v) { return v == TM_NA; }
int BarSec() { return PeriodSeconds(_Period); }

datetime TimeAt(const int k)
  {
   if(k < 0) return g_t[0];
   if(k <= g_n - 1) return g_t[k];
   return g_t[g_n - 1] + (datetime)((k - (g_n - 1)) * BarSec());
  }

color Tint(const color c, const double alpha)   // blend with the chart background (MT objects have no transparency)
  {
   int cr = (int)(c & 0xFF), cg = (int)((c >> 8) & 0xFF), cb = (int)((c >> 16) & 0xFF);
   int br = (int)(g_bg & 0xFF), bgc = (int)((g_bg >> 8) & 0xFF), bb = (int)((g_bg >> 16) & 0xFF);
   int r = (int)(cr * alpha + br * (1.0 - alpha));
   int g = (int)(cg * alpha + bgc * (1.0 - alpha));
   int b = (int)(cb * alpha + bb * (1.0 - alpha));
   return (color)(r | (g << 8) | (b << 16));
  }

string Fp(const double p) { return DoubleToString(p, _Digits); }

string NextName(const string kind)
  {
   g_cnt++;
   return PFX + kind + IntegerToString(g_cnt);
  }

string MkLine(const string kind, const datetime t1, const double p1, const datetime t2, const double p2, const color c, const int style, const int width)
  {
   string n = NextName(kind);
   ObjectCreate(0, n, OBJ_TREND, 0, t1, p1, t2, p2);
   ObjectSetInteger(0, n, OBJPROP_COLOR, c);
   ObjectSetInteger(0, n, OBJPROP_STYLE, style);
   ObjectSetInteger(0, n, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, n, OBJPROP_RAY_RIGHT, false);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   return n;
  }

string MkText(const string kind, const datetime t, const double p, const string text, const color c, const int anchor)
  {
   string n = NextName(kind);
   ObjectCreate(0, n, OBJ_TEXT, 0, t, p);
   ObjectSetString(0, n, OBJPROP_TEXT, text);
   ObjectSetInteger(0, n, OBJPROP_COLOR, c);
   ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpFont);
   ObjectSetInteger(0, n, OBJPROP_ANCHOR, anchor);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   return n;
  }

string MkRect(const string kind, const datetime t1, const double top, const datetime t2, const double bottom, const color col, const bool fill)
  {
   string n = NextName(kind);
   ObjectCreate(0, n, OBJ_RECTANGLE, 0, t1, top, t2, bottom);
   ObjectSetInteger(0, n, OBJPROP_COLOR, col);
   ObjectSetInteger(0, n, OBJPROP_FILL, fill);
   ObjectSetInteger(0, n, OBJPROP_BACK, true);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   return n;
  }

//+------------------------------------------------------------------+
//| input parsing, symbol type                                       |
//+------------------------------------------------------------------+
void ParseRange(const string s, const int defA, const int defB, int &a, int &b)
  {
   a = defA; b = defB;
   if(StringLen(s) < 9 || StringSubstr(s, 4, 1) != "-") return;
   int a1 = (int)StringToInteger(StringSubstr(s, 0, 2)), a2 = (int)StringToInteger(StringSubstr(s, 2, 2));
   int b1 = (int)StringToInteger(StringSubstr(s, 5, 2)), b2 = (int)StringToInteger(StringSubstr(s, 7, 2));
   if(a1 < 0 || a1 > 24 || b1 < 0 || b1 > 24 || a2 < 0 || a2 > 59 || b2 < 0 || b2 > 59) return;
   a = a1 * 60 + a2;
   b = b1 * 60 + b2;
   if(b == 1440) b = 0;
  }

bool HasAny(const string s, const string list)    // list separated by ","
  {
   int p = 0;
   while(p < StringLen(list))
     {
      int q = StringFind(list, ",", p);
      if(q < 0) q = StringLen(list);
      string w = StringSubstr(list, p, q - p);
      if(StringLen(w) > 0 && StringFind(s, w) >= 0) return true;
      p = q + 1;
     }
   return false;
  }

void DetectMarket()
  {
   string s = _Symbol;
   StringToUpper(s);
   bool crypto = HasAny(s, "BTC,ETH,XRP,LTC,DOGE,SOL,ADA,BNB,DOT,AVAX,MATIC,TRX,LINK,SHIB");
   bool forex = false;
   if(!crypto && StringLen(s) >= 6)
     {
      string cur = "USD,EUR,GBP,JPY,CHF,CAD,AUD,NZD";
      string a = StringSubstr(s, 0, 3), b = StringSubstr(s, 3, 3);
      forex = (StringFind(cur, a) >= 0 && StringFind(cur, b) >= 0);
     }
   g_isCrypto = (InpMarket == TM_M_CRYPTO) || (InpMarket == TM_M_AUTO && crypto);
   g_isForex  = (InpMarket == TM_M_FOREX)  || (InpMarket == TM_M_AUTO && forex);
  }

//+------------------------------------------------------------------+
//| series: ATR (RMA), New York clock, flags                         |
//+------------------------------------------------------------------+
bool InSess(const int m, const int a, const int b)
  {
   if(a < b) return m >= a && m < b;
   return m >= a || m < b;
  }

void BuildSeries()
  {
   ArrayResize(g_atr, g_n); ArrayResize(g_ny, g_n); ArrayResize(g_mn, g_n); ArrayResize(g_dow, g_n);
   ArrayResize(g_day, g_n); ArrayResize(g_mon, g_n); ArrayResize(g_fl, g_n);
   // ATR(14): Wilder RMA of the true range (same as Pine ta.atr); 0 = not available yet
   double sum = 0.0;
   for(int i = 0; i < g_n; i++)
     {
      double tr = g_h[i] - g_l[i];
      if(i > 0) tr = MathMax(tr, MathMax(MathAbs(g_h[i] - g_c[i - 1]), MathAbs(g_l[i] - g_c[i - 1])));
      if(i < 13) { sum += tr; g_atr[i] = 0.0; }
      else if(i == 13) { sum += tr; g_atr[i] = sum / 14.0; }
      else g_atr[i] = (g_atr[i - 1] * 13.0 + tr) / 14.0;
     }
   int a1 = 0, b1 = 0;
   for(int i = 0; i < g_n; i++)
     {
      datetime u = ServerToUtc(g_t[i]);
      datetime ny = ToNY(u);
      g_ny[i] = ny;
      MqlDateTime d; TimeToStruct(ny, d);
      g_mn[i] = d.hour * 60 + d.min;
      g_dow[i] = d.day_of_week + 1;           // Pine numbering: Sunday = 1 ... Saturday = 7
      g_day[i] = d.day;
      g_mon[i] = d.mon;
      int f = 0;
      if(g_intra)
        {
         int m = g_mn[i];
         for(int k = 0; k < 8; k++) if(InSess(m, g_s[k].a, g_s[k].b)) f |= (1 << k);
         if(InSess(m, g_sb1A, g_sb1B)) f |= (1 << 8);
         if(InSess(m, g_sb2A, g_sb2B)) f |= (1 << 9);
         if(InSess(m, g_sb3A, g_sb3B)) f |= (1 << 10);
         if(InSess(m, g_jdA, g_jdB))   f |= (1 << 11);
         if(InSess(m, g_poAA, g_poAB)) f |= (1 << 12);
         if(InSess(m, g_poMA, g_poMB)) f |= (1 << 13);
         if(InSess(m, g_poDA, g_poDB)) f |= (1 << 14);
         bool mc = false;
         for(int k = 0; k < 9; k++) if(InSess(m, g_macroA[k], g_macroB[k])) mc = true;
         if(mc) f |= (1 << 15);
        }
      g_fl[i] = f;
     }
   // correlated symbol for SMT (inverse: -low / -high)
   ArrayResize(g_x2h, g_n); ArrayResize(g_x2l, g_n);
   for(int i = 0; i < g_n; i++) { g_x2h[i] = TM_NA; g_x2l[i] = TM_NA; }
   if(InpSmtOn && InpSmtSym != "" && InpSmtSym != _Symbol)
      for(int i = 0; i < g_n; i++)
        {
         int sh = iBarShift(InpSmtSym, _Period, g_t[i], true);
         if(sh < 0) continue;
         double hh = iHigh(InpSmtSym, _Period, sh), ll = iLow(InpSmtSym, _Period, sh);
         if(hh <= 0 || ll <= 0) continue;
         g_x2h[i] = InpSmtInv ? -ll : hh;
         g_x2l[i] = InpSmtInv ? -hh : ll;
        }
  }

bool Fl(const int i, const int bit) { return i >= 0 && (g_fl[i] & (1 << bit)) != 0; }
bool SbAny(const int i)  { return InpSbOn && (Fl(i, 8) || Fl(i, 9) || Fl(i, 10)); }
bool KzAny(const int i)  { return (InpLonOn && Fl(i, 1)) || (InpNyaOn && Fl(i, 2)) || (InpLcOn && Fl(i, 3)) || (InpNypOn && Fl(i, 5)) || SbAny(i); }

//+------------------------------------------------------------------+
//| state                                                            |
//+------------------------------------------------------------------+
void ResetState()
  {
   g_nBox = 0; g_boxId = 0; g_nLq = 0; g_nMk = 0; g_mkId = 0; g_nAh = 0; g_cnt = 0;
   ArrayResize(g_box, 0); ArrayResize(g_lq, 0); ArrayResize(g_mk, 0); ArrayResize(g_ah, 0);
   for(int k = 0; k < 8; k++)
     {
      g_s[k].active = false; g_s[k].sBar = 0; g_s[k].sTime = 0; g_s[k].hi = TM_NA; g_s[k].lo = TM_NA; g_s[k].hiBar = 0; g_s[k].loBar = 0;
      g_s[k].fHi = TM_NA; g_s[k].fLo = TM_NA; g_s[k].fHiBar = 0; g_s[k].fLoBar = 0; g_s[k].boxId = -1;
      g_pj[k].ok = false;
     }
   for(int k = 0; k < 4; k++) { g_an[k].has = false; g_an[k].bar = 0; g_an[k].px = TM_NA; g_an[k].exact = true; }
   g_swHi = TM_NA; g_pSwHi = TM_NA; g_swLo = TM_NA; g_pSwLo = TM_NA; g_swHiBar = 0; g_swLoBar = 0;
   g_raidDir = 0; g_raidBar = -9999; g_raidSc = 0; g_raidScBar = -1; g_raidMk = -1; g_raidPx = TM_NA; g_raidPen = 0.0; g_raidName = ""; g_raidRej = false;
   g_curDH = TM_NA; g_curDL = TM_NA; g_curWH = TM_NA; g_curWL = TM_NA; g_curMH = TM_NA; g_curML = TM_NA;
   g_curDHB = 0; g_curDLB = 0; g_curWHB = 0; g_curWLB = 0; g_curMHB = 0; g_curMLB = 0;
   g_jdDone = false; g_jdPot = 0; g_jdExt = TM_NA; g_moPx = TM_NA;
   g_po3DnBar = -9999; g_po3UpBar = -9999; g_po3Bias = 0; g_po3DnExt = TM_NA; g_po3UpExt = TM_NA; g_po3Ext = TM_NA;
   g_phM = TM_NA; g_phS = TM_NA; g_plM = TM_NA; g_plS = TM_NA; g_smtDir = 0; g_smtBar = -9999;
   g_evRaid = 0; g_evRun = 0; g_evJudas = 0; g_evSmt = 0;
  }

void InitSessions()
  {
   string nm[8] = {"Asia", "London KZ", "NY AM KZ", "London Close KZ", "NY Lunch", "NY PM KZ", "CBDR", "Flout"};
   int    dA[8] = {1200, 120, 420, 600, 720, 810, 840, 840};
   int    dB[8] = {0, 300, 600, 720, 780, 960, 1200, 0};
   string rg[8]; bool on[8]; color cl[8]; bool pr[8];
   rg[0] = InpAsiaR; rg[1] = InpLonR; rg[2] = InpNyaR; rg[3] = InpLcR; rg[4] = InpNylR; rg[5] = InpNypR; rg[6] = InpCbdrR; rg[7] = InpFloutR;
   on[0] = InpAsiaOn; on[1] = InpLonOn; on[2] = InpNyaOn; on[3] = InpLcOn; on[4] = InpNylOn; on[5] = InpNypOn; on[6] = InpCbdrOn; on[7] = InpFloutOn;
   cl[0] = InpAsiaC; cl[1] = InpLonC; cl[2] = InpNyaC; cl[3] = InpLcC; cl[4] = InpNylC; cl[5] = InpNypC; cl[6] = InpCbdrC; cl[7] = InpFloutC;
   pr[0] = InpProjAsia; pr[1] = false; pr[2] = false; pr[3] = false; pr[4] = false; pr[5] = false; pr[6] = InpProjCbdr; pr[7] = InpProjFlout;
   bool   lv[8] = {true, true, true, true, false, true, false, false};
   bool   kz[8] = {false, true, true, true, false, true, false, false};
   for(int k = 0; k < 8; k++)
     {
      g_s[k].name = nm[k]; g_s[k].on = on[k]; g_s[k].col = cl[k]; g_s[k].proj = pr[k]; g_s[k].lvl = lv[k]; g_s[k].kz = kz[k];
      ParseRange(rg[k], dA[k], dB[k], g_s[k].a, g_s[k].b);
     }
   ParseRange(InpSb1R, 180, 240, g_sb1A, g_sb1B);
   ParseRange(InpSb2R, 600, 660, g_sb2A, g_sb2B);
   ParseRange(InpSb3R, 840, 900, g_sb3A, g_sb3B);
   ParseRange(InpJdR, 0, 300, g_jdA, g_jdB);
   ParseRange(InpPo3AR, 1140, 60, g_poAA, g_poAB);
   ParseRange(InpPo3MR, 60, 420, g_poMA, g_poMB);
   ParseRange(InpPo3DR, 420, 780, g_poDA, g_poDB);
   for(int k = 0; k < 9; k++) ParseRange(g_macroR[k], 0, 0, g_macroA[k], g_macroB[k]);
   g_an[0].name = "Midnight Open"; g_an[0].on = InpAncMidOn; g_an[0].col = InpAncMidC;
   g_an[1].name = "08:30 Open";    g_an[1].on = InpAnc830On; g_an[1].col = InpAnc830C;
   g_an[2].name = "09:30 Open";    g_an[2].on = InpAnc930On; g_an[2].col = InpAnc930C;
   g_an[3].name = "Weekly Open";   g_an[3].on = InpAncWkOn;  g_an[3].col = InpAncWkC;
  }

//+------------------------------------------------------------------+
//| markers (Raid / Break? / Run / Judas / SMT)                      |
//+------------------------------------------------------------------+
int AddMark(const int bar, const double price, const string text, const color c, const int dir, const int kind)
  {
   double off = 0.0;
   if(kind == 0)                                 // stack ordinary markers that sit close together
     {
      int lvl = 0;
      for(int k = 0; k < g_nMk; k++) if(g_mk[k].alive && g_mk[k].kind == 0 && MathAbs(g_mk[k].bar - bar) <= 8) lvl++;
      off = g_atr[MathMin(bar, g_n - 1)] * 0.5 * MathMin(lvl, 1) * (dir == 1 ? 1 : -1);
     }
   ArrayResize(g_mk, g_nMk + 1);
   g_mkId++;
   g_mk[g_nMk].id = g_mkId; g_mk[g_nMk].bar = bar; g_mk[g_nMk].price = price + off; g_mk[g_nMk].text = text;
   g_mk[g_nMk].col = c; g_mk[g_nMk].dir = dir; g_mk[g_nMk].kind = kind; g_mk[g_nMk].alive = true;
   g_nMk++;
   int cap = (kind == 0) ? 24 : InpMaxLiq + 10;     // keep the newest ones only
   int cnt = 0;
   for(int k = 0; k < g_nMk; k++) if(g_mk[k].alive && g_mk[k].kind == kind) cnt++;
   for(int k = 0; k < g_nMk && cnt > cap; k++)
      if(g_mk[k].alive && g_mk[k].kind == kind) { g_mk[k].alive = false; cnt--; }
   return g_mkId;
  }

void KillMark(const int id)
  {
   for(int k = 0; k < g_nMk; k++) if(g_mk[k].id == id) g_mk[k].alive = false;
  }

void SetMarkText(const int id, const string text)
  {
   for(int k = 0; k < g_nMk; k++) if(g_mk[k].id == id) g_mk[k].text = text;
  }

//+------------------------------------------------------------------+
//| liquidity levels                                                 |
//+------------------------------------------------------------------+
void RemoveLq(const int idx)
  {
   for(int j = idx; j < g_nLq - 1; j++) g_lq[j] = g_lq[j + 1];
   g_nLq--;
  }

void AddLiq(const double px, const string nm, const int d, const int kind, const int ob, const int i)
  {
   if(IsNa(px)) return;
   for(int k = g_nLq - 1; k >= 0; k--)
      if(g_lq[k].name == nm)
        {
         if(g_lq[k].pend && g_lq[k].mk >= 0) KillMark(g_lq[k].mk);
         RemoveLq(k);
        }
   while(g_nLq >= InpMaxLiq)                      // make room: the oldest level NOT waiting for its verdict goes first
     {
      int idx = -1;
      for(int k = 0; k < g_nLq && idx < 0; k++) if(!g_lq[k].pend) idx = k;
      if(idx < 0) break;
      RemoveLq(idx);
     }
   ArrayResize(g_lq, g_nLq + 1);
   g_lq[g_nLq].price = px; g_lq[g_nLq].name = nm; g_lq[g_nLq].dir = d; g_lq[g_nLq].kind = kind; g_lq[g_nLq].bar = i; g_lq[g_nLq].ob = ob;
   g_lq[g_nLq].swept = false; g_lq[g_nLq].sweptBar = 0; g_lq[g_nLq].pend = false; g_lq[g_nLq].hitBar = 0; g_lq[g_nLq].hitExt = 0.0; g_lq[g_nLq].mk = -1;
   g_nLq++;
  }

int KindScore(const int k) { return (k == 3 || k == 4) ? 4 : (k == 2 ? 3 : (k == 1 ? 2 : 1)); }

void ConfirmRaid(const int qi, const double ext, const int i)
  {
   int sc = KindScore(g_lq[qi].kind);
   double pen = MathAbs(ext - g_lq[qi].price);
   bool sameBar = (g_raidScBar == i);
   bool better = !sameBar || sc > g_raidSc || (sc == g_raidSc && pen > g_raidPen);
   if(g_lq[qi].mk >= 0) { KillMark(g_lq[qi].mk); g_lq[qi].mk = -1; }
   if(!better) return;
   if(sameBar && g_raidMk >= 0) { KillMark(g_raidMk); g_raidMk = -1; }
   g_raidDir = (g_lq[qi].dir == 1) ? -1 : 1;
   g_raidBar = i; g_raidPx = ext; g_raidName = g_lq[qi].name; g_raidRej = true;
   g_raidSc = sc; g_raidPen = pen; g_raidScBar = i; g_evRaid = g_raidDir;
   if(InpRaidLbl) g_raidMk = AddMark(i, g_lq[qi].dir == 1 ? g_h[i] : g_l[i], "Raid " + g_lq[qi].name, C_ORANGE, g_lq[qi].dir == 1 ? 1 : -1, 1);
  }

void ChkLiq(const int i)
  {
   for(int k = g_nLq - 1; k >= 0; k--)
     {
      if(!g_lq[k].swept)
        {
         if(i > g_lq[k].bar)
           {
            bool hit = g_lq[k].dir == 1 ? g_h[i] > g_lq[k].price : g_l[i] < g_lq[k].price;
            if(hit)
              {
               g_lq[k].swept = true; g_lq[k].sweptBar = i;
               bool rej = g_lq[k].dir == 1 ? g_c[i] < g_lq[k].price : g_c[i] > g_lq[k].price;
               double ext = g_lq[k].dir == 1 ? g_h[i] : g_l[i];
               if(rej) ConfirmRaid(k, ext, i);
               else
                 {
                  g_lq[k].pend = true; g_lq[k].hitBar = i; g_lq[k].hitExt = ext;      // PENDING: back through = RAID, still beyond = RUN
                  if(InpRaidLbl) g_lq[k].mk = AddMark(i, ext, "Break? " + g_lq[k].name, C_GRAY, g_lq[k].dir == 1 ? 1 : -1, 1);
                 }
              }
           }
        }
      else
        {
         if(g_lq[k].pend)
           {
            g_lq[k].hitExt = g_lq[k].dir == 1 ? MathMax(g_lq[k].hitExt, g_h[i]) : MathMin(g_lq[k].hitExt, g_l[i]);
            bool back = g_lq[k].dir == 1 ? g_c[i] < g_lq[k].price : g_c[i] > g_lq[k].price;
            if(back)
              {
               g_lq[k].pend = false;
               ConfirmRaid(k, g_lq[k].hitExt, i);
              }
            else if(i - g_lq[k].hitBar >= InpPendBars)
              {
               g_lq[k].pend = false;
               g_evRun = g_lq[k].dir;
               if(g_lq[k].mk >= 0) SetMarkText(g_lq[k].mk, "Run " + g_lq[k].name);
              }
           }
         if(!g_lq[k].pend && i - g_lq[k].sweptBar > InpKeepSwept) RemoveLq(k);
        }
     }
   if(g_raidRej && i - g_raidBar <= 60)
     {
      if(g_raidDir == 1)  g_raidPx = MathMin(IsNa(g_raidPx) ? g_l[i] : g_raidPx, g_l[i]);
      if(g_raidDir == -1) g_raidPx = MathMax(IsNa(g_raidPx) ? g_h[i] : g_raidPx, g_h[i]);
     }
  }

//+------------------------------------------------------------------+
//| sessions                                                         |
//+------------------------------------------------------------------+
int FindBox(const int id)
  {
   for(int k = 0; k < g_nBox; k++) if(g_box[k].id == id) return k;
   return -1;
  }

bool UpdSess(const int k, const bool inS, const int i)
  {
   bool ended = false;
   if(!g_s[k].on) return false;
   if(inS)
     {
      if(!g_s[k].active)
        {
         g_s[k].active = true; g_s[k].sBar = i; g_s[k].sTime = g_t[i]; g_s[k].hi = g_h[i]; g_s[k].lo = g_l[i];
         g_s[k].hiBar = i; g_s[k].loBar = i;
         if(InpSessStyle != TM_ST_OFF)
           {
            ArrayResize(g_box, g_nBox + 1);
            g_boxId++;
            g_box[g_nBox].id = g_boxId; g_box[g_nBox].s = k; g_box[g_nBox].i0 = i; g_box[g_nBox].i1 = i;
            g_box[g_nBox].hi = g_h[i]; g_box[g_nBox].lo = g_l[i]; g_box[g_nBox].sTime = g_t[i];
            g_s[k].boxId = g_boxId;
            g_nBox++;
            while(g_nBox > InpHistDays * 8)
              {
               for(int j = 0; j < g_nBox - 1; j++) g_box[j] = g_box[j + 1];
               g_nBox--;
              }
           }
        }
      else
        {
         if(g_h[i] > g_s[k].hi) { g_s[k].hi = g_h[i]; g_s[k].hiBar = i; }
         if(g_l[i] < g_s[k].lo) { g_s[k].lo = g_l[i]; g_s[k].loBar = i; }
         int b = FindBox(g_s[k].boxId);
         if(b >= 0) { g_box[b].i1 = i; g_box[b].hi = g_s[k].hi; g_box[b].lo = g_s[k].lo; }
        }
     }
   else if(g_s[k].active)
     {
      g_s[k].active = false; ended = true;
      g_s[k].fHi = g_s[k].hi; g_s[k].fLo = g_s[k].lo; g_s[k].fHiBar = g_s[k].hiBar; g_s[k].fLoBar = g_s[k].loBar;
      int b = FindBox(g_s[k].boxId);
      if(b >= 0) g_box[b].i1 = i - 1;
     }
   return ended;
  }

void SessEnd(const int k, const int i)
  {
   if(g_s[k].lvl && InpSessLvlOn)
     {
      AddLiq(g_s[k].fHi, g_s[k].name + " H", 1, 1, g_s[k].fHiBar, i);
      AddLiq(g_s[k].fLo, g_s[k].name + " L", -1, 1, g_s[k].fLoBar, i);
     }
   if(g_s[k].proj)
     { g_pj[k].ok = true; g_pj[k].i0 = g_s[k].sBar; g_pj[k].hi = g_s[k].fHi; g_pj[k].lo = g_s[k].fLo; }
  }

//+------------------------------------------------------------------+
//| day / week / month levels, anchors                               |
//+------------------------------------------------------------------+
bool IsSunday(const int i) { return !g_isCrypto && g_dow[i] == 1; }

bool NewWeek(const int i)
  {
   if(i <= 0) return false;
   int a = g_isCrypto ? (g_dow[i] + 5) % 7 : g_dow[i] - 1;
   int b = g_isCrypto ? (g_dow[i - 1] + 5) % 7 : g_dow[i - 1] - 1;
   return a < b;
  }

void PeriodsBar(const int i)
  {
   bool newDay = i > 0 && g_day[i] != g_day[i - 1] && !IsSunday(i);
   bool newWk  = NewWeek(i);
   bool newMth = i > 0 && g_mon[i] != g_mon[i - 1];
   if(newDay)
     {
      if(!IsNa(g_curDH) && InpPdOn)
        {
         AddLiq(g_curDH, "PDH", 1, 2, g_curDHB, i);
         AddLiq(g_curDL, "PDL", -1, 2, g_curDLB, i);
        }
      g_curDH = g_h[i]; g_curDL = g_l[i]; g_curDHB = i; g_curDLB = i;
      g_jdDone = false; g_jdPot = 0; g_po3DnBar = -9999; g_po3UpBar = -9999; g_po3DnExt = TM_NA; g_po3UpExt = TM_NA; g_po3Bias = 0;
     }
   else if(!IsSunday(i))
     {
      if(IsNa(g_curDH) || g_h[i] > g_curDH) { g_curDH = g_h[i]; g_curDHB = i; }
      if(IsNa(g_curDL) || g_l[i] < g_curDL) { g_curDL = g_l[i]; g_curDLB = i; }
     }
   if(newWk)
     {
      if(!IsNa(g_curWH) && InpPwOn)
        {
         AddLiq(g_curWH, "PWH", 1, 3, g_curWHB, i);
         AddLiq(g_curWL, "PWL", -1, 3, g_curWLB, i);
        }
      g_curWH = g_h[i]; g_curWL = g_l[i]; g_curWHB = i; g_curWLB = i;
     }
   else
     {
      if(IsNa(g_curWH) || g_h[i] > g_curWH) { g_curWH = g_h[i]; g_curWHB = i; }
      if(IsNa(g_curWL) || g_l[i] < g_curWL) { g_curWL = g_l[i]; g_curWLB = i; }
     }
   if(newMth)
     {
      if(!IsNa(g_curMH) && InpPmOn)
        {
         AddLiq(g_curMH, "PMH", 1, 4, g_curMHB, i);
         AddLiq(g_curML, "PML", -1, 4, g_curMLB, i);
        }
      g_curMH = g_h[i]; g_curML = g_l[i]; g_curMHB = i; g_curMLB = i;
     }
   else
     {
      if(IsNa(g_curMH) || g_h[i] > g_curMH) { g_curMH = g_h[i]; g_curMHB = i; }
      if(IsNa(g_curML) || g_l[i] < g_curML) { g_curML = g_l[i]; g_curMLB = i; }
     }
  }

void AncFire(const int k, const bool exact, const int i)
  {
   if(!g_an[k].on) return;
   if(g_an[k].has)
     {
      ArrayResize(g_ah, g_nAh + 1);
      g_ah[g_nAh].x1 = g_an[k].bar; g_ah[g_nAh].x2 = i; g_ah[g_nAh].px = g_an[k].px; g_ah[g_nAh].col = g_an[k].col;
      g_nAh++;
      while(g_nAh > InpHistDays * 4 + 4)
        {
         for(int j = 0; j < g_nAh - 1; j++) g_ah[j] = g_ah[j + 1];
         g_nAh--;
        }
     }
   g_an[k].has = true; g_an[k].bar = i; g_an[k].px = g_o[i]; g_an[k].exact = exact;
  }

void AnchorsBar(const int i)
  {
   if(!g_intra) return;
   bool dayChg = i > 0 && g_day[i] != g_day[i - 1];
   int minDay = g_mn[i], minDay1 = i > 0 ? g_mn[i - 1] : 0;
   if(dayChg && !IsSunday(i)) { AncFire(0, minDay == 0, i); g_moPx = g_o[i]; }
   if(minDay >= 510 && (minDay1 < 510 || dayChg)) AncFire(1, minDay == 510, i);
   if(minDay >= 570 && (minDay1 < 570 || dayChg)) AncFire(2, minDay == 570, i);
   if(NewWeek(i)) AncFire(3, g_isCrypto ? minDay == 0 : minDay == 1020, i);
  }

//+------------------------------------------------------------------+
//| Judas Swing, Power of 3                                          |
//+------------------------------------------------------------------+
void JudasBar(const int i)
  {
   bool inJd = Fl(i, 11);
   if(g_jdPot != 0 && !inJd) g_jdPot = 0;           // a pending Judas expires with its window
   if(!(InpJdOn && inJd && !g_jdDone && !IsNa(g_s[0].fHi))) return;
   if(g_jdPot == 0)
     {
      if(g_h[i] > g_s[0].fHi && g_c[i] < g_s[0].fHi)
        { g_jdPot = -1; g_jdExt = g_h[i]; AddMark(i, g_h[i], "Judas? " + Dn(), C_YELLOW, 1, 0); }
      else if(g_l[i] < g_s[0].fLo && g_c[i] > g_s[0].fLo)
        { g_jdPot = 1; g_jdExt = g_l[i]; AddMark(i, g_l[i], "Judas? " + Up(), C_YELLOW, -1, 0); }
     }
   else
     {
      if(g_jdPot == -1 && g_c[i] > g_jdExt) g_jdPot = 0;     // closed beyond the sweep extreme: a real breakout
      if(g_jdPot == 1 && g_c[i] < g_jdExt) g_jdPot = 0;
      if(g_jdPot == -1 && !IsNa(g_moPx) && g_c[i] < g_moPx)
        { g_jdDone = true; g_evJudas = -1; AddMark(i, g_h[i], "Judas " + Dn() + " " + Ck(), C_YELLOW, 1, 0); }
      if(g_jdPot == 1 && !IsNa(g_moPx) && g_c[i] > g_moPx)
        { g_jdDone = true; g_evJudas = 1; AddMark(i, g_l[i], "Judas " + Up() + " " + Ck(), C_YELLOW, -1, 0); }
     }
  }

void Po3Bar(const int i)
  {
   if(!(InpPo3On && (Fl(i, 13) || Fl(i, 14)) && !IsNa(g_moPx))) return;
   double thr = 0.25 * g_atr[i];
   bool dnX = g_l[i] < g_moPx - thr;
   bool upX = g_h[i] > g_moPx + thr;
   // 0) invalidation
   if(g_po3Bias == 1 && g_c[i] < g_po3Ext)  { g_po3Bias = 0; g_po3DnBar = -9999; }
   if(g_po3Bias == -1 && g_c[i] > g_po3Ext) { g_po3Bias = 0; g_po3UpBar = -9999; }
   // 1) confirmation uses only previous candles; a candle crossing both sides can neither sweep nor confirm
   if(g_po3Bias == 0 && !(dnX && upX))
     {
      if(g_c[i] > g_moPx && g_po3DnBar > g_po3UpBar && i - g_po3DnBar <= InpPo3Fresh)      { g_po3Bias = 1;  g_po3Ext = g_po3DnExt; }
      else if(g_c[i] < g_moPx && g_po3UpBar > g_po3DnBar && i - g_po3UpBar <= InpPo3Fresh) { g_po3Bias = -1; g_po3Ext = g_po3UpExt; }
     }
   // 2) then this candle's own excursion
   if(dnX && !upX) { g_po3DnBar = i; g_po3DnExt = MathMin(IsNa(g_po3DnExt) ? g_l[i] : g_po3DnExt, g_l[i]); }
   if(upX && !dnX) { g_po3UpBar = i; g_po3UpExt = MathMax(IsNa(g_po3UpExt) ? g_h[i] : g_po3UpExt, g_h[i]); }
  }

//+------------------------------------------------------------------+
//| swings: EQH / EQL, SMT                                           |
//+------------------------------------------------------------------+
bool PivotHigh(const int c, const int len)
  {
   for(int k = 1; k <= len; k++) if(!(g_h[c] > g_h[c - k]) || !(g_h[c] > g_h[c + k])) return false;
   return true;
  }

bool PivotLow(const int c, const int len)
  {
   for(int k = 1; k <= len; k++) if(!(g_l[c] < g_l[c - k]) || !(g_l[c] < g_l[c + k])) return false;
   return true;
  }

double MaxN(const double a, const double b) { return (IsNa(a) || IsNa(b)) ? TM_NA : MathMax(a, b); }
double MinN(const double a, const double b) { return (IsNa(a) || IsNa(b)) ? TM_NA : MathMin(a, b); }

void SmtCheck(const bool isHigh, const int i)
  {
   int c = i - InpSwLen;
   if(!InpSmtOn || IsNa(g_x2h[i]) || IsNa(g_x2l[i])) return;
   if(isHigh)
     {
      double m = g_h[c];
      double s2 = MaxN(g_x2h[c - 1], MaxN(g_x2h[c], g_x2h[c + 1]));
      if(!IsNa(g_phM) && !IsNa(g_phS))
        {
         bool mainHH = m > g_phM;
         bool secHH = !IsNa(s2) && s2 > g_phS;
         if((mainHH && !secHH) || (!mainHH && secHH && m < g_phM))
           { g_smtDir = -1; g_smtBar = i; g_evSmt = -1; AddMark(c, m, "SMT " + Dn(), C_PINK, 1, 0); }
        }
      g_phM = m; g_phS = s2;
     }
   else
     {
      double m = g_l[c];
      double s2l = MinN(g_x2l[c - 1], MinN(g_x2l[c], g_x2l[c + 1]));
      if(!IsNa(g_plM) && !IsNa(g_plS))
        {
         bool mainLL = m < g_plM;
         bool secLL = !IsNa(s2l) && s2l < g_plS;
         if((mainLL && !secLL) || (!mainLL && secLL && m > g_plM))
           { g_smtDir = 1; g_smtBar = i; g_evSmt = 1; AddMark(c, m, "SMT " + Up(), C_PINK, -1, 0); }
        }
      g_plM = m; g_plS = s2l;
     }
  }

void PivotsBar(const int i)
  {
   int len = InpSwLen, c = i - len;
   if(c - len < 0) return;
   bool ph = PivotHigh(c, len), pl = PivotLow(c, len);
   bool atrOk = g_atr[i] > 0.0;
   if(ph)
     {
      g_pSwHi = g_swHi; g_swHi = g_h[c]; g_swHiBar = c;
      if(InpEqOn && !IsNa(g_pSwHi) && atrOk && MathAbs(g_swHi - g_pSwHi) <= InpEqTol * g_atr[i])
         AddLiq(MathMax(g_swHi, g_pSwHi), "EQH", 1, 5, g_swHiBar, i);
     }
   if(pl)
     {
      g_pSwLo = g_swLo; g_swLo = g_l[c]; g_swLoBar = c;
      if(InpEqOn && !IsNa(g_pSwLo) && atrOk && MathAbs(g_swLo - g_pSwLo) <= InpEqTol * g_atr[i])
         AddLiq(MathMin(g_swLo, g_pSwLo), "EQL", -1, 5, g_swLoBar, i);
     }
   if(ph) SmtCheck(true, i);
   if(pl) SmtCheck(false, i);
  }

//+------------------------------------------------------------------+
//| one confirmed bar (same order as the Pine main block)            |
//+------------------------------------------------------------------+
void ProcessBar(const int i)
  {
   g_evRaid = 0; g_evRun = 0; g_evJudas = 0; g_evSmt = 0;
   if(g_intra)
      for(int k = 0; k < 8; k++)
        {
         bool ended = UpdSess(k, Fl(i, k), i);
         if(ended) SessEnd(k, i);
        }
   PeriodsBar(i);
   AnchorsBar(i);
   PivotsBar(i);
   ChkLiq(i);
   JudasBar(i);
   Po3Bar(i);
  }

//+------------------------------------------------------------------+
//| drawing                                                          |
//+------------------------------------------------------------------+
int g_nItem = 0;
TmItem g_item[];
double g_lblP[];
int g_nLblP = 0;

void AddItem(const string text, const color c, const double price)
  {
   ArrayResize(g_item, g_nItem + 1);
   g_item[g_nItem].text = text; g_item[g_nItem].col = c; g_item[g_nItem].price = price;
   g_nItem++;
  }

double Rng150()
  {
   int s = MathMax(0, g_n - 2 - 149);
   double hi = -DBL_MAX, lo = DBL_MAX;
   for(int k = s; k <= g_n - 2; k++) { if(g_h[k] > hi) hi = g_h[k]; if(g_l[k] < lo) lo = g_l[k]; }
   return (hi > lo) ? hi - lo : 0.0;
  }

void FlushItems()                       // right-side labels: sorted by price, never closer than the minimum gap
  {
   g_nLblP = 0;
   if(g_nItem <= 0) return;
   int ord[];
   ArrayResize(ord, g_nItem);
   for(int k = 0; k < g_nItem; k++) ord[k] = k;
   for(int a = 1; a < g_nItem; a++)     // insertion sort, price descending
     {
      int v = ord[a], b = a - 1;
      while(b >= 0 && g_item[ord[b]].price < g_item[v].price) { ord[b + 1] = ord[b]; b--; }
      ord[b + 1] = v;
     }
   double gap = Rng150() * InpLblGap / 100.0;
   double prevY = TM_NA;
   datetime tx = TimeAt(g_n - 1 + InpLblPad);
   for(int k = 0; k < g_nItem; k++)
     {
      int ix = ord[k];
      double y = g_item[ix].price;
      if(!IsNa(prevY) && prevY - y < gap) y = prevY - gap;
      MkText("RL", tx, y, g_item[ix].text, g_item[ix].col, ANCHOR_LEFT);
      ArrayResize(g_lblP, g_nLblP + 1); g_lblP[g_nLblP] = y; g_nLblP++;
      prevY = y;
     }
   g_nItem = 0;
  }

void DrawBand(const bool shade, const int a, const int b, const color c, const double lo, const double hi, const int fromBar)
  {
   if(!shade) return;
   int run0 = -1;
   for(int i = fromBar; i < g_n; i++)
     {
      bool inn = g_intra && InSess(g_mn[i], a, b);
      if(inn && run0 < 0) run0 = i;
      if((!inn || i == g_n - 1) && run0 >= 0)
        {
         int r1 = inn ? i : i - 1;
         MkRect("SB", g_t[run0], hi, TimeAt(r1 + 1), lo, Tint(c, 0.07), true);
         run0 = -1;
        }
     }
  }

void DrawShading()
  {
   int from = 0;
   datetime cut = g_t[g_n - 1] - (datetime)(InpShadeDays * 86400);
   for(int i = 0; i < g_n; i++) if(g_t[i] >= cut) { from = i; break; }
   double hi = -DBL_MAX, lo = DBL_MAX;
   for(int i = from; i < g_n; i++) { if(g_h[i] > hi) hi = g_h[i]; if(g_l[i] < lo) lo = g_l[i]; }
   double pad = (hi - lo) * 3.0;       // tall enough to look full-height at any zoom
   hi += pad; lo -= pad;
   if(InpSbOn)
     {
      DrawBand(true, g_sb1A, g_sb1B, InpSbC, lo, hi, from);
      DrawBand(true, g_sb2A, g_sb2B, InpSbC, lo, hi, from);
      DrawBand(true, g_sb3A, g_sb3B, InpSbC, lo, hi, from);
     }
   if(InpMcOn)
      for(int k = 0; k < 9; k++) DrawBand(true, g_macroA[k], g_macroB[k], InpMcC, lo, hi, from);
  }

string ShortName(const int k)
  {
   if(k == 0) return "Asia";
   if(k == 1) return "London";
   if(k == 2) return "NY AM";
   if(k == 3) return "LC";
   if(k == 4) return "Lunch";
   if(k == 5) return "NY PM";
   return g_s[k].name;
  }

void DrawBoxes()
  {
   int fillMode = InpSessStyle;
   for(int b = 0; b < g_nBox; b++)
     {
      int k = g_box[b].s;
      color c = g_s[k].col;
      int i0 = g_box[b].i0, i1 = g_box[b].i1;
      if(i1 < i0) i1 = i0;
      datetime t1 = g_t[i0], t2 = TimeAt(i1 + 1);
      if(fillMode == TM_ST_FILLED) MkRect("BX", t1, g_box[b].hi, t2, g_box[b].lo, Tint(c, 0.15), true);
      else if(fillMode == TM_ST_LIGHT) MkRect("BX", t1, g_box[b].hi, t2, g_box[b].lo, Tint(c, 0.06), true);
      MkRect("BL", t1, g_box[b].hi, t2, g_box[b].lo, Tint(c, fillMode == TM_ST_BORDER ? 0.9 : 0.65), false);
      if(InpShowSessLbl)
        {
         datetime st = ToZone(ServerToUtc(g_box[b].sTime), InpTzDisp);
         int dur = g_s[k].b - g_s[k].a; if(dur <= 0) dur += 1440;
         string z = ZoneTag();
         string txt = ShortName(k) + "  " + Hm(st) + "-" + Hm(st + (datetime)(dur * 60)) + (z == "" ? "" : " " + z);
         MkText("SL", g_t[(i0 + i1) / 2], g_box[b].hi, txt, c, ANCHOR_LOWER);
        }
     }
  }

void DrawProjections()
  {
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   for(int k = 0; k < 8; k++)
     {
      if(!g_s[k].proj || !g_pj[k].ok) continue;
      double r = g_pj[k].hi - g_pj[k].lo;
      if(r <= 0) continue;
      double pips = r / (_Point * 10.0);
      bool tooWide = g_isForex && InpFiltPips > 0 && pips > InpFiltPips;
      datetime t0 = g_t[MathMax(g_pj[k].i0, MathMax(0, g_n - 1400))];
      color c = g_s[k].col;
      MkLine("PJ", t0, g_pj[k].hi, xe, g_pj[k].hi, Tint(c, 0.8), STYLE_SOLID, 1);
      MkLine("PJ", t0, g_pj[k].lo, xe, g_pj[k].lo, Tint(c, 0.8), STYLE_SOLID, 1);
      AddItem(g_s[k].name + " H " + Fp(g_pj[k].hi), c, g_pj[k].hi);
      AddItem(g_s[k].name + " L " + Fp(g_pj[k].lo) + (tooWide ? " (wide " + IntegerToString((int)pips) + "p)" : ""), c, g_pj[k].lo);
      if(tooWide) continue;
      for(int m = 1; m <= InpSdCount; m++)
        {
         double mult = m * InpSdStep;
         double up = g_pj[k].hi + mult * r, dn = g_pj[k].lo - mult * r;
         MkLine("PJ", t0, up, xe, up, InpProjUpC, STYLE_DASH, 1);
         MkLine("PJ", t0, dn, xe, dn, InpProjDnC, STYLE_DASH, 1);
         string ms = Num2(mult);
         AddItem(g_s[k].name + " +" + ms + "x " + Fp(up), InpProjUpC, up);
         AddItem(g_s[k].name + " -" + ms + "x " + Fp(dn), InpProjDnC, dn);
        }
     }
  }

color LiqColor(const int kind, const string nm)
  {
   if(kind == 2) return InpPdC;
   if(kind == 3) return InpPwC;
   if(kind == 4) return InpPmC;
   if(kind == 5) return InpEqC;
   return InpSessLC;
  }

void DrawLiquidity()
  {
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   for(int k = 0; k < g_nLq; k++)
     {
      color c = LiqColor(g_lq[k].kind, g_lq[k].name);
      datetime t0 = g_t[MathMax(g_lq[k].ob, MathMax(0, g_n - 1400))];
      if(g_lq[k].swept)
        {
         MkLine("LQ", t0, g_lq[k].price, g_t[g_lq[k].sweptBar], g_lq[k].price, c, STYLE_DOT, 1);
         MkText("LC", g_t[g_lq[k].sweptBar], g_lq[k].price, Ck(), c, ANCHOR_LEFT);
        }
      else
        {
         MkLine("LQ", t0, g_lq[k].price, xe, g_lq[k].price, c, STYLE_DASH, 1);
         AddItem(g_lq[k].name + " " + Fp(g_lq[k].price), c, g_lq[k].price);
        }
     }
  }

void DrawAnchors()
  {
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   for(int k = 0; k < g_nAh; k++)
      MkLine("AH", g_t[g_ah[k].x1], g_ah[k].px, g_t[g_ah[k].x2], g_ah[k].px, g_ah[k].col, STYLE_SOLID, 1);
   for(int k = 0; k < 4; k++)
     {
      if(!g_an[k].on || !g_an[k].has) continue;
      MkLine("AN", g_t[g_an[k].bar], g_an[k].px, xe, g_an[k].px, g_an[k].col, STYLE_SOLID, 1);
      if(InpShowSessLbl) AddItem(g_an[k].name + (g_an[k].exact ? "" : " " + ShortToString(0x2248)) + " " + Fp(g_an[k].px), g_an[k].col, g_an[k].px);
     }
  }

void DrawMarks()
  {
   for(int k = 0; k < g_nMk; k++)
     {
      if(!g_mk[k].alive) continue;
      MkText("MK", g_t[g_mk[k].bar], g_mk[k].price, g_mk[k].text, g_mk[k].col, g_mk[k].dir == 1 ? ANCHOR_LOWER : ANCHOR_UPPER);
     }
  }

void DrawIpda()
  {
   if(!InpIpdaOn) return;
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   int days[3]; color cs[3];
   days[0] = InpIpda1; days[1] = InpIpda2; days[2] = InpIpda3;
   cs[0] = C_BLUE; cs[1] = C_PURPLE; cs[2] = C_ORANGE;
   double hi3 = TM_NA, lo3 = TM_NA;
   for(int k = 0; k < 3; k++)
     {
      int ih = iHighest(_Symbol, PERIOD_D1, MODE_HIGH, days[k], 1), il = iLowest(_Symbol, PERIOD_D1, MODE_LOW, days[k], 1);
      if(ih < 0 || il < 0) continue;
      double h = iHigh(_Symbol, PERIOD_D1, ih), l = iLow(_Symbol, PERIOD_D1, il);
      MkLine("IP", g_t[MathMax(0, g_n - 61)], h, xe, h, cs[k], STYLE_DOT, 1);
      MkLine("IP", g_t[MathMax(0, g_n - 61)], l, xe, l, cs[k], STYLE_DOT, 1);
      AddItem(IntegerToString(days[k]) + "D H " + Fp(h), cs[k], h);
      AddItem(IntegerToString(days[k]) + "D L " + Fp(l), cs[k], l);
      if(k == 2) { hi3 = h; lo3 = l; }
     }
   if(!IsNa(hi3) && !IsNa(lo3))
     {
      double eq = (hi3 + lo3) / 2.0;
      MkLine("IP", g_t[MathMax(0, g_n - 61)], eq, xe, eq, C_GRAY, STYLE_DOT, 1);
      AddItem(IntegerToString(InpIpda3) + "D EQ " + Fp(eq), C_GRAY, eq);
     }
  }

//+------------------------------------------------------------------+
//| panel (clock, active session, next session, biases)              |
//+------------------------------------------------------------------+
datetime NextStartUtc(const int sm)
  {
   datetime u0 = NowUtc();
   datetime nyNow = ToNY(u0);
   datetime day0 = nyNow - (datetime)(nyNow % 86400);
   for(int d = 0; d <= 8; d++)
     {
      datetime ny = day0 + (datetime)(d * 86400 + sm * 60);
      datetime u = NyToUtc(ny);
      MqlDateTime p; TimeToStruct(ny, p);
      int dow = p.day_of_week + 1;
      bool closed = !g_isCrypto && (dow == 7 || (dow == 1 && sm < 1020) || (dow == 6 && sm >= 1020));
      if(u > u0 && !closed) return u;
     }
   return 0;
  }

const int PN_ROWS = 10, PN_M = 14;
int g_pnW = 430, g_pnRH = 18, g_pnKeyW = 150;
string g_pk[10], g_pv[10];
color  g_pc[10];

int  g_pnCorner = CORNER_LEFT_UPPER;
long g_pnSig = -1;

int PnH() { return PN_ROWS * g_pnRH + 12; }

int WmHeight() { return InpShowWm ? InpWmBottom + (int)(InpWmSize * 2.3) + 6 : 0; }   // bottom-left space used by the watermark

int PnMarginY(const int corner)           // vertical gap from the chart edge; bottom-left leaves room for the watermark
  {
   if(corner == CORNER_LEFT_LOWER) return PN_M + WmHeight();
   return PN_M;
  }

int FixedCorner()
  {
   if(InpPanelPos == TM_C_TOPLEFT) return CORNER_LEFT_UPPER;
   if(InpPanelPos == TM_C_BOTTOMRIGHT) return CORNER_RIGHT_LOWER;
   if(InpPanelPos == TM_C_BOTTOMLEFT) return CORNER_LEFT_LOWER;
   return CORNER_RIGHT_UPPER;
  }

// how much of the visible chart a panel in this corner would cover (bars + right-hand price labels)
double CornerCover(const int corner, const int W, const int H, const int first, const int vis, const double pmax, const double pmin, const double ppb)
  {
   bool right = (corner == CORNER_RIGHT_UPPER || corner == CORNER_RIGHT_LOWER);
   bool bottom = (corner == CORNER_LEFT_LOWER || corner == CORNER_RIGHT_LOWER);
   int x0 = right ? W - PN_M - g_pnW : PN_M, x1 = x0 + g_pnW;
   int my = PnMarginY(corner);
   int y0 = bottom ? H - my - PnH() : my, y1 = y0 + PnH();
   double sc = 0.0, k = (double)H / (pmax - pmin);
   for(int s = first; s > first - vis && s >= 0; s--)
     {
      int i = g_n - 1 - s;
      if(i < 0 || i >= g_n) continue;
      double bx = (first - s + 0.5) * ppb;
      if(bx < x0 - 2 || bx > x1 + 2) continue;
      double yt = (pmax - g_h[i]) * k, yb = (pmax - g_l[i]) * k;
      if(yb >= y0 && yt <= y1) sc += 1.0;
     }
   if(right)                                   // right-hand level labels live under the right corners
      for(int j = 0; j < g_nLblP; j++)
        {
         double yy = (pmax - g_lblP[j]) * k;
         if(yy >= y0 - 6 && yy <= y1 + 6) sc += 6.0;
        }
   return sc;
  }

int PanelCorner()
  {
   if(InpPanelPos != TM_C_AUTO) return FixedCorner();
   return g_pnCorner;
  }

void ChooseCorner()                         // Auto: pick the emptiest corner, with hysteresis so it does not jump around
  {
   if(InpPanelPos != TM_C_AUTO) { g_pnCorner = FixedCorner(); return; }
   int W = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS), H = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   int first = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR), vis = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   double pmax = ChartGetDouble(0, CHART_PRICE_MAX, 0), pmin = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   if(W <= 0 || H <= 0 || vis <= 0 || pmax <= pmin) return;
   long sig = (long)W * 1000003L + H * 10007L + first * 101L + vis + (long)(pmax * 1000.0) % 9973 + g_nLblP * 13L + g_n;
   if(sig == g_pnSig) return;
   g_pnSig = sig;
   double shiftPct = (InpRightMargin > 0) ? (double)MathMin(InpRightMargin, 50) / 100.0 : 0.0;
   double ppb = (double)W * (1.0 - shiftPct) / vis;
   int cs[4];
   cs[0] = CORNER_LEFT_UPPER; cs[1] = CORNER_RIGHT_UPPER; cs[2] = CORNER_LEFT_LOWER; cs[3] = CORNER_RIGHT_LOWER;
   int best = -1; double bs = 1e18, cur = 1e18;
   for(int k = 0; k < 4; k++)
     {
      double sc = CornerCover(cs[k], W, H, first, vis, pmax, pmin, ppb);
      if(cs[k] == g_pnCorner) cur = sc;
      if(sc < bs) { bs = sc; best = cs[k]; }
     }
   if(best >= 0 && cur > bs + 3.0) g_pnCorner = best;      // move only when clearly better
  }

void PanelRow(const int row, const string key, const string val, const color vc)
  {
   g_pk[row] = key; g_pv[row] = (val == "" ? " " : val); g_pc[row] = vc;      // empty text would show the default word "Label"
  }

void PanelMeasure()                         // size the panel from the real text widths (works at any screen scaling)
  {
   TextSetFont("Consolas", -InpFont * 10);
   uint w = 0, h = 0;
   int kw = 0, vw = 0, rh = 0;
   for(int r = 0; r < PN_ROWS; r++)
     {
      TextGetSize(g_pk[r], w, h); if((int)w > kw) kw = (int)w; if((int)h > rh) rh = (int)h;
      TextGetSize(g_pv[r], w, h); if((int)w > vw) vw = (int)w;
     }
   if(kw <= 0 || rh <= 0)                   // measuring not available: rough estimate
     { kw = (int)(InpFont * 8.0 * 20); rh = InpFont * 2; vw = (int)(InpFont * 8.0 * 30); }
   kw = (int)(kw * 1.12); vw = (int)(vw * 1.12);          // safety margin: fonts differ between PCs
   g_pnKeyW = kw + 16;
   g_pnRH = MathMax(18, rh + 4);
   g_pnW = MathMax(260, 10 + g_pnKeyW + vw + 28);
  }

void PanelOrigin(int &x0, int &y0)          // top-left pixel of the panel; every panel object is anchored top-left, so right/bottom corners are computed from the chart size
  {
   int corner = PanelCorner();
   int W = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS), H = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   bool right = (corner == CORNER_RIGHT_UPPER || corner == CORNER_RIGHT_LOWER);
   bool bottom = (corner == CORNER_LEFT_LOWER || corner == CORNER_RIGHT_LOWER);
   x0 = right ? W - PN_M - g_pnW : PN_M;
   y0 = bottom ? H - PnMarginY(corner) - PnH() : PnMarginY(corner);
   if(x0 < 0) x0 = 0;
   if(y0 < 0) y0 = 0;
  }

void PanelDrawRow(const int row, const int x0, const int y0)
  {
   int yy = y0 + 6 + row * g_pnRH;
   for(int k = 0; k < 2; k++)
     {
      string n = PFX + "PN" + IntegerToString(row) + "_" + IntegerToString(k);
      if(ObjectFind(0, n) < 0) ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
      int xin = (k == 0) ? 10 : 10 + g_pnKeyW;               // x inside the panel
      ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
      ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x0 + xin);
      ObjectSetInteger(0, n, OBJPROP_YDISTANCE, yy);
      ObjectSetInteger(0, n, OBJPROP_COLOR, k == 0 ? (row == 0 ? BRAND_GREEN : C_GRAY) : g_pc[row]);
      ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpFont);
      ObjectSetString(0, n, OBJPROP_FONT, "Consolas");
      ObjectSetString(0, n, OBJPROP_TEXT, k == 0 ? g_pk[row] : g_pv[row]);
      ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, n, OBJPROP_ZORDER, 10);
     }
  }

void PanelBack(const int x0, const int y0)
  {
   string n = PFX + "PNBG";
   if(ObjectFind(0, n) < 0) ObjectCreate(0, n, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x0);
   ObjectSetInteger(0, n, OBJPROP_YDISTANCE, y0);
   ObjectSetInteger(0, n, OBJPROP_XSIZE, g_pnW);
   ObjectSetInteger(0, n, OBJPROP_YSIZE, PnH());
   ObjectSetInteger(0, n, OBJPROP_BGCOLOR, Tint(BRAND_DARK, 0.92));
   ObjectSetInteger(0, n, OBJPROP_COLOR, Tint(BRAND_GREEN, 0.5));
   ObjectSetInteger(0, n, OBJPROP_BORDER_TYPE, 1);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, n, OBJPROP_BACK, false);
   ObjectSetInteger(0, n, OBJPROP_ZORDER, 9);
  }

string DirTxt(const int d) { return d == 1 ? "Bullish " + Up() : (d == -1 ? "Bearish " + Dn() : "-"); }
color  DirCol(const int d) { return d == 1 ? C_BULL : (d == -1 ? C_BEAR : C_GRAY); }

void UpdatePanel()
  {
   if(!InpShowPanel)
     {
      ObjectsDeleteAll(0, PFX + "PN");
      return;
     }
   if(g_n < 3) return;
   ChooseCorner();
   int L = g_n - 2;
   datetime u = NowUtc();
   string actNm = "Outside sessions";
   color  actCol = C_GRAY;
   for(int k = 0; k < 8; k++)
      if(g_s[k].on && g_s[k].active && k != 6 && k != 7) { actNm = g_s[k].name; actCol = g_s[k].col; }
   if(SbAny(L)) actNm += " + Silver Bullet";
   if(InpMcOn && Fl(L, 15)) actNm += " + Macro";
   datetime bestT = 0; string bestN = "-";
   for(int k = 0; k < 8; k++)
     {
      if(!g_s[k].on || k == 6 || k == 7) continue;
      datetime c = NextStartUtc(g_s[k].a);
      if(c > 0 && (bestT == 0 || c < bestT)) { bestT = c; bestN = g_s[k].name; }
     }
   string nextTxt = "-";
   if(bestT > 0)
     {
      int bd = (int)((bestT - u) / 60);
      nextTxt = bestN + " in " + IntegerToString(bd / 60) + "h " + IntegerToString(bd % 60) + "m (" + Hm(ToZone(bestT, InpTzDisp)) + ")";
     }
   string clock = Hm(ToZone(u, TM_Z_TEHRAN)) + "  |  " + Hm(ToNY(u));
   string mo = IsNa(g_moPx) ? "-" : (g_c[L] > g_moPx ? "above " : "below ") + Fp(g_moPx);
   color  moC = IsNa(g_moPx) ? C_GRAY : (g_c[L] > g_moPx ? C_BULL : C_BEAR);
   string ph = Fl(L, 12) ? "Accumulation" : (Fl(L, 13) ? "Manipulation" : (Fl(L, 14) ? "Distribution" : "-"));
   string po = ph;
   if(g_po3Bias != 0) { string dw = (g_po3Bias == 1) ? "buy" : "sell"; po += "  -> " + dw + " day"; }
   string rd = g_raidDir == 0 ? "-" : g_raidName + (g_raidRej ? " (raid)" : " (run)") + " - " + IntegerToString(L - g_raidBar) + " bars ago";
   string sm = g_smtDir == 0 ? "-" : DirTxt(g_smtDir) + " - " + IntegerToString(L - g_smtBar) + " bars ago";
   string asr = IsNa(g_s[0].fHi) ? "-" : Fp(g_s[0].fLo) + " - " + Fp(g_s[0].fHi);
   // broker clock check: the assumed GMT offset must match the real server clock
   string bk = "GMT";
   if(InpBrokerGmt >= 0) bk += "+";
   bk += IntegerToString(InpBrokerGmt);
   if(InpBrokerDst != TM_DST_NONE) bk += " (+1 summer)";
   color  bkC = C_GRAY;
#ifdef __MQL5__
   datetime srv = TimeTradeServer(), gm = TimeGMT();
   if(srv > 0 && gm > 0)
     {
      int real = (int)MathRound((double)(srv - gm) / 1800.0);          // half-hour units
      int mine = (int)MathRound((double)(srv - ServerToUtc(srv)) / 1800.0);
      if(real != mine) { bk += "  CHECK offset!"; bkC = C_BEAR; } else bkC = C_BULL;
     }
#endif
   PanelRow(0, "Crypex ICT Time", g_intra ? "" : "use <= H4", C_ORANGE);
   PanelRow(1, "Tehran | New York", clock, clrWhite);
   PanelRow(2, "Now", actNm, actCol);
   PanelRow(3, "Next", nextTxt, clrWhite);
   PanelRow(4, "Midnight Open", mo, moC);
   PanelRow(5, "Power of 3", po, g_po3Bias == 0 ? clrWhite : DirCol(g_po3Bias));
   PanelRow(6, "Last liquidity taken", rd, DirCol(g_raidDir));
   PanelRow(7, "SMT", sm, DirCol(g_smtDir));
   PanelRow(8, "Asia range", asr, clrWhite);
   PanelRow(9, "Broker clock", bk, bkC);
   PanelMeasure();
   int px = 0, py = 0;
   PanelOrigin(px, py);
   PanelBack(px, py);
   for(int r = 0; r < PN_ROWS; r++) PanelDrawRow(r, px, py);
  }

void PaintWatermark()
  {
   if(!InpShowWm)
     {
      ObjectDelete(0, PFX + "WM1");
      ObjectDelete(0, PFX + "WM2");
      return;
     }
   long bg = ChartGetInteger(0, CHART_COLOR_BACKGROUND);
   int r = (int)(bg & 0xFF), g = (int)((bg >> 8) & 0xFF), b = (int)((bg >> 16) & 0xFF);
   bool light = (r * 299 + g * 587 + b * 114) / 1000 > 128;
   color orgCol = light ? C'30,34,41' : clrWhite;
   TextSetFont("Arial Black", -InpWmSize * 10);            // measure the real text width so the two parts never overlap
   uint tw = 0, th = 0;
   TextGetSize("crypex", tw, th);
   int wCrypex = (tw > 0) ? (int)tw + 4 : (int)(InpWmSize * 7.0);
   string n1 = PFX + "WM1", n2 = PFX + "WM2";
   for(int k = 0; k < 2; k++)
     {
      string n = (k == 0) ? n1 : n2;
      if(ObjectFind(0, n) < 0) ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_LOWER);
      ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, n, OBJPROP_XDISTANCE, InpWmGap + (k == 0 ? 0 : wCrypex));
      ObjectSetInteger(0, n, OBJPROP_YDISTANCE, InpWmBottom);
      ObjectSetInteger(0, n, OBJPROP_COLOR, k == 0 ? BRAND_GREEN : orgCol);
      ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpWmSize);
      ObjectSetString(0, n, OBJPROP_FONT, "Arial Black");
      ObjectSetString(0, n, OBJPROP_TEXT, k == 0 ? "crypex" : ".org");
      ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, n, OBJPROP_ZORDER, 10);
     }
  }

//+------------------------------------------------------------------+
//| alerts                                                           |
//+------------------------------------------------------------------+
void Notify(const string msg)
  {
   string full = "Crypex ICT Time " + _Symbol + ": " + msg;
   if(InpAlertPopup) Alert(full);
   if(InpAlertPush)  SendNotification(full);
   if(InpAlertMail)  SendMail("Crypex ICT Time", full);
  }

void FireAlerts()
  {
   string names[9] = {"London killzone started", "New York AM killzone started", "New York PM killzone started", "Silver Bullet window started",
                      "Buy-side liquidity raided (bearish expectation)", "Sell-side liquidity raided (bullish expectation)",
                      "Liquidity run (level broken and held, not a raid)", "Judas Swing confirmed", "SMT divergence"};
   for(int k = 0; k < 9; k++) if(g_alLast[k]) Notify(names[k]);
  }

void FlagAlerts(const int L)
  {
   for(int k = 0; k < 9; k++) g_al[k] = false;
   g_al[0] = InpLonOn && Fl(L, 1) && !Fl(L - 1, 1);
   g_al[1] = InpNyaOn && Fl(L, 2) && !Fl(L - 1, 2);
   g_al[2] = InpNypOn && Fl(L, 5) && !Fl(L - 1, 5);
   g_al[3] = SbAny(L) && !SbAny(L - 1);
   g_al[4] = (g_evRaid == -1);
   g_al[5] = (g_evRaid == 1);
   g_al[6] = (g_evRun != 0);
   g_al[7] = (g_evJudas != 0);
   g_al[8] = (g_evSmt != 0);
   for(int k = 0; k < 9; k++) g_alLast[k] = g_al[k];
  }

//+------------------------------------------------------------------+
//| replay the whole window bar by bar, then draw the final state    |
//+------------------------------------------------------------------+
void Rebuild()
  {
   ObjectsDeleteAll(0, PFX);
   g_bg = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
   g_intra = (PeriodSeconds(_Period) <= 14400);
   DetectMarket();
   InitSessions();
   ResetState();
   BuildSeries();
   const int L = g_n - 2;                          // last CLOSED bar: no repainting
   for(int i = 0; i <= L; i++) ProcessBar(i);
   FlagAlerts(L);
   g_nItem = 0;
   DrawShading();
   DrawBoxes();
   DrawProjections();
   DrawLiquidity();
   DrawAnchors();
   DrawIpda();
   FlushItems();
   DrawMarks();
   UpdatePanel();
   PaintWatermark();
   if(g_firstDone && g_t[g_n - 1] != g_lastAlertBar) FireAlerts();
   g_lastAlertBar = g_t[g_n - 1];
   g_firstDone = true;
   ChartRedraw();
  }


//+------------------------------------------------------------------+
int OnInit()
  {
   IndicatorSetString(INDICATOR_SHORTNAME, "Crypex ICT Time");
   if(InpRightMargin > 0)
     {
      ChartSetInteger(0, CHART_SHIFT, true);
      ChartSetDouble(0, CHART_SHIFT_SIZE, (double)MathMin(InpRightMargin, 50));
     }
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PFX);
  }

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_CHART_CHANGE && g_n >= 3) UpdatePanel();      // scroll / zoom / resize: re-pick the panel corner
  }

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
                const double &open[], const double &high[], const double &low[], const double &close[],
                const long &tick_volume[], const long &volume[], const int &spread[])
  {
   if(rates_total < 300) return 0;
   bool ser = ArrayGetAsSeries(time);               // normally false (oldest first); handled either way
   datetime newest = ser ? time[0] : time[rates_total - 1];
   bool newBar = (newest != g_lastBar) || prev_calculated == 0;
   if(!newBar)
     {
      UpdatePanel();                               // the clock runs on every tick
      return rates_total;
     }
   g_lastBar = newest;
   int start = MathMax(0, rates_total - InpMaxBars);
   g_n = rates_total - start;
   ArrayResize(g_t, g_n); ArrayResize(g_o, g_n); ArrayResize(g_h, g_n); ArrayResize(g_l, g_n); ArrayResize(g_c, g_n);
   for(int i = 0; i < g_n; i++)
     {
      int s = ser ? rates_total - 1 - (start + i) : start + i;
      g_t[i] = time[s];
      g_o[i] = open[s];
      g_h[i] = high[s];
      g_l[i] = low[s];
      g_c[i] = close[s];
     }
   Rebuild();
   return rates_total;
  }
//+------------------------------------------------------------------+
ict-time-crypex.mq4 · MQL4 · ۱٬۵۹۱ خط
//+------------------------------------------------------------------+
//| Crypex ICT Time (MT4)                                          |
//| Sessions & killzones, Silver Bullet, CBDR + projections,         |
//| Midnight / 08:30 / 09:30 / Weekly open, PDH/PDL/PWH/PWL, EQH/EQL,|
//| Raid / Break / Run, Judas Swing, Power of 3, IPDA, SMT, panel    |
//| with Tehran + New York clocks                                    |
//| © Crypex.org — All rights reserved. | حق نشر محفوظ برای Crypex.org |
//| License: personal, non-commercial use only. Redistribution or    |
//| resale without written permission is prohibited.                 |
//| مجوز: استفاده شخصی و غیرتجاری. بازنشر یا فروش مجدد بدون          |
//| اجازه‌ی کتبی ممنوع است.                                           |
//| Educational tool, not investment advice.                         |
//+------------------------------------------------------------------+
#property copyright "Crypex.org"
#property link      "https://crypex.org"
#property version   "1.00"
#property strict
#property indicator_chart_window

enum ENUM_TM_DST    { TM_DST_US, TM_DST_EU, TM_DST_NONE };
enum ENUM_TM_ZONE   { TM_Z_TEHRAN, TM_Z_NY, TM_Z_UTC, TM_Z_LONDON };
enum ENUM_TM_MARKET { TM_M_AUTO, TM_M_FOREX, TM_M_METALS, TM_M_CRYPTO };
enum ENUM_TM_STYLE  { TM_ST_FILLED, TM_ST_LIGHT, TM_ST_BORDER, TM_ST_OFF };
enum ENUM_TM_CORNER { TM_C_AUTO, TM_C_TOPRIGHT, TM_C_TOPLEFT, TM_C_BOTTOMRIGHT, TM_C_BOTTOMLEFT };

// ---- General & time zones
input int             InpBrokerGmt    = 2;               // Broker server time: GMT offset in WINTER (e.g. 2 for GMT+2 / GMT+3 brokers)
input ENUM_TM_DST     InpBrokerDst    = TM_DST_US;       // Broker daylight-saving rule (US = GMT+2/+3 New-York-close brokers)
input ENUM_TM_ZONE    InpTzDisp       = TM_Z_TEHRAN;     // Show times on chart in
input ENUM_TM_MARKET  InpMarket       = TM_M_AUTO;       // Market type (Auto = detect from the symbol name)
input int             InpHistDays     = 2;               // Days of sessions to keep on chart
input int             InpFont         = 9;               // Label font size
input bool            InpShowSessLbl  = true;            // Session name + time labels
input double          InpLblGap       = 3.0;             // Right-side labels: min vertical gap (% of recent range)
input int             InpLblPad       = 3;               // Right-side labels: offset from last bar (bars)
input int             InpRightMargin  = 15;              // Chart right margin (% of the chart, 0 = leave the chart alone)
input int             InpShadeDays    = 10;              // Days of Silver Bullet / Macro shading to draw
// ---- Killzones / sessions
input ENUM_TM_STYLE   InpSessStyle    = TM_ST_LIGHT;     // Session box style
input bool            InpAsiaOn       = true;            // Asian range
input string          InpAsiaR        = "2000-0000";     //   Asian range (New York time)
input color           InpAsiaC        = C'156,39,176';
input bool            InpLonOn        = true;            // London killzone
input string          InpLonR         = "0200-0500";     //   London (New York time)
input color           InpLonC         = C'41,98,255';
input bool            InpNyaOn        = true;            // New York AM killzone
input string          InpNyaR         = "0700-1000";     //   NY AM (New York time)
input color           InpNyaC         = C'8,153,129';
input bool            InpLcOn         = false;           // London Close killzone
input string          InpLcR          = "1000-1200";     //   London Close
input color           InpLcC          = C'255,152,0';
input bool            InpNylOn        = false;           // New York Lunch
input string          InpNylR         = "1200-1300";     //   NY Lunch
input color           InpNylC         = C'120,123,134';
input bool            InpNypOn        = false;           // New York PM killzone
input string          InpNypR         = "1330-1600";     //   NY PM
input color           InpNypC         = C'233,30,99';
input bool            InpSessLvlOn    = false;           // Turn finished session highs/lows into liquidity levels
// ---- Silver Bullet & Macros
input bool            InpSbOn         = true;            // Silver Bullet windows
input color           InpSbC          = C'255,214,0';
input string          InpSb1R         = "0300-0400";     //   SB 1 (London)
input string          InpSb2R         = "1000-1100";     //   SB 2 (NY AM)
input string          InpSb3R         = "1400-1500";     //   SB 3 (NY PM)
input bool            InpMcOn         = false;           // Macro windows (ICT macro times)
input color           InpMcC          = C'255,109,0';
// ---- CBDR / Flout / Asian range + SD projections
input bool            InpCbdrOn       = true;            // CBDR
input string          InpCbdrR        = "1400-2000";     //   CBDR (New York time)
input color           InpCbdrC        = C'0,188,212';
input bool            InpFloutOn      = false;           // Flout
input string          InpFloutR       = "1400-0000";     //   Flout
input color           InpFloutC       = C'139,195,74';
input bool            InpProjCbdr     = true;            // Project CBDR deviations
input bool            InpProjAsia     = false;           // Project Asian range deviations
input bool            InpProjFlout    = false;           // Project Flout deviations
input int             InpSdCount      = 2;               // Number of deviations each side (1-4)
input double          InpSdStep       = 1.0;             // Deviation step (x range)
input double          InpFiltPips     = 40.0;            // Forex: skip projection if range wider than (pips, 0 = off)
input color           InpProjUpC      = C'8,153,129';
input color           InpProjDnC      = C'242,54,69';
// ---- Time anchors
input bool            InpAncMidOn     = true;            // Midnight Open (00:00)
input color           InpAncMidC      = C'255,214,0';
input bool            InpAnc830On     = true;            // 08:30 Open
input color           InpAnc830C      = C'255,152,0';
input bool            InpAnc930On     = false;           // 09:30 Open
input color           InpAnc930C      = C'233,30,99';
input bool            InpAncWkOn      = false;           // Weekly Open
input color           InpAncWkC       = C'0,188,212';
// ---- Liquidity & raids
input bool            InpPdOn         = true;            // PDH / PDL
input color           InpPdC          = C'41,98,255';
input bool            InpPwOn         = true;            // PWH / PWL
input color           InpPwC          = C'156,39,176';
input bool            InpPmOn         = false;           // PMH / PML
input color           InpPmC          = C'233,30,99';
input color           InpSessLC       = C'120,123,134';
input bool            InpEqOn         = true;            // EQH / EQL (equal highs / lows)
input color           InpEqC          = C'255,109,0';
input double          InpEqTol        = 0.10;            // EQ tolerance (x ATR)
input bool            InpRaidLbl      = true;            // Mark raids and runs
input int             InpPendBars     = 3;               // Bars to wait before a break counts as a Run
input int             InpKeepSwept    = 30;              // Keep swept levels for (bars)
input int             InpMaxLiq       = 20;              // Max liquidity levels on chart
// ---- Judas Swing & Power of 3
input bool            InpJdOn         = true;            // Judas Swing
input string          InpJdR          = "0000-0500";     //   Judas search window (New York time)
input bool            InpPo3On        = true;            // Power of 3 (AMD)
input int             InpPo3Fresh     = 12;              // PO3: reclaim must follow the sweep within (bars)
input string          InpPo3AR        = "1900-0100";     //   Accumulation
input string          InpPo3MR        = "0100-0700";     //   Manipulation
input string          InpPo3DR        = "0700-1300";     //   Distribution
// ---- IPDA & SMT
input int             InpSwLen        = 5;               // Swing length
input bool            InpIpdaOn       = false;           // IPDA look-back highs / lows
input int             InpIpda1        = 20;              // IPDA 1 (days)
input int             InpIpda2        = 40;              // IPDA 2 (days)
input int             InpIpda3        = 60;              // IPDA 3 (days)
input bool            InpSmtOn        = false;           // SMT divergence (experimental)
input string          InpSmtSym       = "DXY";           // Correlated symbol (as named by your broker)
input bool            InpSmtInv       = true;            // Inverse correlation (DXY vs gold / EURUSD)
// ---- Panel & watermark
input bool            InpShowPanel    = true;            // Show info panel
input ENUM_TM_CORNER  InpPanelPos     = TM_C_AUTO;       // Panel position (Auto = the emptiest corner of the visible chart)
input bool            InpShowWm       = true;            // Show crypex.org watermark
input int             InpWmSize       = 18;
input int             InpWmGap        = 60;              // Gap from left (px), clears the chart logo
input int             InpWmBottom     = 14;              // Gap from bottom (px)
// ---- Alerts
input bool            InpAlertPopup   = true;
input bool            InpAlertPush    = false;
input bool            InpAlertMail    = false;
// ---- Calculation
input int             InpMaxBars      = 6000;            // Bars used for calculation

const string PFX = "CRX_TIME_";
const double TM_NA = DBL_MAX;
const color  C_BULL = C'8,153,129', C_BEAR = C'242,54,69', C_BLUE = C'41,98,255', C_ORANGE = C'255,152,0', C_PURPLE = C'156,39,176';
const color  C_YELLOW = C'255,214,0', C_GRAY = C'120,123,134', C_CYAN = C'0,188,212', C_PINK = C'233,30,99';
const color  BRAND_GREEN = C'11,163,28', BRAND_DARK = C'30,34,41';

struct TmSess { string name; int a; int b; bool on; color col; bool proj; bool lvl; bool kz; bool active; int sBar; datetime sTime;
                double hi; double lo; int hiBar; int loBar; double fHi; double fLo; int fHiBar; int fLoBar; int boxId; };
struct TmBox  { int id; int s; int i0; int i1; double hi; double lo; datetime sTime; };
struct TmLiq  { double price; string name; int dir; int kind; int bar; int ob; bool swept; int sweptBar; bool pend; int hitBar; double hitExt; int mk; };
struct TmMark { int id; int bar; double price; string text; color col; int dir; int kind; bool alive; };
struct TmProj { bool ok; int i0; double hi; double lo; };
struct TmAnc  { string name; bool on; color col; bool has; int bar; double px; bool exact; };
struct TmAncH { int x1; int x2; double px; color col; };
struct TmItem { string text; color col; double price; };

// ---- chart copy (oldest first) and per-bar derived series
datetime g_t[];
double   g_o[], g_h[], g_l[], g_c[];
double   g_atr[], g_x2h[], g_x2l[];
datetime g_ny[];                       // New York wall clock of every bar (as a plain datetime)
int      g_mn[], g_dow[], g_day[], g_mon[], g_fl[];
int      g_n = 0;

TmSess   g_s[8];
TmBox    g_box[];   int g_nBox = 0;   int g_boxId = 0;
TmLiq    g_lq[];    int g_nLq = 0;
TmMark   g_mk[];    int g_nMk = 0;    int g_mkId = 0;
TmProj   g_pj[8];
TmAnc    g_an[4];
TmAncH   g_ah[];    int g_nAh = 0;

bool     g_intra = true, g_isCrypto = false, g_isForex = false;
// state (St object of the Pine version)
double   g_swHi = TM_NA, g_pSwHi = TM_NA, g_swLo = TM_NA, g_pSwLo = TM_NA;
int      g_swHiBar = 0, g_swLoBar = 0;
int      g_raidDir = 0, g_raidBar = -9999, g_raidSc = 0, g_raidScBar = -1, g_raidMk = -1;
double   g_raidPx = TM_NA, g_raidPen = 0.0;
string   g_raidName = "";
bool     g_raidRej = false;
double   g_curDH = TM_NA, g_curDL = TM_NA, g_curWH = TM_NA, g_curWL = TM_NA, g_curMH = TM_NA, g_curML = TM_NA;
int      g_curDHB = 0, g_curDLB = 0, g_curWHB = 0, g_curWLB = 0, g_curMHB = 0, g_curMLB = 0;
bool     g_jdDone = false;
int      g_jdPot = 0;
double   g_jdExt = TM_NA, g_moPx = TM_NA;
int      g_po3DnBar = -9999, g_po3UpBar = -9999, g_po3Bias = 0;
double   g_po3DnExt = TM_NA, g_po3UpExt = TM_NA, g_po3Ext = TM_NA;
double   g_phM = TM_NA, g_phS = TM_NA, g_plM = TM_NA, g_plS = TM_NA;
int      g_smtDir = 0, g_smtBar = -9999;
int      g_evRaid = 0, g_evRun = 0, g_evJudas = 0, g_evSmt = 0;
bool     g_al[9];
bool     g_alLast[9];
color    g_bg = clrBlack;
datetime g_lastBar = 0, g_lastAlertBar = 0;
bool     g_firstDone = false;
int      g_cnt = 0;
string   g_macroR[9] = {"0233-0300", "0403-0430", "0850-0910", "0950-1010", "1050-1110", "1150-1210", "1310-1340", "1450-1510", "1515-1545"};
int      g_macroA[9], g_macroB[9];
int      g_sb1A, g_sb1B, g_sb2A, g_sb2B, g_sb3A, g_sb3B, g_jdA, g_jdB, g_poAA, g_poAB, g_poMA, g_poMB, g_poDA, g_poDB;

//+------------------------------------------------------------------+
//| time zones (MT has no time-zone database, so the rules are coded) |
//+------------------------------------------------------------------+
datetime NthSunday(const int y, const int m, const int n)
  {
   MqlDateTime d; d.year = y; d.mon = m; d.day = 1; d.hour = 0; d.min = 0; d.sec = 0;
   datetime first = StructToTime(d);
   MqlDateTime f; TimeToStruct(first, f);
   int add = (7 - f.day_of_week) % 7;
   return first + (datetime)((add + 7 * (n - 1)) * 86400);
  }

datetime LastSunday(const int y, const int m)
  {
   MqlDateTime d; d.year = (m == 12 ? y + 1 : y); d.mon = (m == 12 ? 1 : m + 1); d.day = 1; d.hour = 0; d.min = 0; d.sec = 0;
   datetime lastDay = StructToTime(d) - 86400;
   MqlDateTime f; TimeToStruct(lastDay, f);
   return lastDay - (datetime)(f.day_of_week * 86400);
  }

bool UsDstUtc(const datetime u)               // 2nd Sunday of March 02:00 EST  ->  1st Sunday of November 02:00 EDT
  {
   MqlDateTime d; TimeToStruct(u, d);
   datetime s = NthSunday(d.year, 3, 2) + 7 * 3600;
   datetime e = NthSunday(d.year, 11, 1) + 6 * 3600;
   return u >= s && u < e;
  }

bool EuDstUtc(const datetime u)               // last Sunday of March 01:00 UTC  ->  last Sunday of October 01:00 UTC
  {
   MqlDateTime d; TimeToStruct(u, d);
   return u >= LastSunday(d.year, 3) + 3600 && u < LastSunday(d.year, 10) + 3600;
  }

bool BrokerDstAt(const datetime u)
  {
   if(InpBrokerDst == TM_DST_US) return UsDstUtc(u);
   if(InpBrokerDst == TM_DST_EU) return EuDstUtc(u);
   return false;
  }

datetime ServerToUtc(const datetime ts)
  {
   datetime summer = ts - (datetime)((InpBrokerGmt + 1) * 3600);
   if(BrokerDstAt(summer)) return summer;
   return ts - (datetime)(InpBrokerGmt * 3600);
  }

datetime ToNY(const datetime u)  { return u + (datetime)(UsDstUtc(u) ? -4 * 3600 : -5 * 3600); }

datetime NyToUtc(const datetime ny)
  {
   datetime u = ny + 5 * 3600;
   if(ToNY(u) != ny) u = ny + 4 * 3600;
   return u;
  }

datetime ToZone(const datetime u, const ENUM_TM_ZONE z)
  {
   if(z == TM_Z_NY) return ToNY(u);
   if(z == TM_Z_UTC) return u;
   if(z == TM_Z_LONDON) return u + (datetime)(EuDstUtc(u) ? 3600 : 0);
   return u + 12600;                           // Tehran: UTC+3:30, no daylight saving
  }

datetime NowUtc()
  {
   datetime g = TimeGMT();
   if(g > 0) return g;
   return ServerToUtc(TimeCurrent());
  }

string Dn() { return ShortToString(0x25BC); }
string Up() { return ShortToString(0x25B2); }
string Ck() { return ShortToString(0x2713); }

string Num2(const double v)
  {
   string t = DoubleToString(v, 2);
   while(StringLen(t) > 0 && StringSubstr(t, StringLen(t) - 1, 1) == "0") t = StringSubstr(t, 0, StringLen(t) - 1);
   if(StringLen(t) > 0 && StringSubstr(t, StringLen(t) - 1, 1) == ".") t = StringSubstr(t, 0, StringLen(t) - 1);
   return t;
  }

string Two(const int v) { return (v < 10 ? "0" : "") + IntegerToString(v); }

string Hm(const datetime clock)
  {
   MqlDateTime d; TimeToStruct(clock, d);
   return Two(d.hour) + ":" + Two(d.min);
  }

string ZoneTag() { return InpTzDisp == TM_Z_NY ? "NY" : (InpTzDisp == TM_Z_UTC ? "UTC" : (InpTzDisp == TM_Z_LONDON ? "LON" : "")); }

//+------------------------------------------------------------------+
//| generic helpers                                                  |
//+------------------------------------------------------------------+
bool IsNa(const double v) { return v == TM_NA; }
int BarSec() { return PeriodSeconds(_Period); }

datetime TimeAt(const int k)
  {
   if(k < 0) return g_t[0];
   if(k <= g_n - 1) return g_t[k];
   return g_t[g_n - 1] + (datetime)((k - (g_n - 1)) * BarSec());
  }

color Tint(const color c, const double alpha)   // blend with the chart background (MT objects have no transparency)
  {
   int cr = (int)(c & 0xFF), cg = (int)((c >> 8) & 0xFF), cb = (int)((c >> 16) & 0xFF);
   int br = (int)(g_bg & 0xFF), bgc = (int)((g_bg >> 8) & 0xFF), bb = (int)((g_bg >> 16) & 0xFF);
   int r = (int)(cr * alpha + br * (1.0 - alpha));
   int g = (int)(cg * alpha + bgc * (1.0 - alpha));
   int b = (int)(cb * alpha + bb * (1.0 - alpha));
   return (color)(r | (g << 8) | (b << 16));
  }

string Fp(const double p) { return DoubleToString(p, _Digits); }

string NextName(const string kind)
  {
   g_cnt++;
   return PFX + kind + IntegerToString(g_cnt);
  }

string MkLine(const string kind, const datetime t1, const double p1, const datetime t2, const double p2, const color c, const int style, const int width)
  {
   string n = NextName(kind);
   ObjectCreate(0, n, OBJ_TREND, 0, t1, p1, t2, p2);
   ObjectSetInteger(0, n, OBJPROP_COLOR, c);
   ObjectSetInteger(0, n, OBJPROP_STYLE, style);
   ObjectSetInteger(0, n, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, n, OBJPROP_RAY_RIGHT, false);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   return n;
  }

string MkText(const string kind, const datetime t, const double p, const string text, const color c, const int anchor)
  {
   string n = NextName(kind);
   ObjectCreate(0, n, OBJ_TEXT, 0, t, p);
   ObjectSetString(0, n, OBJPROP_TEXT, text);
   ObjectSetInteger(0, n, OBJPROP_COLOR, c);
   ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpFont);
   ObjectSetInteger(0, n, OBJPROP_ANCHOR, anchor);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   return n;
  }

string MkRect(const string kind, const datetime t1, const double top, const datetime t2, const double bottom, const color col, const bool fill)
  {
   string n = NextName(kind);
   ObjectCreate(0, n, OBJ_RECTANGLE, 0, t1, top, t2, bottom);
   ObjectSetInteger(0, n, OBJPROP_COLOR, col);
   ObjectSetInteger(0, n, OBJPROP_FILL, fill);
   ObjectSetInteger(0, n, OBJPROP_BACK, true);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   return n;
  }

//+------------------------------------------------------------------+
//| input parsing, symbol type                                       |
//+------------------------------------------------------------------+
void ParseRange(const string s, const int defA, const int defB, int &a, int &b)
  {
   a = defA; b = defB;
   if(StringLen(s) < 9 || StringSubstr(s, 4, 1) != "-") return;
   int a1 = (int)StringToInteger(StringSubstr(s, 0, 2)), a2 = (int)StringToInteger(StringSubstr(s, 2, 2));
   int b1 = (int)StringToInteger(StringSubstr(s, 5, 2)), b2 = (int)StringToInteger(StringSubstr(s, 7, 2));
   if(a1 < 0 || a1 > 24 || b1 < 0 || b1 > 24 || a2 < 0 || a2 > 59 || b2 < 0 || b2 > 59) return;
   a = a1 * 60 + a2;
   b = b1 * 60 + b2;
   if(b == 1440) b = 0;
  }

bool HasAny(const string s, const string list)    // list separated by ","
  {
   int p = 0;
   while(p < StringLen(list))
     {
      int q = StringFind(list, ",", p);
      if(q < 0) q = StringLen(list);
      string w = StringSubstr(list, p, q - p);
      if(StringLen(w) > 0 && StringFind(s, w) >= 0) return true;
      p = q + 1;
     }
   return false;
  }

void DetectMarket()
  {
   string s = _Symbol;
   StringToUpper(s);
   bool crypto = HasAny(s, "BTC,ETH,XRP,LTC,DOGE,SOL,ADA,BNB,DOT,AVAX,MATIC,TRX,LINK,SHIB");
   bool forex = false;
   if(!crypto && StringLen(s) >= 6)
     {
      string cur = "USD,EUR,GBP,JPY,CHF,CAD,AUD,NZD";
      string a = StringSubstr(s, 0, 3), b = StringSubstr(s, 3, 3);
      forex = (StringFind(cur, a) >= 0 && StringFind(cur, b) >= 0);
     }
   g_isCrypto = (InpMarket == TM_M_CRYPTO) || (InpMarket == TM_M_AUTO && crypto);
   g_isForex  = (InpMarket == TM_M_FOREX)  || (InpMarket == TM_M_AUTO && forex);
  }

//+------------------------------------------------------------------+
//| series: ATR (RMA), New York clock, flags                         |
//+------------------------------------------------------------------+
bool InSess(const int m, const int a, const int b)
  {
   if(a < b) return m >= a && m < b;
   return m >= a || m < b;
  }

void BuildSeries()
  {
   ArrayResize(g_atr, g_n); ArrayResize(g_ny, g_n); ArrayResize(g_mn, g_n); ArrayResize(g_dow, g_n);
   ArrayResize(g_day, g_n); ArrayResize(g_mon, g_n); ArrayResize(g_fl, g_n);
   // ATR(14): Wilder RMA of the true range (same as Pine ta.atr); 0 = not available yet
   double sum = 0.0;
   for(int i = 0; i < g_n; i++)
     {
      double tr = g_h[i] - g_l[i];
      if(i > 0) tr = MathMax(tr, MathMax(MathAbs(g_h[i] - g_c[i - 1]), MathAbs(g_l[i] - g_c[i - 1])));
      if(i < 13) { sum += tr; g_atr[i] = 0.0; }
      else if(i == 13) { sum += tr; g_atr[i] = sum / 14.0; }
      else g_atr[i] = (g_atr[i - 1] * 13.0 + tr) / 14.0;
     }
   int a1 = 0, b1 = 0;
   for(int i = 0; i < g_n; i++)
     {
      datetime u = ServerToUtc(g_t[i]);
      datetime ny = ToNY(u);
      g_ny[i] = ny;
      MqlDateTime d; TimeToStruct(ny, d);
      g_mn[i] = d.hour * 60 + d.min;
      g_dow[i] = d.day_of_week + 1;           // Pine numbering: Sunday = 1 ... Saturday = 7
      g_day[i] = d.day;
      g_mon[i] = d.mon;
      int f = 0;
      if(g_intra)
        {
         int m = g_mn[i];
         for(int k = 0; k < 8; k++) if(InSess(m, g_s[k].a, g_s[k].b)) f |= (1 << k);
         if(InSess(m, g_sb1A, g_sb1B)) f |= (1 << 8);
         if(InSess(m, g_sb2A, g_sb2B)) f |= (1 << 9);
         if(InSess(m, g_sb3A, g_sb3B)) f |= (1 << 10);
         if(InSess(m, g_jdA, g_jdB))   f |= (1 << 11);
         if(InSess(m, g_poAA, g_poAB)) f |= (1 << 12);
         if(InSess(m, g_poMA, g_poMB)) f |= (1 << 13);
         if(InSess(m, g_poDA, g_poDB)) f |= (1 << 14);
         bool mc = false;
         for(int k = 0; k < 9; k++) if(InSess(m, g_macroA[k], g_macroB[k])) mc = true;
         if(mc) f |= (1 << 15);
        }
      g_fl[i] = f;
     }
   // correlated symbol for SMT (inverse: -low / -high)
   ArrayResize(g_x2h, g_n); ArrayResize(g_x2l, g_n);
   for(int i = 0; i < g_n; i++) { g_x2h[i] = TM_NA; g_x2l[i] = TM_NA; }
   if(InpSmtOn && InpSmtSym != "" && InpSmtSym != _Symbol)
      for(int i = 0; i < g_n; i++)
        {
         int sh = iBarShift(InpSmtSym, _Period, g_t[i], true);
         if(sh < 0) continue;
         double hh = iHigh(InpSmtSym, _Period, sh), ll = iLow(InpSmtSym, _Period, sh);
         if(hh <= 0 || ll <= 0) continue;
         g_x2h[i] = InpSmtInv ? -ll : hh;
         g_x2l[i] = InpSmtInv ? -hh : ll;
        }
  }

bool Fl(const int i, const int bit) { return i >= 0 && (g_fl[i] & (1 << bit)) != 0; }
bool SbAny(const int i)  { return InpSbOn && (Fl(i, 8) || Fl(i, 9) || Fl(i, 10)); }
bool KzAny(const int i)  { return (InpLonOn && Fl(i, 1)) || (InpNyaOn && Fl(i, 2)) || (InpLcOn && Fl(i, 3)) || (InpNypOn && Fl(i, 5)) || SbAny(i); }

//+------------------------------------------------------------------+
//| state                                                            |
//+------------------------------------------------------------------+
void ResetState()
  {
   g_nBox = 0; g_boxId = 0; g_nLq = 0; g_nMk = 0; g_mkId = 0; g_nAh = 0; g_cnt = 0;
   ArrayResize(g_box, 0); ArrayResize(g_lq, 0); ArrayResize(g_mk, 0); ArrayResize(g_ah, 0);
   for(int k = 0; k < 8; k++)
     {
      g_s[k].active = false; g_s[k].sBar = 0; g_s[k].sTime = 0; g_s[k].hi = TM_NA; g_s[k].lo = TM_NA; g_s[k].hiBar = 0; g_s[k].loBar = 0;
      g_s[k].fHi = TM_NA; g_s[k].fLo = TM_NA; g_s[k].fHiBar = 0; g_s[k].fLoBar = 0; g_s[k].boxId = -1;
      g_pj[k].ok = false;
     }
   for(int k = 0; k < 4; k++) { g_an[k].has = false; g_an[k].bar = 0; g_an[k].px = TM_NA; g_an[k].exact = true; }
   g_swHi = TM_NA; g_pSwHi = TM_NA; g_swLo = TM_NA; g_pSwLo = TM_NA; g_swHiBar = 0; g_swLoBar = 0;
   g_raidDir = 0; g_raidBar = -9999; g_raidSc = 0; g_raidScBar = -1; g_raidMk = -1; g_raidPx = TM_NA; g_raidPen = 0.0; g_raidName = ""; g_raidRej = false;
   g_curDH = TM_NA; g_curDL = TM_NA; g_curWH = TM_NA; g_curWL = TM_NA; g_curMH = TM_NA; g_curML = TM_NA;
   g_curDHB = 0; g_curDLB = 0; g_curWHB = 0; g_curWLB = 0; g_curMHB = 0; g_curMLB = 0;
   g_jdDone = false; g_jdPot = 0; g_jdExt = TM_NA; g_moPx = TM_NA;
   g_po3DnBar = -9999; g_po3UpBar = -9999; g_po3Bias = 0; g_po3DnExt = TM_NA; g_po3UpExt = TM_NA; g_po3Ext = TM_NA;
   g_phM = TM_NA; g_phS = TM_NA; g_plM = TM_NA; g_plS = TM_NA; g_smtDir = 0; g_smtBar = -9999;
   g_evRaid = 0; g_evRun = 0; g_evJudas = 0; g_evSmt = 0;
  }

void InitSessions()
  {
   string nm[8] = {"Asia", "London KZ", "NY AM KZ", "London Close KZ", "NY Lunch", "NY PM KZ", "CBDR", "Flout"};
   int    dA[8] = {1200, 120, 420, 600, 720, 810, 840, 840};
   int    dB[8] = {0, 300, 600, 720, 780, 960, 1200, 0};
   string rg[8]; bool on[8]; color cl[8]; bool pr[8];
   rg[0] = InpAsiaR; rg[1] = InpLonR; rg[2] = InpNyaR; rg[3] = InpLcR; rg[4] = InpNylR; rg[5] = InpNypR; rg[6] = InpCbdrR; rg[7] = InpFloutR;
   on[0] = InpAsiaOn; on[1] = InpLonOn; on[2] = InpNyaOn; on[3] = InpLcOn; on[4] = InpNylOn; on[5] = InpNypOn; on[6] = InpCbdrOn; on[7] = InpFloutOn;
   cl[0] = InpAsiaC; cl[1] = InpLonC; cl[2] = InpNyaC; cl[3] = InpLcC; cl[4] = InpNylC; cl[5] = InpNypC; cl[6] = InpCbdrC; cl[7] = InpFloutC;
   pr[0] = InpProjAsia; pr[1] = false; pr[2] = false; pr[3] = false; pr[4] = false; pr[5] = false; pr[6] = InpProjCbdr; pr[7] = InpProjFlout;
   bool   lv[8] = {true, true, true, true, false, true, false, false};
   bool   kz[8] = {false, true, true, true, false, true, false, false};
   for(int k = 0; k < 8; k++)
     {
      g_s[k].name = nm[k]; g_s[k].on = on[k]; g_s[k].col = cl[k]; g_s[k].proj = pr[k]; g_s[k].lvl = lv[k]; g_s[k].kz = kz[k];
      ParseRange(rg[k], dA[k], dB[k], g_s[k].a, g_s[k].b);
     }
   ParseRange(InpSb1R, 180, 240, g_sb1A, g_sb1B);
   ParseRange(InpSb2R, 600, 660, g_sb2A, g_sb2B);
   ParseRange(InpSb3R, 840, 900, g_sb3A, g_sb3B);
   ParseRange(InpJdR, 0, 300, g_jdA, g_jdB);
   ParseRange(InpPo3AR, 1140, 60, g_poAA, g_poAB);
   ParseRange(InpPo3MR, 60, 420, g_poMA, g_poMB);
   ParseRange(InpPo3DR, 420, 780, g_poDA, g_poDB);
   for(int k = 0; k < 9; k++) ParseRange(g_macroR[k], 0, 0, g_macroA[k], g_macroB[k]);
   g_an[0].name = "Midnight Open"; g_an[0].on = InpAncMidOn; g_an[0].col = InpAncMidC;
   g_an[1].name = "08:30 Open";    g_an[1].on = InpAnc830On; g_an[1].col = InpAnc830C;
   g_an[2].name = "09:30 Open";    g_an[2].on = InpAnc930On; g_an[2].col = InpAnc930C;
   g_an[3].name = "Weekly Open";   g_an[3].on = InpAncWkOn;  g_an[3].col = InpAncWkC;
  }

//+------------------------------------------------------------------+
//| markers (Raid / Break? / Run / Judas / SMT)                      |
//+------------------------------------------------------------------+
int AddMark(const int bar, const double price, const string text, const color c, const int dir, const int kind)
  {
   double off = 0.0;
   if(kind == 0)                                 // stack ordinary markers that sit close together
     {
      int lvl = 0;
      for(int k = 0; k < g_nMk; k++) if(g_mk[k].alive && g_mk[k].kind == 0 && MathAbs(g_mk[k].bar - bar) <= 8) lvl++;
      off = g_atr[MathMin(bar, g_n - 1)] * 0.5 * MathMin(lvl, 1) * (dir == 1 ? 1 : -1);
     }
   ArrayResize(g_mk, g_nMk + 1);
   g_mkId++;
   g_mk[g_nMk].id = g_mkId; g_mk[g_nMk].bar = bar; g_mk[g_nMk].price = price + off; g_mk[g_nMk].text = text;
   g_mk[g_nMk].col = c; g_mk[g_nMk].dir = dir; g_mk[g_nMk].kind = kind; g_mk[g_nMk].alive = true;
   g_nMk++;
   int cap = (kind == 0) ? 24 : InpMaxLiq + 10;     // keep the newest ones only
   int cnt = 0;
   for(int k = 0; k < g_nMk; k++) if(g_mk[k].alive && g_mk[k].kind == kind) cnt++;
   for(int k = 0; k < g_nMk && cnt > cap; k++)
      if(g_mk[k].alive && g_mk[k].kind == kind) { g_mk[k].alive = false; cnt--; }
   return g_mkId;
  }

void KillMark(const int id)
  {
   for(int k = 0; k < g_nMk; k++) if(g_mk[k].id == id) g_mk[k].alive = false;
  }

void SetMarkText(const int id, const string text)
  {
   for(int k = 0; k < g_nMk; k++) if(g_mk[k].id == id) g_mk[k].text = text;
  }

//+------------------------------------------------------------------+
//| liquidity levels                                                 |
//+------------------------------------------------------------------+
void RemoveLq(const int idx)
  {
   for(int j = idx; j < g_nLq - 1; j++) g_lq[j] = g_lq[j + 1];
   g_nLq--;
  }

void AddLiq(const double px, const string nm, const int d, const int kind, const int ob, const int i)
  {
   if(IsNa(px)) return;
   for(int k = g_nLq - 1; k >= 0; k--)
      if(g_lq[k].name == nm)
        {
         if(g_lq[k].pend && g_lq[k].mk >= 0) KillMark(g_lq[k].mk);
         RemoveLq(k);
        }
   while(g_nLq >= InpMaxLiq)                      // make room: the oldest level NOT waiting for its verdict goes first
     {
      int idx = -1;
      for(int k = 0; k < g_nLq && idx < 0; k++) if(!g_lq[k].pend) idx = k;
      if(idx < 0) break;
      RemoveLq(idx);
     }
   ArrayResize(g_lq, g_nLq + 1);
   g_lq[g_nLq].price = px; g_lq[g_nLq].name = nm; g_lq[g_nLq].dir = d; g_lq[g_nLq].kind = kind; g_lq[g_nLq].bar = i; g_lq[g_nLq].ob = ob;
   g_lq[g_nLq].swept = false; g_lq[g_nLq].sweptBar = 0; g_lq[g_nLq].pend = false; g_lq[g_nLq].hitBar = 0; g_lq[g_nLq].hitExt = 0.0; g_lq[g_nLq].mk = -1;
   g_nLq++;
  }

int KindScore(const int k) { return (k == 3 || k == 4) ? 4 : (k == 2 ? 3 : (k == 1 ? 2 : 1)); }

void ConfirmRaid(const int qi, const double ext, const int i)
  {
   int sc = KindScore(g_lq[qi].kind);
   double pen = MathAbs(ext - g_lq[qi].price);
   bool sameBar = (g_raidScBar == i);
   bool better = !sameBar || sc > g_raidSc || (sc == g_raidSc && pen > g_raidPen);
   if(g_lq[qi].mk >= 0) { KillMark(g_lq[qi].mk); g_lq[qi].mk = -1; }
   if(!better) return;
   if(sameBar && g_raidMk >= 0) { KillMark(g_raidMk); g_raidMk = -1; }
   g_raidDir = (g_lq[qi].dir == 1) ? -1 : 1;
   g_raidBar = i; g_raidPx = ext; g_raidName = g_lq[qi].name; g_raidRej = true;
   g_raidSc = sc; g_raidPen = pen; g_raidScBar = i; g_evRaid = g_raidDir;
   if(InpRaidLbl) g_raidMk = AddMark(i, g_lq[qi].dir == 1 ? g_h[i] : g_l[i], "Raid " + g_lq[qi].name, C_ORANGE, g_lq[qi].dir == 1 ? 1 : -1, 1);
  }

void ChkLiq(const int i)
  {
   for(int k = g_nLq - 1; k >= 0; k--)
     {
      if(!g_lq[k].swept)
        {
         if(i > g_lq[k].bar)
           {
            bool hit = g_lq[k].dir == 1 ? g_h[i] > g_lq[k].price : g_l[i] < g_lq[k].price;
            if(hit)
              {
               g_lq[k].swept = true; g_lq[k].sweptBar = i;
               bool rej = g_lq[k].dir == 1 ? g_c[i] < g_lq[k].price : g_c[i] > g_lq[k].price;
               double ext = g_lq[k].dir == 1 ? g_h[i] : g_l[i];
               if(rej) ConfirmRaid(k, ext, i);
               else
                 {
                  g_lq[k].pend = true; g_lq[k].hitBar = i; g_lq[k].hitExt = ext;      // PENDING: back through = RAID, still beyond = RUN
                  if(InpRaidLbl) g_lq[k].mk = AddMark(i, ext, "Break? " + g_lq[k].name, C_GRAY, g_lq[k].dir == 1 ? 1 : -1, 1);
                 }
              }
           }
        }
      else
        {
         if(g_lq[k].pend)
           {
            g_lq[k].hitExt = g_lq[k].dir == 1 ? MathMax(g_lq[k].hitExt, g_h[i]) : MathMin(g_lq[k].hitExt, g_l[i]);
            bool back = g_lq[k].dir == 1 ? g_c[i] < g_lq[k].price : g_c[i] > g_lq[k].price;
            if(back)
              {
               g_lq[k].pend = false;
               ConfirmRaid(k, g_lq[k].hitExt, i);
              }
            else if(i - g_lq[k].hitBar >= InpPendBars)
              {
               g_lq[k].pend = false;
               g_evRun = g_lq[k].dir;
               if(g_lq[k].mk >= 0) SetMarkText(g_lq[k].mk, "Run " + g_lq[k].name);
              }
           }
         if(!g_lq[k].pend && i - g_lq[k].sweptBar > InpKeepSwept) RemoveLq(k);
        }
     }
   if(g_raidRej && i - g_raidBar <= 60)
     {
      if(g_raidDir == 1)  g_raidPx = MathMin(IsNa(g_raidPx) ? g_l[i] : g_raidPx, g_l[i]);
      if(g_raidDir == -1) g_raidPx = MathMax(IsNa(g_raidPx) ? g_h[i] : g_raidPx, g_h[i]);
     }
  }

//+------------------------------------------------------------------+
//| sessions                                                         |
//+------------------------------------------------------------------+
int FindBox(const int id)
  {
   for(int k = 0; k < g_nBox; k++) if(g_box[k].id == id) return k;
   return -1;
  }

bool UpdSess(const int k, const bool inS, const int i)
  {
   bool ended = false;
   if(!g_s[k].on) return false;
   if(inS)
     {
      if(!g_s[k].active)
        {
         g_s[k].active = true; g_s[k].sBar = i; g_s[k].sTime = g_t[i]; g_s[k].hi = g_h[i]; g_s[k].lo = g_l[i];
         g_s[k].hiBar = i; g_s[k].loBar = i;
         if(InpSessStyle != TM_ST_OFF)
           {
            ArrayResize(g_box, g_nBox + 1);
            g_boxId++;
            g_box[g_nBox].id = g_boxId; g_box[g_nBox].s = k; g_box[g_nBox].i0 = i; g_box[g_nBox].i1 = i;
            g_box[g_nBox].hi = g_h[i]; g_box[g_nBox].lo = g_l[i]; g_box[g_nBox].sTime = g_t[i];
            g_s[k].boxId = g_boxId;
            g_nBox++;
            while(g_nBox > InpHistDays * 8)
              {
               for(int j = 0; j < g_nBox - 1; j++) g_box[j] = g_box[j + 1];
               g_nBox--;
              }
           }
        }
      else
        {
         if(g_h[i] > g_s[k].hi) { g_s[k].hi = g_h[i]; g_s[k].hiBar = i; }
         if(g_l[i] < g_s[k].lo) { g_s[k].lo = g_l[i]; g_s[k].loBar = i; }
         int b = FindBox(g_s[k].boxId);
         if(b >= 0) { g_box[b].i1 = i; g_box[b].hi = g_s[k].hi; g_box[b].lo = g_s[k].lo; }
        }
     }
   else if(g_s[k].active)
     {
      g_s[k].active = false; ended = true;
      g_s[k].fHi = g_s[k].hi; g_s[k].fLo = g_s[k].lo; g_s[k].fHiBar = g_s[k].hiBar; g_s[k].fLoBar = g_s[k].loBar;
      int b = FindBox(g_s[k].boxId);
      if(b >= 0) g_box[b].i1 = i - 1;
     }
   return ended;
  }

void SessEnd(const int k, const int i)
  {
   if(g_s[k].lvl && InpSessLvlOn)
     {
      AddLiq(g_s[k].fHi, g_s[k].name + " H", 1, 1, g_s[k].fHiBar, i);
      AddLiq(g_s[k].fLo, g_s[k].name + " L", -1, 1, g_s[k].fLoBar, i);
     }
   if(g_s[k].proj)
     { g_pj[k].ok = true; g_pj[k].i0 = g_s[k].sBar; g_pj[k].hi = g_s[k].fHi; g_pj[k].lo = g_s[k].fLo; }
  }

//+------------------------------------------------------------------+
//| day / week / month levels, anchors                               |
//+------------------------------------------------------------------+
bool IsSunday(const int i) { return !g_isCrypto && g_dow[i] == 1; }

bool NewWeek(const int i)
  {
   if(i <= 0) return false;
   int a = g_isCrypto ? (g_dow[i] + 5) % 7 : g_dow[i] - 1;
   int b = g_isCrypto ? (g_dow[i - 1] + 5) % 7 : g_dow[i - 1] - 1;
   return a < b;
  }

void PeriodsBar(const int i)
  {
   bool newDay = i > 0 && g_day[i] != g_day[i - 1] && !IsSunday(i);
   bool newWk  = NewWeek(i);
   bool newMth = i > 0 && g_mon[i] != g_mon[i - 1];
   if(newDay)
     {
      if(!IsNa(g_curDH) && InpPdOn)
        {
         AddLiq(g_curDH, "PDH", 1, 2, g_curDHB, i);
         AddLiq(g_curDL, "PDL", -1, 2, g_curDLB, i);
        }
      g_curDH = g_h[i]; g_curDL = g_l[i]; g_curDHB = i; g_curDLB = i;
      g_jdDone = false; g_jdPot = 0; g_po3DnBar = -9999; g_po3UpBar = -9999; g_po3DnExt = TM_NA; g_po3UpExt = TM_NA; g_po3Bias = 0;
     }
   else if(!IsSunday(i))
     {
      if(IsNa(g_curDH) || g_h[i] > g_curDH) { g_curDH = g_h[i]; g_curDHB = i; }
      if(IsNa(g_curDL) || g_l[i] < g_curDL) { g_curDL = g_l[i]; g_curDLB = i; }
     }
   if(newWk)
     {
      if(!IsNa(g_curWH) && InpPwOn)
        {
         AddLiq(g_curWH, "PWH", 1, 3, g_curWHB, i);
         AddLiq(g_curWL, "PWL", -1, 3, g_curWLB, i);
        }
      g_curWH = g_h[i]; g_curWL = g_l[i]; g_curWHB = i; g_curWLB = i;
     }
   else
     {
      if(IsNa(g_curWH) || g_h[i] > g_curWH) { g_curWH = g_h[i]; g_curWHB = i; }
      if(IsNa(g_curWL) || g_l[i] < g_curWL) { g_curWL = g_l[i]; g_curWLB = i; }
     }
   if(newMth)
     {
      if(!IsNa(g_curMH) && InpPmOn)
        {
         AddLiq(g_curMH, "PMH", 1, 4, g_curMHB, i);
         AddLiq(g_curML, "PML", -1, 4, g_curMLB, i);
        }
      g_curMH = g_h[i]; g_curML = g_l[i]; g_curMHB = i; g_curMLB = i;
     }
   else
     {
      if(IsNa(g_curMH) || g_h[i] > g_curMH) { g_curMH = g_h[i]; g_curMHB = i; }
      if(IsNa(g_curML) || g_l[i] < g_curML) { g_curML = g_l[i]; g_curMLB = i; }
     }
  }

void AncFire(const int k, const bool exact, const int i)
  {
   if(!g_an[k].on) return;
   if(g_an[k].has)
     {
      ArrayResize(g_ah, g_nAh + 1);
      g_ah[g_nAh].x1 = g_an[k].bar; g_ah[g_nAh].x2 = i; g_ah[g_nAh].px = g_an[k].px; g_ah[g_nAh].col = g_an[k].col;
      g_nAh++;
      while(g_nAh > InpHistDays * 4 + 4)
        {
         for(int j = 0; j < g_nAh - 1; j++) g_ah[j] = g_ah[j + 1];
         g_nAh--;
        }
     }
   g_an[k].has = true; g_an[k].bar = i; g_an[k].px = g_o[i]; g_an[k].exact = exact;
  }

void AnchorsBar(const int i)
  {
   if(!g_intra) return;
   bool dayChg = i > 0 && g_day[i] != g_day[i - 1];
   int minDay = g_mn[i], minDay1 = i > 0 ? g_mn[i - 1] : 0;
   if(dayChg && !IsSunday(i)) { AncFire(0, minDay == 0, i); g_moPx = g_o[i]; }
   if(minDay >= 510 && (minDay1 < 510 || dayChg)) AncFire(1, minDay == 510, i);
   if(minDay >= 570 && (minDay1 < 570 || dayChg)) AncFire(2, minDay == 570, i);
   if(NewWeek(i)) AncFire(3, g_isCrypto ? minDay == 0 : minDay == 1020, i);
  }

//+------------------------------------------------------------------+
//| Judas Swing, Power of 3                                          |
//+------------------------------------------------------------------+
void JudasBar(const int i)
  {
   bool inJd = Fl(i, 11);
   if(g_jdPot != 0 && !inJd) g_jdPot = 0;           // a pending Judas expires with its window
   if(!(InpJdOn && inJd && !g_jdDone && !IsNa(g_s[0].fHi))) return;
   if(g_jdPot == 0)
     {
      if(g_h[i] > g_s[0].fHi && g_c[i] < g_s[0].fHi)
        { g_jdPot = -1; g_jdExt = g_h[i]; AddMark(i, g_h[i], "Judas? " + Dn(), C_YELLOW, 1, 0); }
      else if(g_l[i] < g_s[0].fLo && g_c[i] > g_s[0].fLo)
        { g_jdPot = 1; g_jdExt = g_l[i]; AddMark(i, g_l[i], "Judas? " + Up(), C_YELLOW, -1, 0); }
     }
   else
     {
      if(g_jdPot == -1 && g_c[i] > g_jdExt) g_jdPot = 0;     // closed beyond the sweep extreme: a real breakout
      if(g_jdPot == 1 && g_c[i] < g_jdExt) g_jdPot = 0;
      if(g_jdPot == -1 && !IsNa(g_moPx) && g_c[i] < g_moPx)
        { g_jdDone = true; g_evJudas = -1; AddMark(i, g_h[i], "Judas " + Dn() + " " + Ck(), C_YELLOW, 1, 0); }
      if(g_jdPot == 1 && !IsNa(g_moPx) && g_c[i] > g_moPx)
        { g_jdDone = true; g_evJudas = 1; AddMark(i, g_l[i], "Judas " + Up() + " " + Ck(), C_YELLOW, -1, 0); }
     }
  }

void Po3Bar(const int i)
  {
   if(!(InpPo3On && (Fl(i, 13) || Fl(i, 14)) && !IsNa(g_moPx))) return;
   double thr = 0.25 * g_atr[i];
   bool dnX = g_l[i] < g_moPx - thr;
   bool upX = g_h[i] > g_moPx + thr;
   // 0) invalidation
   if(g_po3Bias == 1 && g_c[i] < g_po3Ext)  { g_po3Bias = 0; g_po3DnBar = -9999; }
   if(g_po3Bias == -1 && g_c[i] > g_po3Ext) { g_po3Bias = 0; g_po3UpBar = -9999; }
   // 1) confirmation uses only previous candles; a candle crossing both sides can neither sweep nor confirm
   if(g_po3Bias == 0 && !(dnX && upX))
     {
      if(g_c[i] > g_moPx && g_po3DnBar > g_po3UpBar && i - g_po3DnBar <= InpPo3Fresh)      { g_po3Bias = 1;  g_po3Ext = g_po3DnExt; }
      else if(g_c[i] < g_moPx && g_po3UpBar > g_po3DnBar && i - g_po3UpBar <= InpPo3Fresh) { g_po3Bias = -1; g_po3Ext = g_po3UpExt; }
     }
   // 2) then this candle's own excursion
   if(dnX && !upX) { g_po3DnBar = i; g_po3DnExt = MathMin(IsNa(g_po3DnExt) ? g_l[i] : g_po3DnExt, g_l[i]); }
   if(upX && !dnX) { g_po3UpBar = i; g_po3UpExt = MathMax(IsNa(g_po3UpExt) ? g_h[i] : g_po3UpExt, g_h[i]); }
  }

//+------------------------------------------------------------------+
//| swings: EQH / EQL, SMT                                           |
//+------------------------------------------------------------------+
bool PivotHigh(const int c, const int len)
  {
   for(int k = 1; k <= len; k++) if(!(g_h[c] > g_h[c - k]) || !(g_h[c] > g_h[c + k])) return false;
   return true;
  }

bool PivotLow(const int c, const int len)
  {
   for(int k = 1; k <= len; k++) if(!(g_l[c] < g_l[c - k]) || !(g_l[c] < g_l[c + k])) return false;
   return true;
  }

double MaxN(const double a, const double b) { return (IsNa(a) || IsNa(b)) ? TM_NA : MathMax(a, b); }
double MinN(const double a, const double b) { return (IsNa(a) || IsNa(b)) ? TM_NA : MathMin(a, b); }

void SmtCheck(const bool isHigh, const int i)
  {
   int c = i - InpSwLen;
   if(!InpSmtOn || IsNa(g_x2h[i]) || IsNa(g_x2l[i])) return;
   if(isHigh)
     {
      double m = g_h[c];
      double s2 = MaxN(g_x2h[c - 1], MaxN(g_x2h[c], g_x2h[c + 1]));
      if(!IsNa(g_phM) && !IsNa(g_phS))
        {
         bool mainHH = m > g_phM;
         bool secHH = !IsNa(s2) && s2 > g_phS;
         if((mainHH && !secHH) || (!mainHH && secHH && m < g_phM))
           { g_smtDir = -1; g_smtBar = i; g_evSmt = -1; AddMark(c, m, "SMT " + Dn(), C_PINK, 1, 0); }
        }
      g_phM = m; g_phS = s2;
     }
   else
     {
      double m = g_l[c];
      double s2l = MinN(g_x2l[c - 1], MinN(g_x2l[c], g_x2l[c + 1]));
      if(!IsNa(g_plM) && !IsNa(g_plS))
        {
         bool mainLL = m < g_plM;
         bool secLL = !IsNa(s2l) && s2l < g_plS;
         if((mainLL && !secLL) || (!mainLL && secLL && m > g_plM))
           { g_smtDir = 1; g_smtBar = i; g_evSmt = 1; AddMark(c, m, "SMT " + Up(), C_PINK, -1, 0); }
        }
      g_plM = m; g_plS = s2l;
     }
  }

void PivotsBar(const int i)
  {
   int len = InpSwLen, c = i - len;
   if(c - len < 0) return;
   bool ph = PivotHigh(c, len), pl = PivotLow(c, len);
   bool atrOk = g_atr[i] > 0.0;
   if(ph)
     {
      g_pSwHi = g_swHi; g_swHi = g_h[c]; g_swHiBar = c;
      if(InpEqOn && !IsNa(g_pSwHi) && atrOk && MathAbs(g_swHi - g_pSwHi) <= InpEqTol * g_atr[i])
         AddLiq(MathMax(g_swHi, g_pSwHi), "EQH", 1, 5, g_swHiBar, i);
     }
   if(pl)
     {
      g_pSwLo = g_swLo; g_swLo = g_l[c]; g_swLoBar = c;
      if(InpEqOn && !IsNa(g_pSwLo) && atrOk && MathAbs(g_swLo - g_pSwLo) <= InpEqTol * g_atr[i])
         AddLiq(MathMin(g_swLo, g_pSwLo), "EQL", -1, 5, g_swLoBar, i);
     }
   if(ph) SmtCheck(true, i);
   if(pl) SmtCheck(false, i);
  }

//+------------------------------------------------------------------+
//| one confirmed bar (same order as the Pine main block)            |
//+------------------------------------------------------------------+
void ProcessBar(const int i)
  {
   g_evRaid = 0; g_evRun = 0; g_evJudas = 0; g_evSmt = 0;
   if(g_intra)
      for(int k = 0; k < 8; k++)
        {
         bool ended = UpdSess(k, Fl(i, k), i);
         if(ended) SessEnd(k, i);
        }
   PeriodsBar(i);
   AnchorsBar(i);
   PivotsBar(i);
   ChkLiq(i);
   JudasBar(i);
   Po3Bar(i);
  }

//+------------------------------------------------------------------+
//| drawing                                                          |
//+------------------------------------------------------------------+
int g_nItem = 0;
TmItem g_item[];
double g_lblP[];
int g_nLblP = 0;

void AddItem(const string text, const color c, const double price)
  {
   ArrayResize(g_item, g_nItem + 1);
   g_item[g_nItem].text = text; g_item[g_nItem].col = c; g_item[g_nItem].price = price;
   g_nItem++;
  }

double Rng150()
  {
   int s = MathMax(0, g_n - 2 - 149);
   double hi = -DBL_MAX, lo = DBL_MAX;
   for(int k = s; k <= g_n - 2; k++) { if(g_h[k] > hi) hi = g_h[k]; if(g_l[k] < lo) lo = g_l[k]; }
   return (hi > lo) ? hi - lo : 0.0;
  }

void FlushItems()                       // right-side labels: sorted by price, never closer than the minimum gap
  {
   g_nLblP = 0;
   if(g_nItem <= 0) return;
   int ord[];
   ArrayResize(ord, g_nItem);
   for(int k = 0; k < g_nItem; k++) ord[k] = k;
   for(int a = 1; a < g_nItem; a++)     // insertion sort, price descending
     {
      int v = ord[a], b = a - 1;
      while(b >= 0 && g_item[ord[b]].price < g_item[v].price) { ord[b + 1] = ord[b]; b--; }
      ord[b + 1] = v;
     }
   double gap = Rng150() * InpLblGap / 100.0;
   double prevY = TM_NA;
   datetime tx = TimeAt(g_n - 1 + InpLblPad);
   for(int k = 0; k < g_nItem; k++)
     {
      int ix = ord[k];
      double y = g_item[ix].price;
      if(!IsNa(prevY) && prevY - y < gap) y = prevY - gap;
      MkText("RL", tx, y, g_item[ix].text, g_item[ix].col, ANCHOR_LEFT);
      ArrayResize(g_lblP, g_nLblP + 1); g_lblP[g_nLblP] = y; g_nLblP++;
      prevY = y;
     }
   g_nItem = 0;
  }

void DrawBand(const bool shade, const int a, const int b, const color c, const double lo, const double hi, const int fromBar)
  {
   if(!shade) return;
   int run0 = -1;
   for(int i = fromBar; i < g_n; i++)
     {
      bool inn = g_intra && InSess(g_mn[i], a, b);
      if(inn && run0 < 0) run0 = i;
      if((!inn || i == g_n - 1) && run0 >= 0)
        {
         int r1 = inn ? i : i - 1;
         MkRect("SB", g_t[run0], hi, TimeAt(r1 + 1), lo, Tint(c, 0.07), true);
         run0 = -1;
        }
     }
  }

void DrawShading()
  {
   int from = 0;
   datetime cut = g_t[g_n - 1] - (datetime)(InpShadeDays * 86400);
   for(int i = 0; i < g_n; i++) if(g_t[i] >= cut) { from = i; break; }
   double hi = -DBL_MAX, lo = DBL_MAX;
   for(int i = from; i < g_n; i++) { if(g_h[i] > hi) hi = g_h[i]; if(g_l[i] < lo) lo = g_l[i]; }
   double pad = (hi - lo) * 3.0;       // tall enough to look full-height at any zoom
   hi += pad; lo -= pad;
   if(InpSbOn)
     {
      DrawBand(true, g_sb1A, g_sb1B, InpSbC, lo, hi, from);
      DrawBand(true, g_sb2A, g_sb2B, InpSbC, lo, hi, from);
      DrawBand(true, g_sb3A, g_sb3B, InpSbC, lo, hi, from);
     }
   if(InpMcOn)
      for(int k = 0; k < 9; k++) DrawBand(true, g_macroA[k], g_macroB[k], InpMcC, lo, hi, from);
  }

string ShortName(const int k)
  {
   if(k == 0) return "Asia";
   if(k == 1) return "London";
   if(k == 2) return "NY AM";
   if(k == 3) return "LC";
   if(k == 4) return "Lunch";
   if(k == 5) return "NY PM";
   return g_s[k].name;
  }

void DrawBoxes()
  {
   int fillMode = InpSessStyle;
   for(int b = 0; b < g_nBox; b++)
     {
      int k = g_box[b].s;
      color c = g_s[k].col;
      int i0 = g_box[b].i0, i1 = g_box[b].i1;
      if(i1 < i0) i1 = i0;
      datetime t1 = g_t[i0], t2 = TimeAt(i1 + 1);
      if(fillMode == TM_ST_FILLED) MkRect("BX", t1, g_box[b].hi, t2, g_box[b].lo, Tint(c, 0.15), true);
      else if(fillMode == TM_ST_LIGHT) MkRect("BX", t1, g_box[b].hi, t2, g_box[b].lo, Tint(c, 0.06), true);
      MkRect("BL", t1, g_box[b].hi, t2, g_box[b].lo, Tint(c, fillMode == TM_ST_BORDER ? 0.9 : 0.65), false);
      if(InpShowSessLbl)
        {
         datetime st = ToZone(ServerToUtc(g_box[b].sTime), InpTzDisp);
         int dur = g_s[k].b - g_s[k].a; if(dur <= 0) dur += 1440;
         string z = ZoneTag();
         string txt = ShortName(k) + "  " + Hm(st) + "-" + Hm(st + (datetime)(dur * 60)) + (z == "" ? "" : " " + z);
         MkText("SL", g_t[(i0 + i1) / 2], g_box[b].hi, txt, c, ANCHOR_LOWER);
        }
     }
  }

void DrawProjections()
  {
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   for(int k = 0; k < 8; k++)
     {
      if(!g_s[k].proj || !g_pj[k].ok) continue;
      double r = g_pj[k].hi - g_pj[k].lo;
      if(r <= 0) continue;
      double pips = r / (_Point * 10.0);
      bool tooWide = g_isForex && InpFiltPips > 0 && pips > InpFiltPips;
      datetime t0 = g_t[MathMax(g_pj[k].i0, MathMax(0, g_n - 1400))];
      color c = g_s[k].col;
      MkLine("PJ", t0, g_pj[k].hi, xe, g_pj[k].hi, Tint(c, 0.8), STYLE_SOLID, 1);
      MkLine("PJ", t0, g_pj[k].lo, xe, g_pj[k].lo, Tint(c, 0.8), STYLE_SOLID, 1);
      AddItem(g_s[k].name + " H " + Fp(g_pj[k].hi), c, g_pj[k].hi);
      AddItem(g_s[k].name + " L " + Fp(g_pj[k].lo) + (tooWide ? " (wide " + IntegerToString((int)pips) + "p)" : ""), c, g_pj[k].lo);
      if(tooWide) continue;
      for(int m = 1; m <= InpSdCount; m++)
        {
         double mult = m * InpSdStep;
         double up = g_pj[k].hi + mult * r, dn = g_pj[k].lo - mult * r;
         MkLine("PJ", t0, up, xe, up, InpProjUpC, STYLE_DASH, 1);
         MkLine("PJ", t0, dn, xe, dn, InpProjDnC, STYLE_DASH, 1);
         string ms = Num2(mult);
         AddItem(g_s[k].name + " +" + ms + "x " + Fp(up), InpProjUpC, up);
         AddItem(g_s[k].name + " -" + ms + "x " + Fp(dn), InpProjDnC, dn);
        }
     }
  }

color LiqColor(const int kind, const string nm)
  {
   if(kind == 2) return InpPdC;
   if(kind == 3) return InpPwC;
   if(kind == 4) return InpPmC;
   if(kind == 5) return InpEqC;
   return InpSessLC;
  }

void DrawLiquidity()
  {
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   for(int k = 0; k < g_nLq; k++)
     {
      color c = LiqColor(g_lq[k].kind, g_lq[k].name);
      datetime t0 = g_t[MathMax(g_lq[k].ob, MathMax(0, g_n - 1400))];
      if(g_lq[k].swept)
        {
         MkLine("LQ", t0, g_lq[k].price, g_t[g_lq[k].sweptBar], g_lq[k].price, c, STYLE_DOT, 1);
         MkText("LC", g_t[g_lq[k].sweptBar], g_lq[k].price, Ck(), c, ANCHOR_LEFT);
        }
      else
        {
         MkLine("LQ", t0, g_lq[k].price, xe, g_lq[k].price, c, STYLE_DASH, 1);
         AddItem(g_lq[k].name + " " + Fp(g_lq[k].price), c, g_lq[k].price);
        }
     }
  }

void DrawAnchors()
  {
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   for(int k = 0; k < g_nAh; k++)
      MkLine("AH", g_t[g_ah[k].x1], g_ah[k].px, g_t[g_ah[k].x2], g_ah[k].px, g_ah[k].col, STYLE_SOLID, 1);
   for(int k = 0; k < 4; k++)
     {
      if(!g_an[k].on || !g_an[k].has) continue;
      MkLine("AN", g_t[g_an[k].bar], g_an[k].px, xe, g_an[k].px, g_an[k].col, STYLE_SOLID, 1);
      if(InpShowSessLbl) AddItem(g_an[k].name + (g_an[k].exact ? "" : " " + ShortToString(0x2248)) + " " + Fp(g_an[k].px), g_an[k].col, g_an[k].px);
     }
  }

void DrawMarks()
  {
   for(int k = 0; k < g_nMk; k++)
     {
      if(!g_mk[k].alive) continue;
      MkText("MK", g_t[g_mk[k].bar], g_mk[k].price, g_mk[k].text, g_mk[k].col, g_mk[k].dir == 1 ? ANCHOR_LOWER : ANCHOR_UPPER);
     }
  }

void DrawIpda()
  {
   if(!InpIpdaOn) return;
   datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
   int days[3]; color cs[3];
   days[0] = InpIpda1; days[1] = InpIpda2; days[2] = InpIpda3;
   cs[0] = C_BLUE; cs[1] = C_PURPLE; cs[2] = C_ORANGE;
   double hi3 = TM_NA, lo3 = TM_NA;
   for(int k = 0; k < 3; k++)
     {
      int ih = iHighest(_Symbol, PERIOD_D1, MODE_HIGH, days[k], 1), il = iLowest(_Symbol, PERIOD_D1, MODE_LOW, days[k], 1);
      if(ih < 0 || il < 0) continue;
      double h = iHigh(_Symbol, PERIOD_D1, ih), l = iLow(_Symbol, PERIOD_D1, il);
      MkLine("IP", g_t[MathMax(0, g_n - 61)], h, xe, h, cs[k], STYLE_DOT, 1);
      MkLine("IP", g_t[MathMax(0, g_n - 61)], l, xe, l, cs[k], STYLE_DOT, 1);
      AddItem(IntegerToString(days[k]) + "D H " + Fp(h), cs[k], h);
      AddItem(IntegerToString(days[k]) + "D L " + Fp(l), cs[k], l);
      if(k == 2) { hi3 = h; lo3 = l; }
     }
   if(!IsNa(hi3) && !IsNa(lo3))
     {
      double eq = (hi3 + lo3) / 2.0;
      MkLine("IP", g_t[MathMax(0, g_n - 61)], eq, xe, eq, C_GRAY, STYLE_DOT, 1);
      AddItem(IntegerToString(InpIpda3) + "D EQ " + Fp(eq), C_GRAY, eq);
     }
  }

//+------------------------------------------------------------------+
//| panel (clock, active session, next session, biases)              |
//+------------------------------------------------------------------+
datetime NextStartUtc(const int sm)
  {
   datetime u0 = NowUtc();
   datetime nyNow = ToNY(u0);
   datetime day0 = nyNow - (datetime)(nyNow % 86400);
   for(int d = 0; d <= 8; d++)
     {
      datetime ny = day0 + (datetime)(d * 86400 + sm * 60);
      datetime u = NyToUtc(ny);
      MqlDateTime p; TimeToStruct(ny, p);
      int dow = p.day_of_week + 1;
      bool closed = !g_isCrypto && (dow == 7 || (dow == 1 && sm < 1020) || (dow == 6 && sm >= 1020));
      if(u > u0 && !closed) return u;
     }
   return 0;
  }

const int PN_ROWS = 10, PN_M = 14;
int g_pnW = 430, g_pnRH = 18, g_pnKeyW = 150;
string g_pk[10], g_pv[10];
color  g_pc[10];

int  g_pnCorner = CORNER_LEFT_UPPER;
long g_pnSig = -1;

int PnH() { return PN_ROWS * g_pnRH + 12; }

int WmHeight() { return InpShowWm ? InpWmBottom + (int)(InpWmSize * 2.3) + 6 : 0; }   // bottom-left space used by the watermark

int PnMarginY(const int corner)           // vertical gap from the chart edge; bottom-left leaves room for the watermark
  {
   if(corner == CORNER_LEFT_LOWER) return PN_M + WmHeight();
   return PN_M;
  }

int FixedCorner()
  {
   if(InpPanelPos == TM_C_TOPLEFT) return CORNER_LEFT_UPPER;
   if(InpPanelPos == TM_C_BOTTOMRIGHT) return CORNER_RIGHT_LOWER;
   if(InpPanelPos == TM_C_BOTTOMLEFT) return CORNER_LEFT_LOWER;
   return CORNER_RIGHT_UPPER;
  }

// how much of the visible chart a panel in this corner would cover (bars + right-hand price labels)
double CornerCover(const int corner, const int W, const int H, const int first, const int vis, const double pmax, const double pmin, const double ppb)
  {
   bool right = (corner == CORNER_RIGHT_UPPER || corner == CORNER_RIGHT_LOWER);
   bool bottom = (corner == CORNER_LEFT_LOWER || corner == CORNER_RIGHT_LOWER);
   int x0 = right ? W - PN_M - g_pnW : PN_M, x1 = x0 + g_pnW;
   int my = PnMarginY(corner);
   int y0 = bottom ? H - my - PnH() : my, y1 = y0 + PnH();
   double sc = 0.0, k = (double)H / (pmax - pmin);
   for(int s = first; s > first - vis && s >= 0; s--)
     {
      int i = g_n - 1 - s;
      if(i < 0 || i >= g_n) continue;
      double bx = (first - s + 0.5) * ppb;
      if(bx < x0 - 2 || bx > x1 + 2) continue;
      double yt = (pmax - g_h[i]) * k, yb = (pmax - g_l[i]) * k;
      if(yb >= y0 && yt <= y1) sc += 1.0;
     }
   if(right)                                   // right-hand level labels live under the right corners
      for(int j = 0; j < g_nLblP; j++)
        {
         double yy = (pmax - g_lblP[j]) * k;
         if(yy >= y0 - 6 && yy <= y1 + 6) sc += 6.0;
        }
   return sc;
  }

int PanelCorner()
  {
   if(InpPanelPos != TM_C_AUTO) return FixedCorner();
   return g_pnCorner;
  }

void ChooseCorner()                         // Auto: pick the emptiest corner, with hysteresis so it does not jump around
  {
   if(InpPanelPos != TM_C_AUTO) { g_pnCorner = FixedCorner(); return; }
   int W = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS), H = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   int first = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR), vis = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   double pmax = ChartGetDouble(0, CHART_PRICE_MAX, 0), pmin = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   if(W <= 0 || H <= 0 || vis <= 0 || pmax <= pmin) return;
   long sig = (long)W * 1000003L + H * 10007L + first * 101L + vis + (long)(pmax * 1000.0) % 9973 + g_nLblP * 13L + g_n;
   if(sig == g_pnSig) return;
   g_pnSig = sig;
   double shiftPct = (InpRightMargin > 0) ? (double)MathMin(InpRightMargin, 50) / 100.0 : 0.0;
   double ppb = (double)W * (1.0 - shiftPct) / vis;
   int cs[4];
   cs[0] = CORNER_LEFT_UPPER; cs[1] = CORNER_RIGHT_UPPER; cs[2] = CORNER_LEFT_LOWER; cs[3] = CORNER_RIGHT_LOWER;
   int best = -1; double bs = 1e18, cur = 1e18;
   for(int k = 0; k < 4; k++)
     {
      double sc = CornerCover(cs[k], W, H, first, vis, pmax, pmin, ppb);
      if(cs[k] == g_pnCorner) cur = sc;
      if(sc < bs) { bs = sc; best = cs[k]; }
     }
   if(best >= 0 && cur > bs + 3.0) g_pnCorner = best;      // move only when clearly better
  }

void PanelRow(const int row, const string key, const string val, const color vc)
  {
   g_pk[row] = key; g_pv[row] = (val == "" ? " " : val); g_pc[row] = vc;      // empty text would show the default word "Label"
  }

void PanelMeasure()                         // size the panel from the real text widths (works at any screen scaling)
  {
   TextSetFont("Consolas", -InpFont * 10);
   uint w = 0, h = 0;
   int kw = 0, vw = 0, rh = 0;
   for(int r = 0; r < PN_ROWS; r++)
     {
      TextGetSize(g_pk[r], w, h); if((int)w > kw) kw = (int)w; if((int)h > rh) rh = (int)h;
      TextGetSize(g_pv[r], w, h); if((int)w > vw) vw = (int)w;
     }
   if(kw <= 0 || rh <= 0)                   // measuring not available: rough estimate
     { kw = (int)(InpFont * 8.0 * 20); rh = InpFont * 2; vw = (int)(InpFont * 8.0 * 30); }
   kw = (int)(kw * 1.12); vw = (int)(vw * 1.12);          // safety margin: fonts differ between PCs
   g_pnKeyW = kw + 16;
   g_pnRH = MathMax(18, rh + 4);
   g_pnW = MathMax(260, 10 + g_pnKeyW + vw + 28);
  }

void PanelOrigin(int &x0, int &y0)          // top-left pixel of the panel; every panel object is anchored top-left, so right/bottom corners are computed from the chart size
  {
   int corner = PanelCorner();
   int W = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS), H = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   bool right = (corner == CORNER_RIGHT_UPPER || corner == CORNER_RIGHT_LOWER);
   bool bottom = (corner == CORNER_LEFT_LOWER || corner == CORNER_RIGHT_LOWER);
   x0 = right ? W - PN_M - g_pnW : PN_M;
   y0 = bottom ? H - PnMarginY(corner) - PnH() : PnMarginY(corner);
   if(x0 < 0) x0 = 0;
   if(y0 < 0) y0 = 0;
  }

void PanelDrawRow(const int row, const int x0, const int y0)
  {
   int yy = y0 + 6 + row * g_pnRH;
   for(int k = 0; k < 2; k++)
     {
      string n = PFX + "PN" + IntegerToString(row) + "_" + IntegerToString(k);
      if(ObjectFind(0, n) < 0) ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
      int xin = (k == 0) ? 10 : 10 + g_pnKeyW;               // x inside the panel
      ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
      ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x0 + xin);
      ObjectSetInteger(0, n, OBJPROP_YDISTANCE, yy);
      ObjectSetInteger(0, n, OBJPROP_COLOR, k == 0 ? (row == 0 ? BRAND_GREEN : C_GRAY) : g_pc[row]);
      ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpFont);
      ObjectSetString(0, n, OBJPROP_FONT, "Consolas");
      ObjectSetString(0, n, OBJPROP_TEXT, k == 0 ? g_pk[row] : g_pv[row]);
      ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, n, OBJPROP_ZORDER, 10);
     }
  }

void PanelBack(const int x0, const int y0)
  {
   string n = PFX + "PNBG";
   if(ObjectFind(0, n) < 0) ObjectCreate(0, n, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x0);
   ObjectSetInteger(0, n, OBJPROP_YDISTANCE, y0);
   ObjectSetInteger(0, n, OBJPROP_XSIZE, g_pnW);
   ObjectSetInteger(0, n, OBJPROP_YSIZE, PnH());
   ObjectSetInteger(0, n, OBJPROP_BGCOLOR, Tint(BRAND_DARK, 0.92));
   ObjectSetInteger(0, n, OBJPROP_COLOR, Tint(BRAND_GREEN, 0.5));
   ObjectSetInteger(0, n, OBJPROP_BORDER_TYPE, 1);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, n, OBJPROP_BACK, false);
   ObjectSetInteger(0, n, OBJPROP_ZORDER, 9);
  }

string DirTxt(const int d) { return d == 1 ? "Bullish " + Up() : (d == -1 ? "Bearish " + Dn() : "-"); }
color  DirCol(const int d) { return d == 1 ? C_BULL : (d == -1 ? C_BEAR : C_GRAY); }

void UpdatePanel()
  {
   if(!InpShowPanel)
     {
      ObjectsDeleteAll(0, PFX + "PN");
      return;
     }
   if(g_n < 3) return;
   ChooseCorner();
   int L = g_n - 2;
   datetime u = NowUtc();
   string actNm = "Outside sessions";
   color  actCol = C_GRAY;
   for(int k = 0; k < 8; k++)
      if(g_s[k].on && g_s[k].active && k != 6 && k != 7) { actNm = g_s[k].name; actCol = g_s[k].col; }
   if(SbAny(L)) actNm += " + Silver Bullet";
   if(InpMcOn && Fl(L, 15)) actNm += " + Macro";
   datetime bestT = 0; string bestN = "-";
   for(int k = 0; k < 8; k++)
     {
      if(!g_s[k].on || k == 6 || k == 7) continue;
      datetime c = NextStartUtc(g_s[k].a);
      if(c > 0 && (bestT == 0 || c < bestT)) { bestT = c; bestN = g_s[k].name; }
     }
   string nextTxt = "-";
   if(bestT > 0)
     {
      int bd = (int)((bestT - u) / 60);
      nextTxt = bestN + " in " + IntegerToString(bd / 60) + "h " + IntegerToString(bd % 60) + "m (" + Hm(ToZone(bestT, InpTzDisp)) + ")";
     }
   string clock = Hm(ToZone(u, TM_Z_TEHRAN)) + "  |  " + Hm(ToNY(u));
   string mo = IsNa(g_moPx) ? "-" : (g_c[L] > g_moPx ? "above " : "below ") + Fp(g_moPx);
   color  moC = IsNa(g_moPx) ? C_GRAY : (g_c[L] > g_moPx ? C_BULL : C_BEAR);
   string ph = Fl(L, 12) ? "Accumulation" : (Fl(L, 13) ? "Manipulation" : (Fl(L, 14) ? "Distribution" : "-"));
   string po = ph;
   if(g_po3Bias != 0) { string dw = (g_po3Bias == 1) ? "buy" : "sell"; po += "  -> " + dw + " day"; }
   string rd = g_raidDir == 0 ? "-" : g_raidName + (g_raidRej ? " (raid)" : " (run)") + " - " + IntegerToString(L - g_raidBar) + " bars ago";
   string sm = g_smtDir == 0 ? "-" : DirTxt(g_smtDir) + " - " + IntegerToString(L - g_smtBar) + " bars ago";
   string asr = IsNa(g_s[0].fHi) ? "-" : Fp(g_s[0].fLo) + " - " + Fp(g_s[0].fHi);
   // broker clock check: the assumed GMT offset must match the real server clock
   string bk = "GMT";
   if(InpBrokerGmt >= 0) bk += "+";
   bk += IntegerToString(InpBrokerGmt);
   if(InpBrokerDst != TM_DST_NONE) bk += " (+1 summer)";
   color  bkC = C_GRAY;
#ifdef __MQL5__
   datetime srv = TimeTradeServer(), gm = TimeGMT();
   if(srv > 0 && gm > 0)
     {
      int real = (int)MathRound((double)(srv - gm) / 1800.0);          // half-hour units
      int mine = (int)MathRound((double)(srv - ServerToUtc(srv)) / 1800.0);
      if(real != mine) { bk += "  CHECK offset!"; bkC = C_BEAR; } else bkC = C_BULL;
     }
#endif
   PanelRow(0, "Crypex ICT Time", g_intra ? "" : "use <= H4", C_ORANGE);
   PanelRow(1, "Tehran | New York", clock, clrWhite);
   PanelRow(2, "Now", actNm, actCol);
   PanelRow(3, "Next", nextTxt, clrWhite);
   PanelRow(4, "Midnight Open", mo, moC);
   PanelRow(5, "Power of 3", po, g_po3Bias == 0 ? clrWhite : DirCol(g_po3Bias));
   PanelRow(6, "Last liquidity taken", rd, DirCol(g_raidDir));
   PanelRow(7, "SMT", sm, DirCol(g_smtDir));
   PanelRow(8, "Asia range", asr, clrWhite);
   PanelRow(9, "Broker clock", bk, bkC);
   PanelMeasure();
   int px = 0, py = 0;
   PanelOrigin(px, py);
   PanelBack(px, py);
   for(int r = 0; r < PN_ROWS; r++) PanelDrawRow(r, px, py);
  }

void PaintWatermark()
  {
   if(!InpShowWm)
     {
      ObjectDelete(0, PFX + "WM1");
      ObjectDelete(0, PFX + "WM2");
      return;
     }
   long bg = ChartGetInteger(0, CHART_COLOR_BACKGROUND);
   int r = (int)(bg & 0xFF), g = (int)((bg >> 8) & 0xFF), b = (int)((bg >> 16) & 0xFF);
   bool light = (r * 299 + g * 587 + b * 114) / 1000 > 128;
   color orgCol = light ? C'30,34,41' : clrWhite;
   TextSetFont("Arial Black", -InpWmSize * 10);            // measure the real text width so the two parts never overlap
   uint tw = 0, th = 0;
   TextGetSize("crypex", tw, th);
   int wCrypex = (tw > 0) ? (int)tw + 4 : (int)(InpWmSize * 7.0);
   string n1 = PFX + "WM1", n2 = PFX + "WM2";
   for(int k = 0; k < 2; k++)
     {
      string n = (k == 0) ? n1 : n2;
      if(ObjectFind(0, n) < 0) ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_LOWER);
      ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, n, OBJPROP_XDISTANCE, InpWmGap + (k == 0 ? 0 : wCrypex));
      ObjectSetInteger(0, n, OBJPROP_YDISTANCE, InpWmBottom);
      ObjectSetInteger(0, n, OBJPROP_COLOR, k == 0 ? BRAND_GREEN : orgCol);
      ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpWmSize);
      ObjectSetString(0, n, OBJPROP_FONT, "Arial Black");
      ObjectSetString(0, n, OBJPROP_TEXT, k == 0 ? "crypex" : ".org");
      ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, n, OBJPROP_ZORDER, 10);
     }
  }

//+------------------------------------------------------------------+
//| alerts                                                           |
//+------------------------------------------------------------------+
void Notify(const string msg)
  {
   string full = "Crypex ICT Time " + _Symbol + ": " + msg;
   if(InpAlertPopup) Alert(full);
   if(InpAlertPush)  SendNotification(full);
   if(InpAlertMail)  SendMail("Crypex ICT Time", full);
  }

void FireAlerts()
  {
   string names[9] = {"London killzone started", "New York AM killzone started", "New York PM killzone started", "Silver Bullet window started",
                      "Buy-side liquidity raided (bearish expectation)", "Sell-side liquidity raided (bullish expectation)",
                      "Liquidity run (level broken and held, not a raid)", "Judas Swing confirmed", "SMT divergence"};
   for(int k = 0; k < 9; k++) if(g_alLast[k]) Notify(names[k]);
  }

void FlagAlerts(const int L)
  {
   for(int k = 0; k < 9; k++) g_al[k] = false;
   g_al[0] = InpLonOn && Fl(L, 1) && !Fl(L - 1, 1);
   g_al[1] = InpNyaOn && Fl(L, 2) && !Fl(L - 1, 2);
   g_al[2] = InpNypOn && Fl(L, 5) && !Fl(L - 1, 5);
   g_al[3] = SbAny(L) && !SbAny(L - 1);
   g_al[4] = (g_evRaid == -1);
   g_al[5] = (g_evRaid == 1);
   g_al[6] = (g_evRun != 0);
   g_al[7] = (g_evJudas != 0);
   g_al[8] = (g_evSmt != 0);
   for(int k = 0; k < 9; k++) g_alLast[k] = g_al[k];
  }

//+------------------------------------------------------------------+
//| replay the whole window bar by bar, then draw the final state    |
//+------------------------------------------------------------------+
void Rebuild()
  {
   ObjectsDeleteAll(0, PFX);
   g_bg = (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND);
   g_intra = (PeriodSeconds(_Period) <= 14400);
   DetectMarket();
   InitSessions();
   ResetState();
   BuildSeries();
   const int L = g_n - 2;                          // last CLOSED bar: no repainting
   for(int i = 0; i <= L; i++) ProcessBar(i);
   FlagAlerts(L);
   g_nItem = 0;
   DrawShading();
   DrawBoxes();
   DrawProjections();
   DrawLiquidity();
   DrawAnchors();
   DrawIpda();
   FlushItems();
   DrawMarks();
   UpdatePanel();
   PaintWatermark();
   if(g_firstDone && g_t[g_n - 1] != g_lastAlertBar) FireAlerts();
   g_lastAlertBar = g_t[g_n - 1];
   g_firstDone = true;
   ChartRedraw();
  }


//+------------------------------------------------------------------+
int OnInit()
  {
   IndicatorShortName("Crypex ICT Time");
   if(InpRightMargin > 0)
     {
      ChartSetInteger(0, CHART_SHIFT, true);
      ChartSetDouble(0, CHART_SHIFT_SIZE, (double)MathMin(InpRightMargin, 50));
     }
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PFX);
  }

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_CHART_CHANGE && g_n >= 3) UpdatePanel();      // scroll / zoom / resize: re-pick the panel corner
  }

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
                const double &open[], const double &high[], const double &low[], const double &close[],
                const long &tick_volume[], const long &volume[], const int &spread[])
  {
   int total = MathMin(rates_total, InpMaxBars);
   if(total < 300) return 0;
   bool newBar = (Time[0] != g_lastBar) || prev_calculated == 0;
   if(!newBar)
     {
      UpdatePanel();                               // the clock runs on every tick
      return rates_total;
     }
   g_lastBar = Time[0];
   g_n = total;
   ArrayResize(g_t, g_n); ArrayResize(g_o, g_n); ArrayResize(g_h, g_n); ArrayResize(g_l, g_n); ArrayResize(g_c, g_n);
   for(int i = 0; i < g_n; i++)
     {
      int s = g_n - 1 - i;              // MT4 series arrays: index 0 = newest bar
      g_t[i] = Time[s];
      g_o[i] = Open[s];
      g_h[i] = High[s];
      g_l[i] = Low[s];
      g_c[i] = Close[s];
     }
   Rebuild();
   return rates_total;
  }
//+------------------------------------------------------------------+

منطق کار

  • سشن‌ها و کیل‌زون‌ها: ساعت‌ها طبق ICT به وقت نیویورک تعریف شده‌اند (آسیا ۲۰:۰۰ تا ۰۰:۰۰، لندن ۰۲:۰۰ تا ۰۵:۰۰، نیویورک AM ۰۷:۰۰ تا ۱۰:۰۰) و تغییر ساعت تابستانی را خود TradingView اعمال می‌کند. روی هر باکس، ساعت معادل به وقت تهران نوشته می‌شود و سقف و کف سشن در حین ساخته‌شدن دنبال می‌شود.
  • Silver Bullet و ماکرو: پنجره‌های یک‌ساعته‌ی ۰۳:۰۰ تا ۰۴:۰۰، ۱۰:۰۰ تا ۱۱:۰۰ و ۱۴:۰۰ تا ۱۵:۰۰ نیویورک به‌صورت نوار زرد کم‌رنگ نشان داده می‌شوند. پنجره‌های ماکروی کوتاه ICT اختیاری‌اند.
  • CBDR: رنج ۱۴:۰۰ تا ۲۰:۰۰ نیویورک. پروجکشن ±۱× و ±۲× ارتفاع آن به‌عنوان هدف‌های احتمالی کشیده می‌شود. «×» یعنی ضریب ارتفاع رنج، نه انحراف معیار آماری. روی فارکس اگر رنج از حد تنظیم‌شده (پیش‌فرض ۴۰ پیپ) بزرگ‌تر باشد، پروجکشن کشیده نمی‌شود.
  • Midnight Open و 08:30: قیمت باز شدن ساعت ۰۰:۰۰ نیویورک (شروع «روز واقعی» ICT) و ساعت ۰۸:۳۰ (زمان خبرهای مهم آمریکا) به‌عنوان خط مرجع. اگر تایم‌فریم اجازه ندهد قیمت دقیق همان دقیقه گرفته شود، کنار برچسب علامت ≈ می‌آید.
  • نقدینگی: PDH/PDL (روز قبل)، PWH/PWL (هفته‌ی قبل؛ هفته از یکشنبه‌شب برای فارکس و طلا و از دوشنبه برای کریپتو)، EQH/EQL (سقف و کف‌های برابر) و اختیاری PMH/PML و سقف و کف سشن‌ها. هر سطح با خط و برچسب قیمت نمایش داده می‌شود.
  • Raid، Break و Run: وقتی قیمت سطح را رد می‌کند و کندل به داخل سطح برمی‌گردد، «Raid» (جارو) ثبت می‌شود. اگر کندل بیرون سطح ببندد، «Break?» می‌آید و ظرف چند کندل (قابل تنظیم) تکلیفش مشخص می‌شود: برگشت به داخل = Raid و ماندن بیرون = «Run» (ادامه‌ی حرکت، نه سیگنال برگشت). وقتی در یک کندل چند سطح جارو می‌شود، سطح مهم‌تر و بعد نفوذ عمیق‌تر انتخاب می‌شود.
  • Judas Swing: جارو شدن سقف یا کف رنج آسیا بین ۰۰:۰۰ و ۰۵:۰۰ نیویورک با بسته‌شدن کندل داخل رنج، «Judas?» می‌دهد. وقتی کندل از طرف مقابل Midnight Open ببندد «Judas ✔» تأیید می‌شود و اگر بیرون اکسترمم جارو ببندد (شکست واقعی) ابطال می‌شود.
  • Power of 3: جهت روز فقط وقتی اعلام می‌شود که قیمت Midnight Open را جارو کند و در کندلی جدا و ظرف مهلت مشخص دوباره از آن برگردد. بسته‌شدن فراتر از اکسترمم دستکاری، جهت را پس می‌گیرد. کندلی که از هر دو طرف Midnight Open عبور کند نادیده گرفته می‌شود، چون ترتیب حرکت داخل آن معلوم نیست.
  • پنل: ساعت تهران و نیویورک، سشن فعلی، شمارش معکوس سشن بعدی (با در نظر گرفتن تعطیلی آخر هفته و ساعت تابستانی)، Midnight Open، فاز Power of 3 و آخرین نقدینگی برداشته‌شده.
  • بدون ریپینت: همه‌ی رویدادها روی کندل بسته‌شده ثبت می‌شوند و بعد از ثبت جابه‌جا نمی‌شوند.
  • ورژن متاتریدر: همان قواعد و همان ترتیب پردازش را دارد. ساعت سرور بروکر به UTC و بعد به نیویورک و تهران تبدیل می‌شود (قانون تغییر ساعت آمریکا و اروپا داخل کد است). هر بار کندل جدیدی بسته شود، پنجره‌ی محاسبه از اول بازپخش می‌شود و فقط حالت نهایی روی چارت رسم می‌شود. پس بعد از ثبت، رویدادها جابه‌جا نمی‌شوند.

تنظیمات مهم

Show times on chart in
منطقه‌ی زمانی ساعت‌هایی که روی برچسب‌ها و پنل نوشته می‌شود؛ پیش‌فرض تهران. خود ساعت‌های سشن همیشه به وقت نیویورک‌اند.
Broker server time (GMT offset in WINTER) و Broker daylight-saving rule (فقط متاتریدر)
اختلاف ساعت سرور بروکر با GMT در زمستان و قانون ساعت تابستانی بروکر (US، EU یا None). بیشتر بروکرهای فارکس GMT+2 در زمستان و GMT+3 در تابستان هستند و هم‌زمان با ساعت تابستانی آمریکا عوض می‌شوند.
Session box style
سبک باکس سشن‌ها: پر، حاشیه‌دار با پر سبک (پیش‌فرض)، فقط حاشیه (تمیزترین) یا خاموش.
Asian / London / NY AM / London Close / NY Lunch / NY PM
روشن و خاموش‌کردن هر سشن و تغییر ساعتش. پیش‌فرض: آسیا، لندن و نیویورک AM روشن.
Silver Bullet windows
نوارهای زرد ۰۳–۰۴، ۱۰–۱۱ و ۱۴–۱۵ نیویورک.
CBDR + Project deviations
رنج CBDR، تعداد انحراف‌ها (۱ تا ۴)، گام انحراف و فیلتر پیپ برای فارکس.
Midnight Open / 08:30 / 09:30 / Weekly Open
خط‌های مرجع قیمت. پیش‌فرض: Midnight و 08:30 روشن.
PDH/PDL، PWH/PWL، PMH/PML، EQH/EQL
انتخاب سطوح نقدینگی. «Max liquidity levels» سقف تعداد سطوح روی چارت را تعیین می‌کند.
Bars to wait before a break counts as a Run
چند کندل صبر شود تا یک شکست، «Run» حساب شود. روی ۱ دقیقه ۳ کندل یعنی ۳ دقیقه و روی ۱۵ دقیقه یعنی ۴۵ دقیقه.
Judas Swing و Power of 3
روشن و خاموش‌کردن آن‌ها و مهلت برگشت PO3 بر حسب کندل.
Right-side labels: gap و offset
فاصله‌ی حداقل برچسب‌های سمت راست از هم و فاصله‌شان از آخرین کندل. برای دیده‌شدن، حاشیه‌ی راست چارت را باز کنید.
SMT divergence (experimental)
واگرایی با یک نماد همبسته مثل DXY. آزمایشی و پیش‌فرض خاموش است.
IPDA look-back
خطوط سقف و کف ۲۰، ۴۰ و ۶۰ روزه. پیش‌فرض خاموش.
Panel و Watermark
نمایش و جای پنل و واترمارک.
Chart right margin و Days of Silver Bullet shading (فقط متاتریدر)
درصد حاشیه‌ی راست چارت برای جای برچسب‌ها (۰ = دست نزن) و تعداد روزهای آخری که نوار Silver Bullet و Macro برایشان رسم می‌شود.

کجا بهتر کار می‌کند

تحلیل ICT روی طلا، فارکس و شاخص‌ها در تایم‌فریم ۱ تا ۱۵ دقیقه (حداکثر ۴ ساعته): دیدن کیل‌زون‌ها و رنج‌ها به وقت تهران، علامت‌گذاری نقدینگی روز و هفته‌ی قبل، و دیدن اینکه کدام سطح جارو شده یا شکسته. برای ساخت نقشه‌ی روز قبل از ورود مناسب است.

نقطه‌ضعف

سیگنال ورود و خروج نمی‌دهد؛ Judas و جهت Power of 3 الگوهای شرطی‌اند، نه پیش‌بینی. تعریف بعضی مفاهیم (Flout، پنجره‌های Macro، SMT) برداشت رایج جامعه‌ی ICT است و منبع رسمی ندارد. روی تایم‌فریم بالای ۴ ساعته کار نمی‌کند و Openهای 00:00 و 08:30 روی ۱ ساعته و بالاتر ممکن است تقریبی باشند (≈). اگر در یک کندل چند سطح هم‌رتبه جارو شوند، فقط یکی نمایش داده می‌شود. SMT آزمایشی است و به نماد همبسته نیاز دارد. این اندیکاتور روی داده‌ی تاریخی بک‌تست نشده است. کد TradingView کامپایل و اجرا شده است. کد MT4 و MT5 در MetaEditor کامپایل نشده؛ فقط با شبیه‌ساز و داده‌ی واقعی طلا آزمایش شده (ساعت و تغییر ساعت نیویورک با پایگاه رسمی منطقه‌ی زمانی در ۱۱ سال مقایسه شد، سطوح روز و هفته با محاسبه‌ی مستقل یکی بود و خروجی با نسخه‌ی TradingView مقایسه شد). قبل از استفاده آن را کامپایل کنید و روی حساب دمو ببینید. در متاتریدر ساعت سشن‌ها از ساعت سرور بروکر محاسبه می‌شود، پس ساعت و قانون تغییر ساعت بروکر را باید درست وارد کنید. برچسب سشن‌ها یک‌خطی و کوتاه‌تر است، نوار Silver Bullet و Macro فقط برای چند روز آخر رسم می‌شود و کنترل خودکار ساعت بروکر در پنل فقط در MT5 هست.

راهنمای کامل استفاده

معنی رنگ‌ها و علامت‌ها

  • باکس‌ها: بنفش = رنج آسیا، آبی = لندن، سبز = نیویورک AM، فیروزه‌ای = CBDR. نوار زرد کم‌رنگ = پنجره‌ی Silver Bullet.
  • خط‌های ثابت: زرد = Midnight Open، نارنجی = 08:30 Open، آبی خط‌چین = PDH/PDL، بنفش = PWH/PWL، نارنجی‌تیره = EQH/EQL.
  • خط‌چین سبز و قرمز = پروجکشن CBDR (‎+1×، ‎+2×، ‎−1×، ‎−2×).
  • «Raid X» نارنجی = سطح جارو شد و کندل برگشت. «Break? X» خاکستری = شکست و در انتظار تکلیف. «Run X» = شکست ماند و حرکت ادامه یافت.
  • «✓» کنار سطح = آن سطح لمس شده است.
  • «Judas? ▼/▲» = احتمال جاداس. «Judas ▼/▲ ✔» = تأییدشده.
  • علامت ≈ کنار قیمت Open = قیمت تقریبی، چون کندل چارت دقیقاً روی آن دقیقه شروع نمی‌شود.

ساعت‌ها به وقت تهران

  • وقتی نیویورک ساعت زمستانی دارد (تقریباً از نوامبر تا مارس)، اختلاف تهران با نیویورک ۸ ساعت و ۳۰ دقیقه است. در ساعت تابستانی آمریکا ۷ ساعت و ۳۰ دقیقه. تغییر ساعت آمریکا با اروپا هم‌زمان نیست و TradingView آن را خودکار اعمال می‌کند؛ ساعت تهران ثابت است.
  • آسیا: ۰۴:۳۰ تا ۰۸:۳۰ تهران (تابستان آمریکا: ۰۳:۳۰ تا ۰۷:۳۰).
  • لندن: ۱۰:۳۰ تا ۱۳:۳۰ (تابستان آمریکا: ۰۹:۳۰ تا ۱۲:۳۰).
  • نیویورک AM: ۱۵:۳۰ تا ۱۸:۳۰ (تابستان آمریکا: ۱۴:۳۰ تا ۱۷:۳۰).
  • Silver Bullet: ۱۱:۳۰ تا ۱۲:۳۰، ۱۸:۳۰ تا ۱۹:۳۰ و ۲۲:۳۰ تا ۲۳:۳۰ (تابستان آمریکا یک ساعت زودتر).
  • CBDR: ۲۲:۳۰ تا ۰۴:۳۰. Midnight Open: ساعت ۰۸:۳۰ تهران. Open ساعت 08:30 نیویورک: ساعت ۱۷:۰۰ تهران (هر دو در تابستان آمریکا یک ساعت زودتر).

روش پیشنهادی در یک روز معاملاتی طلا

  • قبل از لندن: رنج آسیا، PDH/PDL، CBDR و پروجکشن‌ها را ببینید و بایاس تایم‌فریم بالاتر را مشخص کنید.
  • لندن: ببینید رنج آسیا یا PDH/PDL جارو می‌شود یا نه. «Raid» یا «Judas ✔» یعنی احتمال برگشت؛ برای ورود به ساختار و FVG در تایم‌فریم پایین‌تر نگاه کنید (اندیکاتور Models).
  • نیویورک AM: ساعت 08:30 نیویورک خبرهای مهم می‌آید. Open 08:30 را مرجع قرار دهید و ببینید قیمت بالا یا پایین آن است.
  • حد ضرر معمولاً پشت اکسترمم جارو و هدف نقدینگی سمت مقابل یا پروجکشن‌های CBDR است.
  • «Run» را سیگنال برگشت نگیرید؛ یعنی سطح شکسته و نگه داشته شد.

متاتریدر: تنظیم ساعت بروکر

  • ساعت سشن‌های ICT به وقت نیویورک است ولی متاتریدر ساعت سرور بروکر را می‌دهد. اندیکاتور ساعت سرور را به UTC و بعد به نیویورک و تهران تبدیل می‌کند، پس ساعت بروکر باید درست وارد شود.
  • ورودی «Broker server time (GMT offset in WINTER)»: اختلاف ساعت سرور با GMT در زمستان؛ بیشتر بروکرها عدد ۲ هستند.
  • ورودی «Broker daylight-saving rule»: US برای بروکرهای رایج «بسته‌شدن نیویورک» (GMT+2 زمستان، GMT+3 تابستان)، EU برای بروکرهای اروپایی و None اگر بروکر ساعت تابستانی ندارد.
  • راه کنترل: ساعت Market Watch را با ساعت UTC مقایسه کنید. در MT5 ردیف «Broker clock» پنل سبز می‌شود و اگر تنظیم غلط باشد قرمز با پیام «CHECK offset!».
  • اگر ساعت همه‌ی سشن‌ها یک ساعت جابه‌جا بود، تقریباً همیشه قانون تغییر ساعت یا اختلاف زمستانی اشتباه است.
  • ورودی «Chart right margin» حاشیه‌ی راست چارت را خودش باز می‌کند (پیش‌فرض ۱۵ درصد؛ صفر یعنی چارت را تغییر نده).

نصب و تنظیم اولیه

  • تایم‌فریم را ۱ تا ۱۵ دقیقه بگذارید. روی بالاتر از ۴ ساعته اندیکاتور هشدار می‌دهد.
  • TradingView: از Settings > Scales حاشیه‌ی راست چارت را حدود ۱۵ تا ۲۰ کندل باز کنید تا برچسب‌های قیمت جا داشته باشند.
  • متاتریدر: فایل را در MetaEditor با F7 کامپایل کنید و از Navigator روی چارت بکشید. برای چارت تمیز سبک باکس را «Border only» بگذارید.
  • برای چارت تمیز در هر دو پلتفرم: سطوح اضافه (EQH/EQL، PMH/PML، IPDA) را خاموش نگه دارید.
  • ۹ شرط هشدار تعریف شده: شروع کیل‌زون‌های لندن و نیویورک AM و PM، شروع Silver Bullet، Raid بالا و پایین، Run، Judas تأییدشده و SMT. در متاتریدر هشدار پاپ‌آپ، پوش و ایمیل جدا قابل تنظیم است.
  • برای ساختار بازار، FVG و بلوک‌ها از اندیکاتور Models استفاده کنید؛ Time فقط زمان و نقدینگی را نشان می‌دهد و هیچ‌کدام سیگنال معاملاتی نیست.

پرسش‌های متداول

چرا ساعت سشن‌ها با ساعت چارت من فرق دارد؟

ساعت‌های سشن طبق روش ICT به وقت نیویورک تعریف شده‌اند. اندیکاتور روی هر برچسب ساعت معادل به وقت تهران را می‌نویسد و تغییر ساعت تابستانی آمریکا را خودکار اعمال می‌کند.

فرق Raid و Run چیست؟

Raid یعنی قیمت سطح نقدینگی را رد کرد و کندل به داخل برگشت و انتظار برگشت دارد. Run یعنی بیرون سطح بسته شد و ماند، پس حرکت ادامه دارد و سیگنال برگشت نیست. تا چند کندل (قابل تنظیم) وضعیت «Break?» است.

چرا کنار بعضی Openها علامت ≈ هست؟

اگر کندل چارت دقیقاً روی دقیقه‌ی ۰۰:۰۰ یا ۰۸:۳۰ شروع نشود (مثلاً روی ۱ ساعته یا ۴ ساعته)، قیمت دقیق آن دقیقه از کندل در دسترس نیست. روی ۵، ۱۵ و ۳۰ دقیقه قیمت دقیق است.

روی بیت‌کوین هم کار می‌کند؟

بله، ولی سشن‌ها برای بازار ۲۴ساعته‌ی کریپتو کمتر معنا دارند. هفته در کریپتو از دوشنبه شروع می‌شود و برای فارکس و طلا از یکشنبه‌شب.

چرا SMT پیش‌فرض خاموش است؟

SMT آزمایشی است، با پیوت‌ها کار می‌کند و چند کندل دیرتر تأیید می‌شود و برای آن به نماد همبسته مثل DXY نیاز است. اگر خواستید در تنظیمات روشنش کنید.

آیا سیگنال خرید و فروش می‌دهد؟

نه. این اندیکاتور زمان، رنج‌ها و نقدینگی را نشان می‌دهد. ساختار بازار، FVG و بلوک‌ها (و مدل‌های ورود اختیاری) در اندیکاتور همراه (Models) هستند.

نسخه‌ی متاتریدر دارد؟

بله، MT5 و MT4 با همان قواعد. این دو نسخه را در MetaEditor کامپایل نکرده‌ایم و فقط با شبیه‌ساز و داده‌ی واقعی آزمایش شده‌اند؛ قبل از استفاده کامپایل کنید و روی دمو ببینید. ساعت بروکر را هم باید درست تنظیم کنید.

چرا در متاتریدر ساعت سشن‌ها غلط است؟

معمولاً ساعت سرور بروکر یا قانون ساعت تابستانی در تنظیمات اندیکاتور درست وارد نشده است. اختلاف ساعت سرور با GMT را در زمستان وارد کنید و قانون US یا EU یا None را مطابق بروکر خود بگذارید. در MT5 ردیف «Broker clock» پنل تفاوت را نشان می‌دهد.

اندیکاتور همراه: همراه این اندیکاتور: Crypex ICT Models (ساختار، FVG، بلوک‌ها و Unicorn؛ مدل‌های ورود اختیاری)

این کد آموزشی است و توصیه‌ی سرمایه‌گذاری نیست. قبل از استفاده روی حساب واقعی، کامپایل و رفتار آن را روی حساب دمو و در بک‌تست بررسی کنید؛ نتیجه‌ی گذشته تضمین‌کننده‌ی آینده نیست.

← همه‌ی اسکریپت‌ها