751 lines
23 KiB
Plaintext
751 lines
23 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| MssSetup.mqh — MSS: retest FVG H1 → M5 MSS (phá L0/H0) → M5 FVG → entry |
|
||
//+------------------------------------------------------------------+
|
||
#ifndef ICT2026_MSSSETUP_MQH
|
||
#define ICT2026_MSSSETUP_MQH
|
||
|
||
#include <ICT2026/ConfirmFvg.mqh>
|
||
#include <ICT2026/Journal.mqh>
|
||
#include <ICT2026/LowTfApi.mqh>
|
||
|
||
string IctMssPhaseText(const ENUM_ICT_MSS_PHASE ph)
|
||
{
|
||
switch(ph)
|
||
{
|
||
case ICT_MSS_H1_TOUCH: return "Retest FVG H1 OK";
|
||
case ICT_MSS_CHOCH: return "MSS OK";
|
||
case ICT_MSS_M5_FVG: return "M5 FVG";
|
||
case ICT_MSS_ENTRY_FILL: return "M5 fill 38.2%";
|
||
case ICT_MSS_READY: return "READY entry";
|
||
default: return "Idle";
|
||
}
|
||
}
|
||
|
||
int IctMss_FindH1FvgById(const ulong id)
|
||
{
|
||
for(int i = 0; i < g_ictFvgCount; i++)
|
||
if(g_ictFvgZones[i].id == id)
|
||
return i;
|
||
return -1;
|
||
}
|
||
|
||
// H1 FVG mục tiêu: Bear → Premium cao nhất; Bull → Discount thấp nhất (chờ hồi chạm, chưa cần fill)
|
||
int IctMss_SelectTargetH1Fvg()
|
||
{
|
||
int bestIdx = -1;
|
||
|
||
for(int i = 0; i < g_ictFvgCount; i++)
|
||
{
|
||
if(!IctFvg_MatchesBias(g_ictFvgZones[i]))
|
||
continue;
|
||
if(!IctFvg_IsEntryRepPd(g_ictFvgZones[i]))
|
||
continue;
|
||
if(g_ictFvgZones[i].state == ICT_FVG_USED && g_ictFvgZones[i].maxFillRatio < 0.01)
|
||
continue;
|
||
|
||
if(g_ictDailyBias.bias == ICT_BIAS_BEAR)
|
||
{
|
||
if(bestIdx < 0 || g_ictFvgZones[i].upper > g_ictFvgZones[bestIdx].upper)
|
||
bestIdx = i;
|
||
}
|
||
else if(g_ictDailyBias.bias == ICT_BIAS_BULL)
|
||
{
|
||
if(bestIdx < 0 || g_ictFvgZones[i].lower < g_ictFvgZones[bestIdx].lower)
|
||
bestIdx = i;
|
||
}
|
||
}
|
||
return bestIdx;
|
||
}
|
||
|
||
// Retest FVG H1 (POI): râu H1 chạm gap (InpMssH1RetestWickOnly) hoặc thêm % lấp thân/râu
|
||
bool IctMss_HasH1FvgRetest(const string sym, IctFvgZone &h1)
|
||
{
|
||
IctFvg_UpdateZoneState(sym, InpFvgTf, h1);
|
||
if(h1.firstTouchTime <= 0)
|
||
return false;
|
||
|
||
if(InpMssH1RetestWickOnly)
|
||
{
|
||
h1.mssH1Touch382 = true;
|
||
return true;
|
||
}
|
||
|
||
if(!IctFvg_HasFillAtLeast(h1, InpMssH1MinFillPct))
|
||
return false;
|
||
h1.mssH1Touch382 = true;
|
||
return true;
|
||
}
|
||
|
||
double IctMss_H1Atr(const string sym)
|
||
{
|
||
const int h = iATR(sym, InpFvgTf, InpFvgAtrPeriod);
|
||
if(h == INVALID_HANDLE)
|
||
return 0.0;
|
||
double buf[];
|
||
ArraySetAsSeries(buf, true);
|
||
if(CopyBuffer(h, 0, 1, 1, buf) != 1)
|
||
{
|
||
IndicatorRelease(h);
|
||
return 0.0;
|
||
}
|
||
IndicatorRelease(h);
|
||
return buf[0];
|
||
}
|
||
|
||
double IctMss_H1ZoneBuffer(const IctFvgZone &h1, const string sym)
|
||
{
|
||
const double gapH = h1.upper - h1.lower;
|
||
double buf = (gapH > _Point && InpMssMaxDistGapPct > 0.0) ?
|
||
gapH * InpMssMaxDistGapPct / 100.0 : 0.0;
|
||
const double atr = IctMss_H1Atr(sym);
|
||
if(atr > 0.0 && InpMssMaxDistAtrMult > 0.0)
|
||
buf = MathMax(buf, atr * InpMssMaxDistAtrMult);
|
||
if(buf <= 0.0)
|
||
buf = MathMax(gapH * 0.5, 20.0 * _Point);
|
||
return buf;
|
||
}
|
||
|
||
void IctMss_GetH1ProximityBounds(const IctFvgZone &h1, const string sym,
|
||
const ENUM_ICT_BIAS bias,
|
||
double &outLo, double &outHi)
|
||
{
|
||
const double gapH = h1.upper - h1.lower;
|
||
const double buf = IctMss_H1ZoneBuffer(h1, sym);
|
||
outLo = h1.lower - buf;
|
||
outHi = h1.upper + buf;
|
||
|
||
if(gapH <= _Point)
|
||
return;
|
||
|
||
if(bias == ICT_BIAS_BEAR && InpMssExtraBelowGapPct > 0.0)
|
||
outLo -= gapH * InpMssExtraBelowGapPct / 100.0;
|
||
if(bias == ICT_BIAS_BULL && InpMssExtraAboveGapPct > 0.0)
|
||
outHi += gapH * InpMssExtraAboveGapPct / 100.0;
|
||
}
|
||
|
||
bool IctMss_PriceNearH1Fvg(const IctFvgZone &h1, const string sym,
|
||
const double price, const ENUM_ICT_BIAS bias)
|
||
{
|
||
if(price <= 0.0 || h1.upper <= h1.lower)
|
||
return false;
|
||
double lo = 0.0, hi = 0.0;
|
||
IctMss_GetH1ProximityBounds(h1, sym, bias, lo, hi);
|
||
return (price >= lo - _Point && price <= hi + _Point);
|
||
}
|
||
|
||
bool IctMss_ChochWithinTouchWindow(const IctFvgZone &h1, const datetime chochTime)
|
||
{
|
||
const datetime tTouch = (h1.firstTouchTime > 0) ? h1.firstTouchTime : h1.createdTime;
|
||
if(chochTime > 0 && chochTime < tTouch)
|
||
return false;
|
||
|
||
if(InpMssMaxM5BarsAfterTouch > 0 && chochTime > 0)
|
||
{
|
||
const int sec = (int)PeriodSeconds(InpConfirmTf);
|
||
const datetime tMax = tTouch + (datetime)(InpMssMaxM5BarsAfterTouch * sec);
|
||
if(chochTime > tMax)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
int IctMss_MaxSwingShift(const IctSwingSet &sw)
|
||
{
|
||
int m = 1;
|
||
if(sw.hasH0)
|
||
m = MathMax(m, sw.h0.shift);
|
||
if(sw.hasL0)
|
||
m = MathMax(m, sw.l0.shift);
|
||
if(sw.hasH1)
|
||
m = MathMax(m, sw.h1.shift);
|
||
if(sw.hasL1)
|
||
m = MathMax(m, sw.l1.shift);
|
||
return m + 2;
|
||
}
|
||
|
||
bool IctMss_BodyBrokeLevelSince(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const int maxShift, const double level,
|
||
const bool wantBelow)
|
||
{
|
||
const int lim = MathMax(1, MathMin(maxShift, InpMssConfirmLookback));
|
||
for(int sh = 1; sh <= lim; sh++)
|
||
{
|
||
if(wantBelow && IctBodyBreakBelow(sym, tf, sh, level))
|
||
return true;
|
||
if(!wantBelow && IctBodyBreakAbove(sym, tf, sh, level))
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool IctMss_BarOverlapsFvg(const IctFvgZone &h1, const double barHi, const double barLo)
|
||
{
|
||
return (barHi >= h1.lower - _Point && barLo <= h1.upper + _Point);
|
||
}
|
||
|
||
IctSwingPoint IctMss_ProtectedLowBeforeHigh(const IctSwingPoint &lows[], const int nL,
|
||
const IctSwingPoint &h0,
|
||
const datetime tTouch)
|
||
{
|
||
IctSwingPoint empty;
|
||
empty.Clear();
|
||
if(!h0.Valid())
|
||
return empty;
|
||
|
||
int best = -1;
|
||
for(int i = 0; i < nL; i++)
|
||
{
|
||
if(lows[i].time < tTouch)
|
||
continue;
|
||
if(lows[i].shift <= h0.shift)
|
||
continue;
|
||
if(lows[i].price >= h0.price - _Point)
|
||
continue;
|
||
if(best < 0 || lows[i].shift < lows[best].shift)
|
||
best = i;
|
||
}
|
||
if(best < 0)
|
||
return empty;
|
||
return lows[best];
|
||
}
|
||
|
||
IctSwingPoint IctMss_ProtectedHighAfterLow(const IctSwingPoint &highs[], const int nH,
|
||
const IctSwingPoint &l0,
|
||
const datetime tTouch)
|
||
{
|
||
IctSwingPoint empty;
|
||
empty.Clear();
|
||
if(!l0.Valid())
|
||
return empty;
|
||
|
||
int best = -1;
|
||
for(int i = 0; i < nH; i++)
|
||
{
|
||
if(highs[i].time < tTouch || highs[i].time < l0.time)
|
||
continue;
|
||
if(highs[i].shift >= l0.shift)
|
||
continue;
|
||
if(highs[i].price <= l0.price + _Point)
|
||
continue;
|
||
if(best < 0 || highs[i].shift < highs[best].shift)
|
||
best = i;
|
||
}
|
||
if(best < 0)
|
||
return empty;
|
||
return highs[best];
|
||
}
|
||
|
||
bool IctMss_ReactionHighAfterTouch(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const IctFvgZone &h1, const datetime tTouch,
|
||
IctSwingPoint &outHi)
|
||
{
|
||
outHi.Clear();
|
||
IctSwingSet sw;
|
||
ENUM_ICT_STRUCT st = ICT_STRUCT_NONE;
|
||
if(IctBuildConfirmSwingSet(sym, sw, st) && sw.hasH0 && sw.h0.time >= tTouch)
|
||
{
|
||
outHi = sw.h0;
|
||
return true;
|
||
}
|
||
|
||
int touchSh = iBarShift(sym, tf, tTouch, true);
|
||
if(touchSh < 0)
|
||
touchSh = iBarShift(sym, tf, tTouch, false);
|
||
if(touchSh < 1)
|
||
return false;
|
||
|
||
double bestP = -DBL_MAX;
|
||
for(int sh = touchSh; sh >= 1; sh--)
|
||
{
|
||
if(iTime(sym, tf, sh) < tTouch)
|
||
continue;
|
||
const double hi = iHigh(sym, tf, sh);
|
||
const double lo = iLow(sym, tf, sh);
|
||
if(!IctMss_BarOverlapsFvg(h1, hi, lo))
|
||
continue;
|
||
if(hi > bestP)
|
||
{
|
||
bestP = hi;
|
||
outHi.price = hi;
|
||
outHi.time = iTime(sym, tf, sh);
|
||
outHi.shift = sh;
|
||
}
|
||
}
|
||
return outHi.Valid();
|
||
}
|
||
|
||
bool IctMss_ReactionLowAfterTouch(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const IctFvgZone &h1, const datetime tTouch,
|
||
IctSwingPoint &outLo)
|
||
{
|
||
outLo.Clear();
|
||
IctSwingSet sw;
|
||
ENUM_ICT_STRUCT st = ICT_STRUCT_NONE;
|
||
if(IctBuildConfirmSwingSet(sym, sw, st) && sw.hasL0 && sw.l0.time >= tTouch)
|
||
{
|
||
outLo = sw.l0;
|
||
return true;
|
||
}
|
||
|
||
int touchSh = iBarShift(sym, tf, tTouch, true);
|
||
if(touchSh < 0)
|
||
touchSh = iBarShift(sym, tf, tTouch, false);
|
||
if(touchSh < 1)
|
||
return false;
|
||
|
||
double bestP = DBL_MAX;
|
||
for(int sh = touchSh; sh >= 1; sh--)
|
||
{
|
||
if(iTime(sym, tf, sh) < tTouch)
|
||
continue;
|
||
const double hi = iHigh(sym, tf, sh);
|
||
const double lo = iLow(sym, tf, sh);
|
||
if(!IctMss_BarOverlapsFvg(h1, hi, lo))
|
||
continue;
|
||
if(lo < bestP)
|
||
{
|
||
bestP = lo;
|
||
outLo.price = lo;
|
||
outLo.time = iTime(sym, tf, sh);
|
||
outLo.shift = sh;
|
||
}
|
||
}
|
||
return outLo.Valid();
|
||
}
|
||
|
||
// MSS M5 sau retest FVG H1: pullback ngược cục bộ → phá L0 (bear HTF) hoặc H0 (bull HTF)
|
||
bool IctMss_FindMssAfterFvgRetest(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const IctFvgZone &h1, const datetime tTouch,
|
||
const ENUM_ICT_BIAS bias,
|
||
double &keyLevelOut, datetime &keyTimeOut,
|
||
datetime &mssBarTimeOut, double &slSwingOut)
|
||
{
|
||
keyLevelOut = 0.0;
|
||
keyTimeOut = 0;
|
||
mssBarTimeOut = 0;
|
||
slSwingOut = 0.0;
|
||
|
||
IctSwingPoint highs[], lows[];
|
||
IctCollectSwings(sym, tf, InpConfirmSwingRange, InpConfirmSwingLookback, highs, lows);
|
||
const int nH = ArraySize(highs);
|
||
const int nL = ArraySize(lows);
|
||
|
||
if(bias == ICT_BIAS_BEAR)
|
||
{
|
||
IctSwingPoint h0;
|
||
if(!IctMss_ReactionHighAfterTouch(sym, tf, h1, tTouch, h0))
|
||
return false;
|
||
|
||
IctSwingPoint keyLo = IctMss_ProtectedLowBeforeHigh(lows, nL, h0, tTouch);
|
||
if(!keyLo.Valid())
|
||
return false;
|
||
|
||
const int startSh = MathMax(1, h0.shift - 1);
|
||
for(int sh = startSh; sh >= 1; sh--)
|
||
{
|
||
if(iTime(sym, tf, sh) < h0.time)
|
||
continue;
|
||
if(!IctBodyBreakBelow(sym, tf, sh, keyLo.price))
|
||
continue;
|
||
|
||
keyLevelOut = keyLo.price;
|
||
keyTimeOut = keyLo.time;
|
||
mssBarTimeOut = iTime(sym, tf, sh);
|
||
slSwingOut = h0.price;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
if(bias == ICT_BIAS_BULL)
|
||
{
|
||
IctSwingPoint l0;
|
||
if(!IctMss_ReactionLowAfterTouch(sym, tf, h1, tTouch, l0))
|
||
return false;
|
||
|
||
IctSwingPoint keyHi = IctMss_ProtectedHighAfterLow(highs, nH, l0, tTouch);
|
||
if(!keyHi.Valid())
|
||
return false;
|
||
|
||
const int startSh = MathMax(1, l0.shift - 1);
|
||
for(int sh = startSh; sh >= 1; sh--)
|
||
{
|
||
if(iTime(sym, tf, sh) < l0.time)
|
||
continue;
|
||
if(!IctBodyBreakAbove(sym, tf, sh, keyHi.price))
|
||
continue;
|
||
|
||
keyLevelOut = keyHi.price;
|
||
keyTimeOut = keyHi.time;
|
||
mssBarTimeOut = iTime(sym, tf, sh);
|
||
slSwingOut = l0.price;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const IctFvgZone &h1, const ENUM_ICT_BIAS bias)
|
||
{
|
||
if(g_ictLowTf.mss.chochLocked && g_ictLowTf.mss.chochKeyLevel > 0.0)
|
||
return true;
|
||
|
||
const datetime tTouch = h1.firstTouchTime;
|
||
if(tTouch <= 0)
|
||
return false;
|
||
|
||
double keyLv = 0.0, slSwing = 0.0;
|
||
datetime keyT = 0, mssBarT = 0;
|
||
if(!IctMss_FindMssAfterFvgRetest(sym, tf, h1, tTouch, bias,
|
||
keyLv, keyT, mssBarT, slSwing))
|
||
return false;
|
||
|
||
g_ictLowTf.mss.chochKeyLevel = keyLv;
|
||
g_ictLowTf.mss.chochKeyTime = keyT;
|
||
g_ictLowTf.mss.slSwingPrice = slSwing;
|
||
g_ictLowTf.mss.chochTime = mssBarT;
|
||
g_ictLowTf.mss.chochLocked = true;
|
||
return true;
|
||
}
|
||
|
||
bool IctMss_ZonesOverlapH1(const IctFvgZone &h1, const string sym,
|
||
const ENUM_ICT_BIAS bias,
|
||
const double zoneLo, const double zoneHi)
|
||
{
|
||
if(zoneHi <= zoneLo)
|
||
return false;
|
||
double hLo = 0.0, hHi = 0.0;
|
||
IctMss_GetH1ProximityBounds(h1, sym, bias, hLo, hHi);
|
||
return !(zoneHi < hLo - _Point || zoneLo > hHi + _Point);
|
||
}
|
||
|
||
bool IctMss_IsChochNearH1Fvg(const string sym, const IctFvgZone &h1,
|
||
const ENUM_ICT_BIAS bias,
|
||
const double keyLevel, const double anchorPrice,
|
||
const datetime chochTime)
|
||
{
|
||
const bool nearAnchor = (anchorPrice > 0.0 &&
|
||
IctMss_PriceNearH1Fvg(h1, sym, anchorPrice, bias));
|
||
const bool nearKey = (keyLevel > 0.0 &&
|
||
IctMss_PriceNearH1Fvg(h1, sym, keyLevel, bias));
|
||
|
||
if(!nearAnchor && !nearKey)
|
||
return false;
|
||
|
||
return IctMss_ChochWithinTouchWindow(h1, chochTime);
|
||
}
|
||
|
||
bool IctMss_GetConfirmMssSwing(const string sym, const ENUM_ICT_BIAS bias, double &swingOut)
|
||
{
|
||
swingOut = 0.0;
|
||
|
||
IctSwingSet sw;
|
||
ENUM_ICT_STRUCT structural = ICT_STRUCT_NONE;
|
||
if(IctBuildConfirmSwingSet(sym, sw, structural))
|
||
{
|
||
if(bias == ICT_BIAS_BEAR && sw.hasH0)
|
||
{
|
||
swingOut = sw.h0.price;
|
||
return true;
|
||
}
|
||
if(bias == ICT_BIAS_BULL && sw.hasL0)
|
||
{
|
||
swingOut = sw.l0.price;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
IctSwingPoint highs[], lows[];
|
||
IctCollectSwings(sym, InpConfirmTf, InpConfirmSwingRange, InpConfirmSwingLookback,
|
||
highs, lows);
|
||
const int nH = ArraySize(highs);
|
||
const int nL = ArraySize(lows);
|
||
if(bias == ICT_BIAS_BEAR && nH > 0)
|
||
{
|
||
swingOut = highs[nH - 1].price;
|
||
return true;
|
||
}
|
||
if(bias == ICT_BIAS_BULL && nL > 0)
|
||
{
|
||
swingOut = lows[nL - 1].price;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool IctMss_FindExtremePivot(const IctSwingPoint &pts[], const int n,
|
||
const int maxShift, const bool wantHighest,
|
||
IctSwingPoint &out)
|
||
{
|
||
out.Clear();
|
||
int best = -1;
|
||
for(int i = 0; i < n; i++)
|
||
{
|
||
if(pts[i].shift > maxShift || pts[i].shift < 3)
|
||
continue;
|
||
if(best < 0)
|
||
best = i;
|
||
else if(wantHighest)
|
||
{
|
||
if(pts[i].price > pts[best].price)
|
||
best = i;
|
||
}
|
||
else
|
||
{
|
||
if(pts[i].price < pts[best].price)
|
||
best = i;
|
||
}
|
||
}
|
||
if(best < 0)
|
||
return false;
|
||
out = pts[best];
|
||
return true;
|
||
}
|
||
|
||
bool IctMss_DetectChoch(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const ENUM_ICT_BIAS bias,
|
||
double &keyLevelOut, datetime &keyTimeOut,
|
||
double &slSwingOut)
|
||
{
|
||
keyLevelOut = 0.0;
|
||
keyTimeOut = 0;
|
||
slSwingOut = 0.0;
|
||
|
||
IctSwingPoint highs[], lows[];
|
||
IctCollectSwings(sym, tf, InpConfirmSwingRange, InpConfirmSwingLookback,
|
||
highs, lows);
|
||
const int nH = ArraySize(highs);
|
||
const int nL = ArraySize(lows);
|
||
const int maxSh = InpMssConfirmLookback;
|
||
|
||
if(bias == ICT_BIAS_BULL)
|
||
{
|
||
IctSwingPoint extremeLo;
|
||
if(!IctMss_FindExtremePivot(lows, nL, maxSh, false, extremeLo))
|
||
return false;
|
||
|
||
IctSwingPoint keyHi = IctFindMostRecentOlder(highs, nH, extremeLo.shift);
|
||
if(!keyHi.Valid())
|
||
return false;
|
||
|
||
keyLevelOut = keyHi.price;
|
||
keyTimeOut = keyHi.time;
|
||
slSwingOut = extremeLo.price;
|
||
return IctBodyBreakAbove(sym, tf, 1, keyLevelOut);
|
||
}
|
||
|
||
if(bias == ICT_BIAS_BEAR)
|
||
{
|
||
IctSwingPoint extremeHi;
|
||
if(!IctMss_FindExtremePivot(highs, nH, maxSh, true, extremeHi))
|
||
return false;
|
||
|
||
IctSwingPoint keyLo = IctFindMostRecentOlder(lows, nL, extremeHi.shift);
|
||
if(!keyLo.Valid())
|
||
return false;
|
||
|
||
keyLevelOut = keyLo.price;
|
||
keyTimeOut = keyLo.time;
|
||
slSwingOut = extremeHi.price;
|
||
return IctBodyBreakBelow(sym, tf, 1, keyLevelOut);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool IctMss_PriceInEntryZone(const string sym, const ENUM_TIMEFRAMES tf,
|
||
const IctFvgZone &zone)
|
||
{
|
||
if(!IctFvg_HasFillAtLeast(zone, InpMssEntryFillPct))
|
||
return false;
|
||
|
||
const double fillPx = IctFvg_PriceAtFillPct(zone, InpMssEntryFillPct);
|
||
const double bid = SymbolInfoDouble(sym, SYMBOL_BID);
|
||
const double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
|
||
const double mid = (bid + ask) * 0.5;
|
||
|
||
if(zone.side == ICT_FVG_BULL)
|
||
return (mid <= fillPx + _Point && mid >= zone.lower - _Point);
|
||
if(zone.side == ICT_FVG_BEAR)
|
||
return (mid >= fillPx - _Point && mid <= zone.upper + _Point);
|
||
return false;
|
||
}
|
||
|
||
void IctMss_ResetState()
|
||
{
|
||
g_ictLowTf.mss.Clear();
|
||
IctMss_JournalReset();
|
||
}
|
||
|
||
void IctMss_Update(const string sym)
|
||
{
|
||
g_ictLowTf.mss.displayReason = IctMssPhaseText(g_ictLowTf.mss.phase);
|
||
|
||
if(!g_ictIntraday.isAllowTrade)
|
||
{
|
||
if(g_ictLowTf.mss.phase != ICT_MSS_IDLE)
|
||
IctMss_ResetState();
|
||
g_ictLowTf.mss.displayReason = "AllowTrade=false";
|
||
return;
|
||
}
|
||
|
||
const ENUM_TIMEFRAMES cTf = InpConfirmTf;
|
||
const ENUM_ICT_FVG_SIDE wantSide = IctFvgSideFromBias(g_ictDailyBias.bias);
|
||
if(wantSide == ICT_FVG_NONE)
|
||
{
|
||
IctMss_ResetState();
|
||
g_ictLowTf.mss.displayReason = "Bias none";
|
||
return;
|
||
}
|
||
|
||
if(g_ictLowTf.mss.phase == ICT_MSS_IDLE || g_ictLowTf.mss.h1FvgId == 0)
|
||
{
|
||
const int hIdx = IctMss_SelectTargetH1Fvg();
|
||
if(hIdx < 0)
|
||
{
|
||
g_ictLowTf.mss.displayReason = StringFormat("Chờ H1 %s FVG",
|
||
(wantSide == ICT_FVG_BEAR) ? "Premium" : "Discount");
|
||
return;
|
||
}
|
||
|
||
g_ictLowTf.mss.h1FvgId = g_ictFvgZones[hIdx].id;
|
||
|
||
if(!IctMss_HasH1FvgRetest(sym, g_ictFvgZones[hIdx]))
|
||
{
|
||
g_ictLowTf.mss.displayReason = InpMssH1RetestWickOnly ?
|
||
StringFormat("Chờ râu H1 chạm FVG %s [%.0f–%.0f]",
|
||
IctPdZoneText(IctFvg_GetRepPd(g_ictFvgZones[hIdx])),
|
||
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper) :
|
||
StringFormat("Chờ retest FVG H1 %s [%.0f–%.0f] (%.0f%% lấp)",
|
||
IctPdZoneText(IctFvg_GetRepPd(g_ictFvgZones[hIdx])),
|
||
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper,
|
||
InpMssH1MinFillPct);
|
||
return;
|
||
}
|
||
|
||
g_ictLowTf.mss.phase = ICT_MSS_H1_TOUCH;
|
||
g_ictLowTf.mss.h1TouchTime = g_ictFvgZones[hIdx].firstTouchTime;
|
||
g_ictLowTf.mss.displayReason = InpMssH1RetestWickOnly ?
|
||
"Retest FVG H1 OK | râu chạm POI" :
|
||
StringFormat("Retest FVG H1 OK | lấp %.0f%% (H1)",
|
||
g_ictFvgZones[hIdx].maxFillRatio * 100.0);
|
||
}
|
||
|
||
const int h1Idx = IctMss_FindH1FvgById(g_ictLowTf.mss.h1FvgId);
|
||
if(h1Idx < 0)
|
||
{
|
||
IctMss_ResetState();
|
||
g_ictLowTf.mss.displayReason = "H1 FVG mất";
|
||
return;
|
||
}
|
||
|
||
if(g_ictLowTf.mss.phase >= ICT_MSS_H1_TOUCH &&
|
||
!IctMss_HasH1FvgRetest(sym, g_ictFvgZones[h1Idx]))
|
||
{
|
||
IctMss_ResetState();
|
||
g_ictLowTf.mss.displayReason = "FVG H1 chưa retest đủ % — reset MSS";
|
||
return;
|
||
}
|
||
|
||
if(g_ictLowTf.mss.phase >= ICT_MSS_H1_TOUCH && g_ictLowTf.mss.h1TouchTime <= 0)
|
||
g_ictLowTf.mss.h1TouchTime = g_ictFvgZones[h1Idx].firstTouchTime;
|
||
|
||
if(g_ictLowTf.mss.phase == ICT_MSS_H1_TOUCH)
|
||
{
|
||
if(!IctMss_TryLockMss(sym, cTf, g_ictFvgZones[h1Idx], g_ictDailyBias.bias))
|
||
{
|
||
g_ictLowTf.mss.displayReason = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ?
|
||
"Retest FVG H1 OK — chờ MSS↓ phá L0 M5" :
|
||
"Retest FVG H1 OK — chờ MSS↑ phá H0 M5";
|
||
return;
|
||
}
|
||
|
||
const double keyLv = g_ictLowTf.mss.chochKeyLevel;
|
||
const double slSwing = g_ictLowTf.mss.slSwingPrice;
|
||
const datetime mssT = g_ictLowTf.mss.chochTime;
|
||
|
||
if(!IctMss_IsChochNearH1Fvg(sym, g_ictFvgZones[h1Idx], g_ictDailyBias.bias,
|
||
keyLv, slSwing, mssT))
|
||
{
|
||
g_ictLowTf.mss.displayReason = StringFormat(
|
||
"MSS khóa @ %.2f — xa H1 FVG, chờ gần hơn", keyLv);
|
||
return;
|
||
}
|
||
|
||
g_ictLowTf.mss.phase = ICT_MSS_CHOCH;
|
||
IctConfirmFvg_ScanNew(sym, cTf, wantSide, g_ictLowTf.mss.chochTime, true);
|
||
g_ictLowTf.mss.displayReason = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ?
|
||
StringFormat("MSS↓ khóa L0=%.2f | H0=%.2f — quét M5 FVG", keyLv, slSwing) :
|
||
StringFormat("MSS↑ khóa H0=%.2f | L0=%.2f — quét M5 FVG", keyLv, slSwing);
|
||
}
|
||
|
||
if(g_ictLowTf.mss.phase == ICT_MSS_CHOCH)
|
||
{
|
||
IctConfirmFvg_ScanNew(sym, cTf, wantSide, g_ictLowTf.mss.chochTime, false);
|
||
|
||
const int mIdx = IctConfirmFvg_FindLatestAfter(g_ictLowTf.mss.chochTime, wantSide);
|
||
if(mIdx < 0)
|
||
{
|
||
g_ictLowTf.mss.displayReason = "MSS OK — chờ M5 FVG";
|
||
return;
|
||
}
|
||
|
||
if(!IctMss_ZonesOverlapH1(g_ictFvgZones[h1Idx], sym, g_ictDailyBias.bias,
|
||
g_ictConfirmFvgZones[mIdx].lower,
|
||
g_ictConfirmFvgZones[mIdx].upper))
|
||
{
|
||
g_ictLowTf.mss.displayReason = "M5 FVG xa H1 FVG — chờ FVG gần hơn";
|
||
return;
|
||
}
|
||
|
||
g_ictLowTf.mss.phase = ICT_MSS_M5_FVG;
|
||
g_ictLowTf.mss.m5FvgId = g_ictConfirmFvgZones[mIdx].id;
|
||
g_ictLowTf.mss.m5FvgTime = g_ictConfirmFvgZones[mIdx].createdTime;
|
||
g_ictLowTf.mss.displayReason = StringFormat("M5 FVG #%I64u [%.2f–%.2f]",
|
||
g_ictLowTf.mss.m5FvgId,
|
||
g_ictConfirmFvgZones[mIdx].lower,
|
||
g_ictConfirmFvgZones[mIdx].upper);
|
||
}
|
||
|
||
if(g_ictLowTf.mss.phase >= ICT_MSS_M5_FVG && g_ictLowTf.mss.m5FvgId > 0)
|
||
{
|
||
const int mIdx = IctConfirmFvg_FindById(g_ictLowTf.mss.m5FvgId);
|
||
if(mIdx < 0)
|
||
{
|
||
g_ictLowTf.mss.phase = ICT_MSS_CHOCH;
|
||
g_ictLowTf.mss.m5FvgId = 0;
|
||
g_ictLowTf.mss.displayReason = "M5 FVG mất — quét lại";
|
||
return;
|
||
}
|
||
|
||
IctFvg_UpdateZoneState(sym, cTf, g_ictConfirmFvgZones[mIdx]);
|
||
|
||
if(IctMss_PriceInEntryZone(sym, cTf, g_ictConfirmFvgZones[mIdx]))
|
||
{
|
||
g_ictLowTf.mss.phase = ICT_MSS_READY;
|
||
g_ictLowTf.mss.displayReason = StringFormat("READY | M5 fill %.0f%% | %s %s",
|
||
g_ictConfirmFvgZones[mIdx].maxFillRatio * 100.0,
|
||
IctFvgSideText(wantSide),
|
||
IctPdZoneText(IctFvg_GetRepPd(g_ictFvgZones[h1Idx])));
|
||
}
|
||
else if(IctFvg_HasFillAtLeast(g_ictConfirmFvgZones[mIdx], InpMssEntryFillPct))
|
||
{
|
||
g_ictLowTf.mss.phase = ICT_MSS_ENTRY_FILL;
|
||
g_ictLowTf.mss.displayReason = StringFormat("M5 chạm %.0f%% — canh entry",
|
||
InpMssEntryFillPct);
|
||
}
|
||
else
|
||
{
|
||
g_ictLowTf.mss.phase = ICT_MSS_M5_FVG;
|
||
g_ictLowTf.mss.displayReason = StringFormat("Chờ hồi M5 FVG %.0f%% (fill %.0f%%)",
|
||
InpMssEntryFillPct,
|
||
g_ictConfirmFvgZones[mIdx].maxFillRatio * 100.0);
|
||
}
|
||
}
|
||
|
||
IctMss_JournalPipeline(g_ictLowTf.mss.displayReason);
|
||
}
|
||
|
||
#endif
|