ICT · اندیکاتور
Crypex ICT Models: ساختار بازار، FVG، بلوکها و Unicorn (تحلیلی)
اندیکاتور تحلیلی ساختار و نواحی سبک ICT برای TradingView، متاتریدر ۵ و متاتریدر ۴: MSS و BOS، Fair Value Gap با CE و IFVG، Order Block، Breaker، Mitigation، Unicorn، پریمیوم/دیسکانت و OTE، تشخیص Raid و Run و پنل وضعیت. پیشفرض فقط تحلیلی است و ستاپ خرید و فروش نمیسازد؛ مدلهای ICT 2022، Silver Bullet، Unicorn و OTE اختیاریاند.
پیشنمایش


این تصویرها اسکرینشات از TradingView یا متاتریدر نیستند؛ با اجرای همین کد روی دادهی واقعی بازار ساخته و رسم شدهاند. ظاهر دقیق در پلتفرم شما (فونت، رنگها و اندازه) کمی متفاوت است.
// Crypex ICT Models — اندیکاتور ساختار، ناحیهها و مدلهای ورود ICT (MSS، FVG، OB، Breaker، Unicorn، OTE، ICT 2022، Silver Bullet)
// © 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 Models (2 of 2) — structure, zones and entry models:
// STRUCTURE Swings, Displacement, MSS / BOS, Premium / Discount / Equilibrium, OTE 62-70.5-79.
// ZONES FVG (BISI/SIBI, CE, partial fill), IFVG, Volume Imbalance, BPR, Order Block, Breaker, Mitigation, Rejection, Propulsion,
// Vacuum, NWOG/NDOG, Unicorn (Breaker + FVG).
// MODELS ICT 2022 (raid -> MSS -> FVG), Silver Bullet, Unicorn, OTE — entry / SL / TP, confluence score 0-6, win-rate table.
// It keeps its own light liquidity levels (PDH/PDL, PWH/PWL, session highs/lows, EQH/EQL) for raids and targets;
// the full session/liquidity drawing lives in "Crypex ICT Time". All state changes happen on CONFIRMED bars only (no repaint of signals).
// Session times are New York time by default. Best on intraday charts 1m–1H.
indicator('Crypex ICT Models', 'ICT Models', overlay = true, max_bars_back = 1500, 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 · Killzone times (used by the models)'
G3 = '3 · Silver Bullet windows'
G6 = '4 · Liquidity levels (raids & targets)'
G8 = '5 · Market structure & displacement'
G9 = '6 · Premium / Discount & OTE'
G10 = '7 · Fair Value Gaps (FVG / IFVG / VI / BPR)'
G11 = '8 · Blocks, Breaker, Mitigation, Rejection, Propulsion, Vacuum, Gaps, Unicorn'
G13 = '9 · Models & signals'
G14 = '10 · Panel & watermark'
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// INPUTS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
preset = input.string('Clean (recommended)', 'Preset', options = ['Clean (recommended)', 'Standard', 'Full', 'Custom'], group = G1, tooltip = 'Clean = فقط ارزشمندترین اجزای ICT با چارت خلوت (پیشفرض). Standard = همهی اجزا مثل نسخهی معمول. Full = همهچیز، بیشترین تعداد. Custom = فقط تنظیمات دستی خودتان (همان گزینههای پایین). در سه حالت اول، گزینههای زیر فقط روی «جزئیات نمایش» اثر دارند: نمایش علامت دیسپلیسمنت، BOS، IFVG، VI، BPR، Mitigation، Rejection، Propulsion، Vacuum، خط 50٪ بلوکها، ناحیهی پریمیوم/دیسکانت، لولهای سشن، PMH/PML، جدول Win-rate و سقف تعداد FVG/بلوک/لول. بقیهی گزینهها همیشه فعالاند.')
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)
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 = 'برچسبهای سمت راست (لولها، بلوکها، FVG، ستاپها) خودکار از هم فاصله میگیرند تا روی هم نیفتند. عدد بزرگتر = فاصلهی بیشتر.')
lblPad = input.int(3, 'Right-side labels: offset from last bar (bars)', minval = 1, maxval = 30, group = G1, tooltip = 'برای دیدهشدن برچسبها، حاشیهی راست چارت را بازتر کنید (Settings > Scales > Right margin حدود ۱۵ تا ۲۰).')
lblNear = input.float(0.3, 'Show zone labels within (× recent range from price)', minval = 0.1, maxval = 3.0, step = 0.1, group = G1, tooltip = 'برچسب بلوکها و FVGهای دور از قیمت مخفی میشود تا ستون راست شلوغ نشود (خود ناحیه روی چارت میماند).')
lsz = input.string(size.small, 'Label size', options = [size.tiny, size.small, size.normal, size.large], group = G1)
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(true, 'London Close killzone', group = G2, inline = 'lc')
lcR = input.session('1000-1200', '', group = G2, inline = 'lc')
lcC = input.color(#FF9800, '', group = G2, inline = 'lc')
nypOn = input.bool(true, '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(true, 'Turn finished session highs/lows into liquidity levels', group = G2)
sbOn = input.bool(true, 'Silver Bullet windows', 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)
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')
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(60, 'Keep swept levels for (bars)', minval = 0, maxval = 500, group = G6)
maxLiq = input.int(40, 'Max liquidity levels on chart', minval = 10, maxval = 120, group = G6)
swLen = input.int(5, 'Swing length', minval = 2, maxval = 30, group = G8)
dispATR = input.float(1.2, 'Displacement: body ≥ × ATR', minval = 0.3, step = 0.1, group = G8)
dispBody = input.float(0.6, 'Displacement: body / range ≥', minval = 0.3, maxval = 1.0, step = 0.05, group = G8)
dispMark = input.bool(true, 'Mark displacement candles', group = G8)
mssNeedD = input.bool(false, 'MSS needs displacement (else plain break = BOS/CHoCH label)', group = G8)
showBos = input.bool(true, 'Show BOS', group = G8)
showMss = input.bool(true, 'Show MSS', group = G8)
structKeep = input.int(14, 'Structure lines to keep', minval = 2, maxval = 60, group = G8)
pdZoneOn = input.bool(true, 'Premium / Discount / Equilibrium (last dealing range)', group = G9)
oteOn = input.bool(true, 'OTE zone (62 – 79 %)', group = G9)
oteLo = input.float(0.62, 'OTE from', minval = 0.5, maxval = 0.9, step = 0.005, group = G9)
oteHi = input.float(0.79, 'OTE to', minval = 0.5, maxval = 0.95, step = 0.005, group = G9)
fvgOn = input.bool(true, 'Fair Value Gaps', group = G10)
fvgMin = input.float(0.10, 'Min FVG size (× ATR)', minval = 0, step = 0.05, group = G10)
fvgKeep = input.int(16, 'Max active FVGs', minval = 2, maxval = 60, group = G10)
fvgQual = input.bool(false, 'Quality filter: middle candle must be a strong move', group = G10, tooltip = 'ICT: FVG معتبر معمولاً از کندل میانیِ دیسپلیسمنتی (حرکت قوی) ساخته میشود، نه هر شکاف کوچک سهکندلی. اگر روشن باشد، فقط FVGهایی که بدنهی کندل وسط ≥ مقدار پایین × ATR و همجهت شکاف است ثبت میشوند. در Preset «Clean» همیشه روشن است.')
fvgMid = input.float(0.5, 'Quality filter: middle candle body ≥ × ATR', minval = 0.2, maxval = 3.0, step = 0.1, group = G10)
fvgCE = input.bool(true, 'Show Consequent Encroachment (50 %)', group = G10)
fvgShrink = input.bool(true, 'Shrink FVG when partly filled', group = G10)
ifvgOn = input.bool(true, 'Inversion FVG (IFVG)', group = G10)
viOn = input.bool(true, 'Volume Imbalance (VI)', group = G10)
bprOn = input.bool(true, 'Balanced Price Range (BPR)', group = G10)
fvgBullC = input.color(#089981, 'Bullish FVG colour', group = G10)
fvgBearC = input.color(#F23645, 'Bearish FVG colour', group = G10)
obOn = input.bool(true, 'Order Block (BOS continuation)', group = G11)
brkOn = input.bool(true, 'Breaker Block (approx.: MSS after a lower-low / higher-high sweep)', group = G11)
mitOn = input.bool(true, 'Mitigation Block (approx.: MSS without sweep)', group = G11)
rbOn = input.bool(true, 'Rejection Block (long-wick swing candle)', group = G11)
pbOn = input.bool(true, 'Propulsion Block (candle rejecting a block)', group = G11)
vacOn = input.bool(true, 'Vacuum Block / NWOG / NDOG (price gaps)', group = G11)
uniOn = input.bool(true, 'Unicorn (Breaker + FVG overlap)', group = G11)
zoneMode = input.string('Full candle', 'Block zone from', options = ['Full candle', 'Body only', 'Open to extreme'], group = G11)
zoneMT = input.bool(true, 'Show 50 % mean threshold of blocks', group = G11)
zoneMax = input.int(24, 'Max blocks on chart', minval = 4, maxval = 80, group = G11)
rbPct = input.float(0.5, 'Rejection: wick ≥ % of candle', minval = 0.3, maxval = 0.9, step = 0.05, group = G11)
rbATR = input.float(0.5, 'Rejection: wick ≥ × ATR', minval = 0, step = 0.1, group = G11)
gapATR = input.float(0.3, 'Gap: min size (× ATR)', minval = 0.05, step = 0.05, group = G11)
obBullC = input.color(#089981, 'Bullish blocks colour', group = G11)
obBearC = input.color(#F23645, 'Bearish blocks colour', group = G11)
sigOn = input.bool(false, 'Trade setups: Entry / SL / TP signals (off = analysis only)', group = G13, tooltip = 'پیشفرض خاموش: اندیکاتور فقط تحلیلی است (ساختار، MSS، FVG، بلوکها، نقدینگی، Raid). هیچ ستاپ خرید/فروش، خط ورود/حد ضرر/حد سود، آمار برد و باخت یا هشدار BUY/SELL تولید نمیشود. اگر روشن کنید، مدلهای پایین (ICT 2022، Silver Bullet، Unicorn، OTE) ستاپ میسازند. توجه: آمار برد/باخت یک شبیهسازی ساده است، نه بکتست.')
m22On = input.bool(true, 'Model: ICT 2022 (raid → MSS → FVG)', group = G13)
mSbOn = input.bool(true, 'Model: Silver Bullet (FVG inside window)', group = G13)
mUniOn = input.bool(true, 'Model: Unicorn', group = G13)
mOteOn = input.bool(true, 'Model: OTE', group = G13)
uniWin = input.int(6, 'Unicorn: FVG must form within N bars after the MSS', minval = 1, maxval = 30, group = G13, tooltip = 'FVG باید در همان حرکت MSS ساخته شود، نه ساعتها بعد. عدد کوچکتر = Unicorn سختگیرانهتر.')
kzOnly = input.bool(true, 'Signals only inside killzones / Silver Bullet', group = G13)
mssWin = input.int(30, 'ICT 2022: max bars from raid to MSS', minval = 3, maxval = 200, group = G13)
entryMode = input.string('FVG CE (50%)', 'Entry on FVG', options = ['FVG CE (50%)', 'FVG edge'], group = G13)
slBuf = input.float(0.10, 'Stop buffer (× ATR)', minval = 0, step = 0.05, group = G13)
minRR = input.float(1.5, 'Min reward : risk', minval = 0.5, step = 0.1, group = G13)
fbRR = input.float(2.0, 'Fallback target (R) if no liquidity target', minval = 0.5, step = 0.5, group = G13)
expBars = input.int(40, 'Cancel unfilled setup after (bars)', minval = 3, maxval = 500, group = G13)
minScore = input.int(3, 'Min confluence score (0–6)', minval = 0, maxval = 6, group = G13, tooltip = 'امتیاز: کیلزون، رید نقدینگی همجهت، MSS همجهت، دیسپلیسمنت، بایاس HTF، پریمیوم/دیسکانت')
sbBias = input.bool(true, 'Silver Bullet needs raid or MSS in the same direction', group = G13)
biasTF = input.timeframe('240', 'HTF bias timeframe', group = G13)
biasLen = input.int(21, 'HTF bias EMA length', minval = 2, group = G13)
oteSL = input.string('Beyond OTE zone', 'OTE model stop', options = ['Beyond OTE zone', 'Swing origin (100%)'], group = G13, tooltip = 'Beyond OTE zone = زیر/بالای ۷۹٪ (ریسک کم). Swing origin = پشت ابتدای حرکت (ریسک بیشتر).')
ageMin = input.int(240, 'Event validity for the score (minutes)', minval = 15, maxval = 2880, group = G13, tooltip = 'اعتبار Raid / MSS برای امتیاز و Silver Bullet بر حسب دقیقهی واقعی (با زمان خود رویداد؛ تعطیلی و گپ بازار هم حساب میشود). رویداد قدیمیتر از این مدت امتیاز نمیگیرد.')
htfReq = input.bool(false, 'Require HTF bias in the trade direction', group = G13, tooltip = 'پیشفرض خاموش: HTF فقط یک امتیاز از ۶ است. اگر روشن شود، ستاپ برخلاف بایاس HTF ساخته نمیشود.')
costATR = input.float(0.03, 'Trading cost (× ATR) in the win-rate table', minval = 0, step = 0.01, group = G13, tooltip = 'اسپرد و لغزش تقریبی: برای رسیدن به TP قیمت باید این مقدار فراتر برود و SL بهاندازهی آن زودتر فعال میشود.')
showSetups = input.bool(true, 'Draw entry / SL / TP', group = G13, tooltip = 'آمار برد/باخت یک شبیهسازی ساده روی کندلهای بسته است، بکتست نیست. ترتیب حرکت داخل کندل معلوم نیست، پس سیاست محافظهکارانه است: اگر در یک کندل هم SL و هم TP لمس شود = ضرر. اگر TP قبل از ورود (یا همزمان با ورود) لمس شود = ستاپ لغو و بدون نتیجه. ورود OTE یک لیمیت در قیمت Close کندل سیگنال است و از کندل بعد پر میشود.')
showPanel = input.bool(true, 'Show info panel', group = G14)
panelPos = input.string(position.bottom_right, 'Panel position', options = [position.top_left, position.top_center, position.top_right, position.middle_left, position.middle_center, position.middle_right, position.bottom_left, position.bottom_center, position.bottom_right], group = G14, tooltip = 'جای جدول روی چارت. هر کدام از ۹ نقطه را میتوانید انتخاب کنید.')
psz = input.string(size.small, 'Panel text size', options = [size.tiny, size.small, size.normal], group = G14)
showStats = input.bool(false, 'Win-rate table of the models (adds 5 rows to the panel)', group = G14)
showWatermark = input.bool(true, 'Show crypex.org watermark', group = G14)
wmPos = input.string(position.bottom_center, 'Watermark position', options = [position.top_left, position.top_center, position.top_right, position.middle_left, position.middle_center, position.middle_right, position.bottom_left, position.bottom_center, position.bottom_right], 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('large', 'Watermark size', options = ['small', 'normal', 'large', 'huge'], group = G14)
wmTransp = input.int(0, 'Watermark transparency', minval = 0, maxval = 95, group = G14)
// ── presets: Clean / Standard / Full set the "how much to draw" options; Custom uses the inputs exactly as set
f_pb(bool c, bool st, bool fu, bool cu) => preset == 'Clean (recommended)' ? c : preset == 'Standard' ? st : preset == 'Full' ? fu : cu
f_pi(int c, int st, int fu, int cu) => preset == 'Clean (recommended)' ? c : preset == 'Standard' ? st : preset == 'Full' ? fu : cu
f_pf(float c, float st, float fu, float cu) => preset == 'Clean (recommended)' ? c : preset == 'Standard' ? st : preset == 'Full' ? fu : cu
bool dispMarkE = f_pb(false, true, true, dispMark)
bool showBosE = f_pb(false, true, true, showBos)
bool ifvgOnE = f_pb(false, true, true, ifvgOn)
bool viOnE = f_pb(false, true, true, viOn)
bool bprOnE = f_pb(false, true, true, bprOn)
bool mitOnE = f_pb(false, true, true, mitOn)
bool rbOnE = f_pb(false, true, true, rbOn)
bool pbOnE = f_pb(false, true, true, pbOn)
bool vacOnE = f_pb(false, true, true, vacOn)
bool zoneMTE = f_pb(false, true, true, zoneMT)
bool pdZoneOnE = f_pb(false, true, true, pdZoneOn)
bool sessLvlOnE = f_pb(false, true, true, sessLvlOn)
bool pmOnE = f_pb(false, false, true, pmOn)
bool fvgQualE = f_pb(true, false, false, fvgQual)
float fvgMinE = f_pf(0.3, 0.1, 0.05, fvgMin)
bool showStatsE = sigOn and f_pb(false, false, true, showStats)
int fvgKeepE = f_pi(5, 16, 30, fvgKeep)
int zoneMaxE = f_pi(6, 24, 40, zoneMax)
float lblNearE = f_pf(0.25, 0.3, 0.6, lblNear)
int maxLiqE = f_pi(12, 40, 60, maxLiq)
int keepSweptE = f_pi(20, 60, 100, keepSwept)
int structKeepE = f_pi(6, 14, 24, structKeep)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 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
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 Fvg
float top
float bot
int dir
int bar
int state = 0 // 0 normal, 1 inverted
float ext = na // extreme of the 3-candle formation (SL reference)
bool uni = false
bool bp = false
string nm = 'FVG'
box bx
line ce
label lb
type Zone
float top
float bot
int dir
int kind // 1 OB, 2 Breaker, 3 Mitigation, 4 Rejection, 5 Propulsion, 6 Gap
string nm = ''
int bar
int born = 0
bool touched = false
bool prop = false
bool uni = false
box bx
line mt
label lb
type Setup
int model
int dir
float entry
float sl
float tp
int born
int fillBar = 0
int state = 0 // 0 pending, 1 active, 2 finished
int score
float rr
float cost = 0.0
line lEn
line lSl
line lTp
label lb
type St
// structure
int trend = 0
float swHi = na
int swHiBar = 0
float pSwHi = na
bool swHiX = true
float swLo = na
int swLoBar = 0
float pSwLo = na
bool swLoX = true
int mssDir = 0
int mssBar = -9999
int mssT = 0
int dispBullBar = -9999
int dispBearBar = -9999
// raids
int raidDir = 0
int raidBar = -9999
int raidT = 0
float raidPx = na
string raidName = ''
bool raidRej = false
int raidSc = 0
label raidMk = na
float raidPen = 0.0
int raidScBar = -1
int evRun = 0
// 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
bool jdDone = false
// anchors
float moPx = na
// dealing range
float eq = na
float rngTop = na
float rngBot = na
int oteDir = 0
float oteTop = na
float oteBot = na
float oteMid = na
float oteSl = na
float oteTp = na
bool oteFired = false
// SMT
float phM = na
float phS = na
float plM = na
float plS = na
int smtDir = 0
int smtBar = -9999
// PO3
float po3Hi = na
float po3Lo = na
int po3Bias = 0
// model state
int w22Dir = 0
int w22Bar = 0
float w22Px = na
bool sbFired = false
// events of this bar
int evRaid = 0
int evMss = 0
int evBrk = 0
int evFvg = 0
int evUni = 0
int evSig = 0
int evJudas = 0
int evSmt = 0
string evModel = ''
float nfTop = na
float nfBot = na
float nfExt = na
int nfDir = 0
float uniTop = na
float uniBot = na
float uniFar = na
int uniDir = 0
int uniBar = -9999
bool uniDone = true
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// GLOBAL STATE
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var St S = St.new()
var Sess sAsia = Sess.new(name = 'Asia', rng = asiaR, on = asiaOn, col = asiaC, proj = false, lvl = true, kz = false, pl = array.new<line>(), pll = array.new<label>())
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>())
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>())
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>())
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>())
var array<Sess> allS = array.from(sAsia, sLon, sNya, sLc, sNyp)
var array<Liq> liqs = array.new<Liq>()
var array<Fvg> fvgs = array.new<Fvg>()
var array<Zone> zones = array.new<Zone>()
var array<Setup> setups = array.new<Setup>()
var array<box> miscBx = array.new<box>() // VI / BPR / Unicorn boxes
var array<line> stLines = array.new<line>()
var array<label> stLbls = array.new<label>()
var array<label> evLbls = array.new<label>() // raid / judas / smt markers
var array<label> mkLbls = array.new<label>() // Break? / Run / Raid markers
var array<label> dispLbls = array.new<label>() // displacement markers
var array<line> pdLines = array.new<line>()
var array<box> pdBoxes = array.new<box>()
var array<label> pdLbls = array.new<label>()
var array<float> pdP = array.new<float>()
var array<int> wins = array.new_int(5, 0)
var array<int> losses = array.new_int(5, 0)
var array<int> capRej = array.new_int(1, 0) // attempts rejected because 40 trades were already open / pending (one setup may be retried, so this counts attempts)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// BASIC SERIES
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
float atr = ta.atr(14)
float rng150 = ta.highest(high, 150) - ta.lowest(low, 150)
int ageMs = ageMin * 60000
float pivH = ta.pivothigh(high, swLen, swLen)
float pivL = ta.pivotlow(low, swLen, swLen)
bool intra = timeframe.isintraday and timeframe.in_seconds() <= 14400
float body = math.abs(close - open)
bool midBull = close > open and body >= fvgMid * atr // candidate middle candle of a bullish FVG (read at [1])
bool midBear = close < open and body >= fvgMid * atr
float crng = high - low
bool dispBull = close > open and crng > 0 and body >= dispATR * atr and body / crng >= dispBody
bool dispBear = close < open and crng > 0 and body >= dispATR * atr and body / crng >= dispBody
// previous confirmed higher-timeframe bar only (lookahead_on + [1]) -> the bias cannot repaint
int htfB = request.security(syminfo.tickerid, biasTF, close[1] > ta.ema(close, biasLen)[1] ? 1 : -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 inNyp = intra and f_in(nypR)
bool inSb1 = intra and f_in(sb1R)
bool inSb2 = intra and f_in(sb2R)
bool inSb3 = intra and f_in(sb3R)
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
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
bool wkndGap = bar_index > 0 and (time - time[1]) >= 24 * 3600 * 1000 and timeframe.isintraday
bool ndogBar = bar_index > 0 and timeframe.isintraday and minDay >= 1020 and minDay1 < 1020 and not dayChg
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// HELPERS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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) =>
label m = label.new(x, y, txt, style = st, color = color.new(c, 100), textcolor = c, size = lsz)
array.push(evLbls, m)
f_trimLbl(evLbls, 60)
f_markD(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(dispLbls, m)
f_trimLbl(dispLbls, 12)
f_markL(int x, float y, string txt, color c, string st) =>
// several markers on the same bar are stacked (never printed on top of each other)
int cnt = 0
int nm = array.size(mkLbls)
if nm > 0
for k = 0 to nm - 1
if label.get_x(array.get(mkLbls, k)) == x
cnt += 1
float off = cnt * 0.7 * nz(atr, 0)
label m = label.new(x, st == label.style_label_down ? y + off : y - off, txt, style = st, color = color.new(c, 100), textcolor = c, size = lsz)
array.push(mkLbls, m)
f_trimLbl(mkLbls, maxLiqE + 10)
m
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) >= maxLiqE
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.raidT := time
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 > keepSweptE
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 <= mssWin
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)
f_target(int d, float entry, float minDist) =>
float best = na
for q in liqs
if not q.swept and q.dir == d
if d == 1 and q.price >= entry + minDist
if na(best) or q.price < best
best := q.price
if d == -1 and q.price <= entry - minDist
if na(best) or q.price > best
best := q.price
best
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SESSION HIGHS / LOWS (tracking only, no drawing)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
method upd(Sess s, bool inS) =>
bool ended = false
if s.on
if inS
if not s.active
s.active := true
s.hi := high
s.lo := low
s.hiBar := bar_index
s.loBar := bar_index
else
if high > s.hi
s.hi := high
s.hiBar := bar_index
if low < s.lo
s.lo := low
s.loBar := bar_index
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
ended
f_sessEnd(Sess s) =>
if s.lvl and sessLvlOnE
f_addLiq(s.fHi, s.name + ' H', 1, 1, s.fHiBar, sessLC)
f_addLiq(s.fLo, s.name + ' L', -1, 1, s.fLoBar, sessLC)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// DAY / WEEK / MONTH LEVELS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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.po3Hi := high
S.po3Lo := low
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 pmOnE
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
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ZONES (blocks)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_zoneCol(int kind, int d) =>
color c = d == 1 ? obBullC : obBearC
if kind == 2
c := d == 1 ? C_CYAN : C_ORANGE
if kind == 3
c := d == 1 ? C_PURPLE : C_PINK
if kind == 4
c := C_YELLOW
if kind == 5
c := C_LIME
if kind == 6
c := C_GRAY
c
f_zoneName(int kind, int d) =>
string n = ''
if kind == 1
n := d == 1 ? 'Bull OB' : 'Bear OB'
if kind == 2
n := d == 1 ? 'Bull Breaker' : 'Bear Breaker'
if kind == 3
n := d == 1 ? 'Bull Mitigation' : 'Bear Mitigation'
if kind == 4
n := d == 1 ? 'Bull Rejection' : 'Bear Rejection'
if kind == 5
n := d == 1 ? 'Bull Propulsion' : 'Bear Propulsion'
n
f_addZone(float top, float bot, int d, int kind, int bar, string nm) =>
bool ok = (kind == 1 and obOn) or (kind == 2 and brkOn) or (kind == 3 and mitOnE) or (kind == 4 and rbOnE) or (kind == 5 and pbOnE) or (kind == 6 and vacOnE)
Zone res = na
bool dup = false // the same candle can produce the same block twice (repeated breaks of one swing): keep one
if ok and top > bot
for q in zones
if q.kind == kind and q.dir == d and q.bar == bar
dup := true
res := q
if ok and top > bot and not dup
color c = f_zoneCol(kind, d)
while array.size(zones) >= zoneMaxE
Zone z0 = array.shift(zones)
box.delete(z0.bx)
if not na(z0.mt)
line.delete(z0.mt)
if not na(z0.lb)
label.delete(z0.lb)
box bx = box.new(left = bar, top = top, right = bar_index, bottom = bot, extend = extend.right, border_color = color.new(c, 30), border_width = 1, bgcolor = color.new(c, 85))
line mt = na
if zoneMTE
mt := line.new(bar, (top + bot) / 2, bar_index, (top + bot) / 2, extend = extend.right, color = color.new(c, 40), style = line.style_dotted, width = 1)
label lb = label.new(bar, d == 1 ? top : bot, nm, style = d == 1 ? label.style_label_down : label.style_label_up, color = color.new(c, 100), textcolor = c, size = lsz)
res := Zone.new(top = top, bot = bot, dir = d, kind = kind, bar = bar, born = bar_index, nm = nm, bx = bx, mt = mt, lb = lb)
array.push(zones, res)
res
f_findOff(int cBar, bool wantBear, int span) =>
int base = bar_index - cBar
int res = base
bool found = false
for k = 0 to span
int o = base + k
if o <= 1000 and not found
bool isb = close[o] < open[o]
if isb == wantBear
res := o
found := true
res
f_blockFromOff(int o, int d) =>
float t = high[o]
float b = low[o]
if zoneMode == 'Body only'
t := math.max(open[o], close[o])
b := math.min(open[o], close[o])
if zoneMode == 'Open to extreme'
if d == 1
t := math.max(open[o], close[o])
else
b := math.min(open[o], close[o])
[t, b]
f_zoneAt(int cBar, bool wantBear, int d, int kind, string nm) =>
if bar_index - cBar <= 900
int o = f_findOff(cBar, wantBear, 3)
[t, b] = f_blockFromOff(o, d)
f_addZone(t, b, d, kind, bar_index - o, nm)
f_updZones() =>
array<float> pq = array.new<float>() // deferred propulsion blocks: top, bot, dir
int i = array.size(zones)
while i > 0
i -= 1
Zone z = array.get(zones, i)
bool dead = false
if z.kind == 6
// gap: filled once price trades through the whole zone
if z.dir == 1
dead := low <= z.bot
else
dead := high >= z.top
else
dead := z.dir == 1 ? close < z.bot : close > z.top
if dead
array.remove(zones, i)
box.delete(z.bx)
if not na(z.mt)
line.delete(z.mt)
if not na(z.lb)
label.delete(z.lb)
else
if not z.touched
bool tch = z.dir == 1 ? low <= z.top : high >= z.bot
if tch and bar_index > z.bar
z.touched := true
box.set_bgcolor(z.bx, color.new(f_zoneCol(z.kind, z.dir), 92))
// propulsion block: candle probes the block and closes away from it
if pbOnE and not z.prop and z.kind >= 1 and z.kind <= 3 and bar_index > z.bar
if z.dir == 1 and close > open and low <= z.top and low >= z.bot and close > z.top
z.prop := true
array.push(pq, math.min(open, z.top))
array.push(pq, low)
array.push(pq, 1)
if z.dir == -1 and close < open and high >= z.bot and high <= z.top and close < z.bot
z.prop := true
array.push(pq, high)
array.push(pq, math.max(open, z.bot))
array.push(pq, -1)
int n = array.size(pq)
int k = 0
while k + 2 < n
int d = int(array.get(pq, k + 2))
f_addZone(array.get(pq, k), array.get(pq, k + 1), d, 5, bar_index, d == 1 ? 'Bull Propulsion' : 'Bear Propulsion')
k += 3
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// FAIR VALUE GAPS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_fvgCol(Fvg f) =>
color c = f.dir == 1 ? fvgBullC : fvgBearC
if f.state == 1
c := f.dir == 1 ? C_CYAN : C_ORANGE
c
f_delFvg(Fvg f) =>
box.delete(f.bx)
if not na(f.ce)
line.delete(f.ce)
if not na(f.lb)
label.delete(f.lb)
f_newFvg(float top, float bot, int d, float ext) =>
while array.size(fvgs) >= fvgKeepE
Fvg f0 = array.shift(fvgs)
f_delFvg(f0)
color c = d == 1 ? fvgBullC : fvgBearC
int lb0 = bar_index - 2
box bx = box.new(left = lb0, top = top, right = bar_index, bottom = bot, extend = extend.right, border_color = color.new(c, 40), border_width = 1, bgcolor = color.new(c, 86))
line ce = na
if fvgCE
ce := line.new(lb0, (top + bot) / 2, bar_index, (top + bot) / 2, extend = extend.right, color = color.new(c, 30), style = line.style_dotted, width = 1)
label lb = label.new(lb0, d == 1 ? top : bot, 'FVG', style = d == 1 ? label.style_label_down : label.style_label_up, color = color.new(c, 100), textcolor = c, size = lsz)
Fvg f = Fvg.new(top = top, bot = bot, dir = d, bar = bar_index, ext = ext, bx = bx, ce = ce, lb = lb)
array.push(fvgs, f)
f
f_fvgDetect() =>
if fvgOn
float minSz = fvgMinE * atr
if low > high[2] and (low - high[2]) >= minSz and (not fvgQualE or (midBull[1] and close[1] > high[2]))
Fvg f = f_newFvg(low, high[2], 1, math.min(low, math.min(low[1], low[2])))
S.evFvg := 1
S.nfTop := f.top
S.nfBot := f.bot
S.nfExt := f.ext
S.nfDir := 1
// BPR with an opposite active FVG
if bprOnE
for g in fvgs
if g.dir == -1 and g.state == 0 and not g.bp and bar_index - g.bar <= 40
float oT = math.min(f.top, g.top)
float oB = math.max(f.bot, g.bot)
if oT - oB >= minSz
f.bp := true
g.bp := true
box bb = box.new(left = math.min(g.bar, bar_index) - 2, top = oT, right = bar_index + 6, bottom = oB, border_color = C_PURPLE, border_width = 2, bgcolor = color.new(C_PURPLE, 78), text = 'BPR', text_color = C_PURPLE, text_size = lsz)
array.push(miscBx, bb)
f_trimBox(miscBx, 30)
if high < low[2] and (low[2] - high) >= minSz and (not fvgQualE or (midBear[1] and close[1] < low[2]))
Fvg f = f_newFvg(low[2], high, -1, math.max(high, math.max(high[1], high[2])))
S.evFvg := -1
S.nfTop := f.top
S.nfBot := f.bot
S.nfExt := f.ext
S.nfDir := -1
if bprOnE
for g in fvgs
if g.dir == 1 and g.state == 0 and not g.bp and bar_index - g.bar <= 40
float oT = math.min(f.top, g.top)
float oB = math.max(f.bot, g.bot)
if oT - oB >= minSz
f.bp := true
g.bp := true
box bb = box.new(left = math.min(g.bar, bar_index) - 2, top = oT, right = bar_index + 6, bottom = oB, border_color = C_PURPLE, border_width = 2, bgcolor = color.new(C_PURPLE, 78), text = 'BPR', text_color = C_PURPLE, text_size = lsz)
array.push(miscBx, bb)
f_trimBox(miscBx, 30)
// volume imbalance (bodies gap, wicks overlap)
if viOnE
float vs = 0.05 * atr
if math.min(open, close) - math.max(open[1], close[1]) >= vs and low <= high[1]
box vb = box.new(left = bar_index - 1, top = math.min(open, close), right = bar_index + 8, bottom = math.max(open[1], close[1]), border_color = color.new(C_BULL, 50), bgcolor = color.new(C_BULL, 90), text = 'VI', text_color = C_BULL, text_size = lsz)
array.push(miscBx, vb)
f_trimBox(miscBx, 30)
if math.min(open[1], close[1]) - math.max(open, close) >= vs and high >= low[1]
box vb = box.new(left = bar_index - 1, top = math.min(open[1], close[1]), right = bar_index + 8, bottom = math.max(open, close), border_color = color.new(C_BEAR, 50), bgcolor = color.new(C_BEAR, 90), text = 'VI', text_color = C_BEAR, text_size = lsz)
array.push(miscBx, vb)
f_trimBox(miscBx, 30)
f_fvgUpdate() =>
int i = array.size(fvgs)
while i > 0
i -= 1
Fvg f = array.get(fvgs, i)
bool kill = false
if bar_index > f.bar
if f.state == 0
// bullish gap (dir 1) is filled from above; bearish from below
if f.dir == 1
if low < f.bot
if close < f.bot and ifvgOnE
f.state := 1
f.dir := -1
box.set_border_color(f.bx, color.new(C_ORANGE, 30))
box.set_bgcolor(f.bx, color.new(C_ORANGE, 86))
f.nm := 'IFVG'
label.set_text(f.lb, 'IFVG')
label.set_textcolor(f.lb, C_ORANGE)
label.set_style(f.lb, label.style_label_up)
label.set_y(f.lb, f.bot)
else
kill := true
else if fvgShrink and low < f.top
f.top := math.max(low, f.bot)
box.set_top(f.bx, f.top)
if not na(f.ce)
line.set_y1(f.ce, (f.top + f.bot) / 2)
line.set_y2(f.ce, (f.top + f.bot) / 2)
else
if high > f.top
if close > f.top and ifvgOnE
f.state := 1
f.dir := 1
box.set_border_color(f.bx, color.new(C_CYAN, 30))
box.set_bgcolor(f.bx, color.new(C_CYAN, 86))
f.nm := 'IFVG'
label.set_text(f.lb, 'IFVG')
label.set_textcolor(f.lb, C_CYAN)
label.set_style(f.lb, label.style_label_down)
label.set_y(f.lb, f.top)
else
kill := true
else if fvgShrink and high > f.bot
f.bot := math.min(high, f.top)
box.set_bottom(f.bx, f.bot)
if not na(f.ce)
line.set_y1(f.ce, (f.top + f.bot) / 2)
line.set_y2(f.ce, (f.top + f.bot) / 2)
else
// inverted FVG dies when price closes back through the opposite side
if f.dir == -1 and close > f.top
kill := true
if f.dir == 1 and close < f.bot
kill := true
if kill
f_delFvg(f)
array.remove(fvgs, i)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// UNICORN
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_unicorn() =>
if uniOn
for z in zones
// only a breaker made by the CURRENT MSS, and a live (not inverted) FVG formed in the same displacement leg
if z.kind == 2 and not z.uni and S.mssDir == z.dir and z.born >= S.mssBar and time - S.mssT <= ageMs
for f in fvgs
if not z.uni and not f.uni and f.state == 0 and f.dir == z.dir and f.bar >= S.mssBar - 3 and f.bar <= S.mssBar + uniWin
float oT = math.min(z.top, f.top)
float oB = math.max(z.bot, f.bot)
if oT > oB
z.uni := true
f.uni := true
color c = f.dir == 1 ? C_CYAN : C_ORANGE
box ub = box.new(left = math.min(z.bar, f.bar), top = oT, right = bar_index + 10, bottom = oB, border_color = color.white, border_width = 2, bgcolor = color.new(c, 70), text = 'Unicorn', text_color = color.white, text_size = lsz)
array.push(miscBx, ub)
f_trimBox(miscBx, 30)
S.evUni := f.dir
S.uniTop := oT
S.uniBot := oB
S.uniDir := f.dir
S.uniBar := bar_index
S.uniDone := false
S.uniFar := f.dir == 1 ? z.bot : z.top
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// STRUCTURE: swings, EQH/EQL, rejection blocks, dealing range, MSS / BOS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_drawDealing() =>
while array.size(pdBoxes) > 0
box.delete(array.shift(pdBoxes))
while array.size(pdLines) > 0
line.delete(array.shift(pdLines))
while array.size(pdLbls) > 0
label.delete(array.shift(pdLbls))
array.clear(pdP)
if not na(S.swHi) and not na(S.swLo) and S.swHi > S.swLo
float r = S.swHi - S.swLo
float eq = S.swLo + r / 2
S.eq := eq
S.rngTop := S.swHi
S.rngBot := S.swLo
int x0 = f_cl(math.min(S.swHiBar, S.swLoBar))
if pdZoneOnE
array.push(pdBoxes, box.new(left = x0, top = S.swHi, right = bar_index, bottom = eq, extend = extend.right, border_color = color.new(C_BEAR, 100), bgcolor = color.new(C_BEAR, 94)))
array.push(pdBoxes, box.new(left = x0, top = eq, right = bar_index, bottom = S.swLo, extend = extend.right, border_color = color.new(C_BULL, 100), bgcolor = color.new(C_BULL, 94)))
array.push(pdLines, line.new(x0, eq, bar_index, eq, extend = extend.right, color = color.new(C_GRAY, 20), style = line.style_dashed, width = 1))
array.push(pdLbls, label.new(bar_index + 1, S.swHi, 'Premium', style = label.style_label_left, color = color.new(C_BEAR, 100), textcolor = C_BEAR, size = lsz))
array.push(pdP, S.swHi)
array.push(pdLbls, label.new(bar_index + 1, eq, 'EQ ' + f_px(eq), style = label.style_label_left, color = color.new(C_GRAY, 100), textcolor = C_GRAY, size = lsz))
array.push(pdP, eq)
array.push(pdLbls, label.new(bar_index + 1, S.swLo, 'Discount', style = label.style_label_left, color = color.new(C_BULL, 100), textcolor = C_BULL, size = lsz))
array.push(pdP, S.swLo)
// OTE of the most recent leg
S.oteFired := false
if S.swLoBar < S.swHiBar
S.oteDir := 1
S.oteTop := S.swHi - oteLo * r
S.oteBot := S.swHi - oteHi * r
S.oteMid := S.swHi - 0.705 * r
S.oteSl := S.swLo
S.oteTp := S.swHi + 0.27 * r
else
S.oteDir := -1
S.oteBot := S.swLo + oteLo * r
S.oteTop := S.swLo + oteHi * r
S.oteMid := S.swLo + 0.705 * r
S.oteSl := S.swHi
S.oteTp := S.swLo - 0.27 * r
if oteOn
color oc = S.oteDir == 1 ? C_BULL : C_BEAR
array.push(pdBoxes, box.new(left = f_cl(math.max(S.swHiBar, S.swLoBar)), top = S.oteTop, right = bar_index, bottom = S.oteBot, extend = extend.right, border_color = color.new(oc, 40), border_width = 1, bgcolor = color.new(oc, 82)))
array.push(pdLines, line.new(f_cl(math.max(S.swHiBar, S.swLoBar)), S.oteMid, bar_index, S.oteMid, extend = extend.right, color = color.new(oc, 20), style = line.style_dotted, width = 1))
array.push(pdLbls, label.new(bar_index + 1, S.oteMid, 'OTE 70.5%', style = label.style_label_left, color = color.new(oc, 100), textcolor = oc, size = lsz))
array.push(pdP, S.oteMid)
f_pivots() =>
float ph = pivH
float pl = pivL
bool changed = false
if not na(ph)
S.pSwHi := S.swHi
S.swHi := ph
S.swHiBar := bar_index - swLen
S.swHiX := false
changed := true
// EQH
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)
// rejection block (upper wick of the swing-high candle)
if rbOnE
float wk = high[swLen] - math.max(open[swLen], close[swLen])
float cr = high[swLen] - low[swLen]
if cr > 0 and wk >= rbPct * cr and wk >= rbATR * atr
f_addZone(high[swLen], math.max(open[swLen], close[swLen]), -1, 4, bar_index - swLen, 'Bear Rejection')
if not na(pl)
S.pSwLo := S.swLo
S.swLo := pl
S.swLoBar := bar_index - swLen
S.swLoX := false
changed := true
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)
if rbOnE
float wk = math.min(open[swLen], close[swLen]) - low[swLen]
float cr = high[swLen] - low[swLen]
if cr > 0 and wk >= rbPct * cr and wk >= rbATR * atr
f_addZone(math.min(open[swLen], close[swLen]), low[swLen], 1, 4, bar_index - swLen, 'Bull Rejection')
if changed
f_drawDealing()
f_structLine(float px, int fromBar, string txt, color c) =>
array.push(stLines, line.new(f_cl(fromBar), px, bar_index, px, color = c, style = line.style_dashed, width = 1))
array.push(stLbls, label.new(math.round((fromBar + bar_index) / 2), px, txt, style = label.style_none, textcolor = c, size = lsz))
f_trimLine(stLines, structKeepE)
f_trimLbl(stLbls, structKeepE)
f_breaks() =>
// bullish break of the last swing high
if not na(S.swHi) and not S.swHiX and close > S.swHi
S.swHiX := true
S.evBrk := 1
bool dOk = bar_index - S.dispBullBar <= 3
bool isMss = S.trend == -1 and (dOk or not mssNeedD)
string tx = isMss ? (dOk ? 'MSS ⚡' : 'MSS') : (S.trend == -1 ? 'CHoCH' : 'BOS')
if (isMss and showMss) or (not isMss and showBosE)
f_structLine(S.swHi, S.swHiBar, tx, C_BULL)
if isMss
S.mssDir := 1
S.mssBar := bar_index
S.mssT := time
S.evMss := 1
bool swept = not na(S.pSwLo) and not na(S.swLo) and S.swLo < S.pSwLo
if swept
f_zoneAt(S.swHiBar, false, 1, 2, 'Bull Breaker')
else
f_zoneAt(S.swLoBar, true, 1, 3, 'Bull Mitigation')
else
if S.trend == 1 or S.trend == 0
f_zoneAt(S.swLoBar, true, 1, 1, 'Bull OB')
S.trend := 1
// bearish break of the last swing low
if not na(S.swLo) and not S.swLoX and close < S.swLo
S.swLoX := true
S.evBrk := -1
bool dOk = bar_index - S.dispBearBar <= 3
bool isMss = S.trend == 1 and (dOk or not mssNeedD)
string tx = isMss ? (dOk ? 'MSS ⚡' : 'MSS') : (S.trend == 1 ? 'CHoCH' : 'BOS')
if (isMss and showMss) or (not isMss and showBosE)
f_structLine(S.swLo, S.swLoBar, tx, C_BEAR)
if isMss
S.mssDir := -1
S.mssBar := bar_index
S.mssT := time
S.evMss := -1
bool swept = not na(S.pSwHi) and not na(S.swHi) and S.swHi > S.pSwHi
if swept
f_zoneAt(S.swLoBar, true, -1, 2, 'Bear Breaker')
else
f_zoneAt(S.swHiBar, false, -1, 3, 'Bear Mitigation')
else
if S.trend == -1 or S.trend == 0
f_zoneAt(S.swHiBar, false, -1, 1, 'Bear OB')
S.trend := -1
f_gaps() =>
if vacOnE and bar_index > 0
float g = open - close[1]
if math.abs(g) >= gapATR * atr
string nm = wkndGap ? 'NWOG' : (ndogBar ? 'NDOG' : 'Vacuum')
int d = g > 0 ? 1 : -1
Zone z = f_addZone(math.max(open, close[1]), math.min(open, close[1]), d, 6, bar_index - 1, nm)
if not na(z)
z.nm := nm
label.set_text(z.lb, nm)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// MODELS & SETUPS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
f_score(int d, bool kz) =>
int s = 0
if kz
s += 1
if S.raidDir == d and time - S.raidT <= ageMs
s += 1
if S.mssDir == d and time - S.mssT <= ageMs
s += 1
if (d == 1 and bar_index - S.dispBullBar <= 20) or (d == -1 and bar_index - S.dispBearBar <= 20)
s += 1
if htfB == d
s += 1
if not na(S.eq)
if (d == 1 and close < S.eq) or (d == -1 and close > S.eq)
s += 1
s
f_mname(int m) => m == 1 ? 'ICT 2022' : m == 2 ? 'Silver Bullet' : m == 3 ? 'Unicorn' : 'OTE'
f_newSetup(int model, int d, float entry, float sl, bool inKz, bool filled) =>
bool made = false
float risk = math.abs(entry - sl)
bool dirOk = d == 1 ? sl < entry : sl > entry
int sc = f_score(d, inKz)
bool htfOk = not htfReq or htfB == d
if sigOn and risk > 0 and dirOk and htfOk and sc >= minScore
float tp = f_target(d, entry, risk * minRR)
if na(tp)
tp := entry + d * risk * fbRR
float rr = math.abs(tp - entry) / risk
if rr >= minRR - 0.0001
// only FINISHED setups may be dropped (their result is already counted); pending / open trades are never removed
int oi = 0
while array.size(setups) >= 24 and oi < array.size(setups)
Setup o = array.get(setups, oi)
if o.state == 2
array.remove(setups, oi)
if showSetups
line.delete(o.lEn)
line.delete(o.lSl)
line.delete(o.lTp)
label.delete(o.lb)
else
oi += 1
if array.size(setups) < 40 // 40 open / pending trades is the real limit
color cc = d == 1 ? C_BULL : C_BEAR
line le = na
line ls = na
line lt = na
label lb = na
if showSetups
le := line.new(bar_index, entry, bar_index + 1, entry, color = C_BLUE, style = line.style_solid, width = 2)
ls := line.new(bar_index, sl, bar_index + 1, sl, color = C_BEAR, style = line.style_solid, width = 1)
lt := line.new(bar_index, tp, bar_index + 1, tp, color = C_BULL, style = line.style_solid, width = 1)
lb := label.new(bar_index + 1, entry, f_mname(model) + (d == 1 ? ' ▲ BUY ' : ' ▼ SELL ') + '★' + str.tostring(sc) + '/6 RR ' + str.tostring(rr, '#.#'), style = label.style_label_left, color = color.new(cc, 20), textcolor = color.white, size = lsz)
Setup t = Setup.new(model = model, dir = d, entry = entry, sl = sl, tp = tp, born = bar_index, state = filled ? 1 : 0, score = sc, rr = rr, cost = costATR * atr, lEn = le, lSl = ls, lTp = lt, lb = lb)
if filled
t.fillBar := bar_index
array.push(setups, t)
S.evSig := d
S.evModel := f_mname(model)
made := true
else
array.set(capRej, 0, array.get(capRej, 0) + 1)
made
f_finish(Setup t, bool win) =>
t.state := 2
if win
array.set(wins, t.model, array.get(wins, t.model) + 1)
else
array.set(losses, t.model, array.get(losses, t.model) + 1)
if showSetups
line.set_x2(t.lEn, bar_index)
line.set_x2(t.lSl, bar_index)
line.set_x2(t.lTp, bar_index)
line.set_color(t.lEn, color.new(C_GRAY, 20))
line.set_color(win ? t.lSl : t.lTp, color.new(C_GRAY, 70))
label.set_text(t.lb, f_mname(t.model) + (t.dir == 1 ? ' ▲ ' : ' ▼ ') + (win ? 'TP ✔' : 'SL ✖'))
label.set_color(t.lb, color.new(win ? C_BULL : C_BEAR, 20))
label.set_x(t.lb, bar_index)
f_updSetups() =>
int i = array.size(setups)
while i > 0
i -= 1
Setup t = array.get(setups, i)
if t.state == 0
bool cancel = bar_index - t.born > expBars
if t.dir == 1 and (close < t.sl or high >= t.tp)
cancel := true
if t.dir == -1 and (close > t.sl or low <= t.tp)
cancel := true
if cancel
array.remove(setups, i)
if showSetups
line.delete(t.lEn)
line.delete(t.lSl)
line.delete(t.lTp)
label.delete(t.lb)
else
if bar_index > t.born and ((t.dir == 1 and low <= t.entry) or (t.dir == -1 and high >= t.entry))
t.state := 1
t.fillBar := bar_index
bool slNow = t.dir == 1 ? low <= t.sl + t.cost : high >= t.sl - t.cost
if slNow
f_finish(t, false)
else if showSetups
label.set_text(t.lb, f_mname(t.model) + (t.dir == 1 ? ' ▲ filled ' : ' ▼ filled ') + '★' + str.tostring(t.score))
if t.state == 0 and showSetups
line.set_x2(t.lEn, bar_index)
line.set_x2(t.lSl, bar_index)
line.set_x2(t.lTp, bar_index)
label.set_x(t.lb, bar_index)
else if t.state == 1 and bar_index > t.fillBar
bool slHit = t.dir == 1 ? low <= t.sl + t.cost : high >= t.sl - t.cost // pessimistic: the stop is checked first
bool tpHit = t.dir == 1 ? high >= t.tp + t.cost : low <= t.tp - t.cost
if slHit
f_finish(t, false)
else if tpHit
f_finish(t, true)
else if showSetups
line.set_x2(t.lEn, bar_index)
line.set_x2(t.lSl, bar_index)
line.set_x2(t.lTp, bar_index)
label.set_x(t.lb, bar_index)
f_entryPx(float top, float bot, int d) =>
entryMode == 'FVG edge' ? (d == 1 ? top : bot) : (top + bot) / 2
f_models() =>
float buf = slBuf * atr
// ── ICT 2022: raid → structure break (MSS/BOS) → FVG ───────────────────────────────────────────
if m22On
if S.evMss != 0 and S.raidDir == S.evMss and bar_index - S.raidBar <= mssWin and time - S.raidT <= ageMs and (not kzOnly or kzAny)
S.w22Dir := S.evMss
S.w22Bar := bar_index
S.w22Px := S.raidPx
if S.w22Dir != 0
if bar_index - S.w22Bar > 3
S.w22Dir := 0
else
int n = array.size(fvgs)
bool done = false
int i = n
while i > 0 and not done
i -= 1
Fvg f = array.get(fvgs, i)
if f.dir == S.w22Dir and f.state == 0 and f.bar >= S.w22Bar
float en = f_entryPx(f.top, f.bot, f.dir)
float sl = S.w22Dir == 1 ? S.w22Px - buf : S.w22Px + buf
// the chain ends only when a setup was really made (or after 3 bars); a rejected FVG lets the next one be tried
if f_newSetup(1, S.w22Dir, en, sl, kzAny, false)
S.w22Dir := 0
done := true
// ── Silver Bullet ──────────────────────────────────────────────────────
if not sbAny
S.sbFired := false
if mSbOn and sbAny and not S.sbFired and S.evFvg != 0
int d = S.nfDir
bool aligned = (S.mssDir == d and time - S.mssT <= ageMs) or (S.raidDir == d and time - S.raidT <= ageMs) or not sbBias
if aligned
float en = f_entryPx(S.nfTop, S.nfBot, d)
float sl = d == 1 ? S.nfExt - buf : S.nfExt + buf
if f_newSetup(2, d, en, sl, true, false)
S.sbFired := true
// ── Unicorn ────────────────────────────────────────────────────────────
// by design ONE pending Unicorn at a time: a newer one replaces an older one that is still waiting; the saved zone is not re-validated during the 3 retry bars
// a detected Unicorn zone is retried for 3 bars if the setup was rejected (score / RR / HTF / capacity); it makes at most ONE setup
if mUniOn and S.uniDir != 0 and not S.uniDone and bar_index - S.uniBar <= 3 and (not kzOnly or kzAny)
int d = S.uniDir
float en = entryMode == 'FVG edge' ? (d == 1 ? S.uniTop : S.uniBot) : (S.uniTop + S.uniBot) / 2
float sl = d == 1 ? S.uniFar - buf : S.uniFar + buf
if f_newSetup(3, d, en, sl, kzAny, false)
S.uniDone := true
// ── OTE ───────────────────────────────────────────────────────────────
if mOteOn and S.oteDir != 0 and not S.oteFired and (not kzOnly or kzAny)
bool origin = oteSL == 'Swing origin (100%)'
if S.oteDir == 1 and low <= S.oteTop and close > S.oteBot and close > S.oteMid and close > open
float slv = origin ? S.oteSl : math.min(low, S.oteBot)
if f_newSetup(4, 1, close, slv - buf, kzAny, false)
S.oteFired := true
if S.oteDir == -1 and high >= S.oteBot and close < S.oteTop and close < S.oteMid and close < open
float slv = origin ? S.oteSl : math.max(high, S.oteTop)
if f_newSetup(4, -1, close, slv + buf, kzAny, false)
S.oteFired := true
f_updSetups()
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// MAIN (confirmed bars only → no repaint of signals)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
S.evRaid := 0
S.evRun := 0
S.evMss := 0
S.evBrk := 0
S.evFvg := 0
S.evUni := 0
S.evSig := 0
S.evModel := ''
if barstate.isconfirmed
if dispBull
S.dispBullBar := bar_index
if dispMarkE
f_markD(bar_index, (open + close) / 2, '⚡', C_BULL, label.style_label_center)
if dispBear
S.dispBearBar := bar_index
if dispMarkE
f_markD(bar_index, (open + close) / 2, '⚡', C_BEAR, label.style_label_center)
if intra
bool e1 = sAsia.upd(inAsia)
bool e2 = sLon.upd(inLon)
bool e3 = sNya.upd(inNya)
bool e4 = sLc.upd(inLc)
bool e5 = sNyp.upd(inNyp)
if e1
f_sessEnd(sAsia)
if e2
f_sessEnd(sLon)
if e3
f_sessEnd(sNya)
if e4
f_sessEnd(sLc)
if e5
f_sessEnd(sNyp)
f_periods()
f_fvgDetect()
f_fvgUpdate()
f_pivots()
f_chkLiq()
f_breaks()
f_updZones()
f_gaps()
f_unicorn()
f_models()
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;
// blocks / FVGs far from the current price keep their box but lose the label, so the right column stays readable
f_spread() =>
array<label> ls = array.new<label>()
array<float> ps = array.new<float>()
int xe = bar_index + lblPad - 1
float near = nz(rng150, 0) * lblNearE
for q in liqs
if q.swept
label.set_x(q.lb, q.sweptBar)
else
line.set_x2(q.ln, xe)
if math.abs(q.price - close) <= near * 1.5
label.set_text(q.lb, q.name + ' ' + f_px(q.price))
array.push(ls, q.lb)
array.push(ps, q.price)
else
label.set_text(q.lb, '')
for z in zones
if not na(z.lb)
float mid = (z.top + z.bot) / 2
if math.abs(mid - close) <= near
label.set_text(z.lb, z.nm)
label.set_style(z.lb, label.style_label_left)
array.push(ls, z.lb)
array.push(ps, mid)
else
label.set_text(z.lb, '')
for f in fvgs
if not na(f.lb)
float mid = (f.top + f.bot) / 2
if math.abs(mid - close) <= near
label.set_text(f.lb, f.nm)
label.set_style(f.lb, label.style_label_left)
array.push(ls, f.lb)
array.push(ps, mid)
else
label.set_text(f.lb, '')
int np = array.size(pdLbls)
if np > 0
for k = 0 to np - 1
array.push(ls, array.get(pdLbls, k))
array.push(ps, array.get(pdP, k))
if showSetups
for t in setups
if t.state < 2 and not na(t.lb)
array.push(ls, t.lb)
array.push(ps, t.entry)
f_place(ls, ps)
if barstate.islast
f_spread()
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// PANEL (active killzone, HTF bias, last raid / MSS, dealing range, win-rate)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var table pnl = na
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 = psz, text_halign = text.align_left, bgcolor = color.new(BRAND_DARK, 8))
table.cell(t, 1, r, v, text_color = vc, text_size = psz, 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, 12, border_width = 1, border_color = color.new(color.gray, 70), frame_width = 1, frame_color = color.new(BRAND_GREEN, 20))
string actNm = 'Outside killzones'
color actCol = color.gray
for s in allS
if s.on and s.active
actNm := s.name
actCol := s.col
if sbAny
actNm += ' + Silver Bullet'
table.cell(pnl, 0, 0, 'Crypex ICT Models', text_color = BRAND_GREEN, text_size = psz, 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 = psz, text_halign = text.align_left, bgcolor = color.new(BRAND_DARK, 8))
f_row(pnl, 1, 'Now', actNm, actCol)
f_row(pnl, 2, 'HTF bias ' + biasTF + '/' + str.tostring(biasLen), f_dirTxt(htfB), f_dirCol(htfB))
string rdTxt = S.raidDir == 0 ? '—' : S.raidName + (S.raidRej ? ' (raid)' : ' (run)') + ' · ' + str.tostring(bar_index - S.raidBar) + ' bars ago'
f_row(pnl, 3, 'Last liquidity', rdTxt, f_dirCol(S.raidDir))
string msTxt = S.mssDir == 0 ? '—' : f_dirTxt(S.mssDir) + ' · ' + str.tostring(bar_index - S.mssBar) + ' bars ago'
f_row(pnl, 4, 'Last MSS', msTxt, f_dirCol(S.mssDir))
string pdTxt = '—'
color pdCol = color.gray
if not na(S.rngTop) and S.rngTop > S.rngBot
float pos = (close - S.rngBot) / (S.rngTop - S.rngBot) * 100
if pos > 100
pdTxt := 'Above range (new highs)'
pdCol := C_BEAR
else if pos < 0
pdTxt := 'Below range (new lows)'
pdCol := C_BULL
else
pdTxt := (pos >= 50 ? 'Premium ' : 'Discount ') + str.tostring(pos, '#') + '%'
pdCol := pos >= 50 ? C_BEAR : C_BULL
f_row(pnl, 5, 'Dealing range', pdTxt, pdCol)
if showStatsE
for m = 1 to 4
int w = array.get(wins, m)
int l = array.get(losses, m)
int tot = w + l
string wr = tot > 0 ? str.tostring(w) + 'W / ' + str.tostring(l) + 'L (' + str.tostring(w * 100.0 / tot, '#') + '%)' : 'no closed trades'
f_row(pnl, 5 + m, f_mname(m), wr, tot > 0 and w >= l ? C_BULL : (tot > 0 ? C_BEAR : color.gray))
f_row(pnl, 10, 'Min score / RR', str.tostring(minScore) + '/6 · ' + str.tostring(minRR, '#.#') + 'R', color.gray)
if sigOn and array.get(capRej, 0) > 0
f_row(pnl, 11, 'Rejected: 40 open', str.tostring(array.get(capRej, 0)) + ' attempts', C_ORANGE)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ALERTS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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.evMss == 1, 'Bullish MSS', 'Crypex ICT: bullish market structure shift')
alertcondition(S.evMss == -1, 'Bearish MSS', 'Crypex ICT: bearish market structure shift')
alertcondition(S.evFvg == 1, 'Bullish FVG', 'Crypex ICT: new bullish FVG')
alertcondition(S.evFvg == -1, 'Bearish FVG', 'Crypex ICT: new bearish FVG')
alertcondition(S.evUni != 0, 'Unicorn zone', 'Crypex ICT: unicorn zone formed')
alertcondition(S.evSig == 1, 'BUY setup', 'Crypex ICT: BUY setup')
alertcondition(S.evSig == -1, 'SELL setup', 'Crypex ICT: SELL setup')
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 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 Models (MT5) |
//| Market structure (MSS / BOS), Fair Value Gaps (CE, IFVG, VI, |
//| BPR), Order Block, Breaker, Mitigation, Unicorn, Premium / |
//| Discount, OTE, Raid / Run, panel. Analysis only by default; |
//| ICT 2022 / Silver Bullet / Unicorn / OTE setups are optional |
//| © 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_CORNER { TM_C_AUTO, TM_C_TOPRIGHT, TM_C_TOPLEFT, TM_C_BOTTOMRIGHT, TM_C_BOTTOMLEFT };
enum ENUM_MD_PRESET { MD_P_CLEAN, MD_P_STANDARD, MD_P_FULL, MD_P_CUSTOM };
enum ENUM_MD_ZMODE { MD_Z_FULL, MD_Z_BODY, MD_Z_OPEN };
enum ENUM_MD_ENTRY { MD_E_CE, MD_E_EDGE };
enum ENUM_MD_OTESL { MD_O_BEYOND, MD_O_ORIGIN };
input group "General & time zones"
input ENUM_MD_PRESET InpPreset = MD_P_CLEAN; // Preset (Clean = only the most valuable items, Standard, Full, Custom = my own settings below)
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 InpFont = 9; // Label font size
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 double InpLblNear = 0.30; // Show zone labels within (x recent range from price)
input int InpRightMargin = 15; // Chart right margin (% of the chart, 0 = leave the chart alone)
input group "Killzones (used by the models, New York time)"
input bool InpAsiaOn = true; // Asian range
input string InpAsiaR = "2000-0000"; // Asian range
input bool InpLonOn = true; // London killzone
input string InpLonR = "0200-0500"; // London
input bool InpNyaOn = true; // New York AM killzone
input string InpNyaR = "0700-1000"; // NY AM
input bool InpLcOn = true; // London Close killzone
input string InpLcR = "1000-1200"; // London Close
input bool InpNypOn = true; // New York PM killzone
input string InpNypR = "1330-1600"; // NY PM
input bool InpSessLvlOn = true; // Turn finished session highs/lows into liquidity levels (Custom preset)
input bool InpSbOn = true; // Silver Bullet windows
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 group "Liquidity levels (raids & targets)"
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 (Custom preset)
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 = 60; // Keep swept levels for (bars) (Custom preset)
input int InpMaxLiq = 40; // Max liquidity levels on chart (Custom preset)
input group "Market structure & displacement"
input int InpSwLen = 5; // Swing length
input double InpDispAtr = 1.2; // Displacement: body >= x ATR
input double InpDispBody = 0.6; // Displacement: body / range >=
input bool InpDispMark = true; // Mark displacement candles (Custom preset)
input bool InpMssNeedD = false; // MSS needs displacement (else a plain break = BOS / CHoCH)
input bool InpShowBos = true; // Show BOS (Custom preset)
input bool InpShowMss = true; // Show MSS
input int InpStructKeep = 14; // Structure lines to keep (Custom preset)
input group "Premium / Discount & OTE"
input bool InpPdZoneOn = true; // Premium / Discount / Equilibrium zone (Custom preset)
input bool InpOteOn = true; // OTE zone
input double InpOteLo = 0.62; // OTE from
input double InpOteHi = 0.79; // OTE to
input group "Fair Value Gaps"
input bool InpFvgOn = true; // Fair Value Gaps
input double InpFvgMin = 0.10; // Min FVG size (x ATR) (Custom preset)
input int InpFvgKeep = 16; // Max active FVGs (Custom preset)
input bool InpFvgQual = false; // Quality filter: middle candle must be a strong move (Custom preset)
input double InpFvgMid = 0.5; // Quality filter: middle candle body >= x ATR
input bool InpFvgCE = true; // Show Consequent Encroachment (50 %)
input bool InpFvgShrink = true; // Shrink FVG when partly filled
input bool InpIfvgOn = true; // Inversion FVG (IFVG) (Custom preset)
input bool InpViOn = true; // Volume Imbalance (VI) (Custom preset)
input bool InpBprOn = true; // Balanced Price Range (BPR) (Custom preset)
input color InpFvgBullC = C'8,153,129';
input color InpFvgBearC = C'242,54,69';
input group "Blocks, Breaker, Mitigation, Rejection, Propulsion, Gaps, Unicorn"
input bool InpObOn = true; // Order Block (BOS continuation)
input bool InpBrkOn = true; // Breaker Block (approx.)
input bool InpMitOn = true; // Mitigation Block (approx.) (Custom preset)
input bool InpRbOn = true; // Rejection Block (Custom preset)
input bool InpPbOn = true; // Propulsion Block (Custom preset)
input bool InpVacOn = true; // Vacuum Block / NWOG / NDOG (Custom preset)
input bool InpUniOn = true; // Unicorn (Breaker + FVG overlap)
input ENUM_MD_ZMODE InpZoneMode = MD_Z_FULL; // Block zone from
input bool InpZoneMt = true; // Show 50 % mean threshold of blocks (Custom preset)
input int InpZoneMax = 24; // Max blocks on chart (Custom preset)
input double InpRbPct = 0.5; // Rejection: wick >= % of candle
input double InpRbAtr = 0.5; // Rejection: wick >= x ATR
input double InpGapAtr = 0.3; // Gap: min size (x ATR)
input color InpObBullC = C'8,153,129';
input color InpObBearC = C'242,54,69';
input group "Models & signals"
input bool InpSigOn = false; // Trade setups: Entry / SL / TP signals (OFF = analysis only)
input bool InpM22On = true; // Model: ICT 2022 (raid -> MSS -> FVG)
input bool InpMSbOn = true; // Model: Silver Bullet (FVG inside window)
input bool InpMUniOn = true; // Model: Unicorn
input bool InpMOteOn = true; // Model: OTE
input int InpUniWin = 6; // Unicorn: FVG must form within N bars after the MSS
input bool InpKzOnly = true; // Signals only inside killzones / Silver Bullet
input int InpMssWin = 30; // ICT 2022: max bars from raid to MSS
input ENUM_MD_ENTRY InpEntryMode = MD_E_CE; // Entry on FVG
input double InpSlBuf = 0.10; // Stop buffer (x ATR)
input double InpMinRR = 1.5; // Min reward : risk
input double InpFbRR = 2.0; // Fallback target (R) if no liquidity target
input int InpExpBars = 40; // Cancel unfilled setup after (bars)
input int InpMinScore = 3; // Min confluence score (0-6)
input bool InpSbBias = true; // Silver Bullet needs raid or MSS in the same direction
input ENUM_TIMEFRAMES InpBiasTF = PERIOD_H4; // HTF bias timeframe
input int InpBiasLen = 21; // HTF bias EMA length
input ENUM_MD_OTESL InpOteSL = MD_O_BEYOND; // OTE model stop
input int InpAgeMin = 240; // Event validity for the score (minutes)
input bool InpHtfReq = false; // Require HTF bias in the trade direction
input double InpCostAtr = 0.03; // Trading cost (x ATR) in the win-rate stats
input bool InpShowSetups = true; // Draw entry / SL / TP
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 InpShowStats = false; // Win / loss stats of the models in the panel (Custom preset, needs trade setups)
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_MODELS_";
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[10];
bool g_alLast[10];
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;
const color C_LIME = C'139,195,74';
bool EDispMark = false, EShowBos = false, EIfvg = false, EVi = false, EBpr = false, EMit = false, ERb = false, EPb = false, EVac = false;
bool EZoneMt = false, EPdZone = false, ESessLvl = false, EPm = false, EQual = true, EStats = false;
int EFvgKeep = 5, EZoneMax = 6, EMaxLiq = 12, EKeepSwept = 20, EStructKeep = 6;
double ELblNear = 0.25, EFvgMin = 0.3;
datetime g_raidT = 0;
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+
//| 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 : EMaxLiq + 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 >= EMaxLiq) // 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_raidT = g_t[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 > EKeepSwept) RemoveLq(k);
}
}
if(g_raidRej && i - g_raidBar <= InpMssWin)
{
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]);
}
}
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;
}
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) && EPm)
{
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; }
}
}
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;
}
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;
}
const int PN_ROWS = 8, 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);
}
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);
}
}
void Notify(const string msg)
{
string full = "Crypex ICT Models " + _Symbol + ": " + msg;
if(InpAlertPopup) Alert(full);
if(InpAlertPush) SendNotification(full);
if(InpAlertMail) SendMail("Crypex ICT Models", full);
}
//+------------------------------------------------------------------+
//| Crypex ICT Models: settings resolved from the Preset |
//+------------------------------------------------------------------+
void ResolvePreset()
{
int p = (int)InpPreset;
if(p == MD_P_CUSTOM)
{
EDispMark = InpDispMark; EShowBos = InpShowBos; EIfvg = InpIfvgOn; EVi = InpViOn; EBpr = InpBprOn; EMit = InpMitOn; ERb = InpRbOn;
EPb = InpPbOn; EVac = InpVacOn; EZoneMt = InpZoneMt; EPdZone = InpPdZoneOn; ESessLvl = InpSessLvlOn; EPm = InpPmOn;
EQual = InpFvgQual; EStats = InpSigOn && InpShowStats;
EFvgKeep = InpFvgKeep; EZoneMax = InpZoneMax; EMaxLiq = InpMaxLiq; EKeepSwept = InpKeepSwept; EStructKeep = InpStructKeep;
ELblNear = InpLblNear; EFvgMin = InpFvgMin;
return;
}
bool cl = (p == MD_P_CLEAN), fu = (p == MD_P_FULL);
EDispMark = !cl; EShowBos = !cl; EIfvg = !cl; EVi = !cl; EBpr = !cl; EMit = !cl; ERb = !cl; EPb = !cl; EVac = !cl;
EZoneMt = !cl; EPdZone = !cl; ESessLvl = !cl; EPm = fu; EQual = cl; EStats = InpSigOn && fu;
EFvgKeep = cl ? 5 : (fu ? 30 : 16);
EZoneMax = cl ? 6 : (fu ? 40 : 24);
EMaxLiq = cl ? 12 : (fu ? 60 : 40);
EKeepSwept = cl ? 20 : (fu ? 100 : 60);
EStructKeep = cl ? 6 : (fu ? 24 : 14);
ELblNear = cl ? 0.25 : (fu ? 0.6 : 0.3);
EFvgMin = cl ? 0.3 : (fu ? 0.05 : 0.1);
}
//+------------------------------------------------------------------+
//| model structures |
//+------------------------------------------------------------------+
struct TmSessM { string name; int a; int b; bool on; color col; bool active; double hi; double lo; int hiBar; int loBar;
double fHi; double fLo; int fHiBar; int fLoBar; };
struct TmFvg { double top; double bot; int dir; int bar; int state; double ext; bool uni; bool bp; string nm; };
struct TmZone { double top; double bot; int dir; int kind; string nm; int bar; int born; bool touched; bool prop; bool uni; };
struct TmSetup { int model; int dir; double entry; double sl; double tp; int born; int fillBar; int state; int score; double rr; double cost; int finBar; bool win; };
struct TmStruct { double px; int fromBar; int toBar; string txt; color col; };
struct TmMBox { int b0; int b1; double top; double bot; string txt; color col; };
struct TmDisp { int bar; int dir; };
TmSessM g_ms[5];
TmFvg g_fvg[]; int g_nFvg = 0;
TmZone g_zn[]; int g_nZn = 0;
TmSetup g_su[]; int g_nSu = 0;
TmStruct g_st[]; int g_nSt = 0;
TmMBox g_mb[]; int g_nMb = 0;
TmDisp g_dp[]; int g_nDp = 0;
int g_htf[];
int g_trend = 0, g_mssDir = 0, g_mssBar = -9999;
datetime g_mssT = 0;
bool g_swHiX = true, g_swLoX = true;
int g_dispBullBar = -9999, g_dispBearBar = -9999;
double g_eq = TM_NA, g_rngTop = TM_NA, g_rngBot = TM_NA, g_oteTop = TM_NA, g_oteBot = TM_NA, g_oteMid = TM_NA, g_oteSl = TM_NA, g_oteTp = TM_NA;
int g_oteDir = 0;
bool g_oteFired = false, g_dealOk = false;
int g_w22Dir = 0, g_w22Bar = 0;
double g_w22Px = TM_NA;
bool g_sbFired = false;
int g_evMss = 0, g_evBrk = 0, g_evFvg = 0, g_evUni = 0, g_evSig = 0;
double g_nfTop = TM_NA, g_nfBot = TM_NA, g_nfExt = TM_NA;
int g_nfDir = 0;
double g_uniTop = TM_NA, g_uniBot = TM_NA, g_uniFar = TM_NA;
int g_uniDir = 0, g_uniBar = -9999;
bool g_uniDone = true;
int g_capRej = 0;
int g_win[5], g_loss[5];
int g_sbA[3], g_sbB[3];
string Bolt() { return ShortToString(0x26A1); }
long AgeSec() { return (long)InpAgeMin * 60; }
int Cl1400(const int x) { return MathMax(x, g_n - 1 - 1400); }
//+------------------------------------------------------------------+
//| sessions (high / low tracking only; finished ones become levels) |
//+------------------------------------------------------------------+
void InitModels()
{
ResolvePreset();
string nm[5] = {"Asia", "London KZ", "NY AM KZ", "London Close KZ", "NY PM KZ"};
int dA[5] = {1200, 120, 420, 600, 810};
int dB[5] = {0, 300, 600, 720, 960};
string rg[5]; bool on[5]; color cl[5];
rg[0] = InpAsiaR; rg[1] = InpLonR; rg[2] = InpNyaR; rg[3] = InpLcR; rg[4] = InpNypR;
on[0] = InpAsiaOn; on[1] = InpLonOn; on[2] = InpNyaOn; on[3] = InpLcOn; on[4] = InpNypOn;
cl[0] = C_PURPLE; cl[1] = C_BLUE; cl[2] = C_BULL; cl[3] = C_ORANGE; cl[4] = C_PINK;
for(int k = 0; k < 5; k++)
{
g_ms[k].name = nm[k]; g_ms[k].on = on[k]; g_ms[k].col = cl[k];
ParseRange(rg[k], dA[k], dB[k], g_ms[k].a, g_ms[k].b);
}
ParseRange(InpSb1R, 180, 240, g_sbA[0], g_sbB[0]);
ParseRange(InpSb2R, 600, 660, g_sbA[1], g_sbB[1]);
ParseRange(InpSb3R, 840, 900, g_sbA[2], g_sbB[2]);
}
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, 5) || Fl(i, 6) || Fl(i, 7)); }
bool KzAny(const int i) { return (InpLonOn && Fl(i, 1)) || (InpNyaOn && Fl(i, 2)) || (InpLcOn && Fl(i, 3)) || (InpNypOn && Fl(i, 4)) || SbAny(i); }
bool SessUpd(const int k, const bool inS, const int i)
{
bool ended = false;
if(!g_ms[k].on) return false;
if(inS)
{
if(!g_ms[k].active)
{ g_ms[k].active = true; g_ms[k].hi = g_h[i]; g_ms[k].lo = g_l[i]; g_ms[k].hiBar = i; g_ms[k].loBar = i; }
else
{
if(g_h[i] > g_ms[k].hi) { g_ms[k].hi = g_h[i]; g_ms[k].hiBar = i; }
if(g_l[i] < g_ms[k].lo) { g_ms[k].lo = g_l[i]; g_ms[k].loBar = i; }
}
}
else if(g_ms[k].active)
{
g_ms[k].active = false; ended = true;
g_ms[k].fHi = g_ms[k].hi; g_ms[k].fLo = g_ms[k].lo; g_ms[k].fHiBar = g_ms[k].hiBar; g_ms[k].fLoBar = g_ms[k].loBar;
}
return ended;
}
void SessEnd(const int k, const int i)
{
if(!ESessLvl) return;
AddLiq(g_ms[k].fHi, g_ms[k].name + " H", 1, 1, g_ms[k].fHiBar, i);
AddLiq(g_ms[k].fLo, g_ms[k].name + " L", -1, 1, g_ms[k].fLoBar, i);
}
//+------------------------------------------------------------------+
//| series: ATR, New York clock, session flags, HTF bias |
//+------------------------------------------------------------------+
void BuildHtf()
{
for(int i = 0; i < g_n; i++) g_htf[i] = 0;
string sym = _Symbol;
if(PeriodSeconds(InpBiasTF) <= PeriodSeconds(_Period)) return; // the bias must come from a higher timeframe
int kOld = iBarShift(sym, InpBiasTF, g_t[0], false);
if(kOld < 0) return;
int kMax = kOld + 120;
while(kMax > 0 && iClose(sym, InpBiasTF, kMax) <= 0.0) kMax--;
if(kMax < 2) return;
double ema[];
ArrayResize(ema, kMax + 1);
double a = 2.0 / (InpBiasLen + 1.0);
for(int k = kMax; k >= 0; k--)
{
double c = iClose(sym, InpBiasTF, k);
ema[k] = (k == kMax) ? c : a * c + (1.0 - a) * ema[k + 1];
}
for(int i = 0; i < g_n; i++) // previous COMPLETED higher-timeframe bar only: cannot repaint
{
int k = iBarShift(sym, InpBiasTF, g_t[i], false);
if(k < 0) continue;
int p = k + 1;
if(p > kMax) continue;
g_htf[i] = (iClose(sym, InpBiasTF, p) > ema[p]) ? 1 : -1;
}
}
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); ArrayResize(g_htf, g_n);
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;
}
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;
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 < 5; k++) if(InSess(m, g_ms[k].a, g_ms[k].b)) f |= (1 << k);
for(int k = 0; k < 3; k++) if(InSess(m, g_sbA[k], g_sbB[k])) f |= (1 << (5 + k));
}
g_fl[i] = f;
}
BuildHtf();
}
//+------------------------------------------------------------------+
//| state |
//+------------------------------------------------------------------+
void ResetState()
{
g_nMk = 0; g_mkId = 0; g_nLq = 0; g_cnt = 0;
ArrayResize(g_mk, 0); ArrayResize(g_lq, 0);
g_nFvg = 0; g_nZn = 0; g_nSu = 0; g_nSt = 0; g_nMb = 0; g_nDp = 0;
ArrayResize(g_fvg, 0); ArrayResize(g_zn, 0); ArrayResize(g_su, 0); ArrayResize(g_st, 0); ArrayResize(g_mb, 0); ArrayResize(g_dp, 0);
for(int k = 0; k < 5; k++)
{ g_ms[k].active = false; g_ms[k].fHi = TM_NA; g_ms[k].fLo = TM_NA; g_ms[k].hi = TM_NA; g_ms[k].lo = TM_NA; g_ms[k].hiBar = 0; g_ms[k].loBar = 0; g_ms[k].fHiBar = 0; g_ms[k].fLoBar = 0; }
g_swHi = TM_NA; g_pSwHi = TM_NA; g_swLo = TM_NA; g_pSwLo = TM_NA; g_swHiBar = 0; g_swLoBar = 0;
g_swHiX = true; g_swLoX = true; g_trend = 0; g_mssDir = 0; g_mssBar = -9999; g_mssT = 0;
g_dispBullBar = -9999; g_dispBearBar = -9999;
g_raidDir = 0; g_raidBar = -9999; g_raidT = 0; 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_po3Bias = 0; g_po3DnBar = -9999; g_po3UpBar = -9999; g_po3DnExt = TM_NA; g_po3UpExt = TM_NA; g_moPx = TM_NA;
g_eq = TM_NA; g_rngTop = TM_NA; g_rngBot = TM_NA; g_oteDir = 0; g_oteTop = TM_NA; g_oteBot = TM_NA; g_oteMid = TM_NA; g_oteSl = TM_NA; g_oteTp = TM_NA;
g_oteFired = false; g_dealOk = false;
g_w22Dir = 0; g_w22Bar = 0; g_w22Px = TM_NA; g_sbFired = false;
g_nfTop = TM_NA; g_nfBot = TM_NA; g_nfExt = TM_NA; g_nfDir = 0;
g_uniTop = TM_NA; g_uniBot = TM_NA; g_uniFar = TM_NA; g_uniDir = 0; g_uniBar = -9999; g_uniDone = true;
g_capRej = 0;
for(int k = 0; k < 5; k++) { g_win[k] = 0; g_loss[k] = 0; }
g_evRaid = 0; g_evRun = 0; g_evMss = 0; g_evBrk = 0; g_evFvg = 0; g_evUni = 0; g_evSig = 0;
}
//+------------------------------------------------------------------+
//| displacement |
//+------------------------------------------------------------------+
bool DispBull(const int i)
{
double rg = g_h[i] - g_l[i], bd = MathAbs(g_c[i] - g_o[i]);
return g_c[i] > g_o[i] && rg > 0 && g_atr[i] > 0 && bd >= InpDispAtr * g_atr[i] && bd / rg >= InpDispBody;
}
bool DispBear(const int i)
{
double rg = g_h[i] - g_l[i], bd = MathAbs(g_c[i] - g_o[i]);
return g_c[i] < g_o[i] && rg > 0 && g_atr[i] > 0 && bd >= InpDispAtr * g_atr[i] && bd / rg >= InpDispBody;
}
bool MidBull(const int k) { return k >= 0 && g_atr[k] > 0 && g_c[k] > g_o[k] && MathAbs(g_c[k] - g_o[k]) >= InpFvgMid * g_atr[k]; }
bool MidBear(const int k) { return k >= 0 && g_atr[k] > 0 && g_c[k] < g_o[k] && MathAbs(g_c[k] - g_o[k]) >= InpFvgMid * g_atr[k]; }
void AddDisp(const int bar, const int dir)
{
ArrayResize(g_dp, g_nDp + 1);
g_dp[g_nDp].bar = bar; g_dp[g_nDp].dir = dir; g_nDp++;
while(g_nDp > 12) { for(int j = 0; j < g_nDp - 1; j++) g_dp[j] = g_dp[j + 1]; g_nDp--; }
}
//+------------------------------------------------------------------+
//| zones (blocks) |
//+------------------------------------------------------------------+
int AddZone(const double top, const double bot, const int d, const int kind, const int bar, const string nm, const int i)
{
bool ok = (kind == 1 && InpObOn) || (kind == 2 && InpBrkOn) || (kind == 3 && EMit) || (kind == 4 && ERb) || (kind == 5 && EPb) || (kind == 6 && EVac);
if(!ok || !(top > bot)) return -1;
for(int q = 0; q < g_nZn; q++) // the same candle can give the same block twice (repeated breaks of one swing): keep one
if(g_zn[q].kind == kind && g_zn[q].dir == d && g_zn[q].bar == bar) return q;
while(g_nZn >= EZoneMax) { for(int j = 0; j < g_nZn - 1; j++) g_zn[j] = g_zn[j + 1]; g_nZn--; }
ArrayResize(g_zn, g_nZn + 1);
g_zn[g_nZn].top = top; g_zn[g_nZn].bot = bot; g_zn[g_nZn].dir = d; g_zn[g_nZn].kind = kind; g_zn[g_nZn].nm = nm;
g_zn[g_nZn].bar = bar; g_zn[g_nZn].born = i; g_zn[g_nZn].touched = false; g_zn[g_nZn].prop = false; g_zn[g_nZn].uni = false;
g_nZn++;
return g_nZn - 1;
}
int FindOff(const int cBar, const bool wantBear, const int span, const int i)
{
int base = i - cBar, res = base;
bool found = false;
for(int k = 0; k <= span; k++)
{
int o = base + k;
if(o <= 1000 && !found)
{
int idx = i - o;
if(idx < 0) continue;
bool isb = g_c[idx] < g_o[idx];
if(isb == wantBear) { res = o; found = true; }
}
}
return res;
}
void BlockFromIdx(const int idx, const int d, double &t, double &b)
{
t = g_h[idx]; b = g_l[idx];
if(InpZoneMode == MD_Z_BODY) { t = MathMax(g_o[idx], g_c[idx]); b = MathMin(g_o[idx], g_c[idx]); }
if(InpZoneMode == MD_Z_OPEN)
{
if(d == 1) t = MathMax(g_o[idx], g_c[idx]);
else b = MathMin(g_o[idx], g_c[idx]);
}
}
void ZoneAt(const int cBar, const bool wantBear, const int d, const int kind, const string nm, const int i)
{
if(i - cBar > 900) return;
int o = FindOff(cBar, wantBear, 3, i);
int idx = i - o;
if(idx < 0) return;
double t, b;
BlockFromIdx(idx, d, t, b);
AddZone(t, b, d, kind, idx, nm, i);
}
void UpdZones(const int i)
{
double pqT[], pqB[]; int pqD[]; int np = 0;
for(int k = g_nZn - 1; k >= 0; k--)
{
bool dead = false;
if(g_zn[k].kind == 6) dead = (g_zn[k].dir == 1) ? g_l[i] <= g_zn[k].bot : g_h[i] >= g_zn[k].top;
else dead = (g_zn[k].dir == 1) ? g_c[i] < g_zn[k].bot : g_c[i] > g_zn[k].top;
if(dead)
{
for(int j = k; j < g_nZn - 1; j++) g_zn[j] = g_zn[j + 1];
g_nZn--;
continue;
}
if(!g_zn[k].touched)
{
bool tch = (g_zn[k].dir == 1) ? g_l[i] <= g_zn[k].top : g_h[i] >= g_zn[k].bot;
if(tch && i > g_zn[k].bar) g_zn[k].touched = true;
}
if(EPb && !g_zn[k].prop && g_zn[k].kind >= 1 && g_zn[k].kind <= 3 && i > g_zn[k].bar)
{
if(g_zn[k].dir == 1 && g_c[i] > g_o[i] && g_l[i] <= g_zn[k].top && g_l[i] >= g_zn[k].bot && g_c[i] > g_zn[k].top)
{
g_zn[k].prop = true;
ArrayResize(pqT, np + 1); ArrayResize(pqB, np + 1); ArrayResize(pqD, np + 1);
pqT[np] = MathMin(g_o[i], g_zn[k].top); pqB[np] = g_l[i]; pqD[np] = 1; np++;
}
if(g_zn[k].dir == -1 && g_c[i] < g_o[i] && g_h[i] >= g_zn[k].bot && g_h[i] <= g_zn[k].top && g_c[i] < g_zn[k].bot)
{
g_zn[k].prop = true;
ArrayResize(pqT, np + 1); ArrayResize(pqB, np + 1); ArrayResize(pqD, np + 1);
pqT[np] = g_h[i]; pqB[np] = MathMax(g_o[i], g_zn[k].bot); pqD[np] = -1; np++;
}
}
}
for(int q = 0; q < np; q++)
AddZone(pqT[q], pqB[q], pqD[q], 5, i, pqD[q] == 1 ? "Bull Propulsion" : "Bear Propulsion", i);
}
void Gaps(const int i)
{
if(!EVac || i <= 0 || g_atr[i] <= 0) return;
double g = g_o[i] - g_c[i - 1];
if(MathAbs(g) < InpGapAtr * g_atr[i]) return;
bool wkndGap = g_intra && (g_t[i] - g_t[i - 1]) >= 24 * 3600;
bool dayChg = g_day[i] != g_day[i - 1];
bool ndog = g_intra && g_mn[i] >= 1020 && g_mn[i - 1] < 1020 && !dayChg;
string nm = wkndGap ? "NWOG" : (ndog ? "NDOG" : "Vacuum");
AddZone(MathMax(g_o[i], g_c[i - 1]), MathMin(g_o[i], g_c[i - 1]), g > 0 ? 1 : -1, 6, i - 1, nm, i);
}
//+------------------------------------------------------------------+
//| fair value gaps |
//+------------------------------------------------------------------+
void AddMisc(const int b0, const int b1, const double top, const double bot, const string txt, const color c)
{
ArrayResize(g_mb, g_nMb + 1);
g_mb[g_nMb].b0 = b0; g_mb[g_nMb].b1 = b1; g_mb[g_nMb].top = top; g_mb[g_nMb].bot = bot; g_mb[g_nMb].txt = txt; g_mb[g_nMb].col = c;
g_nMb++;
while(g_nMb > 30) { for(int j = 0; j < g_nMb - 1; j++) g_mb[j] = g_mb[j + 1]; g_nMb--; }
}
int NewFvg(const double top, const double bot, const int d, const double ext, const int i)
{
while(g_nFvg >= EFvgKeep) { for(int j = 0; j < g_nFvg - 1; j++) g_fvg[j] = g_fvg[j + 1]; g_nFvg--; }
ArrayResize(g_fvg, g_nFvg + 1);
g_fvg[g_nFvg].top = top; g_fvg[g_nFvg].bot = bot; g_fvg[g_nFvg].dir = d; g_fvg[g_nFvg].bar = i; g_fvg[g_nFvg].state = 0;
g_fvg[g_nFvg].ext = ext; g_fvg[g_nFvg].uni = false; g_fvg[g_nFvg].bp = false; g_fvg[g_nFvg].nm = "FVG";
g_nFvg++;
return g_nFvg - 1;
}
void FvgBpr(const int fi, const int dirWanted, const double minSz, const int i)
{
if(!EBpr) return;
for(int k = 0; k < g_nFvg; k++)
{
if(k == fi) continue;
if(g_fvg[k].dir == dirWanted && g_fvg[k].state == 0 && !g_fvg[k].bp && i - g_fvg[k].bar <= 40)
{
double oT = MathMin(g_fvg[fi].top, g_fvg[k].top), oB = MathMax(g_fvg[fi].bot, g_fvg[k].bot);
if(oT - oB >= minSz)
{
g_fvg[fi].bp = true; g_fvg[k].bp = true;
AddMisc(MathMin(g_fvg[k].bar, i) - 2, i + 6, oT, oB, "BPR", C_PURPLE);
}
}
}
}
void FvgDetect(const int i)
{
if(!InpFvgOn || i < 2 || g_atr[i] <= 0) return;
double atr = g_atr[i], minSz = EFvgMin * atr;
if(g_l[i] > g_h[i - 2] && (g_l[i] - g_h[i - 2]) >= minSz && (!EQual || (MidBull(i - 1) && g_c[i - 1] > g_h[i - 2])))
{
int fi = NewFvg(g_l[i], g_h[i - 2], 1, MathMin(g_l[i], MathMin(g_l[i - 1], g_l[i - 2])), i);
g_evFvg = 1; g_nfTop = g_fvg[fi].top; g_nfBot = g_fvg[fi].bot; g_nfExt = g_fvg[fi].ext; g_nfDir = 1;
FvgBpr(fi, -1, minSz, i);
}
if(g_h[i] < g_l[i - 2] && (g_l[i - 2] - g_h[i]) >= minSz && (!EQual || (MidBear(i - 1) && g_c[i - 1] < g_l[i - 2])))
{
int fi = NewFvg(g_l[i - 2], g_h[i], -1, MathMax(g_h[i], MathMax(g_h[i - 1], g_h[i - 2])), i);
g_evFvg = -1; g_nfTop = g_fvg[fi].top; g_nfBot = g_fvg[fi].bot; g_nfExt = g_fvg[fi].ext; g_nfDir = -1;
FvgBpr(fi, 1, minSz, i);
}
if(EVi) // volume imbalance (bodies gap, wicks overlap)
{
double vs = 0.05 * atr;
if(MathMin(g_o[i], g_c[i]) - MathMax(g_o[i - 1], g_c[i - 1]) >= vs && g_l[i] <= g_h[i - 1])
AddMisc(i - 1, i + 8, MathMin(g_o[i], g_c[i]), MathMax(g_o[i - 1], g_c[i - 1]), "VI", C_BULL);
if(MathMin(g_o[i - 1], g_c[i - 1]) - MathMax(g_o[i], g_c[i]) >= vs && g_h[i] >= g_l[i - 1])
AddMisc(i - 1, i + 8, MathMin(g_o[i - 1], g_c[i - 1]), MathMax(g_o[i], g_c[i]), "VI", C_BEAR);
}
}
void FvgUpdate(const int i)
{
for(int k = g_nFvg - 1; k >= 0; k--)
{
bool kill = false;
if(i > g_fvg[k].bar)
{
if(g_fvg[k].state == 0)
{
if(g_fvg[k].dir == 1) // bullish gap is filled from above
{
if(g_l[i] < g_fvg[k].bot)
{
if(g_c[i] < g_fvg[k].bot && EIfvg) { g_fvg[k].state = 1; g_fvg[k].dir = -1; g_fvg[k].nm = "IFVG"; }
else kill = true;
}
else if(InpFvgShrink && g_l[i] < g_fvg[k].top) g_fvg[k].top = MathMax(g_l[i], g_fvg[k].bot);
}
else // bearish gap is filled from below
{
if(g_h[i] > g_fvg[k].top)
{
if(g_c[i] > g_fvg[k].top && EIfvg) { g_fvg[k].state = 1; g_fvg[k].dir = 1; g_fvg[k].nm = "IFVG"; }
else kill = true;
}
else if(InpFvgShrink && g_h[i] > g_fvg[k].bot) g_fvg[k].bot = MathMin(g_h[i], g_fvg[k].top);
}
}
else
{
if(g_fvg[k].dir == -1 && g_c[i] > g_fvg[k].top) kill = true; // inverted FVG dies when price closes back through
if(g_fvg[k].dir == 1 && g_c[i] < g_fvg[k].bot) kill = true;
}
}
if(kill)
{
for(int j = k; j < g_nFvg - 1; j++) g_fvg[j] = g_fvg[j + 1];
g_nFvg--;
}
}
}
//+------------------------------------------------------------------+
//| unicorn |
//+------------------------------------------------------------------+
void Unicorn(const int i)
{
if(!InpUniOn) return;
for(int a = 0; a < g_nZn; a++)
{
if(!(g_zn[a].kind == 2 && !g_zn[a].uni && g_mssDir == g_zn[a].dir && g_zn[a].born >= g_mssBar && (g_t[i] - g_mssT) <= AgeSec())) continue;
for(int b = 0; b < g_nFvg; b++)
{
if(g_zn[a].uni) break;
if(!(!g_fvg[b].uni && g_fvg[b].state == 0 && g_fvg[b].dir == g_zn[a].dir && g_fvg[b].bar >= g_mssBar - 3 && g_fvg[b].bar <= g_mssBar + InpUniWin)) continue;
double oT = MathMin(g_zn[a].top, g_fvg[b].top), oB = MathMax(g_zn[a].bot, g_fvg[b].bot);
if(oT > oB)
{
g_zn[a].uni = true; g_fvg[b].uni = true;
AddMisc(MathMin(g_zn[a].bar, g_fvg[b].bar), i + 10, oT, oB, "Unicorn", g_fvg[b].dir == 1 ? C_CYAN : C_ORANGE);
g_evUni = g_fvg[b].dir; g_uniTop = oT; g_uniBot = oB; g_uniDir = g_fvg[b].dir; g_uniBar = i; g_uniDone = false;
g_uniFar = g_fvg[b].dir == 1 ? g_zn[a].bot : g_zn[a].top;
}
}
}
}
//+------------------------------------------------------------------+
//| structure: swings, dealing range, MSS / BOS |
//+------------------------------------------------------------------+
void ComputeDealing()
{
g_dealOk = false;
if(IsNa(g_swHi) || IsNa(g_swLo) || !(g_swHi > g_swLo)) return;
g_dealOk = true;
double r = g_swHi - g_swLo;
g_eq = g_swLo + r / 2.0; g_rngTop = g_swHi; g_rngBot = g_swLo;
g_oteFired = false;
if(g_swLoBar < g_swHiBar)
{
g_oteDir = 1; g_oteTop = g_swHi - InpOteLo * r; g_oteBot = g_swHi - InpOteHi * r; g_oteMid = g_swHi - 0.705 * r;
g_oteSl = g_swLo; g_oteTp = g_swHi + 0.27 * r;
}
else
{
g_oteDir = -1; g_oteBot = g_swLo + InpOteLo * r; g_oteTop = g_swLo + InpOteHi * r; g_oteMid = g_swLo + 0.705 * r;
g_oteSl = g_swHi; g_oteTp = g_swLo - 0.27 * r;
}
}
void PivotsM(const int i)
{
int len = InpSwLen, c = i - len;
if(c - len < 0) return;
bool ph = PivotHigh(c, len), pl = PivotLow(c, len), changed = false;
double atr = g_atr[i];
if(ph)
{
g_pSwHi = g_swHi; g_swHi = g_h[c]; g_swHiBar = c; g_swHiX = false; changed = true;
if(InpEqOn && !IsNa(g_pSwHi) && atr > 0 && MathAbs(g_swHi - g_pSwHi) <= InpEqTol * atr)
AddLiq(MathMax(g_swHi, g_pSwHi), "EQH", 1, 5, g_swHiBar, i);
if(ERb)
{
double wk = g_h[c] - MathMax(g_o[c], g_c[c]), cr = g_h[c] - g_l[c];
if(cr > 0 && wk >= InpRbPct * cr && wk >= InpRbAtr * atr) AddZone(g_h[c], MathMax(g_o[c], g_c[c]), -1, 4, c, "Bear Rejection", i);
}
}
if(pl)
{
g_pSwLo = g_swLo; g_swLo = g_l[c]; g_swLoBar = c; g_swLoX = false; changed = true;
if(InpEqOn && !IsNa(g_pSwLo) && atr > 0 && MathAbs(g_swLo - g_pSwLo) <= InpEqTol * atr)
AddLiq(MathMin(g_swLo, g_pSwLo), "EQL", -1, 5, g_swLoBar, i);
if(ERb)
{
double wk = MathMin(g_o[c], g_c[c]) - g_l[c], cr = g_h[c] - g_l[c];
if(cr > 0 && wk >= InpRbPct * cr && wk >= InpRbAtr * atr) AddZone(MathMin(g_o[c], g_c[c]), g_l[c], 1, 4, c, "Bull Rejection", i);
}
}
if(changed) ComputeDealing();
}
void AddStruct(const double px, const int fromBar, const int toBar, const string txt, const color c)
{
ArrayResize(g_st, g_nSt + 1);
g_st[g_nSt].px = px; g_st[g_nSt].fromBar = fromBar; g_st[g_nSt].toBar = toBar; g_st[g_nSt].txt = txt; g_st[g_nSt].col = c;
g_nSt++;
while(g_nSt > EStructKeep) { for(int j = 0; j < g_nSt - 1; j++) g_st[j] = g_st[j + 1]; g_nSt--; }
}
void Breaks(const int i)
{
if(!IsNa(g_swHi) && !g_swHiX && g_c[i] > g_swHi)
{
g_swHiX = true; g_evBrk = 1;
bool dOk = i - g_dispBullBar <= 3;
bool isMss = g_trend == -1 && (dOk || !InpMssNeedD);
string tx = isMss ? (dOk ? "MSS " + Bolt() : "MSS") : (g_trend == -1 ? "CHoCH" : "BOS");
if((isMss && InpShowMss) || (!isMss && EShowBos)) AddStruct(g_swHi, g_swHiBar, i, tx, C_BULL);
if(isMss)
{
g_mssDir = 1; g_mssBar = i; g_mssT = g_t[i]; g_evMss = 1;
bool swept = !IsNa(g_pSwLo) && !IsNa(g_swLo) && g_swLo < g_pSwLo;
if(swept) ZoneAt(g_swHiBar, false, 1, 2, "Bull Breaker", i);
else ZoneAt(g_swLoBar, true, 1, 3, "Bull Mitigation", i);
}
else if(g_trend == 1 || g_trend == 0) ZoneAt(g_swLoBar, true, 1, 1, "Bull OB", i);
g_trend = 1;
}
if(!IsNa(g_swLo) && !g_swLoX && g_c[i] < g_swLo)
{
g_swLoX = true; g_evBrk = -1;
bool dOk = i - g_dispBearBar <= 3;
bool isMss = g_trend == 1 && (dOk || !InpMssNeedD);
string tx = isMss ? (dOk ? "MSS " + Bolt() : "MSS") : (g_trend == 1 ? "CHoCH" : "BOS");
if((isMss && InpShowMss) || (!isMss && EShowBos)) AddStruct(g_swLo, g_swLoBar, i, tx, C_BEAR);
if(isMss)
{
g_mssDir = -1; g_mssBar = i; g_mssT = g_t[i]; g_evMss = -1;
bool swept = !IsNa(g_pSwHi) && !IsNa(g_swHi) && g_swHi > g_pSwHi;
if(swept) ZoneAt(g_swLoBar, true, -1, 2, "Bear Breaker", i);
else ZoneAt(g_swHiBar, false, -1, 3, "Bear Mitigation", i);
}
else if(g_trend == -1 || g_trend == 0) ZoneAt(g_swHiBar, false, -1, 1, "Bear OB", i);
g_trend = -1;
}
}
//+------------------------------------------------------------------+
//| models and setups |
//+------------------------------------------------------------------+
string ModelName(const int m) { return m == 1 ? "ICT 2022" : (m == 2 ? "Silver Bullet" : (m == 3 ? "Unicorn" : "OTE")); }
int Score(const int d, const bool kz, const int i)
{
int s = 0;
if(kz) s++;
if(g_raidDir == d && (g_t[i] - g_raidT) <= AgeSec()) s++;
if(g_mssDir == d && (g_t[i] - g_mssT) <= AgeSec()) s++;
if((d == 1 && i - g_dispBullBar <= 20) || (d == -1 && i - g_dispBearBar <= 20)) s++;
if(g_htf[i] == d) s++;
if(!IsNa(g_eq) && ((d == 1 && g_c[i] < g_eq) || (d == -1 && g_c[i] > g_eq))) s++;
return s;
}
double TargetPx(const int d, const double entry, const double minDist)
{
double best = TM_NA;
for(int k = 0; k < g_nLq; k++)
{
if(g_lq[k].swept || g_lq[k].dir != d) continue;
if(d == 1 && g_lq[k].price >= entry + minDist && (IsNa(best) || g_lq[k].price < best)) best = g_lq[k].price;
if(d == -1 && g_lq[k].price <= entry - minDist && (IsNa(best) || g_lq[k].price > best)) best = g_lq[k].price;
}
return best;
}
bool NewSetup(const int model, const int d, const double entry, const double sl, const bool inKz, const bool filled, const int i)
{
if(!InpSigOn) return false;
double risk = MathAbs(entry - sl);
bool dirOk = (d == 1) ? sl < entry : sl > entry;
int sc = Score(d, inKz, i);
bool htfOk = !InpHtfReq || g_htf[i] == d;
if(!(risk > 0 && dirOk && htfOk && sc >= InpMinScore)) return false;
double tp = TargetPx(d, entry, risk * InpMinRR);
if(IsNa(tp)) tp = entry + d * risk * InpFbRR;
double rr = MathAbs(tp - entry) / risk;
if(rr < InpMinRR - 0.0001) return false;
int oi = 0; // only FINISHED setups may be dropped; open / pending trades never are
while(g_nSu >= 24 && oi < g_nSu)
{
if(g_su[oi].state == 2) { for(int j = oi; j < g_nSu - 1; j++) g_su[j] = g_su[j + 1]; g_nSu--; }
else oi++;
}
if(g_nSu >= 40) { g_capRej++; return false; }
ArrayResize(g_su, g_nSu + 1);
g_su[g_nSu].model = model; g_su[g_nSu].dir = d; g_su[g_nSu].entry = entry; g_su[g_nSu].sl = sl; g_su[g_nSu].tp = tp;
g_su[g_nSu].born = i; g_su[g_nSu].fillBar = filled ? i : 0; g_su[g_nSu].state = filled ? 1 : 0; g_su[g_nSu].score = sc;
g_su[g_nSu].rr = rr; g_su[g_nSu].cost = InpCostAtr * g_atr[i]; g_su[g_nSu].finBar = -1; g_su[g_nSu].win = false;
g_nSu++;
g_evSig = d;
return true;
}
void FinishSetup(const int k, const bool win, const int i)
{
g_su[k].state = 2; g_su[k].finBar = i; g_su[k].win = win;
if(win) g_win[g_su[k].model]++; else g_loss[g_su[k].model]++;
}
void UpdSetups(const int i)
{
for(int k = g_nSu - 1; k >= 0; k--)
{
if(g_su[k].state == 0)
{
bool cancel = i - g_su[k].born > InpExpBars;
if(g_su[k].dir == 1 && (g_c[i] < g_su[k].sl || g_h[i] >= g_su[k].tp)) cancel = true;
if(g_su[k].dir == -1 && (g_c[i] > g_su[k].sl || g_l[i] <= g_su[k].tp)) cancel = true;
if(cancel)
{
for(int j = k; j < g_nSu - 1; j++) g_su[j] = g_su[j + 1];
g_nSu--;
}
else if(i > g_su[k].born && ((g_su[k].dir == 1 && g_l[i] <= g_su[k].entry) || (g_su[k].dir == -1 && g_h[i] >= g_su[k].entry)))
{
g_su[k].state = 1; g_su[k].fillBar = i;
bool slNow = (g_su[k].dir == 1) ? g_l[i] <= g_su[k].sl + g_su[k].cost : g_h[i] >= g_su[k].sl - g_su[k].cost;
if(slNow) FinishSetup(k, false, i);
}
}
else if(g_su[k].state == 1 && i > g_su[k].fillBar)
{
bool slHit = (g_su[k].dir == 1) ? g_l[i] <= g_su[k].sl + g_su[k].cost : g_h[i] >= g_su[k].sl - g_su[k].cost; // pessimistic: the stop is checked first
bool tpHit = (g_su[k].dir == 1) ? g_h[i] >= g_su[k].tp + g_su[k].cost : g_l[i] <= g_su[k].tp - g_su[k].cost;
if(slHit) FinishSetup(k, false, i);
else if(tpHit) FinishSetup(k, true, i);
}
}
}
double EntryPx(const double top, const double bot, const int d)
{
if(InpEntryMode == MD_E_EDGE) return d == 1 ? top : bot;
return (top + bot) / 2.0;
}
void ModelsBar(const int i)
{
double buf = InpSlBuf * g_atr[i];
bool kz = KzAny(i);
if(InpM22On) // ICT 2022: raid -> MSS -> FVG
{
if(g_evMss != 0 && g_raidDir == g_evMss && i - g_raidBar <= InpMssWin && (g_t[i] - g_raidT) <= AgeSec() && (!InpKzOnly || kz))
{ g_w22Dir = g_evMss; g_w22Bar = i; g_w22Px = g_raidPx; }
if(g_w22Dir != 0)
{
if(i - g_w22Bar > 3) g_w22Dir = 0;
else
{
bool done = false;
for(int k = g_nFvg - 1; k >= 0 && !done; k--)
{
if(g_fvg[k].dir == g_w22Dir && g_fvg[k].state == 0 && g_fvg[k].bar >= g_w22Bar)
{
double en = EntryPx(g_fvg[k].top, g_fvg[k].bot, g_fvg[k].dir);
double sl = g_w22Dir == 1 ? g_w22Px - buf : g_w22Px + buf;
if(NewSetup(1, g_w22Dir, en, sl, kz, false, i)) { g_w22Dir = 0; done = true; } // the chain ends only when a setup was really made
}
}
}
}
}
if(!SbAny(i)) g_sbFired = false; // Silver Bullet
if(InpMSbOn && SbAny(i) && !g_sbFired && g_evFvg != 0)
{
int d = g_nfDir;
bool aligned = (g_mssDir == d && (g_t[i] - g_mssT) <= AgeSec()) || (g_raidDir == d && (g_t[i] - g_raidT) <= AgeSec()) || !InpSbBias;
if(aligned)
{
double en = EntryPx(g_nfTop, g_nfBot, d);
double sl = d == 1 ? g_nfExt - buf : g_nfExt + buf;
if(NewSetup(2, d, en, sl, true, false, i)) g_sbFired = true;
}
}
if(InpMUniOn && g_uniDir != 0 && !g_uniDone && i - g_uniBar <= 3 && (!InpKzOnly || kz)) // Unicorn: retried for 3 bars, ONE setup at most
{
int d = g_uniDir;
double en = EntryPx(g_uniTop, g_uniBot, d);
double sl = d == 1 ? g_uniFar - buf : g_uniFar + buf;
if(NewSetup(3, d, en, sl, kz, false, i)) g_uniDone = true;
}
if(InpMOteOn && g_oteDir != 0 && !g_oteFired && (!InpKzOnly || kz)) // OTE: a limit order at the signal candle's close
{
bool origin = (InpOteSL == MD_O_ORIGIN);
if(g_oteDir == 1 && g_l[i] <= g_oteTop && g_c[i] > g_oteBot && g_c[i] > g_oteMid && g_c[i] > g_o[i])
{
double slv = origin ? g_oteSl : MathMin(g_l[i], g_oteBot);
if(NewSetup(4, 1, g_c[i], slv - buf, kz, false, i)) g_oteFired = true;
}
if(g_oteDir == -1 && g_h[i] >= g_oteBot && g_c[i] < g_oteTop && g_c[i] < g_oteMid && g_c[i] < g_o[i])
{
double slv = origin ? g_oteSl : MathMax(g_h[i], g_oteTop);
if(NewSetup(4, -1, g_c[i], slv + buf, kz, false, i)) g_oteFired = true;
}
}
UpdSetups(i);
}
//+------------------------------------------------------------------+
//| one confirmed bar (same order as the Pine main block) |
//+------------------------------------------------------------------+
void ProcessBar(const int i)
{
g_evRaid = 0; g_evRun = 0; g_evMss = 0; g_evBrk = 0; g_evFvg = 0; g_evUni = 0; g_evSig = 0;
if(DispBull(i)) { g_dispBullBar = i; if(EDispMark) AddDisp(i, 1); }
if(DispBear(i)) { g_dispBearBar = i; if(EDispMark) AddDisp(i, -1); }
if(g_intra)
for(int k = 0; k < 5; k++)
{
bool ended = SessUpd(k, Fl(i, k), i);
if(ended) SessEnd(k, i);
}
PeriodsBar(i);
FvgDetect(i);
FvgUpdate(i);
PivotsM(i);
ChkLiq(i);
Breaks(i);
UpdZones(i);
Gaps(i);
Unicorn(i);
ModelsBar(i);
}
//+------------------------------------------------------------------+
//| drawing |
//+------------------------------------------------------------------+
color ZoneCol(const int kind, const int d)
{
if(kind == 2) return d == 1 ? C_CYAN : C_ORANGE;
if(kind == 3) return d == 1 ? C_PURPLE : C_PINK;
if(kind == 4) return C_YELLOW;
if(kind == 5) return C_LIME;
if(kind == 6) return C_GRAY;
return d == 1 ? InpObBullC : InpObBearC;
}
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;
}
double g_near = 0.0;
void DrawFvgs()
{
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
for(int k = 0; k < g_nFvg; k++)
{
color c = g_fvg[k].dir == 1 ? (g_fvg[k].state == 1 ? C_CYAN : InpFvgBullC) : (g_fvg[k].state == 1 ? C_ORANGE : InpFvgBearC);
datetime t1 = g_t[MathMax(0, g_fvg[k].bar - 2)];
MkRect("FV", t1, g_fvg[k].top, xe, g_fvg[k].bot, Tint(c, 0.14), true);
MkRect("FB", t1, g_fvg[k].top, xe, g_fvg[k].bot, Tint(c, 0.6), false);
double mid = (g_fvg[k].top + g_fvg[k].bot) / 2.0;
if(InpFvgCE) MkLine("FC", t1, mid, xe, mid, Tint(c, 0.7), STYLE_DOT, 1);
if(MathAbs(mid - g_c[g_n - 1]) <= g_near) AddItem(g_fvg[k].nm, c, mid);
}
}
void DrawZones()
{
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
for(int k = 0; k < g_nZn; k++)
{
color c = ZoneCol(g_zn[k].kind, g_zn[k].dir);
datetime t1 = g_t[MathMax(0, g_zn[k].bar)];
MkRect("ZF", t1, g_zn[k].top, xe, g_zn[k].bot, Tint(c, g_zn[k].touched ? 0.08 : 0.15), true);
MkRect("ZB", t1, g_zn[k].top, xe, g_zn[k].bot, Tint(c, 0.7), false);
double mid = (g_zn[k].top + g_zn[k].bot) / 2.0;
if(EZoneMt) MkLine("ZM", t1, mid, xe, mid, Tint(c, 0.6), STYLE_DOT, 1);
if(MathAbs(mid - g_c[g_n - 1]) <= g_near) AddItem(g_zn[k].nm, c, mid);
}
}
void DrawMisc()
{
for(int k = 0; k < g_nMb; k++)
{
int b0 = MathMax(0, g_mb[k].b0);
datetime t1 = g_t[b0], t2 = TimeAt(g_mb[k].b1);
bool uni = (g_mb[k].txt == "Unicorn");
MkRect("MB", t1, g_mb[k].top, t2, g_mb[k].bot, Tint(g_mb[k].col, uni ? 0.30 : 0.22), true);
MkRect("MR", t1, g_mb[k].top, t2, g_mb[k].bot, uni ? clrWhite : Tint(g_mb[k].col, 0.9), false);
MkText("MT", t1 + (datetime)((t2 - t1) / 2), (g_mb[k].top + g_mb[k].bot) / 2.0, g_mb[k].txt, uni ? clrWhite : g_mb[k].col, ANCHOR_CENTER);
}
}
void DrawStructure()
{
for(int k = 0; k < g_nSt; k++)
{
int f = Cl1400(g_st[k].fromBar);
MkLine("SL", g_t[f], g_st[k].px, g_t[g_st[k].toBar], g_st[k].px, g_st[k].col, STYLE_DASH, 1);
MkText("ST", g_t[(f + g_st[k].toBar) / 2], g_st[k].px, g_st[k].txt, g_st[k].col, ANCHOR_LOWER);
}
}
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);
if(MathAbs(g_lq[k].price - g_c[g_n - 1]) <= g_near * 1.5) AddItem(g_lq[k].name + " " + Fp(g_lq[k].price), c, g_lq[k].price);
}
}
}
void DrawDealing()
{
if(!g_dealOk) return;
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
datetime x0 = g_t[Cl1400(MathMin(g_swHiBar, g_swLoBar))];
if(EPdZone)
{
MkRect("PD", x0, g_swHi, xe, g_eq, Tint(C_BEAR, 0.06), true);
MkRect("PD", x0, g_eq, xe, g_swLo, Tint(C_BULL, 0.06), true);
MkLine("PE", x0, g_eq, xe, g_eq, Tint(C_GRAY, 0.8), STYLE_DASH, 1);
AddItem("Premium", C_BEAR, g_swHi);
AddItem("EQ " + Fp(g_eq), C_GRAY, g_eq);
AddItem("Discount", C_BULL, g_swLo);
}
if(InpOteOn)
{
color oc = g_oteDir == 1 ? C_BULL : C_BEAR;
datetime xo = g_t[Cl1400(MathMax(g_swHiBar, g_swLoBar))];
MkRect("OF", xo, g_oteTop, xe, g_oteBot, Tint(oc, 0.18), true);
MkRect("OB", xo, g_oteTop, xe, g_oteBot, Tint(oc, 0.6), false);
MkLine("OM", xo, g_oteMid, xe, g_oteMid, Tint(oc, 0.8), STYLE_DOT, 1);
AddItem("OTE 70.5%", oc, g_oteMid);
}
}
void DrawMarks()
{
for(int k = 0; k < g_nMk; k++)
{
if(!g_mk[k].alive) continue;
int cnt = 0; // several markers on the same bar are stacked
for(int j = 0; j < k; j++) if(g_mk[j].alive && g_mk[j].bar == g_mk[k].bar) cnt++;
double off = cnt * 0.7 * g_atr[MathMin(g_mk[k].bar, g_n - 1)];
double y = g_mk[k].dir == 1 ? g_mk[k].price + off : g_mk[k].price - off;
MkText("MK", g_t[g_mk[k].bar], y, g_mk[k].text, g_mk[k].col, g_mk[k].dir == 1 ? ANCHOR_LOWER : ANCHOR_UPPER);
}
for(int k = 0; k < g_nDp; k++)
MkText("DP", g_t[g_dp[k].bar], (g_o[g_dp[k].bar] + g_c[g_dp[k].bar]) / 2.0, Bolt(), g_dp[k].dir == 1 ? C_BULL : C_BEAR, ANCHOR_CENTER);
}
void DrawSetups()
{
if(!InpSigOn || !InpShowSetups) return;
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
for(int k = 0; k < g_nSu; k++)
{
datetime t0 = g_t[g_su[k].born];
datetime t1 = (g_su[k].state == 2) ? g_t[g_su[k].finBar] : xe;
bool fin = (g_su[k].state == 2);
MkLine("SE", t0, g_su[k].entry, t1, g_su[k].entry, fin ? Tint(C_GRAY, 0.8) : C_BLUE, STYLE_SOLID, 2);
MkLine("SS", t0, g_su[k].sl, t1, g_su[k].sl, (fin && !g_su[k].win) ? Tint(C_GRAY, 0.3) : C_BEAR, STYLE_SOLID, 1);
MkLine("SU", t0, g_su[k].tp, t1, g_su[k].tp, (fin && g_su[k].win) ? Tint(C_GRAY, 0.3) : C_BULL, STYLE_SOLID, 1);
string arrow = g_su[k].dir == 1 ? " " + Up() + " " : " " + Dn() + " ";
if(fin)
MkText("SF", t1, g_su[k].entry, ModelName(g_su[k].model) + arrow + (g_su[k].win ? "TP " + Ck() : "SL x"), g_su[k].win ? C_BULL : C_BEAR, ANCHOR_LEFT);
else
{
string lb = ModelName(g_su[k].model) + (g_su[k].dir == 1 ? " BUY " : " SELL ") + "*" + IntegerToString(g_su[k].score) + "/6 RR " + DoubleToString(g_su[k].rr, 1);
if(g_su[k].state == 1) lb = ModelName(g_su[k].model) + arrow + "filled *" + IntegerToString(g_su[k].score);
AddItem(lb, g_su[k].dir == 1 ? C_BULL : C_BEAR, g_su[k].entry);
}
}
}
//+------------------------------------------------------------------+
//| panel |
//+------------------------------------------------------------------+
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); }
string TfName(const ENUM_TIMEFRAMES tf)
{
int m = PeriodSeconds(tf) / 60;
if(m < 60) return "M" + IntegerToString(m);
if(m < 1440) return "H" + IntegerToString(m / 60);
return "D" + IntegerToString(m / 1440);
}
void UpdatePanel()
{
if(!InpShowPanel)
{
ObjectsDeleteAll(0, PFX + "PN");
return;
}
if(g_n < 3) return;
ChooseCorner();
int L = g_n - 2;
string actNm = "Outside killzones";
color actCol = C_GRAY;
for(int k = 0; k < 5; k++) if(g_ms[k].on && g_ms[k].active) { actNm = g_ms[k].name; actCol = g_ms[k].col; }
if(SbAny(L)) actNm += " + Silver Bullet";
string rd = g_raidDir == 0 ? "-" : g_raidName + (g_raidRej ? " (raid)" : " (run)") + " - " + IntegerToString(L - g_raidBar) + " bars ago";
string ms = g_mssDir == 0 ? "-" : DirTxt(g_mssDir) + " - " + IntegerToString(L - g_mssBar) + " bars ago";
string pdTxt = "-";
color pdCol = C_GRAY;
if(!IsNa(g_rngTop) && g_rngTop > g_rngBot)
{
double pos = (g_c[L] - g_rngBot) / (g_rngTop - g_rngBot) * 100.0;
if(pos > 100.0) { pdTxt = "Above range (new highs)"; pdCol = C_BEAR; }
else if(pos < 0.0) { pdTxt = "Below range (new lows)"; pdCol = C_BULL; }
else { pdTxt = (pos >= 50.0 ? "Premium " : "Discount ") + IntegerToString((int)pos) + "%"; pdCol = pos >= 50.0 ? C_BEAR : C_BULL; }
}
string mode = "Analysis only (no signals)";
color modeCol = C_GRAY;
if(InpSigOn)
{
int nOpen = 0;
for(int k = 0; k < g_nSu; k++) if(g_su[k].state < 2) nOpen++;
mode = "Setups: " + IntegerToString(nOpen) + " open";
if(EStats)
{
mode = "";
for(int m = 1; m <= 4; m++) mode += ModelName(m) + " " + IntegerToString(g_win[m]) + "W/" + IntegerToString(g_loss[m]) + "L ";
}
if(g_capRej > 0) mode += " (rejected " + IntegerToString(g_capRej) + ": 40 open)";
modeCol = C_ORANGE;
}
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);
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 Models", g_intra ? "" : "use <= H4", C_ORANGE);
PanelRow(1, "Now", actNm, actCol);
PanelRow(2, "HTF bias " + TfName(InpBiasTF) + "/" + IntegerToString(InpBiasLen), DirTxt(g_htf[L]), DirCol(g_htf[L]));
PanelRow(3, "Last liquidity", rd, DirCol(g_raidDir));
PanelRow(4, "Last MSS", ms, DirCol(g_mssDir));
PanelRow(5, "Dealing range", pdTxt, pdCol);
PanelRow(6, "Mode", mode, modeCol);
PanelRow(7, "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);
}
//+------------------------------------------------------------------+
//| alerts |
//+------------------------------------------------------------------+
void FireAlerts()
{
string names[10] = {"Buy-side liquidity raided (bearish expectation)", "Sell-side liquidity raided (bullish expectation)",
"Liquidity run (level broken and held, not a raid)", "Bullish MSS", "Bearish MSS", "New bullish FVG", "New bearish FVG",
"Unicorn zone formed", "BUY setup", "SELL setup"};
for(int k = 0; k < 10; k++) if(g_alLast[k]) Notify(names[k]);
}
void FlagAlerts()
{
for(int k = 0; k < 10; k++) g_alLast[k] = false;
g_alLast[0] = (g_evRaid == -1); g_alLast[1] = (g_evRaid == 1); g_alLast[2] = (g_evRun != 0);
g_alLast[3] = (g_evMss == 1); g_alLast[4] = (g_evMss == -1);
g_alLast[5] = (g_evFvg == 1); g_alLast[6] = (g_evFvg == -1);
g_alLast[7] = (g_evUni != 0);
g_alLast[8] = (g_evSig == 1); g_alLast[9] = (g_evSig == -1);
}
//+------------------------------------------------------------------+
//| 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();
InitModels();
ResetState();
BuildSeries();
const int L = g_n - 2; // last CLOSED bar: no repainting
for(int i = 0; i <= L; i++) ProcessBar(i);
FlagAlerts();
g_nItem = 0;
g_near = Rng150() * ELblNear;
DrawDealing();
DrawFvgs();
DrawZones();
DrawMisc();
DrawStructure();
DrawLiquidity();
DrawSetups();
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 Models");
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 Models (MT4) |
//| Market structure (MSS / BOS), Fair Value Gaps (CE, IFVG, VI, |
//| BPR), Order Block, Breaker, Mitigation, Unicorn, Premium / |
//| Discount, OTE, Raid / Run, panel. Analysis only by default; |
//| ICT 2022 / Silver Bullet / Unicorn / OTE setups are optional |
//| © 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_CORNER { TM_C_AUTO, TM_C_TOPRIGHT, TM_C_TOPLEFT, TM_C_BOTTOMRIGHT, TM_C_BOTTOMLEFT };
enum ENUM_MD_PRESET { MD_P_CLEAN, MD_P_STANDARD, MD_P_FULL, MD_P_CUSTOM };
enum ENUM_MD_ZMODE { MD_Z_FULL, MD_Z_BODY, MD_Z_OPEN };
enum ENUM_MD_ENTRY { MD_E_CE, MD_E_EDGE };
enum ENUM_MD_OTESL { MD_O_BEYOND, MD_O_ORIGIN };
// ---- General & time zones
input ENUM_MD_PRESET InpPreset = MD_P_CLEAN; // Preset (Clean = only the most valuable items, Standard, Full, Custom = my own settings below)
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 InpFont = 9; // Label font size
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 double InpLblNear = 0.30; // Show zone labels within (x recent range from price)
input int InpRightMargin = 15; // Chart right margin (% of the chart, 0 = leave the chart alone)
// ---- Killzones (used by the models, New York time)
input bool InpAsiaOn = true; // Asian range
input string InpAsiaR = "2000-0000"; // Asian range
input bool InpLonOn = true; // London killzone
input string InpLonR = "0200-0500"; // London
input bool InpNyaOn = true; // New York AM killzone
input string InpNyaR = "0700-1000"; // NY AM
input bool InpLcOn = true; // London Close killzone
input string InpLcR = "1000-1200"; // London Close
input bool InpNypOn = true; // New York PM killzone
input string InpNypR = "1330-1600"; // NY PM
input bool InpSessLvlOn = true; // Turn finished session highs/lows into liquidity levels (Custom preset)
input bool InpSbOn = true; // Silver Bullet windows
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)
// ---- Liquidity levels (raids & targets)
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 (Custom preset)
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 = 60; // Keep swept levels for (bars) (Custom preset)
input int InpMaxLiq = 40; // Max liquidity levels on chart (Custom preset)
// ---- Market structure & displacement
input int InpSwLen = 5; // Swing length
input double InpDispAtr = 1.2; // Displacement: body >= x ATR
input double InpDispBody = 0.6; // Displacement: body / range >=
input bool InpDispMark = true; // Mark displacement candles (Custom preset)
input bool InpMssNeedD = false; // MSS needs displacement (else a plain break = BOS / CHoCH)
input bool InpShowBos = true; // Show BOS (Custom preset)
input bool InpShowMss = true; // Show MSS
input int InpStructKeep = 14; // Structure lines to keep (Custom preset)
// ---- Premium / Discount & OTE
input bool InpPdZoneOn = true; // Premium / Discount / Equilibrium zone (Custom preset)
input bool InpOteOn = true; // OTE zone
input double InpOteLo = 0.62; // OTE from
input double InpOteHi = 0.79; // OTE to
// ---- Fair Value Gaps
input bool InpFvgOn = true; // Fair Value Gaps
input double InpFvgMin = 0.10; // Min FVG size (x ATR) (Custom preset)
input int InpFvgKeep = 16; // Max active FVGs (Custom preset)
input bool InpFvgQual = false; // Quality filter: middle candle must be a strong move (Custom preset)
input double InpFvgMid = 0.5; // Quality filter: middle candle body >= x ATR
input bool InpFvgCE = true; // Show Consequent Encroachment (50 %)
input bool InpFvgShrink = true; // Shrink FVG when partly filled
input bool InpIfvgOn = true; // Inversion FVG (IFVG) (Custom preset)
input bool InpViOn = true; // Volume Imbalance (VI) (Custom preset)
input bool InpBprOn = true; // Balanced Price Range (BPR) (Custom preset)
input color InpFvgBullC = C'8,153,129';
input color InpFvgBearC = C'242,54,69';
// ---- Blocks, Breaker, Mitigation, Rejection, Propulsion, Gaps, Unicorn
input bool InpObOn = true; // Order Block (BOS continuation)
input bool InpBrkOn = true; // Breaker Block (approx.)
input bool InpMitOn = true; // Mitigation Block (approx.) (Custom preset)
input bool InpRbOn = true; // Rejection Block (Custom preset)
input bool InpPbOn = true; // Propulsion Block (Custom preset)
input bool InpVacOn = true; // Vacuum Block / NWOG / NDOG (Custom preset)
input bool InpUniOn = true; // Unicorn (Breaker + FVG overlap)
input ENUM_MD_ZMODE InpZoneMode = MD_Z_FULL; // Block zone from
input bool InpZoneMt = true; // Show 50 % mean threshold of blocks (Custom preset)
input int InpZoneMax = 24; // Max blocks on chart (Custom preset)
input double InpRbPct = 0.5; // Rejection: wick >= % of candle
input double InpRbAtr = 0.5; // Rejection: wick >= x ATR
input double InpGapAtr = 0.3; // Gap: min size (x ATR)
input color InpObBullC = C'8,153,129';
input color InpObBearC = C'242,54,69';
// ---- Models & signals
input bool InpSigOn = false; // Trade setups: Entry / SL / TP signals (OFF = analysis only)
input bool InpM22On = true; // Model: ICT 2022 (raid -> MSS -> FVG)
input bool InpMSbOn = true; // Model: Silver Bullet (FVG inside window)
input bool InpMUniOn = true; // Model: Unicorn
input bool InpMOteOn = true; // Model: OTE
input int InpUniWin = 6; // Unicorn: FVG must form within N bars after the MSS
input bool InpKzOnly = true; // Signals only inside killzones / Silver Bullet
input int InpMssWin = 30; // ICT 2022: max bars from raid to MSS
input ENUM_MD_ENTRY InpEntryMode = MD_E_CE; // Entry on FVG
input double InpSlBuf = 0.10; // Stop buffer (x ATR)
input double InpMinRR = 1.5; // Min reward : risk
input double InpFbRR = 2.0; // Fallback target (R) if no liquidity target
input int InpExpBars = 40; // Cancel unfilled setup after (bars)
input int InpMinScore = 3; // Min confluence score (0-6)
input bool InpSbBias = true; // Silver Bullet needs raid or MSS in the same direction
input ENUM_TIMEFRAMES InpBiasTF = PERIOD_H4; // HTF bias timeframe
input int InpBiasLen = 21; // HTF bias EMA length
input ENUM_MD_OTESL InpOteSL = MD_O_BEYOND; // OTE model stop
input int InpAgeMin = 240; // Event validity for the score (minutes)
input bool InpHtfReq = false; // Require HTF bias in the trade direction
input double InpCostAtr = 0.03; // Trading cost (x ATR) in the win-rate stats
input bool InpShowSetups = true; // Draw entry / SL / TP
// ---- 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 InpShowStats = false; // Win / loss stats of the models in the panel (Custom preset, needs trade setups)
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_MODELS_";
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[10];
bool g_alLast[10];
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;
const color C_LIME = C'139,195,74';
bool EDispMark = false, EShowBos = false, EIfvg = false, EVi = false, EBpr = false, EMit = false, ERb = false, EPb = false, EVac = false;
bool EZoneMt = false, EPdZone = false, ESessLvl = false, EPm = false, EQual = true, EStats = false;
int EFvgKeep = 5, EZoneMax = 6, EMaxLiq = 12, EKeepSwept = 20, EStructKeep = 6;
double ELblNear = 0.25, EFvgMin = 0.3;
datetime g_raidT = 0;
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+
//| 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 : EMaxLiq + 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 >= EMaxLiq) // 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_raidT = g_t[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 > EKeepSwept) RemoveLq(k);
}
}
if(g_raidRej && i - g_raidBar <= InpMssWin)
{
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]);
}
}
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;
}
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) && EPm)
{
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; }
}
}
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;
}
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;
}
const int PN_ROWS = 8, 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);
}
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);
}
}
void Notify(const string msg)
{
string full = "Crypex ICT Models " + _Symbol + ": " + msg;
if(InpAlertPopup) Alert(full);
if(InpAlertPush) SendNotification(full);
if(InpAlertMail) SendMail("Crypex ICT Models", full);
}
//+------------------------------------------------------------------+
//| Crypex ICT Models: settings resolved from the Preset |
//+------------------------------------------------------------------+
void ResolvePreset()
{
int p = (int)InpPreset;
if(p == MD_P_CUSTOM)
{
EDispMark = InpDispMark; EShowBos = InpShowBos; EIfvg = InpIfvgOn; EVi = InpViOn; EBpr = InpBprOn; EMit = InpMitOn; ERb = InpRbOn;
EPb = InpPbOn; EVac = InpVacOn; EZoneMt = InpZoneMt; EPdZone = InpPdZoneOn; ESessLvl = InpSessLvlOn; EPm = InpPmOn;
EQual = InpFvgQual; EStats = InpSigOn && InpShowStats;
EFvgKeep = InpFvgKeep; EZoneMax = InpZoneMax; EMaxLiq = InpMaxLiq; EKeepSwept = InpKeepSwept; EStructKeep = InpStructKeep;
ELblNear = InpLblNear; EFvgMin = InpFvgMin;
return;
}
bool cl = (p == MD_P_CLEAN), fu = (p == MD_P_FULL);
EDispMark = !cl; EShowBos = !cl; EIfvg = !cl; EVi = !cl; EBpr = !cl; EMit = !cl; ERb = !cl; EPb = !cl; EVac = !cl;
EZoneMt = !cl; EPdZone = !cl; ESessLvl = !cl; EPm = fu; EQual = cl; EStats = InpSigOn && fu;
EFvgKeep = cl ? 5 : (fu ? 30 : 16);
EZoneMax = cl ? 6 : (fu ? 40 : 24);
EMaxLiq = cl ? 12 : (fu ? 60 : 40);
EKeepSwept = cl ? 20 : (fu ? 100 : 60);
EStructKeep = cl ? 6 : (fu ? 24 : 14);
ELblNear = cl ? 0.25 : (fu ? 0.6 : 0.3);
EFvgMin = cl ? 0.3 : (fu ? 0.05 : 0.1);
}
//+------------------------------------------------------------------+
//| model structures |
//+------------------------------------------------------------------+
struct TmSessM { string name; int a; int b; bool on; color col; bool active; double hi; double lo; int hiBar; int loBar;
double fHi; double fLo; int fHiBar; int fLoBar; };
struct TmFvg { double top; double bot; int dir; int bar; int state; double ext; bool uni; bool bp; string nm; };
struct TmZone { double top; double bot; int dir; int kind; string nm; int bar; int born; bool touched; bool prop; bool uni; };
struct TmSetup { int model; int dir; double entry; double sl; double tp; int born; int fillBar; int state; int score; double rr; double cost; int finBar; bool win; };
struct TmStruct { double px; int fromBar; int toBar; string txt; color col; };
struct TmMBox { int b0; int b1; double top; double bot; string txt; color col; };
struct TmDisp { int bar; int dir; };
TmSessM g_ms[5];
TmFvg g_fvg[]; int g_nFvg = 0;
TmZone g_zn[]; int g_nZn = 0;
TmSetup g_su[]; int g_nSu = 0;
TmStruct g_st[]; int g_nSt = 0;
TmMBox g_mb[]; int g_nMb = 0;
TmDisp g_dp[]; int g_nDp = 0;
int g_htf[];
int g_trend = 0, g_mssDir = 0, g_mssBar = -9999;
datetime g_mssT = 0;
bool g_swHiX = true, g_swLoX = true;
int g_dispBullBar = -9999, g_dispBearBar = -9999;
double g_eq = TM_NA, g_rngTop = TM_NA, g_rngBot = TM_NA, g_oteTop = TM_NA, g_oteBot = TM_NA, g_oteMid = TM_NA, g_oteSl = TM_NA, g_oteTp = TM_NA;
int g_oteDir = 0;
bool g_oteFired = false, g_dealOk = false;
int g_w22Dir = 0, g_w22Bar = 0;
double g_w22Px = TM_NA;
bool g_sbFired = false;
int g_evMss = 0, g_evBrk = 0, g_evFvg = 0, g_evUni = 0, g_evSig = 0;
double g_nfTop = TM_NA, g_nfBot = TM_NA, g_nfExt = TM_NA;
int g_nfDir = 0;
double g_uniTop = TM_NA, g_uniBot = TM_NA, g_uniFar = TM_NA;
int g_uniDir = 0, g_uniBar = -9999;
bool g_uniDone = true;
int g_capRej = 0;
int g_win[5], g_loss[5];
int g_sbA[3], g_sbB[3];
string Bolt() { return ShortToString(0x26A1); }
long AgeSec() { return (long)InpAgeMin * 60; }
int Cl1400(const int x) { return MathMax(x, g_n - 1 - 1400); }
//+------------------------------------------------------------------+
//| sessions (high / low tracking only; finished ones become levels) |
//+------------------------------------------------------------------+
void InitModels()
{
ResolvePreset();
string nm[5] = {"Asia", "London KZ", "NY AM KZ", "London Close KZ", "NY PM KZ"};
int dA[5] = {1200, 120, 420, 600, 810};
int dB[5] = {0, 300, 600, 720, 960};
string rg[5]; bool on[5]; color cl[5];
rg[0] = InpAsiaR; rg[1] = InpLonR; rg[2] = InpNyaR; rg[3] = InpLcR; rg[4] = InpNypR;
on[0] = InpAsiaOn; on[1] = InpLonOn; on[2] = InpNyaOn; on[3] = InpLcOn; on[4] = InpNypOn;
cl[0] = C_PURPLE; cl[1] = C_BLUE; cl[2] = C_BULL; cl[3] = C_ORANGE; cl[4] = C_PINK;
for(int k = 0; k < 5; k++)
{
g_ms[k].name = nm[k]; g_ms[k].on = on[k]; g_ms[k].col = cl[k];
ParseRange(rg[k], dA[k], dB[k], g_ms[k].a, g_ms[k].b);
}
ParseRange(InpSb1R, 180, 240, g_sbA[0], g_sbB[0]);
ParseRange(InpSb2R, 600, 660, g_sbA[1], g_sbB[1]);
ParseRange(InpSb3R, 840, 900, g_sbA[2], g_sbB[2]);
}
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, 5) || Fl(i, 6) || Fl(i, 7)); }
bool KzAny(const int i) { return (InpLonOn && Fl(i, 1)) || (InpNyaOn && Fl(i, 2)) || (InpLcOn && Fl(i, 3)) || (InpNypOn && Fl(i, 4)) || SbAny(i); }
bool SessUpd(const int k, const bool inS, const int i)
{
bool ended = false;
if(!g_ms[k].on) return false;
if(inS)
{
if(!g_ms[k].active)
{ g_ms[k].active = true; g_ms[k].hi = g_h[i]; g_ms[k].lo = g_l[i]; g_ms[k].hiBar = i; g_ms[k].loBar = i; }
else
{
if(g_h[i] > g_ms[k].hi) { g_ms[k].hi = g_h[i]; g_ms[k].hiBar = i; }
if(g_l[i] < g_ms[k].lo) { g_ms[k].lo = g_l[i]; g_ms[k].loBar = i; }
}
}
else if(g_ms[k].active)
{
g_ms[k].active = false; ended = true;
g_ms[k].fHi = g_ms[k].hi; g_ms[k].fLo = g_ms[k].lo; g_ms[k].fHiBar = g_ms[k].hiBar; g_ms[k].fLoBar = g_ms[k].loBar;
}
return ended;
}
void SessEnd(const int k, const int i)
{
if(!ESessLvl) return;
AddLiq(g_ms[k].fHi, g_ms[k].name + " H", 1, 1, g_ms[k].fHiBar, i);
AddLiq(g_ms[k].fLo, g_ms[k].name + " L", -1, 1, g_ms[k].fLoBar, i);
}
//+------------------------------------------------------------------+
//| series: ATR, New York clock, session flags, HTF bias |
//+------------------------------------------------------------------+
void BuildHtf()
{
for(int i = 0; i < g_n; i++) g_htf[i] = 0;
string sym = _Symbol;
if(PeriodSeconds(InpBiasTF) <= PeriodSeconds(_Period)) return; // the bias must come from a higher timeframe
int kOld = iBarShift(sym, InpBiasTF, g_t[0], false);
if(kOld < 0) return;
int kMax = kOld + 120;
while(kMax > 0 && iClose(sym, InpBiasTF, kMax) <= 0.0) kMax--;
if(kMax < 2) return;
double ema[];
ArrayResize(ema, kMax + 1);
double a = 2.0 / (InpBiasLen + 1.0);
for(int k = kMax; k >= 0; k--)
{
double c = iClose(sym, InpBiasTF, k);
ema[k] = (k == kMax) ? c : a * c + (1.0 - a) * ema[k + 1];
}
for(int i = 0; i < g_n; i++) // previous COMPLETED higher-timeframe bar only: cannot repaint
{
int k = iBarShift(sym, InpBiasTF, g_t[i], false);
if(k < 0) continue;
int p = k + 1;
if(p > kMax) continue;
g_htf[i] = (iClose(sym, InpBiasTF, p) > ema[p]) ? 1 : -1;
}
}
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); ArrayResize(g_htf, g_n);
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;
}
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;
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 < 5; k++) if(InSess(m, g_ms[k].a, g_ms[k].b)) f |= (1 << k);
for(int k = 0; k < 3; k++) if(InSess(m, g_sbA[k], g_sbB[k])) f |= (1 << (5 + k));
}
g_fl[i] = f;
}
BuildHtf();
}
//+------------------------------------------------------------------+
//| state |
//+------------------------------------------------------------------+
void ResetState()
{
g_nMk = 0; g_mkId = 0; g_nLq = 0; g_cnt = 0;
ArrayResize(g_mk, 0); ArrayResize(g_lq, 0);
g_nFvg = 0; g_nZn = 0; g_nSu = 0; g_nSt = 0; g_nMb = 0; g_nDp = 0;
ArrayResize(g_fvg, 0); ArrayResize(g_zn, 0); ArrayResize(g_su, 0); ArrayResize(g_st, 0); ArrayResize(g_mb, 0); ArrayResize(g_dp, 0);
for(int k = 0; k < 5; k++)
{ g_ms[k].active = false; g_ms[k].fHi = TM_NA; g_ms[k].fLo = TM_NA; g_ms[k].hi = TM_NA; g_ms[k].lo = TM_NA; g_ms[k].hiBar = 0; g_ms[k].loBar = 0; g_ms[k].fHiBar = 0; g_ms[k].fLoBar = 0; }
g_swHi = TM_NA; g_pSwHi = TM_NA; g_swLo = TM_NA; g_pSwLo = TM_NA; g_swHiBar = 0; g_swLoBar = 0;
g_swHiX = true; g_swLoX = true; g_trend = 0; g_mssDir = 0; g_mssBar = -9999; g_mssT = 0;
g_dispBullBar = -9999; g_dispBearBar = -9999;
g_raidDir = 0; g_raidBar = -9999; g_raidT = 0; 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_po3Bias = 0; g_po3DnBar = -9999; g_po3UpBar = -9999; g_po3DnExt = TM_NA; g_po3UpExt = TM_NA; g_moPx = TM_NA;
g_eq = TM_NA; g_rngTop = TM_NA; g_rngBot = TM_NA; g_oteDir = 0; g_oteTop = TM_NA; g_oteBot = TM_NA; g_oteMid = TM_NA; g_oteSl = TM_NA; g_oteTp = TM_NA;
g_oteFired = false; g_dealOk = false;
g_w22Dir = 0; g_w22Bar = 0; g_w22Px = TM_NA; g_sbFired = false;
g_nfTop = TM_NA; g_nfBot = TM_NA; g_nfExt = TM_NA; g_nfDir = 0;
g_uniTop = TM_NA; g_uniBot = TM_NA; g_uniFar = TM_NA; g_uniDir = 0; g_uniBar = -9999; g_uniDone = true;
g_capRej = 0;
for(int k = 0; k < 5; k++) { g_win[k] = 0; g_loss[k] = 0; }
g_evRaid = 0; g_evRun = 0; g_evMss = 0; g_evBrk = 0; g_evFvg = 0; g_evUni = 0; g_evSig = 0;
}
//+------------------------------------------------------------------+
//| displacement |
//+------------------------------------------------------------------+
bool DispBull(const int i)
{
double rg = g_h[i] - g_l[i], bd = MathAbs(g_c[i] - g_o[i]);
return g_c[i] > g_o[i] && rg > 0 && g_atr[i] > 0 && bd >= InpDispAtr * g_atr[i] && bd / rg >= InpDispBody;
}
bool DispBear(const int i)
{
double rg = g_h[i] - g_l[i], bd = MathAbs(g_c[i] - g_o[i]);
return g_c[i] < g_o[i] && rg > 0 && g_atr[i] > 0 && bd >= InpDispAtr * g_atr[i] && bd / rg >= InpDispBody;
}
bool MidBull(const int k) { return k >= 0 && g_atr[k] > 0 && g_c[k] > g_o[k] && MathAbs(g_c[k] - g_o[k]) >= InpFvgMid * g_atr[k]; }
bool MidBear(const int k) { return k >= 0 && g_atr[k] > 0 && g_c[k] < g_o[k] && MathAbs(g_c[k] - g_o[k]) >= InpFvgMid * g_atr[k]; }
void AddDisp(const int bar, const int dir)
{
ArrayResize(g_dp, g_nDp + 1);
g_dp[g_nDp].bar = bar; g_dp[g_nDp].dir = dir; g_nDp++;
while(g_nDp > 12) { for(int j = 0; j < g_nDp - 1; j++) g_dp[j] = g_dp[j + 1]; g_nDp--; }
}
//+------------------------------------------------------------------+
//| zones (blocks) |
//+------------------------------------------------------------------+
int AddZone(const double top, const double bot, const int d, const int kind, const int bar, const string nm, const int i)
{
bool ok = (kind == 1 && InpObOn) || (kind == 2 && InpBrkOn) || (kind == 3 && EMit) || (kind == 4 && ERb) || (kind == 5 && EPb) || (kind == 6 && EVac);
if(!ok || !(top > bot)) return -1;
for(int q = 0; q < g_nZn; q++) // the same candle can give the same block twice (repeated breaks of one swing): keep one
if(g_zn[q].kind == kind && g_zn[q].dir == d && g_zn[q].bar == bar) return q;
while(g_nZn >= EZoneMax) { for(int j = 0; j < g_nZn - 1; j++) g_zn[j] = g_zn[j + 1]; g_nZn--; }
ArrayResize(g_zn, g_nZn + 1);
g_zn[g_nZn].top = top; g_zn[g_nZn].bot = bot; g_zn[g_nZn].dir = d; g_zn[g_nZn].kind = kind; g_zn[g_nZn].nm = nm;
g_zn[g_nZn].bar = bar; g_zn[g_nZn].born = i; g_zn[g_nZn].touched = false; g_zn[g_nZn].prop = false; g_zn[g_nZn].uni = false;
g_nZn++;
return g_nZn - 1;
}
int FindOff(const int cBar, const bool wantBear, const int span, const int i)
{
int base = i - cBar, res = base;
bool found = false;
for(int k = 0; k <= span; k++)
{
int o = base + k;
if(o <= 1000 && !found)
{
int idx = i - o;
if(idx < 0) continue;
bool isb = g_c[idx] < g_o[idx];
if(isb == wantBear) { res = o; found = true; }
}
}
return res;
}
void BlockFromIdx(const int idx, const int d, double &t, double &b)
{
t = g_h[idx]; b = g_l[idx];
if(InpZoneMode == MD_Z_BODY) { t = MathMax(g_o[idx], g_c[idx]); b = MathMin(g_o[idx], g_c[idx]); }
if(InpZoneMode == MD_Z_OPEN)
{
if(d == 1) t = MathMax(g_o[idx], g_c[idx]);
else b = MathMin(g_o[idx], g_c[idx]);
}
}
void ZoneAt(const int cBar, const bool wantBear, const int d, const int kind, const string nm, const int i)
{
if(i - cBar > 900) return;
int o = FindOff(cBar, wantBear, 3, i);
int idx = i - o;
if(idx < 0) return;
double t, b;
BlockFromIdx(idx, d, t, b);
AddZone(t, b, d, kind, idx, nm, i);
}
void UpdZones(const int i)
{
double pqT[], pqB[]; int pqD[]; int np = 0;
for(int k = g_nZn - 1; k >= 0; k--)
{
bool dead = false;
if(g_zn[k].kind == 6) dead = (g_zn[k].dir == 1) ? g_l[i] <= g_zn[k].bot : g_h[i] >= g_zn[k].top;
else dead = (g_zn[k].dir == 1) ? g_c[i] < g_zn[k].bot : g_c[i] > g_zn[k].top;
if(dead)
{
for(int j = k; j < g_nZn - 1; j++) g_zn[j] = g_zn[j + 1];
g_nZn--;
continue;
}
if(!g_zn[k].touched)
{
bool tch = (g_zn[k].dir == 1) ? g_l[i] <= g_zn[k].top : g_h[i] >= g_zn[k].bot;
if(tch && i > g_zn[k].bar) g_zn[k].touched = true;
}
if(EPb && !g_zn[k].prop && g_zn[k].kind >= 1 && g_zn[k].kind <= 3 && i > g_zn[k].bar)
{
if(g_zn[k].dir == 1 && g_c[i] > g_o[i] && g_l[i] <= g_zn[k].top && g_l[i] >= g_zn[k].bot && g_c[i] > g_zn[k].top)
{
g_zn[k].prop = true;
ArrayResize(pqT, np + 1); ArrayResize(pqB, np + 1); ArrayResize(pqD, np + 1);
pqT[np] = MathMin(g_o[i], g_zn[k].top); pqB[np] = g_l[i]; pqD[np] = 1; np++;
}
if(g_zn[k].dir == -1 && g_c[i] < g_o[i] && g_h[i] >= g_zn[k].bot && g_h[i] <= g_zn[k].top && g_c[i] < g_zn[k].bot)
{
g_zn[k].prop = true;
ArrayResize(pqT, np + 1); ArrayResize(pqB, np + 1); ArrayResize(pqD, np + 1);
pqT[np] = g_h[i]; pqB[np] = MathMax(g_o[i], g_zn[k].bot); pqD[np] = -1; np++;
}
}
}
for(int q = 0; q < np; q++)
AddZone(pqT[q], pqB[q], pqD[q], 5, i, pqD[q] == 1 ? "Bull Propulsion" : "Bear Propulsion", i);
}
void Gaps(const int i)
{
if(!EVac || i <= 0 || g_atr[i] <= 0) return;
double g = g_o[i] - g_c[i - 1];
if(MathAbs(g) < InpGapAtr * g_atr[i]) return;
bool wkndGap = g_intra && (g_t[i] - g_t[i - 1]) >= 24 * 3600;
bool dayChg = g_day[i] != g_day[i - 1];
bool ndog = g_intra && g_mn[i] >= 1020 && g_mn[i - 1] < 1020 && !dayChg;
string nm = wkndGap ? "NWOG" : (ndog ? "NDOG" : "Vacuum");
AddZone(MathMax(g_o[i], g_c[i - 1]), MathMin(g_o[i], g_c[i - 1]), g > 0 ? 1 : -1, 6, i - 1, nm, i);
}
//+------------------------------------------------------------------+
//| fair value gaps |
//+------------------------------------------------------------------+
void AddMisc(const int b0, const int b1, const double top, const double bot, const string txt, const color c)
{
ArrayResize(g_mb, g_nMb + 1);
g_mb[g_nMb].b0 = b0; g_mb[g_nMb].b1 = b1; g_mb[g_nMb].top = top; g_mb[g_nMb].bot = bot; g_mb[g_nMb].txt = txt; g_mb[g_nMb].col = c;
g_nMb++;
while(g_nMb > 30) { for(int j = 0; j < g_nMb - 1; j++) g_mb[j] = g_mb[j + 1]; g_nMb--; }
}
int NewFvg(const double top, const double bot, const int d, const double ext, const int i)
{
while(g_nFvg >= EFvgKeep) { for(int j = 0; j < g_nFvg - 1; j++) g_fvg[j] = g_fvg[j + 1]; g_nFvg--; }
ArrayResize(g_fvg, g_nFvg + 1);
g_fvg[g_nFvg].top = top; g_fvg[g_nFvg].bot = bot; g_fvg[g_nFvg].dir = d; g_fvg[g_nFvg].bar = i; g_fvg[g_nFvg].state = 0;
g_fvg[g_nFvg].ext = ext; g_fvg[g_nFvg].uni = false; g_fvg[g_nFvg].bp = false; g_fvg[g_nFvg].nm = "FVG";
g_nFvg++;
return g_nFvg - 1;
}
void FvgBpr(const int fi, const int dirWanted, const double minSz, const int i)
{
if(!EBpr) return;
for(int k = 0; k < g_nFvg; k++)
{
if(k == fi) continue;
if(g_fvg[k].dir == dirWanted && g_fvg[k].state == 0 && !g_fvg[k].bp && i - g_fvg[k].bar <= 40)
{
double oT = MathMin(g_fvg[fi].top, g_fvg[k].top), oB = MathMax(g_fvg[fi].bot, g_fvg[k].bot);
if(oT - oB >= minSz)
{
g_fvg[fi].bp = true; g_fvg[k].bp = true;
AddMisc(MathMin(g_fvg[k].bar, i) - 2, i + 6, oT, oB, "BPR", C_PURPLE);
}
}
}
}
void FvgDetect(const int i)
{
if(!InpFvgOn || i < 2 || g_atr[i] <= 0) return;
double atr = g_atr[i], minSz = EFvgMin * atr;
if(g_l[i] > g_h[i - 2] && (g_l[i] - g_h[i - 2]) >= minSz && (!EQual || (MidBull(i - 1) && g_c[i - 1] > g_h[i - 2])))
{
int fi = NewFvg(g_l[i], g_h[i - 2], 1, MathMin(g_l[i], MathMin(g_l[i - 1], g_l[i - 2])), i);
g_evFvg = 1; g_nfTop = g_fvg[fi].top; g_nfBot = g_fvg[fi].bot; g_nfExt = g_fvg[fi].ext; g_nfDir = 1;
FvgBpr(fi, -1, minSz, i);
}
if(g_h[i] < g_l[i - 2] && (g_l[i - 2] - g_h[i]) >= minSz && (!EQual || (MidBear(i - 1) && g_c[i - 1] < g_l[i - 2])))
{
int fi = NewFvg(g_l[i - 2], g_h[i], -1, MathMax(g_h[i], MathMax(g_h[i - 1], g_h[i - 2])), i);
g_evFvg = -1; g_nfTop = g_fvg[fi].top; g_nfBot = g_fvg[fi].bot; g_nfExt = g_fvg[fi].ext; g_nfDir = -1;
FvgBpr(fi, 1, minSz, i);
}
if(EVi) // volume imbalance (bodies gap, wicks overlap)
{
double vs = 0.05 * atr;
if(MathMin(g_o[i], g_c[i]) - MathMax(g_o[i - 1], g_c[i - 1]) >= vs && g_l[i] <= g_h[i - 1])
AddMisc(i - 1, i + 8, MathMin(g_o[i], g_c[i]), MathMax(g_o[i - 1], g_c[i - 1]), "VI", C_BULL);
if(MathMin(g_o[i - 1], g_c[i - 1]) - MathMax(g_o[i], g_c[i]) >= vs && g_h[i] >= g_l[i - 1])
AddMisc(i - 1, i + 8, MathMin(g_o[i - 1], g_c[i - 1]), MathMax(g_o[i], g_c[i]), "VI", C_BEAR);
}
}
void FvgUpdate(const int i)
{
for(int k = g_nFvg - 1; k >= 0; k--)
{
bool kill = false;
if(i > g_fvg[k].bar)
{
if(g_fvg[k].state == 0)
{
if(g_fvg[k].dir == 1) // bullish gap is filled from above
{
if(g_l[i] < g_fvg[k].bot)
{
if(g_c[i] < g_fvg[k].bot && EIfvg) { g_fvg[k].state = 1; g_fvg[k].dir = -1; g_fvg[k].nm = "IFVG"; }
else kill = true;
}
else if(InpFvgShrink && g_l[i] < g_fvg[k].top) g_fvg[k].top = MathMax(g_l[i], g_fvg[k].bot);
}
else // bearish gap is filled from below
{
if(g_h[i] > g_fvg[k].top)
{
if(g_c[i] > g_fvg[k].top && EIfvg) { g_fvg[k].state = 1; g_fvg[k].dir = 1; g_fvg[k].nm = "IFVG"; }
else kill = true;
}
else if(InpFvgShrink && g_h[i] > g_fvg[k].bot) g_fvg[k].bot = MathMin(g_h[i], g_fvg[k].top);
}
}
else
{
if(g_fvg[k].dir == -1 && g_c[i] > g_fvg[k].top) kill = true; // inverted FVG dies when price closes back through
if(g_fvg[k].dir == 1 && g_c[i] < g_fvg[k].bot) kill = true;
}
}
if(kill)
{
for(int j = k; j < g_nFvg - 1; j++) g_fvg[j] = g_fvg[j + 1];
g_nFvg--;
}
}
}
//+------------------------------------------------------------------+
//| unicorn |
//+------------------------------------------------------------------+
void Unicorn(const int i)
{
if(!InpUniOn) return;
for(int a = 0; a < g_nZn; a++)
{
if(!(g_zn[a].kind == 2 && !g_zn[a].uni && g_mssDir == g_zn[a].dir && g_zn[a].born >= g_mssBar && (g_t[i] - g_mssT) <= AgeSec())) continue;
for(int b = 0; b < g_nFvg; b++)
{
if(g_zn[a].uni) break;
if(!(!g_fvg[b].uni && g_fvg[b].state == 0 && g_fvg[b].dir == g_zn[a].dir && g_fvg[b].bar >= g_mssBar - 3 && g_fvg[b].bar <= g_mssBar + InpUniWin)) continue;
double oT = MathMin(g_zn[a].top, g_fvg[b].top), oB = MathMax(g_zn[a].bot, g_fvg[b].bot);
if(oT > oB)
{
g_zn[a].uni = true; g_fvg[b].uni = true;
AddMisc(MathMin(g_zn[a].bar, g_fvg[b].bar), i + 10, oT, oB, "Unicorn", g_fvg[b].dir == 1 ? C_CYAN : C_ORANGE);
g_evUni = g_fvg[b].dir; g_uniTop = oT; g_uniBot = oB; g_uniDir = g_fvg[b].dir; g_uniBar = i; g_uniDone = false;
g_uniFar = g_fvg[b].dir == 1 ? g_zn[a].bot : g_zn[a].top;
}
}
}
}
//+------------------------------------------------------------------+
//| structure: swings, dealing range, MSS / BOS |
//+------------------------------------------------------------------+
void ComputeDealing()
{
g_dealOk = false;
if(IsNa(g_swHi) || IsNa(g_swLo) || !(g_swHi > g_swLo)) return;
g_dealOk = true;
double r = g_swHi - g_swLo;
g_eq = g_swLo + r / 2.0; g_rngTop = g_swHi; g_rngBot = g_swLo;
g_oteFired = false;
if(g_swLoBar < g_swHiBar)
{
g_oteDir = 1; g_oteTop = g_swHi - InpOteLo * r; g_oteBot = g_swHi - InpOteHi * r; g_oteMid = g_swHi - 0.705 * r;
g_oteSl = g_swLo; g_oteTp = g_swHi + 0.27 * r;
}
else
{
g_oteDir = -1; g_oteBot = g_swLo + InpOteLo * r; g_oteTop = g_swLo + InpOteHi * r; g_oteMid = g_swLo + 0.705 * r;
g_oteSl = g_swHi; g_oteTp = g_swLo - 0.27 * r;
}
}
void PivotsM(const int i)
{
int len = InpSwLen, c = i - len;
if(c - len < 0) return;
bool ph = PivotHigh(c, len), pl = PivotLow(c, len), changed = false;
double atr = g_atr[i];
if(ph)
{
g_pSwHi = g_swHi; g_swHi = g_h[c]; g_swHiBar = c; g_swHiX = false; changed = true;
if(InpEqOn && !IsNa(g_pSwHi) && atr > 0 && MathAbs(g_swHi - g_pSwHi) <= InpEqTol * atr)
AddLiq(MathMax(g_swHi, g_pSwHi), "EQH", 1, 5, g_swHiBar, i);
if(ERb)
{
double wk = g_h[c] - MathMax(g_o[c], g_c[c]), cr = g_h[c] - g_l[c];
if(cr > 0 && wk >= InpRbPct * cr && wk >= InpRbAtr * atr) AddZone(g_h[c], MathMax(g_o[c], g_c[c]), -1, 4, c, "Bear Rejection", i);
}
}
if(pl)
{
g_pSwLo = g_swLo; g_swLo = g_l[c]; g_swLoBar = c; g_swLoX = false; changed = true;
if(InpEqOn && !IsNa(g_pSwLo) && atr > 0 && MathAbs(g_swLo - g_pSwLo) <= InpEqTol * atr)
AddLiq(MathMin(g_swLo, g_pSwLo), "EQL", -1, 5, g_swLoBar, i);
if(ERb)
{
double wk = MathMin(g_o[c], g_c[c]) - g_l[c], cr = g_h[c] - g_l[c];
if(cr > 0 && wk >= InpRbPct * cr && wk >= InpRbAtr * atr) AddZone(MathMin(g_o[c], g_c[c]), g_l[c], 1, 4, c, "Bull Rejection", i);
}
}
if(changed) ComputeDealing();
}
void AddStruct(const double px, const int fromBar, const int toBar, const string txt, const color c)
{
ArrayResize(g_st, g_nSt + 1);
g_st[g_nSt].px = px; g_st[g_nSt].fromBar = fromBar; g_st[g_nSt].toBar = toBar; g_st[g_nSt].txt = txt; g_st[g_nSt].col = c;
g_nSt++;
while(g_nSt > EStructKeep) { for(int j = 0; j < g_nSt - 1; j++) g_st[j] = g_st[j + 1]; g_nSt--; }
}
void Breaks(const int i)
{
if(!IsNa(g_swHi) && !g_swHiX && g_c[i] > g_swHi)
{
g_swHiX = true; g_evBrk = 1;
bool dOk = i - g_dispBullBar <= 3;
bool isMss = g_trend == -1 && (dOk || !InpMssNeedD);
string tx = isMss ? (dOk ? "MSS " + Bolt() : "MSS") : (g_trend == -1 ? "CHoCH" : "BOS");
if((isMss && InpShowMss) || (!isMss && EShowBos)) AddStruct(g_swHi, g_swHiBar, i, tx, C_BULL);
if(isMss)
{
g_mssDir = 1; g_mssBar = i; g_mssT = g_t[i]; g_evMss = 1;
bool swept = !IsNa(g_pSwLo) && !IsNa(g_swLo) && g_swLo < g_pSwLo;
if(swept) ZoneAt(g_swHiBar, false, 1, 2, "Bull Breaker", i);
else ZoneAt(g_swLoBar, true, 1, 3, "Bull Mitigation", i);
}
else if(g_trend == 1 || g_trend == 0) ZoneAt(g_swLoBar, true, 1, 1, "Bull OB", i);
g_trend = 1;
}
if(!IsNa(g_swLo) && !g_swLoX && g_c[i] < g_swLo)
{
g_swLoX = true; g_evBrk = -1;
bool dOk = i - g_dispBearBar <= 3;
bool isMss = g_trend == 1 && (dOk || !InpMssNeedD);
string tx = isMss ? (dOk ? "MSS " + Bolt() : "MSS") : (g_trend == 1 ? "CHoCH" : "BOS");
if((isMss && InpShowMss) || (!isMss && EShowBos)) AddStruct(g_swLo, g_swLoBar, i, tx, C_BEAR);
if(isMss)
{
g_mssDir = -1; g_mssBar = i; g_mssT = g_t[i]; g_evMss = -1;
bool swept = !IsNa(g_pSwHi) && !IsNa(g_swHi) && g_swHi > g_pSwHi;
if(swept) ZoneAt(g_swLoBar, true, -1, 2, "Bear Breaker", i);
else ZoneAt(g_swHiBar, false, -1, 3, "Bear Mitigation", i);
}
else if(g_trend == -1 || g_trend == 0) ZoneAt(g_swHiBar, false, -1, 1, "Bear OB", i);
g_trend = -1;
}
}
//+------------------------------------------------------------------+
//| models and setups |
//+------------------------------------------------------------------+
string ModelName(const int m) { return m == 1 ? "ICT 2022" : (m == 2 ? "Silver Bullet" : (m == 3 ? "Unicorn" : "OTE")); }
int Score(const int d, const bool kz, const int i)
{
int s = 0;
if(kz) s++;
if(g_raidDir == d && (g_t[i] - g_raidT) <= AgeSec()) s++;
if(g_mssDir == d && (g_t[i] - g_mssT) <= AgeSec()) s++;
if((d == 1 && i - g_dispBullBar <= 20) || (d == -1 && i - g_dispBearBar <= 20)) s++;
if(g_htf[i] == d) s++;
if(!IsNa(g_eq) && ((d == 1 && g_c[i] < g_eq) || (d == -1 && g_c[i] > g_eq))) s++;
return s;
}
double TargetPx(const int d, const double entry, const double minDist)
{
double best = TM_NA;
for(int k = 0; k < g_nLq; k++)
{
if(g_lq[k].swept || g_lq[k].dir != d) continue;
if(d == 1 && g_lq[k].price >= entry + minDist && (IsNa(best) || g_lq[k].price < best)) best = g_lq[k].price;
if(d == -1 && g_lq[k].price <= entry - minDist && (IsNa(best) || g_lq[k].price > best)) best = g_lq[k].price;
}
return best;
}
bool NewSetup(const int model, const int d, const double entry, const double sl, const bool inKz, const bool filled, const int i)
{
if(!InpSigOn) return false;
double risk = MathAbs(entry - sl);
bool dirOk = (d == 1) ? sl < entry : sl > entry;
int sc = Score(d, inKz, i);
bool htfOk = !InpHtfReq || g_htf[i] == d;
if(!(risk > 0 && dirOk && htfOk && sc >= InpMinScore)) return false;
double tp = TargetPx(d, entry, risk * InpMinRR);
if(IsNa(tp)) tp = entry + d * risk * InpFbRR;
double rr = MathAbs(tp - entry) / risk;
if(rr < InpMinRR - 0.0001) return false;
int oi = 0; // only FINISHED setups may be dropped; open / pending trades never are
while(g_nSu >= 24 && oi < g_nSu)
{
if(g_su[oi].state == 2) { for(int j = oi; j < g_nSu - 1; j++) g_su[j] = g_su[j + 1]; g_nSu--; }
else oi++;
}
if(g_nSu >= 40) { g_capRej++; return false; }
ArrayResize(g_su, g_nSu + 1);
g_su[g_nSu].model = model; g_su[g_nSu].dir = d; g_su[g_nSu].entry = entry; g_su[g_nSu].sl = sl; g_su[g_nSu].tp = tp;
g_su[g_nSu].born = i; g_su[g_nSu].fillBar = filled ? i : 0; g_su[g_nSu].state = filled ? 1 : 0; g_su[g_nSu].score = sc;
g_su[g_nSu].rr = rr; g_su[g_nSu].cost = InpCostAtr * g_atr[i]; g_su[g_nSu].finBar = -1; g_su[g_nSu].win = false;
g_nSu++;
g_evSig = d;
return true;
}
void FinishSetup(const int k, const bool win, const int i)
{
g_su[k].state = 2; g_su[k].finBar = i; g_su[k].win = win;
if(win) g_win[g_su[k].model]++; else g_loss[g_su[k].model]++;
}
void UpdSetups(const int i)
{
for(int k = g_nSu - 1; k >= 0; k--)
{
if(g_su[k].state == 0)
{
bool cancel = i - g_su[k].born > InpExpBars;
if(g_su[k].dir == 1 && (g_c[i] < g_su[k].sl || g_h[i] >= g_su[k].tp)) cancel = true;
if(g_su[k].dir == -1 && (g_c[i] > g_su[k].sl || g_l[i] <= g_su[k].tp)) cancel = true;
if(cancel)
{
for(int j = k; j < g_nSu - 1; j++) g_su[j] = g_su[j + 1];
g_nSu--;
}
else if(i > g_su[k].born && ((g_su[k].dir == 1 && g_l[i] <= g_su[k].entry) || (g_su[k].dir == -1 && g_h[i] >= g_su[k].entry)))
{
g_su[k].state = 1; g_su[k].fillBar = i;
bool slNow = (g_su[k].dir == 1) ? g_l[i] <= g_su[k].sl + g_su[k].cost : g_h[i] >= g_su[k].sl - g_su[k].cost;
if(slNow) FinishSetup(k, false, i);
}
}
else if(g_su[k].state == 1 && i > g_su[k].fillBar)
{
bool slHit = (g_su[k].dir == 1) ? g_l[i] <= g_su[k].sl + g_su[k].cost : g_h[i] >= g_su[k].sl - g_su[k].cost; // pessimistic: the stop is checked first
bool tpHit = (g_su[k].dir == 1) ? g_h[i] >= g_su[k].tp + g_su[k].cost : g_l[i] <= g_su[k].tp - g_su[k].cost;
if(slHit) FinishSetup(k, false, i);
else if(tpHit) FinishSetup(k, true, i);
}
}
}
double EntryPx(const double top, const double bot, const int d)
{
if(InpEntryMode == MD_E_EDGE) return d == 1 ? top : bot;
return (top + bot) / 2.0;
}
void ModelsBar(const int i)
{
double buf = InpSlBuf * g_atr[i];
bool kz = KzAny(i);
if(InpM22On) // ICT 2022: raid -> MSS -> FVG
{
if(g_evMss != 0 && g_raidDir == g_evMss && i - g_raidBar <= InpMssWin && (g_t[i] - g_raidT) <= AgeSec() && (!InpKzOnly || kz))
{ g_w22Dir = g_evMss; g_w22Bar = i; g_w22Px = g_raidPx; }
if(g_w22Dir != 0)
{
if(i - g_w22Bar > 3) g_w22Dir = 0;
else
{
bool done = false;
for(int k = g_nFvg - 1; k >= 0 && !done; k--)
{
if(g_fvg[k].dir == g_w22Dir && g_fvg[k].state == 0 && g_fvg[k].bar >= g_w22Bar)
{
double en = EntryPx(g_fvg[k].top, g_fvg[k].bot, g_fvg[k].dir);
double sl = g_w22Dir == 1 ? g_w22Px - buf : g_w22Px + buf;
if(NewSetup(1, g_w22Dir, en, sl, kz, false, i)) { g_w22Dir = 0; done = true; } // the chain ends only when a setup was really made
}
}
}
}
}
if(!SbAny(i)) g_sbFired = false; // Silver Bullet
if(InpMSbOn && SbAny(i) && !g_sbFired && g_evFvg != 0)
{
int d = g_nfDir;
bool aligned = (g_mssDir == d && (g_t[i] - g_mssT) <= AgeSec()) || (g_raidDir == d && (g_t[i] - g_raidT) <= AgeSec()) || !InpSbBias;
if(aligned)
{
double en = EntryPx(g_nfTop, g_nfBot, d);
double sl = d == 1 ? g_nfExt - buf : g_nfExt + buf;
if(NewSetup(2, d, en, sl, true, false, i)) g_sbFired = true;
}
}
if(InpMUniOn && g_uniDir != 0 && !g_uniDone && i - g_uniBar <= 3 && (!InpKzOnly || kz)) // Unicorn: retried for 3 bars, ONE setup at most
{
int d = g_uniDir;
double en = EntryPx(g_uniTop, g_uniBot, d);
double sl = d == 1 ? g_uniFar - buf : g_uniFar + buf;
if(NewSetup(3, d, en, sl, kz, false, i)) g_uniDone = true;
}
if(InpMOteOn && g_oteDir != 0 && !g_oteFired && (!InpKzOnly || kz)) // OTE: a limit order at the signal candle's close
{
bool origin = (InpOteSL == MD_O_ORIGIN);
if(g_oteDir == 1 && g_l[i] <= g_oteTop && g_c[i] > g_oteBot && g_c[i] > g_oteMid && g_c[i] > g_o[i])
{
double slv = origin ? g_oteSl : MathMin(g_l[i], g_oteBot);
if(NewSetup(4, 1, g_c[i], slv - buf, kz, false, i)) g_oteFired = true;
}
if(g_oteDir == -1 && g_h[i] >= g_oteBot && g_c[i] < g_oteTop && g_c[i] < g_oteMid && g_c[i] < g_o[i])
{
double slv = origin ? g_oteSl : MathMax(g_h[i], g_oteTop);
if(NewSetup(4, -1, g_c[i], slv + buf, kz, false, i)) g_oteFired = true;
}
}
UpdSetups(i);
}
//+------------------------------------------------------------------+
//| one confirmed bar (same order as the Pine main block) |
//+------------------------------------------------------------------+
void ProcessBar(const int i)
{
g_evRaid = 0; g_evRun = 0; g_evMss = 0; g_evBrk = 0; g_evFvg = 0; g_evUni = 0; g_evSig = 0;
if(DispBull(i)) { g_dispBullBar = i; if(EDispMark) AddDisp(i, 1); }
if(DispBear(i)) { g_dispBearBar = i; if(EDispMark) AddDisp(i, -1); }
if(g_intra)
for(int k = 0; k < 5; k++)
{
bool ended = SessUpd(k, Fl(i, k), i);
if(ended) SessEnd(k, i);
}
PeriodsBar(i);
FvgDetect(i);
FvgUpdate(i);
PivotsM(i);
ChkLiq(i);
Breaks(i);
UpdZones(i);
Gaps(i);
Unicorn(i);
ModelsBar(i);
}
//+------------------------------------------------------------------+
//| drawing |
//+------------------------------------------------------------------+
color ZoneCol(const int kind, const int d)
{
if(kind == 2) return d == 1 ? C_CYAN : C_ORANGE;
if(kind == 3) return d == 1 ? C_PURPLE : C_PINK;
if(kind == 4) return C_YELLOW;
if(kind == 5) return C_LIME;
if(kind == 6) return C_GRAY;
return d == 1 ? InpObBullC : InpObBearC;
}
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;
}
double g_near = 0.0;
void DrawFvgs()
{
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
for(int k = 0; k < g_nFvg; k++)
{
color c = g_fvg[k].dir == 1 ? (g_fvg[k].state == 1 ? C_CYAN : InpFvgBullC) : (g_fvg[k].state == 1 ? C_ORANGE : InpFvgBearC);
datetime t1 = g_t[MathMax(0, g_fvg[k].bar - 2)];
MkRect("FV", t1, g_fvg[k].top, xe, g_fvg[k].bot, Tint(c, 0.14), true);
MkRect("FB", t1, g_fvg[k].top, xe, g_fvg[k].bot, Tint(c, 0.6), false);
double mid = (g_fvg[k].top + g_fvg[k].bot) / 2.0;
if(InpFvgCE) MkLine("FC", t1, mid, xe, mid, Tint(c, 0.7), STYLE_DOT, 1);
if(MathAbs(mid - g_c[g_n - 1]) <= g_near) AddItem(g_fvg[k].nm, c, mid);
}
}
void DrawZones()
{
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
for(int k = 0; k < g_nZn; k++)
{
color c = ZoneCol(g_zn[k].kind, g_zn[k].dir);
datetime t1 = g_t[MathMax(0, g_zn[k].bar)];
MkRect("ZF", t1, g_zn[k].top, xe, g_zn[k].bot, Tint(c, g_zn[k].touched ? 0.08 : 0.15), true);
MkRect("ZB", t1, g_zn[k].top, xe, g_zn[k].bot, Tint(c, 0.7), false);
double mid = (g_zn[k].top + g_zn[k].bot) / 2.0;
if(EZoneMt) MkLine("ZM", t1, mid, xe, mid, Tint(c, 0.6), STYLE_DOT, 1);
if(MathAbs(mid - g_c[g_n - 1]) <= g_near) AddItem(g_zn[k].nm, c, mid);
}
}
void DrawMisc()
{
for(int k = 0; k < g_nMb; k++)
{
int b0 = MathMax(0, g_mb[k].b0);
datetime t1 = g_t[b0], t2 = TimeAt(g_mb[k].b1);
bool uni = (g_mb[k].txt == "Unicorn");
MkRect("MB", t1, g_mb[k].top, t2, g_mb[k].bot, Tint(g_mb[k].col, uni ? 0.30 : 0.22), true);
MkRect("MR", t1, g_mb[k].top, t2, g_mb[k].bot, uni ? clrWhite : Tint(g_mb[k].col, 0.9), false);
MkText("MT", t1 + (datetime)((t2 - t1) / 2), (g_mb[k].top + g_mb[k].bot) / 2.0, g_mb[k].txt, uni ? clrWhite : g_mb[k].col, ANCHOR_CENTER);
}
}
void DrawStructure()
{
for(int k = 0; k < g_nSt; k++)
{
int f = Cl1400(g_st[k].fromBar);
MkLine("SL", g_t[f], g_st[k].px, g_t[g_st[k].toBar], g_st[k].px, g_st[k].col, STYLE_DASH, 1);
MkText("ST", g_t[(f + g_st[k].toBar) / 2], g_st[k].px, g_st[k].txt, g_st[k].col, ANCHOR_LOWER);
}
}
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);
if(MathAbs(g_lq[k].price - g_c[g_n - 1]) <= g_near * 1.5) AddItem(g_lq[k].name + " " + Fp(g_lq[k].price), c, g_lq[k].price);
}
}
}
void DrawDealing()
{
if(!g_dealOk) return;
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
datetime x0 = g_t[Cl1400(MathMin(g_swHiBar, g_swLoBar))];
if(EPdZone)
{
MkRect("PD", x0, g_swHi, xe, g_eq, Tint(C_BEAR, 0.06), true);
MkRect("PD", x0, g_eq, xe, g_swLo, Tint(C_BULL, 0.06), true);
MkLine("PE", x0, g_eq, xe, g_eq, Tint(C_GRAY, 0.8), STYLE_DASH, 1);
AddItem("Premium", C_BEAR, g_swHi);
AddItem("EQ " + Fp(g_eq), C_GRAY, g_eq);
AddItem("Discount", C_BULL, g_swLo);
}
if(InpOteOn)
{
color oc = g_oteDir == 1 ? C_BULL : C_BEAR;
datetime xo = g_t[Cl1400(MathMax(g_swHiBar, g_swLoBar))];
MkRect("OF", xo, g_oteTop, xe, g_oteBot, Tint(oc, 0.18), true);
MkRect("OB", xo, g_oteTop, xe, g_oteBot, Tint(oc, 0.6), false);
MkLine("OM", xo, g_oteMid, xe, g_oteMid, Tint(oc, 0.8), STYLE_DOT, 1);
AddItem("OTE 70.5%", oc, g_oteMid);
}
}
void DrawMarks()
{
for(int k = 0; k < g_nMk; k++)
{
if(!g_mk[k].alive) continue;
int cnt = 0; // several markers on the same bar are stacked
for(int j = 0; j < k; j++) if(g_mk[j].alive && g_mk[j].bar == g_mk[k].bar) cnt++;
double off = cnt * 0.7 * g_atr[MathMin(g_mk[k].bar, g_n - 1)];
double y = g_mk[k].dir == 1 ? g_mk[k].price + off : g_mk[k].price - off;
MkText("MK", g_t[g_mk[k].bar], y, g_mk[k].text, g_mk[k].col, g_mk[k].dir == 1 ? ANCHOR_LOWER : ANCHOR_UPPER);
}
for(int k = 0; k < g_nDp; k++)
MkText("DP", g_t[g_dp[k].bar], (g_o[g_dp[k].bar] + g_c[g_dp[k].bar]) / 2.0, Bolt(), g_dp[k].dir == 1 ? C_BULL : C_BEAR, ANCHOR_CENTER);
}
void DrawSetups()
{
if(!InpSigOn || !InpShowSetups) return;
datetime xe = TimeAt(g_n - 1 + InpLblPad - 1);
for(int k = 0; k < g_nSu; k++)
{
datetime t0 = g_t[g_su[k].born];
datetime t1 = (g_su[k].state == 2) ? g_t[g_su[k].finBar] : xe;
bool fin = (g_su[k].state == 2);
MkLine("SE", t0, g_su[k].entry, t1, g_su[k].entry, fin ? Tint(C_GRAY, 0.8) : C_BLUE, STYLE_SOLID, 2);
MkLine("SS", t0, g_su[k].sl, t1, g_su[k].sl, (fin && !g_su[k].win) ? Tint(C_GRAY, 0.3) : C_BEAR, STYLE_SOLID, 1);
MkLine("SU", t0, g_su[k].tp, t1, g_su[k].tp, (fin && g_su[k].win) ? Tint(C_GRAY, 0.3) : C_BULL, STYLE_SOLID, 1);
string arrow = g_su[k].dir == 1 ? " " + Up() + " " : " " + Dn() + " ";
if(fin)
MkText("SF", t1, g_su[k].entry, ModelName(g_su[k].model) + arrow + (g_su[k].win ? "TP " + Ck() : "SL x"), g_su[k].win ? C_BULL : C_BEAR, ANCHOR_LEFT);
else
{
string lb = ModelName(g_su[k].model) + (g_su[k].dir == 1 ? " BUY " : " SELL ") + "*" + IntegerToString(g_su[k].score) + "/6 RR " + DoubleToString(g_su[k].rr, 1);
if(g_su[k].state == 1) lb = ModelName(g_su[k].model) + arrow + "filled *" + IntegerToString(g_su[k].score);
AddItem(lb, g_su[k].dir == 1 ? C_BULL : C_BEAR, g_su[k].entry);
}
}
}
//+------------------------------------------------------------------+
//| panel |
//+------------------------------------------------------------------+
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); }
string TfName(const ENUM_TIMEFRAMES tf)
{
int m = PeriodSeconds(tf) / 60;
if(m < 60) return "M" + IntegerToString(m);
if(m < 1440) return "H" + IntegerToString(m / 60);
return "D" + IntegerToString(m / 1440);
}
void UpdatePanel()
{
if(!InpShowPanel)
{
ObjectsDeleteAll(0, PFX + "PN");
return;
}
if(g_n < 3) return;
ChooseCorner();
int L = g_n - 2;
string actNm = "Outside killzones";
color actCol = C_GRAY;
for(int k = 0; k < 5; k++) if(g_ms[k].on && g_ms[k].active) { actNm = g_ms[k].name; actCol = g_ms[k].col; }
if(SbAny(L)) actNm += " + Silver Bullet";
string rd = g_raidDir == 0 ? "-" : g_raidName + (g_raidRej ? " (raid)" : " (run)") + " - " + IntegerToString(L - g_raidBar) + " bars ago";
string ms = g_mssDir == 0 ? "-" : DirTxt(g_mssDir) + " - " + IntegerToString(L - g_mssBar) + " bars ago";
string pdTxt = "-";
color pdCol = C_GRAY;
if(!IsNa(g_rngTop) && g_rngTop > g_rngBot)
{
double pos = (g_c[L] - g_rngBot) / (g_rngTop - g_rngBot) * 100.0;
if(pos > 100.0) { pdTxt = "Above range (new highs)"; pdCol = C_BEAR; }
else if(pos < 0.0) { pdTxt = "Below range (new lows)"; pdCol = C_BULL; }
else { pdTxt = (pos >= 50.0 ? "Premium " : "Discount ") + IntegerToString((int)pos) + "%"; pdCol = pos >= 50.0 ? C_BEAR : C_BULL; }
}
string mode = "Analysis only (no signals)";
color modeCol = C_GRAY;
if(InpSigOn)
{
int nOpen = 0;
for(int k = 0; k < g_nSu; k++) if(g_su[k].state < 2) nOpen++;
mode = "Setups: " + IntegerToString(nOpen) + " open";
if(EStats)
{
mode = "";
for(int m = 1; m <= 4; m++) mode += ModelName(m) + " " + IntegerToString(g_win[m]) + "W/" + IntegerToString(g_loss[m]) + "L ";
}
if(g_capRej > 0) mode += " (rejected " + IntegerToString(g_capRej) + ": 40 open)";
modeCol = C_ORANGE;
}
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);
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 Models", g_intra ? "" : "use <= H4", C_ORANGE);
PanelRow(1, "Now", actNm, actCol);
PanelRow(2, "HTF bias " + TfName(InpBiasTF) + "/" + IntegerToString(InpBiasLen), DirTxt(g_htf[L]), DirCol(g_htf[L]));
PanelRow(3, "Last liquidity", rd, DirCol(g_raidDir));
PanelRow(4, "Last MSS", ms, DirCol(g_mssDir));
PanelRow(5, "Dealing range", pdTxt, pdCol);
PanelRow(6, "Mode", mode, modeCol);
PanelRow(7, "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);
}
//+------------------------------------------------------------------+
//| alerts |
//+------------------------------------------------------------------+
void FireAlerts()
{
string names[10] = {"Buy-side liquidity raided (bearish expectation)", "Sell-side liquidity raided (bullish expectation)",
"Liquidity run (level broken and held, not a raid)", "Bullish MSS", "Bearish MSS", "New bullish FVG", "New bearish FVG",
"Unicorn zone formed", "BUY setup", "SELL setup"};
for(int k = 0; k < 10; k++) if(g_alLast[k]) Notify(names[k]);
}
void FlagAlerts()
{
for(int k = 0; k < 10; k++) g_alLast[k] = false;
g_alLast[0] = (g_evRaid == -1); g_alLast[1] = (g_evRaid == 1); g_alLast[2] = (g_evRun != 0);
g_alLast[3] = (g_evMss == 1); g_alLast[4] = (g_evMss == -1);
g_alLast[5] = (g_evFvg == 1); g_alLast[6] = (g_evFvg == -1);
g_alLast[7] = (g_evUni != 0);
g_alLast[8] = (g_evSig == 1); g_alLast[9] = (g_evSig == -1);
}
//+------------------------------------------------------------------+
//| 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();
InitModels();
ResetState();
BuildSeries();
const int L = g_n - 2; // last CLOSED bar: no repainting
for(int i = 0; i <= L; i++) ProcessBar(i);
FlagAlerts();
g_nItem = 0;
g_near = Rng150() * ELblNear;
DrawDealing();
DrawFvgs();
DrawZones();
DrawMisc();
DrawStructure();
DrawLiquidity();
DrawSetups();
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 Models");
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;
}
//+------------------------------------------------------------------+
منطق کار
- ساختار: سقف و کفهای سوئینگ با پیوت تأییدشده پیدا میشوند (طول سوئینگ قابل تنظیم). شکست آخرین سقف یا کف با کلوز کندل ثبت میشود. شکست خلاف روند قبلی «MSS» است و شکست در جهت روند «BOS». اگر کندل شکست یک دیسپلیسمنت باشد (بدنه ≥ ۱٫۲×ATR و ≥ ۶۰٪ دامنه) کنار MSS علامت ⚡ میآید.
- Fair Value Gap: شکاف سهکندلی (low کندل سوم بالاتر از high کندل اول و برعکس) با حداقل اندازهی نسبت به ATR. در Preset «Clean» فیلتر کیفیت روشن است: کندل وسط باید همجهت شکاف باشد، بدنهاش حداقل ۰٫۵×ATR باشد و فراتر از سقف یا کف کندل اول ببندد. خط CE (۵۰٪ شکاف) نمایش داده میشود و شکاف با پر شدن جزئی کوچک میشود. اگر کندلی از لبهی دور شکاف عبور کند و آنطرف ببندد، شکاف به IFVG تبدیل میشود.
- Order Block، Breaker و Mitigation: بعد از BOS، آخرین کندل مخالف قبل از حرکت بهعنوان OB علامت میخورد. بعد از MSS، اگر سوئینگ قبلی با Lower Low یا Higher High جارو شده باشد ناحیه «Breaker» و در غیر اینصورت «Mitigation» است. این دو تشخیص تقریبیاند (در تنظیمات هم با «approx.» نوشته شدهاند) و تأییدشدهی کامل ICT نیستند. ناحیه با کلوز فراتر از لبهی دورش باطل میشود.
- Unicorn: همپوشانی یک Breaker ساختهشده توسط همین MSS با یک FVG سالم که در همان حرکت (از ۳ کندل قبل تا N کندل بعد از MSS) ساخته شده است.
- پریمیوم و دیسکانت: آخرین رنج سوئینگ به دو نیم تقسیم میشود؛ بالای ۵۰٪ پریمیوم و پایین آن دیسکانت. ناحیهی OTE (۶۲٪ تا ۷۹٪ با خط ۷۰٫۵٪) روی آخرین پای حرکت رسم میشود. اگر قیمت بیرون رنج باشد، پنل «Above range» یا «Below range» مینویسد.
- نقدینگی: PDH/PDL، PWH/PWL، EQH/EQL و اختیاری سقف و کف سشنها و PMH/PML. Raid یعنی سطح رد شد و کندل برگشت؛ اگر بیرون ببندد «Break?» است و ظرف چند کندل تکلیف میشود: برگشت = Raid و ماندن = Run. در یک کندل با چند سطح، سطح مهمتر انتخاب میشود.
- Presetها: «Clean» (پیشفرض) فقط ارزشمندترین اجزا را نشان میدهد: MSS، FVG سالم و محدود، OB، Breaker، Unicorn، OTE و نقدینگی روز و هفته. «Standard» همهی اجزا، «Full» بیشترین تعداد و «Custom» فقط تنظیمات دستی شماست.
- پیشفرض تحلیلی است: مدلهای ICT 2022 (Raid ← MSS ← FVG)، Silver Bullet، Unicorn و OTE فقط وقتی «Trade setups» را روشن کنید ستاپ با Entry، SL و TP میسازند. شبیهسازی آمار برد و باخت ساده است و بکتست نیست.
- برچسبها: برچسبهای سمت راست بر اساس قیمت مرتب میشوند و روی هم نمیافتند؛ بلوکها و FVGهای دور از قیمت برچسب ندارند ولی ناحیهشان میماند.
- بدون ریپینت: همهی رویدادها روی کندل بستهشده ثبت میشوند. بایاس تایمفریم بالاتر فقط از آخرین کندل بستهی آن تایمفریم گرفته میشود.
- ورژن متاتریدر: همان قواعد و همان ترتیب پردازش (ساختار، FVG، بلوکها، Unicorn، مدلها) را دارد. ساعت سرور بروکر به UTC و بعد به نیویورک و تهران تبدیل میشود (قانون تغییر ساعت آمریکا و اروپا داخل کد است). هر بار کندل جدیدی بسته شود، پنجرهی محاسبه از اول بازپخش میشود و فقط حالت نهایی رسم میشود. بایاس تایمفریم بالاتر از کندلهای بستهی همان تایمفریم محاسبه میشود. اندازهی پنل و واترمارک از روی عرض واقعی متن تنظیم میشود و پنل در حالت Auto خلوتترین گوشهی چارت را انتخاب میکند.
تنظیمات مهم
- Preset
- Clean، Standard، Full یا Custom. در سه حالت اول، تنظیمات «جزئیات نمایش» (BOS، IFVG، VI، BPR، Mitigation، Rejection، Propulsion، Vacuum، پریمیوم/دیسکانت، لولهای سشن و سقف تعداد FVG/بلوک/لول) از Preset میآید. در Custom همان گزینههای پایین اعمال میشود.
- Show times on chart in
- منطقهی زمانی ساعتها؛ پیشفرض تهران.
- Right-side labels: gap / offset / show within
- فاصلهی برچسبهای سمت راست، فاصله از آخرین کندل و حد فاصلهی قیمتی برای نمایش برچسب بلوکها. حاشیهی راست چارت را حدود ۱۵ تا ۲۰ باز کنید.
- Swing length
- طول پیوت سوئینگ برای ساختار؛ بزرگتر = ساختار کلیتر و کندتر.
- Displacement ATR / body ratio
- شرط دیسپلیسمنت: بدنه ≥ ضریب×ATR و نسبت بدنه به دامنه.
- Min FVG size / Quality filter / middle candle body
- حداقل اندازهی FVG و فیلتر کیفیت کندل وسط. در Clean، فیلتر همیشه روشن است.
- Max active FVGs / Max blocks / Max liquidity levels
- سقف تعداد FVG، بلوک و لول روی چارت (در Preset تعیین میشود).
- Unicorn: FVG within N bars after the MSS
- پنجرهی ساختهشدن FVG بعد از MSS برای Unicorn.
- Trade setups (Entry / SL / TP)
- پیشفرض خاموش. با روشنکردن، مدلهای ICT 2022، Silver Bullet، Unicorn و OTE ستاپ میسازند.
- Min confluence score / Min RR / Stop buffer / Trading cost
- فقط وقتی ستاپ روشن است: حداقل امتیاز (از ۶)، حداقل ریسک به ریوارد، فاصلهی حد ضرر و هزینهی تقریبی معامله.
- Event validity (minutes)
- مدت اعتبار Raid و MSS برای امتیاز و مدلها؛ بر حسب دقیقهی واقعی.
- HTF bias timeframe / EMA length
- بایاس تایمفریم بالاتر از EMA؛ فقط یک امتیاز از ۶ است مگر «Require HTF bias» روشن شود.
- Broker server time (GMT offset in WINTER) و Broker daylight-saving rule (فقط متاتریدر)
- اختلاف ساعت سرور بروکر با GMT در زمستان و قانون ساعت تابستانی بروکر (US، EU یا None). بیشتر بروکرهای فارکس GMT+2 در زمستان و GMT+3 در تابستان هستند.
- Panel position / Watermark (متاتریدر)
- جای پنل: Auto (خلوتترین گوشهی چارت) یا یکی از ۴ گوشه. واترمارک پایینچپ کنار لوگوی چارت است و فاصلهاش قابل تنظیم است.
- Panel position / Watermark position (TradingView)
- جای جدول و واترمارک؛ هر کدام از ۹ نقطهی چارت.
کجا بهتر کار میکند
تحلیل ساختار و نواحی ICT روی طلا، فارکس و شاخصها در تایمفریم ۱ تا ۱۵ دقیقه (حداکثر ۴ ساعته): دیدن MSS، FVGهای سالم، بلوکها، پریمیوم و دیسکانت و Raid نقدینگی در کنار هم، برای ساختن نقشهی روز و تصمیم دستی. همراه «Crypex ICT Time» که سشنها و نقدینگی کامل را رسم میکند، کاملتر است.
نقطهضعف
در حالت پیشفرض سیگنال ورود و خروج نمیدهد و توصیهی معاملاتی نیست. Breaker و Mitigation تشخیص تقریبیاند و OB فقط کندل مخالف قبل از شکست است، نه اعتبارسنجی کامل ICT. تعریف FVG، IFVG و Unicorn برداشت رایج جامعهی ICT است و منبع رسمی ندارد؛ حداقل اندازه و ضریب بدنهی کندل وسط حدسیاند و باید روی نماد خودتان تنظیم شوند. اگر ستاپ را روشن کنید، آمار برد و باخت یک شبیهسازی ساده روی کندلهای بسته است (ترتیب حرکت داخل کندل معلوم نیست و سیاست محافظهکارانه دارد) و بکتست یا پیشبینی نیست؛ این اندیکاتور روی دادهی تاریخی بکتست نشده است. SMT در این اندیکاتور وجود ندارد. روی تایمفریم بالای ۴ ساعته کار نمیکند. کد TradingView کامپایل و روی چارت دیده شده است. کد MT4 و MT5 در MetaEditor کامپایل نشده؛ فقط با شبیهساز و دادهی واقعی طلا اجرا شده است (بدون خطای اجرا، و خروجی MT4 و MT5 با هم یکی است) ولی با نسخهی TradingView مقایسهی عددی نشده، چون اینجا امکان اجرای Pine نیست. قبل از استفاده آن را کامپایل کنید و روی حساب دمو ببینید. در متاتریدر ساعت سشنها از ساعت سرور بروکر محاسبه میشود، پس ساعت و قانون تغییر ساعت بروکر را باید درست وارد کنید؛ کنترل خودکار ساعت بروکر در پنل فقط در MT5 هست. برچسبها در متاتریدر یکخطیاند و شفافیت ندارند؛ رنگ نواحی با پسزمینهی چارت ترکیب میشود. تصاویر پیشنمایش بالا اسکرینشات TradingView نیستند؛ خروجی کد نسخهی MQL روی کندلهای واقعی طلا هستند که با یک رندرکنندهی ساده کشیده شدهاند، پس ظاهر دقیق (فونت، شفافیت، برچسبها) در TradingView و متاتریدر فرق میکند.
راهنمای کامل استفاده
معنی رنگها و علامتها
- FVG: سبز = صعودی، قرمز = نزولی. فیروزهای/نارنجی = IFVG (شکاف معکوسشده). خط نقطهچین وسط = CE.
- بلوکها: سبز/قرمز = Order Block، فیروزهای/نارنجی = Breaker، بنفش/صورتی = Mitigation، زرد = Rejection، لیمویی = Propulsion، خاکستری = Gap.
- خطچین رنگی با MSS/BOS = شکست ساختار. ⚡ = دیسپلیسمنت.
- «Raid X» نارنجی = سطح جارو شد و کندل برگشت. «Break? X» خاکستری = شکست و در انتظار تکلیف. «Run X» = شکست ماند.
- قاب سفید با «Unicorn» = همپوشانی Breaker و FVG.
- قرمز کمرنگ بالا = پریمیوم، سبز کمرنگ پایین = دیسکانت، خط خاکستری = EQ (۵۰٪).
روش پیشنهادی (تحلیلی)
- جهت بزرگتر را از بایاس HTF و محل قیمت در رنج (پریمیوم یا دیسکانت) ببینید.
- دنبال Raid نقدینگی بگردید، بعد MSS همجهت برگشت و بعد FVG یا Breaker که در حرکت MSS ساخته شده.
- ناحیههای سالم نزدیک قیمت را بهعنوان مناطق احتمالی واکنش علامت بزنید؛ ورود و مدیریت ریسک با خودتان است.
- اگر ستاپها را روشن کنید، آنها را ابزار کمکی ببینید، نه سیگنال.
متاتریدر: تنظیم ساعت بروکر
- ساعت سشنهای 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!».
- بایاس تایمفریم بالاتر (HTF) در متاتریدر به دادهی همان تایمفریم نیاز دارد؛ اگر بروکر دادهی کافی نداده باشد، یک بار چارت H4 همان نماد را باز و اسکرول کنید تا بارگذاری شود.
نصب و تنظیم اولیه
- کد را در Pine Editor بچسبانید و Add to chart بزنید.
- روی Preset «Clean» بمانید تا چارت خلوت باشد؛ برای دیدن بیشتر Standard را انتخاب کنید.
- در Settings > Scales حاشیهی راست را حدود ۱۵ تا ۲۰ باز کنید تا برچسبها دیده شوند.
- جای پنل و واترمارک را از تنظیمات «Panel & watermark» عوض کنید.
- متاتریدر: فایل را در MetaEditor با F7 کامپایل کنید و از Navigator روی چارت بکشید. جای پنل در حالت Auto خودش خلوتترین گوشه را انتخاب میکند.
پرسشهای متداول
این اندیکاتور سیگنال خرید و فروش میدهد؟
بهطور پیشفرض نه. فقط تحلیلی است. مدلهای ورود را باید خودتان با گزینهی «Trade setups» روشن کنید و حتی آنوقت نتیجه توصیهی معاملاتی نیست.
چرا FVGهای کمی میبینم؟
در Preset «Clean» فقط FVGهایی که کندل وسطشان یک حرکت قوی و همجهت است و اندازهی کافی دارند ثبت میشوند. برای دیدن همهی شکافها Preset را Standard یا Full کنید.
فرق MSS و BOS چیست؟
MSS شکست ساختار خلاف روند قبلی است (احتمال تغییر جهت) و BOS شکست در جهت روند است. هر دو با کلوز کندل ثبت میشوند.
IFVG یعنی چه؟
وقتی کندلی از لبهی دور یک FVG عبور کند و آنطرف ببندد، شکاف دیگر در جهت اولش کار نمیکند و نقش مخالف میگیرد؛ به آن IFVG میگوییم. این قاعده برداشت رایج است.
چرا بعضی برچسبها ناپدید میشوند؟
برچسب بلوکها، FVGها و لولهای دور از قیمت مخفی میشود تا ستون راست شلوغ نشود. خود ناحیه روی چارت میماند. حد را با «Show zone labels within» عوض کنید.
نسخهی متاتریدر دارد؟
بله، MT5 و MT4 با همان قواعد. این دو نسخه را در MetaEditor کامپایل نکردهایم و فقط با شبیهساز و دادهی واقعی آزمایش شدهاند؛ قبل از استفاده کامپایل کنید و روی دمو ببینید. ساعت بروکر را هم باید درست تنظیم کنید.
چرا در متاتریدر ساعت سشنها غلط است؟
معمولاً ساعت سرور بروکر یا قانون ساعت تابستانی در تنظیمات اندیکاتور درست وارد نشده است. اختلاف ساعت سرور با GMT را در زمستان وارد کنید و قانون US یا EU یا None را مطابق بروکر خود بگذارید.
اندیکاتور همراه: همراه این اندیکاتور: Crypex ICT Time (سشنها، CBDR، Judas و نقدینگی کامل با وقت تهران)
این کد آموزشی است و توصیهی سرمایهگذاری نیست. قبل از استفاده روی حساب واقعی، کامپایل و رفتار آن را روی حساب دمو و در بکتست بررسی کنید؛ نتیجهی گذشته تضمینکنندهی آینده نیست.
← همهی اسکریپتها