This commit is contained in:
Bell
2026-05-24 22:24:36 +07:00
parent e173949468
commit 3a69f4c09a
16 changed files with 1872 additions and 120 deletions
+8 -2
View File
@@ -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; }
//+------------------------------------------------------------------+
+26 -3
View File
@@ -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;
+128
View File
@@ -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
+1 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+53
View File
@@ -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
+15
View File
@@ -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
+44 -6
View File
@@ -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; }
+221
View File
@@ -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
+320
View File
@@ -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
+632
View File
@@ -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
+7 -1
View File
@@ -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
View File
@@ -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
+111
View File
@@ -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/H0L1 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)
{
+53
View File
@@ -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