Files
Bell-PriceActionWithEma-EA/Include/ICT2026/Fvg.mqh
T

825 lines
22 KiB
Plaintext
Raw Normal View History

//+------------------------------------------------------------------+
//| Fvg.mqh — iTF Fair Value Gap (Available / Used) |
//+------------------------------------------------------------------+
#ifndef ICT2026_FVG_MQH
#define ICT2026_FVG_MQH
#include <ICT2026/Config.mqh>
#include <ICT2026/IntradayStructure.mqh>
#include <ICT2026/Swing.mqh>
IctFvgZone g_ictFvgZones[];
int g_ictFvgCount = 0;
static ulong g_ictFvgNextId = 1;
ENUM_ICT_FVG_SIDE IctFvgSideFromBias(const ENUM_ICT_BIAS bias)
{
if(bias == ICT_BIAS_BULL)
return ICT_FVG_BULL;
if(bias == ICT_BIAS_BEAR)
return ICT_FVG_BEAR;
return ICT_FVG_NONE;
}
string IctFvgSideText(const ENUM_ICT_FVG_SIDE side)
{
switch(side)
{
case ICT_FVG_BULL: return "Bull";
case ICT_FVG_BEAR: return "Bear";
default: return "None";
}
}
string IctFvgStateText(const ENUM_ICT_FVG_STATE state)
{
return (state == ICT_FVG_USED) ? "Used" : "Available";
}
string IctPdZoneText(const ENUM_ICT_PD_ZONE zone)
{
switch(zone)
{
case ICT_PD_PREMIUM: return "Premium";
case ICT_PD_DISCOUNT: return "Discount";
case ICT_PD_EQUILIBRIUM: return "EQ";
default: return "";
}
}
bool IctFvg_UseGapSizeFilter(const ENUM_TIMEFRAMES tf)
{
return (tf == InpFvgTf);
}
double IctFvg_MinRequiredGapHeight(const string sym, const ENUM_TIMEFRAMES tf, const int shiftC)
{
double minH = 0.0;
if(!IctFvg_UseGapSizeFilter(tf))
return 0.0;
if(InpFvgMinGapPoints > 0.0)
minH = MathMax(minH, InpFvgMinGapPoints);
if(InpFvgMinGapATRPct > 0.0)
{
const int hAtr = iATR(sym, tf, InpFvgAtrPeriod);
if(hAtr != INVALID_HANDLE)
{
double atr[];
ArraySetAsSeries(atr, true);
if(CopyBuffer(hAtr, 0, shiftC, 1, atr) == 1 && atr[0] > 0.0)
minH = MathMax(minH, atr[0] * InpFvgMinGapATRPct / 100.0);
IndicatorRelease(hAtr);
}
}
return minH;
}
bool IctFvg_IsValidGapSize(const string sym, const ENUM_TIMEFRAMES tf, const int shiftC,
const double upper, const double lower)
{
const double height = upper - lower;
if(height <= _Point)
return false;
if(!IctFvg_UseGapSizeFilter(tf))
return true;
const double minH = IctFvg_MinRequiredGapHeight(sym, tf, shiftC);
if(minH > 0.0 && height + _Point * 0.5 < minH)
return false;
if(InpFvgMinGapVsBarPct > 0.0)
{
const int shiftB = shiftC + 1;
const double barRange = iHigh(sym, tf, shiftB) - iLow(sym, tf, shiftB);
if(barRange > _Point && height < barRange * InpFvgMinGapVsBarPct / 100.0)
return false;
}
return true;
}
bool IctFvg_IsDuplicate(const datetime createdTime, const double upper, const double lower)
{
const double tol = _Point * 2.0;
for(int i = 0; i < g_ictFvgCount; i++)
{
if(g_ictFvgZones[i].createdTime != createdTime)
continue;
if(MathAbs(g_ictFvgZones[i].upper - upper) <= tol &&
MathAbs(g_ictFvgZones[i].lower - lower) <= tol)
return true;
}
return false;
}
bool IctFvg_TryDetectAtShift(const string sym, const ENUM_TIMEFRAMES tf, const int shiftC,
ENUM_ICT_FVG_SIDE wantSide,
ENUM_ICT_FVG_SIDE &sideOut,
double &upperOut, double &lowerOut,
datetime &timeAOut, datetime &timeCOut)
{
sideOut = ICT_FVG_NONE;
upperOut = 0.0;
lowerOut = 0.0;
timeAOut = 0;
timeCOut = 0;
const int shiftA = shiftC + 2;
const int shiftB = shiftC + 1;
if(shiftA >= Bars(sym, tf))
return false;
const double aHi = iHigh(sym, tf, shiftA);
const double aLo = iLow(sym, tf, shiftA);
const double cHi = iHigh(sym, tf, shiftC);
const double cLo = iLow(sym, tf, shiftC);
if(wantSide == ICT_FVG_BULL)
{
if(aHi >= cLo)
return false;
const double upper = cLo;
const double lower = aHi;
if(!IctFvg_IsValidGapSize(sym, tf, shiftC, upper, lower))
return false;
sideOut = ICT_FVG_BULL;
upperOut = upper;
lowerOut = lower;
timeAOut = iTime(sym, tf, shiftA);
timeCOut = iTime(sym, tf, shiftC);
return true;
}
if(wantSide == ICT_FVG_BEAR)
{
if(aLo <= cHi)
return false;
const double upper = aLo;
const double lower = cHi;
if(!IctFvg_IsValidGapSize(sym, tf, shiftC, upper, lower))
return false;
sideOut = ICT_FVG_BEAR;
upperOut = upper;
lowerOut = lower;
timeAOut = iTime(sym, tf, shiftA);
timeCOut = iTime(sym, tf, shiftC);
return true;
}
return false;
}
ENUM_ICT_PD_ZONE IctFvg_ClassifyPd(const ENUM_ICT_FVG_SIDE side,
const double fvgMid,
const double pdEq,
const double pdHigh,
const double pdLow)
{
if(pdHigh <= pdLow || pdEq <= 0.0)
return ICT_PD_NONE;
const double tol = (pdHigh - pdLow) * 0.02;
if(MathAbs(fvgMid - pdEq) <= tol)
return ICT_PD_EQUILIBRIUM;
if(side == ICT_FVG_BULL)
return (fvgMid < pdEq) ? ICT_PD_DISCOUNT : ICT_PD_PREMIUM;
if(side == ICT_FVG_BEAR)
return (fvgMid > pdEq) ? ICT_PD_PREMIUM : ICT_PD_DISCOUNT;
return ICT_PD_NONE;
}
2026-05-24 22:24:36 +07:00
bool IctFvg_OverlapsPremium(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0 || zone.pdHigh <= zone.pdLow + _Point)
return false;
return (zone.upper > zone.pdEq + _Point && zone.lower < zone.pdHigh + _Point);
}
bool IctFvg_OverlapsDiscount(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0 || zone.pdHigh <= zone.pdLow + _Point)
return false;
return (zone.lower < zone.pdEq - _Point && zone.upper > zone.pdLow - _Point);
}
ENUM_ICT_PD_ZONE IctFvg_ComputeRepPd(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0)
return ICT_PD_NONE;
const bool inPrem = IctFvg_OverlapsPremium(zone);
const bool inDisc = IctFvg_OverlapsDiscount(zone);
if(inPrem && !inDisc)
return ICT_PD_PREMIUM;
if(inDisc && !inPrem)
return ICT_PD_DISCOUNT;
if(inPrem && inDisc)
{
if(zone.side == ICT_FVG_BEAR)
return ICT_PD_PREMIUM;
if(zone.side == ICT_FVG_BULL)
return ICT_PD_DISCOUNT;
}
return ICT_PD_NONE;
}
double IctFvg_BarExtremeSince(const string sym, const ENUM_TIMEFRAMES tf,
const datetime fromTime, const bool wantLow)
{
double v = wantLow ? DBL_MAX : -DBL_MAX;
bool ok = false;
for(int sh = 0; sh < Bars(sym, tf); sh++)
{
const datetime t = iTime(sym, tf, sh);
if(t < fromTime)
break;
const double px = wantLow ? iLow(sym, tf, sh) : iHigh(sym, tf, sh);
if(wantLow)
v = MathMin(v, px);
else
v = MathMax(v, px);
ok = true;
}
return ok ? v : 0.0;
}
bool IctFvg_IsConfirmedPivot(const string sym, const ENUM_TIMEFRAMES tf,
const IctSwingPoint &pt, const bool isHigh)
{
if(!pt.Valid() || pt.shift < 1)
return false;
const int str = MathMax(1, InpIntradaySwingRange);
if(pt.shift < str + 1)
return false;
if(isHigh)
return IctIsSwingHigh(sym, tf, pt.shift, str);
return IctIsSwingLow(sym, tf, pt.shift, str);
}
2026-05-24 22:24:36 +07:00
// Đỉnh PD bear = pivot swing high gần FVG nhất (theo bar shift), trên upper — không dùng wick nến
bool IctFvg_ResolvePdHighBear(const string sym, const IctFvgZone &zone, double &outHigh)
{
2026-05-24 22:24:36 +07:00
outHigh = 0.0;
const ENUM_TIMEFRAMES pdTf = InpIntradayTf;
2026-05-24 22:24:36 +07:00
int fvgShift = iBarShift(sym, pdTf, zone.createdTime, true);
if(fvgShift < 0)
fvgShift = iBarShift(sym, pdTf, zone.createdTime, false);
if(fvgShift < 0)
return false;
IctSwingPoint highs[], lows[];
IctCollectSwings(sym, pdTf, InpIntradaySwingRange, InpIntradaySwingLookback, highs, lows);
const int nH = ArraySize(highs);
2026-05-24 22:24:36 +07:00
IctSwingPoint pt = IctFindNearestSwingHighBefore(highs, nH, fvgShift, zone.upper);
if(!pt.Valid())
return false;
2026-05-24 22:24:36 +07:00
outHigh = pt.price;
return true;
}
2026-05-24 22:24:36 +07:00
// Đáy PD bull = pivot swing low gần FVG nhất (theo bar shift), dưới lower
bool IctFvg_ResolvePdLowBull(const string sym, const IctFvgZone &zone, double &outLow)
{
2026-05-24 22:24:36 +07:00
outLow = 0.0;
const ENUM_TIMEFRAMES pdTf = InpIntradayTf;
2026-05-24 22:24:36 +07:00
int fvgShift = iBarShift(sym, pdTf, zone.createdTime, true);
if(fvgShift < 0)
fvgShift = iBarShift(sym, pdTf, zone.createdTime, false);
if(fvgShift < 0)
return false;
IctSwingPoint highs[], lows[];
IctCollectSwings(sym, pdTf, InpIntradaySwingRange, InpIntradaySwingLookback, highs, lows);
const int nL = ArraySize(lows);
2026-05-24 22:24:36 +07:00
IctSwingPoint pt = IctFindNearestSwingLowBefore(lows, nL, fvgShift, zone.lower);
if(!pt.Valid())
return false;
2026-05-24 22:24:36 +07:00
outLow = pt.price;
return true;
}
bool IctFvg_NearestLowBelow(const string sym, const double refLower,
const datetime beforeTime, double &outLow)
{
outLow = 0.0;
double best = -DBL_MAX;
bool found = false;
IctSwingPoint highs[], lows[];
IctCollectSwings(sym, InpIntradayTf, InpIntradaySwingRange, InpIntradaySwingLookback,
highs, lows);
const int nL = ArraySize(lows);
for(int i = 0; i < nL; i++)
{
if(lows[i].price >= refLower - _Point)
continue;
if(lows[i].time > beforeTime)
continue;
if(lows[i].price > best)
{
best = lows[i].price;
found = true;
}
}
IctSwingSet sw = g_ictIntraday.swings;
if(sw.hasL0 && sw.l0.price < refLower - _Point && sw.l0.time <= beforeTime &&
sw.l0.price > best)
{
best = sw.l0.price;
found = true;
}
if(sw.hasL1 && sw.l1.price < refLower - _Point && sw.l1.time <= beforeTime &&
sw.l1.price > best)
{
best = sw.l1.price;
found = true;
}
if(!found)
{
const int maxBars = MathMax(InpIntradayRecentBars, 30);
for(int sh = 0; sh < maxBars && sh < Bars(sym, InpIntradayTf); sh++)
{
const datetime t = iTime(sym, InpIntradayTf, sh);
if(t > beforeTime)
continue;
const double l = iLow(sym, InpIntradayTf, sh);
if(l < refLower - _Point && l > best)
{
best = l;
found = true;
}
}
}
if(!found)
return false;
outLow = best;
return true;
}
bool IctFvg_FindFirstSwingLowAfter(const IctSwingPoint &lows[], const int nL,
const datetime afterTime, IctSwingPoint &out)
{
out.Clear();
for(int i = 0; i < nL; i++)
{
if(lows[i].time >= afterTime)
{
out = lows[i];
return true;
}
}
return false;
}
bool IctFvg_FindFirstSwingHighAfter(const IctSwingPoint &highs[], const int nH,
const datetime afterTime, IctSwingPoint &out)
{
out.Clear();
for(int i = 0; i < nH; i++)
{
if(highs[i].time >= afterTime)
{
out = highs[i];
return true;
}
}
return false;
}
bool IctFvg_MatchesBias(const IctFvgZone &zone)
{
const ENUM_ICT_FVG_SIDE want = IctFvgSideFromBias(g_ictDailyBias.bias);
return (want != ICT_FVG_NONE && zone.side == want);
}
void IctFvg_FinalizePdMetrics(IctFvgZone &zone)
{
if(zone.pdHigh <= zone.pdLow + _Point)
return;
zone.pdEq = (zone.pdHigh + zone.pdLow) * 0.5;
const double mid = (zone.upper + zone.lower) * 0.5;
zone.pdZone = IctFvg_ClassifyPd(zone.side, mid, zone.pdEq, zone.pdHigh, zone.pdLow);
2026-05-24 22:24:36 +07:00
zone.repPd = IctFvg_ComputeRepPd(zone);
}
ENUM_ICT_PD_ZONE IctFvg_GetRepPd(const IctFvgZone &zone)
{
if(zone.repPd != ICT_PD_NONE)
return zone.repPd;
return zone.pdZone;
}
bool IctFvg_IsEntryRepPd(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0)
return false;
if(g_ictDailyBias.bias == ICT_BIAS_BEAR)
return IctFvg_OverlapsPremium(zone);
if(g_ictDailyBias.bias == ICT_BIAS_BULL)
return IctFvg_OverlapsDiscount(zone);
return false;
}
bool IctFvg_HasFillAtLeast(const IctFvgZone &zone, const double pct)
{
if(pct <= 0.0)
return true;
return (zone.maxFillRatio >= pct / 100.0 - 1e-9);
}
double IctFvg_PriceAtFillPct(const IctFvgZone &zone, const double pct)
{
const double h = zone.upper - zone.lower;
if(h <= _Point)
return 0.0;
const double r = MathMax(0.0, MathMin(100.0, pct)) / 100.0;
if(zone.side == ICT_FVG_BULL)
return zone.upper - h * r;
if(zone.side == ICT_FVG_BEAR)
return zone.lower + h * r;
return 0.0;
}
void IctFvg_UpdatePdForZone(const string sym, IctFvgZone &zone)
{
if(zone.pdComplete)
return;
zone.pdRangeStart = zone.createdTime;
const ENUM_TIMEFRAMES pdTf = InpIntradayTf;
const int pdPeriod = (int)PeriodSeconds(pdTf);
datetime nowEnd = iTime(sym, pdTf, 0) + pdPeriod;
IctSwingPoint highs[], lows[];
IctCollectSwings(sym, pdTf, InpIntradaySwingRange, InpIntradaySwingLookback, highs, lows);
const int nL = ArraySize(lows);
const int nH = ArraySize(highs);
2026-05-24 22:24:36 +07:00
int fvgShift = iBarShift(sym, pdTf, zone.createdTime, true);
if(fvgShift < 0)
fvgShift = iBarShift(sym, pdTf, zone.createdTime, false);
if(zone.side == ICT_FVG_BEAR)
{
if(!zone.pdSwing1Set)
{
if(!IctFvg_ResolvePdHighBear(sym, zone, zone.pdHigh))
return;
zone.pdSwing1Set = true;
}
IctSwingPoint swingLow;
2026-05-24 22:24:36 +07:00
if(fvgShift >= 0)
swingLow = IctFindFirstSwingLowAfterShift(lows, nL, fvgShift);
else
IctFvg_FindFirstSwingLowAfter(lows, nL, zone.createdTime, swingLow);
if(swingLow.Valid() &&
IctFvg_IsConfirmedPivot(sym, pdTf, swingLow, false))
{
zone.pdLow = swingLow.price;
zone.pdRangeEnd = swingLow.time + pdPeriod;
zone.pdComplete = true;
}
else
{
const double runLo = IctFvg_BarExtremeSince(sym, pdTf, zone.createdTime, true);
if(runLo <= 0.0)
return;
zone.pdLow = runLo;
zone.pdRangeEnd = nowEnd;
}
}
else if(zone.side == ICT_FVG_BULL)
{
if(!zone.pdSwing1Set)
{
if(!IctFvg_ResolvePdLowBull(sym, zone, zone.pdLow))
return;
zone.pdSwing1Set = true;
}
IctSwingPoint swingHigh;
2026-05-24 22:24:36 +07:00
if(fvgShift >= 0)
swingHigh = IctFindFirstSwingHighAfterShift(highs, nH, fvgShift);
else
IctFvg_FindFirstSwingHighAfter(highs, nH, zone.createdTime, swingHigh);
if(swingHigh.Valid() &&
IctFvg_IsConfirmedPivot(sym, pdTf, swingHigh, true))
{
zone.pdHigh = swingHigh.price;
zone.pdRangeEnd = swingHigh.time + pdPeriod;
zone.pdComplete = true;
}
else
{
const double runHi = IctFvg_BarExtremeSince(sym, pdTf, zone.createdTime, false);
if(runHi <= 0.0)
return;
zone.pdHigh = runHi;
zone.pdRangeEnd = nowEnd;
}
}
else
return;
IctFvg_FinalizePdMetrics(zone);
}
void IctFvg_UpdateAllPd(const string sym)
{
for(int i = 0; i < g_ictFvgCount; i++)
IctFvg_UpdatePdForZone(sym, g_ictFvgZones[i]);
}
void IctFvg_AddZone(const IctFvgZone &zone)
{
if(g_ictFvgCount >= InpFvgMaxZones)
{
for(int i = 0; i < g_ictFvgCount - 1; i++)
g_ictFvgZones[i] = g_ictFvgZones[i + 1];
g_ictFvgCount--;
}
ArrayResize(g_ictFvgZones, g_ictFvgCount + 1);
g_ictFvgZones[g_ictFvgCount] = zone;
g_ictFvgCount++;
}
bool IctFvg_BarTouchesZone(const ENUM_ICT_FVG_SIDE side,
const double barHigh, const double barLow,
const double upper, const double lower)
{
if(barHigh < barLow)
return false;
if(barHigh < lower - _Point || barLow > upper + _Point)
return false;
if(side == ICT_FVG_BULL)
return (barLow <= upper + _Point);
if(side == ICT_FVG_BEAR)
return (barHigh >= lower - _Point);
return false;
}
void IctFvg_RegisterTouch(IctFvgZone &zone, const datetime barTime)
{
if(barTime <= zone.createdTime)
return;
if(zone.firstTouchTime == 0 || barTime < zone.firstTouchTime)
zone.firstTouchTime = barTime;
}
void IctFvg_UpdateTouchAndFill(const string sym, const ENUM_TIMEFRAMES tf, IctFvgZone &zone)
{
zone.maxFillRatio = 0.0;
zone.firstTouchTime = 0;
const double height = zone.upper - zone.lower;
if(height <= _Point)
return;
double deepest = zone.upper;
for(int sh = 1; sh < Bars(sym, tf); sh++)
{
const datetime t = iTime(sym, tf, sh);
if(t <= zone.createdTime)
break;
const double barHi = iHigh(sym, tf, sh);
const double barLo = iLow(sym, tf, sh);
if(IctFvg_BarTouchesZone(zone.side, barHi, barLo, zone.upper, zone.lower))
IctFvg_RegisterTouch(zone, t);
if(zone.side == ICT_FVG_BULL)
{
if(barLo >= zone.upper)
continue;
if(barLo <= zone.lower)
zone.maxFillRatio = 1.0;
else
deepest = MathMin(deepest, barLo);
}
else if(zone.side == ICT_FVG_BEAR)
{
if(barHi <= zone.lower)
continue;
if(barHi >= zone.upper)
zone.maxFillRatio = 1.0;
else
deepest = MathMax(deepest, barHi);
}
}
const datetime t0 = iTime(sym, tf, 0);
if(t0 > zone.createdTime)
{
const double barHi0 = iHigh(sym, tf, 0);
const double barLo0 = iLow(sym, tf, 0);
if(IctFvg_BarTouchesZone(zone.side, barHi0, barLo0, zone.upper, zone.lower))
IctFvg_RegisterTouch(zone, t0);
if(zone.side == ICT_FVG_BULL && barLo0 < zone.upper)
{
if(barLo0 <= zone.lower)
zone.maxFillRatio = 1.0;
else
deepest = MathMin(deepest, barLo0);
}
else if(zone.side == ICT_FVG_BEAR && barHi0 > zone.lower)
{
if(barHi0 >= zone.upper)
zone.maxFillRatio = 1.0;
else
deepest = MathMax(deepest, barHi0);
}
}
if(zone.maxFillRatio < 1.0)
{
if(zone.side == ICT_FVG_BULL)
zone.maxFillRatio = (zone.upper - deepest) / height;
else if(zone.side == ICT_FVG_BEAR)
zone.maxFillRatio = (deepest - zone.lower) / height;
}
}
datetime IctFvg_GetFvgDrawTimeEnd(const string sym, const ENUM_TIMEFRAMES tf,
const IctFvgZone &zone)
{
const int periodSec = (int)PeriodSeconds(tf);
if(zone.state == ICT_FVG_USED)
{
if(zone.fvgUsedTime > 0)
return zone.fvgUsedTime + periodSec;
return zone.createdTime + periodSec;
}
const datetime barEnd = iTime(sym, tf, 0) + periodSec;
if(zone.expireTime > 0 && barEnd > zone.expireTime)
return zone.expireTime;
return barEnd;
}
datetime IctFvg_GetPdDrawTimeEnd(const IctFvgZone &zone)
{
if(zone.pdRangeEnd > zone.pdRangeStart)
return zone.pdRangeEnd;
return zone.createdTime + (datetime)PeriodSeconds(InpIntradayTf);
}
void IctFvg_UpdateZoneState(const string sym, const ENUM_TIMEFRAMES tf, IctFvgZone &zone)
{
const ENUM_ICT_FVG_STATE prevState = zone.state;
IctFvg_UpdateTouchAndFill(sym, tf, zone);
2026-05-25 01:59:57 +07:00
if(tf != InpFvgTf)
{
const double usedPct = InpFvgUsedFillPct / 100.0;
if(zone.maxFillRatio >= usedPct || zone.maxFillRatio >= 0.999)
zone.state = ICT_FVG_USED;
}
if(zone.state == ICT_FVG_USED && prevState == ICT_FVG_AVAILABLE && zone.fvgUsedTime == 0)
zone.fvgUsedTime = iTime(sym, tf, 0);
zone.timeEnd = IctFvg_GetFvgDrawTimeEnd(sym, tf, zone);
}
void IctFvg_RemoveAt(const int index)
{
if(index < 0 || index >= g_ictFvgCount)
return;
for(int i = index; i < g_ictFvgCount - 1; i++)
g_ictFvgZones[i] = g_ictFvgZones[i + 1];
g_ictFvgCount--;
ArrayResize(g_ictFvgZones, g_ictFvgCount);
}
void IctFvg_PurgeExpired()
{
const datetime now = TimeCurrent();
for(int i = g_ictFvgCount - 1; i >= 0; i--)
{
if(!IctFvg_MatchesBias(g_ictFvgZones[i]) &&
g_ictFvgZones[i].state == ICT_FVG_AVAILABLE)
{
IctFvg_RemoveAt(i);
continue;
}
if(g_ictFvgZones[i].state == ICT_FVG_AVAILABLE &&
g_ictFvgZones[i].expireTime > 0 &&
now >= g_ictFvgZones[i].expireTime)
IctFvg_RemoveAt(i);
}
}
void IctFvg_ScanNew(const string sym, const ENUM_TIMEFRAMES tf,
const ENUM_ICT_FVG_SIDE wantSide,
const bool fullLookback = false)
{
if(wantSide == ICT_FVG_NONE)
return;
const int maxShift = MathMin(InpFvgLookbackBars, Bars(sym, tf) - 3);
if(maxShift < 1)
return;
int fromSh = 1;
if(fullLookback)
fromSh = maxShift;
else
{
const int scanN = MathMax(1, InpFvgScanBarsPerUpdate);
fromSh = MathMin(maxShift, scanN);
}
for(int sh = fromSh; sh >= 1; sh--)
{
ENUM_ICT_FVG_SIDE side = ICT_FVG_NONE;
double upper = 0.0, lower = 0.0;
datetime timeA = 0, timeC = 0;
if(!IctFvg_TryDetectAtShift(sym, tf, sh, wantSide, side, upper, lower, timeA, timeC))
continue;
if(IctFvg_IsDuplicate(timeC, upper, lower))
continue;
IctFvgZone zone;
zone.Clear();
zone.id = g_ictFvgNextId++;
zone.side = side;
zone.state = ICT_FVG_AVAILABLE;
zone.upper = upper;
zone.lower = lower;
zone.createdTime = timeC;
zone.timeStart = timeA;
zone.expireTime = timeC + (datetime)(InpFvgExpireDays * 86400);
zone.locked = true;
IctFvg_UpdateZoneState(sym, tf, zone);
IctFvg_AddZone(zone);
if(InpDebug)
PrintFormat("[ICT2026/FVG] New %s %s [%.2f %.2f] | %s | fill=%.1f%%",
IctFvgSideText(zone.side), IctFvgStateText(zone.state),
zone.lower, zone.upper, IctPdZoneText(zone.pdZone),
zone.maxFillRatio * 100.0);
}
IctFvg_UpdateAllPd(sym);
}
void IctFvg_UpdateAll(const string sym, const ENUM_TIMEFRAMES tf)
{
for(int i = 0; i < g_ictFvgCount; i++)
IctFvg_UpdateZoneState(sym, tf, g_ictFvgZones[i]);
IctFvg_UpdateAllPd(sym);
IctFvg_PurgeExpired();
}
void IctFvg_Reset()
{
g_ictFvgCount = 0;
ArrayResize(g_ictFvgZones, 0);
}
int IctFvg_CountAvailable()
{
int n = 0;
for(int i = 0; i < g_ictFvgCount; i++)
if(g_ictFvgZones[i].state == ICT_FVG_AVAILABLE)
n++;
return n;
}
#endif