From 63c81f16bb557688f1b554b6c956e60892d9ffad Mon Sep 17 00:00:00 2001 From: Bell Date: Tue, 10 Mar 2026 20:39:33 +0700 Subject: [PATCH] Format code --- Experts/EA_ICT_CL/Config.mqh | 52 +++--- Experts/EA_ICT_CL/Contexts.mqh | 86 ++++----- Experts/EA_ICT_CL/Drawing.mqh | 143 ++++++++------- Experts/EA_ICT_CL/Filters.mqh | 20 +-- Experts/EA_ICT_CL/Guards.mqh | 31 ++-- Experts/EA_ICT_CL/Indicators.mqh | 27 ++- Experts/EA_ICT_CL/Logging.mqh | 17 +- Experts/EA_ICT_CL/Orders.mqh | 17 +- Experts/EA_ICT_CL/Risk.mqh | 44 +++-- Experts/EA_ICT_CL/Sessions.mqh | 22 ++- Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh | 218 +++++++++++------------ Experts/EA_ICT_CL/State.mqh | 96 +++++----- Experts/EA_ICT_CL/Trade.mqh | 46 +++-- Experts/EA_ICT_CL/Trailing.mqh | 7 +- Experts/EA_ICT_CL/Utils.mqh | 35 ++-- 15 files changed, 421 insertions(+), 440 deletions(-) diff --git a/Experts/EA_ICT_CL/Config.mqh b/Experts/EA_ICT_CL/Config.mqh index b314037..4b557a8 100644 --- a/Experts/EA_ICT_CL/Config.mqh +++ b/Experts/EA_ICT_CL/Config.mqh @@ -1,38 +1,32 @@ #ifndef EA_ICT_CL__CONFIG_MQH -#define EA_ICT_CL__CONFIG_MQH // Tránh include trùng +#define EA_ICT_CL__CONFIG_MQH -// Module: Config – toàn bộ input của EA +input ENUM_TIMEFRAMES InpBiasTF = PERIOD_D1; +input ENUM_TIMEFRAMES InpMiddleTF = PERIOD_H1; +input ENUM_TIMEFRAMES InpTriggerTF = PERIOD_M5; -//==================================================== -// INPUTS -//==================================================== -input ENUM_TIMEFRAMES InpBiasTF = PERIOD_D1; // Timeframe xác định bias (D1) -input ENUM_TIMEFRAMES InpMiddleTF = PERIOD_H1; // TF quét FVG + trend (H1) -input ENUM_TIMEFRAMES InpTriggerTF = PERIOD_M5; // TF xác nhận entry bằng MSS (M5) +input double InpRiskPercent = 1.0; +input double InpRiskReward = 2.0; +input double InpMaxDailyLossPct = 3.0; -input double InpRiskPercent = 1.0; // % balance risk mỗi lệnh -input double InpRiskReward = 2.0; // Tỷ lệ R:R (TP = entry ± R*riskDist) -input double InpMaxDailyLossPct = 3.0; // % lỗ tối đa trong ngày → dừng trade +input int InpLondonStartHour = 8; +input int InpLondonEndHour = 17; +input int InpNYStartHour = 13; +input int InpNYEndHour = 22; -input int InpLondonStartHour = 8; // Giờ mở London (UTC) -input int InpLondonEndHour = 17; // Giờ đóng London (UTC) -input int InpNYStartHour = 13; // Giờ mở NY (UTC) -input int InpNYEndHour = 22; // Giờ đóng NY (UTC) +input int InpSwingRange = 3; +input int InpSwingLookback = 50; +input int InpTriggerSwingLookback = 30; -input int InpSwingRange = 3; // Số nến mỗi bên để xác nhận swing high/low -input int InpSwingLookback = 50; // Số bar quét swing trên MiddleTF (H1) -input int InpTriggerSwingLookback = 30; // Số bar quét swing trên TriggerTF (M5) +input int InpFVGMaxAliveMin = 4320; +input int InpFVGScanBars = 50; +input double InpFVGMinBodyPct = 60.0; +input int InpMSSMinDepthPts = 30; -input int InpFVGMaxAliveMin = 4320; // Thời gian sống FVG tối đa (phút), 4320 = 72h -input int InpFVGScanBars = 50; // Số bar H1 quét tìm FVG -input double InpFVGMinBodyPct = 60.0; // Nến giữa FVG: body % tối thiểu (strong candle) -input int InpMSSMinDepthPts = 30; // Độ sâu swing M5 tối thiểu (point) mới chấp nhận MSS +input long InpMagicNumber = 20250308; +input int InpSlippage = 5; -input long InpMagicNumber = 20250308; // Magic number cho lệnh EA -input int InpSlippage = 5; // Slippage tối đa (point) khi gửi lệnh - -input bool InpDebugLog = true; // Bật log ra Journal -input bool InpDebugDraw = true; // Bật vẽ swing/FVG/order/debug panel - -#endif // EA_ICT_CL__CONFIG_MQH +input bool InpDebugLog = true; +input bool InpDebugDraw = true; +#endif diff --git a/Experts/EA_ICT_CL/Contexts.mqh b/Experts/EA_ICT_CL/Contexts.mqh index 1bc3c8a..b851a13 100644 --- a/Experts/EA_ICT_CL/Contexts.mqh +++ b/Experts/EA_ICT_CL/Contexts.mqh @@ -1,53 +1,53 @@ #ifndef EA_ICT_CL__CONTEXTS_MQH -#define EA_ICT_CL__CONTEXTS_MQH // Tránh include trùng - -// Module: Contexts – cập nhật D1 bias, H1/M5 trend (swing + MSS), daily risk +#define EA_ICT_CL__CONTEXTS_MQH +/** Resolves D1 bias from two consecutive daily bars (bar1 = last closed, bar2 = prior). */ inline HTFBias ResolveBias(double bar1High, double bar1Low, double bar1Close, double bar2High, double bar2Low) { - if (bar1Close > bar2High) return BIAS_UP; // Close bar 1 trên range bar 2 → tăng - if (bar1Close < bar2Low) return BIAS_DOWN; // Close bar 1 dưới range bar 2 → giảm - if (bar1High > bar2High && bar1Close < bar2High) return BIAS_DOWN; // Phá cao rồi đóng dưới → bearish - if (bar1Low < bar2Low && bar1Close > bar2Low) return BIAS_UP; // Phá thấp rồi đóng trên → bullish - return BIAS_SIDEWAY; // Còn lại = sideway + if (bar1Close > bar2High) return BIAS_UP; + if (bar1Close < bar2Low) return BIAS_DOWN; + if (bar1High > bar2High && bar1Close < bar2High) return BIAS_DOWN; + if (bar1Low < bar2Low && bar1Close > bar2Low) return BIAS_UP; + return BIAS_SIDEWAY; } +/** Updates global D1 bias context once per new bias-TF bar. */ inline void UpdateBiasContext() { - datetime currentBiasBarTime = iTime(_Symbol, InpBiasTF, 0); // Bar D1 hiện tại (đang hình thành) - if (currentBiasBarTime == g_Bias.lastBarTime) return; // Đã xử lý bar này rồi → bỏ qua + datetime currentBiasBarTime = iTime(_Symbol, InpBiasTF, 0); + if (currentBiasBarTime == g_Bias.lastBarTime) return; g_Bias.lastBarTime = currentBiasBarTime; - if (Bars(_Symbol, InpBiasTF) < 4) { g_Bias.bias = BIAS_NONE; return; } // Không đủ dữ liệu + if (Bars(_Symbol, InpBiasTF) < 4) { g_Bias.bias = BIAS_NONE; return; } - double bar1High = iHigh (_Symbol, InpBiasTF, 1); // Bar D1 vừa đóng + double bar1High = iHigh (_Symbol, InpBiasTF, 1); double bar1Low = iLow (_Symbol, InpBiasTF, 1); double bar1Close = iClose(_Symbol, InpBiasTF, 1); - double bar2High = iHigh (_Symbol, InpBiasTF, 2); // Bar D1 trước đó + double bar2High = iHigh (_Symbol, InpBiasTF, 2); double bar2Low = iLow (_Symbol, InpBiasTF, 2); HTFBias prev = g_Bias.bias; - g_Bias.bias = ResolveBias(bar1High, bar1Low, bar1Close, bar2High, bar2Low); // Tính bias - g_Bias.rangeHigh = (g_Bias.bias == BIAS_SIDEWAY) ? bar2High : 0; // Vùng sideway + g_Bias.bias = ResolveBias(bar1High, bar1Low, bar1Close, bar2High, bar2Low); + g_Bias.rangeHigh = (g_Bias.bias == BIAS_SIDEWAY) ? bar2High : 0; g_Bias.rangeLow = (g_Bias.bias == BIAS_SIDEWAY) ? bar2Low : 0; - if (InpDebugLog && g_Bias.bias != prev) // Chỉ log khi bias đổi + if (InpDebugLog && g_Bias.bias != prev) PrintFormat("[BIAS] %s → %s | b1[H=%.5f L=%.5f C=%.5f] b2[H=%.5f L=%.5f]", EnumToString(prev), EnumToString(g_Bias.bias), bar1High, bar1Low, bar1Close, bar2High, bar2Low); } +/** Updates TF trend context (swing + trend + MSS detection when on trigger TF). */ inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContext &ctx) { datetime currentTfBarTime = iTime(_Symbol, tf, 0); - if (currentTfBarTime == ctx.lastBarTime) return; // Bar chưa đổi → không cập nhật + if (currentTfBarTime == ctx.lastBarTime) return; ctx.lastBarTime = currentTfBarTime; - double lastBarClose = iClose(_Symbol, tf, 1); // Close bar vừa đóng + double lastBarClose = iClose(_Symbol, tf, 1); datetime lastBarTime = iTime (_Symbol, tf, 1); - // Chỉ detect MSS khi đang WAIT_TRIGGER, trên M5, đã có h0/l0, H1 trend rõ if (tf == InpTriggerTF && g_State == EA_WAIT_TRIGGER && ctx.h0 > 0 && ctx.l0 > 0 @@ -58,14 +58,14 @@ inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContex double entryLevel = 0; double slLevel = 0; - if (g_MiddleTrend.trend == DIR_UP && lastBarClose > ctx.h0) // H1 uptrend + M5 close phá tH0 = bull MSS + if (g_MiddleTrend.trend == DIR_UP && lastBarClose > ctx.h0) { isMssTriggered = true; mssBreakDirection = DIR_UP; entryLevel = ctx.h0; // Entry = tH0 slLevel = ctx.l0; // SL = tL0 } - else if (g_MiddleTrend.trend == DIR_DOWN && lastBarClose < ctx.l0) // Bear MSS + else if (g_MiddleTrend.trend == DIR_DOWN && lastBarClose < ctx.l0) { isMssTriggered = true; mssBreakDirection = DIR_DOWN; @@ -73,10 +73,10 @@ inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContex slLevel = ctx.h0; } - if (isMssTriggered && lastBarTime != ctx.lastMssTime) // MSS mới (chưa ghi nhận bar này) + if (isMssTriggered && lastBarTime != ctx.lastMssTime) { - double mssSwingDepthPoints = MathAbs(ctx.h0 - ctx.l0) / _Point; // Độ sâu swing (point) - if (mssSwingDepthPoints < InpMSSMinDepthPts) // Quá nông → bỏ qua + double mssSwingDepthPoints = MathAbs(ctx.h0 - ctx.l0) / _Point; + if (mssSwingDepthPoints < InpMSSMinDepthPts) { if (InpDebugLog) PrintFormat("[M5 MSS SKIP] depth=%.0f pts < %d | H0=%.5f L0=%.5f | %s", @@ -84,10 +84,10 @@ inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContex } else { - ctx.lastMssTime = lastBarTime; // Ghi nhận thời điểm MSS - ctx.lastMssLevel = entryLevel; // Giá entry - ctx.lastMssBreak = mssBreakDirection; // UP/DOWN - ctx.mssSLSwing = slLevel; // Giá SL (tL0 hoặc tH0) + ctx.lastMssTime = lastBarTime; + ctx.lastMssLevel = entryLevel; + ctx.lastMssBreak = mssBreakDirection; + ctx.mssSLSwing = slLevel; if (InpDebugLog) PrintFormat("[M5 MSS] %s | entry=%.5f SL=%.5f depth=%.0fpts | close=%.5f | H0=%.5f L0=%.5f | %s", @@ -101,15 +101,15 @@ inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContex double h0, h1, l0, l1; int idxH0, idxH1, idxL0, idxL1; if (!ScanSwingStructure(tf, lookback, h0, h1, idxH0, idxH1, l0, l1, idxL0, idxL1)) - { ctx.trend = DIR_NONE; return; } // Không đủ swing + { ctx.trend = DIR_NONE; return; } - ctx.h0 = h0; ctx.idxH0 = idxH0; // Gán swing vào context + ctx.h0 = h0; ctx.idxH0 = idxH0; ctx.h1 = h1; ctx.idxH1 = idxH1; ctx.l0 = l0; ctx.idxL0 = idxL0; ctx.l1 = l1; ctx.idxL1 = idxL1; MarketDir prev = ctx.trend; - ResolveTrendFromSwings(tf, h0, h1, l0, l1, ctx.trend, ctx.keyLevel); // Suy trend + key level + ResolveTrendFromSwings(tf, h0, h1, l0, l1, ctx.trend, ctx.keyLevel); if (InpDebugLog && ctx.trend != prev) PrintFormat("[%s TREND] %s → %s | H0=%.5f H1=%.5f L0=%.5f L1=%.5f | KL=%.5f", @@ -117,22 +117,23 @@ inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContex h0, h1, l0, l1, ctx.keyLevel); } +/** Updates daily risk context (day start balance, current balance, limit-hit flag). */ inline void UpdateDailyRiskContext() { - datetime currentDayTime = iTime(_Symbol, PERIOD_D1, 0); // Mốc D1 hiện tại - if (currentDayTime != g_DailyRisk.dayStartTime) // Sang ngày mới + datetime currentDayTime = iTime(_Symbol, PERIOD_D1, 0); + if (currentDayTime != g_DailyRisk.dayStartTime) { g_DailyRisk.dayStartTime = currentDayTime; - g_DailyRisk.startBalance = AccountInfoDouble(ACCOUNT_BALANCE); // Balance đầu ngày - g_DailyRisk.limitHit = false; // Reset cờ chạm limit + g_DailyRisk.startBalance = AccountInfoDouble(ACCOUNT_BALANCE); + g_DailyRisk.limitHit = false; if (InpDebugLog) PrintFormat("[DAILY RISK] New day | start=%.2f", g_DailyRisk.startBalance); } - if (g_DailyRisk.limitHit) return; // Đã chạm limit → không cập nhật gì thêm + if (g_DailyRisk.limitHit) return; g_DailyRisk.currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); double lossPercentToday = (g_DailyRisk.startBalance - g_DailyRisk.currentBalance) - / g_DailyRisk.startBalance * 100.0; // % lỗ so với đầu ngày - if (lossPercentToday >= InpMaxDailyLossPct) // Vượt ngưỡng max daily loss + / g_DailyRisk.startBalance * 100.0; + if (lossPercentToday >= InpMaxDailyLossPct) { g_DailyRisk.limitHit = true; PrintFormat("[DAILY RISK] ⛔ Limit hit | lost=%.2f%% | bal=%.2f", @@ -140,12 +141,13 @@ inline void UpdateDailyRiskContext() } } +/** Updates all contexts: daily risk, bias, middle TF trend, trigger TF trend. */ inline void UpdateAllContexts() { - UpdateDailyRiskContext(); // Cập nhật balance, limit hit - UpdateBiasContext(); // D1 bias - UpdateTFTrendContext(InpMiddleTF, InpSwingLookback, g_MiddleTrend); // H1 swing + trend + (MSS không ở đây) - UpdateTFTrendContext(InpTriggerTF, InpTriggerSwingLookback, g_TriggerTrend); // M5 swing + trend + MSS + UpdateDailyRiskContext(); + UpdateBiasContext(); + UpdateTFTrendContext(InpMiddleTF, InpSwingLookback, g_MiddleTrend); + UpdateTFTrendContext(InpTriggerTF, InpTriggerSwingLookback, g_TriggerTrend); } #endif // EA_ICT_CL__CONTEXTS_MQH diff --git a/Experts/EA_ICT_CL/Drawing.mqh b/Experts/EA_ICT_CL/Drawing.mqh index 82f05d6..d1a123a 100644 --- a/Experts/EA_ICT_CL/Drawing.mqh +++ b/Experts/EA_ICT_CL/Drawing.mqh @@ -1,51 +1,48 @@ #ifndef EA_ICT_CL__DRAWING_MQH #define EA_ICT_CL__DRAWING_MQH -// Module: Drawing -// Extracted from EA_ICT_CL.mq5 (Section 11 – Drawing). -// NOTE: Uses EA globals (g_*), prefix constants (PREFIX_SWING_MIDDLE, PREFIX_ORDER_VISUAL, etc.), and inputs. -// Include AFTER structs + globals + prefixes are declared in EA_ICT_CL.mq5. - +/** Draws one swing point (arrow + label + optional key-level line) on chart. */ inline void DrawOneSwingPoint( string prefix, ENUM_TIMEFRAMES tf, string tag, bool isHigh, int barIdx, double price, color clr, bool isKL, int arrowSz = 2, int fontSize = 8) { - string arrN = prefix + "ARR_" + tag; - string txtN = prefix + "TXT_" + tag; - string klN = prefix + "KL_" + tag; - datetime t = iTime(_Symbol, tf, barIdx); + string arrowObjName = prefix + "ARR_" + tag; + string textObjName = prefix + "TXT_" + tag; + string keyLevelObjName = prefix + "KL_" + tag; + datetime barTime = iTime(_Symbol, tf, barIdx); - if (ObjectFind(0, arrN) < 0) ObjectCreate(0, arrN, OBJ_ARROW, 0, t, price); - ObjectSetInteger(0, arrN, OBJPROP_ARROWCODE, isHigh ? 234 : 233); - ObjectSetInteger(0, arrN, OBJPROP_COLOR, clr); - ObjectSetInteger(0, arrN, OBJPROP_WIDTH, arrowSz); - ObjectSetInteger(0, arrN, OBJPROP_ANCHOR, isHigh ? ANCHOR_BOTTOM : ANCHOR_TOP); - ObjectMove(0, arrN, 0, t, price); + if (ObjectFind(0, arrowObjName) < 0) ObjectCreate(0, arrowObjName, OBJ_ARROW, 0, barTime, price); + ObjectSetInteger(0, arrowObjName, OBJPROP_ARROWCODE, isHigh ? 234 : 233); + ObjectSetInteger(0, arrowObjName, OBJPROP_COLOR, clr); + ObjectSetInteger(0, arrowObjName, OBJPROP_WIDTH, arrowSz); + ObjectSetInteger(0, arrowObjName, OBJPROP_ANCHOR, isHigh ? ANCHOR_BOTTOM : ANCHOR_TOP); + ObjectMove(0, arrowObjName, 0, barTime, price); - double rng = iHigh(_Symbol, tf, barIdx) - iLow(_Symbol, tf, barIdx); - double txtY = isHigh ? price + rng * 0.3 : price - rng * 0.3; - if (ObjectFind(0, txtN) < 0) ObjectCreate(0, txtN, OBJ_TEXT, 0, t, txtY); - ObjectMove(0, txtN, 0, t, txtY); - ObjectSetString (0, txtN, OBJPROP_TEXT, tag); - ObjectSetInteger(0, txtN, OBJPROP_COLOR, clr); - ObjectSetInteger(0, txtN, OBJPROP_FONTSIZE, fontSize); - ObjectSetInteger(0, txtN, OBJPROP_ANCHOR, isHigh ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); + double barRange = iHigh(_Symbol, tf, barIdx) - iLow(_Symbol, tf, barIdx); + double textY = isHigh ? price + barRange * 0.3 : price - barRange * 0.3; + if (ObjectFind(0, textObjName) < 0) ObjectCreate(0, textObjName, OBJ_TEXT, 0, barTime, textY); + ObjectMove(0, textObjName, 0, barTime, textY); + ObjectSetString (0, textObjName, OBJPROP_TEXT, tag); + ObjectSetInteger(0, textObjName, OBJPROP_COLOR, clr); + ObjectSetInteger(0, textObjName, OBJPROP_FONTSIZE, fontSize); + ObjectSetInteger(0, textObjName, OBJPROP_ANCHOR, isHigh ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); if (isKL) { - datetime tEnd = iTime(_Symbol, tf, 0); - if (ObjectFind(0, klN) < 0) ObjectCreate(0, klN, OBJ_TREND, 0, t, price, tEnd, price); - ObjectSetInteger(0, klN, OBJPROP_COLOR, clr); - ObjectSetInteger(0, klN, OBJPROP_STYLE, STYLE_DASH); - ObjectSetInteger(0, klN, OBJPROP_WIDTH, 1); - ObjectSetInteger(0, klN, OBJPROP_RAY_RIGHT, false); - ObjectMove(0, klN, 0, t, price); - ObjectMove(0, klN, 1, tEnd, price); + datetime endTime = iTime(_Symbol, tf, 0); + if (ObjectFind(0, keyLevelObjName) < 0) ObjectCreate(0, keyLevelObjName, OBJ_TREND, 0, barTime, price, endTime, price); + ObjectSetInteger(0, keyLevelObjName, OBJPROP_COLOR, clr); + ObjectSetInteger(0, keyLevelObjName, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, keyLevelObjName, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, keyLevelObjName, OBJPROP_RAY_RIGHT, false); + ObjectMove(0, keyLevelObjName, 0, barTime, price); + ObjectMove(0, keyLevelObjName, 1, endTime, price); } - else ObjectDelete(0, klN); + else ObjectDelete(0, keyLevelObjName); } +/** Draws H1 swing points (H0/H1/L0/L1) when debug draw enabled. */ inline void DrawMiddleSwingPoints() { if (!InpDebugDraw) return; @@ -61,6 +58,7 @@ inline void DrawMiddleSwingPoints() DrawOneSwingPoint(PREFIX_SWING_MIDDLE, InpMiddleTF, "MiddleL1", false, g_MiddleTrend.idxL1, g_MiddleTrend.l1, C'160,140,0', false, 2, 8); } +/** Draws M5 swing points when debug draw enabled. */ inline void DrawTriggerSwingPoints() { if (!InpDebugDraw) return; @@ -76,52 +74,54 @@ inline void DrawTriggerSwingPoints() DrawOneSwingPoint(PREFIX_SWING_TRIGGER, InpTriggerTF, "TriggerL1", false, g_TriggerTrend.idxL1, g_TriggerTrend.l1, C'160,100,20', false, 1, 7); } +/** Draws one MSS marker (arrow + label + horizontal level) at given time/level. */ inline void DrawMSSMarker( string mssId, ENUM_TIMEFRAMES tf, datetime mssTime, double mssLevel, MarketDir mssBreak) { if (!InpDebugDraw || mssTime == 0) return; - string arrN = PREFIX_MSS_MARKER + mssId + "_ARR"; - string lblN = PREFIX_MSS_MARKER + mssId + "_LBL"; - string klN = PREFIX_MSS_MARKER + mssId + "_KL"; + string arrowObjName = PREFIX_MSS_MARKER + mssId + "_ARR"; + string labelObjName = PREFIX_MSS_MARKER + mssId + "_LBL"; + string levelObjName = PREFIX_MSS_MARKER + mssId + "_KL"; - bool isBull = (mssBreak == DIR_UP); - color clr = isBull ? clrLime : clrTomato; + bool isBull = (mssBreak == DIR_UP); + color clr = isBull ? clrLime : clrTomato; - int shift = MyBarShift(_Symbol, tf, mssTime); - if (shift < 0) return; - double closeAtMss = iClose(_Symbol, tf, shift); + int barShift = MyBarShift(_Symbol, tf, mssTime); + if (barShift < 0) return; + double closeAtMss = iClose(_Symbol, tf, barShift); - if (ObjectFind(0, arrN) < 0) - ObjectCreate(0, arrN, OBJ_ARROW, 0, mssTime, closeAtMss); - ObjectSetInteger(0, arrN, OBJPROP_ARROWCODE, isBull ? 233 : 234); - ObjectSetInteger(0, arrN, OBJPROP_COLOR, clr); - ObjectSetInteger(0, arrN, OBJPROP_WIDTH, 2); - ObjectSetInteger(0, arrN, OBJPROP_ANCHOR, isBull ? ANCHOR_TOP : ANCHOR_BOTTOM); - ObjectMove(0, arrN, 0, mssTime, closeAtMss); + if (ObjectFind(0, arrowObjName) < 0) + ObjectCreate(0, arrowObjName, OBJ_ARROW, 0, mssTime, closeAtMss); + ObjectSetInteger(0, arrowObjName, OBJPROP_ARROWCODE, isBull ? 233 : 234); + ObjectSetInteger(0, arrowObjName, OBJPROP_COLOR, clr); + ObjectSetInteger(0, arrowObjName, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, arrowObjName, OBJPROP_ANCHOR, isBull ? ANCHOR_TOP : ANCHOR_BOTTOM); + ObjectMove(0, arrowObjName, 0, mssTime, closeAtMss); - double rng = iHigh(_Symbol, tf, shift) - iLow(_Symbol, tf, shift); - double lblY = isBull ? closeAtMss - rng * 0.4 : closeAtMss + rng * 0.4; - if (ObjectFind(0, lblN) < 0) - ObjectCreate(0, lblN, OBJ_TEXT, 0, mssTime, lblY); - ObjectMove(0, lblN, 0, mssTime, lblY); - ObjectSetString (0, lblN, OBJPROP_TEXT, isBull ? "▲MSS" : "▼MSS"); - ObjectSetInteger(0, lblN, OBJPROP_COLOR, clr); - ObjectSetInteger(0, lblN, OBJPROP_FONTSIZE, 8); - ObjectSetInteger(0, lblN, OBJPROP_ANCHOR, isBull ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); + double barRange = iHigh(_Symbol, tf, barShift) - iLow(_Symbol, tf, barShift); + double labelY = isBull ? closeAtMss - barRange * 0.4 : closeAtMss + barRange * 0.4; + if (ObjectFind(0, labelObjName) < 0) + ObjectCreate(0, labelObjName, OBJ_TEXT, 0, mssTime, labelY); + ObjectMove(0, labelObjName, 0, mssTime, labelY); + ObjectSetString (0, labelObjName, OBJPROP_TEXT, isBull ? "▲MSS" : "▼MSS"); + ObjectSetInteger(0, labelObjName, OBJPROP_COLOR, clr); + ObjectSetInteger(0, labelObjName, OBJPROP_FONTSIZE, 8); + ObjectSetInteger(0, labelObjName, OBJPROP_ANCHOR, isBull ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); - datetime tEnd = iTime(_Symbol, tf, 0); - if (ObjectFind(0, klN) < 0) - ObjectCreate(0, klN, OBJ_TREND, 0, mssTime, mssLevel, tEnd, mssLevel); - ObjectSetInteger(0, klN, OBJPROP_COLOR, clr); - ObjectSetInteger(0, klN, OBJPROP_STYLE, STYLE_DOT); - ObjectSetInteger(0, klN, OBJPROP_WIDTH, 1); - ObjectSetInteger(0, klN, OBJPROP_RAY_RIGHT, false); - ObjectMove(0, klN, 0, mssTime, mssLevel); - ObjectMove(0, klN, 1, tEnd, mssLevel); + datetime endTime = iTime(_Symbol, tf, 0); + if (ObjectFind(0, levelObjName) < 0) + ObjectCreate(0, levelObjName, OBJ_TREND, 0, mssTime, mssLevel, endTime, mssLevel); + ObjectSetInteger(0, levelObjName, OBJPROP_COLOR, clr); + ObjectSetInteger(0, levelObjName, OBJPROP_STYLE, STYLE_DOT); + ObjectSetInteger(0, levelObjName, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, levelObjName, OBJPROP_RAY_RIGHT, false); + ObjectMove(0, levelObjName, 0, mssTime, mssLevel); + ObjectMove(0, levelObjName, 1, endTime, mssLevel); } +/** Draws MSS markers for all FVGs that were triggered by MSS (usedCase == 2). */ inline void DrawMSSMarkers() { if (!InpDebugDraw) return; @@ -138,6 +138,7 @@ inline void DrawMSSMarkers() } } +/** Draws order plan zones/lines/labels (entry, SL, TP) when valid and debug draw on. */ inline void DrawOrderVisualization() { if (!InpDebugDraw) return; @@ -233,6 +234,7 @@ inline void DrawOrderVisualization() ObjectSetInteger(0, infoLblN, OBJPROP_ANCHOR, ANCHOR_LEFT); } +/** Draws one FVG record (rectangle + mid line + label) by pool index. */ inline void DrawOneFVGRecord(int idx) { if (!InpDebugDraw || idx < 0 || idx >= g_FVGCount) return; @@ -294,12 +296,14 @@ inline void DrawOneFVGRecord(int idx) ObjectSetInteger(0, lblN, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); } +/** Draws all FVGs in pool (calls DrawOneFVGRecord for each). */ inline void DrawFVGPool() { if (!InpDebugDraw) return; for (int i = 0; i < g_FVGCount; i++) DrawOneFVGRecord(i); } +/** Draws debug panel (bias, H1/M5 trend, risk, state, pool, active FVG, order). */ inline void DrawContextDebug() { if (!InpDebugDraw) return; @@ -350,10 +354,10 @@ inline void DrawContextDebug() { LBL(PREFIX_DEBUG_PANEL + "BLK", StringFormat("Block: %s", EnumToString(g_BlockReason)), 178, clrTomato) } else ObjectDelete(0, PREFIX_DEBUG_PANEL + "BLK"); - int nP=0,nT=0,nU=0; - for(int i=0;i= 0 && g_ActiveFVGIdx < g_FVGCount) { @@ -376,6 +380,7 @@ inline void DrawContextDebug() ChartRedraw(0); } +/** Draws all debug visuals: swings, MSS markers, FVG pool, order, debug panel. */ inline void DrawVisuals() { DrawMiddleSwingPoints(); diff --git a/Experts/EA_ICT_CL/Filters.mqh b/Experts/EA_ICT_CL/Filters.mqh index bc0a435..68d393a 100644 --- a/Experts/EA_ICT_CL/Filters.mqh +++ b/Experts/EA_ICT_CL/Filters.mqh @@ -1,16 +1,14 @@ #ifndef EA_ICT_CL__FILTERS_MQH -#define EA_ICT_CL__FILTERS_MQH // Tránh include trùng +#define EA_ICT_CL__FILTERS_MQH -// Module: Filters – lọc spread (có thể mở rộng news, max trades...) - -inline bool Filter_MaxSpreadPoints(const string symbol, const double max_spread_points) +/** Returns true if symbol spread in points is within max allowed (or no limit). */ +inline bool Filter_MaxSpreadPoints(const string symbol, const double maxSpreadPoints) { - if (max_spread_points <= 0) return true; // Không giới hạn → luôn pass - const double pt = SymbolInfoDouble(symbol, SYMBOL_POINT); - if (pt <= 0.0) return true; // Point lỗi → bỏ qua check - const double spread_pts = (SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)) / pt; // Spread theo point - return (spread_pts <= max_spread_points); // Pass khi spread <= ngưỡng + if (maxSpreadPoints <= 0) return true; + const double pointSize = SymbolInfoDouble(symbol, SYMBOL_POINT); + if (pointSize <= 0.0) return true; + const double spreadInPoints = (SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)) / pointSize; + return (spreadInPoints <= maxSpreadPoints); } -#endif // EA_ICT_CL__FILTERS_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Guards.mqh b/Experts/EA_ICT_CL/Guards.mqh index 6bc572d..636c470 100644 --- a/Experts/EA_ICT_CL/Guards.mqh +++ b/Experts/EA_ICT_CL/Guards.mqh @@ -1,28 +1,29 @@ #ifndef EA_ICT_CL__GUARDS_MQH -#define EA_ICT_CL__GUARDS_MQH // Tránh include trùng +#define EA_ICT_CL__GUARDS_MQH -// Module: Guards – điều kiện trước khi cho chạy state machine (session, daily loss, bias, alignment) - -inline bool IsSessionAllowed() { return true; /* TODO: lọc giờ London/NY theo InpLondonStartHour/End, InpNY... */ } -inline bool IsDailyLossOK() { return !g_DailyRisk.limitHit; } // Chưa chạm max daily loss -inline bool IsBiasValid() { return g_Bias.bias == BIAS_UP || g_Bias.bias == BIAS_DOWN; } // Bias rõ (không NONE/SIDEWAY) +inline bool IsSessionAllowed() { return true; } +/** True if daily loss limit has not been hit. */ +inline bool IsDailyLossOK() { return !g_DailyRisk.limitHit; } +/** True if bias is UP or DOWN (not NONE or SIDEWAY). */ +inline bool IsBiasValid() { return g_Bias.bias == BIAS_UP || g_Bias.bias == BIAS_DOWN; } +/** True if H1 trend aligns with D1 bias (same direction). */ inline bool IsMiddleTrendAligned() { - if (g_MiddleTrend.trend == DIR_NONE) return false; // H1 không có trend → không align + if (g_MiddleTrend.trend == DIR_NONE) return false; return (g_Bias.bias == BIAS_UP && g_MiddleTrend.trend == DIR_UP) || - (g_Bias.bias == BIAS_DOWN && g_MiddleTrend.trend == DIR_DOWN); // D1 và H1 cùng chiều + (g_Bias.bias == BIAS_DOWN && g_MiddleTrend.trend == DIR_DOWN); } +/** Evaluates all guards; sets g_BlockReason and returns false if any guard fails. */ inline bool EvaluateGuards() { g_BlockReason = BLOCK_NONE; - if (!IsSessionAllowed()) { g_BlockReason = BLOCK_SESSION; return false; } // Ngoài giờ trade - if (!IsDailyLossOK()) { g_BlockReason = BLOCK_DAILY_LOSS; return false; } // Đã chạm limit lỗ - if (!IsBiasValid()) { g_BlockReason = BLOCK_NO_BIAS; return false; } // Không có bias - if (!IsMiddleTrendAligned()) { g_BlockReason = BLOCK_BIAS_MISMATCH; return false; } // D1 ≠ H1 - return true; // Tất cả pass + if (!IsSessionAllowed()) { g_BlockReason = BLOCK_SESSION; return false; } + if (!IsDailyLossOK()) { g_BlockReason = BLOCK_DAILY_LOSS; return false; } + if (!IsBiasValid()) { g_BlockReason = BLOCK_NO_BIAS; return false; } + if (!IsMiddleTrendAligned()) { g_BlockReason = BLOCK_BIAS_MISMATCH; return false; } + return true; } -#endif // EA_ICT_CL__GUARDS_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Indicators.mqh b/Experts/EA_ICT_CL/Indicators.mqh index f5ec0cd..ff1448f 100644 --- a/Experts/EA_ICT_CL/Indicators.mqh +++ b/Experts/EA_ICT_CL/Indicators.mqh @@ -1,22 +1,21 @@ #ifndef EA_ICT_CL__INDICATORS_MQH -#define EA_ICT_CL__INDICATORS_MQH // Tránh include trùng +#define EA_ICT_CL__INDICATORS_MQH -// Module: Indicators – copy dữ liệu buffer/time an toàn (series index 0 = mới nhất) - -inline bool CopyBufferSafe(const int handle, const int buffer, const int start_pos, const int count, double &out[]) +/** Copies indicator buffer into out[] (series); returns true only if copied count equals requested. */ +inline bool CopyBufferSafe(const int handle, const int buffer, const int startPos, const int count, double &out[]) { - if (handle == INVALID_HANDLE) return false; // Handle không hợp lệ - ArraySetAsSeries(out, true); // Index 0 = bar mới nhất - const int copied = CopyBuffer(handle, buffer, start_pos, count, out); // Copy dữ liệu indicator - return (copied == count); // Thành công khi copy đủ count phần tử + if (handle == INVALID_HANDLE) return false; + ArraySetAsSeries(out, true); + const int copiedCount = CopyBuffer(handle, buffer, startPos, count, out); + return (copiedCount == count); } -inline bool CopyTimeSafe(const string symbol, const ENUM_TIMEFRAMES tf, const int start_pos, const int count, datetime &out[]) +/** Copies bar times into out[] (series); returns true only if copied count equals requested. */ +inline bool CopyTimeSafe(const string symbol, const ENUM_TIMEFRAMES tf, const int startPos, const int count, datetime &out[]) { - ArraySetAsSeries(out, true); // Index 0 = bar mới nhất - const int copied = CopyTime(symbol, tf, start_pos, count, out); // Copy thời gian mở bar - return (copied == count); // Thành công khi copy đủ count + ArraySetAsSeries(out, true); + const int copiedCount = CopyTime(symbol, tf, startPos, count, out); + return (copiedCount == count); } -#endif // EA_ICT_CL__INDICATORS_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Logging.mqh b/Experts/EA_ICT_CL/Logging.mqh index a690206..5163e9b 100644 --- a/Experts/EA_ICT_CL/Logging.mqh +++ b/Experts/EA_ICT_CL/Logging.mqh @@ -1,19 +1,18 @@ #ifndef EA_ICT_CL__LOGGING_MQH -#define EA_ICT_CL__LOGGING_MQH // Tránh include trùng - -// Module: Logging – in log ra Journal khi bật InpDebugLog +#define EA_ICT_CL__LOGGING_MQH +/** Prints message to Journal when enabled. */ inline void LogPrint(const bool enabled, const string msg) { - if (!enabled) return; // Tắt log thì thoát - Print(msg); // In chuỗi ra Experts/Journal + if (!enabled) return; + Print(msg); } +/** Prints formatted string (up to 3 string args) to Journal when enabled. */ inline void LogPrintF(const bool enabled, const string fmt, const string a0 = "", const string a1 = "", const string a2 = "") { - if (!enabled) return; // Tắt log thì thoát - Print(StringFormat(fmt, a0, a1, a2)); // Format rồi in (tối đa 3 tham số string) + if (!enabled) return; + Print(StringFormat(fmt, a0, a1, a2)); } -#endif // EA_ICT_CL__LOGGING_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Orders.mqh b/Experts/EA_ICT_CL/Orders.mqh index bb15e07..5e04e31 100644 --- a/Experts/EA_ICT_CL/Orders.mqh +++ b/Experts/EA_ICT_CL/Orders.mqh @@ -1,18 +1,17 @@ #ifndef EA_ICT_CL__ORDERS_MQH -#define EA_ICT_CL__ORDERS_MQH // Tránh include trùng - -// Module: Orders – chuẩn hóa giá, tạo comment lệnh (magic, fvg_id) +#define EA_ICT_CL__ORDERS_MQH +/** Normalizes price to symbol digits. */ inline double NormalizePrice(const string symbol, const double price) { - const int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); // Số chữ số thập phân của symbol - return NormalizeDouble(price, digits); // Làm tròn giá theo digits + const int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + return NormalizeDouble(price, digits); } -inline string BuildOrderComment(const long magic, const int fvg_id, const string tag = "ICT") +/** Builds order comment string for tracing (magic, FVG id, optional tag). */ +inline string BuildOrderComment(const long magic, const int fvgId, const string tag = "ICT") { - return StringFormat("%s|mg=%I64d|fvg=%d", tag, magic, fvg_id); // Comment để trace EA + FVG + return StringFormat("%s|mg=%I64d|fvg=%d", tag, magic, fvgId); } -#endif // EA_ICT_CL__ORDERS_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Risk.mqh b/Experts/EA_ICT_CL/Risk.mqh index 8d72507..ddbcbd2 100644 --- a/Experts/EA_ICT_CL/Risk.mqh +++ b/Experts/EA_ICT_CL/Risk.mqh @@ -1,40 +1,38 @@ #ifndef EA_ICT_CL__RISK_MQH -#define EA_ICT_CL__RISK_MQH // Tránh include trùng - -// Module: Risk – chuẩn hóa volume, tính lot từ tiền risk và SL (point) +#define EA_ICT_CL__RISK_MQH +/** Normalizes volume to symbol min/max/step. */ inline double NormalizeVolume(const string symbol, const double vol) { - const double vmin = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); // Lot tối thiểu - const double vmax = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); // Lot tối đa - const double vstep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); // Bước lot + const double minVol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); + const double maxVol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); + const double stepVol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double v = vol; - if (vstep > 0.0) v = MathFloor(v / vstep) * vstep; // Làm tròn xuống theo step - if (v < vmin) v = vmin; // Không nhỏ hơn min - if (v > vmax) v = vmax; // Không lớn hơn max + if (stepVol > 0.0) v = MathFloor(v / stepVol) * stepVol; + if (v < minVol) v = minVol; + if (v > maxVol) v = maxVol; return v; } -inline double LotsFromRiskMoneyAndSLPoints(const string symbol, const double risk_money, const double sl_points) +/** Computes lot size from risk money and SL distance in points. */ +inline double LotsFromRiskMoneyAndSLPoints(const string symbol, const double riskMoney, const double slPoints) { - if (risk_money <= 0.0) return 0.0; // Không risk → 0 lot - if (sl_points <= 0.0) return 0.0; // SL = 0 → không tính được + if (riskMoney <= 0.0) return 0.0; + if (slPoints <= 0.0) return 0.0; - double tick_value = 0.0, tick_size = 0.0; - if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE, tick_value)) return 0.0; // Giá trị 1 tick/lot - if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tick_size)) return 0.0; // Kích thước tick - if (tick_value <= 0.0 || tick_size <= 0.0) return 0.0; + double tickValue = 0.0, tickSize = 0.0; + if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE, tickValue)) return 0.0; + if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tickSize)) return 0.0; + if (tickValue <= 0.0 || tickSize <= 0.0) return 0.0; const double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if (point <= 0.0) return 0.0; - // Tiền mất trên 1 point khi di chuyển 1 lot: - const double money_per_point_1lot = tick_value * (point / tick_size); - if (money_per_point_1lot <= 0.0) return 0.0; + const double moneyPerPointPerLot = tickValue * (point / tickSize); + if (moneyPerPointPerLot <= 0.0) return 0.0; - const double lots = risk_money / (sl_points * money_per_point_1lot); // Lot = risk_money / (sl_points * $/point) - return NormalizeVolume(symbol, lots); // Chuẩn hóa theo min/max/step + const double lots = riskMoney / (slPoints * moneyPerPointPerLot); + return NormalizeVolume(symbol, lots); } -#endif // EA_ICT_CL__RISK_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Sessions.mqh b/Experts/EA_ICT_CL/Sessions.mqh index 97ab5b9..7e12596 100644 --- a/Experts/EA_ICT_CL/Sessions.mqh +++ b/Experts/EA_ICT_CL/Sessions.mqh @@ -1,22 +1,20 @@ #ifndef EA_ICT_CL__SESSIONS_MQH -#define EA_ICT_CL__SESSIONS_MQH // Tránh include trùng +#define EA_ICT_CL__SESSIONS_MQH -// Module: Sessions – kiểm tra giờ trong khoảng (UTC), dùng cho London/NY - -inline bool IsHourInRange(const int hour, const int start_hour, const int end_hour) +/** True if hour (0–23) falls in [startHour, endHour); supports overnight range. */ +inline bool IsHourInRange(const int hour, const int startHour, const int endHour) { - // start < end: [start, end); start > end: qua đêm [start,24) U [0,end) - if (start_hour == end_hour) return true; // Cả ngày - if (start_hour < end_hour) return (hour >= start_hour && hour < end_hour); // Cùng ngày - return (hour >= start_hour || hour < end_hour); // Qua nửa đêm + if (startHour == endHour) return true; + if (startHour < endHour) return (hour >= startHour && hour < endHour); + return (hour >= startHour || hour < endHour); } +/** Returns hour (0–23) in UTC for given datetime. */ inline int GetUTCHour(const datetime t) { MqlDateTime dt; - TimeToStruct(t, dt); // Chuyển datetime sang struct (năm, tháng, giờ...) - return dt.hour; // Trả về giờ (0–23) + TimeToStruct(t, dt); + return dt.hour; } -#endif // EA_ICT_CL__SESSIONS_MQH - +#endif diff --git a/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh b/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh index 6f49f38..5b4f36e 100644 --- a/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh +++ b/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh @@ -1,168 +1,169 @@ #ifndef EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH -#define EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH // Tránh include trùng +#define EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH -// Module: Signals (FVG) – helper FVG, quét/đăng ký pool, cập nhật trạng thái, chọn FVG tốt nhất - -//+------------------------------------------------------------------+ -//| SECTION 5 – FVG HELPERS | -//+------------------------------------------------------------------+ - -inline bool IsCandleStrong(ENUM_TIMEFRAMES tf, int i) +/** True if candle at bar index has body % of range >= InpFVGMinBodyPct. */ +inline bool IsCandleStrong(ENUM_TIMEFRAMES tf, int barIndex) { - double h = iHigh(_Symbol, tf, i), l = iLow(_Symbol, tf, i); - double o = iOpen(_Symbol, tf, i), c = iClose(_Symbol, tf, i); - double range = h - l; - if (range < _Point) return false; // Tránh chia 0 - return (MathAbs(c - o) / range * 100.0) >= InpFVGMinBodyPct; // Body % >= ngưỡng = nến mạnh + double high = iHigh(_Symbol, tf, barIndex); + double low = iLow(_Symbol, tf, barIndex); + double open = iOpen(_Symbol, tf, barIndex); + double close = iClose(_Symbol, tf, barIndex); + double range = high - low; + if (range < _Point) return false; + return (MathAbs(close - open) / range * 100.0) >= InpFVGMinBodyPct; } -inline bool IsFVGInPool(datetime created) +/** True if an FVG with given created time already exists in pool. */ +inline bool IsFVGInPool(datetime createdTime) { for (int j = 0; j < g_FVGCount; j++) - if (g_FVGPool[j].createdTime == created) return true; // Đã có FVG cùng thời điểm tạo + if (g_FVGPool[j].createdTime == createdTime) return true; return false; } -//+------------------------------------------------------------------+ -//| SECTION 6 – FVG POOL: SCAN & REGISTER | -//+------------------------------------------------------------------+ - +/** Scans middle TF for FVGs and registers them into g_FVGPool (once per new bar). */ inline void ScanAndRegisterFVGs() { - static datetime s_lastScan = 0; - datetime t0 = iTime(_Symbol, InpMiddleTF, 0); - if (t0 == s_lastScan) return; - s_lastScan = t0; + static datetime lastScanBarTime = 0; + datetime currentBarTime = iTime(_Symbol, InpMiddleTF, 0); + if (currentBarTime == lastScanBarTime) return; + lastScanBarTime = currentBarTime; - MarketDir dir = g_MiddleTrend.trend; - if (dir == DIR_NONE) return; + MarketDir trendDir = g_MiddleTrend.trend; + if (trendDir == DIR_NONE) return; - int maxBar = MathMin(InpFVGScanBars, Bars(_Symbol, InpMiddleTF) - 2); + int maxBarIndex = MathMin(InpFVGScanBars, Bars(_Symbol, InpMiddleTF) - 2); - for (int i = 2; i <= maxBar; i++) + for (int i = 2; i <= maxBarIndex; i++) { - double leftH = iHigh (_Symbol, InpMiddleTF, i + 1); - double leftL = iLow (_Symbol, InpMiddleTF, i + 1); - double rightH = iHigh (_Symbol, InpMiddleTF, i - 1); - double rightL = iLow (_Symbol, InpMiddleTF, i - 1); - double midO = iOpen (_Symbol, InpMiddleTF, i); - double midC = iClose(_Symbol, InpMiddleTF, i); - double gH = 0, gL = 0; + double leftBarHigh = iHigh (_Symbol, InpMiddleTF, i + 1); + double leftBarLow = iLow (_Symbol, InpMiddleTF, i + 1); + double rightBarHigh = iHigh (_Symbol, InpMiddleTF, i - 1); + double rightBarLow = iLow (_Symbol, InpMiddleTF, i - 1); + double midOpen = iOpen (_Symbol, InpMiddleTF, i); + double midClose = iClose(_Symbol, InpMiddleTF, i); + double gapHigh = 0, gapLow = 0; - if (dir == DIR_UP) + if (trendDir == DIR_UP) { - if (leftH >= rightL) continue; - if (midC <= midO) continue; + if (leftBarHigh >= rightBarLow) continue; + if (midClose <= midOpen) continue; if (!IsCandleStrong(InpMiddleTF, i)) continue; - gL = leftH; gH = rightL; + gapLow = leftBarHigh; + gapHigh = rightBarLow; } else { - if (leftL <= rightH) continue; - if (midC >= midO) continue; + if (leftBarLow <= rightBarHigh) continue; + if (midClose >= midOpen) continue; if (!IsCandleStrong(InpMiddleTF, i)) continue; - gH = leftL; gL = rightH; + gapHigh = leftBarLow; + gapLow = rightBarHigh; } - datetime created = iTime(_Symbol, InpMiddleTF, i - 1); - if (IsFVGInPool(created)) continue; + datetime createdTime = iTime(_Symbol, InpMiddleTF, i - 1); + if (IsFVGInPool(createdTime)) continue; if (g_FVGCount >= MAX_FVG_POOL) { - int evict = -1; datetime oldest = TimeCurrent(); + int evictIndex = -1; + datetime oldestTime = TimeCurrent(); for (int j = 0; j < g_FVGCount; j++) - if (g_FVGPool[j].status == FVG_USED && g_FVGPool[j].createdTime < oldest) - { oldest = g_FVGPool[j].createdTime; evict = j; } - if (evict < 0) { if (InpDebugLog) Print("[FVG POOL] Full"); break; } - for (int j = evict; j < g_FVGCount - 1; j++) g_FVGPool[j] = g_FVGPool[j + 1]; + if (g_FVGPool[j].status == FVG_USED && g_FVGPool[j].createdTime < oldestTime) + { oldestTime = g_FVGPool[j].createdTime; evictIndex = j; } + if (evictIndex < 0) { if (InpDebugLog) Print("[FVG POOL] Full"); break; } + for (int j = evictIndex; j < g_FVGCount - 1; j++) g_FVGPool[j] = g_FVGPool[j + 1]; g_FVGCount--; - if (g_ActiveFVGIdx > evict) g_ActiveFVGIdx--; - else if (g_ActiveFVGIdx == evict) g_ActiveFVGIdx = -1; + if (g_ActiveFVGIdx > evictIndex) g_ActiveFVGIdx--; + else if (g_ActiveFVGIdx == evictIndex) g_ActiveFVGIdx = -1; } - FVGRecord rec; - ZeroMemory(rec); - rec.id = g_NextFVGId++; rec.direction = dir; - rec.high = gH; rec.low = gL; rec.mid = (gH + gL) / 2.0; - rec.createdTime = created; - int rightBar = i - 1; + FVGRecord record; + ZeroMemory(record); + record.id = g_NextFVGId++; + record.direction = trendDir; + record.high = gapHigh; + record.low = gapLow; + record.mid = (gapHigh + gapLow) / 2.0; + record.createdTime = createdTime; + int rightBarIndex = i - 1; - bool c1Hit = false; datetime c1T = 0; - for (int j = rightBar - 1; j >= 1; j--) + bool isBrokenByClose = false; + datetime breakTime = 0; + for (int j = rightBarIndex - 1; j >= 1; j--) { - double cl = iClose(_Symbol, InpMiddleTF, j); - if ((rec.direction == DIR_UP && cl < rec.low) || - (rec.direction == DIR_DOWN && cl > rec.high)) - { c1Hit = true; c1T = iTime(_Symbol, InpMiddleTF, j); break; } + double closeAtJ = iClose(_Symbol, InpMiddleTF, j); + if ((record.direction == DIR_UP && closeAtJ < record.low) || + (record.direction == DIR_DOWN && closeAtJ > record.high)) + { isBrokenByClose = true; breakTime = iTime(_Symbol, InpMiddleTF, j); break; } } - if (c1Hit) { rec.status = FVG_USED; rec.usedCase = 1; rec.usedTime = c1T; } + if (isBrokenByClose) { record.status = FVG_USED; record.usedCase = 1; record.usedTime = breakTime; } else { - bool tdHit = false; datetime tdT = 0; - for (int j = rightBar - 1; j >= 1; j--) + bool isTouchedInGap = false; + datetime touchTimeInGap = 0; + for (int j = rightBarIndex - 1; j >= 1; j--) { - bool inGap = (rec.direction == DIR_UP && iLow (_Symbol, InpMiddleTF, j) <= rec.high) || - (rec.direction == DIR_DOWN && iHigh(_Symbol, InpMiddleTF, j) >= rec.low); - if (inGap) { tdHit = true; tdT = iTime(_Symbol, InpMiddleTF, j); break; } + bool inGap = (record.direction == DIR_UP && iLow (_Symbol, InpMiddleTF, j) <= record.high) || + (record.direction == DIR_DOWN && iHigh(_Symbol, InpMiddleTF, j) >= record.low); + if (inGap) { isTouchedInGap = true; touchTimeInGap = iTime(_Symbol, InpMiddleTF, j); break; } } - if (tdHit) { rec.status = FVG_TOUCHED; rec.touchTime = tdT; rec.triggerTrendAtTouch = g_TriggerTrend.trend; } + if (isTouchedInGap) { record.status = FVG_TOUCHED; record.touchTime = touchTimeInGap; record.triggerTrendAtTouch = g_TriggerTrend.trend; } else { - rec.status = FVG_PENDING; - if ((int)(TimeCurrent() - rec.createdTime) > InpFVGMaxAliveMin * 60) - { rec.status = FVG_USED; rec.usedCase = 0; rec.usedTime = TimeCurrent(); } + record.status = FVG_PENDING; + if ((int)(TimeCurrent() - record.createdTime) > InpFVGMaxAliveMin * 60) + { record.status = FVG_USED; record.usedCase = 0; record.usedTime = TimeCurrent(); } } } - g_FVGPool[g_FVGCount] = rec; + g_FVGPool[g_FVGCount] = record; g_FVGCount++; if (InpDebugLog) PrintFormat("[FVG +] #%d %s [%.5f–%.5f] %s | %s", - rec.id, EnumToString(rec.direction), rec.low, rec.high, - EnumToString(rec.status), TimeToString(rec.createdTime)); + record.id, EnumToString(record.direction), record.low, record.high, + EnumToString(record.status), TimeToString(record.createdTime)); } } -//+------------------------------------------------------------------+ -//| SECTION 7 – FVG POOL: UPDATE STATUSES (every tick) | -//+------------------------------------------------------------------+ - +/** Updates status of each FVG in pool (broken, touched, expired, MSS-triggered). */ inline void UpdateFVGStatuses() { - double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); - double midC1 = iClose(_Symbol, InpMiddleTF, 1); - datetime midT1 = iTime (_Symbol, InpMiddleTF, 1); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double lastBarClose = iClose(_Symbol, InpMiddleTF, 1); + datetime lastBarTime = iTime (_Symbol, InpMiddleTF, 1); for (int i = 0; i < g_FVGCount; i++) { if (g_FVGPool[i].status == FVG_USED) continue; - bool c1 = (g_FVGPool[i].direction == DIR_UP && midC1 < g_FVGPool[i].low) || - (g_FVGPool[i].direction == DIR_DOWN && midC1 > g_FVGPool[i].high); - if (c1) + bool isBroken = (g_FVGPool[i].direction == DIR_UP && lastBarClose < g_FVGPool[i].low) || + (g_FVGPool[i].direction == DIR_DOWN && lastBarClose > g_FVGPool[i].high); + if (isBroken) { g_FVGPool[i].status = FVG_USED; g_FVGPool[i].usedCase = 1; - g_FVGPool[i].usedTime = midT1; + g_FVGPool[i].usedTime = lastBarTime; if (InpDebugLog) - PrintFormat("[FVG #%d] BROKEN | close=%.5f", g_FVGPool[i].id, midC1); + PrintFormat("[FVG #%d] BROKEN | close=%.5f", g_FVGPool[i].id, lastBarClose); continue; } if (g_FVGPool[i].status == FVG_PENDING) { - int age = (int)(TimeCurrent() - g_FVGPool[i].createdTime); - if (age > InpFVGMaxAliveMin * 60) + int ageSeconds = (int)(TimeCurrent() - g_FVGPool[i].createdTime); + if (ageSeconds > InpFVGMaxAliveMin * 60) { - g_FVGPool[i].status = FVG_USED; g_FVGPool[i].usedCase = 0; + g_FVGPool[i].status = FVG_USED; + g_FVGPool[i].usedCase = 0; g_FVGPool[i].usedTime = TimeCurrent(); continue; } - bool touched = (g_FVGPool[i].direction == DIR_UP && bid <= g_FVGPool[i].high) || - (g_FVGPool[i].direction == DIR_DOWN && bid >= g_FVGPool[i].low); - if (touched) + bool isTouched = (g_FVGPool[i].direction == DIR_UP && bid <= g_FVGPool[i].high) || + (g_FVGPool[i].direction == DIR_DOWN && bid >= g_FVGPool[i].low); + if (isTouched) { g_FVGPool[i].status = FVG_TOUCHED; g_FVGPool[i].touchTime = TimeCurrent(); @@ -174,11 +175,11 @@ inline void UpdateFVGStatuses() } else if (g_FVGPool[i].status == FVG_TOUCHED) { - bool hasMSS = + bool hasMssAfterTouch = g_TriggerTrend.lastMssTime > g_FVGPool[i].touchTime && g_TriggerTrend.lastMssBreak == g_FVGPool[i].direction; - if (hasMSS) + if (hasMssAfterTouch) { g_FVGPool[i].status = FVG_USED; g_FVGPool[i].usedCase = 2; @@ -196,8 +197,8 @@ inline void UpdateFVGStatuses() continue; } - int ageMin = (int)((TimeCurrent() - g_FVGPool[i].createdTime) / 60); - if (ageMin > InpFVGMaxAliveMin) + int ageMinutes = (int)((TimeCurrent() - g_FVGPool[i].createdTime) / 60); + if (ageMinutes > InpFVGMaxAliveMin) { g_FVGPool[i].status = FVG_USED; g_FVGPool[i].usedCase = 0; @@ -209,31 +210,30 @@ inline void UpdateFVGStatuses() if (InpDebugLog) PrintFormat("[FVG #%d] TOUCHED EXPIRED (age=%dmin > %d) → USED", - g_FVGPool[i].id, ageMin, InpFVGMaxAliveMin); + g_FVGPool[i].id, ageMinutes, InpFVGMaxAliveMin); } } } } -//+------------------------------------------------------------------+ -//| SECTION 8 – BEST FVG SELECTOR | -//+------------------------------------------------------------------+ - +/** Returns index of best active FVG (prefer TOUCHED, then newest by created time). */ inline int GetBestActiveFVGIdx() { - int bestIdx = -1; datetime bestTime = 0; bool foundTouch = false; + int bestIndex = -1; + datetime bestCreatedTime = 0; + bool foundTouched = false; for (int i = 0; i < g_FVGCount; i++) { if (g_FVGPool[i].status == FVG_USED) continue; if (g_FVGPool[i].status == FVG_TOUCHED) { - if (!foundTouch || g_FVGPool[i].createdTime > bestTime) - { foundTouch = true; bestIdx = i; bestTime = g_FVGPool[i].createdTime; } + if (!foundTouched || g_FVGPool[i].createdTime > bestCreatedTime) + { foundTouched = true; bestIndex = i; bestCreatedTime = g_FVGPool[i].createdTime; } } - else if (!foundTouch && g_FVGPool[i].createdTime > bestTime) - { bestIdx = i; bestTime = g_FVGPool[i].createdTime; } + else if (!foundTouched && g_FVGPool[i].createdTime > bestCreatedTime) + { bestIndex = i; bestCreatedTime = g_FVGPool[i].createdTime; } } - return bestIdx; + return bestIndex; } -#endif // EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH +#endif diff --git a/Experts/EA_ICT_CL/State.mqh b/Experts/EA_ICT_CL/State.mqh index bff1d8b..a6c9dc6 100644 --- a/Experts/EA_ICT_CL/State.mqh +++ b/Experts/EA_ICT_CL/State.mqh @@ -1,87 +1,79 @@ #ifndef EA_ICT_CL__STATE_MQH -#define EA_ICT_CL__STATE_MQH // Tránh include trùng +#define EA_ICT_CL__STATE_MQH -// Module: State – enums và struct dùng chung toàn EA - -//==================================================== -// ENUMS -//==================================================== enum EAState { - EA_IDLE, // Chờ chọn FVG tốt nhất - EA_WAIT_TOUCH, // Chờ giá chạm vào vùng FVG - EA_WAIT_TRIGGER, // Đã touch FVG, chờ M5 MSS - EA_IN_TRADE // Đã gửi pending / đang giữ position + EA_IDLE, + EA_WAIT_TOUCH, + EA_WAIT_TRIGGER, + EA_IN_TRADE }; -enum HTFBias { BIAS_NONE=0, BIAS_UP=1, BIAS_DOWN=-1, BIAS_SIDEWAY=2 }; // Bias D1 -enum MarketDir { DIR_NONE=0, DIR_UP=1, DIR_DOWN=-1 }; // Hướng trend H1/M5 +enum HTFBias { BIAS_NONE=0, BIAS_UP=1, BIAS_DOWN=-1, BIAS_SIDEWAY=2 }; +enum MarketDir { DIR_NONE=0, DIR_UP=1, DIR_DOWN=-1 }; enum BlockReason { - BLOCK_NONE, BLOCK_SESSION, BLOCK_DAILY_LOSS, // Không chặn / chặn session / chặn daily loss - BLOCK_BIAS_MISMATCH, BLOCK_NO_BIAS // Bias không khớp H1 / không có bias + BLOCK_NONE, BLOCK_SESSION, BLOCK_DAILY_LOSS, + BLOCK_BIAS_MISMATCH, BLOCK_NO_BIAS }; enum FVGStatus { - FVG_PENDING, // Chưa bị giá chạm - FVG_TOUCHED, // Đã chạm, chờ MSS - FVG_USED // Đã dùng (broken/expired/triggered) + FVG_PENDING, + FVG_TOUCHED, + FVG_USED }; -//==================================================== -// STRUCTS -//==================================================== struct BiasContext { - HTFBias bias; // UP/DOWN/SIDEWAY/NONE - double rangeHigh, rangeLow; // Vùng sideway (nếu có) - datetime lastBarTime; // Thời gian bar D1 đã xử lý (tránh cập nhật trùng) + HTFBias bias; + double rangeHigh, rangeLow; + datetime lastBarTime; }; struct TFTrendContext { - MarketDir trend; // DIR_UP / DIR_DOWN / DIR_NONE - double h0, h1, l0, l1; // Giá 2 swing high, 2 swing low gần nhất - int idxH0, idxH1, idxL0, idxL1; // Chỉ số bar tương ứng - double keyLevel; // Mức key (l0 hoặc h0 tùy trend) - datetime lastBarTime; // Bar đã cập nhật (tránh cập nhật lại mỗi tick) + MarketDir trend; + double h0, h1, l0, l1; + int idxH0, idxH1, idxL0, idxL1; + double keyLevel; + datetime lastBarTime; - datetime lastMssTime; // Thời gian MSS gần nhất (chỉ M5) - double lastMssLevel; // Giá entry MSS (tH0 hoặc tL0) - MarketDir lastMssBreak; // Hướng break (UP/DOWN) - double mssSLSwing; // Giá SL tương ứng (tL0 hoặc tH0) + datetime lastMssTime; + double lastMssLevel; + MarketDir lastMssBreak; + double mssSLSwing; }; struct FVGRecord { - int id; // ID FVG trong pool - FVGStatus status; // PENDING / TOUCHED / USED - int usedCase; // 0=expired, 1=broken, 2=triggered by MSS - MarketDir direction; // DIR_UP (bull FVG) / DIR_DOWN (bear FVG) - double high, low, mid; // Vùng gap và điểm giữa - datetime createdTime; // Thời điểm tạo FVG - datetime touchTime; // Thời điểm giá chạm (nếu TOUCHED) - datetime usedTime; // Thời điểm chuyển USED - MarketDir triggerTrendAtTouch; // Trend M5 lúc touch (debug) + int id; + FVGStatus status; + int usedCase; + MarketDir direction; + double high, low, mid; + datetime createdTime; + datetime touchTime; + datetime usedTime; + MarketDir triggerTrendAtTouch; - datetime mssTime; // Thời điểm MSS (nếu usedCase==2) - double mssEntry; // Entry level từ MSS - double mssSL; // SL level từ MSS + datetime mssTime; + double mssEntry; + double mssSL; }; struct OrderPlan { - bool valid; // Plan có hợp lệ không - int direction; // +1 buy, -1 sell - double entry, stopLoss, takeProfit, lot; // Giá và khối lượng - int parentFVGId; // ID FVG gốc (để trace) + bool valid; + int direction; + double entry, stopLoss, takeProfit, lot; + int parentFVGId; }; struct DailyRiskContext { - double startBalance, currentBalance; // Balance đầu ngày và hiện tại - datetime dayStartTime; // Mốc đầu ngày D1 - bool limitHit; // Đã chạm max daily loss chưa + double startBalance, currentBalance; + datetime dayStartTime; + bool limitHit; }; -#endif // EA_ICT_CL__STATE_MQH +#endif diff --git a/Experts/EA_ICT_CL/Trade.mqh b/Experts/EA_ICT_CL/Trade.mqh index ec6c19f..4a6743a 100644 --- a/Experts/EA_ICT_CL/Trade.mqh +++ b/Experts/EA_ICT_CL/Trade.mqh @@ -1,28 +1,24 @@ #ifndef EA_ICT_CL__TRADE_MQH #define EA_ICT_CL__TRADE_MQH -// Module: Trade -// Order plan building + limit order execution. -// Extracted from EA_ICT_CL.mq5 (Section 7B – Order plan & execution). -// NOTE: Uses EA globals (g_*) and inputs. Include AFTER globals exist. - +/** Calculates lot size from account risk percent and entry–SL distance. */ inline double CalcLotFromRisk(double entry, double sl) { - double balance = AccountInfoDouble(ACCOUNT_BALANCE); - double riskMoney = balance * InpRiskPercent / 100.0; - double slPips = MathAbs(entry - sl) / _Point; + double balance = AccountInfoDouble(ACCOUNT_BALANCE); + double riskMoney = balance * InpRiskPercent / 100.0; + double slPips = MathAbs(entry - sl) / _Point; if (slPips < 1) return 0; - double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); - double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); + double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); + double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if (tickValue <= 0 || tickSize <= 0) return 0; - double pipValue = tickValue * (_Point / tickSize); - double rawLot = riskMoney / (slPips * pipValue); + double pipValue = tickValue * (_Point / tickSize); + double rawLot = riskMoney / (slPips * pipValue); - double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); - double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); - double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); if (lotStep <= 0) lotStep = 0.01; rawLot = MathFloor(rawLot / lotStep) * lotStep; @@ -30,12 +26,13 @@ inline double CalcLotFromRisk(double entry, double sl) return NormalizeDouble(rawLot, 2); } +/** Fills g_OrderPlan from FVG id, direction, MSS entry and SL; returns true if valid. */ inline bool BuildOrderPlan(int fvgId, MarketDir dir, double mssEntry, double mssSL) { ZeroMemory(g_OrderPlan); double entry = NormalizeDouble(mssEntry, _Digits); - double sl = mssSL; + double sl = mssSL; if (entry <= 0 || sl <= 0) return false; @@ -73,24 +70,25 @@ inline bool BuildOrderPlan(int fvgId, MarketDir dir, double mssEntry, double mss return true; } +/** Sends limit order from g_OrderPlan; returns ticket or 0 on failure. */ inline ulong ExecuteLimitOrder() { if (!g_OrderPlan.valid) return 0; - ENUM_ORDER_TYPE cmd = (g_OrderPlan.direction > 0) - ? ORDER_TYPE_BUY_LIMIT - : ORDER_TYPE_SELL_LIMIT; + ENUM_ORDER_TYPE orderType = (g_OrderPlan.direction > 0) + ? ORDER_TYPE_BUY_LIMIT + : ORDER_TYPE_SELL_LIMIT; double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); - if (cmd == ORDER_TYPE_BUY_LIMIT && g_OrderPlan.entry >= ask) + if (orderType == ORDER_TYPE_BUY_LIMIT && g_OrderPlan.entry >= ask) { if (InpDebugLog) PrintFormat("[ORDER] BUY LIMIT entry %.5f >= ask %.5f → skip", g_OrderPlan.entry, ask); return 0; } - if (cmd == ORDER_TYPE_SELL_LIMIT && g_OrderPlan.entry <= bid) + if (orderType == ORDER_TYPE_SELL_LIMIT && g_OrderPlan.entry <= bid) { if (InpDebugLog) PrintFormat("[ORDER] SELL LIMIT entry %.5f <= bid %.5f → skip", g_OrderPlan.entry, bid); @@ -105,7 +103,7 @@ inline ulong ExecuteLimitOrder() request.action = TRADE_ACTION_PENDING; request.symbol = _Symbol; request.volume = g_OrderPlan.lot; - request.type = cmd; + request.type = orderType; request.price = g_OrderPlan.entry; request.sl = g_OrderPlan.stopLoss; request.tp = g_OrderPlan.takeProfit; @@ -127,10 +125,10 @@ inline ulong ExecuteLimitOrder() } PrintFormat("[ORDER] ✅ %s #%llu | %.2f @ %.5f SL=%.5f TP=%.5f", - (cmd == ORDER_TYPE_BUY_LIMIT) ? "BUY_LIM" : "SELL_LIM", + (orderType == ORDER_TYPE_BUY_LIMIT) ? "BUY_LIM" : "SELL_LIM", result.order, g_OrderPlan.lot, g_OrderPlan.entry, g_OrderPlan.stopLoss, g_OrderPlan.takeProfit); return result.order; } -#endif // EA_ICT_CL__TRADE_MQH +#endif diff --git a/Experts/EA_ICT_CL/Trailing.mqh b/Experts/EA_ICT_CL/Trailing.mqh index 52cb3ab..0055b5d 100644 --- a/Experts/EA_ICT_CL/Trailing.mqh +++ b/Experts/EA_ICT_CL/Trailing.mqh @@ -1,7 +1,4 @@ #ifndef EA_ICT_CL__TRAILING_MQH -#define EA_ICT_CL__TRAILING_MQH // Tránh include trùng - -// Module: Trailing – placeholder cho trailing stop / breakeven / partial close (chưa implement) - -#endif // EA_ICT_CL__TRAILING_MQH +#define EA_ICT_CL__TRAILING_MQH +#endif diff --git a/Experts/EA_ICT_CL/Utils.mqh b/Experts/EA_ICT_CL/Utils.mqh index 1164c3f..57de884 100644 --- a/Experts/EA_ICT_CL/Utils.mqh +++ b/Experts/EA_ICT_CL/Utils.mqh @@ -1,32 +1,33 @@ #ifndef EA_ICT_CL__UTILS_MQH -#define EA_ICT_CL__UTILS_MQH // Tránh include trùng +#define EA_ICT_CL__UTILS_MQH -// Module: Utils – helper không phụ thuộc logic EA (clamp, round, new bar) - -inline double ClampDouble(const double v, const double lo, const double hi) +/** Clamps value to [lo, hi]. */ +inline double ClampDouble(const double value, const double lo, const double hi) { - if (v < lo) return lo; // Nhỏ hơn min → trả về min - if (v > hi) return hi; // Lớn hơn max → trả về max - return v; // Nằm trong [lo,hi] → giữ nguyên + if (value < lo) return lo; + if (value > hi) return hi; + return value; } -inline int ClampInt(const int v, const int lo, const int hi) +/** Clamps integer value to [lo, hi]. */ +inline int ClampInt(const int value, const int lo, const int hi) { - if (v < lo) return lo; - if (v > hi) return hi; - return v; + if (value < lo) return lo; + if (value > hi) return hi; + return value; } +/** Rounds value to nearest step (step must be > 0). */ inline double RoundToStep(const double value, const double step) { - if (step <= 0.0) return value; // Step không hợp lệ → không làm tròn - return MathRound(value / step) * step; // Làm tròn theo bước step + if (step <= 0.0) return value; + return MathRound(value / step) * step; } -inline bool IsNewBar(const datetime last_bar_time, const datetime current_bar_time) +/** Returns true when current bar time differs from last processed bar time (new bar). */ +inline bool IsNewBar(const datetime lastBarTime, const datetime currentBarTime) { - return (current_bar_time != 0 && current_bar_time != last_bar_time); // Bar mới khi thời gian bar đổi + return (currentBarTime != 0 && currentBarTime != lastBarTime); } -#endif // EA_ICT_CL__UTILS_MQH - +#endif