Format code

This commit is contained in:
Bell
2026-03-10 20:39:33 +07:00
parent b4d5afa941
commit 63c81f16bb
15 changed files with 421 additions and 440 deletions
+23 -29
View File
@@ -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
+44 -42
View File
@@ -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
+74 -69
View File
@@ -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<g_FVGCount;i++)
{ if(g_FVGPool[i].status==FVG_PENDING) nP++; else if(g_FVGPool[i].status==FVG_TOUCHED) nT++; else nU++; }
LBL(PREFIX_DEBUG_PANEL + "POOL", StringFormat("Pool : P=%d T=%d U=%d (%d/%d)", nP,nT,nU,g_FVGCount,MAX_FVG_POOL), 202, clrDodgerBlue)
int countPending = 0, countTouched = 0, countUsed = 0;
for (int i = 0; i < g_FVGCount; i++)
{ if (g_FVGPool[i].status == FVG_PENDING) countPending++; else if (g_FVGPool[i].status == FVG_TOUCHED) countTouched++; else countUsed++; }
LBL(PREFIX_DEBUG_PANEL + "POOL", StringFormat("Pool : P=%d T=%d U=%d (%d/%d)", countPending, countTouched, countUsed, g_FVGCount, MAX_FVG_POOL), 202, clrDodgerBlue)
if (g_ActiveFVGIdx >= 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();
+9 -11
View File
@@ -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
+16 -15
View File
@@ -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
+13 -14
View File
@@ -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
+8 -9
View File
@@ -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
+8 -9
View File
@@ -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
+21 -23
View File
@@ -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
+10 -12
View File
@@ -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 (023) 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 (023) 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ờ (023)
TimeToStruct(t, dt);
return dt.hour;
}
#endif // EA_ICT_CL__SESSIONS_MQH
#endif
+109 -109
View File
@@ -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
+44 -52
View File
@@ -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
+22 -24
View File
@@ -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 entrySL 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
+2 -5
View File
@@ -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
+18 -17
View File
@@ -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