From b3132b500bf500970224c102154707aaac07c146 Mon Sep 17 00:00:00 2001 From: Bell Date: Mon, 25 May 2026 23:24:30 +0700 Subject: [PATCH] =?UTF-8?q?Th=C3=AAm=20th=E1=BB=91ng=20k=C3=AA=20cho=20bot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Experts/ICT_2026.mq5 | 42 +++++++- Include/ICT2026/Config.mqh | 3 + Include/ICT2026/MssEntry.mqh | 124 +++++++++++++++++++++++- Include/ICT2026/Panel.mqh | 22 ++++- Include/ICT2026/README.md | 30 ++++++ Include/ICT2026/Stats.mqh | 182 +++++++++++++++++++++++++++++++++++ experts.dat | Bin 86767 -> 87324 bytes 7 files changed, 398 insertions(+), 5 deletions(-) create mode 100644 Include/ICT2026/Stats.mqh diff --git a/Experts/ICT_2026.mq5 b/Experts/ICT_2026.mq5 index 703aa43..f2f1d67 100644 --- a/Experts/ICT_2026.mq5 +++ b/Experts/ICT_2026.mq5 @@ -3,7 +3,7 @@ //| BOS = Continue | CHoCH = Reversal | //+------------------------------------------------------------------+ #property copyright "ICT 2026" -#property version "1.138" +#property version "1.142" #property description "ICT2026 MSS limit entry + SL swing CHoCH + TP intraday" #include @@ -27,15 +27,18 @@ int OnInit() if(!lowOk) Print("[ICT2026] LowTF/FVG: chưa đủ dữ liệu ", EnumToString(InpFvgTf)); + IctMssStats_Recompute(_Symbol, InpMssMagic); + if(biasOk || intraOk) { IctEaState_Refresh(_Symbol); - PrintFormat("[ICT2026] Init — EA=%s | Bias=%s | H1=%s | Allow=%s | FVG=%d", + PrintFormat("[ICT2026] Init — EA=%s | Bias=%s | H1=%s | Allow=%s | FVG=%d | Stats=%d trades", IctEaState_Code(IctEaState_Current()), IctBiasDisplayShort(g_ictDailyBias.bias), IctTrendDisplayShort(g_ictIntraday.trend), g_ictIntraday.isAllowTrade ? "true" : "false", - g_ictLowTf.availableCount); + g_ictLowTf.availableCount, + g_ictMssStats.total); IctPanel_Render(_Symbol); IctDraw_Render(_Symbol); } @@ -87,6 +90,39 @@ void OnChartEvent(const int id, const long &lparam, const double &dparam, const } } +//+------------------------------------------------------------------+ +//| Bắt close (SL/TP/manual) → mark M5 FVG Used + reset MSS pipeline | +//+------------------------------------------------------------------+ +void OnTradeTransaction(const MqlTradeTransaction &trans, + const MqlTradeRequest &request, + const MqlTradeResult &result) +{ + if(trans.type != TRADE_TRANSACTION_DEAL_ADD) + return; + + const ulong deal = trans.deal; + if(deal == 0 || !HistoryDealSelect(deal)) + return; + if(HistoryDealGetString(deal, DEAL_SYMBOL) != _Symbol) + return; + if((ulong)HistoryDealGetInteger(deal, DEAL_MAGIC) != InpMssMagic) + return; + if(HistoryDealGetInteger(deal, DEAL_ENTRY) != DEAL_ENTRY_OUT) + return; + + const long reason = HistoryDealGetInteger(deal, DEAL_REASON); + const double net = HistoryDealGetDouble(deal, DEAL_PROFIT) + + HistoryDealGetDouble(deal, DEAL_SWAP) + + HistoryDealGetDouble(deal, DEAL_COMMISSION); + + IctMss_OnPositionClosed(_Symbol, reason, net); + IctMssStats_Recompute(_Symbol, InpMssMagic); + IctEaState_Refresh(_Symbol); + IctPanel_Render(_Symbol); + IctMssDraw_Render(_Symbol); + IctFvgDraw_Render(_Symbol); +} + //+------------------------------------------------------------------+ ENUM_ICT_BIAS ICT2026_GetDailyBias() { return g_ictDailyBias.bias; } diff --git a/Include/ICT2026/Config.mqh b/Include/ICT2026/Config.mqh index b897ec0..3bc3f2e 100644 --- a/Include/ICT2026/Config.mqh +++ b/Include/ICT2026/Config.mqh @@ -81,6 +81,9 @@ input double InpMssSlAtrMult = 0.5; // SL: buffer ngo input double InpMssMinRR = 2.0; // TP tối thiểu (× risk entry→SL) input int InpMssPendingExpireHours = 24; // Hết hạn pending (giờ, 0=không) 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) +input int InpMssEodMinute = 0; // Phút EOD input group "══ Debug ══" input bool InpDebug = true; diff --git a/Include/ICT2026/MssEntry.mqh b/Include/ICT2026/MssEntry.mqh index 889112a..9d96236 100644 --- a/Include/ICT2026/MssEntry.mqh +++ b/Include/ICT2026/MssEntry.mqh @@ -136,7 +136,7 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone, return true; } -bool IctMssEntry_HasOpenExposure(const string sym) +bool IctMssEntry_HasOpenPositionMagic(const string sym) { for(int i = PositionsTotal() - 1; i >= 0; i--) { @@ -148,6 +148,13 @@ bool IctMssEntry_HasOpenExposure(const string sym) if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMssMagic) return true; } + return false; +} + +bool IctMssEntry_HasOpenExposure(const string sym) +{ + if(IctMssEntry_HasOpenPositionMagic(sym)) + return true; for(int i = OrdersTotal() - 1; i >= 0; i--) { @@ -165,6 +172,109 @@ bool IctMssEntry_HasOpenExposure(const string sym) return false; } +string IctMssEntry_DealReasonText(const long r) +{ + switch((ENUM_DEAL_REASON)r) + { + case DEAL_REASON_SL: return "SL"; + case DEAL_REASON_TP: return "TP"; + case DEAL_REASON_SO: return "Stop Out"; + case DEAL_REASON_EXPERT: return "Expert close"; + case DEAL_REASON_CLIENT: return "Manual"; + case DEAL_REASON_MOBILE: return "Mobile"; + case DEAL_REASON_WEB: return "Web"; + default: return (r < 0) ? "Detected" : "Unknown"; + } +} + +void IctMssEntry_MarkM5FvgUsed(const string sym, const ulong fvgId) +{ + if(fvgId == 0) + return; + const int idx = IctConfirmFvg_FindById(fvgId); + if(idx < 0) + return; + g_ictConfirmFvgZones[idx].state = ICT_FVG_USED; + if(g_ictConfirmFvgZones[idx].fvgUsedTime == 0) + g_ictConfirmFvgZones[idx].fvgUsedTime = iTime(sym, InpConfirmTf, 0); +} + +void IctMss_OnEodCancel(const string sym, const ulong ticket) +{ + const ulong m5Id = g_ictLowTf.mss.m5FvgId; + const ulong h1Id = g_ictLowTf.mss.h1FvgId; + + if(ticket > 0 && OrderSelect(ticket)) + g_ictMssTrade.OrderDelete(ticket); + + IctMss_ResetState(); + g_ictLowTf.mss.displayReason = StringFormat( + "EOD cancel pending #%I64u | H1 #%I64u — reset, chờ phiên mới", + ticket, h1Id); + + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] EOD cancel limit #%I64u | H1 #%I64u | M5 #%I64u | reset → WAIT_FVG_TOUCH", + ticket, h1Id, m5Id); +} + +void IctMssEntry_CheckEodCancel(const string sym) +{ + if(!InpMssCancelPendingEod) + return; + if(g_ictLowTf.mss.pendingTicket == 0) + return; + if(IctMssEntry_HasOpenPositionMagic(sym)) + return; + + const ulong ticket = g_ictLowTf.mss.pendingTicket; + if(!OrderSelect(ticket)) + { + g_ictLowTf.mss.pendingTicket = 0; + return; + } + + MqlDateTime mdt; + TimeToStruct(TimeCurrent(), mdt); + const int nowMinutes = mdt.hour * 60 + mdt.min; + const int eodMinutes = InpMssEodHour * 60 + InpMssEodMinute; + if(nowMinutes < eodMinutes) + return; + + MqlDateTime d0; + d0.year = mdt.year; + d0.mon = mdt.mon; + d0.day = mdt.day; + d0.hour = 0; + d0.min = 0; + d0.sec = 0; + const datetime dayStart = StructToTime(d0); + + static datetime s_lastEodHandledDate = 0; + if(s_lastEodHandledDate == dayStart) + return; + s_lastEodHandledDate = dayStart; + + IctMss_OnEodCancel(sym, ticket); +} + +void IctMss_OnPositionClosed(const string sym, const long reason, const double netProfit) +{ + const ulong m5Id = g_ictLowTf.mss.m5FvgId; + const ulong h1Id = g_ictLowTf.mss.h1FvgId; + const string rt = IctMssEntry_DealReasonText(reason); + + IctMssEntry_MarkM5FvgUsed(sym, m5Id); + + IctMss_ResetState(); + g_ictLowTf.mss.displayReason = StringFormat( + "Đóng %s (net %.2f) | M5 FVG #%I64u → Used | chờ POI mới", + rt, netProfit, m5Id); + + if(InpMssLogJournal) + PrintFormat("[ICT2026/MSS] Close (%s, net=%.2f) | H1 #%I64u | M5 #%I64u → Used | reset → WAIT_FVG_TOUCH", + rt, netProfit, h1Id, m5Id); +} + bool IctMssEntry_CancelTicket(const ulong ticket) { if(ticket == 0) @@ -216,6 +326,18 @@ void IctMssEntry_Init() void IctMssEntry_Update(const string sym) { + static bool s_hadPosition = false; + const bool nowPosition = IctMssEntry_HasOpenPositionMagic(sym); + + if(s_hadPosition && !nowPosition && g_ictLowTf.mss.phase != ICT_MSS_IDLE) + { + // Fallback nếu OnTradeTransaction miss (reload, disconnect, …) + IctMss_OnPositionClosed(sym, -1L, 0.0); + } + s_hadPosition = nowPosition; + + IctMssEntry_CheckEodCancel(sym); + if(g_ictLowTf.mss.phase == ICT_MSS_IDLE && g_ictLowTf.mss.pendingTicket > 0) { IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket); diff --git a/Include/ICT2026/Panel.mqh b/Include/ICT2026/Panel.mqh index 9fb397d..238c489 100644 --- a/Include/ICT2026/Panel.mqh +++ b/Include/ICT2026/Panel.mqh @@ -10,10 +10,11 @@ #include #include #include +#include const string ICT26_PANEL_PFX = "ICT26_PNL_"; const string ICT26_PANEL_LEGACY = "ICT26_BIAS_PANEL"; -const int ICT26_PANEL_MAXLINE = 16; +const int ICT26_PANEL_MAXLINE = 20; const color ICT_PANEL_CLR_UP = clrLime; const color ICT_PANEL_CLR_DOWN = clrOrangeRed; @@ -197,6 +198,25 @@ void IctPanel_Render(const string sym) lines[n] = StringFormat("H1 FVG: %s", g_ictLowTf.displayReason); colors[n++] = (g_ictLowTf.availableCount > 0) ? ICT_PANEL_CLR_ALLOW : ICT_PANEL_CLR_MUTED; + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = "-------------------------"; + colors[n++] = ICT_PANEL_CLR_NEUTRAL; + + const color clrStatsCounts = (g_ictMssStats.total > 0) ? ICT_PANEL_CLR_ALLOW : ICT_PANEL_CLR_MUTED; + const color clrStatsPerf = (g_ictMssStats.netProfit > 0.0) ? ICT_PANEL_CLR_UP : + (g_ictMssStats.netProfit < 0.0) ? ICT_PANEL_CLR_DOWN : ICT_PANEL_CLR_MUTED; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = IctMssStats_LineCounts(); + colors[n++] = clrStatsCounts; + + ArrayResize(lines, n + 1); + ArrayResize(colors, n + 1); + lines[n] = IctMssStats_LinePerf(); + colors[n++] = clrStatsPerf; + for(int i = 0; i < n; i++) IctPanel_SetLine(ch, i, i * lh, lines[i], colors[i]); diff --git a/Include/ICT2026/README.md b/Include/ICT2026/README.md index f2aed4f..d1d22a1 100644 --- a/Include/ICT2026/README.md +++ b/Include/ICT2026/README.md @@ -566,6 +566,36 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias(); ## Changelog +### 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** + → selection bị thay → các index sau đọc từ history của 1 position → chỉ đếm được lệnh đầu tiên +- Fix: 2-pass — collect ticket/reason/net trước (selection toàn cục còn nguyên), tính R sau +- Thêm log journal `[ICT2026/Stats] Recompute …` để verify mỗi lần quét + +### v1.141 — Stats lệnh MSS trên panel + +- `Stats.mqh`: `IctMssStats_Recompute` quét HistoryDeals theo magic +- Tính: total, TP, SL, khác, win, loss, sumR, netProfit +- R thực = (close - entry)/(entry - SL) lấy từ deal IN + order SL +- WR = TP / (TP+SL); Ravg = sumR/total +- Hiển thị 2 dòng cuối panel: counts + WR/Ravg/Net +- Recompute trên `OnInit` và sau mỗi `OnTradeTransaction` close + +### v1.140 — Hủy pending cuối phiên Mỹ (EOD) + +- Input: `InpMssCancelPendingEod` (true), `InpMssEodHour` (23), `InpMssEodMinute` (0) — **server time** +- Mỗi tick: nếu có `pendingTicket` chưa fill + thời gian ≥ EOD → `OrderDelete` + `IctMss_ResetState` +- Idempotent theo ngày: dùng `s_lastEodHandledDate` để không lặp +- Bỏ qua nếu đang có position magic (chỉ hủy pending, không động vào lệnh đã khớp) + +### v1.139 — Reset MSS pipeline sau SL/TP/close (option A) + +- `OnTradeTransaction` lọc `DEAL_ENTRY_OUT` + magic → `IctMss_OnPositionClosed` +- Mark **M5 FVG → Used**, `IctMss_ResetState`, journal lý do (SL/TP/Manual/…) +- Fallback: detect transition `hadPosition → false` trong `IctMssEntry_Update` (miss OnTradeTransaction) +- Sau close: EaState = `WAIT_FVG_TOUCH` → `SelectNearestH1Poi` (H1 cũ đã Used từ v1.136 → bỏ qua) + ### v1.138 — POI H1 = FVG chưa Used **gần giá nhất** - `IctMss_SelectNearestH1Poi`: khoảng cách bid → FVG; bỏ qua Used; tie → FVG mới hơn diff --git a/Include/ICT2026/Stats.mqh b/Include/ICT2026/Stats.mqh new file mode 100644 index 0000000..95fd8e8 --- /dev/null +++ b/Include/ICT2026/Stats.mqh @@ -0,0 +1,182 @@ +//+------------------------------------------------------------------+ +//| Stats.mqh — thống kê lệnh MSS từ history (theo magic) | +//+------------------------------------------------------------------+ +#ifndef ICT2026_STATS_MQH +#define ICT2026_STATS_MQH + +#include + +struct IctMssStats +{ + int total; + int tpCount; + int slCount; + int otherCount; // manual / expert / mobile / web + int winCount; // profit > 0 + int lossCount; // profit < 0 + double sumR; // tổng R đạt được (đã quy chuẩn theo risk lúc vào lệnh) + double netProfit; + + void Clear() + { + total = tpCount = slCount = otherCount = 0; + winCount = lossCount = 0; + sumR = 0.0; + netProfit = 0.0; + } +}; + +IctMssStats g_ictMssStats; +static datetime g_ictMssStatsLastScan = 0; + +double IctMssStats_ComputeR(const ulong outDeal) +{ + const ulong posId = (ulong)HistoryDealGetInteger(outDeal, DEAL_POSITION_ID); + const double closePrice = HistoryDealGetDouble(outDeal, DEAL_PRICE); + if(posId == 0 || closePrice <= 0.0) + return 0.0; + + if(!HistorySelectByPosition(posId)) + return 0.0; + + double entryPrice = 0.0; + double slPrice = 0.0; + bool isBuy = false; + + const int n = HistoryDealsTotal(); + for(int i = 0; i < n; i++) + { + const ulong d = HistoryDealGetTicket(i); + if(d == 0) + continue; + if((ulong)HistoryDealGetInteger(d, DEAL_POSITION_ID) != posId) + continue; + if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_IN) + continue; + + entryPrice = HistoryDealGetDouble(d, DEAL_PRICE); + isBuy = (HistoryDealGetInteger(d, DEAL_TYPE) == DEAL_TYPE_BUY); + + const ulong orderTicket = (ulong)HistoryDealGetInteger(d, DEAL_ORDER); + if(orderTicket > 0 && HistoryOrderSelect(orderTicket)) + slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL); + break; + } + + if(entryPrice <= 0.0 || slPrice <= 0.0) + return 0.0; + + const double risk = isBuy ? (entryPrice - slPrice) : (slPrice - entryPrice); + if(risk <= _Point) + return 0.0; + + const double move = isBuy ? (closePrice - entryPrice) : (entryPrice - closePrice); + return move / risk; +} + +void IctMssStats_Recompute(const string sym, const ulong magic) +{ + g_ictMssStats.Clear(); + + if(!HistorySelect(0, TimeCurrent())) + return; + + // Pass 1: thu thập ticket + dữ liệu cơ bản trong khi global history selection còn nguyên. + // Lưu ý: nếu gọi HistorySelectByPosition (trong ComputeR) ở đây → selection bị thay, + // các index sau sẽ đọc từ history của 1 position → đếm thiếu lệnh. + ulong outTickets[]; + long reasons[]; + double nets[]; + int cached = 0; + + const int n = HistoryDealsTotal(); + ArrayResize(outTickets, n); + ArrayResize(reasons, n); + ArrayResize(nets, n); + + for(int i = 0; i < n; i++) + { + const ulong d = HistoryDealGetTicket(i); + if(d == 0) + continue; + if(HistoryDealGetString(d, DEAL_SYMBOL) != sym) + continue; + if((ulong)HistoryDealGetInteger(d, DEAL_MAGIC) != magic) + continue; + if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_OUT) + continue; + + outTickets[cached] = d; + reasons[cached] = HistoryDealGetInteger(d, DEAL_REASON); + nets[cached] = HistoryDealGetDouble(d, DEAL_PROFIT) + + HistoryDealGetDouble(d, DEAL_SWAP) + + HistoryDealGetDouble(d, DEAL_COMMISSION); + 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). + 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 + g_ictMssStats.otherCount++; + + if(nets[k] > 0.0) + g_ictMssStats.winCount++; + else if(nets[k] < 0.0) + g_ictMssStats.lossCount++; + } + + 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", + sym, magic, + g_ictMssStats.total, g_ictMssStats.tpCount, + g_ictMssStats.slCount, g_ictMssStats.otherCount, + g_ictMssStats.netProfit, g_ictMssStats.sumR); +} + +double IctMssStats_WinratePct() +{ + const int decided = g_ictMssStats.tpCount + g_ictMssStats.slCount; + if(decided == 0) + return 0.0; + return 100.0 * (double)g_ictMssStats.tpCount / (double)decided; +} + +double IctMssStats_AvgR() +{ + if(g_ictMssStats.total <= 0) + return 0.0; + return g_ictMssStats.sumR / (double)g_ictMssStats.total; +} + +string IctMssStats_LineCounts() +{ + return StringFormat("Stats: %d lệnh | TP %d | SL %d | khác %d", + g_ictMssStats.total, + g_ictMssStats.tpCount, + g_ictMssStats.slCount, + g_ictMssStats.otherCount); +} + +string IctMssStats_LinePerf() +{ + const double wr = IctMssStats_WinratePct(); + const double ar = IctMssStats_AvgR(); + return StringFormat("WR %.1f%% | Ravg %+.2fR | Net %+.2f", + wr, ar, g_ictMssStats.netProfit); +} + +#endif diff --git a/experts.dat b/experts.dat index e9abf203b4c93e467abc3a0ad11cc00ebf53028b..1a47876a27536f415d70c4c36113654fd54ed378 100644 GIT binary patch delta 46 zcmaFAly%N3)(vYI8Rtx1IQ8`A3MRQieoqExh7g8$1|tRoAU50VoiE3U*!*yBToU#m2