278 lines
9.6 KiB
Plaintext
278 lines
9.6 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| 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
|
||
{
|
||
bool isActive;
|
||
ENUM_FVG_TYPE type;
|
||
double upperEdge;
|
||
double lowerEdge;
|
||
datetime createdTime;
|
||
int ageInBars;
|
||
bool isMitigated;
|
||
};
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| 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;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Check if impulse candle has strong body (body/range >= threshold) |
|
||
//+------------------------------------------------------------------+
|
||
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);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Check if this FVG timestamp already exists in our array |
|
||
//+------------------------------------------------------------------+
|
||
bool FVGAlreadyTracked(datetime fvgTime, ENUM_FVG_TYPE fvgType)
|
||
{
|
||
for(int i = 0; i < g_FVGCount; i++)
|
||
{
|
||
if(g_FVGZones[i].isActive
|
||
&& g_FVGZones[i].createdTime == fvgTime
|
||
&& g_FVGZones[i].type == fvgType)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Add a new FVG zone to the array |
|
||
//+------------------------------------------------------------------+
|
||
bool AddFVGZone(ENUM_FVG_TYPE type, double upper, double lower, datetime time)
|
||
{
|
||
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]);
|
||
g_FVGZones[g_FVGCount].isActive = true;
|
||
g_FVGZones[g_FVGCount].type = type;
|
||
g_FVGZones[g_FVGCount].upperEdge = upper;
|
||
g_FVGZones[g_FVGCount].lowerEdge = lower;
|
||
g_FVGZones[g_FVGCount].createdTime = time;
|
||
g_FVGZones[g_FVGCount].ageInBars = 0;
|
||
g_FVGZones[g_FVGCount].isMitigated = false;
|
||
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);
|
||
|
||
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);
|
||
|
||
//--- Bullish FVG: gap between A.High and C.Low ---
|
||
if(candleA_High < candleC_Low
|
||
&& candleB_Close > candleB_Open
|
||
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
|
||
{
|
||
if(!FVGAlreadyTracked(fvgTime, FVG_BULLISH))
|
||
AddFVGZone(FVG_BULLISH, candleC_Low, candleA_High, fvgTime);
|
||
}
|
||
|
||
//--- Bearish FVG: gap between C.High and A.Low ---
|
||
if(candleA_Low > candleC_High
|
||
&& candleB_Close < candleB_Open
|
||
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
|
||
{
|
||
if(!FVGAlreadyTracked(fvgTime, FVG_BEARISH))
|
||
AddFVGZone(FVG_BEARISH, candleA_Low, candleC_High, fvgTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Check if price has mitigated (filled through) any active FVG |
|
||
//+------------------------------------------------------------------+
|
||
void CheckMitigationStatus()
|
||
{
|
||
string symbol = GetTradeSymbol();
|
||
|
||
for(int i = 0; i < g_FVGCount; i++)
|
||
{
|
||
if(!g_FVGZones[i].isActive || g_FVGZones[i].isMitigated)
|
||
continue;
|
||
|
||
double lastLow = iLow (symbol, InpTimeframe, 1);
|
||
double lastHigh = iHigh(symbol, InpTimeframe, 1);
|
||
|
||
// Bullish FVG mitigated: price drops through lower edge
|
||
if(g_FVGZones[i].type == FVG_BULLISH && lastLow <= g_FVGZones[i].lowerEdge)
|
||
{
|
||
g_FVGZones[i].isMitigated = true;
|
||
if(InpDebugLog)
|
||
PrintFormat("[FVG] MITIGATED BULL [%.5f – %.5f]",
|
||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
||
}
|
||
|
||
// Bearish FVG mitigated: price rises through upper edge
|
||
if(g_FVGZones[i].type == FVG_BEARISH && lastHigh >= g_FVGZones[i].upperEdge)
|
||
{
|
||
g_FVGZones[i].isMitigated = true;
|
||
if(InpDebugLog)
|
||
PrintFormat("[FVG] MITIGATED BEAR [%.5f – %.5f]",
|
||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
||
}
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Age out old FVGs beyond max age |
|
||
//+------------------------------------------------------------------+
|
||
void ExpireOldFVGs()
|
||
{
|
||
for(int i = 0; i < g_FVGCount; i++)
|
||
{
|
||
if(!g_FVGZones[i].isActive) continue;
|
||
|
||
g_FVGZones[i].ageInBars++;
|
||
|
||
if(g_FVGZones[i].ageInBars > InpFVGMaxAgeBars)
|
||
{
|
||
g_FVGZones[i].isActive = false;
|
||
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++)
|
||
{
|
||
if(g_FVGZones[i].isActive)
|
||
{
|
||
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++)
|
||
{
|
||
if(g_FVGZones[i].isActive && g_FVGZones[i].type == type)
|
||
count++;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
int CountMitigatedFVGs()
|
||
{
|
||
int count = 0;
|
||
for(int i = 0; i < g_FVGCount; i++)
|
||
{
|
||
if(g_FVGZones[i].isActive && g_FVGZones[i].isMitigated)
|
||
count++;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
#endif
|