diff --git a/Experts/ICT_2026.mq5 b/Experts/ICT_2026.mq5 index 3308d99..27577cb 100644 --- a/Experts/ICT_2026.mq5 +++ b/Experts/ICT_2026.mq5 @@ -1,10 +1,30 @@ //+------------------------------------------------------------------+ -//| ICT_2026.mq5 — Daily Bias + Intraday Structure (H1) | -//| BOS = Continue | CHoCH = Reversal | +//| ICT_2026.mq5 — Root EA (orchestrator only, no business logic) | +//+------------------------------------------------------------------+ +//| Layer pipeline (đọc từ trên xuống = TF cao xuống thấp): | +//| D1 → DailyBias — bias hôm nay (BULL/BEAR/RANGE/NONE) | +//| H1 → IntradayStructure — H1 trend + IsAllowTrade | +//| H1 → LowTfTrend — orchestrator FVG + MSS pipeline | +//| ├── Fvg — detect/state/PD | +//| ├── ConfirmFvg — M5 FVG pool | +//| ├── MssSetup — phase machine + keylv lock | +//| └── MssEntry — order + partial + BE + timeout | +//| UI → Panel / Draw / FvgDraw / MssDraw + Stats | +//| | +//| Trigger: | +//| OnInit — init từng module, gọi Update force=true | +//| OnTick — orchestrator (xem README ▸ "Current | +//| architecture A.") | +//| OnTradeTransaction — filter DEAL_ENTRY_OUT magic → call | +//| IctMss_OnPositionClosed (reset state) | +//| OnDeinit — clean up objects | +//| | +//| Quy ước: file này KHÔNG được chứa business logic. Mọi rule đi vào| +//| module tương ứng. | //+------------------------------------------------------------------+ #property copyright "ICT 2026" -#property version "1.170" -#property description "ICT2026 MSS | Pipeline gate=Bias, Entry gate=AllowTrade — track MSS cả khi intraday ngược, chỉ block entry" +#property version "1.187" +#property description "ICT2026 MSS | strict body-break, PD-for-all-FVG, bias-aware touch, stale-limit cap" #include #include diff --git a/Include/ICT2026/Config.mqh b/Include/ICT2026/Config.mqh index 6beb10f..67d3281 100644 --- a/Include/ICT2026/Config.mqh +++ b/Include/ICT2026/Config.mqh @@ -1,5 +1,29 @@ //+------------------------------------------------------------------+ -//| Config.mqh — ICT 2026 inputs | +//| Config.mqh — ICT 2026 input parameters (single source of truth) | +//+------------------------------------------------------------------+ +//| Tất cả `input` của EA gom vào file này, chia thành 8 groups: | +//| 1) Daily Bias — TF, swing range, fib | +//| 2) Intraday Structure — TF (default H1), pivot, recent bars | +//| 3) Test mode — InpOnlyStatsMode (skip render cho tester)| +//| 4) Display panel — panel font, position | +//| 5) Chart draw — bias/intra/confirm swing labels | +//| 6) Confirm swing (M5)— InpConfirmTf (M5), swing range, lookback| +//| 7) Low TF / FVG — InpFvgTf (H1), ATR period, gap filters, | +//| PD toggle / overlap %, large bypass, | +//| tiered touch (Pure/Mixed), | +//| draw top-N + price-near (v1.180) | +//| 8) MSS / Trade — risk %, SL/TP spread mults, min RR, | +//| partial close, BE@N×R, pending timeout,| +//| require intraday aligned (v1.181), | +//| max limit distance (v1.184), | +//| one-position, EOD cancel | +//| | +//| Lưu ý: KHÔNG đặt logic ở đây; chỉ khai báo `input`. Default values| +//| phải tương đương "production safe" — user mới chỉ cần tắt/bật là | +//| chạy ổn. | +//| | +//| Khi thêm input mới: cập nhật README mục [Input](#inputs) + | +//| changelog v1.x trong cùng commit. | //+------------------------------------------------------------------+ #ifndef ICT2026_CONFIG_MQH #define ICT2026_CONFIG_MQH @@ -40,6 +64,7 @@ input group "══ Confirm swing (M5) ══" input ENUM_TIMEFRAMES InpConfirmTf = PERIOD_M5; // TF confirmation (tương lai) input int InpConfirmSwingRange = 2; // M5 swing strength: N bar mỗi bên (5-bar fractal khi =2) input int InpConfirmSwingLookback = 80; +input int InpBodyBreakSpreadMult = 10; // Body break: thân nến đóng vượt level ≥ N × spread (siết chặt BOS/CHoCH, MSS confirm, H1 invalidate) input int InpConfirmRecentBars = 40; input group "══ Low TF / iTF FVG ══" @@ -52,6 +77,7 @@ input double InpFvgMinGapATRPct = 12.0; // H1 only: gap t input double InpFvgMinGapPoints = 0.0; // H1 only: gap tối thiểu (giá) input double InpFvgMinGapVsBarPct = 25.0; // H1 only: gap >= % range nến B (0=tắt) input double InpFvgUsedFillPct = 25.0; // % lấp FVG → Used +input bool InpFvgPdEnabled = true; // Bật/tắt điều kiện Premium/Discount toàn cục (false = chấp nhận FVG bất kể vùng) input double InpFvgPdMinOverlapPct = 0.0; // POI filter: % chiều cao FVG nằm trong Premium/Discount (0=chấp nhận bất kỳ overlap > 0) input double InpFvgTouchFillPure = 25.0; // Touch threshold khi FVG nằm hẳn trong vùng PD đúng chiều (bear→Premium, bull→Discount) input double InpFvgTouchFillMixed = 50.0; // Touch threshold khi FVG straddle equilibrium (overlap cả Premium & Discount) @@ -63,6 +89,8 @@ input color InpFvgBearColor = clrRed; // Bear FVG input color InpFvgUsedColor = clrDimGray; // Used FVG input color InpPdPremiumColor = clrMaroon; input color InpPdDiscountColor = clrDarkGreen; +input int InpDrawMaxRecentPerState = 3; // FVG/PD: vẽ tối đa N gần nhất cho mỗi state (Used / Available) +input double InpDrawNearAtrMult = 2.0; // FVG khác chỉ vẽ khi giá cách ≤ N × ATR(FvgTf) (0=không vẽ "near", >0=vẽ thêm) input group "══ MSS / Entry (Confirm TF) ══" input bool InpMssH1RetestWickOnly = true; // (legacy, bỏ qua) Retest = giá chạm FVG @@ -87,7 +115,13 @@ input int InpMssSlSpreadMult = 8; // SL buffer = N input int InpMssTpSpreadMult = 16; // TP buffer = N × spread (chốt trước iL0/iH0 để dễ khớp) input double InpMssMinRR = 2.0; // Ngưỡng RR: nếu TP@iL0/iH0 < ngưỡng → dùng TP cố định = entry ± risk × InpMssMinRR (= 2R), ngược lại TP tại iL0/iH0 input double InpMssPartialClosePct = 50.0; // % volume chốt khi giá đạt swing iL0/iH0 (TP gồng xa hơn) — sau đó dời SL về BE (0=tắt) -input int InpMssPendingExpireHours = 4; // Hết hạn pending limit (giờ): hủy + reset → WAIT_FVG_TOUCH (0=không timeout) +input bool InpMssBeEnabled = false; // Bật/tắt dời SL về entry khi giá đi được N×R (mặc định tắt) +input double InpMssBeAtRR = 2.0; // Ngưỡng N×R để kích hoạt dời BE (chỉ áp dụng khi InpMssBeEnabled=true) +input int InpMssPendingExpireHours = 1; // Hết hạn pending limit (giờ): hủy + reset → WAIT_FVG_TOUCH (0=không timeout) +input double InpMssMaxLimitDistAtrMult = 3.0; // Cap khoảng cách limit-giá theo bội số ATR(FvgTf). >cap ⇒ không đặt / cancel limit hiện có (tránh limit chết). 0 = tắt +input bool InpMssCancelStaleLimit = true; // Cancel pending + reset state khi giá rời xa entry vượt cap (mark FVG used để chọn POI khác) +input bool InpMssCancelLimitWhenTpReached = true; // Nếu giá chạm TP TRƯỚC khi limit khớp ⇒ cancel limit + reset (không đợi hồi nữa) +input bool InpMssRequireIntradayAligned = false; // Bắt buộc Intraday cùng chiều Bias mới entry? false=cho phép entry dù Intraday ngược/None (tránh miss setup khi trend chuyển muộn) input bool InpMssOnePosition = true; // Một position/pending MSS input bool InpMssCancelPendingEod = true; // Hủy pending cuối phiên Mỹ (nếu chưa khớp) input int InpMssEodHour = 23; // Giờ EOD theo SERVER time (24h, vd EET broker = 23h ≈ 16:00 ET DST) diff --git a/Include/ICT2026/ConfirmFvg.mqh b/Include/ICT2026/ConfirmFvg.mqh index 6dac29f..7f7c760 100644 --- a/Include/ICT2026/ConfirmFvg.mqh +++ b/Include/ICT2026/ConfirmFvg.mqh @@ -1,5 +1,26 @@ //+------------------------------------------------------------------+ -//| ConfirmFvg.mqh — FVG trên Confirm TF (M5) cho MSS entry | +//| ConfirmFvg.mqh — pool FVG trên Confirm TF (M5) cho MSS entry | +//+------------------------------------------------------------------+ +//| Pool độc lập với g_ictFvgZones (H1) — chỉ dùng làm entry zone | +//| sau khi MSS lock trên Confirm TF. | +//| | +//| KHÁC với H1 FVG: | +//| - KHÔNG áp dụng size filter (InpFvgMinGap*) — phát hiện M5 FVG | +//| nhỏ là cần thiết cho entry precision | +//| - KHÔNG track PD | +//| - Scan trigger từ ngoài: IctConfirmFvg_ScanNew(sym, tf, side, | +//| sinceTime, force) — chỉ scan FVG hình thành SAU chochTime | +//| | +//| Globals owned: | +//| g_ictConfirmFvgZones[] — array M5 FVG (max InpFvgMaxZones) | +//| g_ictConfirmFvgCount — số M5 FVG | +//| | +//| Public API: | +//| void IctConfirmFvg_Init() | +//| void IctConfirmFvg_UpdateAll(sym, tf) — update state/fill | +//| void IctConfirmFvg_ScanNew(sym, tf, side, sinceTime, force) | +//| int IctConfirmFvg_FindLatestAfter(sinceTime, side) | +//| int IctConfirmFvg_FindById(id) | //+------------------------------------------------------------------+ #ifndef ICT2026_CONFIRMFVG_MQH #define ICT2026_CONFIRMFVG_MQH diff --git a/Include/ICT2026/DailyBias.mqh b/Include/ICT2026/DailyBias.mqh index a5301a9..a6588bf 100644 --- a/Include/ICT2026/DailyBias.mqh +++ b/Include/ICT2026/DailyBias.mqh @@ -1,5 +1,35 @@ //+------------------------------------------------------------------+ -//| DailyBias.mqh — Bias rõ (HH-HL/LH-LL) vs sớm (sau CHoCH) | +//| DailyBias.mqh — Daily Bias resolver (D1) | +//+------------------------------------------------------------------+ +//| Trả lời câu hỏi: "Hôm nay nên ưu tiên BUY hay SELL?" | +//| | +//| Thứ tự ưu tiên (cao→thấp, v1.175): | +//| 1) D1/D2 pattern (IctDailyBias_ResolveD1D2): | +//| - D[1].close > D[2].high → BULL (breakout) | +//| - D[1].close < D[2].low → BEAR (breakdown) | +//| - D[1] sweep D[2] high + close back → BEAR (sweep bear liq)| +//| - D[1] sweep D[2] low + close back → BULL (sweep bull liq)| +//| 2) Structural HH-HL / LH-LL (IctResolveTrend) | +//| 3) HTF fallback (IctUpdateHTFBias — Previous Day Model) | +//| | +//| Globals owned: | +//| g_ictDailyBias — IctDailyBiasState (kết quả + reason) | +//| g_ictDailyCtx — IctTrendResolveCtx (input cho resolver) | +//| g_ictBiasIntoBullTime — TS Bias chuyển vào BULL (v1.182) | +//| g_ictBiasIntoBearTime — TS Bias chuyển vào BEAR (v1.182) | +//| ⇒ làm cutoff cho IctMss_HasFreshFvgTouch (loại touch xảy ra | +//| trước khi bias xoay sang side hiện tại) | +//| | +//| Public API: | +//| bool IctDailyBias_Init(sym) | +//| bool IctDailyBias_Update(sym) — true khi có nến D1 mới | +//| void IctDailyBias_Get(IctDailyBiasState &out) | +//| bool IctDailyBias_IsBull/IsBear() | +//| ENUM_ICT_BIAS ICT2026_GetDailyBias() | +//| | +//| Internal: | +//| IctDailyBias_TrackTransition() — gọi cuối Update để cập nhật | +//| g_ictBiasInto*Time khi bias xoay side | //+------------------------------------------------------------------+ #ifndef ICT2026_DAILYBIAS_MQH #define ICT2026_DAILYBIAS_MQH @@ -12,6 +42,99 @@ IctDailyBiasState g_ictDailyBias; IctTrendResolveCtx g_ictDailyCtx; +// Thời điểm Bias chuyển vào mỗi side — dùng để gác "fresh touch": +// FVG touch xảy ra TRƯỚC thời điểm Bias chuyển sang side đó ⇒ KHÔNG hợp lệ. +// Tránh case: bias UP → bear FVG bị chạm (lúc EA bỏ qua) → bias xoay DOWN → EA +// tưởng đã chạm POI từ quá khứ → vào lệnh limit luôn. +datetime g_ictBiasIntoBullTime = 0; +datetime g_ictBiasIntoBearTime = 0; + +void IctDailyBias_TrackTransition() +{ + static ENUM_ICT_BIAS prev = ICT_BIAS_NONE; + const ENUM_ICT_BIAS cur = g_ictDailyBias.bias; + if(cur == prev) + return; + const datetime now = TimeCurrent(); + if(cur == ICT_BIAS_BULL && prev != ICT_BIAS_BULL) + g_ictBiasIntoBullTime = now; + else if(cur == ICT_BIAS_BEAR && prev != ICT_BIAS_BEAR) + g_ictBiasIntoBearTime = now; + prev = cur; +} + +// Bias từ pattern D1 vs D2 (ưu tiên cao hơn structural HH-HL/LH-LL). +// +// Thứ tự ưu tiên: +// 1. Breakout/Breakdown rõ ràng: +// - D[1].close > D[2].high → BULL +// - D[1].close < D[2].low → BEAR +// 2. Liquidity sweep (false breakout): +// - D[1].high > D[2].high & D[1].close < D[2].high & close > D[2].low +// → sweep bear liquidity (quét stop trên) → BEAR +// - D[1].low < D[2].low & D[1].close > D[2].low & close < D[2].high +// → sweep bull liquidity (quét stop dưới) → BULL +// 3. Trường hợp đặc biệt: D1 sweep CẢ 2 phía + close kẹt trong range D2 +// ⇒ không xác định ⇒ rơi xuống fallback structural +// +// Return: true nếu match 1 trong các pattern (biasOut + reasonOut được set). +bool IctDailyBias_ResolveD1D2(const string sym, const ENUM_TIMEFRAMES tf, + ENUM_ICT_BIAS &biasOut, string &reasonOut) +{ + biasOut = ICT_BIAS_NONE; + reasonOut = ""; + + const double b1H = iHigh(sym, tf, 1); + const double b1L = iLow(sym, tf, 1); + const double b1C = iClose(sym, tf, 1); + const double b2H = iHigh(sym, tf, 2); + const double b2L = iLow(sym, tf, 2); + + if(b2H <= 0.0 || b2L <= 0.0 || b1H <= 0.0) + return false; + + // 1) Clear breakout / breakdown + if(b1C > b2H) + { + biasOut = ICT_BIAS_BULL; + reasonOut = "D[1].close > D[2].high → Breakout BULL"; + return true; + } + if(b1C < b2L) + { + biasOut = ICT_BIAS_BEAR; + reasonOut = "D[1].close < D[2].low → Breakdown BEAR"; + return true; + } + + // Đến đây: b2L ≤ b1C ≤ b2H (close kẹt trong range D2) + const bool sweepHigh = (b1H > b2H); + const bool sweepLow = (b1L < b2L); + + // 2a) Cả 2 phía cùng sweep + close inside ⇒ không xác định + if(sweepHigh && sweepLow) + return false; + + // 2b) Sweep bear liquidity + if(sweepHigh) + { + biasOut = ICT_BIAS_BEAR; + reasonOut = "D[1].high > D[2].high & close < D[2].high → sweep bear liq → BEAR"; + return true; + } + + // 2c) Sweep bull liquidity + if(sweepLow) + { + biasOut = ICT_BIAS_BULL; + reasonOut = "D[1].low < D[2].low & close > D[2].low → sweep bull liq → BULL"; + return true; + } + + // Inside day hoặc trùng range — không match D1/D2 pattern + return false; +} + ENUM_ICT_BIAS IctBiasFromTrend(const ENUM_ICT_TREND trend) { if(trend == ICT_TREND_UP) @@ -291,13 +414,41 @@ bool IctDailyBias_Update(const string sym, const bool force = false) IctResolveTrend(sym, tf, sw, pivotStruct, g_ictDailyCtx, 1); IctDailyBias_SyncFromCtx(sw, pivotStruct); - if(g_ictDailyBias.bias == ICT_BIAS_NONE) - IctDailyBias_ApplyHtfFallback(); - else + // ƯU TIÊN 1: D1/D2 pattern (4 cases: breakout, breakdown, 2 sweeps). + // D1/D2 thắng cả structural — override bias nếu match. + ENUM_ICT_BIAS d1d2Bias = ICT_BIAS_NONE; + string d1d2Reason = ""; + if(IctDailyBias_ResolveD1D2(sym, tf, d1d2Bias, d1d2Reason)) + { + g_ictDailyBias.bias = d1d2Bias; + g_ictDailyBias.biasPhase = ICT_TREND_PHASE_CLEAR; + g_ictDailyBias.reason = d1d2Reason; + g_ictDailyBias.keyLv1 = 0.0; + g_ictDailyBias.keyLv2 = 0.0; + if(sw.IsComplete()) + { + const ENUM_ICT_STRUCT keyStruct = (d1d2Bias == ICT_BIAS_BULL) + ? ICT_STRUCT_BULL : ICT_STRUCT_BEAR; + IctGetKeyLevels(keyStruct, sw, g_ictDailyBias.keyLv1, g_ictDailyBias.keyLv2); + } + g_ictDailyBias.displayReason = d1d2Reason; + } + // ƯU TIÊN 2: Structural HH-HL/LH-LL (đã set ở SyncFromCtx ở trên). + else if(g_ictDailyBias.bias == ICT_BIAS_BULL || g_ictDailyBias.bias == ICT_BIAS_BEAR) + { g_ictDailyBias.reason = StringFormat("Structural: %s", IctBiasText(g_ictDailyBias.bias)); + g_ictDailyBias.displayReason = IctBuildDisplayReason(g_ictDailyBias, sym, tf); + } + // ƯU TIÊN 3: HTF fallback (outside day / range). + else + { + IctDailyBias_ApplyHtfFallback(); + g_ictDailyBias.displayReason = IctBuildDisplayReason(g_ictDailyBias, sym, tf); + } - g_ictDailyBias.displayReason = IctBuildDisplayReason(g_ictDailyBias, sym, tf); + // Cập nhật timestamp Bias chuyển side (để gác fresh touch sau Bias flip) + IctDailyBias_TrackTransition(); if(InpDebug) { diff --git a/Include/ICT2026/Draw.mqh b/Include/ICT2026/Draw.mqh index b392380..b3e9a10 100644 --- a/Include/ICT2026/Draw.mqh +++ b/Include/ICT2026/Draw.mqh @@ -1,6 +1,29 @@ //+------------------------------------------------------------------+ -//| Draw.mqh — label swing theo TF chart hiện tại | -//| Chart lớn (D1): b* | Chart H1: b*+i* | Chart M5: b*+i*+H0–L1 | +//| Draw.mqh — swing labels trên chart (adaptive theo chart TF) | +//+------------------------------------------------------------------+ +//| Vẽ 3 lớp swing labels, tự động ẩn lớp có TF nhỏ hơn chart hiện | +//| tại để không bị nhiễu: | +//| | +//| Chart TF | Vẽ | +//| --------------+-------------------------------------------------| +//| ≥ InpBiasTf | bH0, bH1, bL0, bL1 | +//| ≥ InpIntra | + iH0, iH1, iL0, iL1 | +//| ≥ InpConfirm | + H0, H1, L0, L1 (Confirm TF swing) | +//| | +//| Bật/tắt từng lớp: | +//| InpDrawBiasSwingLabels | +//| InpDrawIntraSwingLabels | +//| InpDrawConfirmLabels | +//| | +//| So sánh: PeriodSeconds(chart) vs PeriodSeconds(InpXxxTf). | +//| | +//| Skip nếu: InpOnlyStatsMode = true | +//| | +//| Object prefix: ICT26_DR_ | +//| | +//| Public API: | +//| void IctDraw_Render(sym) | +//| void IctDraw_DeleteAll() | //+------------------------------------------------------------------+ #ifndef ICT2026_DRAW_MQH #define ICT2026_DRAW_MQH diff --git a/Include/ICT2026/EaState.mqh b/Include/ICT2026/EaState.mqh index 9bedc42..de9b8a5 100644 --- a/Include/ICT2026/EaState.mqh +++ b/Include/ICT2026/EaState.mqh @@ -1,5 +1,37 @@ //+------------------------------------------------------------------+ -//| EaState.mqh — State machine tổng: STOP vs SETUP pipeline MSS | +//| EaState.mqh — STATE machine cao cấp cho panel + API export | +//+------------------------------------------------------------------+ +//| Map (mss.phase + flags + bias/intraday) → CODE category: | +//| | +//| STOP — không trade được: | +//| STOP_NO_BIAS — bias = NONE | +//| STOP_BIAS_RANGE — bias = RANGE | +//| STOP_INTRADAY_NONE — bias rõ + intraday = NONE | +//| STOP_BIAS_INTRADAY_MISMATCH — bias ≠ intraday | +//| | +//| SETUP — đang setup: | +//| WAIT_FVG_TOUCH — chờ retest H1 POI | +//| FVG_TOUCHED_WAIT_MSS — H1 retest + chờ M5 phá keylv | +//| MSS_OK_WAIT_M5_FVG — MSS lock + chờ M5 FVG | +//| M5_FVG_WAIT_RETRACE — có M5 FVG + chờ hồi fill | +//| READY_FOR_LIMIT — vùng entry, sắp đặt | +//| | +//| TRADE — có order: | +//| LIMIT_ORDER_PENDING — pending limit magic | +//| ON_TRADE — position magic | +//| | +//| Globals owned: | +//| g_ictEaState — IctEaState (code, title, detail) | +//| | +//| Lưu ý category vs code: | +//| Category {STOP/SETUP/TRADE} là "phân nhóm" hiển thị; code là | +//| "vị trí cụ thể trong nhóm". | +//| | +//| Public API: | +//| void IctEaState_Init() / IctEaState_Refresh(sym) | +//| ENUM_ICT_EA_STATE ICT2026_GetEaState() | +//| string ICT2026_GetEaStateCode() | +//| string ICT2026_GetEaStateDetail() | //+------------------------------------------------------------------+ #ifndef ICT2026_EASTATE_MQH #define ICT2026_EASTATE_MQH diff --git a/Include/ICT2026/Fvg.mqh b/Include/ICT2026/Fvg.mqh index 764c3b6..85032c5 100644 --- a/Include/ICT2026/Fvg.mqh +++ b/Include/ICT2026/Fvg.mqh @@ -1,5 +1,42 @@ //+------------------------------------------------------------------+ -//| Fvg.mqh — iTF Fair Value Gap (Available / Used) | +//| Fvg.mqh — FVG detection + state + Premium/Discount (FvgTf = H1) | +//+------------------------------------------------------------------+ +//| Quản lý FVG trên `InpFvgTf` (default H1): | +//| - Detect FVG 3 nến A-B-C (Bull: A.HighC.High)| +//| - State: AVAILABLE / USED (fill ≥ InpFvgUsedFillPct ⇒ USED) | +//| - PD zone (Premium/Discount) từ 2 pivot swing-1 + swing-2: | +//| swing-1 = pivot gần FVG nhất (trước bar C) | +//| swing-2 = pivot ngược chiều đầu tiên SAU FVG | +//| pdHigh / pdLow / pdEq (giữa) | +//| - `pdComplete` khoá khi swing-2 xác nhận (không cập nhật nữa) | +//| | +//| Globals owned: | +//| g_ictFvgZones[] — array FVG (max InpFvgMaxZones) | +//| g_ictFvgCount — số FVG đang track | +//| | +//| Filters kích thước (chỉ áp dụng `InpFvgTf` — H1, không M5): | +//| InpFvgMinGapATRPct (12%) — gap ≥ % ATR(14) | +//| InpFvgMinGapPoints (0) — gap tuyệt đối | +//| InpFvgMinGapVsBarPct(25%) — gap ≥ % range nến B | +//| | +//| PD filter (Premium/Discount eligibility): | +//| IctFvg_IsEntryRepPd(zone, bias): | +//| - !InpFvgPdEnabled (v1.179) ⇒ true | +//| - else: BEAR overlap Premium / BULL overlap Discount | +//| - (v1.187) bỏ large-FVG bypass — áp PD cho TẤT CẢ FVG | +//| | +//| Touch threshold (Pure/Mixed — v1.167): | +//| IctFvg_RequiredTouchFillPct(zone): | +//| - PD tắt (InpFvgPdEnabled=false) ⇒ Pure (25%) | +//| - FVG hoàn toàn trong vùng PD đúng chiều ⇒ Pure (25%) | +//| - FVG straddle equilibrium ⇒ Mixed (50%) | +//| | +//| Public API (chính): | +//| void IctFvg_Init() / IctFvg_UpdateAll(sym, tf) | +//| void IctFvg_ScanNew(sym, tf, side, force) | +//| int IctFvg_CountAvailable() | +//| void IctFvg_UpdateZoneState(sym, tf, zone) | +//| double IctFvg_AtrFvgTf(sym) — ATR helper (M5/H1 buffers, v1.184)| //+------------------------------------------------------------------+ #ifndef ICT2026_FVG_MQH #define ICT2026_FVG_MQH @@ -454,10 +491,33 @@ ENUM_ICT_PD_ZONE IctFvg_GetRepPd(const IctFvgZone &zone) return zone.pdZone; } +// Lấy ATR(InpFvgTf, period) tại bar shift=1 (đã đóng) +double IctFvg_AtrFvgTf(const string sym) +{ + const int h = iATR(sym, InpFvgTf, InpFvgAtrPeriod); + if(h == INVALID_HANDLE) + return 0.0; + double buf[]; + ArraySetAsSeries(buf, true); + const int got = CopyBuffer(h, 0, 1, 1, buf); + IndicatorRelease(h); + return (got == 1) ? buf[0] : 0.0; +} + bool IctFvg_IsEntryRepPd(const IctFvgZone &zone) { + // Toggle global: tắt PD ⇒ chấp nhận mọi FVG có pdEq hợp lệ + // (vẫn yêu cầu có Bias rõ ràng để chọn side BEAR/BULL) + if(!InpFvgPdEnabled) + { + if(g_ictDailyBias.bias != ICT_BIAS_BEAR && g_ictDailyBias.bias != ICT_BIAS_BULL) + return false; + return true; + } + if(zone.pdEq <= 0.0) return false; + const double minPct = MathMax(0.0, MathMin(100.0, InpFvgPdMinOverlapPct)); double pct = 0.0; if(g_ictDailyBias.bias == ICT_BIAS_BEAR) @@ -474,11 +534,16 @@ bool IctFvg_IsEntryRepPd(const IctFvgZone &zone) } // Touch threshold cho POI: +// - PD toàn cục tắt (InpFvgPdEnabled=false): luôn 25% (không phân biệt vùng) // - FVG nằm hẳn trong Premium (bear) / Discount (bull) → 25% // - FVG straddle equilibrium (mixed) → 50% // - Trả về 100 nếu FVG sai vùng (không thể là POI) double IctFvg_RequiredTouchFillPct(const IctFvgZone &zone) { + // Toggle global tắt PD ⇒ dùng touch threshold thấp + if(!InpFvgPdEnabled) + return InpFvgTouchFillPure; + if(zone.pdEq <= 0.0) return 100.0; double pct = 0.0; diff --git a/Include/ICT2026/FvgDraw.mqh b/Include/ICT2026/FvgDraw.mqh index cffe943..2238c4f 100644 --- a/Include/ICT2026/FvgDraw.mqh +++ b/Include/ICT2026/FvgDraw.mqh @@ -1,5 +1,30 @@ //+------------------------------------------------------------------+ -//| FvgDraw.mqh — vẽ iTF FVG + Premium/Discount trên chart | +//| FvgDraw.mqh — render FVG + Premium/Discount lên chart | +//+------------------------------------------------------------------+ +//| v1.180 — selective rendering (chart không lag với 24+ FVG): | +//| 1) Top-N USED gần nhất (default 3) — sort theo createdTime desc | +//| 2) Top-N AVAILABLE gần nhất (default 3) | +//| 3) Price-near: giá hiện tại trong [lower-N×ATR, upper+N×ATR] | +//| 4) M5 FVG đang dùng cho MSS pipeline — luôn ép vẽ | +//| | +//| Inputs control: | +//| InpDrawFvgZones — bật/tắt vẽ | +//| InpDrawMaxRecentPerState — N FVG gần nhất mỗi state | +//| InpDrawNearAtrMult — bán kính near-price theo ATR | +//| | +//| Render: rectangle (FVG body) + 2 horizontal lines (Premium / | +//| Discount edge). Bull/Bear/Used có màu riêng (InpFvgBull/Bear/Used)| +//| | +//| Skip nếu: InpOnlyStatsMode = true (tester mode) | +//| | +//| Object prefix: | +//| ICT26_FVG_ — body rectangle | +//| ICT26_PD_ — Premium / Discount lines | +//| ICT26_CFVG_ — Confirm TF (M5) FVG body | +//| | +//| Public API: | +//| void IctFvgDraw_Render(sym) | +//| void IctFvgDraw_DeleteAll() | //+------------------------------------------------------------------+ #ifndef ICT2026_FVGDRAW_MQH #define ICT2026_FVGDRAW_MQH @@ -57,6 +82,75 @@ void IctFvgDraw_HLine(const string name, const datetime t1, const datetime t2, ObjectMove(ch, name, 1, t2, price); } +// Insertion sort (descending theo createdTime) cho mảng indices nhỏ +void IctFvgDraw_SortIdxByTimeDesc(int &idx[], const int n, + const IctFvgZone &zones[]) +{ + for(int i = 1; i < n; i++) + { + const int cur = idx[i]; + const datetime ct = zones[cur].createdTime; + int j = i - 1; + while(j >= 0 && zones[idx[j]].createdTime < ct) + { + idx[j + 1] = idx[j]; + j--; + } + idx[j + 1] = cur; + } +} + +// Build mask "có vẽ" cho mảng FVG: top-N USED + top-N AVAILABLE gần nhất, +// cộng các FVG mà price hiện tại gần (≤ N×ATR). +void IctFvgDraw_BuildVisibleMask(const IctFvgZone &zones[], const int count, + const string sym, bool &visible[]) +{ + ArrayResize(visible, count); + for(int i = 0; i < count; i++) visible[i] = false; + if(count == 0) + return; + + const int maxN = MathMax(0, InpDrawMaxRecentPerState); + + // Phân loại theo state + int avail[], used[]; + ArrayResize(avail, count); + ArrayResize(used, count); + int nA = 0, nU = 0; + for(int i = 0; i < count; i++) + { + if(zones[i].state == ICT_FVG_USED) + used[nU++] = i; + else + avail[nA++] = i; + } + ArrayResize(avail, nA); + ArrayResize(used, nU); + + IctFvgDraw_SortIdxByTimeDesc(avail, nA, zones); + IctFvgDraw_SortIdxByTimeDesc(used, nU, zones); + + for(int i = 0; i < MathMin(maxN, nA); i++) visible[avail[i]] = true; + for(int i = 0; i < MathMin(maxN, nU); i++) visible[used[i]] = true; + + // Mở rộng theo price-near + if(InpDrawNearAtrMult > 0.0) + { + const double atr = IctFvg_AtrFvgTf(sym); + if(atr > 0.0) + { + const double thresh = atr * InpDrawNearAtrMult; + const double px = SymbolInfoDouble(sym, SYMBOL_BID); + for(int i = 0; i < count; i++) + { + if(visible[i]) continue; + if(px >= zones[i].lower - thresh && px <= zones[i].upper + thresh) + visible[i] = true; + } + } + } +} + void IctFvgDraw_Render(const string sym) { if(InpOnlyStatsMode) @@ -69,8 +163,14 @@ void IctFvgDraw_Render(const string sym) IctFvgDraw_DeleteAll(); + // Lọc H1 FVG cho top-N + price-near + bool visH1[]; + IctFvgDraw_BuildVisibleMask(g_ictFvgZones, g_ictFvgCount, sym, visH1); + for(int i = 0; i < g_ictFvgCount; i++) { + if(!visH1[i]) + continue; if(!IctFvg_MatchesBias(g_ictFvgZones[i])) continue; @@ -104,8 +204,18 @@ void IctFvgDraw_Render(const string sym) if(InpDrawConfirmFvg) { + // Lọc tương tự cho M5 FVG. M5 FVG đang được dùng cho MSS → ép vẽ. + bool visM5[]; + IctFvgDraw_BuildVisibleMask(g_ictConfirmFvgZones, g_ictConfirmFvgCount, sym, visM5); + const ulong activeM5 = IctLowTf_MssM5FvgId(); + for(int j = 0; j < g_ictConfirmFvgCount; j++) { + const bool isActive = (activeM5 != 0 && g_ictConfirmFvgZones[j].id == activeM5 && + IctLowTf_MssPhase() >= ICT_MSS_M5_FVG); + if(!visM5[j] && !isActive) + continue; + const string cid = IntegerToString((long)g_ictConfirmFvgZones[j].id); const string cpfx = ICT26_CFVG_PFX + cid + "_"; const datetime cStart = g_ictConfirmFvgZones[j].createdTime; @@ -115,9 +225,7 @@ void IctFvgDraw_Render(const string sym) clrDarkGreen : clrFireBrick; if(g_ictConfirmFvgZones[j].state == ICT_FVG_USED) cClr = InpFvgUsedColor; - - if(IctLowTf_MssM5FvgId() == g_ictConfirmFvgZones[j].id && - IctLowTf_MssPhase() >= ICT_MSS_M5_FVG) + if(isActive) cClr = (g_ictConfirmFvgZones[j].side == ICT_FVG_BULL) ? clrAqua : clrOrange; IctFvgDraw_Rect(cpfx + "BOX", cStart, g_ictConfirmFvgZones[j].upper, diff --git a/Include/ICT2026/IntradayStructure.mqh b/Include/ICT2026/IntradayStructure.mqh index c60b49e..1daf541 100644 --- a/Include/ICT2026/IntradayStructure.mqh +++ b/Include/ICT2026/IntradayStructure.mqh @@ -1,5 +1,36 @@ //+------------------------------------------------------------------+ -//| IntradayStructure.mqh — trend rõ (HH-HL/LH-LL) vs sớm (CHoCH) | +//| IntradayStructure.mqh — Intraday trend (H1) + IsAllowTrade gate | +//+------------------------------------------------------------------+ +//| TF: InpIntradayTf (default H1) | +//| | +//| Phân loại HH-HL / LH-LL bằng "2 đỉnh + 2 đáy gần nhất" trong | +//| InpIntradayRecentBars (default 80 H1 bars) — KHÔNG ghép leg 4 | +//| swing dài như Daily (tránh dùng cấu trúc cũ khi giá đã đổi). | +//| | +//| CHoCH ưu tiên trước HH-HL/LH-LL: nếu H[1].close phá Key cũ → | +//| chuyển sang "sớm" (Bull/Bear sớm) bất kể pivot vẫn còn HH-HL. | +//| | +//| Globals owned: | +//| g_ictIntraday — IctIntradayState (trend, swings, event, | +//| isAllowTrade) | +//| g_ictIntradayCtx — context truyền vào IctResolveTrend | +//| | +//| IsAllowTrade (truth table): | +//| Bias=BULL + Intraday∈{UP, BULL EARLY} ⇒ true | +//| Bias=BEAR + Intraday∈{DOWN, BEAR EARLY} ⇒ true | +//| Intraday=NONE / opposite ⇒ false | +//| | +//| Public API: | +//| bool IctIntraday_Init(sym) | +//| bool IctIntraday_Update(sym) — true khi có nến H1 mới | +//| void IctIntraday_UpdateAllowTrade() — tính lại isAllowTrade | +//| bool IctIntraday_IsAllowTrade() | +//| ENUM_ICT_TREND ICT2026_GetIntradayTrend() | +//| bool IctIntraday_TrendAlignsWithBias(bias, trend) — predicate | +//| | +//| Lưu ý v1.181: isAllowTrade KHÔNG còn dùng để gate MSS pipeline | +//| nữa (mặc định). Pipeline chạy theo Bias, isAllowTrade chỉ gate | +//| entry khi InpMssRequireIntradayAligned=true. | //+------------------------------------------------------------------+ #ifndef ICT2026_INTRADAYSTRUCTURE_MQH #define ICT2026_INTRADAYSTRUCTURE_MQH diff --git a/Include/ICT2026/Journal.mqh b/Include/ICT2026/Journal.mqh index 9e265ad..73573f8 100644 --- a/Include/ICT2026/Journal.mqh +++ b/Include/ICT2026/Journal.mqh @@ -1,5 +1,23 @@ //+------------------------------------------------------------------+ -//| Journal.mqh — log lý do chặn MSS/entry vào Experts journal | +//| Journal.mqh — log MSS pipeline + entry block reasons (de-dup) | +//+------------------------------------------------------------------+ +//| Log lên Experts Journal kèm de-dup (cùng reason consecutive → | +//| skip) để journal không spam khi pipeline đứng yên. | +//| | +//| Skip nếu: InpOnlyStatsMode = true (silent tester mode v1.162) | +//| | +//| 2 channels: | +//| IctMss_JournalPipeline(reason) — phase transition / wait | +//| IctMss_JournalEntryBlock(reason) — lý do block không entry | +//| IctMss_JournalReset() — reset cache khi setup reset | +//| | +//| Globals owned: | +//| g_ictMssJournalLast — cache last pipeline reason | +//| g_ictMssEntryJournalLast — cache last block reason | +//| | +//| Helper: | +//| IctMss_IsEntryReadyReason(reason) — true nếu reason hợp lệ | +//| (không thuộc "block" — e.g. "READY", "Limit placed") | //+------------------------------------------------------------------+ #ifndef ICT2026_JOURNAL_MQH #define ICT2026_JOURNAL_MQH diff --git a/Include/ICT2026/LowTfApi.mqh b/Include/ICT2026/LowTfApi.mqh index 6cbe646..7b46672 100644 --- a/Include/ICT2026/LowTfApi.mqh +++ b/Include/ICT2026/LowTfApi.mqh @@ -1,5 +1,21 @@ //+------------------------------------------------------------------+ -//| LowTfApi.mqh — state Low TF / MSS (tránh include vòng) | +//| LowTfApi.mqh — shared state cho Low TF pipeline (anti-cycle) | +//+------------------------------------------------------------------+ +//| Tách g_ictLowTf khỏi LowTfTrend.mqh để các module MssSetup / | +//| MssEntry / FvgDraw / MssDraw / EaState có thể include shared | +//| state mà KHÔNG tạo vòng phụ thuộc với LowTfTrend.mqh. | +//| | +//| Globals owned: | +//| g_ictLowTf — IctLowTfState { | +//| lastBarTime, lastConfirmBarTime, | +//| mss (IctMssState — phase, h1FvgId, chochLocked, …), | +//| activeCount, availableCount, confirmFvgCount, | +//| displayReason | +//| } | +//| | +//| Helpers chính: | +//| ENUM_ICT_FVG_SIDE IctFvgSideFromBias(bias) | +//| string IctFvgSideText(side) / IctMarketSideText(side) | //+------------------------------------------------------------------+ #ifndef ICT2026_LOWTFAPI_MQH #define ICT2026_LOWTFAPI_MQH diff --git a/Include/ICT2026/LowTfTrend.mqh b/Include/ICT2026/LowTfTrend.mqh index 87c3ff1..5009ebe 100644 --- a/Include/ICT2026/LowTfTrend.mqh +++ b/Include/ICT2026/LowTfTrend.mqh @@ -1,5 +1,32 @@ //+------------------------------------------------------------------+ -//| LowTfTrend.mqh — Low TF layer: iTF FVG khi IsAllowTrade | +//| LowTfTrend.mqh — Orchestrator cho FVG + MSS pipeline | +//+------------------------------------------------------------------+ +//| File này KHÔNG chứa logic core. Chỉ điều phối: | +//| 1) Nến mới `InpFvgTf` (H1) ⇒ IctLowTfTrend_Update: | +//| - IctIntraday_UpdateAllowTrade | +//| - IctFvg_UpdateAll (state + fill + PD) | +//| - IctFvg_ScanNew (FVG thuận Bias) — nếu Bias rõ | +//| - Nến mới `InpConfirmTf` (M5) ⇒ IctConfirmFvg_UpdateAll, | +//| IctMss_Update, IctMssEntry_Update, IctEaState_Refresh | +//| - Render: IctFvgDraw_Render, IctMssDraw_Render | +//| | +//| 2) Tick (giữa nến) ⇒ IctLowTfTrend_TickRefresh: | +//| - IctFvg_UpdateZoneState mỗi FVG (refresh fill ratio) | +//| - IctFvg_UpdateAllPd (PD update khi pivot thay đổi) | +//| - Nếu Bias rõ: IctMss_Update + IctMssEntry_Update on tick | +//| (catch live MSS lock + per-tick checks) | +//| | +//| Pipeline gate (v1.170): chỉ check `g_ictDailyBias.bias != NONE`. | +//| Không gate theo IsAllowTrade (cho phép MSS detect khi intraday | +//| transition). | +//| | +//| Globals dùng: | +//| g_ictLowTf (từ LowTfApi.mqh) — state aggregate | +//| | +//| Public API: | +//| bool IctLowTfTrend_Init(sym) | +//| bool IctLowTfTrend_Update(sym, force=false) | +//| void IctLowTfTrend_TickRefresh(sym) | //+------------------------------------------------------------------+ #ifndef ICT2026_LOWTFTREND_MQH #define ICT2026_LOWTFTREND_MQH diff --git a/Include/ICT2026/MssDraw.mqh b/Include/ICT2026/MssDraw.mqh index f85e434..3ab96bb 100644 --- a/Include/ICT2026/MssDraw.mqh +++ b/Include/ICT2026/MssDraw.mqh @@ -1,5 +1,30 @@ //+------------------------------------------------------------------+ -//| MssDraw.mqh — vẽ MSS sau H1 chạm FVG: touch, 38.2%, CHoCH, H0 | +//| MssDraw.mqh — render MSS objects (touch line, keylv, entry/SL/TP)| +//+------------------------------------------------------------------+ +//| Vẽ khi phase >= H1_TOUCH && h1FvgId > 0. Hết phase ⇒ DeleteAll. | +//| | +//| Objects: | +//| H1_TOUCH (vline + label) — TS giá chạm H1 FVG (dodger blue) | +//| H1_382 (hline) — mức touch fill % tham khảo | +//| CHOCH (hline + label): | +//| - dotted gold + "L0 live"/"H0 live" khi phase=H1_TOUCH | +//| && !chochLocked | +//| - solid gold + "MSS↓ L0"/"MSS↑ H0" khi chochLocked | +//| MSS_SW (hline) — swing đối diện (SL anchor) — yellow | +//| ENTRY/SL/TP (hline) — khi pendingEntry > 0 + phase >= M5_FVG | +//| | +//| Lưu ý visibility: | +//| - "live" label chỉ vẽ khi UpdateLiveM5Swings tìm được pivot | +//| hợp lệ (đủ cấu trúc 2 đỉnh/đáy SAU touch — v1.165/1.171) | +//| - Phase reset (TP/SL/EOD/timeout/stale/bias flip) ⇒ DeleteAll | +//| | +//| Skip nếu: InpOnlyStatsMode = true | +//| | +//| Object prefix: ICT26_MSS_ | +//| | +//| Public API: | +//| void IctMssDraw_Render(sym) | +//| void IctMssDraw_DeleteAll() | //+------------------------------------------------------------------+ #ifndef ICT2026_MSSDRAW_MQH #define ICT2026_MSSDRAW_MQH diff --git a/Include/ICT2026/MssEntry.mqh b/Include/ICT2026/MssEntry.mqh index 4f9898e..44114c2 100644 --- a/Include/ICT2026/MssEntry.mqh +++ b/Include/ICT2026/MssEntry.mqh @@ -1,5 +1,64 @@ //+------------------------------------------------------------------+ -//| MssEntry.mqh — Limit @ M5 FVG edge | SL H0/L0 M5 | TP min RR | +//| MssEntry.mqh — Order management cho MSS pipeline | +//+------------------------------------------------------------------+ +//| Trách nhiệm: tính SL/TP/lot, đặt + sửa + huỷ pending limit, | +//| theo dõi position để partial close + BE + cancel. | +//| | +//| Gating (từ ngoài vào trong): | +//| gate1: InpMssTradeEnabled | +//| gate2: phase >= CHOCH && chochLocked && chochKeyLevel > 0 | +//| gate3: !intradayBlock | +//| intradayBlock = InpMssRequireIntradayAligned | +//| && !isAllowTrade (v1.181) | +//| gate4: !InpMssOnePosition || no existing position+pending | +//| | +//| ComputeLevels (v1.164 + v1.184): | +//| side ← (m5Zone.side) hoặc chochBias | +//| SL ← max(H0,H1)+buf (BEAR) / min(L0,L1)-buf (BULL) | +//| buf = InpMssSlSpreadMult × spread | +//| 2 candidates entry: | +//| A) M5 FVG limit price | +//| B) MSS keyLV (chochKeyLevel) | +//| ⇒ chọn cái có risk = |entry-SL| nhỏ hơn (đúng phía thị trường) | +//| TP target = iH0/iL0 (sóng H1): | +//| RR(TP@swing) > InpMssMinRR ⇒ TP @ swing - InpMssTpSpreadMult | +//| else ⇒ TP = entry ± risk × InpMssMinRR (2R fixed) | +//| partialTrigger = swingTp nếu TP gồng xa hơn swing | +//| ⇒ partial 50% + SL→BE khi giá đạt swingTp | +//| | +//| CHECK CUỐI (v1.184): | +//| |entry - market| ≤ InpMssMaxLimitDistAtrMult × ATR(FvgTf) | +//| Quá xa ⇒ skip + mark FVG used + reset state | +//| | +//| Lot sizing: IctMssEntry_VolumeForRisk uses InpMssRiskPct × balance| +//| | +//| Per-tick checks (gọi trong IctMssEntry_Update): | +//| IctMssEntry_CheckEodCancel(sym) — EOD phiên Mỹ | +//| IctMssEntry_CheckPendingTimeout(sym) — timeout 1h | +//| IctMssEntry_CheckTpReachedBeforeFill(sym) — TP chạm trước fill| +//| (v1.185) | +//| IctMssEntry_CheckStaleLimit(sym) — limit xa giá | +//| > N×ATR (v1.184)| +//| IctMssEntry_CheckBreakevenAtRR(sym) — SL→entry @ N×R | +//| (v1.176-7, off) | +//| IctMssEntry_CheckPartialClose(sym) — partial 50% + | +//| SL→BE @ swingTp | +//| | +//| Globals owned: | +//| g_ictMssTrade — CTrade instance dùng cho tất cả order ops | +//| | +//| Lifecycle: | +//| IctMss_OnPositionClosed (gọi từ OnTradeTransaction trong EA): | +//| - MarkM5FvgUsed | +//| - IctMss_ResetState | +//| - set g_ictMssAfterCloseGuard = TimeCurrent | +//| - reset về WAIT_FVG_TOUCH | +//| | +//| Public API (chính): | +//| void IctMssEntry_Init() / IctMssEntry_Update(sym) | +//| bool IctMssEntry_ComputeLevels(sym, m5Zone, &entry, &sl, &tp, | +//| &reasonOut) | +//| void IctMss_OnPositionClosed(sym, reason, netProfit) | //+------------------------------------------------------------------+ #ifndef ICT2026_MSSENTRY_MQH #define ICT2026_MSSENTRY_MQH @@ -64,6 +123,32 @@ double IctMssEntry_LimitPrice(const IctFvgZone &zone) return 0.0; } +// Cap khoảng cách entry-giá theo bội số ATR(FvgTf): +// BUY ⇒ distance = Ask - entry (entry phải nằm dưới Ask cho limit buy) +// SELL ⇒ distance = entry - Bid (entry phải nằm trên Bid cho limit sell) +// Trả về true nếu distance > InpMssMaxLimitDistAtrMult × ATR ⇒ "limit quá xa" (chết). +bool IctMssEntry_IsLimitTooFar(const string sym, const double entry, const bool isBuy, + double &distOut, double &thresholdOut) +{ + distOut = 0.0; + thresholdOut = 0.0; + if(InpMssMaxLimitDistAtrMult <= 0.0) + return false; + + const double atr = IctFvg_AtrFvgTf(sym); + if(atr <= 0.0) + return false; + + const double mkt = isBuy ? SymbolInfoDouble(sym, SYMBOL_ASK) + : SymbolInfoDouble(sym, SYMBOL_BID); + if(mkt <= 0.0 || entry <= 0.0) + return false; + + distOut = isBuy ? (mkt - entry) : (entry - mkt); + thresholdOut = atr * InpMssMaxLimitDistAtrMult; + return (distOut > thresholdOut + _Point); +} + bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone, double &entryOut, double &slOut, double &tpOut, string &reasonOut) @@ -166,6 +251,18 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone, return false; } + // Cap khoảng cách entry-giá: tránh đặt limit "chết" sau khi MSS confirm muộn + // ⇒ limit phải nằm trong InpMssMaxLimitDistAtrMult × ATR(FvgTf) tính từ giá hiện tại + double tooFarDist = 0.0, tooFarThresh = 0.0; + if(IctMssEntry_IsLimitTooFar(sym, entryOut, isBuy, tooFarDist, tooFarThresh)) + { + reasonOut = StringFormat( + "Entry xa giá %.0f pts > %.0f pts (%.1f×ATR) — bỏ qua [src=%s]", + tooFarDist / _Point, tooFarThresh / _Point, + InpMssMaxLimitDistAtrMult, entrySource); + return false; + } + // ── TP target = iL0 (BEAR) / iH0 (BULL): sóng H1, m5 chỉ để entry // TP đặt CÁCH target một buffer (gần chạm — không chờ hit chính xác) const IctSwingSet iSw = g_ictIntraday.swings; @@ -352,6 +449,198 @@ void IctMss_OnPendingTimeout(const string sym, const ulong ticket, const int hou TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES)); } +// Cancel pending + mark FVG used + reset state khi limit rời xa giá vượt cap. +// ⇒ tránh case MSS confirm muộn → limit đặt rất xa hiện tại, không bao giờ khớp. +// Trigger: +// - Bị gọi mỗi tick trong IctMssEntry_Update +// - InpMssCancelStaleLimit phải bật +// - Có pendingTicket hợp lệ (đã đặt limit) +// - (mkt - entry) (BUY) hoặc (entry - mkt) (SELL) > InpMssMaxLimitDistAtrMult × ATR(FvgTf) +void IctMssEntry_CheckStaleLimit(const string sym) +{ + if(!InpMssCancelStaleLimit) + return; + if(InpMssMaxLimitDistAtrMult <= 0.0) + return; + if(g_ictLowTf.mss.pendingTicket == 0) + return; + if(!OrderSelect(g_ictLowTf.mss.pendingTicket)) + return; + + const ENUM_ORDER_TYPE otype = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); + if(otype != ORDER_TYPE_BUY_LIMIT && otype != ORDER_TYPE_SELL_LIMIT) + return; + + const bool isBuy = (otype == ORDER_TYPE_BUY_LIMIT); + const double entry = OrderGetDouble(ORDER_PRICE_OPEN); + + double dist = 0.0, thresh = 0.0; + if(!IctMssEntry_IsLimitTooFar(sym, entry, isBuy, dist, thresh)) + return; + + const ulong ticket = g_ictLowTf.mss.pendingTicket; + const ulong m5Id = g_ictLowTf.mss.m5FvgId; + const ulong h1Id = g_ictLowTf.mss.h1FvgId; + + g_ictMssTrade.OrderDelete(ticket); + g_ictLowTf.mss.pendingTicket = 0; + IctMssEntry_MarkM5FvgUsed(sym, m5Id); + IctMss_ResetState(); + g_ictMssAfterCloseGuard = TimeCurrent(); + + g_ictLowTf.mss.displayReason = StringFormat( + "Limit chết: cách giá %.0f pts > %.0f pts (%.1f×ATR) — cancel #%I64u + reset", + dist / _Point, thresh / _Point, InpMssMaxLimitDistAtrMult, ticket); + + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] Stale limit cancel %s #%I64u | dist=%.0f pts thresh=%.0f pts | H1 #%I64u | M5 #%I64u → Used | guard=%s | reset → WAIT_FVG_TOUCH", + isBuy ? "BUY" : "SELL", ticket, + dist / _Point, thresh / _Point, h1Id, m5Id, + TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES)); +} + +// Cancel pending limit khi giá chạm TP TRƯỚC khi limit khớp. +// ⇒ Tránh case: giá chạy thẳng đến TP target, không hồi lại entry, sau đó +// đảo chiều quay về khớp limit ở entry cũ → lệnh chạy ngược, SL. +// Logic: +// BUY limit: nếu Bid ≥ TP ⇒ giá đã chạm TP (lúc fill xong sẽ TP ngay) +// SELL limit: nếu Ask ≤ TP ⇒ giá đã chạm TP +// Sau cancel: mark FVG used + reset (giống stale limit) + guard sau-close. +void IctMssEntry_CheckTpReachedBeforeFill(const string sym) +{ + if(!InpMssCancelLimitWhenTpReached) + return; + if(g_ictLowTf.mss.pendingTicket == 0) + return; + if(!OrderSelect(g_ictLowTf.mss.pendingTicket)) + return; + + const ENUM_ORDER_TYPE otype = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); + if(otype != ORDER_TYPE_BUY_LIMIT && otype != ORDER_TYPE_SELL_LIMIT) + return; + + const bool isBuy = (otype == ORDER_TYPE_BUY_LIMIT); + const double tp = OrderGetDouble(ORDER_TP); + if(tp <= 0.0) + return; + + const double bid = SymbolInfoDouble(sym, SYMBOL_BID); + const double ask = SymbolInfoDouble(sym, SYMBOL_ASK); + + const bool reached = isBuy ? (bid >= tp - _Point) + : (ask <= tp + _Point); + if(!reached) + return; + + const ulong ticket = g_ictLowTf.mss.pendingTicket; + const ulong m5Id = g_ictLowTf.mss.m5FvgId; + const ulong h1Id = g_ictLowTf.mss.h1FvgId; + const double entry = OrderGetDouble(ORDER_PRICE_OPEN); + + g_ictMssTrade.OrderDelete(ticket); + g_ictLowTf.mss.pendingTicket = 0; + IctMssEntry_MarkM5FvgUsed(sym, m5Id); + IctMss_ResetState(); + g_ictMssAfterCloseGuard = TimeCurrent(); + + g_ictLowTf.mss.displayReason = StringFormat( + "Giá chạm TP trước khi khớp limit (TP=%.2f, %s=%.2f) — cancel #%I64u + reset", + tp, isBuy ? "Bid" : "Ask", isBuy ? bid : ask, ticket); + + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] TP-reached cancel %s #%I64u | entry=%.5f tp=%.5f bid=%.5f ask=%.5f | H1 #%I64u | M5 #%I64u → Used | guard=%s | reset → WAIT_FVG_TOUCH", + isBuy ? "BUY" : "SELL", ticket, + entry, tp, bid, ask, h1Id, m5Id, + TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES)); +} + +// Dời SL về entry (BE) khi lệnh đi được N×R (mặc định 2R) — bảo toàn vốn sớm. +// - Không partial close (khác với CheckPartialClose ở swing iL0/iH0) +// - Skip nếu BE đã move (beMovedDone) hoặc partial close đã chạy (đã set BE) +void IctMssEntry_CheckBreakevenAtRR(const string sym) +{ + if(!InpMssBeEnabled) + return; + if(InpMssBeAtRR <= 0.0) + return; + if(g_ictLowTf.mss.beMovedDone || g_ictLowTf.mss.partialCloseDone) + return; + if(g_ictLowTf.mss.pendingEntry <= 0.0 || g_ictLowTf.mss.pendingSl <= 0.0) + return; + + const double entry = g_ictLowTf.mss.pendingEntry; + const double slOrig = g_ictLowTf.mss.pendingSl; + const double risk = MathAbs(entry - slOrig); + if(risk <= 0.0) + return; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + const ulong ticket = PositionGetTicket(i); + if(ticket == 0 || !PositionSelectByTicket(ticket)) + continue; + if(PositionGetString(POSITION_SYMBOL) != sym) + continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMssMagic) + continue; + + const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + const double openPx = PositionGetDouble(POSITION_PRICE_OPEN); + const double curSl = PositionGetDouble(POSITION_SL); + const double curTp = PositionGetDouble(POSITION_TP); + const double bid = SymbolInfoDouble(sym, SYMBOL_BID); + const double ask = SymbolInfoDouble(sym, SYMBOL_ASK); + + bool reached = false; + double trigPx = 0.0; + if(ptype == POSITION_TYPE_BUY) + { + trigPx = openPx + InpMssBeAtRR * risk; + reached = (bid >= trigPx - _Point); + } + else if(ptype == POSITION_TYPE_SELL) + { + trigPx = openPx - InpMssBeAtRR * risk; + reached = (ask <= trigPx + _Point); + } + + if(!reached) + return; + + // Skip nếu SL hiện tại đã ≥ BE (tránh dời lùi) + bool slAlreadyAtBe = false; + if(ptype == POSITION_TYPE_BUY) + slAlreadyAtBe = (curSl >= openPx - _Point); + else + slAlreadyAtBe = (curSl > 0.0 && curSl <= openPx + _Point); + + if(slAlreadyAtBe) + { + g_ictLowTf.mss.beMovedDone = true; + return; + } + + g_ictMssTrade.SetExpertMagicNumber(InpMssMagic); + if(!g_ictMssTrade.PositionModify(ticket, openPx, curTp)) + { + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] BE@%.1fR move fail #%I64u — %d %s", + InpMssBeAtRR, ticket, g_ictMssTrade.ResultRetcode(), + g_ictMssTrade.ResultRetcodeDescription()); + return; + } + + g_ictLowTf.mss.beMovedDone = true; + + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] BE@%.1fR #%I64u price=%.2f → SL %.2f→%.2f (entry) | TP %.2f", + InpMssBeAtRR, ticket, + (ptype == POSITION_TYPE_BUY ? bid : ask), + curSl, openPx, curTp); + return; + } +} + // Chốt 50% volume + dời SL về BE khi giá đạt swing iL0/iH0 (chỉ áp dụng khi TP gồng xa hơn swing) void IctMssEntry_CheckPartialClose(const string sym) { @@ -581,6 +870,9 @@ void IctMssEntry_Update(const string sym) IctMssEntry_CheckEodCancel(sym); IctMssEntry_CheckPendingTimeout(sym); + IctMssEntry_CheckTpReachedBeforeFill(sym); + IctMssEntry_CheckStaleLimit(sym); + IctMssEntry_CheckBreakevenAtRR(sym); IctMssEntry_CheckPartialClose(sym); if(g_ictLowTf.mss.phase == ICT_MSS_IDLE && g_ictLowTf.mss.pendingTicket > 0) @@ -597,8 +889,14 @@ void IctMssEntry_Update(const string sym) } // Cho phép entry từ CHOCH trở lên (không bắt buộc M5 FVG): - // ComputeLevels sẽ chọn entry giữa M5 FVG (nếu có) và MSS keyLV (luôn có sau lock) - if(!g_ictIntraday.isAllowTrade || + // ComputeLevels sẽ chọn entry giữa M5 FVG (nếu có) và MSS keyLV (luôn có sau lock). + // + // Intraday gate: CHỈ chặn khi InpMssRequireIntradayAligned=true. + // Mặc định (false) ⇒ entry chỉ dựa vào Daily Bias, không quan tâm Intraday trend + // (tránh miss setup khi trend chuyển hướng muộn). + const bool intradayBlock = (InpMssRequireIntradayAligned && !g_ictIntraday.isAllowTrade); + + if(intradayBlock || g_ictLowTf.mss.phase < ICT_MSS_CHOCH || !g_ictLowTf.mss.chochLocked || g_ictLowTf.mss.chochKeyLevel <= 0.0) @@ -610,8 +908,8 @@ void IctMssEntry_Update(const string sym) } if(g_ictLowTf.mss.phase >= ICT_MSS_H1_TOUCH) { - if(!g_ictIntraday.isAllowTrade) - IctMss_JournalEntryBlock("AllowTrade=false"); + if(intradayBlock) + IctMss_JournalEntryBlock("Intraday ngược Bias (InpMssRequireIntradayAligned=true)"); else if(g_ictLowTf.mss.phase < ICT_MSS_CHOCH) IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason); else @@ -648,6 +946,27 @@ void IctMssEntry_Update(const string sym) IctMssPhaseText(g_ictLowTf.mss.phase), lvlReason); IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason); + + // Nếu fail vì "Entry xa giá" + bật cancel-stale ⇒ reset setup ngay + // (tránh stuck CHOCH với entry không bao giờ khớp; chờ POI/touch mới) + if(InpMssCancelStaleLimit && StringFind(lvlReason, "Entry xa giá") == 0) + { + const ulong h1Id = g_ictLowTf.mss.h1FvgId; + const ulong m5Id = g_ictLowTf.mss.m5FvgId; + if(g_ictLowTf.mss.pendingTicket > 0) + { + IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket); + g_ictLowTf.mss.pendingTicket = 0; + } + IctMssEntry_MarkM5FvgUsed(sym, m5Id); + IctMss_ResetState(); + g_ictMssAfterCloseGuard = TimeCurrent(); + g_ictLowTf.mss.displayReason = StringFormat( + "%s | %s — reset, chờ POI/touch mới", IctMssPhaseText(ICT_MSS_IDLE), lvlReason); + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] Skip entry (too far): %s | H1 #%I64u | M5 #%I64u → Used | reset", + lvlReason, h1Id, m5Id); + } return; } diff --git a/Include/ICT2026/MssSetup.mqh b/Include/ICT2026/MssSetup.mqh index 7ad0110..9d69423 100644 --- a/Include/ICT2026/MssSetup.mqh +++ b/Include/ICT2026/MssSetup.mqh @@ -1,10 +1,60 @@ //+------------------------------------------------------------------+ -//| MssSetup.mqh — MSS: retest FVG H1 → M5 MSS (phá L0/H0) → M5 FVG → entry | +//| MssSetup.mqh — MSS pipeline (setup phase, không đặt order) | +//+------------------------------------------------------------------+ +//| Flow: | +//| IDLE | +//| │ IctMss_SelectNearestH1Poi() ⇒ chọn H1 FVG gần giá nhất | +//| │ IctMss_HasFreshFvgTouch() ⇒ touch hợp lệ | +//| ↓ | +//| H1_TOUCH | +//| │ IctMss_UpdateLiveM5Swings() ⇒ liveL0/H0/L1/H1 structural | +//| │ (v1.171: keylv lock) | +//| │ IctMss_TryLockMss() ⇒ break body M5 qua L0/H0 | +//| │ veto MSS nếu InpMssRequireIntradayAligned && ngược bias | +//| ↓ | +//| CHOCH (locked) | +//| │ IctConfirmFvg_ScanNew() ⇒ tìm M5 FVG sau chochTime | +//| │ IctMss_ZonesOverlapH1() ⇒ M5 FVG gần H1 FVG | +//| ↓ | +//| M5_FVG → ENTRY_FILL → READY (chờ giá hồi vào M5 FVG) | +//| | +//| FVG fresh touch (v1.182) — cutoff bias-aware: | +//| cutoff = max(g_ictMssAfterCloseGuard, | +//| g_ictBiasInto{Bull,Bear}Time) | +//| Touch hợp lệ phải xảy ra SAU cutoff. | +//| | +//| Structural keylv (v1.171) — không phải "pivot mới nhất": | +//| BEAR (pullback UP): | +//| liveH0 = đỉnh CAO NHẤT sau touch (extreme) | +//| liveL0 = đáy gần nhất trước liveH0 (keylv MSS↓) | +//| liveH1 = đỉnh trước liveL0 | +//| BULL (pullback DOWN): | +//| liveL0 = đáy THẤP NHẤT sau touch (extreme) | +//| liveH0 = đỉnh gần nhất trước liveL0 (keylv MSS↑) | +//| liveL1 = đáy trước liveH0 | +//| ⇒ "lock" tự nhiên: pivot SAU extreme không đổi keylv | +//| | +//| MSS lock yêu cầu (v1.165): | +//| - cấu trúc đủ 2 đỉnh/đáy SAU touch | +//| - body M5 phá liveL0 (BEAR) / liveH0 (BULL) | +//| | +//| Globals owned: | +//| g_ictMssAfterCloseGuard — TS sau khi 1 lệnh đóng (TP/SL/timeout/| +//| stale limit): pipeline phải đợi touch fresh sau TS này | +//| | +//| Public API (chính): | +//| void IctMss_Update(sym) | +//| bool IctMss_HasFreshFvgTouch(sym, &h1Zone) | +//| datetime IctMss_GetFvgTouchTime(sym, &h1Zone) | +//| void IctMss_ResetState() | +//| bool IctMss_TryLockMss(sym, tf, h1, bias) | +//| int IctMss_CountH1PoiEligible() | //+------------------------------------------------------------------+ #ifndef ICT2026_MSSSETUP_MQH #define ICT2026_MSSSETUP_MQH #include +#include #include #include #include @@ -109,16 +159,18 @@ bool IctMss_LivePriceTouchesFvg(const string sym, const IctFvgZone &h1) return IctMss_PriceInsideFvg(h1, bid) || IctMss_PriceInsideFvg(h1, ask); } -datetime IctMss_FirstM5TouchInFvg(const string sym, const IctFvgZone &h1) +// Touch M5 đầu tiên sau `since` (0 = bất kỳ) +datetime IctMss_FirstM5TouchInFvg(const string sym, const IctFvgZone &h1, const datetime since = 0) { const ENUM_TIMEFRAMES tf = InpConfirmTf; const int lim = MathMin(InpMssConfirmLookback, iBars(sym, tf) - 2); + const datetime minT = (since > h1.createdTime) ? since : h1.createdTime; datetime best = 0; for(int sh = lim; sh >= 1; sh--) { const datetime t = iTime(sym, tf, sh); - if(t <= h1.createdTime) - break; + if(t <= minT) + continue; if(!IctMss_BarOverlapsFvg(h1, iHigh(sym, tf, sh), iLow(sym, tf, sh))) continue; if(best == 0 || t < best) @@ -127,17 +179,41 @@ datetime IctMss_FirstM5TouchInFvg(const string sym, const IctFvgZone &h1) return best; } +// Cutoff bias-aware: touch chỉ hợp lệ khi xảy ra SAU thời điểm Bias chuyển vào +// side hiện tại VÀ sau thời điểm lệnh trước đóng. +datetime IctMss_GetFvgTouchCutoff() +{ + datetime cutoff = 0; + if(g_ictDailyBias.bias == ICT_BIAS_BULL && g_ictBiasIntoBullTime > 0) + cutoff = g_ictBiasIntoBullTime; + else if(g_ictDailyBias.bias == ICT_BIAS_BEAR && g_ictBiasIntoBearTime > 0) + cutoff = g_ictBiasIntoBearTime; + if(g_ictMssAfterCloseGuard > cutoff) + cutoff = g_ictMssAfterCloseGuard; + return cutoff; +} + datetime IctMss_GetFvgTouchTime(const string sym, IctFvgZone &h1) { IctFvg_UpdateZoneState(sym, InpFvgTf, h1); + const datetime cutoff = IctMss_GetFvgTouchCutoff(); - datetime t = h1.firstTouchTime; - const datetime tM5 = IctMss_FirstM5TouchInFvg(sym, h1); + // firstTouchTime chỉ hợp lệ nếu xảy ra sau cutoff + datetime t = 0; + if(h1.firstTouchTime > 0 && h1.firstTouchTime >= cutoff) + t = h1.firstTouchTime; + + // Quét M5 bars để tìm touch fresh sau cutoff (bao gồm cả re-touch sau bias flip) + const datetime tM5 = IctMss_FirstM5TouchInFvg(sym, h1, cutoff); if(tM5 > 0 && (t <= 0 || tM5 < t)) t = tM5; if(t <= 0 && IctMss_LivePriceTouchesFvg(sym, h1)) - t = iTime(sym, InpConfirmTf, 0); + { + const datetime curBar = iTime(sym, InpConfirmTf, 0); + if(curBar >= cutoff) + t = curBar; + } return t; } @@ -148,16 +224,15 @@ bool IctMss_HasFvgPriceTouch(const string sym, IctFvgZone &h1) return (IctMss_GetFvgTouchTime(sym, h1) > 0); } -// Touch hợp lệ cho POI mới = touch xảy ra SAU thời điểm lệnh trước đóng + đạt FILL threshold theo PD position. -// - FVG nằm hẳn trong vùng đúng chiều → InpFvgTouchFillPure (25%) -// - FVG straddle equilibrium → InpFvgTouchFillMixed (50%) +// Touch hợp lệ cho POI mới — cutoff đã được apply trong `IctMss_GetFvgTouchTime`: +// - touch xảy ra SAU lệnh trước đóng (g_ictMssAfterCloseGuard) +// - touch xảy ra SAU Bias chuyển vào side hiện tại (g_ictBiasInto*Time) +// - đạt FILL threshold theo PD position (Pure 25% / Mixed 50%) bool IctMss_HasFreshFvgTouch(const string sym, IctFvgZone &h1) { const datetime tTouch = IctMss_GetFvgTouchTime(sym, h1); if(tTouch <= 0) return false; - if(g_ictMssAfterCloseGuard > 0 && tTouch < g_ictMssAfterCloseGuard) - return false; const double requiredPct = IctFvg_RequiredTouchFillPct(h1); if(requiredPct >= 100.0 - 1e-6) @@ -289,6 +364,29 @@ bool IctMss_HasM5BarInH1Fvg(const string sym, const IctFvgZone &h1, return false; } +// Structural keylv — KHÔNG dùng "pivot mới nhất" (nhảy mỗi nến). +// Định nghĩa: keylv = pivot GẦN NHẤT TRƯỚC extreme của pullback ⇒ +// tự nhiên "lock" tới khi pullback mở rộng (BEAR: tạo high mới cao hơn; +// BULL: tạo low mới thấp hơn). +// +// BEAR bias (pullback UP, mục tiêu MSS↓ phá L0): +// liveH0 = ĐỈNH CAO NHẤT sau tTouch (extreme đỉnh pullback) +// liveL0 = đáy gần nhất theo time TRƯỚC liveH0 (keylv — "đáy tạo ra đỉnh cao nhất") +// liveH1 = đỉnh gần nhất TRƯỚC liveL0 (đỉnh cũ — đủ cấu trúc 2 đỉnh) +// liveL1 = đáy gần nhất TRƯỚC liveH1 (đáy cũ — đủ cấu trúc 2 đáy) +// SL = max(H0, H1) + buffer +// +// BULL bias (pullback DOWN, mục tiêu MSS↑ phá H0): +// liveL0 = ĐÁY THẤP NHẤT sau tTouch (extreme đáy pullback) +// liveH0 = đỉnh gần nhất theo time TRƯỚC liveL0 (keylv — "đỉnh tạo ra đáy thấp nhất") +// liveL1 = đáy gần nhất TRƯỚC liveH0 (đáy cũ — đủ cấu trúc 2 đáy) +// liveH1 = đỉnh gần nhất TRƯỚC liveL1 (đỉnh cũ — đủ cấu trúc 2 đỉnh) +// SL = min(L0, L1) − buffer +// +// Nhờ tham chiếu "TRƯỚC extreme": +// - Đỉnh mới tạo SAU extreme low (bullish bias) ⇒ KHÔNG ảnh hưởng keylv +// - Đáy mới tạo SAU extreme high (bearish bias) ⇒ KHÔNG ảnh hưởng keylv +// ⇒ keylv chỉ "nhảy" khi pullback thực sự mở rộng tạo extreme mới bool IctMss_UpdateLiveM5Swings(const string sym, const ENUM_ICT_BIAS bias, const datetime tTouch) { @@ -301,38 +399,184 @@ bool IctMss_UpdateLiveM5Swings(const string sym, const ENUM_ICT_BIAS bias, g_ictLowTf.mss.liveL1Time = 0; g_ictLowTf.mss.liveH1Time = 0; - IctSwingSet sw; - ENUM_ICT_STRUCT st = ICT_STRUCT_NONE; - if(!IctBuildConfirmSwingSet(sym, sw, st)) + if(bias != ICT_BIAS_BEAR && bias != ICT_BIAS_BULL) return false; - if(bias == ICT_BIAS_BEAR || bias == ICT_BIAS_BULL) + // BẮT BUỘC có tTouch hợp lệ — nếu không pipeline sẽ lấy cả pivot trước touch + // (filter `time < tTouch` không có tác dụng khi tTouch = 0). + if(tTouch <= 0) + return false; + + IctSwingPoint highs[], lows[]; + IctCollectSwings(sym, InpConfirmTf, InpConfirmSwingRange, + InpConfirmSwingLookback, highs, lows); + const int nH = ArraySize(highs); + const int nL = ArraySize(lows); + if(nH == 0 || nL == 0) + return false; + + if(bias == ICT_BIAS_BEAR) { - if(!sw.hasL0 || !sw.hasH0) - return false; - if(sw.l0.time < tTouch && sw.h0.time < tTouch) - return false; - g_ictLowTf.mss.liveL0Price = sw.l0.price; - g_ictLowTf.mss.liveL0Time = sw.l0.time; - g_ictLowTf.mss.liveH0Price = sw.h0.price; - g_ictLowTf.mss.liveH0Time = sw.h0.time; - if(sw.hasL1) + // 1) liveH0 = đỉnh cao nhất sau tTouch (extreme của pullback up) + int idxH0 = -1; + double maxHigh = -DBL_MAX; + for(int i = 0; i < nH; i++) { - g_ictLowTf.mss.liveL1Price = sw.l1.price; - g_ictLowTf.mss.liveL1Time = sw.l1.time; + if(highs[i].time < tTouch) continue; + if(highs[i].price > maxHigh) + { + maxHigh = highs[i].price; + idxH0 = i; + } } - if(sw.hasH1) + if(idxH0 < 0) return false; + g_ictLowTf.mss.liveH0Price = highs[idxH0].price; + g_ictLowTf.mss.liveH0Time = highs[idxH0].time; + const datetime tH0 = highs[idxH0].time; + + // 2) liveL0 = đáy GẦN NHẤT THEO TIME TRƯỚC liveH0 (keylv MSS↓) + int idxL0 = -1; + datetime tL0Best = 0; + for(int i = 0; i < nL; i++) { - g_ictLowTf.mss.liveH1Price = sw.h1.price; - g_ictLowTf.mss.liveH1Time = sw.h1.time; + if(lows[i].time < tTouch) continue; + if(lows[i].time >= tH0) continue; + if(lows[i].time > tL0Best) + { + tL0Best = lows[i].time; + idxL0 = i; + } + } + if(idxL0 < 0) return false; + g_ictLowTf.mss.liveL0Price = lows[idxL0].price; + g_ictLowTf.mss.liveL0Time = lows[idxL0].time; + const datetime tL0 = lows[idxL0].time; + + // 3) liveH1 = đỉnh GẦN NHẤT THEO TIME TRƯỚC liveL0 + int idxH1 = -1; + datetime tH1Best = 0; + for(int i = 0; i < nH; i++) + { + if(highs[i].time < tTouch) continue; + if(highs[i].time >= tL0) continue; + if(highs[i].time > tH1Best) + { + tH1Best = highs[i].time; + idxH1 = i; + } + } + if(idxH1 >= 0) + { + g_ictLowTf.mss.liveH1Price = highs[idxH1].price; + g_ictLowTf.mss.liveH1Time = highs[idxH1].time; + } + + // 4) liveL1 = đáy GẦN NHẤT THEO TIME TRƯỚC liveH1 (nếu có) + if(idxH1 >= 0) + { + int idxL1 = -1; + datetime tL1Best = 0; + for(int i = 0; i < nL; i++) + { + if(lows[i].time < tTouch) continue; + if(lows[i].time >= tH1Best) continue; + if(lows[i].time > tL1Best) + { + tL1Best = lows[i].time; + idxL1 = i; + } + } + if(idxL1 >= 0) + { + g_ictLowTf.mss.liveL1Price = lows[idxL1].price; + g_ictLowTf.mss.liveL1Time = lows[idxL1].time; + } } return true; } - return false; + // BULL bias (pullback DOWN) + // 1) liveL0 = đáy thấp nhất sau tTouch (extreme của pullback down) + int idxL0 = -1; + double minLow = DBL_MAX; + for(int i = 0; i < nL; i++) + { + if(lows[i].time < tTouch) continue; + if(lows[i].price < minLow) + { + minLow = lows[i].price; + idxL0 = i; + } + } + if(idxL0 < 0) return false; + g_ictLowTf.mss.liveL0Price = lows[idxL0].price; + g_ictLowTf.mss.liveL0Time = lows[idxL0].time; + const datetime tL0 = lows[idxL0].time; + + // 2) liveH0 = đỉnh GẦN NHẤT THEO TIME TRƯỚC liveL0 (keylv MSS↑) + int idxH0 = -1; + datetime tH0Best = 0; + for(int i = 0; i < nH; i++) + { + if(highs[i].time < tTouch) continue; + if(highs[i].time >= tL0) continue; + if(highs[i].time > tH0Best) + { + tH0Best = highs[i].time; + idxH0 = i; + } + } + if(idxH0 < 0) return false; + g_ictLowTf.mss.liveH0Price = highs[idxH0].price; + g_ictLowTf.mss.liveH0Time = highs[idxH0].time; + const datetime tH0 = highs[idxH0].time; + + // 3) liveL1 = đáy GẦN NHẤT THEO TIME TRƯỚC liveH0 + int idxL1 = -1; + datetime tL1Best = 0; + for(int i = 0; i < nL; i++) + { + if(lows[i].time < tTouch) continue; + if(lows[i].time >= tH0) continue; + if(lows[i].time > tL1Best) + { + tL1Best = lows[i].time; + idxL1 = i; + } + } + if(idxL1 >= 0) + { + g_ictLowTf.mss.liveL1Price = lows[idxL1].price; + g_ictLowTf.mss.liveL1Time = lows[idxL1].time; + } + + // 4) liveH1 = đỉnh GẦN NHẤT THEO TIME TRƯỚC liveL1 (nếu có) + if(idxL1 >= 0) + { + int idxH1 = -1; + datetime tH1Best = 0; + for(int i = 0; i < nH; i++) + { + if(highs[i].time < tTouch) continue; + if(highs[i].time >= tL1Best) continue; + if(highs[i].time > tH1Best) + { + tH1Best = highs[i].time; + idxH1 = i; + } + } + if(idxH1 >= 0) + { + g_ictLowTf.mss.liveH1Price = highs[idxH1].price; + g_ictLowTf.mss.liveH1Time = highs[idxH1].time; + } + } + return true; } // Hủy setup: H1 đóng (shift=1) — thân xuyên FVG (không giữ giá). Râu xuyên vẫn tiếp tục MSS. +// v1.186: dùng IctBodyBreakBuffer (N × spread) thay vì _Point — siết để +// một close-through "sát mép" do nhiễu không làm invalidate setup. bool IctMss_H1BodyInvalidatedSetup(const string sym, const IctFvgZone &h1, const ENUM_ICT_BIAS bias) { @@ -340,10 +584,11 @@ bool IctMss_H1BodyInvalidatedSetup(const string sym, const IctFvgZone &h1, return false; const double cls = iClose(sym, InpFvgTf, 1); + const double buf = IctBodyBreakBuffer(sym); if(bias == ICT_BIAS_BEAR) - return (cls > h1.upper + _Point); + return (cls > h1.upper + buf); if(bias == ICT_BIAS_BULL) - return (cls < h1.lower - _Point); + return (cls < h1.lower - buf); return false; } @@ -875,6 +1120,22 @@ void IctMss_Update(const string sym) if(!IctMss_TryLockMss(sym, cTf, g_ictFvgZones[h1Idx], g_ictDailyBias.bias)) return; + // Veto MSS nếu Intraday NGƯỢC Bias — CHỈ áp dụng khi yêu cầu align (mặc định OFF). + // Mặc định: cho phép MSS dù Intraday ngược ⇒ entry sớm hơn khi trend đổi muộn, + // giảm trường hợp "giá chạy tới TP rồi mới khớp limit → quay lại SL". + if(InpMssRequireIntradayAligned && + !IctIntraday_TrendAlignsWithBias(g_ictDailyBias.bias, g_ictIntraday.trend)) + { + IctMss_MarkFvgUsedOnMssSuccess(sym, g_ictLowTf.mss.h1FvgId); + const string sideLbl = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ? "MSS↓" : "MSS↑"; + const string trendLbl = IctTrendDisplayShort(g_ictIntraday.trend); + IctMss_ResetState(); + g_ictLowTf.mss.displayReason = StringFormat( + "%s khớp khi Intraday=%s ngược Bias — bỏ POI, chờ POI mới", + sideLbl, trendLbl); + return; + } + IctMss_MarkFvgUsedOnMssSuccess(sym, g_ictLowTf.mss.h1FvgId); const double keyLv = g_ictLowTf.mss.chochKeyLevel; diff --git a/Include/ICT2026/Panel.mqh b/Include/ICT2026/Panel.mqh index 213da64..d475665 100644 --- a/Include/ICT2026/Panel.mqh +++ b/Include/ICT2026/Panel.mqh @@ -1,6 +1,38 @@ //+------------------------------------------------------------------+ -//| Panel.mqh — Daily Bias + Intraday (mỗi dòng = 1 OBJ_LABEL) | -//| Bias/Intraday: xanh = Up, đỏ = Down — cùng màu = canh trade | +//| Panel.mqh — overview panel góc trên trái (~10 dòng) | +//+------------------------------------------------------------------+ +//| Mỗi dòng = 1 OBJ_LABEL độc lập (OBJ_LABEL không hỗ trợ \n trong | +//| MT5). Stack dọc với spacing InpPanelLineSpacing px. | +//| | +//| Layout (top→bottom): | +//| [CATEGORY] CODE ← EaState | +//| | +//| | +//| ──────────── | +//| Bias: (xanh=Up, đỏ=Down, vàng=Range, xám=None)| +//| Ly do: | +//| ──────────── | +//| PERIOD_ | +//| Intraday: | +//| Ly do: | +//| IsAllowTrade: | +//| AllowEntry: | +//| ──────────── | +//| | +//| ──────────── | +//| Stats: N lệnh | TP X | SL Y | +//| WR XX.X% | Ravg ±N.NNR | Net ±N.NN | +//| | +//| Trigger render: từ ICT_2026.mq5 OnTick khi có nến mới Bias/ | +//| Intraday/LowTF, hoặc force=true ở OnInit. | +//| | +//| Skip nếu: InpOnlyStatsMode = true | +//| | +//| Object prefix: ICT26_PNL_ | +//| | +//| Public API: | +//| void IctPanel_Render(sym) | +//| void IctPanel_DeleteAll() | //+------------------------------------------------------------------+ #ifndef ICT2026_PANEL_MQH #define ICT2026_PANEL_MQH diff --git a/Include/ICT2026/README.md b/Include/ICT2026/README.md index 00e0ac6..29707a9 100644 --- a/Include/ICT2026/README.md +++ b/Include/ICT2026/README.md @@ -2,7 +2,7 @@ Expert Advisor ICT 2026 — module hóa theo hướng ICT/SMC. **Không phụ thuộc HyperICT** (chỉ tham khảo ý tưởng swing & key level). -Phiên bản hiện tại: **1.10** +Phiên bản hiện tại: **1.184** --- @@ -17,13 +17,192 @@ Phiên bản hiện tại: **1.10** ## Mục lục 1. [Cấu trúc thư mục](#folder-structure) -2. [Pipeline Daily Bias](#pipeline) -3. [Logic nghiệp vụ](#business-logic) -4. [Mô tả từng file](#file-reference) -5. [API](#api) -6. [Input](#inputs) -7. [Chưa implement](#roadmap) -8. [Changelog](#changelog) +2. [Current architecture (v1.187) — flow end-to-end](#current-architecture) +3. [Pipeline Daily Bias](#pipeline) +4. [Logic nghiệp vụ](#business-logic) +5. [Mô tả từng file](#file-reference) +6. [API](#api) +7. [Input](#inputs) +8. [Chưa implement](#roadmap) +9. [Changelog](#changelog) + +--- + + + +## Current Architecture (v1.187) — flow end-to-end + +> **Nếu bạn chỉ đọc 1 mục, đọc mục này.** Các mục bên dưới là chi tiết từng phần. + +### A. Mỗi tick (`OnTick` trong `ICT_2026.mq5`) + +``` +OnTick(sym) + ├─ IctDailyBias_Update(sym) ── D1 nến mới ⇒ recompute bias + │ └─ IctDailyBias_TrackTransition() ⇒ set g_ictBiasInto{Bull,Bear}Time (v1.182) + │ + ├─ IctIntraday_Update(sym) ── H1 nến mới ⇒ trend + IsAllowTrade + │ + ├─ IctLowTfTrend_Update(sym) ── H1 (FvgTf) nến mới ⇒ FVG scan + MSS pipeline + │ ├─ IctIntraday_UpdateAllowTrade() + │ ├─ IctFvg_UpdateAll(sym, tf) ⇒ Available / Used / PD + │ ├─ IctFvg_ScanNew(...) ⇒ thêm FVG thuận Bias + │ ├─ IctMss_Update(sym) ⇒ phase machine (IDLE→H1_TOUCH→CHOCH→M5_FVG→ENTRY_FILL→READY) + │ ├─ IctMssEntry_Update(sym) ⇒ place/replace/cancel limit + │ └─ IctEaState_Refresh(sym) ⇒ map sang STATE code cho panel + │ + ├─ IctLowTfTrend_TickRefresh(sym) ── update fill ratio + PD on every tick + │ + └─ if(!OnlyStatsMode) + ├─ IctPanel_Render(sym) + ├─ IctDraw_Render(sym) + ├─ IctFvgDraw_Render(sym) ⇒ top-N + price-near (v1.180) + └─ IctMssDraw_Render(sym) ⇒ live/locked labels +``` + +### B. Daily Bias resolution (`DailyBias.mqh`, v1.175) + +Thứ tự ưu tiên (cao→thấp): + +``` +1) D1/D2 pattern (IctDailyBias_ResolveD1D2) + ├─ D[1].close > D[2].high → BULL (breakout) + ├─ D[1].close < D[2].low → BEAR (breakdown) + ├─ D[1].high > D[2].high && close ∈ D[2] → BEAR (sweep bear liq) + └─ D[1].low < D[2].low && close ∈ D[2] → BULL (sweep bull liq) + +2) Structural HH-HL / LH-LL (IctResolveTrend) + +3) HTF fallback (IctUpdateHTFBias — Previous Day Model) +``` + +Sau khi resolve: `IctDailyBias_TrackTransition()` cập nhật `g_ictBiasIntoBullTime` / `g_ictBiasIntoBearTime` nếu bias xoay side ⇒ dùng làm cutoff cho FVG fresh touch (xem D bên dưới). + +### C. MSS Pipeline (`MssSetup.mqh`) + +State machine `g_ictLowTf.mss.phase`: + +``` +IDLE + │ IctMss_SelectNearestH1Poi() ⇒ chọn H1 FVG gần giá nhất + │ IctMss_HasFreshFvgTouch() ⇒ touch hợp lệ (xem D) + ↓ +H1_TOUCH ──── (Retest FVG H1 OK) + │ IctMss_UpdateLiveM5Swings() ⇒ liveL0/H0/L1/H1 structural (v1.171) + │ IctMss_TryLockMss() ⇒ M5 phá L0 (BEAR) / H0 (BULL) + │ cấu trúc đầy đủ ≥ 2 đỉnh/đáy SAU touch + │ veto nếu InpMssRequireIntradayAligned && intraday ngược bias (v1.174/1.181) + ↓ +CHOCH ──── (MSS↑/↓ locked) + │ IctConfirmFvg_ScanNew() ⇒ tìm M5 FVG sau chochTime + │ (M5 FVG optional — entry tại MSS keyLV nếu vắng) + │ IctMss_ZonesOverlapH1() ⇒ M5 FVG phải gần H1 FVG + ↓ +M5_FVG → ENTRY_FILL → READY ──── (chờ giá hồi vào M5 FVG) +``` + +Entry gating (`IctMssEntry_Update`): + +``` +gate1: InpMssTradeEnabled (true) +gate2: phase >= CHOCH && chochLocked && chochKeyLevel > 0 +gate3: intradayBlock = InpMssRequireIntradayAligned && !isAllowTrade (default FALSE) +gate4: !InpMssOnePosition || no existing position+pending +``` + +ComputeLevels (`MssEntry.mqh`, v1.164 + v1.184): + +``` +side ← (m5 FVG.side) hoặc chochBias +SL ← max(H0,H1)+buffer (BEAR) / min(L0,L1)-buffer (BULL) + buffer = InpMssSlSpreadMult × spread + +2 candidates entry: + A) M5 FVG limit price + B) MSS keyLV (chochKeyLevel) +chọn cái có risk = |entry - SL| nhỏ hơn (limit phải đúng phía thị trường) + +TP target = iH0/iL0 (sóng H1): + RR(TP@iH0) > InpMssMinRR ⇒ TP @ iH0 - InpMssTpSpreadMult × spread (swing target) + else ⇒ TP = entry ± risk × InpMssMinRR (= 2R fixed) + +partialTriggerPrice = swingTp (nếu TP gồng xa hơn swing) ⇒ partial 50% + SL→BE + +CHECK CUỐI (v1.184): |entry - market| ≤ InpMssMaxLimitDistAtrMult × ATR(FvgTf) + Quá xa ⇒ skip + mark FVG used + reset state +``` + +Place limit (`IctMssEntry_PlaceLimit`): +- BUY_LIMIT (BULL) / SELL_LIMIT (BEAR), magic = `InpMssMagic` +- Lưu `pendingTicket`, `pendingPlacedTime`, `pendingEntry/Sl/Tp`, `partialTriggerPrice` + +Per-tick checks (xem `IctMssEntry_Update`): +``` +IctMssEntry_CheckEodCancel() ── cancel cuối phiên Mỹ +IctMssEntry_CheckPendingTimeout() ── cancel sau N giờ (default 1h) +IctMssEntry_CheckTpReachedBeforeFill() ── cancel nếu giá đã chạm TP (v1.185) +IctMssEntry_CheckStaleLimit() ── cancel nếu rời xa giá > N×ATR (v1.184) +IctMssEntry_CheckBreakevenAtRR() ── dời SL→entry @ N×R (default off) +IctMssEntry_CheckPartialClose() ── partial 50% @ swingTp + SL→BE +``` + +### D. FVG fresh touch (`MssSetup.mqh`, v1.182) + +Touch hợp lệ khi xảy ra **sau** cutoff (latest của): +1. `g_ictMssAfterCloseGuard` (sau TP/SL/manual close / timeout / stale limit) +2. `g_ictBiasIntoBullTime` (nếu bias hiện tại = BULL) hoặc `g_ictBiasIntoBearTime` (nếu BEAR) + +``` +IctMss_GetFvgTouchTime(sym, h1): + cutoff = IctMss_GetFvgTouchCutoff() + t = h1.firstTouchTime (nếu > cutoff) || 0 + tM5 = IctMss_FirstM5TouchInFvg(sym, h1, since=cutoff) + t = min(t, tM5) (lấy sớm nhất sau cutoff) + nếu vẫn 0 && IctMss_LivePriceTouchesFvg(sym, h1) && curBar ≥ cutoff + t = curBar + return t + +IctMss_HasFreshFvgTouch(sym, h1): + tTouch = IctMss_GetFvgTouchTime(sym, h1) + nếu tTouch ≤ 0 ⇒ false + requiredPct = IctFvg_RequiredTouchFillPct(h1) + = Pure (25%) nếu FVG hoàn toàn trong vùng PD đúng chiều / lớn / PD off + = Mixed (50%) nếu straddle equilibrium + return h1.maxFillRatio × 100 ≥ requiredPct +``` + +### E. POI filtering (`Fvg.mqh`) + +``` +IctFvg_IsEntryRepPd(zone, bias): + nếu !InpFvgPdEnabled ⇒ true (toàn bộ FVG qualify) [v1.179] + else: (v1.187: bỏ large-FVG bypass — áp PD cho TẤT CẢ FVG) + BEAR ⇒ overlap với Premium ≥ InpFvgPdMinOverlapPct (0=any overlap) + BULL ⇒ overlap với Discount tương tự +``` + +### F. State reset triggers (giai đoạn sau-trade / failsafe) + +| Trigger | Hành động | Function | +|---|---|---| +| TP/SL/manual close | mark M5 used + reset + set guard | `IctMss_OnPositionClosed` | +| EOD (cuối phiên Mỹ) | cancel pending + reset | `IctMss_OnEodCancel` | +| Pending timeout (1h) | cancel + mark used + reset + guard | `IctMss_OnPendingTimeout` | +| Stale limit (> N×ATR) | cancel + mark used + reset + guard | `IctMssEntry_CheckStaleLimit` | +| Bias xoay side | cutoff áp dụng cho touch tự động | `IctDailyBias_TrackTransition` | +| H1 body đóng xuyên FVG | FVG → Used, reset | `IctMss_FailFvgH1Body` | +| FVG POI → Used giữa setup | reset | trong `IctMss_Update` | + +### G. Tầng gating 2-layer + +| Layer | Check | File | +|---|---|---| +| **Pipeline gate** | `g_ictDailyBias.bias != NONE` (Bias rõ) | `IctMss_Update`, `IctLowTfTrend_Update` | +| **Entry gate** | `intradayBlock = InpMssRequireIntradayAligned && !isAllowTrade` | `IctMssEntry_Update` | + +Mặc định v1.181: cả 2 layer đều **cho phép** khi Bias rõ, không yêu cầu intraday align. + +--- --- @@ -463,16 +642,56 @@ Màu chữ: **Up** = xanh lá, **Down** = đỏ cam, **Range** = vàng, **None** ## Mô tả từng file +### Foundation (types + utils + io) + +| File | Trách nhiệm | Globals owned | +|------|-------------|---------------| +| `Types.mqh` | Enums (`ENUM_ICT_BIAS/STRUCT/MS_EVENT/TREND/FVG_*/MSS_PHASE`), structs (`IctSwingPoint`, `IctSwingSet`, `IctDailyBiasState`, `IctIntradayState`, `IctFvgZone`, `IctMssState`, `IctEaState`, `IctLowTfState`) | — | +| `Config.mqh` | Tất cả `input` params (~50 inputs gom 8 groups: Bias / Intraday / Test / Display / Confirm / FVG / MSS / Debug) | — | +| `Journal.mqh` | `IctMss_JournalPipeline`, `IctMss_JournalEntryBlock`, `IctMss_JournalReset` — gated bởi `InpOnlyStatsMode` | `g_ictMssLastJournalReason` | +| `Swing.mqh` | Pivot detection (5-bar fractal), `IctBuildBiasSwingSet`, `IctBuildIntradaySwingSet`, `IctBuildConfirmSwingSet`, fib validate | — | +| `StructureCore.mqh` | `IctBodyBreakAbove/Below`, `IctDetectStructureEvent` (BOS/CHoCH detect trên Key H0/L0) | — | +| `StructureTrend.mqh` | `IctResolveTrend` (rõ HH-HL / LH-LL vs sớm sau CHoCH) — chia sẻ cho cả Daily + Intraday | — | + +### Bias & Trend (3 tầng TF) + +| File | Trách nhiệm | Globals owned | +|------|-------------|---------------| +| `DailyBias.mqh` | D1 bias resolver theo thứ tự ưu tiên D1/D2 → structural → HTF fallback. Track bias transition (v1.182) | `g_ictDailyBias`, `g_ictDailyCtx`, `g_ictBiasIntoBullTime`, `g_ictBiasIntoBearTime` | +| `IntradayStructure.mqh` | H1 trend (HH-HL / LH-LL với 2 đỉnh + 2 đáy gần nhất trong `InpIntradayRecentBars`), CHoCH ưu tiên trước, `isAllowTrade` (`Bias align Intraday`) | `g_ictIntraday` | + +### Low TF (FVG + Confirm) + +| File | Trách nhiệm | Globals owned | +|------|-------------|---------------| +| `Fvg.mqh` | Detect FVG 3 nến A-B-C, fill ratio, PD zone từ pivot swing-1/swing-2, ATR helper | `g_ictFvgZones[]`, `g_ictFvgCount` | +| `ConfirmFvg.mqh` | M5 (Confirm TF) FVG pool — scan sau CHoCH | `g_ictConfirmFvgZones[]`, `g_ictConfirmFvgCount` | +| `LowTfApi.mqh` | Helpers truy cập `g_ictLowTf`, FVG side from bias, market side text | `g_ictLowTf` | +| `LowTfTrend.mqh` | Orchestrator: H1 nến mới ⇒ scan FVG, gọi MSS pipeline, refresh tick (PD/fill update) | — (sử dụng `g_ictLowTf`) | + +### MSS Pipeline (setup → entry) + +| File | Trách nhiệm | Globals owned | +|------|-------------|---------------| +| `MssSetup.mqh` | Phase machine IDLE→H1_TOUCH→CHOCH→M5_FVG→ENTRY_FILL→READY, fresh touch cutoff (v1.182), structural keylv lock (v1.171), MSS lock + invalidation | `g_ictMssAfterCloseGuard` | +| `MssEntry.mqh` | Order management: ComputeLevels (2-candidate min-risk + cap distance), PlaceLimit, partial close, BE@N×R, stale limit, EOD/timeout cancel, OnPositionClosed | `g_ictMssTrade` (CTrade) | + +### State + UI + +| File | Trách nhiệm | Globals owned | +|------|-------------|---------------| +| `EaState.mqh` | Map `mss.phase` + flags ⇒ EA STATE code (STOP_*/SETUP_*/TRADE_*). Cấp tiêu đề + chi tiết cho panel | `g_ictEaState` | +| `Panel.mqh` | Vẽ panel 8-10 dòng góc trên trái: state, bias, intraday, AllowEntry, MSS reason, Stats line | — | +| `Draw.mqh` | Label swing trên chart (bH0–bL1, iH0–iL1, H0–L1 confirm) — auto theo TF chart | — | +| `FvgDraw.mqh` | Render FVG + PD top-N gần nhất + price-near (v1.180) — bỏ vẽ FVG xa giá để chart không lag | — | +| `MssDraw.mqh` | Vẽ MSS objects: H1 touch line, retest label, live L0/H0 (dotted gold), MSS↑/↓ confirmed (solid), entry/SL/TP lines | — | +| `Stats.mqh` | Quét HistoryDeals theo magic, đếm TP/SL/total/WR/sumR/netProfit (chỉ TP/SL — v1.183) | `g_ictMssStats`, `g_ictMssStatsLastScan` | + +### EA root + | File | Trách nhiệm | |------|-------------| -| `Types.mqh` | `ENUM_ICT_BIAS`, `ENUM_ICT_STRUCT`, `ENUM_ICT_MS_EVENT`, `IctSwingSet`, `IctDailyBiasState` | -| `Config.mqh` | Input: TF, swing range, lookback, Fib, debug | -| `Swing.mqh` | Pivot detection, build swing set, classify HH-HL/LH-LL, key level, Fib validate | -| `StructureCore.mqh` | `IctBodyBreak*`, `IctDetectStructureEvent` | -| `DailyBias.mqh` | `IctUpdateHTFBias`, veto, `g_ictDailyBias` | -| `IntradayStructure.mqh` | `IctIntraday_Update`, trend, `isAllowTrade`, `g_ictIntraday` | -| `Panel.mqh` | Bias + Intraday + IsAllowTrade trên chart | -| `ICT_2026.mq5` | Init/Update Daily + Intraday → panel | +| `Experts/ICT_2026.mq5` | `OnInit` (gọi init từng module), `OnTick` (orchestrator), `OnTradeTransaction` (detect close → call `IctMss_OnPositionClosed`), `OnDeinit` | --- @@ -566,6 +785,217 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias(); ## Changelog +### v1.187 — Bỏ large-FVG bypass: áp PD cho TẤT CẢ FVG + +- **Lý do**: feature "dấu chân cá mập" (v1.178) cho FVG lớn vào MSS bất kể Premium/Discount → nhiều setup vào ngược vùng (Premium khi bull, Discount khi bear) → kết quả backtest kém. +- **Thay đổi**: + - **Xoá inputs** khỏi `Config.mqh`: `InpFvgLargeBypassPd`, `InpFvgLargeMinAtrMult`. + - **Xoá helper** `IctFvg_IsLargeFvg(zone, sym)` trong `Fvg.mqh`. + - **Bỏ bypass call** trong `IctFvg_IsEntryRepPd` (mọi FVG phải qua check overlap ≥ `InpFvgPdMinOverlapPct`). + - **Bỏ bypass call** trong `IctFvg_RequiredTouchFillPct` (mọi FVG phải dùng threshold theo overlap thực tế: 25% Pure / 50% Mixed). +- **Giữ lại**: + - Helper `IctFvg_AtrFvgTf(sym)` — vẫn được `MssEntry.mqh` dùng cho stale-limit cap (v1.184) và `FvgDraw.mqh` dùng cho rendering. + - Toggle global `InpFvgPdEnabled` — vẫn dùng được để tắt toàn bộ PD filter (cho A/B test). +- **Hệ quả**: ít POI hơn (FVG lớn nằm sai vùng giờ bị loại), nhưng quality cao hơn (mọi entry đều ở vùng PD đúng chiều bias). Khôi phục hành vi trước v1.178. + +### v1.186 — Siết chặt body-break: thân nến đóng vượt level ≥ N × spread + +- **Vấn đề**: nến đóng "sát mép" key level/swing (close vượt 1 point) vẫn được tính là body-break ⇒ BOS/CHoCH/MSS-confirm + H1 invalidate kích hoạt nhầm trên nhiễu giá / spike trong spread → setup hủy oan hoặc MSS lock sai keyLv. +- **Input mới** (`Config.mqh`): + - `InpBodyBreakSpreadMult = 10` — body close phải vượt level ≥ N × spread (Ask−Bid hiện tại). Fallback `_Point` khi spread = 0 (thị trường đóng cửa). +- **Logic** (`StructureCore.mqh`): + - Helper mới `IctBodyBreakBuffer(sym) = MathMax(_Point, max(0, Ask−Bid) × InpBodyBreakSpreadMult)`. + - `IctBodyBreakAbove(level)` ⇔ `BodyTop > level + buffer`. + - `IctBodyBreakBelow(level)` ⇔ `BodyBottom < level − buffer`. +- **Áp dụng** (mọi callsite dùng `IctBodyBreakAbove/Below`): + - BOS/CHoCH detect daily/intraday (`IctDetectStructureEvent`, `IctDetectTrendSwingEvent`). + - MSS confirm (`IctMss_FindMssBreakBar`, `IctMss_TryLockMss` shift=1). + - H1 invalidation (`IctMss_H1BodyInvalidatedSetup`) cũng dùng `IctBodyBreakBuffer` thay vì `_Point`. +- **Hệ quả**: ít event giả; cấu trúc chỉ flip khi có close-through "có ý nghĩa" (≥ 10 spread, ~5–10 pip tuỳ symbol). Tránh churn keyLv khi giá lưỡng lự quanh đỉnh/đáy. + +### v1.185 — Cancel limit khi giá đã chạm TP trước khi khớp + +- **Vấn đề**: setup MSS bull/bear đúng, limit đặt tại M5 FVG / MSS keyLV, nhưng giá chạy thẳng đến TP target (iL0/iH0) mà KHÔNG hồi lại entry → khi giá đảo chiều quay về khớp limit thì "TP move" đã hết, lệnh chạy ngược → SL. +- **Input mới** (`Config.mqh`): + - `InpMssCancelLimitWhenTpReached = true` — bật/tắt feature. +- **Logic** (`MssEntry.mqh` → `IctMssEntry_CheckTpReachedBeforeFill`): + - Gọi mỗi tick trong `IctMssEntry_Update`, ngay sau `CheckPendingTimeout`, trước `CheckStaleLimit`. + - Có pending `BUY_LIMIT`/`SELL_LIMIT` + TP > 0 → so sánh: + - **BUY**: `Bid ≥ TP − Point` ⇒ giá đã chạm TP target + - **SELL**: `Ask ≤ TP + Point` ⇒ giá đã chạm TP target + - Nếu match: `OrderDelete(pendingTicket)` + `MarkM5FvgUsed` + `IctMss_ResetState` + set `g_ictMssAfterCloseGuard` → quay về `WAIT_FVG_TOUCH`. +- **Hệ quả**: không bao giờ khớp lệnh "muộn" sau khi target đã được giá quét. Setup này coi như miss, EA tìm POI mới. +- **So sánh stale-limit (v1.184)**: stale-limit cancel khi giá CHẠY XA entry (vượt N×ATR theo hướng cùng phía); TP-reached cancel khi giá đã ĐẾN TP (vượt qua entry rồi qua TP). Hai cơ chế bổ sung cho nhau. + +### v1.181 — Mặc định KHÔNG yêu cầu Intraday cùng chiều Bias + +- **Vấn đề**: nhiều setup giá đã chạy tới TP rồi mới khớp limit (vì Intraday chuyển sang thuận Bias muộn) → giá quay lại → SL +- **Lý do**: v1.169-v1.174 yêu cầu Intraday cùng chiều Bias mới entry, dẫn đến miss setup khi Intraday lag +- **Fix**: thêm toggle `InpMssRequireIntradayAligned = false` (mặc định **tắt**) + - **Tắt** (mặc định mới): MSS + entry chạy thuần theo Daily Bias, **không** quan tâm Intraday trend. Pipeline lock MSS + đặt limit ngay khi MSS valid + - **Bật**: behavior cũ — chặn entry + veto MSS lock khi Intraday ngược Bias +- Thay đổi cụ thể: + - `MssSetup.mqh` (v1.174 veto): wrap với `if(InpMssRequireIntradayAligned && ...)` + - `MssEntry.mqh` (gate `isAllowTrade`): thay bằng `intradayBlock = (InpMssRequireIntradayAligned && !isAllowTrade)` +- Trade-off: + - Tắt: nhiều entry hơn, ít miss setup, nhưng có thể vào lệnh khi Intraday đang ngược (phụ thuộc vào MSS quality) + - Bật: chất lượng entry cao hơn (đồng thuận TF), nhưng dễ bị "lag entry" +- Intraday vẫn được tính + hiển thị trên panel — chỉ KHÔNG gate entry nữa (mặc định) + +### v1.180 — Tối ưu rendering: top-N FVG/PD + price-near, bỏ qua các FVG xa + +- **Vấn đề**: mỗi tick `IctFvgDraw_Render` `DeleteAll` + redraw toàn bộ FVG (24 H1 + N M5), mỗi FVG kèm 3 PD object → ~100+ object operation/tick → chart nặng +- **Fix**: chỉ vẽ tập hợp tối thiểu cần thiết + - **Top-N USED gần nhất** (mặc định 3) — sort theo `createdTime` desc + - **Top-N AVAILABLE gần nhất** (mặc định 3) + - **FVG price-near**: giá hiện tại nằm trong `[lower − N×ATR, upper + N×ATR]` (mặc định 2×ATR) + - **M5 FVG đang được dùng cho MSS** luôn ép vẽ (ngay cả khi không nằm trong top/near) +- Inputs mới: + - `InpDrawMaxRecentPerState = 3` — số FVG/PD gần nhất mỗi state (Used/Available) + - `InpDrawNearAtrMult = 2.0` — bán kính "near price" theo ATR(InpFvgTf) (0=tắt) +- Helpers mới trong `FvgDraw.mqh`: + - `IctFvgDraw_SortIdxByTimeDesc(idx[], n, zones[])` — insertion sort indices + - `IctFvgDraw_BuildVisibleMask(zones[], count, sym, &visible[])` — build mask vẽ +- Áp dụng cho cả H1 FVG (kèm PD) và M5 FVG (ConfirmFvg) +- PD array tự động đi theo FVG → giảm tương ứng +- Không ảnh hưởng logic pipeline (chỉ thay đổi rendering) + +### v1.179 — Toggle global bật/tắt PD filter (cho A/B test) + +- Input mới: `InpFvgPdEnabled = true` — bật/tắt điều kiện Premium/Discount toàn cục +- Khi `false`: mọi FVG đều qualify làm POI (chỉ cần có Bias rõ ràng để chọn side) + - `IctFvg_IsEntryRepPd`: return true ngay + - `IctFvg_RequiredTouchFillPct`: trả về `InpFvgTouchFillPure` (25%) thay vì 50% mixed +- Mặc định `true` ⇒ giữ hành vi cũ +- Mục đích: A/B test "số lượng vs chất lượng": + - `true` = ít POI hơn nhưng chất lượng cao (sai vùng PD bị loại) + - `false` = nhiều POI hơn nhưng có thể trade trong vùng counter (Discount khi BEAR / Premium khi BULL) +- Tương tác: + - Toggle global `InpFvgPdEnabled=true` ⇒ áp PD cho TẤT CẢ FVG (không còn ngoại lệ kể từ v1.187 — large-FVG bypass đã bị bỏ) + - `InpFvgPdEnabled=false` ⇒ bỏ qua PD hoàn toàn cho mọi FVG + +### v1.178 — (REMOVED in v1.187) FVG "dấu chân cá mập" bypass PD filter + +- ~~FVG height ≥ N × ATR ⇒ bypass PD filter~~ — **đã bỏ** ở v1.187 (xem changelog v1.187). +- Inputs `InpFvgLargeBypassPd` và `InpFvgLargeMinAtrMult` đã được xoá khỏi `Config.mqh`. +- Helper `IctFvg_IsLargeFvg(zone, sym)` đã bị xoá khỏi `Fvg.mqh`. +- `IctFvg_AtrFvgTf(sym)` **vẫn được giữ** vì còn được dùng bởi `MssEntry.mqh` (stale-limit cap v1.184) và `FvgDraw.mqh`. + +### v1.177 — Tách toggle bật/tắt BE@RR (mặc định tắt) + +- Input mới: `InpMssBeEnabled = false` — bật/tắt độc lập với ngưỡng RR (mặc định **tắt**) +- `InpMssBeAtRR = 2.0` — vẫn dùng để cấu hình ngưỡng khi `InpMssBeEnabled=true` +- `IctMssEntry_CheckBreakevenAtRR`: thêm `if(!InpMssBeEnabled) return;` ở đầu +- Lý do: tách boolean toggle khỏi giá trị số để config rõ ràng hơn, dễ A/B test BE on/off mà không phải nhớ giá trị cũ + +### v1.176 — Dời SL về entry khi đạt 2R (Breakeven sớm) + +- Input mới: `InpMssBeAtRR = 2.0` — dời SL về entry khi giá đi được N×R (0=tắt) +- Function mới: `IctMssEntry_CheckBreakevenAtRR(sym)` — check mỗi tick +- State mới: `IctMssState.beMovedDone` (bool) — đánh dấu đã dời BE, tránh dời lặp +- Trigger: `price ≥ entry + N×risk` (BUY) hoặc `price ≤ entry − N×risk` (SELL) + - `risk = |pendingEntry − pendingSl|` (từ thời điểm đặt limit) +- Hành vi: `PositionModify(ticket, entry, curTp)` — chỉ dời SL, TP giữ nguyên +- Skip cases: + - `InpMssBeAtRR ≤ 0` + - `beMovedDone` đã true + - `partialCloseDone` đã true (partial close ở swing đã set BE) + - SL hiện tại đã ≥ entry (cho BUY) hoặc ≤ entry (cho SELL) — chỉ set flag +- Tương tác với partial close (v1.168): + - Nếu **2R < swing target**: BE@2R chạy trước, dời SL→entry. Khi giá tiếp tục lên swing → partial close vẫn 50% (PositionModify SL=entry là no-op, không lỗi) + - Nếu **2R ≥ swing target**: partial close chạy trước ở swing (50% + SL→BE), khi giá lên 2R thì BE check skip vì `partialCloseDone=true` + +### v1.175 — Bias D1/D2 ưu tiên trên structural (thêm sweep liquidity) + +- **Thay đổi**: thứ tự ưu tiên xác định bias đảo lại + - **Trước**: structural HH-HL/LH-LL trước → HTF (UP/DOWN/SIDEWAY) fallback + - **Sau**: D1/D2 pattern trước → structural HH-HL/LH-LL fallback → HTF fallback +- **D1/D2 pattern (4 cases)**: + 1. `D[1].close > D[2].high` → **BULL** (Breakout) + 2. `D[1].close < D[2].low` → **BEAR** (Breakdown) + 3. `D[1].high > D[2].high & close < D[2].high & close > D[2].low` → **BEAR** (sweep bear liq — false breakout) + 4. `D[1].low < D[2].low & close > D[2].low & close < D[2].high` → **BULL** (sweep bull liq — false breakdown) +- **Tie-break**: D1 sweep cả 2 phía + close kẹt trong range D2 ⇒ không xác định ⇒ rơi xuống structural +- Function mới: `IctDailyBias_ResolveD1D2(sym, tf, &biasOut, &reasonOut)` trong `DailyBias.mqh` +- D1/D2 bias luôn `biasPhase = CLEAR` (không "sớm") +- DisplayReason hiển thị trực tiếp pattern match (vd: `"D[1].high > D[2].high & close < D[2].high → sweep bear liq → BEAR"`) +- Lý do: liquidity sweep + close-back là tín hiệu reversal mạnh, structural HH-HL/LH-LL có thể vẫn chưa "xoay" kịp ⇒ D1/D2 phản ánh "bias hôm nay" chuẩn hơn + +### v1.174 — Veto MSS khi Intraday ngược Bias tại thời điểm lock + +- **Bug**: MSS lock khi Intraday=BEAR / Bias=BULL → keylv ở đáy xa. Khi Intraday đảo về BULL, AllowTrade=true → bot đặt limit ở keylv cũ (xa): + - Giá quay lại khớp = thường hết trend → SL + - Giá tiếp tục đi = không khớp → khoá cơ hội tới khi timeout (1h) +- **Fix**: Ngay sau `IctMss_TryLockMss` thành công, check `IctIntraday_TrendAlignsWithBias(bias, intraday)`: + - Nếu **ngược** ⇒ `MarkM5FvgUsed` + `IctMss_ResetState` ⇒ về `WAIT_FVG_TOUCH` + - Không transition `phase = ICT_MSS_CHOCH`, không quét M5 FVG +- DisplayReason: `"MSS↑/↓ khớp khi Intraday= ngược Bias — bỏ POI, chờ POI mới"` +- Khác với v1.170 (decouple pipeline-AllowTrade): pipeline vẫn chạy phát hiện MSS, **chỉ veto tại thời điểm lock** nếu intraday lệch hướng — không để MSS dangling chờ AllowTrade + +### v1.173 — Timeout pending limit 4h → 1h + +- `Config.mqh`: `InpMssPendingExpireHours` mặc định `4 → 1` +- Logic huỷ pending unchanged (vẫn dùng `IctMssEntry_CheckPendingTimeout` ở mỗi tick) +- Sau 1h limit chưa khớp ⇒ `OrderDelete` + `MarkM5FvgUsed` + `IctMss_ResetState` → quay về `WAIT_FVG_TOUCH` + +### v1.172 — Guard tTouch ≤ 0 trong UpdateLiveM5Swings + +- **Bug v1.171**: filter `if(highs[i].time < tTouch) continue;` không có tác dụng khi `tTouch = 0` (mọi `time >= 0`) ⇒ liveH0/L0 lấy cả pivot **TRƯỚC** thời điểm chạm FVG ⇒ MSS keylv sai +- **Fix**: thêm guard `if(tTouch <= 0) return false;` ở đầu `IctMss_UpdateLiveM5Swings` +- Trường hợp đi vào guard: `h1TouchTime` chưa được set xong (race / GetFvgTouchTime trả 0) — pipeline sẽ hiển thị "M5 trong FVG — chờ pivot L0/H0" thay vì compute sai +- Đảm bảo: `liveH0Time` (BULL) & `liveL0Time` (BEAR) luôn `>= tTouch` + +### v1.171 — Lock keylv MSS (chống "H0 nhảy") + +- **Vấn đề**: `liveL0/H0` dùng "pivot mới nhất" (`IctBuildConfirmSwingSet`) → mỗi nến mới có swing mới là keylv MSS đổi → label "L0/H0" nhảy liên tục, MSS chạy lệch theo đỉnh/đáy mới hình thành SAU extreme pullback +- **Fix**: `IctMss_UpdateLiveM5Swings` chuyển sang định nghĩa **structural keylv**: + - **BEAR bias** (pullback UP): + - `liveH0` = đỉnh CAO NHẤT sau touch (extreme đỉnh pullback) + - `liveL0` = đáy **gần nhất theo time TRƯỚC liveH0** = *"đáy tạo ra đỉnh cao nhất"* — keylv MSS↓ + - `liveH1` = đỉnh gần nhất TRƯỚC `liveL0` + - `liveL1` = đáy gần nhất TRƯỚC `liveH1` + - **BULL bias** (pullback DOWN): + - `liveL0` = đáy THẤP NHẤT sau touch (extreme đáy pullback) + - `liveH0` = đỉnh **gần nhất theo time TRƯỚC liveL0** = *"đỉnh tạo ra đáy thấp nhất"* — keylv MSS↑ + - `liveL1` = đáy gần nhất TRƯỚC `liveH0` + - `liveH1` = đỉnh gần nhất TRƯỚC `liveL1` +- **Hệ quả "lock" tự nhiên**: pivot mới hình thành SAU extreme KHÔNG đổi keylv; chỉ khi pullback thực sự tạo extreme mới (BEAR: high cao hơn; BULL: low thấp hơn) thì keylv mới recompute +- **Không đổi**: validation cấu trúc đủ 2 đỉnh/đáy SAU touch vẫn giữ (v1.165), SL stack max(H0,H1) / min(L0,L1) vẫn giữ +- **UI**: label "L0 live" / "H0 live" (gold dotted) bây giờ ổn định, chỉ dịch khi pullback mở rộng + +### v1.184 — Cap khoảng cách limit-giá (tránh limit chết) + +- **Vấn đề**: MSS confirm muộn khi Intraday đảo chiều mạnh → entry tại MSS keyLV / M5 FVG nằm xa giá hiện tại → limit "chết", không bao giờ khớp; hết phiên hoặc timeout 1h mới hủy → lỡ cơ hội mới. +- **Inputs mới** (`Config.mqh`): + - `InpMssMaxLimitDistAtrMult = 3.0` — cap = N × ATR(`InpFvgTf`). `>cap` ⇒ skip / cancel. `0` = tắt. + - `InpMssCancelStaleLimit = true` — cancel pending + mark FVG used + reset state khi vượt cap (chọn POI khác). +- **Logic**: + - `IctMssEntry_IsLimitTooFar(sym, entry, isBuy, …)` so sánh `|Ask − entry|` (BUY) / `|entry − Bid|` (SELL) với cap. + - `ComputeLevels` check sau khi pick entry → fail với reason `"Entry xa giá X pts > Y pts (N×ATR)"`. + - `IctMssEntry_Update`: nếu fail vì lý do trên + `InpMssCancelStaleLimit` ⇒ cancel pending + `MarkM5FvgUsed` + `IctMss_ResetState` + set `g_ictMssAfterCloseGuard` → quay về `WAIT_FVG_TOUCH`. + - `IctMssEntry_CheckStaleLimit(sym)` chạy mỗi tick: pending order đã đặt nhưng giá rời xa → cancel + reset (cùng flow). +- **Hệ quả**: nếu MSS confirm muộn (giá đã chạy xa khỏi keyLV/FVG), EA bỏ qua POI hiện tại + chọn POI/touch mới ngay thay vì đặt limit chết hàng giờ. + +### v1.183 — Stats chỉ đếm TP/SL, bỏ partial/manual close khỏi total + +- `Stats.mqh`: pass-2 skip non-TP/SL deals khỏi `total`, `tpCount`, `slCount`, + `sumR`, `winCount`, `lossCount`. Chỉ giữ `otherCount` để log. +- `netProfit` vẫn cộng dồn TẤT CẢ deals (partial close P&L được phản ánh đúng). +- `AvgR` = `sumR / (TP+SL)` thay vì `/ total` (đồng nhất với cách đếm). +- Panel: `IctMssStats_LineCounts` bỏ phần `khác X` → "Stats: N lệnh | TP X | SL Y". +- **Lý do**: partial close (50% ở swing target) tạo deal `DEAL_REASON_EXPERT` → trước + đây tính vào "khác" nhưng không phản ánh kết quả lệnh. Sau fix: 1 setup = 1 entry + → 1 TP/SL outcome. + +### v1.182 — Fix: chặn FVG touch xảy ra TRƯỚC khi Bias chuyển vào side + +- **Bug**: Bias UP → giá đi qua bear FVG (EA bỏ qua vì bias ngược) → bias xoay DOWN → EA pick up `firstTouchTime` cũ (trước khi xoay) → đặt limit ngay, dù chưa có touch fresh sau khi bias đổi side +- **Fix**: thêm `g_ictBiasIntoBullTime` / `g_ictBiasIntoBearTime` (`DailyBias.mqh`) lưu thời điểm Bias chuyển vào BULL/BEAR. `IctDailyBias_TrackTransition()` chạy mỗi `IctDailyBias_Update` để cập nhật. +- **Cutoff bias-aware** (`IctMss_GetFvgTouchCutoff`): touch hợp lệ phải xảy ra SAU `max(BiasIntoTime, AfterCloseGuard)` +- `IctMss_GetFvgTouchTime` reject `firstTouchTime` cũ → scan M5 bars sau cutoff (`IctMss_FirstM5TouchInFvg` nhận thêm param `since`) → nếu vẫn 0 → check `LivePriceTouchesFvg` (curBar phải ≥ cutoff) +- **Hệ quả**: Sau Bias flip, FVG side mới chỉ hợp lệ khi có touch NEW (re-touch hoặc giá đang trong gap với current bar sau cutoff). Tránh entry từ context cũ. +- **Tích hợp**: `HasFreshFvgTouch` dùng `GetFvgTouchTime` (đã apply cutoff) → bỏ block check redundant; fill ratio vẫn check Pure 25% / Mixed 50% + ### v1.142 — Fix Stats đếm thiếu lệnh - Bug v1.141: `IctMssStats_ComputeR` gọi `HistorySelectByPosition` **bên trong vòng quét history toàn cục** @@ -597,7 +1027,7 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias(); ### v1.160 — Pending timeout 4h → reset chờ FVG mới -- Input: `InpMssPendingExpireHours = 4` (mặc định, 0 = không timeout) +- Input: `InpMssPendingExpireHours = 1` (mặc định, 0 = không timeout) - Tracked `pendingPlacedTime` trong `IctMssState` - Mỗi tick (`IctMssEntry_CheckPendingTimeout`): nếu `TimeCurrent() - pendingPlacedTime ≥ N×3600s` hoặc order biến mất (broker expire) → `OrderDelete` + `MarkM5FvgUsed` + `IctMss_ResetState` + set `g_ictMssAfterCloseGuard` - Hành vi giống TP/SL close: reset về `WAIT_FVG_TOUCH`, chỉ accept H1 FVG có touch sau timeout diff --git a/Include/ICT2026/Stats.mqh b/Include/ICT2026/Stats.mqh index 95fd8e8..59f248a 100644 --- a/Include/ICT2026/Stats.mqh +++ b/Include/ICT2026/Stats.mqh @@ -1,5 +1,34 @@ //+------------------------------------------------------------------+ -//| Stats.mqh — thống kê lệnh MSS từ history (theo magic) | +//| Stats.mqh — thống kê lệnh MSS từ HistoryDeals (theo magic) | +//+------------------------------------------------------------------+ +//| Quét HistoryDeals filter symbol + magic = InpMssMagic. Compute: | +//| total / tpCount / slCount / otherCount | +//| winCount / lossCount / sumR / netProfit | +//| | +//| v1.183 — chỉ TP/SL được đếm vào total/win/loss/sumR/tp/sl. Partial| +//| close / manual / EA-close → otherCount (chỉ log, không vào panel).| +//| netProfit cộng dồn TẤT CẢ deals (kể cả partial — P&L thực). | +//| AvgR = sumR / (tpCount + slCount) — đồng nhất với cách đếm. | +//| | +//| 2-pass scan (v1.142 fix): | +//| Pass 1: chỉ collect ticket + reason + net (global selection còn | +//| nguyên, không gọi HistorySelectByPosition) | +//| Pass 2: tính R cho từng ticket (mỗi call thay selection) | +//| | +//| R computation (ComputeR): | +//| R = (closePrice - entryPrice) / (entryPrice - slPrice) for BUY | +//| = (entryPrice - closePrice) / (slPrice - entryPrice) for SELL| +//| entryPrice + slPrice lấy từ DEAL_ENTRY_IN + ORDER_SL của | +//| position. | +//| | +//| Globals owned: | +//| g_ictMssStats — IctMssStats struct | +//| g_ictMssStatsLastScan — TS scan gần nhất (debounce) | +//| | +//| Public API: | +//| void IctMssStats_Recompute(sym, magic) | +//| double IctMssStats_WinratePct() / IctMssStats_AvgR() | +//| string IctMssStats_LineCounts() / IctMssStats_LinePerf() | //+------------------------------------------------------------------+ #ifndef ICT2026_STATS_MQH #define ICT2026_STATS_MQH @@ -114,22 +143,29 @@ void IctMssStats_Recompute(const string sym, const ulong magic) cached++; } - // Pass 2: tính R cho từng deal (mỗi lần gọi sẽ thay selection — không ảnh hưởng vì đã cache xong). + // Pass 2: chỉ tính TP/SL vào stats. Partial close / manual close + // không phải kết quả lệnh ⇒ skip total/tp/sl/win/loss/sumR. + // netProfit vẫn cộng dồn TẤT CẢ deals để phản ánh đúng P&L thực tế. for(int k = 0; k < cached; k++) { - const double r = IctMssStats_ComputeR(outTickets[k]); - - g_ictMssStats.total++; g_ictMssStats.netProfit += nets[k]; - g_ictMssStats.sumR += r; const ENUM_DEAL_REASON rs = (ENUM_DEAL_REASON)reasons[k]; - if(rs == DEAL_REASON_TP) - g_ictMssStats.tpCount++; - else if(rs == DEAL_REASON_SL) - g_ictMssStats.slCount++; - else + const bool isTp = (rs == DEAL_REASON_TP); + const bool isSl = (rs == DEAL_REASON_SL); + if(!isTp && !isSl) + { g_ictMssStats.otherCount++; + continue; + } + + const double r = IctMssStats_ComputeR(outTickets[k]); + g_ictMssStats.total++; + g_ictMssStats.sumR += r; + if(isTp) + g_ictMssStats.tpCount++; + else + g_ictMssStats.slCount++; if(nets[k] > 0.0) g_ictMssStats.winCount++; @@ -140,7 +176,7 @@ void IctMssStats_Recompute(const string sym, const ulong magic) g_ictMssStatsLastScan = TimeCurrent(); if(InpMssLogJournal) - PrintFormat("[ICT2026/Stats] Recompute %s magic=%I64u | total=%d TP=%d SL=%d khác=%d | net=%.2f sumR=%.2f", + PrintFormat("[ICT2026/Stats] Recompute %s magic=%I64u | total=%d TP=%d SL=%d (skip khác=%d) | net=%.2f sumR=%.2f", sym, magic, g_ictMssStats.total, g_ictMssStats.tpCount, g_ictMssStats.slCount, g_ictMssStats.otherCount, @@ -157,18 +193,18 @@ double IctMssStats_WinratePct() double IctMssStats_AvgR() { - if(g_ictMssStats.total <= 0) + const int decided = g_ictMssStats.tpCount + g_ictMssStats.slCount; + if(decided <= 0) return 0.0; - return g_ictMssStats.sumR / (double)g_ictMssStats.total; + return g_ictMssStats.sumR / (double)decided; } string IctMssStats_LineCounts() { - return StringFormat("Stats: %d lệnh | TP %d | SL %d | khác %d", + return StringFormat("Stats: %d lệnh | TP %d | SL %d", g_ictMssStats.total, g_ictMssStats.tpCount, - g_ictMssStats.slCount, - g_ictMssStats.otherCount); + g_ictMssStats.slCount); } string IctMssStats_LinePerf() diff --git a/Include/ICT2026/StructureCore.mqh b/Include/ICT2026/StructureCore.mqh index 80c5a28..f324248 100644 --- a/Include/ICT2026/StructureCore.mqh +++ b/Include/ICT2026/StructureCore.mqh @@ -1,5 +1,37 @@ //+------------------------------------------------------------------+ -//| StructureCore.mqh — body break & BOS/CHoCH detect (dùng chung) | +//| StructureCore.mqh — body break primitives + BOS/CHoCH detector | +//+------------------------------------------------------------------+ +//| Helpers cho cả Daily + Intraday — phá level dùng THÂN NẾN (body), | +//| không dùng wick. | +//| | +//| IctBodyTop / IctBodyBot — body top/bot của nến shift | +//| IctBodyBreakBuffer(sym) — N × spread (≥ _Point) | +//| IctBodyBreakAbove(level) — body close > level + buffer | +//| IctBodyBreakBelow(level) — body close < level − buffer | +//| | +//| Buffer (v1.186): siết chặt — thân nến phải đóng vượt level ≥ | +//| InpBodyBreakSpreadMult × (Ask−Bid) (mặc định 10 spread). | +//| Áp dụng cho mọi callsite: BOS/CHoCH (Daily, Intraday), MSS | +//| confirm, H1 FVG body-invalidate. | +//| | +//| BOS vs CHoCH (cùng rule, khác bản chất): | +//| BOS = phá Key tiếp diễn xu hướng | +//| CHoCH= phá Key NGƯỢC xu hướng (đảo chiều) | +//| | +//| Bear (LH-LL): Key1=H0, Key2=L0 | +//| - Phá H0 ⇒ CHoCH (chuyển sang Bull sớm) | +//| - Phá L0 ⇒ BOS | +//| Bull (HH-HL): Key1=L0, Key2=H0 | +//| - Phá L0 ⇒ CHoCH (chuyển sang Bear sớm) | +//| - Phá H0 ⇒ BOS | +//| | +//| Detection: trên bar đã đóng (shift=1) — KHÔNG dùng bar đang chạy. | +//| | +//| Public API: | +//| double IctBodyTop/Bot(sym, tf, shift) | +//| bool IctBodyBreakAbove/Below(sym, tf, shift, level) | +//| ENUM_ICT_MS_EVENT IctDetectStructureEvent(sym, tf, &set, | +//| structural, shift) | //+------------------------------------------------------------------+ #ifndef ICT2026_STRUCTURECORE_MQH #define ICT2026_STRUCTURECORE_MQH @@ -16,16 +48,29 @@ double IctBodyBottom(const string sym, const ENUM_TIMEFRAMES tf, const int shift return MathMin(iOpen(sym, tf, shift), iClose(sym, tf, shift)); } +// Buffer xác nhận phá level: N × spread hiện tại (fallback _Point khi spread = 0). +// Siết chặt body-break để loại các close-through "sát mép" do biến động nhỏ/spike. +// N = InpBodyBreakSpreadMult (mặc định 10). +double IctBodyBreakBuffer(const string sym) +{ + const double sp = MathMax(0.0, + SymbolInfoDouble(sym, SYMBOL_ASK) - + SymbolInfoDouble(sym, SYMBOL_BID)); + const double unit = (sp > 0.0) ? sp : _Point; + const double mult = MathMax(0.0, (double)InpBodyBreakSpreadMult); + return MathMax(_Point, unit * mult); +} + bool IctBodyBreakAbove(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const double level) { - return IctBodyTop(sym, tf, shift) > level + _Point; + return IctBodyTop(sym, tf, shift) > level + IctBodyBreakBuffer(sym); } bool IctBodyBreakBelow(const string sym, const ENUM_TIMEFRAMES tf, const int shift, const double level) { - return IctBodyBottom(sym, tf, shift) < level - _Point; + return IctBodyBottom(sym, tf, shift) < level - IctBodyBreakBuffer(sym); } // BOS = Continue (Key LV2) | CHoCH = Reversal (Key LV1) diff --git a/Include/ICT2026/StructureTrend.mqh b/Include/ICT2026/StructureTrend.mqh index 103f303..d3b1ce0 100644 --- a/Include/ICT2026/StructureTrend.mqh +++ b/Include/ICT2026/StructureTrend.mqh @@ -1,5 +1,28 @@ //+------------------------------------------------------------------+ -//| StructureTrend.mqh — trend rõ (HH-HL/LH-LL) vs sớm (sau CHoCH) | +//| StructureTrend.mqh — phân giải trend "rõ" vs "sớm" | +//+------------------------------------------------------------------+ +//| Module dùng chung cho cả Daily + Intraday — chia 2 phase: | +//| | +//| CLEAR ("rõ ràng") — HH-HL hoặc LH-LL pivot xác nhận | +//| ⇒ trend = UP / DOWN | +//| | +//| EARLY ("sớm") — sau CHoCH nhưng cấu trúc mới chưa rõ | +//| ⇒ trend = UP_EARLY / DOWN_EARLY (Bull/Bear sớm) | +//| giữ phase EARLY cho đến khi: | +//| - Pivot xác nhận HH-HL/LH-LL → CLEAR | +//| - CHoCH ngược → đổi phase EARLY ngược | +//| | +//| CHoCH ưu tiên trước pivot: nếu pivot vẫn LH-LL nhưng H[1] body | +//| phá H0 ⇒ trend = Bull sớm (không giữ Down). | +//| | +//| Mixed (không HH-HL không LH-LL) + không EARLY ⇒ NONE. | +//| | +//| Lưu ý "rõ" vs "sớm" do user tự đặt tên trong tiếng Việt — code | +//| dùng enum ENUM_ICT_TREND_PHASE = {CLEAR, EARLY}. | +//| | +//| Public API: | +//| bool IctResolveTrend(&ctx, &trendOut, &phaseOut, &eventOut) | +//| ctx = {sym, tf, swings, prevStructural, prevTrend, ...} | //+------------------------------------------------------------------+ #ifndef ICT2026_STRUCTURETREND_MQH #define ICT2026_STRUCTURETREND_MQH diff --git a/Include/ICT2026/Swing.mqh b/Include/ICT2026/Swing.mqh index 4ff1a2d..0713f5d 100644 --- a/Include/ICT2026/Swing.mqh +++ b/Include/ICT2026/Swing.mqh @@ -1,5 +1,39 @@ //+------------------------------------------------------------------+ -//| Swing.mqh — pivot & swing set H0–L1 (tham khảo HyperICT) | +//| Swing.mqh — pivot detection + swing set 4-point (H0/L0/H1/L1) | +//+------------------------------------------------------------------+ +//| Foundation module — không phụ thuộc bất kỳ file nào khác ngoài | +//| Types.mqh. | +//| | +//| Pivot definition (N-bar fractal, N = swingRange): | +//| Swing High at shift k = High[k] > High[k±i] for i=1..N | +//| Swing Low at shift k = Low[k] < Low[k±i] for i=1..N | +//| | +//| Build swing set (4-point leg): | +//| IctBuildBiasSwingSet — H0/L0/H1/L1 ghép theo leg time | +//| IctBuildIntradaySwingSet — 2 đỉnh + 2 đáy gần nhất (không leg)| +//| IctBuildConfirmSwingSet — như intraday cho M5 | +//| | +//| Classification (IctClassifyStructure): | +//| H0 > H1 && L0 > L1 ⇒ HH-HL (bull) | +//| H0 < H1 && L0 < L1 ⇒ LH-LL (bear) | +//| else ⇒ MIXED | +//| | +//| Key level: | +//| Bull: keyLv1 = L0, keyLv2 = H0 | +//| Bear: keyLv1 = H0, keyLv2 = L0 | +//| | +//| Fib validation (tùy chọn): | +//| Sóng hồi H1→L0 ≥ InpFibMinRatio × sóng L1→H1 | +//| | +//| Public API: | +//| bool IctIsSwingHigh/Low(sym, tf, shift, range) | +//| bool IctBuildBiasSwingSet(sym, tf, &set, range, lookback) | +//| bool IctBuildIntradaySwingSet(sym, tf, &set, range, lookback, | +//| recentBars) | +//| bool IctBuildConfirmSwingSet(sym, tf, &set, range, lookback, | +//| recentBars) | +//| ENUM_ICT_STRUCT IctClassifyStructure(&set) | +//| bool IctValidateFib(&set, minRatio) | //+------------------------------------------------------------------+ #ifndef ICT2026_SWING_MQH #define ICT2026_SWING_MQH diff --git a/Include/ICT2026/Types.mqh b/Include/ICT2026/Types.mqh index eb97d42..e031faa 100644 --- a/Include/ICT2026/Types.mqh +++ b/Include/ICT2026/Types.mqh @@ -1,5 +1,46 @@ //+------------------------------------------------------------------+ -//| Types.mqh — ICT 2026 data model | +//| Types.mqh — ICT 2026 data model (foundation layer) | +//+------------------------------------------------------------------+ +//| Mục đích: | +//| File này CHỈ CHỨA enums + structs + một vài hằng số. KHÔNG có | +//| function logic. Bất kỳ file nào trong ICT2026 module đều có thể | +//| include trực tiếp file này mà không lo cycle dependency. | +//| | +//| Enums chính: | +//| ENUM_ICT_BIAS : BULL / BEAR / NONE / RANGE | +//| ENUM_ICT_STRUCT : HH_HL / LH_LL / MIXED / UNKNOWN | +//| ENUM_ICT_MS_EVENT : NONE / BOS / CHOCH | +//| ENUM_ICT_TREND : UP / DOWN / NONE (+ EARLY phase) | +//| ENUM_ICT_FVG_SIDE : BULL / BEAR / NONE | +//| ENUM_ICT_FVG_STATE : AVAILABLE / USED | +//| ENUM_ICT_MSS_PHASE : IDLE → H1_TOUCH → CHOCH → M5_FVG → | +//| ENTRY_FILL → READY | +//| ENUM_ICT_HTF_BIAS : UP / DOWN / SIDEWAY / NONE (PreviousDay)| +//| ENUM_ICT_TREND_PHASE : CLEAR (HH-HL/LH-LL rõ) / EARLY (sau | +//| CHoCH chưa rõ) | +//| | +//| Structs chính: | +//| IctSwingPoint : 1 pivot {price, time, shift} | +//| IctSwingSet : 4 pivot H0/L0/H1/L1 + flags isComplete | +//| IctTrendResolveCtx : input for IctResolveTrend | +//| IctDailyBiasState : bias, structural, lastEvent, pdh/pdl/eq, | +//| keyLv1/keyLv2, swings, reason, htf | +//| IctIntradayState : trend, isAllowTrade, swings, lastEvent | +//| IctFvgZone : side, state, lower/upper, createdTime, | +//| firstTouchTime, maxFillRatio, pdHigh/Low,| +//| pdEq, pdComplete, pdSwing1Set, id, ... | +//| IctMssState : phase, h1FvgId, h1TouchTime, h1WatchFvgId,| +//| chochLocked, chochBias, chochKeyLevel, | +//| chochKeyTime, chochTime, slSwingPrice, | +//| liveL0/H0/L1/H1 price+time, | +//| m5FvgId, m5FvgTime, pendingTicket, | +//| pendingEntry/Sl/Tp, pendingPlacedTime, | +//| partialTriggerPrice, partialCloseDone, | +//| beMovedDone, displayReason | +//| IctEaState : code (STATE enum), title, detail | +//| IctLowTfState : lastBarTime, lastConfirmBarTime, mss, | +//| activeCount, availableCount, | +//| confirmFvgCount, displayReason | //+------------------------------------------------------------------+ #ifndef ICT2026_TYPES_MQH #define ICT2026_TYPES_MQH @@ -232,6 +273,7 @@ struct IctMssState double pendingTp; double partialTriggerPrice; bool partialCloseDone; + bool beMovedDone; double liveL0Price; double liveH0Price; datetime liveL0Time; @@ -264,6 +306,7 @@ struct IctMssState pendingTp = 0.0; partialTriggerPrice = 0.0; partialCloseDone = false; + beMovedDone = false; liveL0Price = 0.0; liveH0Price = 0.0; liveL0Time = 0;