Structuring code into files
This commit is contained in:
+55
-1416
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
# This file keeps the folder in version control if empty in some setups.
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef EA_ICT_CL__CONFIG_MQH
|
||||
#define EA_ICT_CL__CONFIG_MQH
|
||||
|
||||
// Module: Config
|
||||
// This file now hosts EA inputs. Keep other types (enums/structs/globals) in the .mq5
|
||||
// until you intentionally migrate them to State/Config to avoid redefinition.
|
||||
|
||||
//====================================================
|
||||
// INPUTS
|
||||
//====================================================
|
||||
input ENUM_TIMEFRAMES InpBiasTF = PERIOD_D1; // Bias TF (HTF)
|
||||
input ENUM_TIMEFRAMES InpMiddleTF = PERIOD_H1; // FVG + trend TF (MTF)
|
||||
input ENUM_TIMEFRAMES InpTriggerTF = PERIOD_M5; // Entry confirmation (LTF)
|
||||
|
||||
input double InpRiskPercent = 1.0; // Risk % per trade
|
||||
input double InpRiskReward = 2.0; // TP/SL ratio
|
||||
input double InpMaxDailyLossPct = 3.0; // Max daily loss %
|
||||
|
||||
input int InpLondonStartHour = 8; // London open (UTC)
|
||||
input int InpLondonEndHour = 17; // London close (UTC)
|
||||
input int InpNYStartHour = 13; // NY open (UTC)
|
||||
input int InpNYEndHour = 22; // NY close (UTC)
|
||||
|
||||
input int InpSwingRange = 3; // Bars each side for swing confirm
|
||||
input int InpSwingLookback = 50; // MiddleTF swing scan bars
|
||||
input int InpTriggerSwingLookback = 30; // TriggerTF swing scan bars
|
||||
|
||||
input int InpFVGMaxAliveMin = 4320; // Max FVG lifetime (min) = 72h (cả PENDING + TOUCHED)
|
||||
input int InpFVGScanBars = 50; // MiddleTF bars to scan for FVGs
|
||||
input double InpFVGMinBodyPct = 60.0; // Mid-candle min body %
|
||||
input int InpMSSMinDepthPts = 30; // MSS min swing depth (points): |tH0-tL0| phải >= giá trị này
|
||||
|
||||
input long InpMagicNumber = 20250308; // EA magic number
|
||||
input int InpSlippage = 5; // Max slippage (points)
|
||||
|
||||
input bool InpDebugLog = true; // Journal logging
|
||||
input bool InpDebugDraw = true; // Chart drawing
|
||||
|
||||
#endif // EA_ICT_CL__CONFIG_MQH
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
#ifndef EA_ICT_CL__CONTEXTS_MQH
|
||||
#define EA_ICT_CL__CONTEXTS_MQH
|
||||
|
||||
// Module: Contexts
|
||||
// Context updaters extracted from EA_ICT_CL.mq5 (Section 2 – Context updaters).
|
||||
// NOTE: These functions currently use EA globals (g_*) and existing structs.
|
||||
// Include this file AFTER structs + globals are declared in EA_ICT_CL.mq5.
|
||||
|
||||
inline HTFBias ResolveBias(double b1H, double b1L, double b1C, double b2H, double b2L)
|
||||
{
|
||||
if (b1C > b2H) return BIAS_UP;
|
||||
if (b1C < b2L) return BIAS_DOWN;
|
||||
if (b1H > b2H && b1C < b2H) return BIAS_DOWN;
|
||||
if (b1L < b2L && b1C > b2L) return BIAS_UP;
|
||||
return BIAS_SIDEWAY;
|
||||
}
|
||||
|
||||
inline void UpdateBiasContext()
|
||||
{
|
||||
datetime t0 = iTime(_Symbol, InpBiasTF, 0);
|
||||
if (t0 == g_Bias.lastBarTime) return;
|
||||
g_Bias.lastBarTime = t0;
|
||||
|
||||
if (Bars(_Symbol, InpBiasTF) < 4) { g_Bias.bias = BIAS_NONE; return; }
|
||||
|
||||
double b1H = iHigh (_Symbol, InpBiasTF, 1);
|
||||
double b1L = iLow (_Symbol, InpBiasTF, 1);
|
||||
double b1C = iClose(_Symbol, InpBiasTF, 1);
|
||||
double b2H = iHigh (_Symbol, InpBiasTF, 2);
|
||||
double b2L = iLow (_Symbol, InpBiasTF, 2);
|
||||
|
||||
HTFBias prev = g_Bias.bias;
|
||||
g_Bias.bias = ResolveBias(b1H, b1L, b1C, b2H, b2L);
|
||||
g_Bias.rangeHigh = (g_Bias.bias == BIAS_SIDEWAY) ? b2H : 0;
|
||||
g_Bias.rangeLow = (g_Bias.bias == BIAS_SIDEWAY) ? b2L : 0;
|
||||
|
||||
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), b1H, b1L, b1C, b2H, b2L);
|
||||
}
|
||||
|
||||
inline void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContext &ctx)
|
||||
{
|
||||
datetime t0 = iTime(_Symbol, tf, 0);
|
||||
if (t0 == ctx.lastBarTime) return;
|
||||
ctx.lastBarTime = t0;
|
||||
|
||||
double bar1C = iClose(_Symbol, tf, 1);
|
||||
datetime bar1T = iTime (_Symbol, tf, 1);
|
||||
|
||||
if (tf == InpTriggerTF
|
||||
&& g_State == EA_WAIT_TRIGGER
|
||||
&& ctx.h0 > 0 && ctx.l0 > 0
|
||||
&& g_MiddleTrend.trend != DIR_NONE)
|
||||
{
|
||||
bool mssHit = false;
|
||||
MarketDir breakDir = DIR_NONE;
|
||||
double entryLevel = 0, slLevel = 0;
|
||||
|
||||
if (g_MiddleTrend.trend == DIR_UP && bar1C > ctx.h0)
|
||||
{
|
||||
mssHit = true;
|
||||
breakDir = DIR_UP;
|
||||
entryLevel = ctx.h0;
|
||||
slLevel = ctx.l0;
|
||||
}
|
||||
else if (g_MiddleTrend.trend == DIR_DOWN && bar1C < ctx.l0)
|
||||
{
|
||||
mssHit = true;
|
||||
breakDir = DIR_DOWN;
|
||||
entryLevel = ctx.l0;
|
||||
slLevel = ctx.h0;
|
||||
}
|
||||
|
||||
if (mssHit && bar1T != ctx.lastMssTime)
|
||||
{
|
||||
double swingDepth = MathAbs(ctx.h0 - ctx.l0) / _Point;
|
||||
if (swingDepth < InpMSSMinDepthPts)
|
||||
{
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[M5 MSS SKIP] depth=%.0f pts < %d | H0=%.5f L0=%.5f | %s",
|
||||
swingDepth, InpMSSMinDepthPts, ctx.h0, ctx.l0, TimeToString(bar1T));
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.lastMssTime = bar1T;
|
||||
ctx.lastMssLevel = entryLevel;
|
||||
ctx.lastMssBreak = breakDir;
|
||||
ctx.mssSLSwing = slLevel;
|
||||
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[M5 MSS] %s | entry=%.5f SL=%.5f depth=%.0fpts | close=%.5f | H0=%.5f L0=%.5f | %s",
|
||||
(breakDir == DIR_UP) ? "▲ Bull" : "▼ Bear",
|
||||
entryLevel, slLevel, swingDepth, bar1C,
|
||||
ctx.h0, ctx.l0, TimeToString(bar1T));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
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);
|
||||
|
||||
if (InpDebugLog && ctx.trend != prev)
|
||||
PrintFormat("[%s TREND] %s → %s | H0=%.5f H1=%.5f L0=%.5f L1=%.5f | KL=%.5f",
|
||||
EnumToString(tf), EnumToString(prev), EnumToString(ctx.trend),
|
||||
h0, h1, l0, l1, ctx.keyLevel);
|
||||
}
|
||||
|
||||
inline void UpdateDailyRiskContext()
|
||||
{
|
||||
datetime today = iTime(_Symbol, PERIOD_D1, 0);
|
||||
if (today != g_DailyRisk.dayStartTime)
|
||||
{
|
||||
g_DailyRisk.dayStartTime = today;
|
||||
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;
|
||||
|
||||
g_DailyRisk.currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
double lostPct = (g_DailyRisk.startBalance - g_DailyRisk.currentBalance)
|
||||
/ g_DailyRisk.startBalance * 100.0;
|
||||
if (lostPct >= InpMaxDailyLossPct)
|
||||
{
|
||||
g_DailyRisk.limitHit = true;
|
||||
PrintFormat("[DAILY RISK] ⛔ Limit hit | lost=%.2f%% | bal=%.2f",
|
||||
lostPct, g_DailyRisk.currentBalance);
|
||||
}
|
||||
}
|
||||
|
||||
inline void UpdateAllContexts()
|
||||
{
|
||||
UpdateDailyRiskContext();
|
||||
UpdateBiasContext();
|
||||
UpdateTFTrendContext(InpMiddleTF, InpSwingLookback, g_MiddleTrend);
|
||||
UpdateTFTrendContext(InpTriggerTF, InpTriggerSwingLookback, g_TriggerTrend);
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__CONTEXTS_MQH
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
#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.
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
else ObjectDelete(0, klN);
|
||||
}
|
||||
|
||||
inline void DrawMiddleSwingPoints()
|
||||
{
|
||||
if (!InpDebugDraw) return;
|
||||
if (g_MiddleTrend.idxH0 <= 0 || g_MiddleTrend.idxH1 <= 0 ||
|
||||
g_MiddleTrend.idxL0 <= 0 || g_MiddleTrend.idxL1 <= 0)
|
||||
{ ObjectsDeleteAll(0, PREFIX_SWING_MIDDLE); return; }
|
||||
|
||||
bool isUp = (g_MiddleTrend.trend == DIR_UP);
|
||||
bool isDown = (g_MiddleTrend.trend == DIR_DOWN);
|
||||
DrawOneSwingPoint(PREFIX_SWING_MIDDLE, InpMiddleTF, "MiddleH0", true, g_MiddleTrend.idxH0, g_MiddleTrend.h0, clrAqua, isDown, 2, 8);
|
||||
DrawOneSwingPoint(PREFIX_SWING_MIDDLE, InpMiddleTF, "MiddleH1", true, g_MiddleTrend.idxH1, g_MiddleTrend.h1, C'0,140,160', false, 2, 8);
|
||||
DrawOneSwingPoint(PREFIX_SWING_MIDDLE, InpMiddleTF, "MiddleL0", false, g_MiddleTrend.idxL0, g_MiddleTrend.l0, clrYellow, isUp, 2, 8);
|
||||
DrawOneSwingPoint(PREFIX_SWING_MIDDLE, InpMiddleTF, "MiddleL1", false, g_MiddleTrend.idxL1, g_MiddleTrend.l1, C'160,140,0', false, 2, 8);
|
||||
}
|
||||
|
||||
inline void DrawTriggerSwingPoints()
|
||||
{
|
||||
if (!InpDebugDraw) return;
|
||||
if (g_TriggerTrend.idxH0 <= 0 || g_TriggerTrend.idxH1 <= 0 ||
|
||||
g_TriggerTrend.idxL0 <= 0 || g_TriggerTrend.idxL1 <= 0)
|
||||
{ ObjectsDeleteAll(0, PREFIX_SWING_TRIGGER); return; }
|
||||
|
||||
bool isUp = (g_TriggerTrend.trend == DIR_UP);
|
||||
bool isDown = (g_TriggerTrend.trend == DIR_DOWN);
|
||||
DrawOneSwingPoint(PREFIX_SWING_TRIGGER, InpTriggerTF, "TriggerH0", true, g_TriggerTrend.idxH0, g_TriggerTrend.h0, C'180,100,255', isDown, 1, 7);
|
||||
DrawOneSwingPoint(PREFIX_SWING_TRIGGER, InpTriggerTF, "TriggerH1", true, g_TriggerTrend.idxH1, g_TriggerTrend.h1, C'100,60,160', false, 1, 7);
|
||||
DrawOneSwingPoint(PREFIX_SWING_TRIGGER, InpTriggerTF, "TriggerL0", false, g_TriggerTrend.idxL0, g_TriggerTrend.l0, C'255,160,40', isUp, 1, 7);
|
||||
DrawOneSwingPoint(PREFIX_SWING_TRIGGER, InpTriggerTF, "TriggerL1", false, g_TriggerTrend.idxL1, g_TriggerTrend.l1, C'160,100,20', false, 1, 7);
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
inline void DrawMSSMarkers()
|
||||
{
|
||||
if (!InpDebugDraw) return;
|
||||
for (int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if (g_FVGPool[i].status != FVG_USED || g_FVGPool[i].usedCase != 2) continue;
|
||||
if (g_FVGPool[i].mssTime == 0) continue;
|
||||
|
||||
string tid = "T_" + IntegerToString(g_FVGPool[i].id);
|
||||
DrawMSSMarker(tid, InpTriggerTF,
|
||||
g_FVGPool[i].mssTime,
|
||||
g_FVGPool[i].mssEntry,
|
||||
g_FVGPool[i].direction == DIR_UP ? DIR_UP : DIR_DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
inline void DrawOrderVisualization()
|
||||
{
|
||||
if (!InpDebugDraw) return;
|
||||
|
||||
string tpZoneN = PREFIX_ORDER_VISUAL + "TP_ZONE";
|
||||
string slZoneN = PREFIX_ORDER_VISUAL + "SL_ZONE";
|
||||
string entLineN = PREFIX_ORDER_VISUAL + "ENTRY_LINE";
|
||||
string slLineN = PREFIX_ORDER_VISUAL + "SL_LINE";
|
||||
string tpLineN = PREFIX_ORDER_VISUAL + "TP_LINE";
|
||||
string entLblN = PREFIX_ORDER_VISUAL + "ENTRY_LBL";
|
||||
string slLblN = PREFIX_ORDER_VISUAL + "SL_LBL";
|
||||
string tpLblN = PREFIX_ORDER_VISUAL + "TP_LBL";
|
||||
string infoLblN = PREFIX_ORDER_VISUAL + "INFO_LBL";
|
||||
|
||||
if (!g_OrderPlan.valid || g_State == EA_IDLE)
|
||||
{
|
||||
ObjectDelete(0, tpZoneN); ObjectDelete(0, slZoneN);
|
||||
ObjectDelete(0, entLineN); ObjectDelete(0, slLineN); ObjectDelete(0, tpLineN);
|
||||
ObjectDelete(0, entLblN); ObjectDelete(0, slLblN); ObjectDelete(0, tpLblN);
|
||||
ObjectDelete(0, infoLblN);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isBuy = (g_OrderPlan.direction > 0);
|
||||
double entry = g_OrderPlan.entry;
|
||||
double sl = g_OrderPlan.stopLoss;
|
||||
double tp = g_OrderPlan.takeProfit;
|
||||
|
||||
datetime tStart = (g_TriggerTrend.lastMssTime > 0) ? g_TriggerTrend.lastMssTime : iTime(_Symbol, InpTriggerTF, 20);
|
||||
datetime tEnd = iTime(_Symbol, InpTriggerTF, 0) + PeriodSeconds(InpTriggerTF) * 25;
|
||||
|
||||
double slPips = MathAbs(entry - sl) / _Point;
|
||||
double tpPips = MathAbs(tp - entry) / _Point;
|
||||
double rr = (slPips > 0) ? tpPips / slPips : 0;
|
||||
|
||||
color entryClr = isBuy ? C'33,150,243' : C'255,152,0';
|
||||
color tpFill = C'15,65,35';
|
||||
color slFill = C'85,15,15';
|
||||
color tpClr = C'38,166,91';
|
||||
color slClr = C'229,57,53';
|
||||
|
||||
double tpTop = isBuy ? tp : entry, tpBot = isBuy ? entry : tp;
|
||||
if (ObjectFind(0, tpZoneN) < 0) ObjectCreate(0, tpZoneN, OBJ_RECTANGLE, 0, tStart, tpTop, tEnd, tpBot);
|
||||
ObjectSetInteger(0, tpZoneN, OBJPROP_COLOR, tpFill); ObjectSetInteger(0, tpZoneN, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, tpZoneN, OBJPROP_BACK, true);
|
||||
ObjectMove(0, tpZoneN, 0, tStart, tpTop); ObjectMove(0, tpZoneN, 1, tEnd, tpBot);
|
||||
|
||||
double slTop = isBuy ? entry : sl, slBot = isBuy ? sl : entry;
|
||||
if (ObjectFind(0, slZoneN) < 0) ObjectCreate(0, slZoneN, OBJ_RECTANGLE, 0, tStart, slTop, tEnd, slBot);
|
||||
ObjectSetInteger(0, slZoneN, OBJPROP_COLOR, slFill); ObjectSetInteger(0, slZoneN, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, slZoneN, OBJPROP_BACK, true);
|
||||
ObjectMove(0, slZoneN, 0, tStart, slTop); ObjectMove(0, slZoneN, 1, tEnd, slBot);
|
||||
|
||||
if (ObjectFind(0, entLineN) < 0) ObjectCreate(0, entLineN, OBJ_TREND, 0, tStart, entry, tEnd, entry);
|
||||
ObjectSetInteger(0, entLineN, OBJPROP_COLOR, entryClr); ObjectSetInteger(0, entLineN, OBJPROP_STYLE, STYLE_SOLID);
|
||||
ObjectSetInteger(0, entLineN, OBJPROP_WIDTH, 2); ObjectSetInteger(0, entLineN, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectMove(0, entLineN, 0, tStart, entry); ObjectMove(0, entLineN, 1, tEnd, entry);
|
||||
|
||||
if (ObjectFind(0, tpLineN) < 0) ObjectCreate(0, tpLineN, OBJ_TREND, 0, tStart, tp, tEnd, tp);
|
||||
ObjectSetInteger(0, tpLineN, OBJPROP_COLOR, tpClr); ObjectSetInteger(0, tpLineN, OBJPROP_STYLE, STYLE_DASH);
|
||||
ObjectSetInteger(0, tpLineN, OBJPROP_WIDTH, 1); ObjectSetInteger(0, tpLineN, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectMove(0, tpLineN, 0, tStart, tp); ObjectMove(0, tpLineN, 1, tEnd, tp);
|
||||
|
||||
if (ObjectFind(0, slLineN) < 0) ObjectCreate(0, slLineN, OBJ_TREND, 0, tStart, sl, tEnd, sl);
|
||||
ObjectSetInteger(0, slLineN, OBJPROP_COLOR, slClr); ObjectSetInteger(0, slLineN, OBJPROP_STYLE, STYLE_DASH);
|
||||
ObjectSetInteger(0, slLineN, OBJPROP_WIDTH, 1); ObjectSetInteger(0, slLineN, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectMove(0, slLineN, 0, tStart, sl); ObjectMove(0, slLineN, 1, tEnd, sl);
|
||||
|
||||
datetime lblT = tEnd;
|
||||
|
||||
string eTxt = StringFormat("%s %s | %.2f lot", isBuy?"▶ BUY LIM":"▶ SELL LIM", DoubleToString(entry,_Digits), g_OrderPlan.lot);
|
||||
if (ObjectFind(0, entLblN) < 0) ObjectCreate(0, entLblN, OBJ_TEXT, 0, lblT, entry);
|
||||
ObjectMove(0, entLblN, 0, lblT, entry); ObjectSetString(0, entLblN, OBJPROP_TEXT, eTxt);
|
||||
ObjectSetInteger(0, entLblN, OBJPROP_COLOR, entryClr); ObjectSetInteger(0, entLblN, OBJPROP_FONTSIZE, 8);
|
||||
ObjectSetInteger(0, entLblN, OBJPROP_ANCHOR, ANCHOR_LEFT);
|
||||
|
||||
string tTxt = StringFormat("◎ TP %s +%.0fp (%.1fR)", DoubleToString(tp,_Digits), tpPips, rr);
|
||||
if (ObjectFind(0, tpLblN) < 0) ObjectCreate(0, tpLblN, OBJ_TEXT, 0, lblT, tp);
|
||||
ObjectMove(0, tpLblN, 0, lblT, tp); ObjectSetString(0, tpLblN, OBJPROP_TEXT, tTxt);
|
||||
ObjectSetInteger(0, tpLblN, OBJPROP_COLOR, tpClr); ObjectSetInteger(0, tpLblN, OBJPROP_FONTSIZE, 8);
|
||||
ObjectSetInteger(0, tpLblN, OBJPROP_ANCHOR, isBuy?ANCHOR_LEFT_LOWER:ANCHOR_LEFT_UPPER);
|
||||
|
||||
string sTxt = StringFormat("✕ SL %s -%.0fp", DoubleToString(sl,_Digits), slPips);
|
||||
if (ObjectFind(0, slLblN) < 0) ObjectCreate(0, slLblN, OBJ_TEXT, 0, lblT, sl);
|
||||
ObjectMove(0, slLblN, 0, lblT, sl); ObjectSetString(0, slLblN, OBJPROP_TEXT, sTxt);
|
||||
ObjectSetInteger(0, slLblN, OBJPROP_COLOR, slClr); ObjectSetInteger(0, slLblN, OBJPROP_FONTSIZE, 8);
|
||||
ObjectSetInteger(0, slLblN, OBJPROP_ANCHOR, isBuy?ANCHOR_LEFT_UPPER:ANCHOR_LEFT_LOWER);
|
||||
|
||||
string iTxt = StringFormat("Risk %.1f%% | %.0f:%.0f pips | %.1fR", InpRiskPercent, slPips, tpPips, rr);
|
||||
if (ObjectFind(0, infoLblN) < 0) ObjectCreate(0, infoLblN, OBJ_TEXT, 0, lblT, (entry+sl)/2.0);
|
||||
ObjectMove(0, infoLblN, 0, lblT, (entry+sl)/2.0); ObjectSetString(0, infoLblN, OBJPROP_TEXT, iTxt);
|
||||
ObjectSetInteger(0, infoLblN, OBJPROP_COLOR, C'160,160,160'); ObjectSetInteger(0, infoLblN, OBJPROP_FONTSIZE, 7);
|
||||
ObjectSetInteger(0, infoLblN, OBJPROP_ANCHOR, ANCHOR_LEFT);
|
||||
}
|
||||
|
||||
inline void DrawOneFVGRecord(int idx)
|
||||
{
|
||||
if (!InpDebugDraw || idx < 0 || idx >= g_FVGCount) return;
|
||||
|
||||
string sid = IntegerToString(g_FVGPool[idx].id);
|
||||
string rectN = PREFIX_FVG_POOL + "RECT_" + sid;
|
||||
string midN = PREFIX_FVG_POOL + "MID_" + sid;
|
||||
string lblN = PREFIX_FVG_POOL + "LBL_" + sid;
|
||||
|
||||
datetime rectEnd;
|
||||
if (g_FVGPool[idx].status == FVG_PENDING)
|
||||
rectEnd = iTime(_Symbol, InpMiddleTF, 0);
|
||||
else if (g_FVGPool[idx].touchTime > 0)
|
||||
{
|
||||
int shift = MyBarShift(_Symbol, InpTriggerTF, g_FVGPool[idx].touchTime);
|
||||
rectEnd = (shift >= 0) ? iTime(_Symbol, InpTriggerTF, shift) : iTime(_Symbol, InpMiddleTF, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift = MyBarShift(_Symbol, InpMiddleTF, g_FVGPool[idx].usedTime);
|
||||
rectEnd = (shift >= 0) ? iTime(_Symbol, InpMiddleTF, shift) : iTime(_Symbol, InpMiddleTF, 0);
|
||||
}
|
||||
if (rectEnd <= g_FVGPool[idx].createdTime) rectEnd = iTime(_Symbol, InpMiddleTF, 0);
|
||||
|
||||
color fillColor;
|
||||
if (g_FVGPool[idx].status == FVG_PENDING) fillColor = (g_FVGPool[idx].direction == DIR_UP) ? C'0,50,110' : C'90,25,0';
|
||||
else if (g_FVGPool[idx].status == FVG_TOUCHED) fillColor = (g_FVGPool[idx].direction == DIR_UP) ? C'0,120,220' : C'220,75,0';
|
||||
else if (g_FVGPool[idx].usedCase == 2) fillColor = C'0,100,0';
|
||||
else if (g_FVGPool[idx].usedCase == 1) fillColor = C'70,0,0';
|
||||
else fillColor = C'50,50,50';
|
||||
|
||||
if (ObjectFind(0, rectN) < 0)
|
||||
ObjectCreate(0, rectN, OBJ_RECTANGLE, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].high, rectEnd, g_FVGPool[idx].low);
|
||||
ObjectSetInteger(0, rectN, OBJPROP_COLOR, fillColor); ObjectSetInteger(0, rectN, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, rectN, OBJPROP_BACK, true);
|
||||
ObjectMove(0, rectN, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].high);
|
||||
ObjectMove(0, rectN, 1, rectEnd, g_FVGPool[idx].low);
|
||||
|
||||
color midColor = (g_FVGPool[idx].status == FVG_USED) ? C'60,60,60' : clrSilver;
|
||||
if (ObjectFind(0, midN) < 0)
|
||||
ObjectCreate(0, midN, OBJ_TREND, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].mid, rectEnd, g_FVGPool[idx].mid);
|
||||
ObjectSetInteger(0, midN, OBJPROP_COLOR, midColor); ObjectSetInteger(0, midN, OBJPROP_STYLE, STYLE_DOT);
|
||||
ObjectSetInteger(0, midN, OBJPROP_WIDTH, 1); ObjectSetInteger(0, midN, OBJPROP_RAY_RIGHT, false);
|
||||
ObjectMove(0, midN, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].mid);
|
||||
ObjectMove(0, midN, 1, rectEnd, g_FVGPool[idx].mid);
|
||||
|
||||
string sym = (g_FVGPool[idx].direction == DIR_UP) ? "▲" : "▼";
|
||||
string stTxt = "";
|
||||
if (g_FVGPool[idx].status == FVG_TOUCHED) stTxt = " [T]";
|
||||
else if (g_FVGPool[idx].usedCase == 2) stTxt = " [TRIG]";
|
||||
else if (g_FVGPool[idx].usedCase == 1) stTxt = " [BRK]";
|
||||
else if (g_FVGPool[idx].status == FVG_USED) stTxt = " [EXP]";
|
||||
|
||||
if (ObjectFind(0, lblN) < 0)
|
||||
ObjectCreate(0, lblN, OBJ_TEXT, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].high);
|
||||
ObjectMove(0, lblN, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].high);
|
||||
ObjectSetString(0, lblN, OBJPROP_TEXT, StringFormat("FVG#%d %s%s", g_FVGPool[idx].id, sym, stTxt));
|
||||
ObjectSetInteger(0, lblN, OBJPROP_COLOR, fillColor); ObjectSetInteger(0, lblN, OBJPROP_FONTSIZE, 8);
|
||||
ObjectSetInteger(0, lblN, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
|
||||
}
|
||||
|
||||
inline void DrawFVGPool()
|
||||
{
|
||||
if (!InpDebugDraw) return;
|
||||
for (int i = 0; i < g_FVGCount; i++) DrawOneFVGRecord(i);
|
||||
}
|
||||
|
||||
inline void DrawContextDebug()
|
||||
{
|
||||
if (!InpDebugDraw) return;
|
||||
|
||||
#define LBL(name,txt,y,clr) \
|
||||
if(ObjectFind(0,name)<0) ObjectCreate(0,name,OBJ_LABEL,0,0,0); \
|
||||
ObjectSetInteger(0,name,OBJPROP_CORNER, CORNER_LEFT_UPPER); \
|
||||
ObjectSetInteger(0,name,OBJPROP_XDISTANCE, 10); \
|
||||
ObjectSetInteger(0,name,OBJPROP_YDISTANCE, y); \
|
||||
ObjectSetInteger(0,name,OBJPROP_FONTSIZE, 9); \
|
||||
ObjectSetInteger(0,name,OBJPROP_COLOR, clr); \
|
||||
ObjectSetString (0,name,OBJPROP_TEXT, txt);
|
||||
|
||||
LBL(PREFIX_DEBUG_PANEL + "HDR", "── ICT EA v4.3 ──", 10, clrSilver)
|
||||
|
||||
color cB = (g_Bias.bias==BIAS_UP)?clrLime:(g_Bias.bias==BIAS_DOWN)?clrTomato:(g_Bias.bias==BIAS_SIDEWAY)?clrOrange:clrGray;
|
||||
LBL(PREFIX_DEBUG_PANEL + "BIAS", StringFormat("Bias : %s", EnumToString(g_Bias.bias)), 34, cB)
|
||||
|
||||
color cMT = (g_MiddleTrend.trend==DIR_UP)?clrLime:(g_MiddleTrend.trend==DIR_DOWN)?clrTomato:clrGray;
|
||||
LBL(PREFIX_DEBUG_PANEL + "MT", StringFormat("H1 : %s KL=%.5f", EnumToString(g_MiddleTrend.trend), g_MiddleTrend.keyLevel), 58, cMT)
|
||||
|
||||
color cTT = (g_TriggerTrend.trend==DIR_UP)?clrLime:(g_TriggerTrend.trend==DIR_DOWN)?clrTomato:clrGray;
|
||||
LBL(PREFIX_DEBUG_PANEL + "TT", StringFormat("M5 : %s KL=%.5f", EnumToString(g_TriggerTrend.trend), g_TriggerTrend.keyLevel), 82, cTT)
|
||||
|
||||
if (g_ActiveFVGIdx >= 0 && g_ActiveFVGIdx < g_FVGCount
|
||||
&& g_FVGPool[g_ActiveFVGIdx].usedCase == 2
|
||||
&& g_FVGPool[g_ActiveFVGIdx].mssTime > 0)
|
||||
{
|
||||
int ai = g_ActiveFVGIdx;
|
||||
color cM = (g_FVGPool[ai].direction==DIR_UP)?clrLime:clrTomato;
|
||||
LBL(PREFIX_DEBUG_PANEL + "MSS", StringFormat("MSS : %s entry=%.5f SL=%.5f @ %s (FVG#%d)",
|
||||
(g_FVGPool[ai].direction==DIR_UP)?"▲":"▼",
|
||||
g_FVGPool[ai].mssEntry, g_FVGPool[ai].mssSL,
|
||||
TimeToString(g_FVGPool[ai].mssTime, TIME_MINUTES),
|
||||
g_FVGPool[ai].id), 106, cM)
|
||||
}
|
||||
else ObjectDelete(0, PREFIX_DEBUG_PANEL + "MSS");
|
||||
|
||||
double lostPct = g_DailyRisk.startBalance > 0
|
||||
? (g_DailyRisk.startBalance - g_DailyRisk.currentBalance) / g_DailyRisk.startBalance * 100.0 : 0.0;
|
||||
color cR = g_DailyRisk.limitHit?clrRed:(lostPct>InpMaxDailyLossPct*0.7?clrOrange:clrLime);
|
||||
LBL(PREFIX_DEBUG_PANEL + "RISK", StringFormat("Risk : %.2f%% / %.2f%%", lostPct, InpMaxDailyLossPct), 130, cR)
|
||||
|
||||
color cS = (g_State==EA_IDLE)?clrSilver:(g_State==EA_WAIT_TOUCH)?clrOrange:(g_State==EA_WAIT_TRIGGER)?clrYellow:clrLime;
|
||||
LBL(PREFIX_DEBUG_PANEL + "ST", StringFormat("State: %s", EnumToString(g_State)), 154, cS)
|
||||
|
||||
if (g_BlockReason != BLOCK_NONE)
|
||||
{ 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)
|
||||
|
||||
if (g_ActiveFVGIdx >= 0 && g_ActiveFVGIdx < g_FVGCount)
|
||||
{
|
||||
int ai = g_ActiveFVGIdx;
|
||||
LBL(PREFIX_DEBUG_PANEL + "ACT", StringFormat("Act : #%d %s [%.5f–%.5f] %s",
|
||||
g_FVGPool[ai].id, EnumToString(g_FVGPool[ai].direction),
|
||||
g_FVGPool[ai].low, g_FVGPool[ai].high, EnumToString(g_FVGPool[ai].status)), 226, clrDeepSkyBlue)
|
||||
}
|
||||
else ObjectDelete(0, PREFIX_DEBUG_PANEL + "ACT");
|
||||
|
||||
if (g_PendingTicket > 0 && g_OrderPlan.valid)
|
||||
{
|
||||
LBL(PREFIX_DEBUG_PANEL + "ORD", StringFormat("Order: %s #%llu @ %.5f SL=%.5f TP=%.5f",
|
||||
(g_OrderPlan.direction>0)?"BUY":"SELL", g_PendingTicket,
|
||||
g_OrderPlan.entry, g_OrderPlan.stopLoss, g_OrderPlan.takeProfit), 250, clrGold)
|
||||
}
|
||||
else ObjectDelete(0, PREFIX_DEBUG_PANEL + "ORD");
|
||||
|
||||
#undef LBL
|
||||
ChartRedraw(0);
|
||||
}
|
||||
|
||||
inline void DrawVisuals()
|
||||
{
|
||||
DrawMiddleSwingPoints();
|
||||
DrawTriggerSwingPoints();
|
||||
DrawMSSMarkers();
|
||||
DrawFVGPool();
|
||||
DrawOrderVisualization();
|
||||
DrawContextDebug();
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__DRAWING_MQH
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef EA_ICT_CL__FILTERS_MQH
|
||||
#define EA_ICT_CL__FILTERS_MQH
|
||||
|
||||
// Module: Filters
|
||||
// Put spread/news/day-of-week/max-trades filters here.
|
||||
|
||||
inline bool Filter_MaxSpreadPoints(const string symbol, const double max_spread_points)
|
||||
{
|
||||
if (max_spread_points <= 0) return true;
|
||||
const double pt = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
if (pt <= 0.0) return true;
|
||||
const double spread_pts = (SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)) / pt;
|
||||
return (spread_pts <= max_spread_points);
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__FILTERS_MQH
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef EA_ICT_CL__GUARDS_MQH
|
||||
#define EA_ICT_CL__GUARDS_MQH
|
||||
|
||||
// Module: Guards
|
||||
// Pre-trade guard checks extracted from EA_ICT_CL.mq5 (Section 3 – Guards).
|
||||
// NOTE: Uses EA globals (g_*) and inputs. Include AFTER globals exist.
|
||||
|
||||
inline bool IsSessionAllowed() { return true; /* TODO: implement session filter using InpLondon/NY hours */ }
|
||||
inline bool IsDailyLossOK() { return !g_DailyRisk.limitHit; }
|
||||
inline bool IsBiasValid() { return g_Bias.bias == BIAS_UP || g_Bias.bias == BIAS_DOWN; }
|
||||
|
||||
inline bool IsMiddleTrendAligned()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
inline bool EvaluateGuards()
|
||||
{
|
||||
g_BlockReason = BLOCK_NONE;
|
||||
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
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef EA_ICT_CL__INDICATORS_MQH
|
||||
#define EA_ICT_CL__INDICATORS_MQH
|
||||
|
||||
// Module: Indicators
|
||||
// Keep indicator handle lifecycle helpers here. This file is safe to include now.
|
||||
|
||||
inline bool CopyBufferSafe(const int handle, const int buffer, const int start_pos, const int count, double &out[])
|
||||
{
|
||||
if (handle == INVALID_HANDLE) return false;
|
||||
ArraySetAsSeries(out, true);
|
||||
const int copied = CopyBuffer(handle, buffer, start_pos, count, out);
|
||||
return (copied == count);
|
||||
}
|
||||
|
||||
inline bool CopyTimeSafe(const string symbol, const ENUM_TIMEFRAMES tf, const int start_pos, const int count, datetime &out[])
|
||||
{
|
||||
ArraySetAsSeries(out, true);
|
||||
const int copied = CopyTime(symbol, tf, start_pos, count, out);
|
||||
return (copied == count);
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__INDICATORS_MQH
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef EA_ICT_CL__LOGGING_MQH
|
||||
#define EA_ICT_CL__LOGGING_MQH
|
||||
|
||||
// Module: Logging
|
||||
|
||||
inline void LogPrint(const bool enabled, const string msg)
|
||||
{
|
||||
if (!enabled) return;
|
||||
Print(msg);
|
||||
}
|
||||
|
||||
inline void LogPrintF(const bool enabled, const string fmt, const string a0 = "", const string a1 = "", const string a2 = "")
|
||||
{
|
||||
if (!enabled) return;
|
||||
Print(StringFormat(fmt, a0, a1, a2));
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__LOGGING_MQH
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef EA_ICT_CL__MARKET_MQH
|
||||
#define EA_ICT_CL__MARKET_MQH
|
||||
|
||||
// Module: Market
|
||||
// Market/tick/contract-spec helpers live here.
|
||||
|
||||
inline double GetBid(const string symbol) { return SymbolInfoDouble(symbol, SYMBOL_BID); }
|
||||
inline double GetAsk(const string symbol) { return SymbolInfoDouble(symbol, SYMBOL_ASK); }
|
||||
inline double GetPoint(const string symbol) { return SymbolInfoDouble(symbol, SYMBOL_POINT); }
|
||||
inline int GetDigits(const string symbol) { return (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); }
|
||||
|
||||
inline double GetSpreadPoints(const string symbol)
|
||||
{
|
||||
const double bid = GetBid(symbol);
|
||||
const double ask = GetAsk(symbol);
|
||||
const double pt = GetPoint(symbol);
|
||||
if (pt <= 0.0) return 0.0;
|
||||
return (ask - bid) / pt;
|
||||
}
|
||||
|
||||
inline bool IsTradeAllowedNow(const string symbol)
|
||||
{
|
||||
if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return false;
|
||||
if (!MQLInfoInteger(MQL_TRADE_ALLOWED)) return false;
|
||||
if (!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__MARKET_MQH
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef EA_ICT_CL__ORDERS_MQH
|
||||
#define EA_ICT_CL__ORDERS_MQH
|
||||
|
||||
// Module: Orders
|
||||
// Helpers for building request fields, normalize price, magic/comment conventions.
|
||||
|
||||
inline double NormalizePrice(const string symbol, const double price)
|
||||
{
|
||||
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")
|
||||
{
|
||||
return StringFormat("%s|mg=%I64d|fvg=%d", tag, magic, fvg_id);
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__ORDERS_MQH
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# EA_ICT_CL – Kiến trúc & luồng chạy
|
||||
|
||||
File chính: `Experts/EA_ICT_CL.mq5`
|
||||
Thư mục module: `Experts/EA_ICT_CL/` (các `.mqh`)
|
||||
|
||||
## 1) Mục tiêu thiết kế
|
||||
|
||||
- `EA_ICT_CL.mq5` nên là **file “glue”**: `OnInit/OnDeinit/OnTick/...` + gọi hàm module.
|
||||
- Mỗi `.mqh` chỉ gánh **một trách nhiệm**: config/state/signals/risk/trade/filters/…
|
||||
- Migrate **từng phần nhỏ** để tránh trùng định nghĩa (enum/struct/global) và dễ debug.
|
||||
|
||||
## 2) Luồng chạy tổng thể (theo mô tả hiện tại trong header v4.3)
|
||||
|
||||
### Bối cảnh timeframe
|
||||
|
||||
- **BiasTF (D1)**: xác định bias (UP/DOWN/SIDEWAY/NONE)
|
||||
- **MiddleTF (H1)**: xác định trend + quét FVG thuận xu hướng
|
||||
- **TriggerTF (M5)**: xác nhận entry bằng **MSS** (market structure shift)
|
||||
|
||||
### State machine
|
||||
|
||||
EA đang dùng state machine:
|
||||
|
||||
- **`EA_IDLE`**
|
||||
- Mục tiêu: tìm/đặt “FVG tốt nhất” (MiddleTF) để theo dõi.
|
||||
- **`EA_WAIT_TOUCH`**
|
||||
- Mục tiêu: chờ giá retrace và **touch vào vùng FVG** (H1).
|
||||
- **`EA_WAIT_TRIGGER`**
|
||||
- Mục tiêu: khi đã touch FVG, chờ **M5 MSS** xác nhận đảo chiều theo trend.
|
||||
- v4.3: MSS chỉ detect khi state này.
|
||||
- **`EA_IN_TRADE`**
|
||||
- Mục tiêu: quản lý pending/position (trailing/timeout/cleanup tuỳ EA).
|
||||
|
||||
### Decision flow “từ trên xuống”
|
||||
|
||||
1) **D1 Bias**
|
||||
- Xác định `HTFBias` (UP/DOWN/SIDEWAY/NONE).
|
||||
2) **H1 Trend / Swing structure**
|
||||
- Xác định `MarketDir` (UP/DOWN/NONE) và các swing gần nhất: `h0/h1/l0/l1`.
|
||||
3) **Quét H1 FVG**
|
||||
- Tạo/duy trì pool FVG (`g_FVGPool`) tối đa `MAX_FVG_POOL`.
|
||||
- FVG có lifecycle: `PENDING → TOUCHED → USED`.
|
||||
4) **Touch FVG**
|
||||
- Khi bid/ask đi vào vùng gap → `touchTime` được set, chuyển state sang chờ trigger.
|
||||
5) **M5 MSS (chỉ khi `EA_WAIT_TRIGGER`)**
|
||||
- Nếu H1 UP: bull MSS khi close phá **OLD** `tH0` → entry = `tH0`, SL = `tL0`.
|
||||
- Nếu H1 DOWN: bear MSS khi close phá **OLD** `tL0` → entry = `tL0`, SL = `tH0`.
|
||||
6) **Lập kế hoạch lệnh (OrderPlan)**
|
||||
- `direction`: +1 BUY LIMIT / -1 SELL LIMIT
|
||||
- `entry`, `stopLoss`, `takeProfit`, `lot`
|
||||
7) **Risk sizing**
|
||||
- Lot theo `%risk` và khoảng SL (points).
|
||||
- Chặn trade nếu chạm max daily loss (`InpMaxDailyLossPct`).
|
||||
8) **Gửi pending limit**
|
||||
- Gắn `magic` và comment có `fvg_id` để trace.
|
||||
|
||||
## 3) Các folder/file module (vai trò + khi nào dùng)
|
||||
|
||||
Thư mục: `Experts/EA_ICT_CL/`
|
||||
|
||||
- **`Config.mqh`**
|
||||
- Nơi đưa `input`, `enum` và hằng số cấu hình.
|
||||
- Khi migrate: chuyển dần `input ...` và các enum khỏi `.mq5` sang đây (rồi include).
|
||||
|
||||
- **`State.mqh`**
|
||||
- Nơi đưa `struct` contexts (bias/trend/fvg/orderplan/daily risk) + state machine.
|
||||
- Khi migrate: chuyển dần các `struct`, `g_*` globals sang đây để module nào cũng dùng chung.
|
||||
|
||||
- **`Utils.mqh`**
|
||||
- Helper thuần: clamp/round/newbar/time formatting…
|
||||
- Ưu tiên migrate các hàm nhỏ, không phụ thuộc EA logic (vd `MyBarShift` cũng có thể đặt vào đây hoặc `Market/Indicators`).
|
||||
|
||||
- **`Logging.mqh`**
|
||||
- Chuẩn hoá log bật/tắt theo `InpDebugLog`.
|
||||
- Có thể mở rộng: prefix theo symbol/tf/state, ghi file, debug panel.
|
||||
|
||||
- **`Market.mqh`**
|
||||
- Helper về symbol specs (digits/point/spread), trade allowed, quote access…
|
||||
|
||||
- **`Sessions.mqh`**
|
||||
- Helper session/time filter (London/NY theo UTC).
|
||||
- Khi migrate: đưa logic “được trade trong giờ nào?” sang đây.
|
||||
|
||||
- **`Filters.mqh`**
|
||||
- Các bộ lọc: spread, news (nếu có), day-of-week, max trades/day, cooldown…
|
||||
|
||||
- **`Indicators.mqh`**
|
||||
- Wrapper CopyBuffer/CopyTime, quản lý handles nếu bạn dùng iMA/iATR/…
|
||||
|
||||
- **`Signals_BOS_FVG_OB.mqh`**
|
||||
- Nơi tập trung **logic tín hiệu**: BOS/MSS/FVG/OB… và build `OrderPlan`.
|
||||
- Mục tiêu: module này “pure” nhất có thể (input/state snapshot → plan).
|
||||
|
||||
- **`Risk.mqh`**
|
||||
- Tính lot, normalize volume, kiểm soát daily drawdown.
|
||||
|
||||
- **`Orders.mqh`**
|
||||
- Chuẩn hoá normalize price, build comment, mapping direction→order type…
|
||||
|
||||
- **`Trade.mqh`**
|
||||
- Các hàm “side-effect”: `OrderSend`, modify, cancel, close.
|
||||
|
||||
- **`Trailing.mqh`**
|
||||
- Trailing/BE/partial close (nếu có).
|
||||
|
||||
## 4) Gợi ý thứ tự migrate (an toàn, ít rủi ro nhất)
|
||||
|
||||
Để tránh trùng định nghĩa và lỗi include:
|
||||
|
||||
1) **Move helpers**: `MyBarShift`, swing helper functions → `Utils.mqh` (hoặc tách `Swing.mqh` nếu bạn muốn).
|
||||
2) **Move trade wrappers**: các đoạn `MqlTradeRequest/Result` → `Trade.mqh` + helper normalize → `Orders.mqh`.
|
||||
3) **Move risk**: tính lot / daily loss → `Risk.mqh`.
|
||||
4) **Move filters/session** → `Sessions.mqh`, `Filters.mqh`.
|
||||
5) **Cuối cùng mới move types**: enums/struct/global sang `Config.mqh` + `State.mqh`.
|
||||
|
||||
> Lưu ý: Chỉ include module nào khi bạn đã chuyển phần tương ứng khỏi `.mq5` (để tránh “redefinition”).
|
||||
|
||||
## 5) Quy ước include trong `EA_ICT_CL.mq5` (gợi ý)
|
||||
|
||||
Khi bắt đầu migrate, thường include theo thứ tự:
|
||||
|
||||
1. `Config.mqh`
|
||||
2. `State.mqh`
|
||||
3. `Utils.mqh`
|
||||
4. `Logging.mqh`
|
||||
5. `Market.mqh`
|
||||
6. `Indicators.mqh`
|
||||
7. `Sessions.mqh` / `Filters.mqh`
|
||||
8. `Risk.mqh`
|
||||
9. `Orders.mqh`
|
||||
10. `Trade.mqh`
|
||||
11. `Signals_BOS_FVG_OB.mqh`
|
||||
12. `Trailing.mqh`
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef EA_ICT_CL__RISK_MQH
|
||||
#define EA_ICT_CL__RISK_MQH
|
||||
|
||||
// Module: Risk
|
||||
// Put risk sizing and daily loss logic here.
|
||||
|
||||
inline double NormalizeVolume(const string symbol, const double vol)
|
||||
{
|
||||
const double vmin = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
const double vmax = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
const double vstep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
double v = vol;
|
||||
if (vstep > 0.0) v = MathFloor(v / vstep) * vstep;
|
||||
if (v < vmin) v = vmin;
|
||||
if (v > vmax) v = vmax;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline double LotsFromRiskMoneyAndSLPoints(const string symbol, const double risk_money, const double sl_points)
|
||||
{
|
||||
if (risk_money <= 0.0) return 0.0;
|
||||
if (sl_points <= 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;
|
||||
if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tick_size)) return 0.0;
|
||||
if (tick_value <= 0.0 || tick_size <= 0.0) return 0.0;
|
||||
|
||||
const double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
if (point <= 0.0) return 0.0;
|
||||
|
||||
// Money per 1 lot per point move:
|
||||
const double money_per_point_1lot = tick_value * (point / tick_size);
|
||||
if (money_per_point_1lot <= 0.0) return 0.0;
|
||||
|
||||
const double lots = risk_money / (sl_points * money_per_point_1lot);
|
||||
return NormalizeVolume(symbol, lots);
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__RISK_MQH
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef EA_ICT_CL__SESSIONS_MQH
|
||||
#define EA_ICT_CL__SESSIONS_MQH
|
||||
|
||||
// Module: Sessions
|
||||
// Time/session filters live here (UTC-based if your EA uses UTC inputs).
|
||||
|
||||
inline bool IsHourInRange(const int hour, const int start_hour, const int end_hour)
|
||||
{
|
||||
// Handles normal and overnight windows:
|
||||
// - start < end: [start, end)
|
||||
// - start > end: [start, 24) U [0, end)
|
||||
if (start_hour == end_hour) return true;
|
||||
if (start_hour < end_hour) return (hour >= start_hour && hour < end_hour);
|
||||
return (hour >= start_hour || hour < end_hour);
|
||||
}
|
||||
|
||||
inline int GetUTCHour(const datetime t)
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(t, dt);
|
||||
return dt.hour;
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__SESSIONS_MQH
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
#ifndef EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH
|
||||
#define EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH
|
||||
|
||||
// Module: Signals (FVG)
|
||||
// Extracted from EA_ICT_CL.mq5 (Sections 5, 6, 7, 8).
|
||||
// FVG helpers, scan/register, status update, best selector.
|
||||
// NOTE: Uses EA globals (g_*) and inputs. Include AFTER globals exist.
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 5 – FVG HELPERS |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline bool IsCandleStrong(ENUM_TIMEFRAMES tf, int i)
|
||||
{
|
||||
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;
|
||||
return (MathAbs(c - o) / range * 100.0) >= InpFVGMinBodyPct;
|
||||
}
|
||||
|
||||
inline bool IsFVGInPool(datetime created)
|
||||
{
|
||||
for (int j = 0; j < g_FVGCount; j++)
|
||||
if (g_FVGPool[j].createdTime == created) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 6 – FVG POOL: SCAN & REGISTER |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline void ScanAndRegisterFVGs()
|
||||
{
|
||||
static datetime s_lastScan = 0;
|
||||
datetime t0 = iTime(_Symbol, InpMiddleTF, 0);
|
||||
if (t0 == s_lastScan) return;
|
||||
s_lastScan = t0;
|
||||
|
||||
MarketDir dir = g_MiddleTrend.trend;
|
||||
if (dir == DIR_NONE) return;
|
||||
|
||||
int maxBar = MathMin(InpFVGScanBars, Bars(_Symbol, InpMiddleTF) - 2);
|
||||
|
||||
for (int i = 2; i <= maxBar; 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;
|
||||
|
||||
if (dir == DIR_UP)
|
||||
{
|
||||
if (leftH >= rightL) continue;
|
||||
if (midC <= midO) continue;
|
||||
if (!IsCandleStrong(InpMiddleTF, i)) continue;
|
||||
gL = leftH; gH = rightL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (leftL <= rightH) continue;
|
||||
if (midC >= midO) continue;
|
||||
if (!IsCandleStrong(InpMiddleTF, i)) continue;
|
||||
gH = leftL; gL = rightH;
|
||||
}
|
||||
|
||||
datetime created = iTime(_Symbol, InpMiddleTF, i - 1);
|
||||
if (IsFVGInPool(created)) continue;
|
||||
|
||||
if (g_FVGCount >= MAX_FVG_POOL)
|
||||
{
|
||||
int evict = -1; datetime oldest = 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];
|
||||
g_FVGCount--;
|
||||
if (g_ActiveFVGIdx > evict) g_ActiveFVGIdx--;
|
||||
else if (g_ActiveFVGIdx == evict) 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;
|
||||
|
||||
bool c1Hit = false; datetime c1T = 0;
|
||||
for (int j = rightBar - 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; }
|
||||
}
|
||||
if (c1Hit) { rec.status = FVG_USED; rec.usedCase = 1; rec.usedTime = c1T; }
|
||||
else
|
||||
{
|
||||
bool tdHit = false; datetime tdT = 0;
|
||||
for (int j = rightBar - 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; }
|
||||
}
|
||||
if (tdHit) { rec.status = FVG_TOUCHED; rec.touchTime = tdT; rec.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(); }
|
||||
}
|
||||
}
|
||||
|
||||
g_FVGPool[g_FVGCount] = rec;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 7 – FVG POOL: UPDATE STATUSES (every tick) |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline void UpdateFVGStatuses()
|
||||
{
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double midC1 = iClose(_Symbol, InpMiddleTF, 1);
|
||||
datetime midT1 = 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)
|
||||
{
|
||||
g_FVGPool[i].status = FVG_USED;
|
||||
g_FVGPool[i].usedCase = 1;
|
||||
g_FVGPool[i].usedTime = midT1;
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[FVG #%d] BROKEN | close=%.5f", g_FVGPool[i].id, midC1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g_FVGPool[i].status == FVG_PENDING)
|
||||
{
|
||||
int age = (int)(TimeCurrent() - g_FVGPool[i].createdTime);
|
||||
if (age > InpFVGMaxAliveMin * 60)
|
||||
{
|
||||
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)
|
||||
{
|
||||
g_FVGPool[i].status = FVG_TOUCHED;
|
||||
g_FVGPool[i].touchTime = TimeCurrent();
|
||||
g_FVGPool[i].triggerTrendAtTouch = g_TriggerTrend.trend;
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[FVG #%d] TOUCHED | bid=%.5f [%.5f–%.5f]",
|
||||
g_FVGPool[i].id, bid, g_FVGPool[i].low, g_FVGPool[i].high);
|
||||
}
|
||||
}
|
||||
else if (g_FVGPool[i].status == FVG_TOUCHED)
|
||||
{
|
||||
bool hasMSS =
|
||||
g_TriggerTrend.lastMssTime > g_FVGPool[i].touchTime &&
|
||||
g_TriggerTrend.lastMssBreak == g_FVGPool[i].direction;
|
||||
|
||||
if (hasMSS)
|
||||
{
|
||||
g_FVGPool[i].status = FVG_USED;
|
||||
g_FVGPool[i].usedCase = 2;
|
||||
g_FVGPool[i].usedTime = TimeCurrent();
|
||||
g_FVGPool[i].mssTime = g_TriggerTrend.lastMssTime;
|
||||
g_FVGPool[i].mssEntry = g_TriggerTrend.lastMssLevel;
|
||||
g_FVGPool[i].mssSL = g_TriggerTrend.mssSLSwing;
|
||||
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[FVG #%d] TRIGGERED | MSS %s entry=%.5f SL=%.5f @ %s",
|
||||
g_FVGPool[i].id,
|
||||
(g_TriggerTrend.lastMssBreak == DIR_UP) ? "▲" : "▼",
|
||||
g_FVGPool[i].mssEntry, g_FVGPool[i].mssSL,
|
||||
TimeToString(g_FVGPool[i].mssTime, TIME_MINUTES));
|
||||
continue;
|
||||
}
|
||||
|
||||
int ageMin = (int)((TimeCurrent() - g_FVGPool[i].createdTime) / 60);
|
||||
if (ageMin > InpFVGMaxAliveMin)
|
||||
{
|
||||
g_FVGPool[i].status = FVG_USED;
|
||||
g_FVGPool[i].usedCase = 0;
|
||||
g_FVGPool[i].usedTime = TimeCurrent();
|
||||
|
||||
if (g_ActiveFVGIdx >= 0
|
||||
&& g_FVGPool[g_ActiveFVGIdx].id == g_FVGPool[i].id)
|
||||
g_ActiveFVGIdx = -1;
|
||||
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[FVG #%d] TOUCHED EXPIRED (age=%dmin > %d) → USED",
|
||||
g_FVGPool[i].id, ageMin, InpFVGMaxAliveMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 8 – BEST FVG SELECTOR |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline int GetBestActiveFVGIdx()
|
||||
{
|
||||
int bestIdx = -1; datetime bestTime = 0; bool foundTouch = 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; }
|
||||
}
|
||||
else if (!foundTouch && g_FVGPool[i].createdTime > bestTime)
|
||||
{ bestIdx = i; bestTime = g_FVGPool[i].createdTime; }
|
||||
}
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH
|
||||
@@ -0,0 +1,89 @@
|
||||
#ifndef EA_ICT_CL__STATE_MQH
|
||||
#define EA_ICT_CL__STATE_MQH
|
||||
|
||||
// Module: State
|
||||
// Core EA types: enums + structs used across all modules.
|
||||
// Extracted from EA_ICT_CL.mq5 (enums + structs sections).
|
||||
|
||||
//====================================================
|
||||
// ENUMS
|
||||
//====================================================
|
||||
enum EAState
|
||||
{
|
||||
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 };
|
||||
enum MarketDir { DIR_NONE=0, DIR_UP=1, DIR_DOWN=-1 };
|
||||
enum BlockReason
|
||||
{
|
||||
BLOCK_NONE, BLOCK_SESSION, BLOCK_DAILY_LOSS,
|
||||
BLOCK_BIAS_MISMATCH, BLOCK_NO_BIAS
|
||||
};
|
||||
enum FVGStatus
|
||||
{
|
||||
FVG_PENDING,
|
||||
FVG_TOUCHED,
|
||||
FVG_USED
|
||||
};
|
||||
|
||||
//====================================================
|
||||
// STRUCTS
|
||||
//====================================================
|
||||
struct BiasContext
|
||||
{
|
||||
HTFBias bias;
|
||||
double rangeHigh, rangeLow;
|
||||
datetime lastBarTime;
|
||||
};
|
||||
|
||||
struct TFTrendContext
|
||||
{
|
||||
MarketDir trend;
|
||||
double h0, h1, l0, l1;
|
||||
int idxH0, idxH1, idxL0, idxL1;
|
||||
double keyLevel;
|
||||
datetime lastBarTime;
|
||||
|
||||
datetime lastMssTime;
|
||||
double lastMssLevel;
|
||||
MarketDir lastMssBreak;
|
||||
double mssSLSwing;
|
||||
};
|
||||
|
||||
struct FVGRecord
|
||||
{
|
||||
int id;
|
||||
FVGStatus status;
|
||||
int usedCase;
|
||||
MarketDir direction;
|
||||
double high, low, mid;
|
||||
datetime createdTime;
|
||||
datetime touchTime;
|
||||
datetime usedTime;
|
||||
MarketDir triggerTrendAtTouch;
|
||||
|
||||
datetime mssTime;
|
||||
double mssEntry;
|
||||
double mssSL;
|
||||
};
|
||||
|
||||
struct OrderPlan
|
||||
{
|
||||
bool valid;
|
||||
int direction;
|
||||
double entry, stopLoss, takeProfit, lot;
|
||||
int parentFVGId;
|
||||
};
|
||||
|
||||
struct DailyRiskContext
|
||||
{
|
||||
double startBalance, currentBalance;
|
||||
datetime dayStartTime;
|
||||
bool limitHit;
|
||||
};
|
||||
|
||||
#endif // EA_ICT_CL__STATE_MQH
|
||||
@@ -0,0 +1,203 @@
|
||||
#ifndef EA_ICT_CL__STATE_MACHINE_MQH
|
||||
#define EA_ICT_CL__STATE_MACHINE_MQH
|
||||
|
||||
// Module: StateMachine
|
||||
// State transitions + handlers + runner.
|
||||
// Extracted from EA_ICT_CL.mq5 (Sections 4, 9, 10).
|
||||
// NOTE: Uses EA globals + functions from Signals/Trade modules.
|
||||
// Include AFTER Signals_BOS_FVG_OB.mqh and Trade.mqh.
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 4 – STATE MACHINE HELPERS |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline void TransitionTo(EAState next)
|
||||
{
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[STATE] %s → %s", EnumToString(g_State), EnumToString(next));
|
||||
g_State = next;
|
||||
}
|
||||
|
||||
inline void ResetToIdle(string reason = "")
|
||||
{
|
||||
if (InpDebugLog && reason != "")
|
||||
PrintFormat("[RESET→IDLE] %s", reason);
|
||||
g_ActiveFVGIdx = -1;
|
||||
g_TradeBarIndex = -1;
|
||||
g_PendingTicket = 0;
|
||||
ZeroMemory(g_OrderPlan);
|
||||
TransitionTo(EA_IDLE);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 9 – STATE HANDLERS |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline void OnStateIdle()
|
||||
{
|
||||
int idx = GetBestActiveFVGIdx();
|
||||
if (idx < 0) return;
|
||||
g_ActiveFVGIdx = idx;
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[ACTIVE FVG] #%d %s [%.5f–%.5f] %s",
|
||||
g_FVGPool[idx].id, EnumToString(g_FVGPool[idx].direction),
|
||||
g_FVGPool[idx].low, g_FVGPool[idx].high, EnumToString(g_FVGPool[idx].status));
|
||||
TransitionTo(g_FVGPool[idx].status == FVG_TOUCHED ? EA_WAIT_TRIGGER : EA_WAIT_TOUCH);
|
||||
}
|
||||
|
||||
inline void OnStateWaitTouch()
|
||||
{
|
||||
if (g_ActiveFVGIdx < 0) { ResetToIdle("active lost"); return; }
|
||||
int ai = g_ActiveFVGIdx;
|
||||
|
||||
if (g_FVGPool[ai].status == FVG_USED)
|
||||
{ ResetToIdle(StringFormat("FVG #%d broken/expired", g_FVGPool[ai].id)); return; }
|
||||
if (g_FVGPool[ai].status == FVG_TOUCHED)
|
||||
{ TransitionTo(EA_WAIT_TRIGGER); return; }
|
||||
|
||||
int better = GetBestActiveFVGIdx();
|
||||
if (better >= 0 && better != ai)
|
||||
{
|
||||
bool bTouch = (g_FVGPool[better].status == FVG_TOUCHED);
|
||||
bool bNewer = (g_FVGPool[better].createdTime > g_FVGPool[ai].createdTime);
|
||||
if (bTouch || bNewer)
|
||||
{
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[SWITCH] FVG #%d → #%d", g_FVGPool[ai].id, g_FVGPool[better].id);
|
||||
g_ActiveFVGIdx = better;
|
||||
if (bTouch) TransitionTo(EA_WAIT_TRIGGER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void OnStateWaitTrigger()
|
||||
{
|
||||
if (g_ActiveFVGIdx < 0) { ResetToIdle("active lost"); return; }
|
||||
int ai = g_ActiveFVGIdx;
|
||||
|
||||
if (g_FVGPool[ai].status == FVG_USED)
|
||||
{
|
||||
if (g_FVGPool[ai].usedCase == 2)
|
||||
{
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[ENTRY SIGNAL] FVG #%d %s [%.5f–%.5f] | MSS entry=%.5f SL=%.5f",
|
||||
g_FVGPool[ai].id, EnumToString(g_FVGPool[ai].direction),
|
||||
g_FVGPool[ai].low, g_FVGPool[ai].high,
|
||||
g_FVGPool[ai].mssEntry, g_FVGPool[ai].mssSL);
|
||||
|
||||
if (BuildOrderPlan(g_FVGPool[ai].id, g_FVGPool[ai].direction,
|
||||
g_FVGPool[ai].mssEntry, g_FVGPool[ai].mssSL))
|
||||
{
|
||||
ulong ticket = ExecuteLimitOrder();
|
||||
if (ticket > 0)
|
||||
{
|
||||
g_PendingTicket = ticket;
|
||||
g_TradeBarIndex = Bars(_Symbol, InpTriggerTF);
|
||||
TransitionTo(EA_IN_TRADE);
|
||||
}
|
||||
else
|
||||
ResetToIdle(StringFormat("FVG #%d order failed", g_FVGPool[ai].id));
|
||||
}
|
||||
else
|
||||
ResetToIdle(StringFormat("FVG #%d invalid plan", g_FVGPool[ai].id));
|
||||
}
|
||||
else
|
||||
ResetToIdle(StringFormat("FVG #%d case%d", g_FVGPool[ai].id, g_FVGPool[ai].usedCase));
|
||||
return;
|
||||
}
|
||||
if (g_FVGPool[ai].status == FVG_PENDING) { TransitionTo(EA_WAIT_TOUCH); return; }
|
||||
}
|
||||
|
||||
inline void OnStateInTrade()
|
||||
{
|
||||
if (g_PendingTicket > 0)
|
||||
{
|
||||
bool foundPending = false;
|
||||
for (int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if (OrderGetTicket(i) == g_PendingTicket) { foundPending = true; break; }
|
||||
}
|
||||
|
||||
if (!foundPending)
|
||||
{
|
||||
bool posFound = false;
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong pt = PositionGetTicket(i);
|
||||
if (pt > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber
|
||||
&& PositionGetString(POSITION_SYMBOL) == _Symbol)
|
||||
{
|
||||
posFound = true;
|
||||
g_PendingTicket = 0;
|
||||
if (InpDebugLog) PrintFormat("[TRADE] Filled → position #%llu", pt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!posFound)
|
||||
{
|
||||
HistorySelect(TimeCurrent() - 86400, TimeCurrent());
|
||||
|
||||
bool cancelled = false;
|
||||
for (int i = HistoryOrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ht = HistoryOrderGetTicket(i);
|
||||
if (ht == g_PendingTicket)
|
||||
{
|
||||
long st = HistoryOrderGetInteger(ht, ORDER_STATE);
|
||||
if (st == ORDER_STATE_CANCELED || st == ORDER_STATE_EXPIRED || st == ORDER_STATE_REJECTED)
|
||||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cancelled) { ResetToIdle("order cancelled"); return; }
|
||||
|
||||
bool closed = false;
|
||||
for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong dt = HistoryDealGetTicket(i);
|
||||
if (HistoryDealGetInteger(dt, DEAL_MAGIC) == InpMagicNumber
|
||||
&& HistoryDealGetString(dt, DEAL_SYMBOL) == _Symbol
|
||||
&& HistoryDealGetInteger(dt, DEAL_ENTRY) == DEAL_ENTRY_OUT)
|
||||
{
|
||||
double profit = HistoryDealGetDouble(dt, DEAL_PROFIT);
|
||||
if (InpDebugLog) PrintFormat("[TRADE] Closed | profit=%.2f", profit);
|
||||
closed = true; break;
|
||||
}
|
||||
}
|
||||
ResetToIdle(closed ? "trade closed" : "order lost");
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasPos = false;
|
||||
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong pt = PositionGetTicket(i);
|
||||
if (pt > 0 && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber
|
||||
&& PositionGetString(POSITION_SYMBOL) == _Symbol)
|
||||
{ hasPos = true; break; }
|
||||
}
|
||||
if (!hasPos) ResetToIdle("position closed");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| SECTION 10 – STATE MACHINE RUNNER |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
inline void RunStateMachine()
|
||||
{
|
||||
UpdateFVGStatuses();
|
||||
ScanAndRegisterFVGs();
|
||||
switch (g_State)
|
||||
{
|
||||
case EA_IDLE: OnStateIdle(); break;
|
||||
case EA_WAIT_TOUCH: OnStateWaitTouch(); break;
|
||||
case EA_WAIT_TRIGGER: OnStateWaitTrigger(); break;
|
||||
case EA_IN_TRADE: OnStateInTrade(); break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__STATE_MACHINE_MQH
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef EA_ICT_CL__SWING_MQH
|
||||
#define EA_ICT_CL__SWING_MQH
|
||||
|
||||
// Module: Swing
|
||||
// Extracted from EA_ICT_CL.mq5 (Section 1 – Swing helpers).
|
||||
// Depends on:
|
||||
// - InpSwingRange (input)
|
||||
// - MarketDir enum values DIR_UP/DIR_DOWN/DIR_NONE
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| MQL5 HELPER: iBarShift replacement |
|
||||
//+------------------------------------------------------------------+
|
||||
inline int MyBarShift(string symbol, ENUM_TIMEFRAMES tf, datetime time, bool exact = false)
|
||||
{
|
||||
datetime arr[];
|
||||
int maxCopy = MathMin(Bars(symbol, tf), 5000);
|
||||
int copied = CopyTime(symbol, tf, 0, maxCopy, arr);
|
||||
if (copied <= 0) return -1;
|
||||
|
||||
for (int i = copied - 1; i >= 0; i--)
|
||||
{
|
||||
if (arr[i] <= time)
|
||||
return copied - 1 - i;
|
||||
}
|
||||
return exact ? -1 : copied - 1;
|
||||
}
|
||||
|
||||
inline bool IsSwingHighAt(ENUM_TIMEFRAMES tf, int i)
|
||||
{
|
||||
double p = iHigh(_Symbol, tf, i);
|
||||
for (int k = 1; k <= InpSwingRange; k++)
|
||||
if (iHigh(_Symbol, tf, i-k) >= p || iHigh(_Symbol, tf, i+k) >= p) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IsSwingLowAt(ENUM_TIMEFRAMES tf, int i)
|
||||
{
|
||||
double p = iLow(_Symbol, tf, i);
|
||||
for (int k = 1; k <= InpSwingRange; k++)
|
||||
if (iLow(_Symbol, tf, i-k) <= p || iLow(_Symbol, tf, i+k) <= p) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ScanSwingStructure(
|
||||
ENUM_TIMEFRAMES tf, int lookback,
|
||||
double &h0, double &h1, int &idxH0, int &idxH1,
|
||||
double &l0, double &l1, int &idxL0, int &idxL1)
|
||||
{
|
||||
int maxBar = MathMin(lookback, Bars(_Symbol, tf) - InpSwingRange - 2);
|
||||
double highs[2]; int hiIdx[2]; int hc = 0;
|
||||
double lows [2]; int loIdx[2]; int lc = 0;
|
||||
|
||||
for (int i = InpSwingRange + 1; i <= maxBar; i++)
|
||||
{
|
||||
if (hc < 2 && IsSwingHighAt(tf, i)) { highs[hc] = iHigh(_Symbol, tf, i); hiIdx[hc] = i; hc++; }
|
||||
if (lc < 2 && IsSwingLowAt (tf, i)) { lows [lc] = iLow (_Symbol, tf, i); loIdx[lc] = i; lc++; }
|
||||
if (hc == 2 && lc == 2) break;
|
||||
}
|
||||
if (hc < 2 || lc < 2) return false;
|
||||
|
||||
h0 = highs[0]; idxH0 = hiIdx[0];
|
||||
h1 = highs[1]; idxH1 = hiIdx[1];
|
||||
l0 = lows [0]; idxL0 = loIdx[0];
|
||||
l1 = lows [1]; idxL1 = loIdx[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ResolveTrendFromSwings(
|
||||
ENUM_TIMEFRAMES tf,
|
||||
double h0, double h1, double l0, double l1,
|
||||
MarketDir &trend, double &keyLevel)
|
||||
{
|
||||
double c1 = iClose(_Symbol, tf, 1);
|
||||
if (h0 > h1 && l0 > l1 && c1 > l0) { trend = DIR_UP; keyLevel = l0; }
|
||||
else if (h0 < h1 && l0 < l1 && c1 < h0) { trend = DIR_DOWN; keyLevel = h0; }
|
||||
else { trend = DIR_NONE; keyLevel = 0; }
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__SWING_MQH
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
#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.
|
||||
|
||||
inline double CalcLotFromRisk(double entry, double sl)
|
||||
{
|
||||
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);
|
||||
if (tickValue <= 0 || tickSize <= 0) return 0;
|
||||
|
||||
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);
|
||||
if (lotStep <= 0) lotStep = 0.01;
|
||||
|
||||
rawLot = MathFloor(rawLot / lotStep) * lotStep;
|
||||
rawLot = MathMax(minLot, MathMin(maxLot, rawLot));
|
||||
return NormalizeDouble(rawLot, 2);
|
||||
}
|
||||
|
||||
inline bool BuildOrderPlan(int fvgId, MarketDir dir, double mssEntry, double mssSL)
|
||||
{
|
||||
ZeroMemory(g_OrderPlan);
|
||||
|
||||
double entry = NormalizeDouble(mssEntry, _Digits);
|
||||
double sl = mssSL;
|
||||
|
||||
if (entry <= 0 || sl <= 0) return false;
|
||||
|
||||
double tp;
|
||||
if (dir == DIR_UP)
|
||||
{
|
||||
if (sl >= entry) return false;
|
||||
sl = NormalizeDouble(sl - 2 * _Point, _Digits);
|
||||
double riskDist = entry - sl;
|
||||
tp = NormalizeDouble(entry + InpRiskReward * riskDist, _Digits);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sl <= entry) return false;
|
||||
sl = NormalizeDouble(sl + 2 * _Point, _Digits);
|
||||
double riskDist = sl - entry;
|
||||
tp = NormalizeDouble(entry - InpRiskReward * riskDist, _Digits);
|
||||
}
|
||||
|
||||
double lot = CalcLotFromRisk(entry, sl);
|
||||
if (lot <= 0) return false;
|
||||
|
||||
g_OrderPlan.valid = true;
|
||||
g_OrderPlan.direction = (dir == DIR_UP) ? 1 : -1;
|
||||
g_OrderPlan.entry = entry;
|
||||
g_OrderPlan.stopLoss = sl;
|
||||
g_OrderPlan.takeProfit = tp;
|
||||
g_OrderPlan.lot = lot;
|
||||
g_OrderPlan.parentFVGId = fvgId;
|
||||
|
||||
if (InpDebugLog)
|
||||
PrintFormat("[ORDER PLAN] %s | entry=%.5f SL=%.5f TP=%.5f lot=%.2f | FVG#%d",
|
||||
(dir == DIR_UP) ? "BUY LIMIT" : "SELL LIMIT",
|
||||
entry, sl, tp, lot, fvgId);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if (cmd == 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 (InpDebugLog) PrintFormat("[ORDER] SELL LIMIT entry %.5f <= bid %.5f → skip",
|
||||
g_OrderPlan.entry, bid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
MqlTradeRequest request;
|
||||
MqlTradeResult result;
|
||||
ZeroMemory(request);
|
||||
ZeroMemory(result);
|
||||
|
||||
request.action = TRADE_ACTION_PENDING;
|
||||
request.symbol = _Symbol;
|
||||
request.volume = g_OrderPlan.lot;
|
||||
request.type = cmd;
|
||||
request.price = g_OrderPlan.entry;
|
||||
request.sl = g_OrderPlan.stopLoss;
|
||||
request.tp = g_OrderPlan.takeProfit;
|
||||
request.deviation = (ulong)InpSlippage;
|
||||
request.magic = InpMagicNumber;
|
||||
request.comment = StringFormat("ICT#%d", g_OrderPlan.parentFVGId);
|
||||
request.type_filling = ORDER_FILLING_RETURN;
|
||||
request.type_time = ORDER_TIME_GTC;
|
||||
|
||||
if (!OrderSend(request, result))
|
||||
{
|
||||
PrintFormat("[ORDER] ❌ retcode=%u", result.retcode);
|
||||
return 0;
|
||||
}
|
||||
if (result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_PLACED)
|
||||
{
|
||||
PrintFormat("[ORDER] ❌ rejected retcode=%u: %s", result.retcode, result.comment);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrintFormat("[ORDER] ✅ %s #%llu | %.2f @ %.5f SL=%.5f TP=%.5f",
|
||||
(cmd == 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
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef EA_ICT_CL__TRAILING_MQH
|
||||
#define EA_ICT_CL__TRAILING_MQH
|
||||
|
||||
// Module: Trailing
|
||||
// Put trailing/BE/partial-close logic here once you migrate it.
|
||||
|
||||
#endif // EA_ICT_CL__TRAILING_MQH
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef EA_ICT_CL__UTILS_MQH
|
||||
#define EA_ICT_CL__UTILS_MQH
|
||||
|
||||
// Module: Utils
|
||||
// Put small, dependency-free helpers here (time, rounding, clamps, formatting).
|
||||
|
||||
inline double ClampDouble(const double v, const double lo, const double hi)
|
||||
{
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline int ClampInt(const int v, const int lo, const int hi)
|
||||
{
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline double RoundToStep(const double value, const double 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)
|
||||
{
|
||||
return (current_bar_time != 0 && current_bar_time != last_bar_time);
|
||||
}
|
||||
|
||||
#endif // EA_ICT_CL__UTILS_MQH
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user