CHoCH
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
//| BOS = Continue | CHoCH = Reversal |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "ICT 2026"
|
||||
#property version "1.112"
|
||||
#property description "Daily Bias + Intraday H1 + iTF FVG + IsAllowTrade"
|
||||
#property version "1.126"
|
||||
#property description "ICT2026 MSS limit entry + SL swing CHoCH + TP intraday"
|
||||
|
||||
#include <ICT2026/Config.mqh>
|
||||
#include <ICT2026/DailyBias.mqh>
|
||||
@@ -47,6 +47,7 @@ void OnDeinit(const int reason)
|
||||
IctPanel_Clear();
|
||||
IctDraw_Clear();
|
||||
IctFvgDraw_DeleteAll();
|
||||
IctMssDraw_DeleteAll();
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ void OnChartEvent(const int id, const long &lparam, const double &dparam, const
|
||||
IctPanel_Render(_Symbol);
|
||||
IctDraw_Render(_Symbol);
|
||||
IctFvgDraw_Render(_Symbol);
|
||||
IctMssDraw_Render(_Symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +94,8 @@ bool ICT2026_IsAllowTrade() { return g_ictIntraday.isAllowTrade; }
|
||||
|
||||
int ICT2026_GetFvgAvailableCount() { return g_ictLowTf.availableCount; }
|
||||
|
||||
ENUM_ICT_MSS_PHASE ICT2026_GetMssPhase() { return g_ictLowTf.mss.phase; }
|
||||
|
||||
string ICT2026_GetMssReason() { return g_ictLowTf.mss.displayReason; }
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
@@ -52,12 +52,35 @@ input double InpFvgUsedFillPct = 38.2; // % lấp FVG →
|
||||
input int InpFvgExpireDays = 3; // Xóa Available sau N ngày
|
||||
input int InpFvgMaxZones = 24; // Số FVG tối đa trên chart
|
||||
input bool InpDrawFvgZones = true; // Vẽ FVG + Premium/Discount
|
||||
input color InpFvgBullColor = clrDodgerBlue;
|
||||
input color InpFvgBearColor = clrOrangeRed;
|
||||
input color InpFvgUsedColor = clrDimGray;
|
||||
input color InpFvgBullColor = clrLimeGreen; // Bull FVG
|
||||
input color InpFvgBearColor = clrRed; // Bear FVG
|
||||
input color InpFvgUsedColor = clrDimGray; // Used FVG
|
||||
input color InpPdPremiumColor = clrMaroon;
|
||||
input color InpPdDiscountColor = clrDarkGreen;
|
||||
|
||||
input group "══ MSS / Entry (Confirm TF) ══"
|
||||
input double InpMssH1MinFillPct = 38.2; // Retest FVG H1 (POI): % lấp tối thiểu trên H1
|
||||
input double InpMssEntryFillPct = 38.2; // M5 FVG: % lấp để sẵn sàng entry
|
||||
input int InpMssConfirmLookback = 80; // Lookback pivot CHoCH trên Confirm TF
|
||||
input int InpMssConfirmFvgBars = 40; // Quét M5 FVG sau CHoCH
|
||||
input double InpMssMaxDistGapPct = 75.0; // MSS: buffer quanh H1 FVG (% chiều cao gap)
|
||||
input double InpMssMaxDistAtrMult = 1.25; // MSS: thêm buffer (× ATR H1)
|
||||
input double InpMssExtraBelowGapPct = 100.0; // Bear: thêm % gap dưới lower (MSS dưới FVG)
|
||||
input double InpMssExtraAboveGapPct = 100.0; // Bull: thêm % gap trên upper
|
||||
input int InpMssMaxM5BarsAfterTouch = 0; // MSS: max M5 bar sau chạm H1 (0=không giới hạn)
|
||||
input bool InpDrawConfirmFvg = true; // Vẽ FVG Confirm TF (màu nhạt)
|
||||
input bool InpDrawMssChoch = true; // Vẽ MSS sau H1 chạm FVG (CHoCH/H0)
|
||||
input bool InpMssLogJournal = true; // Log lý do chặn vào Experts journal
|
||||
|
||||
input group "══ MSS Trade ══"
|
||||
input bool InpMssTradeEnabled = true; // Đặt lệnh limit MSS
|
||||
input ulong InpMssMagic = 202604; // Magic number
|
||||
input double InpMssRiskPct = 1.0; // R % balance mỗi lệnh
|
||||
input double InpMssSlAtrMult = 0.5; // SL: buffer ngoài H0/L0 M5 (× ATR M5)
|
||||
input double InpMssMinRR = 2.0; // TP tối thiểu (× risk entry→SL)
|
||||
input int InpMssPendingExpireHours = 24; // Hết hạn pending (giờ, 0=không)
|
||||
input bool InpMssOnePosition = true; // Một position/pending MSS
|
||||
|
||||
input group "══ Debug ══"
|
||||
input bool InpDebug = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| ConfirmFvg.mqh — FVG trên Confirm TF (M5) cho MSS entry |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef ICT2026_CONFIRMFVG_MQH
|
||||
#define ICT2026_CONFIRMFVG_MQH
|
||||
|
||||
#include <ICT2026/Fvg.mqh>
|
||||
|
||||
IctFvgZone g_ictConfirmFvgZones[];
|
||||
int g_ictConfirmFvgCount = 0;
|
||||
static ulong g_ictConfirmFvgNextId = 100000;
|
||||
|
||||
void IctConfirmFvg_Reset()
|
||||
{
|
||||
g_ictConfirmFvgCount = 0;
|
||||
ArrayResize(g_ictConfirmFvgZones, 0);
|
||||
}
|
||||
|
||||
bool IctConfirmFvg_IsDuplicate(const datetime createdTime, const double upper, const double lower)
|
||||
{
|
||||
const double tol = _Point * 2.0;
|
||||
for(int i = 0; i < g_ictConfirmFvgCount; i++)
|
||||
{
|
||||
if(g_ictConfirmFvgZones[i].createdTime != createdTime)
|
||||
continue;
|
||||
if(MathAbs(g_ictConfirmFvgZones[i].upper - upper) <= tol &&
|
||||
MathAbs(g_ictConfirmFvgZones[i].lower - lower) <= tol)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void IctConfirmFvg_AddZone(const IctFvgZone &zone)
|
||||
{
|
||||
if(g_ictConfirmFvgCount >= InpFvgMaxZones)
|
||||
{
|
||||
for(int i = 0; i < g_ictConfirmFvgCount - 1; i++)
|
||||
g_ictConfirmFvgZones[i] = g_ictConfirmFvgZones[i + 1];
|
||||
g_ictConfirmFvgCount--;
|
||||
}
|
||||
ArrayResize(g_ictConfirmFvgZones, g_ictConfirmFvgCount + 1);
|
||||
g_ictConfirmFvgZones[g_ictConfirmFvgCount] = zone;
|
||||
g_ictConfirmFvgCount++;
|
||||
}
|
||||
|
||||
void IctConfirmFvg_UpdateAll(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
for(int i = 0; i < g_ictConfirmFvgCount; i++)
|
||||
IctFvg_UpdateZoneState(sym, tf, g_ictConfirmFvgZones[i]);
|
||||
}
|
||||
|
||||
void IctConfirmFvg_ScanNew(const string sym, const ENUM_TIMEFRAMES tf,
|
||||
const ENUM_ICT_FVG_SIDE wantSide,
|
||||
const datetime notBefore,
|
||||
const bool fullLookback = false)
|
||||
{
|
||||
if(wantSide == ICT_FVG_NONE)
|
||||
return;
|
||||
|
||||
const int maxShift = MathMin(InpMssConfirmFvgBars, Bars(sym, tf) - 3);
|
||||
if(maxShift < 1)
|
||||
return;
|
||||
|
||||
int fromSh = fullLookback ? maxShift : MathMin(maxShift, MathMax(1, InpFvgScanBarsPerUpdate));
|
||||
|
||||
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(notBefore > 0 && timeC < notBefore)
|
||||
continue;
|
||||
if(IctConfirmFvg_IsDuplicate(timeC, upper, lower))
|
||||
continue;
|
||||
|
||||
IctFvgZone zone;
|
||||
zone.Clear();
|
||||
zone.id = g_ictConfirmFvgNextId++;
|
||||
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);
|
||||
IctConfirmFvg_AddZone(zone);
|
||||
|
||||
if(InpDebug)
|
||||
PrintFormat("[ICT2026/CFVG] New %s [%.2f – %.2f] @ %s",
|
||||
IctFvgSideText(zone.side), zone.lower, zone.upper,
|
||||
TimeToString(timeC, TIME_DATE | TIME_MINUTES));
|
||||
}
|
||||
}
|
||||
|
||||
int IctConfirmFvg_FindLatestAfter(const datetime notBefore, const ENUM_ICT_FVG_SIDE side)
|
||||
{
|
||||
int bestIdx = -1;
|
||||
datetime bestT = 0;
|
||||
|
||||
for(int i = 0; i < g_ictConfirmFvgCount; i++)
|
||||
{
|
||||
if(g_ictConfirmFvgZones[i].side != side)
|
||||
continue;
|
||||
if(notBefore > 0 && g_ictConfirmFvgZones[i].createdTime < notBefore)
|
||||
continue;
|
||||
if(g_ictConfirmFvgZones[i].createdTime >= bestT)
|
||||
{
|
||||
bestT = g_ictConfirmFvgZones[i].createdTime;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
int IctConfirmFvg_FindById(const ulong id)
|
||||
{
|
||||
for(int i = 0; i < g_ictConfirmFvgCount; i++)
|
||||
if(g_ictConfirmFvgZones[i].id == id)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -90,13 +90,8 @@ void IctDraw_SwingSet(const string objPfx, const string tagPfx,
|
||||
|
||||
bool IctDraw_BuildConfirmSwings(const string sym, IctSwingSet &sw)
|
||||
{
|
||||
sw.Clear();
|
||||
ENUM_ICT_STRUCT structural = ICT_STRUCT_NONE;
|
||||
return IctBuildSwingSetRecentPivots(sym, InpConfirmTf,
|
||||
InpConfirmSwingRange,
|
||||
InpConfirmSwingLookback,
|
||||
InpConfirmRecentBars,
|
||||
sw, structural);
|
||||
return IctBuildConfirmSwingSet(sym, sw, structural);
|
||||
}
|
||||
|
||||
void IctDraw_Render(const string sym)
|
||||
|
||||
+115
-78
@@ -185,6 +185,42 @@ ENUM_ICT_PD_ZONE IctFvg_ClassifyPd(const ENUM_ICT_FVG_SIDE side,
|
||||
return ICT_PD_NONE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -220,104 +256,52 @@ bool IctFvg_IsConfirmedPivot(const string sym, const ENUM_TIMEFRAMES tf,
|
||||
return IctIsSwingLow(sym, tf, pt.shift, str);
|
||||
}
|
||||
|
||||
// Đỉnh PD bear = swing high gần nhất (theo thời gian) trên FVG tại lúc tạo FVG — không dùng iH0/iH1 live
|
||||
// Đỉ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)
|
||||
{
|
||||
outHigh = 0.0;
|
||||
datetime bestTime = 0;
|
||||
bool found = false;
|
||||
outHigh = 0.0;
|
||||
|
||||
const ENUM_TIMEFRAMES pdTf = InpIntradayTf;
|
||||
const int pdSec = (int)PeriodSeconds(pdTf);
|
||||
const datetime tCutoff = zone.createdTime + (datetime)pdSec;
|
||||
const datetime tMin = zone.createdTime - (datetime)(InpIntradayRecentBars * pdSec);
|
||||
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);
|
||||
|
||||
for(int i = 0; i < nH; i++)
|
||||
{
|
||||
if(highs[i].price <= zone.upper + _Point)
|
||||
continue;
|
||||
if(highs[i].time > tCutoff || highs[i].time < tMin)
|
||||
continue;
|
||||
if(!found || highs[i].time > bestTime)
|
||||
{
|
||||
bestTime = highs[i].time;
|
||||
outHigh = highs[i].price;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
IctSwingPoint pt = IctFindNearestSwingHighBefore(highs, nH, fvgShift, zone.upper);
|
||||
if(!pt.Valid())
|
||||
return false;
|
||||
|
||||
const int maxBars = MathMax(InpIntradayRecentBars, 30);
|
||||
for(int sh = 0; sh < maxBars && sh < Bars(sym, pdTf); sh++)
|
||||
{
|
||||
const datetime t = iTime(sym, pdTf, sh);
|
||||
if(t > tCutoff || t < tMin)
|
||||
continue;
|
||||
const double h = iHigh(sym, pdTf, sh);
|
||||
if(h <= zone.upper + _Point)
|
||||
continue;
|
||||
if(!found || t > bestTime)
|
||||
{
|
||||
bestTime = t;
|
||||
outHigh = h;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
outHigh = pt.price;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Đáy PD bull = swing low gần nhất (theo thời gian) dưới FVG tại lúc tạo FVG
|
||||
// Đá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)
|
||||
{
|
||||
outLow = 0.0;
|
||||
datetime bestTime = 0;
|
||||
bool found = false;
|
||||
outLow = 0.0;
|
||||
|
||||
const ENUM_TIMEFRAMES pdTf = InpIntradayTf;
|
||||
const int pdSec = (int)PeriodSeconds(pdTf);
|
||||
const datetime tCutoff = zone.createdTime + (datetime)pdSec;
|
||||
const datetime tMin = zone.createdTime - (datetime)(InpIntradayRecentBars * pdSec);
|
||||
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);
|
||||
|
||||
for(int i = 0; i < nL; i++)
|
||||
{
|
||||
if(lows[i].price >= zone.lower - _Point)
|
||||
continue;
|
||||
if(lows[i].time > tCutoff || lows[i].time < tMin)
|
||||
continue;
|
||||
if(!found || lows[i].time > bestTime)
|
||||
{
|
||||
bestTime = lows[i].time;
|
||||
outLow = lows[i].price;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
IctSwingPoint pt = IctFindNearestSwingLowBefore(lows, nL, fvgShift, zone.lower);
|
||||
if(!pt.Valid())
|
||||
return false;
|
||||
|
||||
const int maxBars = MathMax(InpIntradayRecentBars, 30);
|
||||
for(int sh = 0; sh < maxBars && sh < Bars(sym, pdTf); sh++)
|
||||
{
|
||||
const datetime t = iTime(sym, pdTf, sh);
|
||||
if(t > tCutoff || t < tMin)
|
||||
continue;
|
||||
const double l = iLow(sym, pdTf, sh);
|
||||
if(l >= zone.lower - _Point)
|
||||
continue;
|
||||
if(!found || t > bestTime)
|
||||
{
|
||||
bestTime = t;
|
||||
outLow = l;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
outLow = pt.price;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IctFvg_NearestLowBelow(const string sym, const double refLower,
|
||||
@@ -427,6 +411,45 @@ void IctFvg_FinalizePdMetrics(IctFvgZone &zone)
|
||||
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);
|
||||
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)
|
||||
@@ -445,6 +468,10 @@ void IctFvg_UpdatePdForZone(const string sym, IctFvgZone &zone)
|
||||
const int nL = ArraySize(lows);
|
||||
const int nH = ArraySize(highs);
|
||||
|
||||
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)
|
||||
@@ -455,7 +482,12 @@ void IctFvg_UpdatePdForZone(const string sym, IctFvgZone &zone)
|
||||
}
|
||||
|
||||
IctSwingPoint swingLow;
|
||||
if(IctFvg_FindFirstSwingLowAfter(lows, nL, zone.createdTime, swingLow) &&
|
||||
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;
|
||||
@@ -481,7 +513,12 @@ void IctFvg_UpdatePdForZone(const string sym, IctFvgZone &zone)
|
||||
}
|
||||
|
||||
IctSwingPoint swingHigh;
|
||||
if(IctFvg_FindFirstSwingHighAfter(highs, nH, zone.createdTime, swingHigh) &&
|
||||
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;
|
||||
|
||||
+26
-23
@@ -6,8 +6,11 @@
|
||||
|
||||
#include <ICT2026/Config.mqh>
|
||||
#include <ICT2026/Fvg.mqh>
|
||||
#include <ICT2026/ConfirmFvg.mqh>
|
||||
#include <ICT2026/LowTfApi.mqh>
|
||||
|
||||
const string ICT26_FVG_PFX = "ICT26_FVG_";
|
||||
const string ICT26_FVG_PFX = "ICT26_FVG_";
|
||||
const string ICT26_CFVG_PFX = "ICT26_CFVG_";
|
||||
|
||||
void IctFvgDraw_DeleteAll()
|
||||
{
|
||||
@@ -15,7 +18,7 @@ void IctFvgDraw_DeleteAll()
|
||||
for(int i = ObjectsTotal(ch, 0, -1) - 1; i >= 0; i--)
|
||||
{
|
||||
const string name = ObjectName(ch, i, 0, -1);
|
||||
if(StringFind(name, ICT26_FVG_PFX) == 0)
|
||||
if(StringFind(name, ICT26_FVG_PFX) == 0 || StringFind(name, ICT26_CFVG_PFX) == 0)
|
||||
ObjectDelete(ch, name);
|
||||
}
|
||||
}
|
||||
@@ -54,22 +57,6 @@ void IctFvgDraw_HLine(const string name, const datetime t1, const datetime t2,
|
||||
ObjectMove(ch, name, 1, t2, price);
|
||||
}
|
||||
|
||||
void IctFvgDraw_Label(const string name, const datetime t, const double price,
|
||||
const string text, const color clr)
|
||||
{
|
||||
const long ch = ChartID();
|
||||
if(ObjectFind(ch, name) < 0)
|
||||
ObjectCreate(ch, name, OBJ_TEXT, 0, t, price);
|
||||
|
||||
ObjectMove(ch, name, 0, t, price);
|
||||
ObjectSetString(ch, name, OBJPROP_TEXT, text);
|
||||
ObjectSetInteger(ch, name, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, InpChartLabelFontSize);
|
||||
ObjectSetString(ch, name, OBJPROP_FONT, "Arial Bold");
|
||||
ObjectSetInteger(ch, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
|
||||
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
void IctFvgDraw_Render(const string sym)
|
||||
{
|
||||
if(!InpDrawFvgZones)
|
||||
@@ -111,13 +98,29 @@ void IctFvgDraw_Render(const string sym)
|
||||
IctFvgDraw_HLine(pfx + "PD_EQ", pdStart, pdEnd,
|
||||
g_ictFvgZones[i].pdEq, clrWhite, STYLE_DOT);
|
||||
}
|
||||
}
|
||||
|
||||
string tag = StringFormat("iFVG %s %s", IctFvgSideText(g_ictFvgZones[i].side),
|
||||
IctFvgStateText(g_ictFvgZones[i].state));
|
||||
if(g_ictFvgZones[i].pdZone != ICT_PD_NONE)
|
||||
tag += " " + IctPdZoneText(g_ictFvgZones[i].pdZone);
|
||||
if(InpDrawConfirmFvg)
|
||||
{
|
||||
for(int j = 0; j < g_ictConfirmFvgCount; j++)
|
||||
{
|
||||
const string cid = IntegerToString((long)g_ictConfirmFvgZones[j].id);
|
||||
const string cpfx = ICT26_CFVG_PFX + cid + "_";
|
||||
const datetime cStart = g_ictConfirmFvgZones[j].createdTime;
|
||||
const datetime cEnd = IctFvg_GetFvgDrawTimeEnd(sym, InpConfirmTf, g_ictConfirmFvgZones[j]);
|
||||
|
||||
IctFvgDraw_Label(pfx + "LBL", fvgEnd, g_ictFvgZones[i].upper, tag, fvgClr);
|
||||
color cClr = (g_ictConfirmFvgZones[j].side == ICT_FVG_BULL) ?
|
||||
clrDarkGreen : clrFireBrick;
|
||||
if(g_ictConfirmFvgZones[j].state == ICT_FVG_USED)
|
||||
cClr = InpFvgUsedColor;
|
||||
|
||||
if(IctLowTf_MssM5FvgId() == g_ictConfirmFvgZones[j].id &&
|
||||
IctLowTf_MssPhase() >= ICT_MSS_M5_FVG)
|
||||
cClr = (g_ictConfirmFvgZones[j].side == ICT_FVG_BULL) ? clrAqua : clrOrange;
|
||||
|
||||
IctFvgDraw_Rect(cpfx + "BOX", cStart, g_ictConfirmFvgZones[j].upper,
|
||||
cEnd, g_ictConfirmFvgZones[j].lower, cClr, true);
|
||||
}
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Journal.mqh — log lý do chặn MSS/entry vào Experts journal |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef ICT2026_JOURNAL_MQH
|
||||
#define ICT2026_JOURNAL_MQH
|
||||
|
||||
#include <ICT2026/Config.mqh>
|
||||
|
||||
string g_ictMssJournalLast = "";
|
||||
string g_ictMssEntryJournalLast = "";
|
||||
|
||||
bool IctMss_IsEntryReadyReason(const string reason)
|
||||
{
|
||||
if(StringFind(reason, "READY") == 0)
|
||||
return true;
|
||||
if(StringFind(reason, "Limit ") >= 0)
|
||||
return true;
|
||||
if(StringFind(reason, "Sell/Buy limit") >= 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void IctMss_JournalPipeline(const string reason)
|
||||
{
|
||||
if(!InpMssLogJournal)
|
||||
return;
|
||||
if(IctMss_IsEntryReadyReason(reason))
|
||||
return;
|
||||
if(reason == g_ictMssJournalLast)
|
||||
return;
|
||||
|
||||
g_ictMssJournalLast = reason;
|
||||
PrintFormat("[ICT2026/MSS] %s", reason);
|
||||
}
|
||||
|
||||
void IctMss_JournalEntryBlock(const string reason)
|
||||
{
|
||||
if(!InpMssLogJournal)
|
||||
return;
|
||||
if(reason == g_ictMssEntryJournalLast)
|
||||
return;
|
||||
|
||||
g_ictMssEntryJournalLast = reason;
|
||||
PrintFormat("[ICT2026/Entry] Chặn lệnh: %s", reason);
|
||||
}
|
||||
|
||||
void IctMss_JournalReset()
|
||||
{
|
||||
g_ictMssJournalLast = "";
|
||||
g_ictMssEntryJournalLast = "";
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| LowTfApi.mqh — state Low TF / MSS (tránh include vòng) |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef ICT2026_LOWTFAPI_MQH
|
||||
#define ICT2026_LOWTFAPI_MQH
|
||||
|
||||
#include <ICT2026/Types.mqh>
|
||||
|
||||
IctLowTfState g_ictLowTf;
|
||||
|
||||
ulong IctLowTf_MssM5FvgId() { return g_ictLowTf.mss.m5FvgId; }
|
||||
|
||||
ENUM_ICT_MSS_PHASE IctLowTf_MssPhase() { return g_ictLowTf.mss.phase; }
|
||||
|
||||
#endif
|
||||
@@ -6,22 +6,29 @@
|
||||
|
||||
#include <ICT2026/Config.mqh>
|
||||
#include <ICT2026/Fvg.mqh>
|
||||
#include <ICT2026/LowTfApi.mqh>
|
||||
#include <ICT2026/MssSetup.mqh>
|
||||
#include <ICT2026/MssEntry.mqh>
|
||||
#include <ICT2026/FvgDraw.mqh>
|
||||
|
||||
IctLowTfState g_ictLowTf;
|
||||
#include <ICT2026/MssDraw.mqh>
|
||||
|
||||
void IctLowTfTrend_Reset()
|
||||
{
|
||||
g_ictLowTf.lastBarTime = 0;
|
||||
g_ictLowTf.activeCount = 0;
|
||||
g_ictLowTf.availableCount = 0;
|
||||
g_ictLowTf.displayReason = "";
|
||||
g_ictLowTf.lastBarTime = 0;
|
||||
g_ictLowTf.lastConfirmBarTime = 0;
|
||||
g_ictLowTf.activeCount = 0;
|
||||
g_ictLowTf.availableCount = 0;
|
||||
g_ictLowTf.confirmFvgCount = 0;
|
||||
g_ictLowTf.displayReason = "";
|
||||
g_ictLowTf.mss.Clear();
|
||||
IctFvg_Reset();
|
||||
IctConfirmFvg_Reset();
|
||||
}
|
||||
|
||||
bool IctLowTfTrend_Init(const string sym)
|
||||
{
|
||||
IctLowTfTrend_Reset();
|
||||
IctMssEntry_Init();
|
||||
if(Bars(sym, InpFvgTf) < 10)
|
||||
return false;
|
||||
IctLowTfTrend_Update(sym, true);
|
||||
@@ -62,6 +69,17 @@ bool IctLowTfTrend_Update(const string sym, const bool force = false)
|
||||
g_ictLowTf.activeCount = g_ictFvgCount;
|
||||
g_ictLowTf.availableCount = IctFvg_CountAvailable();
|
||||
|
||||
const datetime cBar0 = iTime(sym, InpConfirmTf, 0);
|
||||
const bool confirmNewBar = (force || cBar0 != g_ictLowTf.lastConfirmBarTime);
|
||||
if(confirmNewBar)
|
||||
{
|
||||
g_ictLowTf.lastConfirmBarTime = cBar0;
|
||||
IctConfirmFvg_UpdateAll(sym, InpConfirmTf);
|
||||
IctMss_Update(sym);
|
||||
IctMssEntry_Update(sym);
|
||||
g_ictLowTf.confirmFvgCount = g_ictConfirmFvgCount;
|
||||
}
|
||||
|
||||
if(InpDebug && (force || g_ictIntraday.isAllowTrade))
|
||||
PrintFormat("[ICT2026/LowTF] %s | Allow=%s | FVG avail=%d total=%d | %s",
|
||||
sym,
|
||||
@@ -70,6 +88,7 @@ bool IctLowTfTrend_Update(const string sym, const bool force = false)
|
||||
g_ictLowTf.displayReason);
|
||||
|
||||
IctFvgDraw_Render(sym);
|
||||
IctMssDraw_Render(sym);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,8 +122,27 @@ void IctLowTfTrend_TickRefresh(const string sym)
|
||||
MathAbs(g_ictFvgZones[i].pdLow - oldPdLo[i]) > _Point)
|
||||
dirty = true;
|
||||
}
|
||||
if(g_ictIntraday.isAllowTrade)
|
||||
{
|
||||
const ENUM_ICT_MSS_PHASE prevMss = g_ictLowTf.mss.phase;
|
||||
IctMss_Update(sym);
|
||||
IctMssEntry_Update(sym);
|
||||
if(prevMss != g_ictLowTf.mss.phase)
|
||||
dirty = true;
|
||||
}
|
||||
else if(g_ictLowTf.mss.pendingTicket > 0)
|
||||
{
|
||||
IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket);
|
||||
g_ictLowTf.mss.pendingTicket = 0;
|
||||
}
|
||||
|
||||
if(dirty)
|
||||
IctFvgDraw_Render(sym);
|
||||
|
||||
if(g_ictLowTf.mss.phase >= ICT_MSS_CHOCH)
|
||||
IctMssDraw_Render(sym);
|
||||
else if(!InpDrawMssChoch)
|
||||
IctMssDraw_DeleteAll();
|
||||
}
|
||||
|
||||
void IctLowTfTrend_Get(IctLowTfState &out) { out = g_ictLowTf; }
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| MssDraw.mqh — vẽ MSS sau H1 chạm FVG: touch, 38.2%, CHoCH, H0 |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef ICT2026_MSSDRAW_MQH
|
||||
#define ICT2026_MSSDRAW_MQH
|
||||
|
||||
#include <ICT2026/Config.mqh>
|
||||
#include <ICT2026/LowTfApi.mqh>
|
||||
#include <ICT2026/MssSetup.mqh>
|
||||
|
||||
const string ICT26_MSS_PFX = "ICT26_MSS_";
|
||||
|
||||
void IctMssDraw_DeleteAll()
|
||||
{
|
||||
const long ch = ChartID();
|
||||
for(int i = ObjectsTotal(ch, 0, -1) - 1; i >= 0; i--)
|
||||
{
|
||||
const string name = ObjectName(ch, i, 0, -1);
|
||||
if(StringFind(name, ICT26_MSS_PFX) == 0)
|
||||
ObjectDelete(ch, name);
|
||||
}
|
||||
}
|
||||
|
||||
void IctMssDraw_HLine(const string name, const datetime t1, const datetime t2,
|
||||
const double price, const color clr, const ENUM_LINE_STYLE style,
|
||||
const int width = 1)
|
||||
{
|
||||
const long ch = ChartID();
|
||||
const string obj = ICT26_MSS_PFX + name;
|
||||
if(ObjectFind(ch, obj) < 0)
|
||||
ObjectCreate(ch, obj, OBJ_TREND, 0, t1, price, t2, price);
|
||||
|
||||
ObjectSetInteger(ch, obj, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_STYLE, style);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_WIDTH, width);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_SELECTABLE, false);
|
||||
ObjectMove(ch, obj, 0, t1, price);
|
||||
ObjectMove(ch, obj, 1, t2, price);
|
||||
}
|
||||
|
||||
void IctMssDraw_VLine(const string name, const datetime t, const color clr)
|
||||
{
|
||||
const long ch = ChartID();
|
||||
const string obj = ICT26_MSS_PFX + name;
|
||||
if(ObjectFind(ch, obj) < 0)
|
||||
ObjectCreate(ch, obj, OBJ_VLINE, 0, t, 0.0);
|
||||
|
||||
ObjectSetInteger(ch, obj, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_STYLE, STYLE_DOT);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_SELECTABLE, false);
|
||||
ObjectMove(ch, obj, 0, t, 0.0);
|
||||
}
|
||||
|
||||
void IctMssDraw_Label(const string name, const string text,
|
||||
const datetime t, const double price,
|
||||
const bool above, const color clr)
|
||||
{
|
||||
const long ch = ChartID();
|
||||
const string obj = ICT26_MSS_PFX + name;
|
||||
if(ObjectFind(ch, obj) < 0)
|
||||
ObjectCreate(ch, obj, OBJ_TEXT, 0, t, price);
|
||||
|
||||
ObjectMove(ch, obj, 0, t, price);
|
||||
ObjectSetString(ch, obj, OBJPROP_TEXT, text);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_COLOR, clr);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_FONTSIZE, InpChartLabelFontSize);
|
||||
ObjectSetString(ch, obj, OBJPROP_FONT, "Arial Bold");
|
||||
ObjectSetInteger(ch, obj, OBJPROP_ANCHOR, above ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER);
|
||||
ObjectSetInteger(ch, obj, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
datetime IctMssDraw_TimeEnd(const string sym)
|
||||
{
|
||||
const ENUM_TIMEFRAMES cTf = InpConfirmTf;
|
||||
return iTime(sym, cTf, 0) + (datetime)PeriodSeconds(cTf);
|
||||
}
|
||||
|
||||
void IctMssDraw_ResolveChoch(const string sym, const IctFvgZone &h1,
|
||||
const IctMssState &mss,
|
||||
double &keyLv, datetime &keyT, double &slSwing, datetime &chochT)
|
||||
{
|
||||
keyLv = mss.chochKeyLevel;
|
||||
keyT = mss.chochKeyTime;
|
||||
slSwing = mss.slSwingPrice;
|
||||
chochT = mss.chochTime;
|
||||
|
||||
if(keyLv <= 0.0 && mss.phase >= ICT_MSS_H1_TOUCH)
|
||||
{
|
||||
datetime kt = 0;
|
||||
double ss = 0.0;
|
||||
if(IctMss_DetectChochAfterH1FvgRetest(sym, InpConfirmTf, h1, g_ictDailyBias.bias,
|
||||
keyLv, kt, ss))
|
||||
{
|
||||
keyT = kt;
|
||||
slSwing = ss;
|
||||
chochT = iTime(sym, InpConfirmTf, 1);
|
||||
if(chochT == 0)
|
||||
chochT = kt;
|
||||
}
|
||||
}
|
||||
|
||||
if(chochT <= 0)
|
||||
chochT = keyT;
|
||||
}
|
||||
|
||||
void IctMssDraw_Render(const string sym)
|
||||
{
|
||||
if(!InpDrawMssChoch)
|
||||
{
|
||||
IctMssDraw_DeleteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const IctMssState mss = g_ictLowTf.mss;
|
||||
if(mss.phase < ICT_MSS_H1_TOUCH || mss.h1FvgId == 0)
|
||||
{
|
||||
IctMssDraw_DeleteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const int h1Idx = IctMss_FindH1FvgById(mss.h1FvgId);
|
||||
if(h1Idx < 0)
|
||||
{
|
||||
IctMssDraw_DeleteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
IctMssDraw_DeleteAll();
|
||||
|
||||
const IctFvgZone h1 = g_ictFvgZones[h1Idx];
|
||||
const datetime tEnd = IctMssDraw_TimeEnd(sym);
|
||||
const bool isBear = (g_ictDailyBias.bias == ICT_BIAS_BEAR);
|
||||
|
||||
datetime tTouch = mss.h1TouchTime;
|
||||
if(tTouch <= 0)
|
||||
tTouch = h1.firstTouchTime;
|
||||
if(tTouch <= 0)
|
||||
tTouch = h1.createdTime;
|
||||
|
||||
if(tTouch > 0)
|
||||
{
|
||||
IctMssDraw_VLine("H1_TOUCH", tTouch, clrDodgerBlue);
|
||||
IctMssDraw_Label("H1_TOUCH_LBL", " Retest FVG H1 ", tTouch, h1.upper, true, clrDodgerBlue);
|
||||
}
|
||||
|
||||
const double fill382 = IctFvg_PriceAtFillPct(h1, InpMssH1MinFillPct);
|
||||
if(fill382 > 0.0 && tTouch > 0)
|
||||
{
|
||||
IctMssDraw_HLine("H1_382", tTouch, tEnd, fill382, clrSilver, STYLE_DOT, 1);
|
||||
IctMssDraw_Label("H1_382_LBL",
|
||||
StringFormat(" H1 %.0f%% ", InpMssH1MinFillPct),
|
||||
tTouch, fill382, !isBear, clrSilver);
|
||||
}
|
||||
|
||||
double keyLv = 0.0, slSwing = 0.0;
|
||||
datetime keyT = 0, chochT = 0;
|
||||
IctMssDraw_ResolveChoch(sym, h1, mss, keyLv, keyT, slSwing, chochT);
|
||||
|
||||
if(keyLv > 0.0)
|
||||
{
|
||||
if(keyT <= 0)
|
||||
keyT = tTouch;
|
||||
if(chochT <= 0)
|
||||
chochT = keyT;
|
||||
|
||||
const color clrChoch = clrGold;
|
||||
const string chochLbl = isBear ? " CHoCH↓ " : " CHoCH↑ ";
|
||||
const bool chochAbove = !isBear;
|
||||
const bool chochPending = (mss.phase == ICT_MSS_H1_TOUCH);
|
||||
|
||||
IctMssDraw_HLine("CHOCH", keyT, tEnd, keyLv, clrChoch,
|
||||
chochPending ? STYLE_DASH : STYLE_SOLID, 2);
|
||||
IctMssDraw_Label("CHOCH_LBL", chochLbl, tEnd, keyLv, chochAbove, clrChoch);
|
||||
}
|
||||
|
||||
if(slSwing <= 0.0)
|
||||
{
|
||||
double mssSwing = 0.0;
|
||||
if(IctMss_GetConfirmMssSwing(sym, g_ictDailyBias.bias, mssSwing))
|
||||
slSwing = mssSwing;
|
||||
}
|
||||
|
||||
if(slSwing > 0.0)
|
||||
{
|
||||
const string swTag = isBear ? " H0 " : " L0 ";
|
||||
const color clrSw = clrYellow;
|
||||
datetime tSw = (keyT > 0) ? keyT : tTouch;
|
||||
|
||||
IctSwingSet sw;
|
||||
ENUM_ICT_STRUCT structural = ICT_STRUCT_NONE;
|
||||
if(IctBuildConfirmSwingSet(sym, sw, structural))
|
||||
{
|
||||
if(isBear && sw.hasH0)
|
||||
tSw = sw.h0.time;
|
||||
else if(!isBear && sw.hasL0)
|
||||
tSw = sw.l0.time;
|
||||
}
|
||||
|
||||
IctMssDraw_HLine("MSS_SW", tSw, tEnd, slSwing, clrSw, STYLE_DASH, 1);
|
||||
IctMssDraw_Label("MSS_SW_LBL", swTag, tEnd, slSwing, true, clrSw);
|
||||
}
|
||||
|
||||
if(mss.pendingEntry > 0.0 && mss.phase >= ICT_MSS_M5_FVG)
|
||||
{
|
||||
datetime tSw = (keyT > 0) ? keyT : tTouch;
|
||||
IctMssDraw_HLine("ENTRY", tEnd - (datetime)PeriodSeconds(InpConfirmTf) * 3,
|
||||
tEnd, mss.pendingEntry, clrAqua, STYLE_DOT, 1);
|
||||
if(mss.pendingSl > 0.0)
|
||||
IctMssDraw_HLine("SL", tSw, tEnd, mss.pendingSl, clrOrangeRed, STYLE_DOT, 1);
|
||||
if(mss.pendingTp > 0.0)
|
||||
IctMssDraw_HLine("TP", tSw, tEnd, mss.pendingTp, clrLimeGreen, STYLE_DOT, 1);
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,320 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| MssEntry.mqh — Limit @ M5 FVG edge | SL H0/L0 M5 | TP min RR |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef ICT2026_MSSENTRY_MQH
|
||||
#define ICT2026_MSSENTRY_MQH
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
#include <ICT2026/Journal.mqh>
|
||||
#include <ICT2026/MssSetup.mqh>
|
||||
#include <ICT2026/IntradayStructure.mqh>
|
||||
|
||||
CTrade g_ictMssTrade;
|
||||
|
||||
double IctMssEntry_NormalizePrice(const string sym, const double price)
|
||||
{
|
||||
const double tick = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
|
||||
if(tick <= 0.0)
|
||||
return NormalizeDouble(price, (int)SymbolInfoInteger(sym, SYMBOL_DIGITS));
|
||||
return NormalizeDouble(MathRound(price / tick) * tick,
|
||||
(int)SymbolInfoInteger(sym, SYMBOL_DIGITS));
|
||||
}
|
||||
|
||||
double IctMssEntry_NormalizeVolume(const string sym, double vol)
|
||||
{
|
||||
const double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
||||
const double vmin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
||||
const double vmax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
||||
if(step <= 0.0)
|
||||
return 0.0;
|
||||
vol = MathFloor(vol / step) * step;
|
||||
if(vol < vmin - 1e-12)
|
||||
return 0.0;
|
||||
if(vol > vmax)
|
||||
vol = vmax;
|
||||
return NormalizeDouble(vol, 2);
|
||||
}
|
||||
|
||||
double IctMssEntry_VolumeForRisk(const string sym, const bool isBuy,
|
||||
const double entry, const double sl)
|
||||
{
|
||||
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
|
||||
if(MathAbs(entry - sl) < pt)
|
||||
return 0.0;
|
||||
|
||||
const double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpMssRiskPct / 100.0);
|
||||
double profit = 0.0;
|
||||
if(!OrderCalcProfit(isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,
|
||||
sym, 1.0, entry, sl, profit))
|
||||
return 0.0;
|
||||
|
||||
const double lossPerLot = MathAbs(profit);
|
||||
if(lossPerLot < DBL_EPSILON)
|
||||
return 0.0;
|
||||
|
||||
return riskMoney / lossPerLot;
|
||||
}
|
||||
|
||||
double IctMssEntry_Atr(const string sym, const ENUM_TIMEFRAMES tf, const int period)
|
||||
{
|
||||
const int h = iATR(sym, tf, period);
|
||||
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 IctMssEntry_LimitPrice(const IctFvgZone &zone)
|
||||
{
|
||||
if(zone.side == ICT_FVG_BULL)
|
||||
return zone.upper;
|
||||
if(zone.side == ICT_FVG_BEAR)
|
||||
return zone.lower;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
|
||||
double &entryOut, double &slOut, double &tpOut)
|
||||
{
|
||||
entryOut = slOut = tpOut = 0.0;
|
||||
|
||||
entryOut = IctMssEntry_LimitPrice(m5Zone);
|
||||
if(entryOut <= 0.0)
|
||||
return false;
|
||||
|
||||
const bool isBuy = (m5Zone.side == ICT_FVG_BULL);
|
||||
const ENUM_ICT_BIAS bias = isBuy ? ICT_BIAS_BULL : ICT_BIAS_BEAR;
|
||||
|
||||
double swingSl = 0.0;
|
||||
if(!IctMss_GetConfirmMssSwing(sym, bias, swingSl))
|
||||
{
|
||||
if(g_ictLowTf.mss.slSwingPrice > 0.0)
|
||||
swingSl = g_ictLowTf.mss.slSwingPrice;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const double atrM5 = IctMssEntry_Atr(sym, InpConfirmTf, InpFvgAtrPeriod);
|
||||
const double bufSl = (atrM5 > 0.0) ? atrM5 * InpMssSlAtrMult : 10.0 * _Point;
|
||||
const double minRR = MathMax(1.0, InpMssMinRR);
|
||||
|
||||
if(isBuy)
|
||||
{
|
||||
slOut = swingSl - bufSl;
|
||||
const double risk = entryOut - slOut;
|
||||
if(risk <= _Point)
|
||||
return false;
|
||||
tpOut = entryOut + risk * minRR;
|
||||
}
|
||||
else
|
||||
{
|
||||
slOut = swingSl + bufSl;
|
||||
const double risk = slOut - entryOut;
|
||||
if(risk <= _Point)
|
||||
return false;
|
||||
tpOut = entryOut - risk * minRR;
|
||||
}
|
||||
|
||||
entryOut = IctMssEntry_NormalizePrice(sym, entryOut);
|
||||
slOut = IctMssEntry_NormalizePrice(sym, slOut);
|
||||
tpOut = IctMssEntry_NormalizePrice(sym, tpOut);
|
||||
|
||||
if(isBuy && (slOut >= entryOut - _Point || tpOut <= entryOut + _Point))
|
||||
return false;
|
||||
if(!isBuy && (slOut <= entryOut + _Point || tpOut >= entryOut - _Point))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IctMssEntry_HasOpenExposure(const string sym)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != sym)
|
||||
continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMssMagic)
|
||||
return true;
|
||||
}
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
const ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0 || !OrderSelect(ticket))
|
||||
continue;
|
||||
if(OrderGetString(ORDER_SYMBOL) != sym)
|
||||
continue;
|
||||
if((ulong)OrderGetInteger(ORDER_MAGIC) != InpMssMagic)
|
||||
continue;
|
||||
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||||
if(t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IctMssEntry_CancelTicket(const ulong ticket)
|
||||
{
|
||||
if(ticket == 0)
|
||||
return true;
|
||||
if(!OrderSelect(ticket))
|
||||
return true;
|
||||
return g_ictMssTrade.OrderDelete(ticket);
|
||||
}
|
||||
|
||||
bool IctMssEntry_PlaceLimit(const string sym, const bool isBuy,
|
||||
const double entry, const double sl, const double tp,
|
||||
const double volume, ulong &ticketOut)
|
||||
{
|
||||
ticketOut = 0;
|
||||
g_ictMssTrade.SetExpertMagicNumber(InpMssMagic);
|
||||
g_ictMssTrade.SetDeviationInPoints(20);
|
||||
|
||||
const string cmt = StringFormat("ICT26_MSS %s", isBuy ? "BUY" : "SELL");
|
||||
bool ok = false;
|
||||
if(InpMssPendingExpireHours > 0)
|
||||
{
|
||||
const datetime exp = TimeCurrent() + (datetime)(InpMssPendingExpireHours * 3600);
|
||||
if(isBuy)
|
||||
ok = g_ictMssTrade.BuyLimit(volume, entry, sym, sl, tp, ORDER_TIME_SPECIFIED, exp, cmt);
|
||||
else
|
||||
ok = g_ictMssTrade.SellLimit(volume, entry, sym, sl, tp, ORDER_TIME_SPECIFIED, exp, cmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isBuy)
|
||||
ok = g_ictMssTrade.BuyLimit(volume, entry, sym, sl, tp, ORDER_TIME_GTC, 0, cmt);
|
||||
else
|
||||
ok = g_ictMssTrade.SellLimit(volume, entry, sym, sl, tp, ORDER_TIME_GTC, 0, cmt);
|
||||
}
|
||||
|
||||
if(ok)
|
||||
ticketOut = g_ictMssTrade.ResultOrder();
|
||||
else if(InpDebug)
|
||||
PrintFormat("[ICT2026/Entry] Order fail %d — %s", g_ictMssTrade.ResultRetcode(),
|
||||
g_ictMssTrade.ResultRetcodeDescription());
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void IctMssEntry_Init()
|
||||
{
|
||||
g_ictMssTrade.SetExpertMagicNumber(InpMssMagic);
|
||||
}
|
||||
|
||||
void IctMssEntry_Update(const string sym)
|
||||
{
|
||||
if(g_ictLowTf.mss.phase == ICT_MSS_IDLE && g_ictLowTf.mss.pendingTicket > 0)
|
||||
{
|
||||
IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket);
|
||||
g_ictLowTf.mss.pendingTicket = 0;
|
||||
}
|
||||
|
||||
if(!InpMssTradeEnabled)
|
||||
{
|
||||
if(g_ictLowTf.mss.phase >= ICT_MSS_M5_FVG)
|
||||
IctMss_JournalEntryBlock("InpMssTradeEnabled=false");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!g_ictIntraday.isAllowTrade || g_ictLowTf.mss.phase < ICT_MSS_M5_FVG ||
|
||||
g_ictLowTf.mss.m5FvgId == 0)
|
||||
{
|
||||
if(g_ictLowTf.mss.pendingTicket > 0)
|
||||
{
|
||||
IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket);
|
||||
g_ictLowTf.mss.pendingTicket = 0;
|
||||
}
|
||||
if(g_ictLowTf.mss.phase >= ICT_MSS_CHOCH)
|
||||
{
|
||||
if(!g_ictIntraday.isAllowTrade)
|
||||
IctMss_JournalEntryBlock("AllowTrade=false");
|
||||
else if(g_ictLowTf.mss.phase < ICT_MSS_M5_FVG)
|
||||
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
|
||||
else if(g_ictLowTf.mss.m5FvgId == 0)
|
||||
IctMss_JournalEntryBlock("Chưa có M5 FVG cho entry");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(InpMssOnePosition && IctMssEntry_HasOpenExposure(sym))
|
||||
{
|
||||
if(g_ictLowTf.mss.pendingTicket > 0 && !OrderSelect(g_ictLowTf.mss.pendingTicket))
|
||||
g_ictLowTf.mss.pendingTicket = 0;
|
||||
g_ictLowTf.mss.displayReason = StringFormat("%s | position/pending active",
|
||||
IctMssPhaseText(g_ictLowTf.mss.phase));
|
||||
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
|
||||
return;
|
||||
}
|
||||
|
||||
const int mIdx = IctConfirmFvg_FindById(g_ictLowTf.mss.m5FvgId);
|
||||
if(mIdx < 0)
|
||||
return;
|
||||
|
||||
const IctFvgZone m5 = g_ictConfirmFvgZones[mIdx];
|
||||
double entry = 0.0, sl = 0.0, tp = 0.0;
|
||||
if(!IctMssEntry_ComputeLevels(sym, m5, entry, sl, tp))
|
||||
{
|
||||
g_ictLowTf.mss.displayReason = StringFormat("%s | chờ H0/L0 M5 cho SL",
|
||||
IctMssPhaseText(g_ictLowTf.mss.phase));
|
||||
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool isBuy = (m5.side == ICT_FVG_BULL);
|
||||
const double vol = IctMssEntry_NormalizeVolume(sym,
|
||||
IctMssEntry_VolumeForRisk(sym, isBuy, entry, sl));
|
||||
if(vol <= 0.0)
|
||||
{
|
||||
g_ictLowTf.mss.displayReason = "Lot=0 (SL quá gần hoặc risk)";
|
||||
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
|
||||
return;
|
||||
}
|
||||
|
||||
const double risk = isBuy ? (entry - sl) : (sl - entry);
|
||||
const double rr = (risk > _Point) ? (isBuy ? (tp - entry) : (entry - tp)) / risk : 0.0;
|
||||
|
||||
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT) * 2.0;
|
||||
if(g_ictLowTf.mss.pendingTicket > 0 && OrderSelect(g_ictLowTf.mss.pendingTicket))
|
||||
{
|
||||
if(MathAbs(g_ictLowTf.mss.pendingEntry - entry) < pt &&
|
||||
MathAbs(g_ictLowTf.mss.pendingSl - sl) < pt &&
|
||||
MathAbs(g_ictLowTf.mss.pendingTp - tp) < pt)
|
||||
{
|
||||
g_ictLowTf.mss.displayReason = StringFormat("Limit %.2f SL %.2f TP %.2f | RR %.1f",
|
||||
entry, sl, tp, rr);
|
||||
return;
|
||||
}
|
||||
IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket);
|
||||
g_ictLowTf.mss.pendingTicket = 0;
|
||||
}
|
||||
|
||||
ulong ticket = 0;
|
||||
if(!IctMssEntry_PlaceLimit(sym, isBuy, entry, sl, tp, vol, ticket))
|
||||
{
|
||||
g_ictLowTf.mss.displayReason = StringFormat("Đặt limit thất bại (%d %s)",
|
||||
g_ictMssTrade.ResultRetcode(),
|
||||
g_ictMssTrade.ResultRetcodeDescription());
|
||||
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
|
||||
return;
|
||||
}
|
||||
|
||||
g_ictLowTf.mss.pendingTicket = ticket;
|
||||
g_ictLowTf.mss.pendingEntry = entry;
|
||||
g_ictLowTf.mss.pendingSl = sl;
|
||||
g_ictLowTf.mss.pendingTp = tp;
|
||||
g_ictLowTf.mss.displayReason = StringFormat("Sell/Buy limit %.2f | SL %.2f (H0/L0) | TP %.2f RR%.1f",
|
||||
entry, sl, tp, rr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,632 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| MssSetup.mqh — MSS: retest FVG H1 (POI) → CHoCH M5 → 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 "CHoCH 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): giá chạm gap H1 + lấp ≥ InpMssH1MinFillPct (đo trên InpFvgTf, không phải M5)
|
||||
bool IctMss_HasH1FvgRetest(const string sym, IctFvgZone &h1)
|
||||
{
|
||||
IctFvg_UpdateZoneState(sym, InpFvgTf, h1);
|
||||
if(h1.firstTouchTime <= 0)
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
// MSS confirm: cùng swing set M5 như chart (H0/H1/L0/L1) + body phá swing thuận bias
|
||||
bool IctMss_DetectChochFromM5Swings(const string sym, const ENUM_TIMEFRAMES tf,
|
||||
const datetime tTouch, const ENUM_ICT_BIAS bias,
|
||||
double &keyLevelOut, datetime &keyTimeOut,
|
||||
double &slSwingOut)
|
||||
{
|
||||
keyLevelOut = 0.0;
|
||||
keyTimeOut = 0;
|
||||
slSwingOut = 0.0;
|
||||
|
||||
IctSwingSet sw;
|
||||
ENUM_ICT_STRUCT structural = ICT_STRUCT_NONE;
|
||||
if(!IctBuildConfirmSwingSet(sym, sw, structural))
|
||||
return false;
|
||||
|
||||
const int maxSh = IctMss_MaxSwingShift(sw);
|
||||
|
||||
if(bias == ICT_BIAS_BEAR)
|
||||
{
|
||||
if(sw.hasH0 && sw.hasL0 && sw.h0.time >= tTouch && sw.l0.time >= sw.h0.time)
|
||||
{
|
||||
if(IctMss_BodyBrokeLevelSince(sym, tf, maxSh, sw.l0.price, true))
|
||||
{
|
||||
keyLevelOut = sw.l0.price;
|
||||
keyTimeOut = sw.l0.time;
|
||||
slSwingOut = sw.h0.price;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(sw.IsComplete() && structural == ICT_STRUCT_BULL && sw.l1.time >= tTouch)
|
||||
{
|
||||
const ENUM_ICT_MS_EVENT ev =
|
||||
IctDetectStructureEvent(sym, tf, structural, sw.h1.price, sw.l1.price, 1);
|
||||
if(ev == ICT_MS_CHOCH ||
|
||||
IctMss_BodyBrokeLevelSince(sym, tf, maxSh, sw.l1.price, true))
|
||||
{
|
||||
keyLevelOut = sw.l1.price;
|
||||
keyTimeOut = sw.l1.time;
|
||||
slSwingOut = sw.hasH0 ? sw.h0.price : sw.h1.price;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(sw.IsComplete() && structural == ICT_STRUCT_BEAR && sw.l1.time >= tTouch)
|
||||
{
|
||||
if(IctMss_BodyBrokeLevelSince(sym, tf, maxSh, sw.l1.price, true))
|
||||
{
|
||||
keyLevelOut = sw.l1.price;
|
||||
keyTimeOut = sw.l1.time;
|
||||
slSwingOut = sw.hasH0 ? sw.h0.price : sw.h1.price;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(bias == ICT_BIAS_BULL)
|
||||
{
|
||||
if(sw.hasL0 && sw.hasH0 && sw.l0.time >= tTouch && sw.h0.time >= sw.l0.time)
|
||||
{
|
||||
if(IctMss_BodyBrokeLevelSince(sym, tf, maxSh, sw.h0.price, false))
|
||||
{
|
||||
keyLevelOut = sw.h0.price;
|
||||
keyTimeOut = sw.h0.time;
|
||||
slSwingOut = sw.l0.price;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(sw.IsComplete() && structural == ICT_STRUCT_BEAR && sw.h1.time >= tTouch)
|
||||
{
|
||||
const ENUM_ICT_MS_EVENT ev =
|
||||
IctDetectStructureEvent(sym, tf, structural, sw.h1.price, sw.l1.price, 1);
|
||||
if(ev == ICT_MS_CHOCH ||
|
||||
IctMss_BodyBrokeLevelSince(sym, tf, maxSh, sw.h1.price, false))
|
||||
{
|
||||
keyLevelOut = sw.h1.price;
|
||||
keyTimeOut = sw.h1.time;
|
||||
slSwingOut = sw.hasL0 ? sw.l0.price : sw.l1.price;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(sw.IsComplete() && structural == ICT_STRUCT_BULL && sw.h1.time >= tTouch)
|
||||
{
|
||||
if(IctMss_BodyBrokeLevelSince(sym, tf, maxSh, sw.h1.price, false))
|
||||
{
|
||||
keyLevelOut = sw.h1.price;
|
||||
keyTimeOut = sw.h1.time;
|
||||
slSwingOut = sw.hasL0 ? sw.l0.price : sw.l1.price;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sau retest FVG H1: MSS = phá swing M5 (confirm TF)
|
||||
bool IctMss_DetectChochAfterH1FvgRetest(const string sym, const ENUM_TIMEFRAMES tf,
|
||||
const IctFvgZone &h1, const ENUM_ICT_BIAS bias,
|
||||
double &keyLevelOut, datetime &keyTimeOut,
|
||||
double &slSwingOut)
|
||||
{
|
||||
const datetime tTouch = h1.firstTouchTime;
|
||||
if(tTouch <= 0)
|
||||
return false;
|
||||
|
||||
return IctMss_DetectChochFromM5Swings(sym, tf, tTouch, bias,
|
||||
keyLevelOut, keyTimeOut, slSwingOut);
|
||||
}
|
||||
|
||||
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 = StringFormat(
|
||||
"Chờ retest FVG H1 %s [%.0f–%.0f] (%.0f%% lấp trên H1)",
|
||||
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 = 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)
|
||||
{
|
||||
double keyLv = 0.0;
|
||||
datetime keyT = 0;
|
||||
double slSwing = 0.0;
|
||||
if(!IctMss_DetectChochAfterH1FvgRetest(sym, cTf, g_ictFvgZones[h1Idx],
|
||||
g_ictDailyBias.bias, keyLv, keyT, slSwing))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
g_ictLowTf.mss.chochKeyLevel = keyLv;
|
||||
g_ictLowTf.mss.chochKeyTime = keyT;
|
||||
g_ictLowTf.mss.slSwingPrice = slSwing;
|
||||
|
||||
datetime chochT = iTime(sym, cTf, 1);
|
||||
if(chochT == 0)
|
||||
chochT = keyT;
|
||||
|
||||
if(!IctMss_IsChochNearH1Fvg(sym, g_ictFvgZones[h1Idx], g_ictDailyBias.bias,
|
||||
keyLv, slSwing, chochT))
|
||||
{
|
||||
g_ictLowTf.mss.displayReason = "CHoCH xa vùng gần H1 FVG — chờ MSS gần hơn";
|
||||
return;
|
||||
}
|
||||
|
||||
g_ictLowTf.mss.phase = ICT_MSS_CHOCH;
|
||||
g_ictLowTf.mss.chochKeyLevel = keyLv;
|
||||
g_ictLowTf.mss.chochKeyTime = keyT;
|
||||
g_ictLowTf.mss.slSwingPrice = slSwing;
|
||||
double mssH0 = 0.0;
|
||||
if(IctMss_GetConfirmMssSwing(sym, g_ictDailyBias.bias, mssH0))
|
||||
g_ictLowTf.mss.slSwingPrice = mssH0;
|
||||
g_ictLowTf.mss.chochTime = chochT;
|
||||
|
||||
IctConfirmFvg_ScanNew(sym, cTf, wantSide, g_ictLowTf.mss.chochTime, true);
|
||||
g_ictLowTf.mss.displayReason = StringFormat("CHoCH @ %.2f — quét M5 FVG", keyLv);
|
||||
}
|
||||
|
||||
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 = "CHoCH 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
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
const string ICT26_PANEL_PFX = "ICT26_PNL_";
|
||||
const string ICT26_PANEL_LEGACY = "ICT26_BIAS_PANEL";
|
||||
const int ICT26_PANEL_MAXLINE = 12;
|
||||
const int ICT26_PANEL_MAXLINE = 14;
|
||||
|
||||
const color ICT_PANEL_CLR_UP = clrLime;
|
||||
const color ICT_PANEL_CLR_DOWN = clrOrangeRed;
|
||||
@@ -162,6 +162,12 @@ void IctPanel_Render(const string sym)
|
||||
lines[n] = StringFormat("iTF FVG: %s", g_ictLowTf.displayReason);
|
||||
colors[n++] = (g_ictLowTf.availableCount > 0) ? ICT_PANEL_CLR_ALLOW : ICT_PANEL_CLR_MUTED;
|
||||
|
||||
ArrayResize(lines, n + 1);
|
||||
ArrayResize(colors, n + 1);
|
||||
lines[n] = StringFormat("MSS: %s", g_ictLowTf.mss.displayReason);
|
||||
colors[n++] = (g_ictLowTf.mss.phase >= ICT_MSS_READY) ? ICT_PANEL_CLR_ALLOW :
|
||||
(g_ictLowTf.mss.phase > ICT_MSS_IDLE) ? clrGold : ICT_PANEL_CLR_MUTED;
|
||||
|
||||
for(int i = 0; i < n; i++)
|
||||
IctPanel_SetLine(ch, i, i * lh, lines[i], colors[i]);
|
||||
|
||||
|
||||
+112
-1
@@ -193,7 +193,7 @@ Chỉ **quét / giữ** FVG thuận Daily Bias. **Vẽ FVG** (ngang):
|
||||
|
||||
| Bear FVG | Bull FVG |
|
||||
|----------|----------|
|
||||
| Swing 1: đỉnh **gần nhất theo thời gian** trên FVG (snapshot lúc tạo FVG) → `pdHigh` | Swing 1: đáy gần nhất dưới FVG → `pdLow` |
|
||||
| Swing 1: pivot **gần FVG nhất** (bar shift trước nến C), giá > `upper` → `pdHigh` | Swing 1: pivot gần FVG, giá < `lower` → `pdLow` |
|
||||
| Swing 2: **đáy đầu tiên** sau FVG khi pivot xác nhận (`pdLow`) | Swing 2: **đỉnh đầu tiên** sau FVG (`pdHigh`) |
|
||||
|
||||
**Vẽ P/D (ngang):**
|
||||
@@ -208,6 +208,44 @@ Swing 1 (`pdSwing1Set`) gán **một lần** khi FVG mới; swing 2 xác nhận
|
||||
|
||||
FVG Used **không** kéo dài P/D thêm; P/D độc lập với Used (theo swing 2).
|
||||
|
||||
**`repPd`**: overlap Premium/Discount (không chỉ midpoint FVG). Bear thỏa MSS nếu **một phần FVG** nằm trên `pdEq` (Premium); fill 38.2% luôn tính từ **`lower`→`upper`** của FVG.
|
||||
|
||||
### Retest FVG H1 (POI) — không nhầm với retest swing H1
|
||||
|
||||
**Retest FVG H1** = giá **hồi vào gap Fair Value Gap trên H1** (vùng POI đã chọn), đo trên **`InpFvgTf` (H1)**:
|
||||
|
||||
| | |
|
||||
|--|--|
|
||||
| **Chạm** | `firstTouchTime` — nến H1 đầu tiên chạm vùng `[lower … upper]` |
|
||||
| **Lấp %** | Từ cạnh FVG (`lower`→`upper` bear) — ≥ `InpMssH1MinFillPct` (mặc định 38.2%) |
|
||||
| **Không phải** | Retest swing/pivot H1; retest trên M5; retest đường Premium/Discount riêng |
|
||||
|
||||
Sau retest FVG H1 OK → mới arm MSS trên **M5** (phá swing confirm).
|
||||
|
||||
### MSS entry (Confirm TF, v1.115)
|
||||
|
||||
Khi `IsAllowTrade`, state machine `g_ictLowTf.mss`:
|
||||
|
||||
| Phase | Điều kiện |
|
||||
|-------|-----------|
|
||||
| `H1_TOUCH` | **Retest FVG H1** OK: bias + `repPd` + lấp ≥ `InpMssH1MinFillPct` trên H1 |
|
||||
| `CHOCH` | Sau retest FVG H1: **body phá swing M5** (H0/L0/L1/H1 — cùng build như label chart) |
|
||||
| `M5_FVG` | FVG `InpConfirmTf` cùng hướng bias, sau thời điểm CHoCH |
|
||||
| `ENTRY_FILL` / `READY` | Giá hồi lấp ≥ `InpMssEntryFillPct` vào M5 FVG đó |
|
||||
|
||||
Sweep liquidity: **chưa** (phase sau). Module: `MssSetup.mqh`, pool `ConfirmFvg.mqh`.
|
||||
|
||||
**Lệnh limit (v1.116)** — khi có M5 FVG (`MssEntry.mqh`):
|
||||
|
||||
| | Bull | Bear |
|
||||
|--|------|------|
|
||||
| Limit | Buy @ `upper` M5 FVG | Sell @ `lower` M5 FVG |
|
||||
| SL | Dưới `L0` M5 − buffer | Trên `H0` M5 + buffer |
|
||||
| TP | Entry + `InpMssMinRR`×R | Entry − `InpMssMinRR`×R |
|
||||
| Size | `InpMssRiskPct` % balance | |
|
||||
|
||||
`InpMssTradeEnabled`, `InpMssMagic`, `InpMssOnePosition`.
|
||||
|
||||
### IsAllowTrade
|
||||
|
||||
Mục tiêu: **phát hiện sớm đảo chiều H1** khi CHoCH — không cần đợi HH-HL/LH-LL rõ.
|
||||
@@ -489,6 +527,79 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias();
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.126 — Thuật ngữ: Retest FVG H1 (POI), không ghi chung “H1 retest”
|
||||
|
||||
- Panel/journal: “Retest FVG H1”, “Chờ retest FVG H1 … (lấp trên H1)”
|
||||
- `IctMss_HasH1FvgRetest`, `IctMss_DetectChochAfterH1FvgRetest`
|
||||
|
||||
### v1.125 — MSS = phá swing M5 (confirm), build structure như chart
|
||||
|
||||
- `IctBuildConfirmSwingSet`: H0/H1/L0/L1 M5 dùng chung cho vẽ + MSS
|
||||
- MSS/CHoCH: chỉ xét body phá L0/L1/H0/H1 sau H1 touch (bỏ pivot trong FVG)
|
||||
- Bear: phá L0 sau H0, hoặc CHoCH phá L1 (bull→bear), hoặc BOS phá L1 (bear)
|
||||
|
||||
### v1.124 — Sửa detect CHoCH↓ sau H1 retest (L0 M5)
|
||||
|
||||
- CHoCH: ưu tiên `H0/L0` M5 (`BuildSwingSetRecentPivots`) + body phá trong lookback (không chỉ bar 1)
|
||||
- Key level: swing **sau** đỉnh phản ứng (`IctFindMostRecentNewer`), fallback swing cũ hơn
|
||||
- Bỏ `keyLo.time < tTouch` (gây miss khi L0 hình thành trước touch H1 trên timeline)
|
||||
|
||||
### v1.123 — Vẽ MSS sau H1 touch + journal chặn lệnh
|
||||
|
||||
- `MssDraw`: từ `H1_TOUCH` — vạch touch, mức 38.2% H1, CHoCH/H0 (kể cả khi pipeline chưa advance)
|
||||
- `Journal.mqh` + `InpMssLogJournal`: `[ICT2026/MSS]` pipeline, `[ICT2026/Entry] Chặn lệnh:` khi không đặt được limit
|
||||
|
||||
### v1.122 — MSS gần H1 FVG (không siết chặt)
|
||||
|
||||
- Vùng gần: buffer % gap + ATR; bear thêm `InpMssExtraBelowGapPct` dưới `lower` (CHoCH/M5 FVG ngay dưới FVG vẫn hợp lệ)
|
||||
- CHoCH OK nếu **key level** hoặc **H0** nằm trong vùng gần (không bắt buộc trong FVG)
|
||||
- `InpMssMaxM5BarsAfterTouch=0` mặc định = không giới hạn thời gian
|
||||
|
||||
### v1.121 — FVG qualify: overlap Premium/Discount; fill từ cạnh FVG
|
||||
|
||||
- `IctFvg_OverlapsPremium` / `OverlapsDiscount`: bear MSS khi FVG chạm vùng Premium (kể cả nửa dưới trong Discount)
|
||||
- `maxFillRatio`: % lấp theo chiều cao FVG (`lower`→`upper` bear), không theo pdEq
|
||||
|
||||
### v1.120 — MSS đúng thứ tự: retest H1 Premium/Discount → CHoCH thuận bias
|
||||
|
||||
- Target H1: Premium **cao nhất** (bear) / Discount **thấp nhất** (bull) — không nhảy CHoCH khi chưa retest
|
||||
- CHoCH: đỉnh phản ứng **trong** H1 FVG sau retest → phá key **down** (bear) / **up** (bull)
|
||||
- Vẽ: vàng **CHoCH↓** = đáy key; nét đứt **H0** = đỉnh MSS (SL)
|
||||
|
||||
### v1.119 — Vẽ CHoCH + H0/L0 MSS trên chart (`MssDraw.mqh`)
|
||||
|
||||
- Vàng: đường **CHoCH** (key level) | Vàng nét đứt: **H0/L0** swing MSS
|
||||
- Aqua/đỏ/xanh: entry / SL / TP khi có pending
|
||||
- `InpDrawMssChoch`
|
||||
|
||||
### v1.118 — MSS phải gần / trong H1 FVG
|
||||
|
||||
- CHoCH/M5 FVG **gần** H1 FVG: ± buffer + mở rộng phía dưới (bear) / trên (bull); key CHoCH hoặc H0 đều được tính
|
||||
- Thời gian: ≤ `InpMssMaxM5BarsAfterTouch` M5 bar sau lần chạm H1 FVG
|
||||
- M5 FVG phải overlap vùng H1 FVG mở rộng
|
||||
|
||||
### v1.117 — MSS entry: SL = H0/L0 M5, TP min 2R
|
||||
|
||||
- Sell limit @ **lower** M5 FVG | Buy limit @ **upper**
|
||||
- SL: trên **H0** M5 (bear) / dưới **L0** M5 (bull) + `InpMssSlAtrMult`×ATR
|
||||
- TP: tối thiểu `InpMssMinRR` (mặc định 2R), không phụ thuộc swing H1
|
||||
|
||||
### v1.116 — MSS limit entry: FVG edge, SL swing CHoCH, TP trước iH0/iL0
|
||||
|
||||
### v1.115 — MSS Confirm TF (H1 touch → CHoCH → M5 FVG → fill 38.2%)
|
||||
|
||||
- `repPd` / MSS H1: overlap Premium (bear) hoặc Discount (bull); fill % theo biên FVG
|
||||
- `MssSetup.mqh` + pool `ConfirmFvg` (M5); sweep tạm bỏ
|
||||
- Panel dòng MSS; vẽ CFVG trên chart
|
||||
|
||||
### v1.114 — FVG: bỏ label text, màu bull xanh / bear đỏ / used xám
|
||||
|
||||
### v1.113 — PD swing 1: pivot gần FVG (shift), bỏ wick nến
|
||||
|
||||
- `IctFindNearestSwingHighBefore` / `LowBefore`: cùng logic `IctFindMostRecentOlder` neo theo `iBarShift(createdTime)`
|
||||
- Không fallback `iHigh`/`iLow` từng nến (gây đỉnh PD cao hơn swing vàng trên chart)
|
||||
- Swing 2: `IctFindFirstSwing*AfterShift` — đáy/đỉnh đầu tiên sau nến FVG
|
||||
|
||||
### v1.112 — PD lock: không cập nhật đỉnh sau pdComplete
|
||||
|
||||
- `pdSwing1Set`: đỉnh/đáy swing 1 snapshot một lần
|
||||
|
||||
@@ -102,6 +102,104 @@ IctSwingPoint IctFindMostRecentOlder(IctSwingPoint &pts[], const int count,
|
||||
return pts[best];
|
||||
}
|
||||
|
||||
// Pivot mới hơn anchor (shift nhỏ hơn = gần hiện tại hơn)
|
||||
IctSwingPoint IctFindMostRecentNewer(IctSwingPoint &pts[], const int count,
|
||||
const int anchorShift)
|
||||
{
|
||||
IctSwingPoint empty;
|
||||
empty.Clear();
|
||||
int best = -1;
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(pts[i].shift >= anchorShift)
|
||||
continue;
|
||||
if(best < 0 || pts[i].shift < pts[best].shift)
|
||||
best = i;
|
||||
}
|
||||
if(best < 0)
|
||||
return empty;
|
||||
return pts[best];
|
||||
}
|
||||
|
||||
// Pivot swing gần anchor nhất (shift nhỏ nhất trong các bar cũ hơn anchor)
|
||||
IctSwingPoint IctFindNearestSwingHighBefore(IctSwingPoint &pts[], const int count,
|
||||
const int anchorShift, const double refUpper)
|
||||
{
|
||||
IctSwingPoint empty;
|
||||
empty.Clear();
|
||||
int best = -1;
|
||||
const double minP = refUpper + _Point;
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(pts[i].shift <= anchorShift)
|
||||
continue;
|
||||
if(pts[i].price <= minP)
|
||||
continue;
|
||||
if(best < 0 || pts[i].shift < pts[best].shift)
|
||||
best = i;
|
||||
}
|
||||
if(best < 0)
|
||||
return empty;
|
||||
return pts[best];
|
||||
}
|
||||
|
||||
IctSwingPoint IctFindNearestSwingLowBefore(IctSwingPoint &pts[], const int count,
|
||||
const int anchorShift, const double refLower)
|
||||
{
|
||||
IctSwingPoint empty;
|
||||
empty.Clear();
|
||||
int best = -1;
|
||||
const double maxP = refLower - _Point;
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(pts[i].shift <= anchorShift)
|
||||
continue;
|
||||
if(pts[i].price >= maxP)
|
||||
continue;
|
||||
if(best < 0 || pts[i].shift < pts[best].shift)
|
||||
best = i;
|
||||
}
|
||||
if(best < 0)
|
||||
return empty;
|
||||
return pts[best];
|
||||
}
|
||||
|
||||
IctSwingPoint IctFindFirstSwingLowAfterShift(IctSwingPoint &pts[], const int count,
|
||||
const int anchorShift)
|
||||
{
|
||||
IctSwingPoint empty;
|
||||
empty.Clear();
|
||||
int best = -1;
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(pts[i].shift >= anchorShift)
|
||||
continue;
|
||||
if(best < 0 || pts[i].shift > pts[best].shift)
|
||||
best = i;
|
||||
}
|
||||
if(best < 0)
|
||||
return empty;
|
||||
return pts[best];
|
||||
}
|
||||
|
||||
IctSwingPoint IctFindFirstSwingHighAfterShift(IctSwingPoint &pts[], const int count,
|
||||
const int anchorShift)
|
||||
{
|
||||
IctSwingPoint empty;
|
||||
empty.Clear();
|
||||
int best = -1;
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(pts[i].shift >= anchorShift)
|
||||
continue;
|
||||
if(best < 0 || pts[i].shift > pts[best].shift)
|
||||
best = i;
|
||||
}
|
||||
if(best < 0)
|
||||
return empty;
|
||||
return pts[best];
|
||||
}
|
||||
|
||||
IctSwingSet IctPickStructuralBear(IctSwingPoint &highs[], const int nH,
|
||||
IctSwingPoint &lows[], const int nL)
|
||||
{
|
||||
@@ -308,6 +406,19 @@ bool IctBuildSwingSetRecentPivots(const string sym, const ENUM_TIMEFRAMES tf,
|
||||
return out.IsComplete();
|
||||
}
|
||||
|
||||
// M5 confirm: cùng pivot/H0–L1 như label chart (InpConfirmTf)
|
||||
bool IctBuildConfirmSwingSet(const string sym, IctSwingSet &sw,
|
||||
ENUM_ICT_STRUCT &structuralOut)
|
||||
{
|
||||
sw.Clear();
|
||||
structuralOut = ICT_STRUCT_NONE;
|
||||
return IctBuildSwingSetRecentPivots(sym, InpConfirmTf,
|
||||
InpConfirmSwingRange,
|
||||
InpConfirmSwingLookback,
|
||||
InpConfirmRecentBars,
|
||||
sw, structuralOut);
|
||||
}
|
||||
|
||||
void IctGetKeyLevels(const ENUM_ICT_STRUCT bias, IctSwingSet &sw,
|
||||
double &keyLv1, double &keyLv2)
|
||||
{
|
||||
|
||||
@@ -148,12 +148,23 @@ enum ENUM_ICT_PD_ZONE
|
||||
ICT_PD_EQUILIBRIUM = 2
|
||||
};
|
||||
|
||||
enum ENUM_ICT_MSS_PHASE
|
||||
{
|
||||
ICT_MSS_IDLE = 0,
|
||||
ICT_MSS_H1_TOUCH = 1, // Đã retest FVG H1 (POI): chạm + lấp ≥ InpMssH1MinFillPct trên H1
|
||||
ICT_MSS_CHOCH = 2,
|
||||
ICT_MSS_M5_FVG = 3,
|
||||
ICT_MSS_ENTRY_FILL = 4,
|
||||
ICT_MSS_READY = 5
|
||||
};
|
||||
|
||||
struct IctFvgZone
|
||||
{
|
||||
ulong id;
|
||||
ENUM_ICT_FVG_SIDE side;
|
||||
ENUM_ICT_FVG_STATE state;
|
||||
ENUM_ICT_PD_ZONE pdZone;
|
||||
ENUM_ICT_PD_ZONE repPd;
|
||||
double upper;
|
||||
double lower;
|
||||
double pdHigh;
|
||||
@@ -171,6 +182,7 @@ struct IctFvgZone
|
||||
datetime pdRangeEnd;
|
||||
bool pdComplete;
|
||||
bool pdSwing1Set;
|
||||
bool mssH1Touch382;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
@@ -178,6 +190,7 @@ struct IctFvgZone
|
||||
side = ICT_FVG_NONE;
|
||||
state = ICT_FVG_AVAILABLE;
|
||||
pdZone = ICT_PD_NONE;
|
||||
repPd = ICT_PD_NONE;
|
||||
upper = 0.0;
|
||||
lower = 0.0;
|
||||
pdHigh = 0.0;
|
||||
@@ -195,15 +208,55 @@ struct IctFvgZone
|
||||
pdRangeEnd = 0;
|
||||
pdComplete = false;
|
||||
pdSwing1Set = false;
|
||||
mssH1Touch382 = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct IctMssState
|
||||
{
|
||||
ENUM_ICT_MSS_PHASE phase;
|
||||
ulong h1FvgId;
|
||||
datetime h1TouchTime;
|
||||
ulong m5FvgId;
|
||||
double chochKeyLevel;
|
||||
datetime chochKeyTime;
|
||||
datetime chochTime;
|
||||
datetime m5FvgTime;
|
||||
double slSwingPrice;
|
||||
ulong pendingTicket;
|
||||
double pendingEntry;
|
||||
double pendingSl;
|
||||
double pendingTp;
|
||||
string displayReason;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
phase = ICT_MSS_IDLE;
|
||||
h1FvgId = 0;
|
||||
h1TouchTime = 0;
|
||||
m5FvgId = 0;
|
||||
chochKeyLevel = 0.0;
|
||||
chochKeyTime = 0;
|
||||
chochTime = 0;
|
||||
m5FvgTime = 0;
|
||||
slSwingPrice = 0.0;
|
||||
pendingTicket = 0;
|
||||
pendingEntry = 0.0;
|
||||
pendingSl = 0.0;
|
||||
pendingTp = 0.0;
|
||||
displayReason = "";
|
||||
}
|
||||
};
|
||||
|
||||
struct IctLowTfState
|
||||
{
|
||||
datetime lastBarTime;
|
||||
datetime lastConfirmBarTime;
|
||||
int activeCount;
|
||||
int availableCount;
|
||||
int confirmFvgCount;
|
||||
string displayReason;
|
||||
IctMssState mss;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user