Format code
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user