From 3cc1106ed9725477b483b100a18bff1f40985b75 Mon Sep 17 00:00:00 2001 From: Bell Date: Tue, 26 May 2026 20:27:12 +0700 Subject: [PATCH] Optimize POI range, SL range --- Experts/ICT_2026.mq5 | 4 +- Include/ICT2026/Config.mqh | 4 + Include/ICT2026/Fvg.mqh | 61 +++++++- Include/ICT2026/MssEntry.mqh | 249 +++++++++++++++++++++++------- Include/ICT2026/MssSetup.mqh | 89 ++++++++--- Include/ICT2026/README.md | 13 ++ Include/ICT2026/Types.mqh | 16 +- Profiles/Charts/Default/order.wnd | Bin 756 -> 106 bytes 8 files changed, 353 insertions(+), 83 deletions(-) diff --git a/Experts/ICT_2026.mq5 b/Experts/ICT_2026.mq5 index fcdaafd..327d15f 100644 --- a/Experts/ICT_2026.mq5 +++ b/Experts/ICT_2026.mq5 @@ -3,8 +3,8 @@ //| BOS = Continue | CHoCH = Reversal | //+------------------------------------------------------------------+ #property copyright "ICT 2026" -#property version "1.163" -#property description "ICT2026 MSS | OnlyStatsMode: skip render + in stats mỗi lệnh close + tổng kết deinit" +#property version "1.168" +#property description "ICT2026 MSS | Partial close 50% + SL→BE khi giá đạt swing iL0/iH0 (TP gồng xa hơn)" #include #include diff --git a/Include/ICT2026/Config.mqh b/Include/ICT2026/Config.mqh index d5e9659..6beb10f 100644 --- a/Include/ICT2026/Config.mqh +++ b/Include/ICT2026/Config.mqh @@ -52,6 +52,9 @@ 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 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) input int InpFvgExpireDays = 3; // Xóa Available sau N ngày input int InpFvgMaxZones = 24; // Số FVG tối đa trên chart input bool InpDrawFvgZones = true; // Vẽ FVG + Premium/Discount @@ -83,6 +86,7 @@ input double InpMssRiskPct = 1.0; // R % balance mỗ input int InpMssSlSpreadMult = 8; // SL buffer = N × spread (cộng ra ngoài max(H0,H1)/min(L0,L1)) 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 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) diff --git a/Include/ICT2026/Fvg.mqh b/Include/ICT2026/Fvg.mqh index 603ab1c..764c3b6 100644 --- a/Include/ICT2026/Fvg.mqh +++ b/Include/ICT2026/Fvg.mqh @@ -210,6 +210,28 @@ bool IctFvg_OverlapsDiscount(const IctFvgZone &zone) return (zone.lower < zone.pdEq - _Point && zone.upper > zone.pdLow - _Point); } +// % chiều cao FVG nằm trên equilibrium (Premium portion) +double IctFvg_PremiumOverlapPct(const IctFvgZone &zone) +{ + const double h = zone.upper - zone.lower; + if(h <= _Point || zone.pdEq <= 0.0) + return 0.0; + const double premLo = MathMax(zone.lower, zone.pdEq); + const double portion = MathMax(0.0, zone.upper - premLo); + return 100.0 * portion / h; +} + +// % chiều cao FVG nằm dưới equilibrium (Discount portion) +double IctFvg_DiscountOverlapPct(const IctFvgZone &zone) +{ + const double h = zone.upper - zone.lower; + if(h <= _Point || zone.pdEq <= 0.0) + return 0.0; + const double discHi = MathMin(zone.upper, zone.pdEq); + const double portion = MathMax(0.0, discHi - zone.lower); + return 100.0 * portion / h; +} + ENUM_ICT_PD_ZONE IctFvg_ComputeRepPd(const IctFvgZone &zone) { if(zone.pdEq <= 0.0) @@ -436,11 +458,42 @@ bool IctFvg_IsEntryRepPd(const IctFvgZone &zone) { 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) - return IctFvg_OverlapsPremium(zone); - if(g_ictDailyBias.bias == ICT_BIAS_BULL) - return IctFvg_OverlapsDiscount(zone); - return false; + pct = IctFvg_PremiumOverlapPct(zone); + else if(g_ictDailyBias.bias == ICT_BIAS_BULL) + pct = IctFvg_DiscountOverlapPct(zone); + else + return false; + + // minPct = 0 → chấp nhận bất kỳ overlap > 0 + if(minPct <= 0.0) + return pct > 1e-9; + return pct >= minPct - 1e-9; +} + +// Touch threshold cho POI: +// - 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) +{ + if(zone.pdEq <= 0.0) + return 100.0; + double pct = 0.0; + if(g_ictDailyBias.bias == ICT_BIAS_BEAR) + pct = IctFvg_PremiumOverlapPct(zone); + else if(g_ictDailyBias.bias == ICT_BIAS_BULL) + pct = IctFvg_DiscountOverlapPct(zone); + else + return 100.0; + + if(pct >= 100.0 - 1e-6) + return InpFvgTouchFillPure; // 100% trong vùng đúng → threshold thấp + if(pct > 1e-9) + return InpFvgTouchFillMixed; // Mixed → threshold cao + return 100.0; // 0% trong vùng đúng → không qualify } bool IctFvg_HasFillAtLeast(const IctFvgZone &zone, const double pct) diff --git a/Include/ICT2026/MssEntry.mqh b/Include/ICT2026/MssEntry.mqh index 46f395a..4f9898e 100644 --- a/Include/ICT2026/MssEntry.mqh +++ b/Include/ICT2026/MssEntry.mqh @@ -71,15 +71,20 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone, entryOut = slOut = tpOut = 0.0; reasonOut = ""; - entryOut = IctMssEntry_LimitPrice(m5Zone); - if(entryOut <= 0.0) + // ── Xác định side: ưu tiên M5 FVG, fallback chochBias khi không có FVG + const bool hasFvg = (m5Zone.side != ICT_FVG_NONE); + ENUM_ICT_BIAS bias = ICT_BIAS_NONE; + if(hasFvg) + bias = (m5Zone.side == ICT_FVG_BULL) ? ICT_BIAS_BULL : ICT_BIAS_BEAR; + else if(g_ictLowTf.mss.chochLocked) + bias = g_ictLowTf.mss.chochBias; + + if(bias == ICT_BIAS_NONE) { - reasonOut = "M5 FVG side không xác định"; + reasonOut = "Không xác định được side (no FVG + no chochBias)"; return false; } - - const bool isBuy = (m5Zone.side == ICT_FVG_BULL); - const ENUM_ICT_BIAS bias = isBuy ? ICT_BIAS_BULL : ICT_BIAS_BEAR; + const bool isBuy = (bias == ICT_BIAS_BULL); double swingSl = 0.0; if(g_ictLowTf.mss.chochLocked && g_ictLowTf.mss.slSwingPrice > 0.0) @@ -107,6 +112,60 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone, const double bufTp = InpMssTpSpreadMult * spreadUnit; const double minRR = MathMax(1.0, InpMssMinRR); + // SL final (đã buffer + spread) + slOut = isBuy ? swingSl - bufSl : swingSl + bufSl; + + // ── 2 candidate entries: + // A) M5 FVG limit (nếu có) + // B) MSS keyLV (chochKeyLevel — L0 broken cho bear, H0 broken cho bull) + const double mktBid = SymbolInfoDouble(sym, SYMBOL_BID); + const double mktAsk = SymbolInfoDouble(sym, SYMBOL_ASK); + + double entryFvg = 0.0, riskFvg = DBL_MAX; + if(hasFvg) + { + entryFvg = IctMssEntry_LimitPrice(m5Zone); + const double rA = isBuy ? (entryFvg - slOut) : (slOut - entryFvg); + const bool limitOk = isBuy ? (entryFvg < mktAsk - _Point) + : (entryFvg > mktBid + _Point); + if(entryFvg > 0.0 && rA > _Point && limitOk) + riskFvg = rA; + } + + double entryMss = g_ictLowTf.mss.chochKeyLevel; + double riskMss = DBL_MAX; + { + const double rB = isBuy ? (entryMss - slOut) : (slOut - entryMss); + const bool limitOk = isBuy ? (entryMss < mktAsk - _Point) + : (entryMss > mktBid + _Point); + if(entryMss > 0.0 && rB > _Point && limitOk) + riskMss = rB; + } + + string entrySource = ""; + if(riskFvg < DBL_MAX && (riskMss == DBL_MAX || riskFvg < riskMss)) + { + entryOut = entryFvg; + entrySource = StringFormat("M5 FVG (risk %.2f < MSS %.2f)", + riskFvg, (riskMss == DBL_MAX ? 0.0 : riskMss)); + } + else if(riskMss < DBL_MAX) + { + entryOut = entryMss; + if(hasFvg) + entrySource = StringFormat("MSS keyLV (risk %.2f ≤ FVG %.2f)", + riskMss, (riskFvg == DBL_MAX ? 999.99 : riskFvg)); + else + entrySource = "MSS keyLV (no M5 FVG)"; + } + else + { + reasonOut = StringFormat( + "Cả 2 entry không hợp lệ | mkt=%.2f/%.2f | FVG=%.2f | MSS=%.2f | SL=%.2f", + mktBid, mktAsk, entryFvg, entryMss, slOut); + 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; @@ -119,53 +178,55 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone, // ── Logic TP (1 ngưỡng duy nhất): // RR(TP@iL0/iH0) > InpMssMinRR (= 2.0) → dùng TP tại iL0/iH0 (mục tiêu sóng H1) // Ngược lại (RR ≤ 2.0 hoặc iL0/iH0 không khả dụng) → TP cố định = entry ± risk × InpMssMinRR (= 2R) + const double risk = isBuy ? (entryOut - slOut) : (slOut - entryOut); + if(risk <= _Point) + { + reasonOut = StringFormat("Risk≤0 (SL %.5f vs entry %.5f)", slOut, entryOut); + return false; + } + + // Swing-based TP (= TP "tự nhiên" tại iL0/iH0 ± bufTp) — dùng cho partial close trigger + double swingTp = 0.0; if(isBuy) { - slOut = swingSl - bufSl; - const double risk = entryOut - slOut; - if(risk <= _Point) - { - reasonOut = StringFormat("Risk≤0 (SL %.5f ≥ entry %.5f)", slOut, entryOut); - return false; - } - - double tpAtIH0 = 0.0; - double rrAtIH0 = 0.0; + double tpAtIH0 = 0.0, rrAtIH0 = 0.0; if(tpTarget > 0.0 && tpTarget > entryOut + bufTp + _Point) { tpAtIH0 = tpTarget - bufTp; rrAtIH0 = (tpAtIH0 - entryOut) / risk; + swingTp = tpAtIH0; } - - if(rrAtIH0 > minRR) - tpOut = tpAtIH0; - else - tpOut = entryOut + risk * minRR; + tpOut = (rrAtIH0 > minRR) ? tpAtIH0 : (entryOut + risk * minRR); } else { - slOut = swingSl + bufSl; - const double risk = slOut - entryOut; - if(risk <= _Point) - { - reasonOut = StringFormat("Risk≤0 (SL %.5f ≤ entry %.5f)", slOut, entryOut); - return false; - } - - double tpAtIL0 = 0.0; - double rrAtIL0 = 0.0; + double tpAtIL0 = 0.0, rrAtIL0 = 0.0; if(tpTarget > 0.0 && tpTarget < entryOut - bufTp - _Point) { tpAtIL0 = tpTarget + bufTp; rrAtIL0 = (entryOut - tpAtIL0) / risk; + swingTp = tpAtIL0; } - - if(rrAtIL0 > minRR) - tpOut = tpAtIL0; - else - tpOut = entryOut - risk * minRR; + tpOut = (rrAtIL0 > minRR) ? tpAtIL0 : (entryOut - risk * minRR); } + // Partial close trigger: chỉ kích hoạt khi TP gồng XA HƠN swing TP + // BUY: tpOut > swingTp + _Point → partial khi giá lên đến swingTp + // SELL: tpOut < swingTp - _Point → partial khi giá xuống đến swingTp + double partialTrigger = 0.0; + if(swingTp > 0.0 && InpMssPartialClosePct > 0.0) + { + if(isBuy && tpOut > swingTp + _Point) + partialTrigger = swingTp; + else if(!isBuy && tpOut < swingTp - _Point) + partialTrigger = swingTp; + } + g_ictLowTf.mss.partialTriggerPrice = partialTrigger; + g_ictLowTf.mss.partialCloseDone = false; + + // entrySource lưu vào reasonOut để display thấy nguồn entry + reasonOut = entrySource; + entryOut = IctMssEntry_NormalizePrice(sym, entryOut); slOut = IctMssEntry_NormalizePrice(sym, slOut); tpOut = IctMssEntry_NormalizePrice(sym, tpOut); @@ -291,6 +352,79 @@ void IctMss_OnPendingTimeout(const string sym, const ulong ticket, const int hou TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES)); } +// 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) +{ + if(InpMssPartialClosePct <= 0.0) + return; + if(g_ictLowTf.mss.partialCloseDone) + return; + if(g_ictLowTf.mss.partialTriggerPrice <= 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 volume = PositionGetDouble(POSITION_VOLUME); + const double curTp = PositionGetDouble(POSITION_TP); + const double bid = SymbolInfoDouble(sym, SYMBOL_BID); + const double ask = SymbolInfoDouble(sym, SYMBOL_ASK); + const double trig = g_ictLowTf.mss.partialTriggerPrice; + + bool reached = false; + if(ptype == POSITION_TYPE_BUY) + reached = (bid >= trig - _Point); + else if(ptype == POSITION_TYPE_SELL) + reached = (ask <= trig + _Point); + + if(!reached) + return; + + // Tính volume cần đóng (1 nửa, đã normalize step) + const double pct = MathMax(0.0, MathMin(100.0, InpMssPartialClosePct)) / 100.0; + const double vClose = IctMssEntry_NormalizeVolume(sym, volume * pct); + if(vClose <= 0.0 || vClose >= volume) + return; + + g_ictMssTrade.SetExpertMagicNumber(InpMssMagic); + if(!g_ictMssTrade.PositionClosePartial(ticket, vClose)) + { + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] PartialClose fail #%I64u vol=%.2f — %d %s", + ticket, vClose, g_ictMssTrade.ResultRetcode(), + g_ictMssTrade.ResultRetcodeDescription()); + return; + } + + // Dời SL về BE (entry price). Giữ TP cũ. + if(!g_ictMssTrade.PositionModify(ticket, openPx, curTp)) + { + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] BE move fail #%I64u — %d %s", + ticket, g_ictMssTrade.ResultRetcode(), + g_ictMssTrade.ResultRetcodeDescription()); + } + + g_ictLowTf.mss.partialCloseDone = true; + + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] PartialClose #%I64u %.0f%% (%.2f→%.2f lot) @ %.2f | SL→BE %.2f | TP %.2f", + ticket, InpMssPartialClosePct, volume, volume - vClose, + (ptype == POSITION_TYPE_BUY ? bid : ask), + openPx, curTp); + return; + } +} + void IctMssEntry_CheckPendingTimeout(const string sym) { if(InpMssPendingExpireHours <= 0) @@ -447,6 +581,7 @@ void IctMssEntry_Update(const string sym) IctMssEntry_CheckEodCancel(sym); IctMssEntry_CheckPendingTimeout(sym); + IctMssEntry_CheckPartialClose(sym); if(g_ictLowTf.mss.phase == ICT_MSS_IDLE && g_ictLowTf.mss.pendingTicket > 0) { @@ -456,27 +591,31 @@ void IctMssEntry_Update(const string sym) if(!InpMssTradeEnabled) { - if(g_ictLowTf.mss.phase >= ICT_MSS_M5_FVG) + if(g_ictLowTf.mss.phase >= ICT_MSS_CHOCH) IctMss_JournalEntryBlock("InpMssTradeEnabled=false"); return; } - if(!g_ictIntraday.isAllowTrade || g_ictLowTf.mss.phase < ICT_MSS_M5_FVG || - g_ictLowTf.mss.m5FvgId == 0) + // 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 || + g_ictLowTf.mss.phase < ICT_MSS_CHOCH || + !g_ictLowTf.mss.chochLocked || + g_ictLowTf.mss.chochKeyLevel <= 0.0) { if(g_ictLowTf.mss.pendingTicket > 0) { IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket); g_ictLowTf.mss.pendingTicket = 0; } - if(g_ictLowTf.mss.phase >= ICT_MSS_CHOCH) + if(g_ictLowTf.mss.phase >= ICT_MSS_H1_TOUCH) { if(!g_ictIntraday.isAllowTrade) IctMss_JournalEntryBlock("AllowTrade=false"); - else if(g_ictLowTf.mss.phase < ICT_MSS_M5_FVG) + else if(g_ictLowTf.mss.phase < ICT_MSS_CHOCH) IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason); - else if(g_ictLowTf.mss.m5FvgId == 0) - IctMss_JournalEntryBlock("Chưa có M5 FVG cho entry"); + else + IctMss_JournalEntryBlock("Chờ MSS lock (chochKeyLevel)"); } return; } @@ -491,11 +630,16 @@ void IctMssEntry_Update(const string sym) return; } - const int mIdx = IctConfirmFvg_FindById(g_ictLowTf.mss.m5FvgId); - if(mIdx < 0) - return; + // M5 FVG là OPTIONAL: nếu có thì compare với MSS keyLV để chọn entry SL gần hơn + IctFvgZone m5; + m5.side = ICT_FVG_NONE; + if(g_ictLowTf.mss.m5FvgId > 0) + { + const int mIdx = IctConfirmFvg_FindById(g_ictLowTf.mss.m5FvgId); + if(mIdx >= 0) + m5 = g_ictConfirmFvgZones[mIdx]; + } - const IctFvgZone m5 = g_ictConfirmFvgZones[mIdx]; double entry = 0.0, sl = 0.0, tp = 0.0; string lvlReason = ""; if(!IctMssEntry_ComputeLevels(sym, m5, entry, sl, tp, lvlReason)) @@ -507,7 +651,8 @@ void IctMssEntry_Update(const string sym) return; } - const bool isBuy = (m5.side == ICT_FVG_BULL); + const bool isBuy = (g_ictLowTf.mss.chochBias == ICT_BIAS_BULL); + const string entrySource = lvlReason; // ComputeLevels trả entrySource qua reasonOut const double vol = IctMssEntry_NormalizeVolume(sym, IctMssEntry_VolumeForRisk(sym, isBuy, entry, sl)); if(vol <= 0.0) @@ -527,8 +672,8 @@ void IctMssEntry_Update(const string sym) MathAbs(g_ictLowTf.mss.pendingSl - sl) < pt && MathAbs(g_ictLowTf.mss.pendingTp - tp) < pt) { - g_ictLowTf.mss.displayReason = StringFormat("Limit %.2f SL %.2f TP %.2f | RR %.1f", - entry, sl, tp, rr); + g_ictLowTf.mss.displayReason = StringFormat("Limit %.2f SL %.2f TP %.2f | RR %.1f | %s", + entry, sl, tp, rr, entrySource); return; } IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket); @@ -550,8 +695,8 @@ void IctMssEntry_Update(const string sym) g_ictLowTf.mss.pendingEntry = entry; g_ictLowTf.mss.pendingSl = sl; g_ictLowTf.mss.pendingTp = tp; - g_ictLowTf.mss.displayReason = StringFormat("Sell/Buy limit %.2f | SL %.2f (H0/L0) | TP %.2f RR%.1f", - entry, sl, tp, rr); + g_ictLowTf.mss.displayReason = StringFormat("Sell/Buy limit %.2f | SL %.2f | TP %.2f RR%.1f | %s", + entry, sl, tp, rr, entrySource); } #endif diff --git a/Include/ICT2026/MssSetup.mqh b/Include/ICT2026/MssSetup.mqh index 9e7e0f6..86f854b 100644 --- a/Include/ICT2026/MssSetup.mqh +++ b/Include/ICT2026/MssSetup.mqh @@ -148,8 +148,9 @@ 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. -// Tránh pick H1 FVG đã có firstTouch từ trước khi lệnh cũ TP/SL. +// 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%) bool IctMss_HasFreshFvgTouch(const string sym, IctFvgZone &h1) { const datetime tTouch = IctMss_GetFvgTouchTime(sym, h1); @@ -157,6 +158,12 @@ bool IctMss_HasFreshFvgTouch(const string sym, IctFvgZone &h1) return false; if(g_ictMssAfterCloseGuard > 0 && tTouch < g_ictMssAfterCloseGuard) return false; + + const double requiredPct = IctFvg_RequiredTouchFillPct(h1); + if(requiredPct >= 100.0 - 1e-6) + return false; + if(h1.maxFillRatio * 100.0 < requiredPct - 1e-9) + return false; return true; } @@ -393,20 +400,24 @@ bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf, if(!IctMss_UpdateLiveM5Swings(sym, bias, tTouch)) return false; - if(g_ictLowTf.mss.liveL0Time < tTouch && g_ictLowTf.mss.liveH0Time < tTouch) - return false; - datetime breakT = 0; if(bias == ICT_BIAS_BEAR) { + // Pullback bullish phải có CẤU TRÚC ĐẦY ĐỦ: 2 đỉnh (H0 & H1) đều SAU touch + // → tránh case mới có 1 đỉnh từ pullback mới, H1 còn dính sóng cũ + if(g_ictLowTf.mss.liveH1Price <= 0.0 || + g_ictLowTf.mss.liveH0Time < tTouch || + g_ictLowTf.mss.liveH1Time < tTouch) + return false; + if(!IctMss_FindMssBreakBar(sym, tf, g_ictLowTf.mss.liveL0Price, g_ictLowTf.mss.liveL0Time, true, breakT)) return false; if(breakT < tTouch) return false; - // SL trên cao nhất giữa H0 và H1 (cap toàn pullback bull) + buffer ATR (cộng ở MssEntry) + // SL trên cao nhất giữa H0 và H1 (cap toàn pullback bull) + buffer (cộng ở MssEntry) double slSwingHi = g_ictLowTf.mss.liveH0Price; if(g_ictLowTf.mss.liveH1Price > slSwingHi) slSwingHi = g_ictLowTf.mss.liveH1Price; @@ -417,13 +428,20 @@ bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf, } else if(bias == ICT_BIAS_BULL) { + // Pullback bearish phải có CẤU TRÚC ĐẦY ĐỦ: 2 đáy (L0 & L1) đều SAU touch + // → tránh case mới có 1 đáy từ pullback mới, L1 còn dính sóng cũ + if(g_ictLowTf.mss.liveL1Price <= 0.0 || + g_ictLowTf.mss.liveL0Time < tTouch || + g_ictLowTf.mss.liveL1Time < tTouch) + return false; + if(!IctMss_FindMssBreakBar(sym, tf, g_ictLowTf.mss.liveH0Price, g_ictLowTf.mss.liveH0Time, false, breakT)) return false; if(breakT < tTouch) return false; - // SL dưới thấp nhất giữa L0 và L1 (đáy toàn pullback bear) + buffer ATR (trừ ở MssEntry) + // SL dưới thấp nhất giữa L0 và L1 (đáy toàn pullback bear) + buffer (trừ ở MssEntry) double slSwingLo = g_ictLowTf.mss.liveL0Price; if(g_ictLowTf.mss.liveL1Price > 0.0 && g_ictLowTf.mss.liveL1Price < slSwingLo) slSwingLo = g_ictLowTf.mss.liveL1Price; @@ -437,6 +455,7 @@ bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf, g_ictLowTf.mss.chochTime = breakT; g_ictLowTf.mss.chochLocked = true; + g_ictLowTf.mss.chochBias = bias; return true; } @@ -765,14 +784,25 @@ void IctMss_Update(const string sym) const datetime tTouch = IctMss_GetFvgTouchTime(sym, g_ictFvgZones[hIdx]); const bool staleTouch = (tTouch > 0 && g_ictMssAfterCloseGuard > 0 && tTouch < g_ictMssAfterCloseGuard); - g_ictLowTf.mss.displayReason = staleTouch ? - StringFormat("POI #%I64u [%.0f–%.0f] — chờ touch mới (sau lệnh trước)", - g_ictFvgZones[hIdx].id, - g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper) : - StringFormat("POI #%I64u [%.0f–%.0f] dist %.0f pts — chờ chạm", - g_ictFvgZones[hIdx].id, - g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper, - dist / _Point); + const double reqPct = IctFvg_RequiredTouchFillPct(g_ictFvgZones[hIdx]); + const double curPct = g_ictFvgZones[hIdx].maxFillRatio * 100.0; + if(staleTouch) + g_ictLowTf.mss.displayReason = StringFormat( + "POI #%I64u [%.0f–%.0f] — chờ touch mới (sau lệnh trước)", + g_ictFvgZones[hIdx].id, + g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper); + else if(tTouch > 0 && curPct < reqPct) + g_ictLowTf.mss.displayReason = StringFormat( + "POI #%I64u [%.0f–%.0f] fill %.0f%% / cần %.0f%% — chờ vào sâu hơn", + g_ictFvgZones[hIdx].id, + g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper, + curPct, reqPct); + else + g_ictLowTf.mss.displayReason = StringFormat( + "POI #%I64u [%.0f–%.0f] dist %.0f pts — chờ chạm", + g_ictFvgZones[hIdx].id, + g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper, + dist / _Point); return; } @@ -816,14 +846,33 @@ void IctMss_Update(const string sym) const datetime tTouch = g_ictLowTf.mss.h1TouchTime; if(IctMss_UpdateLiveM5Swings(sym, g_ictDailyBias.bias, tTouch)) { - if(g_ictDailyBias.bias == ICT_BIAS_BEAR) + // Check cấu trúc pullback đầy đủ (2 đỉnh/đáy đều sau touch) + const bool fullStruct = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ? + (g_ictLowTf.mss.liveH1Price > 0.0 && + g_ictLowTf.mss.liveH0Time >= tTouch && + g_ictLowTf.mss.liveH1Time >= tTouch) : + (g_ictLowTf.mss.liveL1Price > 0.0 && + g_ictLowTf.mss.liveL0Time >= tTouch && + g_ictLowTf.mss.liveL1Time >= tTouch); + + if(!fullStruct) + { + g_ictLowTf.mss.displayReason = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ? + "Chờ pullback đủ cấu trúc (2 đỉnh H0&H1 sau touch)" : + "Chờ pullback đủ cấu trúc (2 đáy L0&L1 sau touch)"; + } + else if(g_ictDailyBias.bias == ICT_BIAS_BEAR) g_ictLowTf.mss.displayReason = StringFormat( - "Chờ MSS↓ phá L0=%.2f | H0=%.2f (M5 bull)", - g_ictLowTf.mss.liveL0Price, g_ictLowTf.mss.liveH0Price); + "Chờ MSS↓ phá L0=%.2f | H0=%.2f H1=%.2f (M5 bull)", + g_ictLowTf.mss.liveL0Price, + g_ictLowTf.mss.liveH0Price, + g_ictLowTf.mss.liveH1Price); else g_ictLowTf.mss.displayReason = StringFormat( - "Chờ MSS↑ phá H0=%.2f | L0=%.2f (M5 bear)", - g_ictLowTf.mss.liveH0Price, g_ictLowTf.mss.liveL0Price); + "Chờ MSS↑ phá H0=%.2f | L0=%.2f L1=%.2f (M5 bear)", + g_ictLowTf.mss.liveH0Price, + g_ictLowTf.mss.liveL0Price, + g_ictLowTf.mss.liveL1Price); } else g_ictLowTf.mss.displayReason = "M5 trong FVG — chờ pivot L0/H0"; diff --git a/Include/ICT2026/README.md b/Include/ICT2026/README.md index 6778e14..00e0ac6 100644 --- a/Include/ICT2026/README.md +++ b/Include/ICT2026/README.md @@ -582,6 +582,19 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias(); - Hiển thị 2 dòng cuối panel: counts + WR/Ravg/Net - Recompute trên `OnInit` và sau mỗi `OnTradeTransaction` close +### v1.164 — Entry chọn M5 FVG vs MSS keyLV theo SL gần nhất + +- **Bỏ yêu cầu phải có M5 FVG**: gating mở từ `phase >= ICT_MSS_CHOCH` (sau MSS lock) — không chờ M5_FVG +- Thêm field `chochBias` trong `IctMssState` (lưu side BEAR/BULL khi `TryLockMss` thành công) +- `IctMssEntry_ComputeLevels`: tính 2 candidate entries, chọn cái có risk = `|entry − SL|` nhỏ hơn: + - **A) M5 FVG limit** (nếu có): `IctMssEntry_LimitPrice(m5Zone)` + - **B) MSS keyLV**: `chochKeyLevel` (L0 phá cho BEAR, H0 phá cho BULL) +- Lọc validity: limit phải đúng phía thị trường (`entry < ask` cho BUY, `entry > bid` cho SELL), risk > 0 +- Nếu có FVG nhưng entry@FVG cho risk lớn hơn entry@MSS → chọn MSS +- Nếu chưa có FVG → chọn MSS ngay khi MSS lock +- DisplayReason in nguồn entry: `"M5 FVG (risk X < MSS Y)"` hoặc `"MSS keyLV (risk X ≤ FVG Y)"` / `"MSS keyLV (no M5 FVG)"` +- Trên mỗi tick: nếu candidate mới tốt hơn (M5 FVG xuất hiện sau MSS) → cancel limit cũ + replace + ### v1.160 — Pending timeout 4h → reset chờ FVG mới - Input: `InpMssPendingExpireHours = 4` (mặc định, 0 = không timeout) diff --git a/Include/ICT2026/Types.mqh b/Include/ICT2026/Types.mqh index 67f6aa4..eb97d42 100644 --- a/Include/ICT2026/Types.mqh +++ b/Include/ICT2026/Types.mqh @@ -224,11 +224,14 @@ struct IctMssState datetime m5FvgTime; double slSwingPrice; bool chochLocked; + ENUM_ICT_BIAS chochBias; ulong pendingTicket; datetime pendingPlacedTime; double pendingEntry; double pendingSl; double pendingTp; + double partialTriggerPrice; + bool partialCloseDone; double liveL0Price; double liveH0Price; datetime liveL0Time; @@ -253,11 +256,14 @@ struct IctMssState m5FvgTime = 0; slSwingPrice = 0.0; chochLocked = false; - pendingTicket = 0; - pendingPlacedTime = 0; - pendingEntry = 0.0; - pendingSl = 0.0; - pendingTp = 0.0; + chochBias = ICT_BIAS_NONE; + pendingTicket = 0; + pendingPlacedTime = 0; + pendingEntry = 0.0; + pendingSl = 0.0; + pendingTp = 0.0; + partialTriggerPrice = 0.0; + partialCloseDone = false; liveL0Price = 0.0; liveH0Price = 0.0; liveL0Time = 0; diff --git a/Profiles/Charts/Default/order.wnd b/Profiles/Charts/Default/order.wnd index b64f0926516d015c5a1bed3b8101dbdcf62839d3..f8deec0706a182f984dc29f8ae7b49720b06a2a6 100644 GIT binary patch delta 9 Qcmeyunl&M2vKJE{02C7gJpcdz literal 756 zcmaLVI|>3p6hzS*3vOlTD#zb)#Kd@T{eHnjyk^}LUA%Yb`|Zqy6AM=cGZU}-!kX3l z^hq|FM3bE+drc0S93{zf?%z`fn=Mq|*rrjPu+=s*2xbt>AecebplVPxC}uEp)A6Ux QepR)tm|NAY>eer}2k3-#M*si-