Files
Bell-PriceActionWithEma-EA/Experts/EA_ICT_CL/Signals_BOS_FVG_OB.mqh
T

214 lines
7.7 KiB
Plaintext
Raw Normal View History

2026-03-10 13:47:42 +07:00
#ifndef EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH
2026-03-10 20:39:33 +07:00
#define EA_ICT_CL__SIGNALS_BOS_FVG_OB_MQH
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
/** True if candle at bar index has body % of range >= InpFVGMinBodyPct. */
inline bool IsCandleStrong(ENUM_TIMEFRAMES tf, int barIndex)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:39:33 +07:00
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;
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
/** True if an FVG with given created time already exists in pool. */
inline bool IsFVGInPool(datetime createdTime)
2026-03-10 13:47:42 +07:00
{
for (int j = 0; j < g_FVGCount; j++)
2026-03-10 20:39:33 +07:00
if (g_FVGPool[j].createdTime == createdTime) return true;
2026-03-10 13:47:42 +07:00
return false;
}
2026-03-10 20:39:33 +07:00
/** Scans middle TF for FVGs and registers them into g_FVGPool (once per new bar). */
2026-03-10 13:47:42 +07:00
inline void ScanAndRegisterFVGs()
{
2026-03-10 20:39:33 +07:00
static datetime lastScanBarTime = 0;
datetime currentBarTime = iTime(_Symbol, InpMiddleTF, 0);
if (currentBarTime == lastScanBarTime) return;
lastScanBarTime = currentBarTime;
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
MarketDir trendDir = g_MiddleTrend.trend;
if (trendDir == DIR_NONE) return;
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
int maxBarIndex = MathMin(InpFVGScanBars, Bars(_Symbol, InpMiddleTF) - 2);
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
for (int i = 2; i <= maxBarIndex; i++)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:39:33 +07:00
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;
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
if (trendDir == DIR_UP)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:39:33 +07:00
if (leftBarHigh >= rightBarLow) continue;
if (midClose <= midOpen) continue;
2026-03-10 13:47:42 +07:00
if (!IsCandleStrong(InpMiddleTF, i)) continue;
2026-03-10 20:39:33 +07:00
gapLow = leftBarHigh;
gapHigh = rightBarLow;
2026-03-10 13:47:42 +07:00
}
else
{
2026-03-10 20:39:33 +07:00
if (leftBarLow <= rightBarHigh) continue;
if (midClose >= midOpen) continue;
2026-03-10 13:47:42 +07:00
if (!IsCandleStrong(InpMiddleTF, i)) continue;
2026-03-10 20:39:33 +07:00
gapHigh = leftBarLow;
gapLow = rightBarHigh;
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
datetime createdTime = iTime(_Symbol, InpMiddleTF, i - 1);
if (IsFVGInPool(createdTime)) continue;
2026-03-10 13:47:42 +07:00
if (g_FVGCount >= MAX_FVG_POOL)
{
2026-03-10 20:39:33 +07:00
int evictIndex = -1;
datetime oldestTime = TimeCurrent();
2026-03-10 13:47:42 +07:00
for (int j = 0; j < g_FVGCount; j++)
2026-03-10 20:39:33 +07:00
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];
2026-03-10 13:47:42 +07:00
g_FVGCount--;
2026-03-10 20:39:33 +07:00
if (g_ActiveFVGIdx > evictIndex) g_ActiveFVGIdx--;
else if (g_ActiveFVGIdx == evictIndex) g_ActiveFVGIdx = -1;
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
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;
2026-03-10 13:47:42 +07:00
2026-03-10 20:39:33 +07:00
bool isBrokenByClose = false;
datetime breakTime = 0;
for (int j = rightBarIndex - 1; j >= 1; j--)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:39:33 +07:00
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; }
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
if (isBrokenByClose) { record.status = FVG_USED; record.usedCase = 1; record.usedTime = breakTime; }
2026-03-10 13:47:42 +07:00
else
{
2026-03-10 23:43:41 +07:00
record.status = FVG_PENDING;
if ((int)(TimeCurrent() - record.createdTime) > InpFVGMaxAliveMin * 60)
{ record.status = FVG_USED; record.usedCase = 0; record.usedTime = TimeCurrent(); }
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
g_FVGPool[g_FVGCount] = record;
2026-03-10 13:47:42 +07:00
g_FVGCount++;
if (InpDebugLog)
PrintFormat("[FVG +] #%d %s [%.5f%.5f] %s | %s",
2026-03-10 20:39:33 +07:00
record.id, EnumToString(record.direction), record.low, record.high,
EnumToString(record.status), TimeToString(record.createdTime));
2026-03-10 13:47:42 +07:00
}
}
2026-03-10 22:21:59 +07:00
/** Updates status of each FVG in pool (broken, touched, MSS-triggered). TOUCHED chỉ chuyển USED khi broke hoặc MSS, không expire. */
2026-03-10 13:47:42 +07:00
inline void UpdateFVGStatuses()
{
2026-03-10 20:39:33 +07:00
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double lastBarClose = iClose(_Symbol, InpMiddleTF, 1);
datetime lastBarTime = iTime (_Symbol, InpMiddleTF, 1);
2026-03-10 13:47:42 +07:00
for (int i = 0; i < g_FVGCount; i++)
{
if (g_FVGPool[i].status == FVG_USED) continue;
2026-03-10 20:39:33 +07:00
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)
2026-03-10 13:47:42 +07:00
{
g_FVGPool[i].status = FVG_USED;
g_FVGPool[i].usedCase = 1;
2026-03-10 20:39:33 +07:00
g_FVGPool[i].usedTime = lastBarTime;
2026-03-10 13:47:42 +07:00
if (InpDebugLog)
2026-03-10 20:39:33 +07:00
PrintFormat("[FVG #%d] BROKEN | close=%.5f", g_FVGPool[i].id, lastBarClose);
2026-03-10 13:47:42 +07:00
continue;
}
if (g_FVGPool[i].status == FVG_PENDING)
{
2026-03-10 20:39:33 +07:00
int ageSeconds = (int)(TimeCurrent() - g_FVGPool[i].createdTime);
if (ageSeconds > InpFVGMaxAliveMin * 60)
2026-03-10 13:47:42 +07:00
{
2026-03-10 20:39:33 +07:00
g_FVGPool[i].status = FVG_USED;
g_FVGPool[i].usedCase = 0;
2026-03-10 13:47:42 +07:00
g_FVGPool[i].usedTime = TimeCurrent();
continue;
}
2026-03-10 23:43:41 +07:00
double fvgRange = g_FVGPool[i].high - g_FVGPool[i].low;
double touchDepth = fvgRange * InpFVGTouchPct / 100.0;
bool isTouched = (g_FVGPool[i].direction == DIR_UP && bid <= g_FVGPool[i].high - touchDepth) ||
(g_FVGPool[i].direction == DIR_DOWN && bid >= g_FVGPool[i].low + touchDepth);
2026-03-10 20:39:33 +07:00
if (isTouched)
2026-03-10 13:47:42 +07:00
{
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)
{
2026-03-10 20:39:33 +07:00
bool hasMssAfterTouch =
2026-03-10 13:47:42 +07:00
g_TriggerTrend.lastMssTime > g_FVGPool[i].touchTime &&
g_TriggerTrend.lastMssBreak == g_FVGPool[i].direction;
2026-03-10 20:39:33 +07:00
if (hasMssAfterTouch)
2026-03-10 13:47:42 +07:00
{
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));
}
2026-03-10 22:21:59 +07:00
// TOUCHED chỉ kết thúc khi BROKE (usedCase 1) hoặc MSS (usedCase 2); không expire theo thời gian.
2026-03-10 13:47:42 +07:00
}
}
}
2026-03-10 20:39:33 +07:00
/** Returns index of best active FVG (prefer TOUCHED, then newest by created time). */
2026-03-10 13:47:42 +07:00
inline int GetBestActiveFVGIdx()
{
2026-03-10 20:39:33 +07:00
int bestIndex = -1;
datetime bestCreatedTime = 0;
bool foundTouched = false;
2026-03-10 13:47:42 +07:00
for (int i = 0; i < g_FVGCount; i++)
{
if (g_FVGPool[i].status == FVG_USED) continue;
if (g_FVGPool[i].status == FVG_TOUCHED)
{
2026-03-10 20:39:33 +07:00
if (!foundTouched || g_FVGPool[i].createdTime > bestCreatedTime)
{ foundTouched = true; bestIndex = i; bestCreatedTime = g_FVGPool[i].createdTime; }
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
else if (!foundTouched && g_FVGPool[i].createdTime > bestCreatedTime)
{ bestIndex = i; bestCreatedTime = g_FVGPool[i].createdTime; }
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
return bestIndex;
2026-03-10 13:47:42 +07:00
}
2026-03-10 20:39:33 +07:00
#endif