2026-03-11 16:06:24 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| FVG.mqh – Step 2: Fair Value Gap detection & management |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| 3-candle pattern: |
|
|
|
|
|
|
//| CandleA (oldest) ── CandleB (impulse) ── CandleC (newest) |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| Bullish FVG: CandleA.High < CandleC.Low → gap UP |
|
|
|
|
|
|
//| zone = [CandleA.High .. CandleC.Low] |
|
|
|
|
|
|
//| CandleB must be bullish with strong body |
|
|
|
|
|
|
//| |
|
|
|
|
|
|
//| Bearish FVG: CandleA.Low > CandleC.High → gap DOWN |
|
|
|
|
|
|
//| zone = [CandleC.High .. CandleA.Low] |
|
|
|
|
|
|
//| CandleB must be bearish with strong body |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
#ifndef __SIMPLE_FVG_FVG_MQH__
|
|
|
|
|
|
#define __SIMPLE_FVG_FVG_MQH__
|
|
|
|
|
|
|
|
|
|
|
|
#include "Config.mqh"
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| FVG data structure |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
struct FVGZone
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
ENUM_FVG_TYPE type;
|
|
|
|
|
|
ENUM_FVG_STATUS status;
|
|
|
|
|
|
double upperEdge;
|
|
|
|
|
|
double lowerEdge;
|
|
|
|
|
|
double slReferencePrice;
|
|
|
|
|
|
datetime createdTime;
|
|
|
|
|
|
int ageInBars;
|
2026-05-09 23:16:50 +07:00
|
|
|
|
bool tradeLocked; // true after one order attempt is placed from this zone
|
|
|
|
|
|
ulong linkedOrderTicket;
|
|
|
|
|
|
datetime tradeLockedTime;
|
2026-03-11 16:06:24 +07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Module state |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
FVGZone g_FVGZones[];
|
|
|
|
|
|
int g_FVGCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Init: allocate array |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void FVGInit()
|
|
|
|
|
|
{
|
|
|
|
|
|
ArrayResize(g_FVGZones, MAX_FVG_SLOTS);
|
|
|
|
|
|
for(int i = 0; i < MAX_FVG_SLOTS; i++)
|
|
|
|
|
|
ZeroMemory(g_FVGZones[i]);
|
|
|
|
|
|
g_FVGCount = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-03-11 18:13:27 +07:00
|
|
|
|
//| Helper: status helpers & impulse strength |
|
2026-03-11 16:06:24 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
2026-03-11 18:13:27 +07:00
|
|
|
|
bool IsZoneActive(const FVGZone &z)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (z.status != EXPIRED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool IsZoneLive(const FVGZone &z)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (z.status == ACTIVE || z.status == TOUCHED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool IsZoneMitigated(const FVGZone &z)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (z.status == MITIGATED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool IsZoneTouched(const FVGZone &z)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (z.status == TOUCHED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool IsZoneExpired(const FVGZone &z)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (z.status == EXPIRED);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Impulse candle strength: body/range >= threshold
|
2026-03-11 16:06:24 +07:00
|
|
|
|
bool IsImpulseCandleStrong(string symbol, ENUM_TIMEFRAMES tf, int shift)
|
|
|
|
|
|
{
|
|
|
|
|
|
double high = iHigh(symbol, tf, shift);
|
|
|
|
|
|
double low = iLow(symbol, tf, shift);
|
|
|
|
|
|
double open = iOpen(symbol, tf, shift);
|
|
|
|
|
|
double close = iClose(symbol, tf, shift);
|
|
|
|
|
|
|
|
|
|
|
|
double totalRange = high - low;
|
|
|
|
|
|
if(totalRange < _Point)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
double bodySize = MathAbs(close - open);
|
|
|
|
|
|
double bodyRatio = bodySize / totalRange * 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
return (bodyRatio >= InpFVGMinBodyPct);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-03-11 18:13:27 +07:00
|
|
|
|
//| Check if this FVG timestamp already exists in our array |
|
2026-03-11 16:06:24 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
bool FVGAlreadyTracked(datetime fvgTime, ENUM_FVG_TYPE fvgType)
|
|
|
|
|
|
{
|
|
|
|
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(IsZoneActive(g_FVGZones[i])
|
2026-03-11 16:06:24 +07:00
|
|
|
|
&& g_FVGZones[i].createdTime == fvgTime
|
|
|
|
|
|
&& g_FVGZones[i].type == fvgType)
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-03-11 18:13:27 +07:00
|
|
|
|
//| Add a new FVG zone to the array |
|
2026-03-11 16:06:24 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
2026-03-11 18:13:27 +07:00
|
|
|
|
bool AddFVGZone(ENUM_FVG_TYPE type,
|
|
|
|
|
|
double upper,
|
|
|
|
|
|
double lower,
|
|
|
|
|
|
double slReference,
|
|
|
|
|
|
datetime time)
|
2026-03-11 16:06:24 +07:00
|
|
|
|
{
|
|
|
|
|
|
if(g_FVGCount >= MAX_FVG_SLOTS)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
if(InpFVGMinSizePoints > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
double gapSizePoints = (upper - lower) / _Point;
|
|
|
|
|
|
if(gapSizePoints < InpFVGMinSizePoints)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ZeroMemory(g_FVGZones[g_FVGCount]);
|
2026-03-11 18:13:27 +07:00
|
|
|
|
g_FVGZones[g_FVGCount].type = type;
|
|
|
|
|
|
g_FVGZones[g_FVGCount].status = ACTIVE;
|
|
|
|
|
|
g_FVGZones[g_FVGCount].upperEdge = upper;
|
|
|
|
|
|
g_FVGZones[g_FVGCount].lowerEdge = lower;
|
|
|
|
|
|
g_FVGZones[g_FVGCount].slReferencePrice = slReference;
|
|
|
|
|
|
g_FVGZones[g_FVGCount].createdTime = time;
|
|
|
|
|
|
g_FVGZones[g_FVGCount].ageInBars = 0;
|
2026-03-11 16:06:24 +07:00
|
|
|
|
g_FVGCount++;
|
|
|
|
|
|
|
|
|
|
|
|
if(InpDebugLog)
|
|
|
|
|
|
PrintFormat("[FVG] NEW %s [%.5f – %.5f] at %s",
|
|
|
|
|
|
(type == FVG_BULLISH) ? "BULL" : "BEAR",
|
|
|
|
|
|
lower, upper, TimeToString(time));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Scan for new FVG patterns across the lookback range |
|
|
|
|
|
|
//| Finds ALL FVGs regardless of current trend |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void ScanForNewFVGs()
|
|
|
|
|
|
{
|
|
|
|
|
|
string symbol = GetTradeSymbol();
|
|
|
|
|
|
int totalBars = Bars(symbol, InpTimeframe);
|
|
|
|
|
|
int maxShift = MathMin(InpFVGLookbackBars, totalBars - 3);
|
|
|
|
|
|
|
|
|
|
|
|
for(int shift = 1; shift <= maxShift; shift++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int shiftA = shift + 2; // oldest candle
|
|
|
|
|
|
int shiftB = shift + 1; // impulse (middle) candle
|
|
|
|
|
|
int shiftC = shift; // newest candle
|
|
|
|
|
|
|
|
|
|
|
|
double candleA_High = iHigh(symbol, InpTimeframe, shiftA);
|
|
|
|
|
|
double candleA_Low = iLow (symbol, InpTimeframe, shiftA);
|
|
|
|
|
|
|
2026-03-11 19:08:21 +07:00
|
|
|
|
double candleB_High = iHigh(symbol, InpTimeframe, shiftB);
|
|
|
|
|
|
double candleB_Low = iLow (symbol, InpTimeframe, shiftB);
|
2026-03-11 16:06:24 +07:00
|
|
|
|
double candleB_Open = iOpen (symbol, InpTimeframe, shiftB);
|
|
|
|
|
|
double candleB_Close = iClose(symbol, InpTimeframe, shiftB);
|
|
|
|
|
|
|
|
|
|
|
|
double candleC_High = iHigh(symbol, InpTimeframe, shiftC);
|
|
|
|
|
|
double candleC_Low = iLow (symbol, InpTimeframe, shiftC);
|
|
|
|
|
|
|
|
|
|
|
|
datetime fvgTime = iTime(symbol, InpTimeframe, shiftC);
|
|
|
|
|
|
|
2026-03-11 19:08:21 +07:00
|
|
|
|
double rangeA = candleA_High - candleA_Low;
|
|
|
|
|
|
double rangeB = candleB_High - candleB_Low;
|
|
|
|
|
|
double rangeC = candleC_High - candleC_Low;
|
2026-03-11 19:34:20 +07:00
|
|
|
|
double bodyB = MathAbs(candleB_Close - candleB_Open);
|
2026-03-11 19:08:21 +07:00
|
|
|
|
|
|
|
|
|
|
if(rangeB <= 0)
|
|
|
|
|
|
continue;
|
2026-03-11 19:34:20 +07:00
|
|
|
|
if(bodyB <= 0)
|
|
|
|
|
|
continue;
|
2026-03-11 19:08:21 +07:00
|
|
|
|
|
|
|
|
|
|
double maxOuterRatio = InpFVGMaxOuterBarRatio;
|
|
|
|
|
|
if(rangeA > maxOuterRatio * rangeB || rangeC > maxOuterRatio * rangeB)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-03-11 19:34:20 +07:00
|
|
|
|
double minGapVsBody = InpFVGMinGapVsImpulsePct / 100.0;
|
2026-03-11 19:08:21 +07:00
|
|
|
|
|
2026-03-11 16:06:24 +07:00
|
|
|
|
//--- Bullish FVG: gap between A.High and C.Low ---
|
|
|
|
|
|
if(candleA_High < candleC_Low
|
|
|
|
|
|
&& candleB_Close > candleB_Open
|
|
|
|
|
|
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
|
|
|
|
|
|
{
|
2026-03-11 19:08:21 +07:00
|
|
|
|
double gap = candleC_Low - candleA_High;
|
2026-03-11 19:34:20 +07:00
|
|
|
|
double gapRatio = gap / bodyB;
|
2026-03-11 19:08:21 +07:00
|
|
|
|
|
2026-03-11 19:34:20 +07:00
|
|
|
|
if(gapRatio >= minGapVsBody && !FVGAlreadyTracked(fvgTime, FVG_BULLISH))
|
2026-03-11 18:13:27 +07:00
|
|
|
|
{
|
2026-03-11 23:42:37 +07:00
|
|
|
|
double slRef = candleB_Low; // SL dưới bar B (impulse, dưới FVG)
|
2026-03-11 18:13:27 +07:00
|
|
|
|
AddFVGZone(FVG_BULLISH, candleC_Low, candleA_High, slRef, fvgTime);
|
|
|
|
|
|
}
|
2026-03-11 16:06:24 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//--- Bearish FVG: gap between C.High and A.Low ---
|
|
|
|
|
|
if(candleA_Low > candleC_High
|
|
|
|
|
|
&& candleB_Close < candleB_Open
|
|
|
|
|
|
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
|
|
|
|
|
|
{
|
2026-03-11 19:08:21 +07:00
|
|
|
|
double gap = candleA_Low - candleC_High;
|
2026-03-11 19:34:20 +07:00
|
|
|
|
double gapRatio = gap / bodyB;
|
2026-03-11 19:08:21 +07:00
|
|
|
|
|
2026-03-11 19:34:20 +07:00
|
|
|
|
if(gapRatio >= minGapVsBody && !FVGAlreadyTracked(fvgTime, FVG_BEARISH))
|
2026-03-11 18:13:27 +07:00
|
|
|
|
{
|
2026-03-11 23:42:37 +07:00
|
|
|
|
double slRef = candleB_High; // SL trên bar B (impulse, dưới FVG)
|
2026-03-11 18:13:27 +07:00
|
|
|
|
AddFVGZone(FVG_BEARISH, candleA_Low, candleC_High, slRef, fvgTime);
|
|
|
|
|
|
}
|
2026-03-11 16:06:24 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 21:22:25 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Quét TF bất kỳ, tìm FVG cùng hướng gần nhất (shift 1..maxLookback) |
|
|
|
|
|
|
//| Trả về cạnh trên/dưới vùng và bar A low/high (để đặt SL). |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
bool GetLatestLowTFFVG(string symbol,
|
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
|
ENUM_FVG_TYPE type,
|
|
|
|
|
|
int maxLookbackBars,
|
|
|
|
|
|
double &outUpper,
|
|
|
|
|
|
double &outLower,
|
|
|
|
|
|
double &outBarALow,
|
|
|
|
|
|
double &outBarAHigh)
|
|
|
|
|
|
{
|
|
|
|
|
|
int totalBars = Bars(symbol, tf);
|
|
|
|
|
|
int maxShift = MathMin(maxLookbackBars, totalBars - 3);
|
|
|
|
|
|
if(maxShift < 1)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
double maxOuterRatio = InpFVGMaxOuterBarRatio;
|
|
|
|
|
|
double minGapVsBody = InpFVGMinGapVsImpulsePct / 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
for(int shift = 1; shift <= maxShift; shift++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int shiftA = shift + 2;
|
|
|
|
|
|
int shiftB = shift + 1;
|
|
|
|
|
|
int shiftC = shift;
|
|
|
|
|
|
|
|
|
|
|
|
double candleA_High = iHigh(symbol, tf, shiftA);
|
|
|
|
|
|
double candleA_Low = iLow (symbol, tf, shiftA);
|
|
|
|
|
|
double candleB_High = iHigh(symbol, tf, shiftB);
|
|
|
|
|
|
double candleB_Low = iLow (symbol, tf, shiftB);
|
|
|
|
|
|
double candleB_Open = iOpen (symbol, tf, shiftB);
|
|
|
|
|
|
double candleB_Close = iClose(symbol, tf, shiftB);
|
|
|
|
|
|
double candleC_High = iHigh(symbol, tf, shiftC);
|
|
|
|
|
|
double candleC_Low = iLow (symbol, tf, shiftC);
|
|
|
|
|
|
|
|
|
|
|
|
double rangeA = candleA_High - candleA_Low;
|
|
|
|
|
|
double rangeB = candleB_High - candleB_Low;
|
|
|
|
|
|
double rangeC = candleC_High - candleC_Low;
|
|
|
|
|
|
double bodyB = MathAbs(candleB_Close - candleB_Open);
|
|
|
|
|
|
|
|
|
|
|
|
if(rangeB <= 0 || bodyB <= 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if(rangeA > maxOuterRatio * rangeB || rangeC > maxOuterRatio * rangeB)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if(!IsImpulseCandleStrong(symbol, tf, shiftB))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
if(type == FVG_BULLISH)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(candleA_High >= candleC_Low || candleB_Close <= candleB_Open)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
double gap = candleC_Low - candleA_High;
|
|
|
|
|
|
double gapRatio = gap / bodyB;
|
|
|
|
|
|
if(gapRatio < minGapVsBody)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
outUpper = candleC_Low;
|
|
|
|
|
|
outLower = candleA_High;
|
|
|
|
|
|
outBarALow = candleA_Low;
|
|
|
|
|
|
outBarAHigh = candleA_High;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else // FVG_BEARISH
|
|
|
|
|
|
{
|
|
|
|
|
|
if(candleA_Low <= candleC_High || candleB_Close >= candleB_Open)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
double gap = candleA_Low - candleC_High;
|
|
|
|
|
|
double gapRatio = gap / bodyB;
|
|
|
|
|
|
if(gapRatio < minGapVsBody)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
outUpper = candleA_Low;
|
|
|
|
|
|
outLower = candleC_High;
|
|
|
|
|
|
outBarALow = candleA_Low;
|
|
|
|
|
|
outBarAHigh = candleA_High;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 23:16:50 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Low-TF FVG nearest and aligned with HTF zone price range |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
bool GetLatestLowTFFVGInRange(string symbol,
|
|
|
|
|
|
ENUM_TIMEFRAMES tf,
|
|
|
|
|
|
ENUM_FVG_TYPE type,
|
|
|
|
|
|
int maxLookbackBars,
|
|
|
|
|
|
double rangeLower,
|
|
|
|
|
|
double rangeUpper,
|
|
|
|
|
|
double rangeBufferPoints,
|
|
|
|
|
|
double &outUpper,
|
|
|
|
|
|
double &outLower,
|
|
|
|
|
|
double &outBarALow,
|
|
|
|
|
|
double &outBarAHigh)
|
|
|
|
|
|
{
|
|
|
|
|
|
int totalBars = Bars(symbol, tf);
|
|
|
|
|
|
int maxShift = MathMin(maxLookbackBars, totalBars - 3);
|
|
|
|
|
|
if(maxShift < 1)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
double maxOuterRatio = InpFVGMaxOuterBarRatio;
|
|
|
|
|
|
double minGapVsBody = InpFVGMinGapVsImpulsePct / 100.0;
|
|
|
|
|
|
double bufferPrice = rangeBufferPoints * _Point;
|
|
|
|
|
|
double minAllowed = rangeLower - bufferPrice;
|
|
|
|
|
|
double maxAllowed = rangeUpper + bufferPrice;
|
|
|
|
|
|
|
|
|
|
|
|
for(int shift = 1; shift <= maxShift; shift++)
|
|
|
|
|
|
{
|
|
|
|
|
|
int shiftA = shift + 2;
|
|
|
|
|
|
int shiftB = shift + 1;
|
|
|
|
|
|
int shiftC = shift;
|
|
|
|
|
|
|
|
|
|
|
|
double candleA_High = iHigh(symbol, tf, shiftA);
|
|
|
|
|
|
double candleA_Low = iLow (symbol, tf, shiftA);
|
|
|
|
|
|
double candleB_High = iHigh(symbol, tf, shiftB);
|
|
|
|
|
|
double candleB_Low = iLow (symbol, tf, shiftB);
|
|
|
|
|
|
double candleB_Open = iOpen (symbol, tf, shiftB);
|
|
|
|
|
|
double candleB_Close = iClose(symbol, tf, shiftB);
|
|
|
|
|
|
double candleC_High = iHigh(symbol, tf, shiftC);
|
|
|
|
|
|
double candleC_Low = iLow (symbol, tf, shiftC);
|
|
|
|
|
|
|
|
|
|
|
|
double rangeA = candleA_High - candleA_Low;
|
|
|
|
|
|
double rangeB = candleB_High - candleB_Low;
|
|
|
|
|
|
double rangeC = candleC_High - candleC_Low;
|
|
|
|
|
|
double bodyB = MathAbs(candleB_Close - candleB_Open);
|
|
|
|
|
|
|
|
|
|
|
|
if(rangeB <= 0 || bodyB <= 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if(rangeA > maxOuterRatio * rangeB || rangeC > maxOuterRatio * rangeB)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if(!IsImpulseCandleStrong(symbol, tf, shiftB))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
if(type == FVG_BULLISH)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(candleA_High >= candleC_Low || candleB_Close <= candleB_Open)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
double gap = candleC_Low - candleA_High;
|
|
|
|
|
|
double gapRatio = gap / bodyB;
|
|
|
|
|
|
if(gapRatio < minGapVsBody)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
double entry = candleC_Low;
|
|
|
|
|
|
if(entry < minAllowed || entry > maxAllowed)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
outUpper = candleC_Low;
|
|
|
|
|
|
outLower = candleA_High;
|
|
|
|
|
|
outBarALow = candleA_Low;
|
|
|
|
|
|
outBarAHigh = candleA_High;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if(candleA_Low <= candleC_High || candleB_Close >= candleB_Open)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
double gap = candleA_Low - candleC_High;
|
|
|
|
|
|
double gapRatio = gap / bodyB;
|
|
|
|
|
|
if(gapRatio < minGapVsBody)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
double entry = candleC_High;
|
|
|
|
|
|
if(entry < minAllowed || entry > maxAllowed)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
outUpper = candleA_Low;
|
|
|
|
|
|
outLower = candleC_High;
|
|
|
|
|
|
outBarALow = candleA_Low;
|
|
|
|
|
|
outBarAHigh = candleA_High;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 16:06:24 +07:00
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Check if price has mitigated (filled through) any active FVG |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void CheckMitigationStatus()
|
|
|
|
|
|
{
|
|
|
|
|
|
string symbol = GetTradeSymbol();
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(IsZoneMitigated(g_FVGZones[i]) || !IsZoneActive(g_FVGZones[i]))
|
2026-03-11 16:06:24 +07:00
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-03-11 22:22:44 +07:00
|
|
|
|
double lastLow = iLow (symbol, InpTimeframe, 1);
|
|
|
|
|
|
double lastHigh = iHigh (symbol, InpTimeframe, 1);
|
|
|
|
|
|
double lastClose = iClose(symbol, InpTimeframe, 1);
|
2026-03-11 16:06:24 +07:00
|
|
|
|
|
2026-03-11 18:13:27 +07:00
|
|
|
|
double zoneHeight = g_FVGZones[i].upperEdge - g_FVGZones[i].lowerEdge;
|
|
|
|
|
|
if(zoneHeight <= 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2026-03-11 22:22:44 +07:00
|
|
|
|
//--- Bullish FVG: mitigated chỉ khi nến ĐÓNG dưới FVG (rút râu chưa tính) ---
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(g_FVGZones[i].type == FVG_BULLISH)
|
2026-03-11 16:06:24 +07:00
|
|
|
|
{
|
2026-03-11 22:22:44 +07:00
|
|
|
|
if(lastClose <= g_FVGZones[i].lowerEdge)
|
2026-03-11 18:13:27 +07:00
|
|
|
|
{
|
|
|
|
|
|
g_FVGZones[i].status = MITIGATED;
|
|
|
|
|
|
if(InpDebugLog)
|
|
|
|
|
|
PrintFormat("[FVG] MITIGATED BULL [%.5f – %.5f]",
|
|
|
|
|
|
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Touched: price has filled at least X% of gap, but not fully
|
|
|
|
|
|
double deepestPrice = MathMin(lastLow, g_FVGZones[i].upperEdge);
|
|
|
|
|
|
deepestPrice = MathMax(deepestPrice, g_FVGZones[i].lowerEdge);
|
|
|
|
|
|
double fillPct = (g_FVGZones[i].upperEdge - deepestPrice) / zoneHeight * 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
if(g_FVGZones[i].status == ACTIVE && fillPct >= InpFVGTouchedPercent)
|
|
|
|
|
|
g_FVGZones[i].status = TOUCHED;
|
2026-03-11 16:06:24 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 22:22:44 +07:00
|
|
|
|
//--- Bearish FVG: mitigated chỉ khi nến ĐÓNG trên FVG (rút râu chưa tính) ---
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(g_FVGZones[i].type == FVG_BEARISH)
|
2026-03-11 16:06:24 +07:00
|
|
|
|
{
|
2026-03-11 22:22:44 +07:00
|
|
|
|
if(lastClose >= g_FVGZones[i].upperEdge)
|
2026-03-11 18:13:27 +07:00
|
|
|
|
{
|
|
|
|
|
|
g_FVGZones[i].status = MITIGATED;
|
|
|
|
|
|
if(InpDebugLog)
|
|
|
|
|
|
PrintFormat("[FVG] MITIGATED BEAR [%.5f – %.5f]",
|
|
|
|
|
|
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Touched: price has filled at least X% of gap, but not fully
|
|
|
|
|
|
double highestPrice = MathMax(lastHigh, g_FVGZones[i].lowerEdge);
|
|
|
|
|
|
highestPrice = MathMin(highestPrice, g_FVGZones[i].upperEdge);
|
|
|
|
|
|
double fillPct = (highestPrice - g_FVGZones[i].lowerEdge) / zoneHeight * 100.0;
|
|
|
|
|
|
|
|
|
|
|
|
if(g_FVGZones[i].status == ACTIVE && fillPct >= InpFVGTouchedPercent)
|
|
|
|
|
|
g_FVGZones[i].status = TOUCHED;
|
2026-03-11 16:06:24 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Age out old FVGs beyond max age |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void ExpireOldFVGs()
|
|
|
|
|
|
{
|
|
|
|
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(!IsZoneActive(g_FVGZones[i])) continue;
|
2026-03-11 16:06:24 +07:00
|
|
|
|
|
|
|
|
|
|
g_FVGZones[i].ageInBars++;
|
|
|
|
|
|
|
|
|
|
|
|
if(g_FVGZones[i].ageInBars > InpFVGMaxAgeBars)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
g_FVGZones[i].status = EXPIRED;
|
2026-03-11 16:06:24 +07:00
|
|
|
|
if(InpDebugLog)
|
|
|
|
|
|
PrintFormat("[FVG] EXPIRED %s [%.5f – %.5f] age=%d bars",
|
|
|
|
|
|
(g_FVGZones[i].type == FVG_BULLISH) ? "BULL" : "BEAR",
|
|
|
|
|
|
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge,
|
|
|
|
|
|
g_FVGZones[i].ageInBars);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Remove inactive FVGs from array to free slots |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void CompactFVGArray()
|
|
|
|
|
|
{
|
|
|
|
|
|
int writeIndex = 0;
|
|
|
|
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(IsZoneActive(g_FVGZones[i]))
|
2026-03-11 16:06:24 +07:00
|
|
|
|
{
|
|
|
|
|
|
if(writeIndex != i)
|
|
|
|
|
|
g_FVGZones[writeIndex] = g_FVGZones[i];
|
|
|
|
|
|
writeIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
g_FVGCount = writeIndex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Main update: mitigation → expiry → scan new → compact |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
void FVGUpdate()
|
|
|
|
|
|
{
|
|
|
|
|
|
CheckMitigationStatus();
|
|
|
|
|
|
ExpireOldFVGs();
|
|
|
|
|
|
ScanForNewFVGs();
|
|
|
|
|
|
CompactFVGArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
//| Count helpers for panel display |
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
|
int CountActiveFVGs(ENUM_FVG_TYPE type)
|
|
|
|
|
|
{
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(IsZoneActive(g_FVGZones[i]) && g_FVGZones[i].type == type)
|
2026-03-11 16:06:24 +07:00
|
|
|
|
count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CountMitigatedFVGs()
|
|
|
|
|
|
{
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
for(int i = 0; i < g_FVGCount; i++)
|
|
|
|
|
|
{
|
2026-03-11 18:13:27 +07:00
|
|
|
|
if(IsZoneMitigated(g_FVGZones[i]))
|
2026-03-11 16:06:24 +07:00
|
|
|
|
count++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif
|