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 و ساعت زنده به وقت تهران.
پیشنمایش


این تصویرها اسکرینشات از TradingView یا متاتریدر نیستند؛ با اجرای همین کد روی دادهی واقعی بازار ساخته و رسم شدهاند. ظاهر دقیق در پلتفرم شما (فونت، رنگها و اندازه) کمی متفاوت است.
// 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)
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 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؛ مدلهای ورود اختیاری)
این کد آموزشی است و توصیهی سرمایهگذاری نیست. قبل از استفاده روی حساب واقعی، کامپایل و رفتار آن را روی حساب دمو و در بکتست بررسی کنید؛ نتیجهی گذشته تضمینکنندهی آینده نیست.
← همهی اسکریپتها