Optimize POI range, SL range

This commit is contained in:
Bell
2026-05-26 20:27:12 +07:00
parent 8f4a3df295
commit 3cc1106ed9
8 changed files with 353 additions and 83 deletions
+2 -2
View File
@@ -3,8 +3,8 @@
//| BOS = Continue | CHoCH = Reversal |
//+------------------------------------------------------------------+
#property copyright "ICT 2026"
#property version "1.163"
#property description "ICT2026 MSS | OnlyStatsMode: skip render + in stats mỗi lệnh close + tổng kết deinit"
#property version "1.168"
#property description "ICT2026 MSS | Partial close 50% + SL→BE khi giá đạt swing iL0/iH0 (TP gồng xa hơn)"
#include <ICT2026/Config.mqh>
#include <ICT2026/DailyBias.mqh>
+4
View File
@@ -52,6 +52,9 @@ input double InpFvgMinGapATRPct = 12.0; // H1 only: gap t
input double InpFvgMinGapPoints = 0.0; // H1 only: gap tối thiểu (giá)
input double InpFvgMinGapVsBarPct = 25.0; // H1 only: gap >= % range nến B (0=tắt)
input double InpFvgUsedFillPct = 25.0; // % lấp FVG → Used
input double InpFvgPdMinOverlapPct = 0.0; // POI filter: % chiều cao FVG nằm trong Premium/Discount (0=chấp nhận bất kỳ overlap > 0)
input double InpFvgTouchFillPure = 25.0; // Touch threshold khi FVG nằm hẳn trong vùng PD đúng chiều (bear→Premium, bull→Discount)
input double InpFvgTouchFillMixed = 50.0; // Touch threshold khi FVG straddle equilibrium (overlap cả Premium & Discount)
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
@@ -83,6 +86,7 @@ input double InpMssRiskPct = 1.0; // R % balance mỗ
input int InpMssSlSpreadMult = 8; // SL buffer = N × spread (cộng ra ngoài max(H0,H1)/min(L0,L1))
input int InpMssTpSpreadMult = 16; // TP buffer = N × spread (chốt trước iL0/iH0 để dễ khớp)
input double InpMssMinRR = 2.0; // Ngưỡng RR: nếu TP@iL0/iH0 < ngưỡng → dùng TP cố định = entry ± risk × InpMssMinRR (= 2R), ngược lại TP tại iL0/iH0
input double InpMssPartialClosePct = 50.0; // % volume chốt khi giá đạt swing iL0/iH0 (TP gồng xa hơn) — sau đó dời SL về BE (0=tắt)
input int InpMssPendingExpireHours = 4; // Hết hạn pending limit (giờ): hủy + reset → WAIT_FVG_TOUCH (0=không timeout)
input bool InpMssOnePosition = true; // Một position/pending MSS
input bool InpMssCancelPendingEod = true; // Hủy pending cuối phiên Mỹ (nếu chưa khớp)
+57 -4
View File
@@ -210,6 +210,28 @@ bool IctFvg_OverlapsDiscount(const IctFvgZone &zone)
return (zone.lower < zone.pdEq - _Point && zone.upper > zone.pdLow - _Point);
}
// % chiều cao FVG nằm trên equilibrium (Premium portion)
double IctFvg_PremiumOverlapPct(const IctFvgZone &zone)
{
const double h = zone.upper - zone.lower;
if(h <= _Point || zone.pdEq <= 0.0)
return 0.0;
const double premLo = MathMax(zone.lower, zone.pdEq);
const double portion = MathMax(0.0, zone.upper - premLo);
return 100.0 * portion / h;
}
// % chiều cao FVG nằm dưới equilibrium (Discount portion)
double IctFvg_DiscountOverlapPct(const IctFvgZone &zone)
{
const double h = zone.upper - zone.lower;
if(h <= _Point || zone.pdEq <= 0.0)
return 0.0;
const double discHi = MathMin(zone.upper, zone.pdEq);
const double portion = MathMax(0.0, discHi - zone.lower);
return 100.0 * portion / h;
}
ENUM_ICT_PD_ZONE IctFvg_ComputeRepPd(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0)
@@ -436,11 +458,42 @@ bool IctFvg_IsEntryRepPd(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0)
return false;
const double minPct = MathMax(0.0, MathMin(100.0, InpFvgPdMinOverlapPct));
double pct = 0.0;
if(g_ictDailyBias.bias == ICT_BIAS_BEAR)
return IctFvg_OverlapsPremium(zone);
if(g_ictDailyBias.bias == ICT_BIAS_BULL)
return IctFvg_OverlapsDiscount(zone);
return false;
pct = IctFvg_PremiumOverlapPct(zone);
else if(g_ictDailyBias.bias == ICT_BIAS_BULL)
pct = IctFvg_DiscountOverlapPct(zone);
else
return false;
// minPct = 0 → chấp nhận bất kỳ overlap > 0
if(minPct <= 0.0)
return pct > 1e-9;
return pct >= minPct - 1e-9;
}
// Touch threshold cho POI:
// - FVG nằm hẳn trong Premium (bear) / Discount (bull) → 25%
// - FVG straddle equilibrium (mixed) → 50%
// - Trả về 100 nếu FVG sai vùng (không thể là POI)
double IctFvg_RequiredTouchFillPct(const IctFvgZone &zone)
{
if(zone.pdEq <= 0.0)
return 100.0;
double pct = 0.0;
if(g_ictDailyBias.bias == ICT_BIAS_BEAR)
pct = IctFvg_PremiumOverlapPct(zone);
else if(g_ictDailyBias.bias == ICT_BIAS_BULL)
pct = IctFvg_DiscountOverlapPct(zone);
else
return 100.0;
if(pct >= 100.0 - 1e-6)
return InpFvgTouchFillPure; // 100% trong vùng đúng → threshold thấp
if(pct > 1e-9)
return InpFvgTouchFillMixed; // Mixed → threshold cao
return 100.0; // 0% trong vùng đúng → không qualify
}
bool IctFvg_HasFillAtLeast(const IctFvgZone &zone, const double pct)
+197 -52
View File
@@ -71,15 +71,20 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
entryOut = slOut = tpOut = 0.0;
reasonOut = "";
entryOut = IctMssEntry_LimitPrice(m5Zone);
if(entryOut <= 0.0)
// ── Xác định side: ưu tiên M5 FVG, fallback chochBias khi không có FVG
const bool hasFvg = (m5Zone.side != ICT_FVG_NONE);
ENUM_ICT_BIAS bias = ICT_BIAS_NONE;
if(hasFvg)
bias = (m5Zone.side == ICT_FVG_BULL) ? ICT_BIAS_BULL : ICT_BIAS_BEAR;
else if(g_ictLowTf.mss.chochLocked)
bias = g_ictLowTf.mss.chochBias;
if(bias == ICT_BIAS_NONE)
{
reasonOut = "M5 FVG side không xác định";
reasonOut = "Không xác định được side (no FVG + no chochBias)";
return false;
}
const bool isBuy = (m5Zone.side == ICT_FVG_BULL);
const ENUM_ICT_BIAS bias = isBuy ? ICT_BIAS_BULL : ICT_BIAS_BEAR;
const bool isBuy = (bias == ICT_BIAS_BULL);
double swingSl = 0.0;
if(g_ictLowTf.mss.chochLocked && g_ictLowTf.mss.slSwingPrice > 0.0)
@@ -107,6 +112,60 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
const double bufTp = InpMssTpSpreadMult * spreadUnit;
const double minRR = MathMax(1.0, InpMssMinRR);
// SL final (đã buffer + spread)
slOut = isBuy ? swingSl - bufSl : swingSl + bufSl;
// ── 2 candidate entries:
// A) M5 FVG limit (nếu có)
// B) MSS keyLV (chochKeyLevel — L0 broken cho bear, H0 broken cho bull)
const double mktBid = SymbolInfoDouble(sym, SYMBOL_BID);
const double mktAsk = SymbolInfoDouble(sym, SYMBOL_ASK);
double entryFvg = 0.0, riskFvg = DBL_MAX;
if(hasFvg)
{
entryFvg = IctMssEntry_LimitPrice(m5Zone);
const double rA = isBuy ? (entryFvg - slOut) : (slOut - entryFvg);
const bool limitOk = isBuy ? (entryFvg < mktAsk - _Point)
: (entryFvg > mktBid + _Point);
if(entryFvg > 0.0 && rA > _Point && limitOk)
riskFvg = rA;
}
double entryMss = g_ictLowTf.mss.chochKeyLevel;
double riskMss = DBL_MAX;
{
const double rB = isBuy ? (entryMss - slOut) : (slOut - entryMss);
const bool limitOk = isBuy ? (entryMss < mktAsk - _Point)
: (entryMss > mktBid + _Point);
if(entryMss > 0.0 && rB > _Point && limitOk)
riskMss = rB;
}
string entrySource = "";
if(riskFvg < DBL_MAX && (riskMss == DBL_MAX || riskFvg < riskMss))
{
entryOut = entryFvg;
entrySource = StringFormat("M5 FVG (risk %.2f < MSS %.2f)",
riskFvg, (riskMss == DBL_MAX ? 0.0 : riskMss));
}
else if(riskMss < DBL_MAX)
{
entryOut = entryMss;
if(hasFvg)
entrySource = StringFormat("MSS keyLV (risk %.2f ≤ FVG %.2f)",
riskMss, (riskFvg == DBL_MAX ? 999.99 : riskFvg));
else
entrySource = "MSS keyLV (no M5 FVG)";
}
else
{
reasonOut = StringFormat(
"Cả 2 entry không hợp lệ | mkt=%.2f/%.2f | FVG=%.2f | MSS=%.2f | SL=%.2f",
mktBid, mktAsk, entryFvg, entryMss, slOut);
return false;
}
// ── TP target = iL0 (BEAR) / iH0 (BULL): sóng H1, m5 chỉ để entry
// TP đặt CÁCH target một buffer (gần chạm — không chờ hit chính xác)
const IctSwingSet iSw = g_ictIntraday.swings;
@@ -119,53 +178,55 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
// ── Logic TP (1 ngưỡng duy nhất):
// RR(TP@iL0/iH0) > InpMssMinRR (= 2.0) → dùng TP tại iL0/iH0 (mục tiêu sóng H1)
// Ngược lại (RR ≤ 2.0 hoặc iL0/iH0 không khả dụng) → TP cố định = entry ± risk × InpMssMinRR (= 2R)
const double risk = isBuy ? (entryOut - slOut) : (slOut - entryOut);
if(risk <= _Point)
{
reasonOut = StringFormat("Risk≤0 (SL %.5f vs entry %.5f)", slOut, entryOut);
return false;
}
// Swing-based TP (= TP "tự nhiên" tại iL0/iH0 ± bufTp) — dùng cho partial close trigger
double swingTp = 0.0;
if(isBuy)
{
slOut = swingSl - bufSl;
const double risk = entryOut - slOut;
if(risk <= _Point)
{
reasonOut = StringFormat("Risk≤0 (SL %.5f ≥ entry %.5f)", slOut, entryOut);
return false;
}
double tpAtIH0 = 0.0;
double rrAtIH0 = 0.0;
double tpAtIH0 = 0.0, rrAtIH0 = 0.0;
if(tpTarget > 0.0 && tpTarget > entryOut + bufTp + _Point)
{
tpAtIH0 = tpTarget - bufTp;
rrAtIH0 = (tpAtIH0 - entryOut) / risk;
swingTp = tpAtIH0;
}
if(rrAtIH0 > minRR)
tpOut = tpAtIH0;
else
tpOut = entryOut + risk * minRR;
tpOut = (rrAtIH0 > minRR) ? tpAtIH0 : (entryOut + risk * minRR);
}
else
{
slOut = swingSl + bufSl;
const double risk = slOut - entryOut;
if(risk <= _Point)
{
reasonOut = StringFormat("Risk≤0 (SL %.5f ≤ entry %.5f)", slOut, entryOut);
return false;
}
double tpAtIL0 = 0.0;
double rrAtIL0 = 0.0;
double tpAtIL0 = 0.0, rrAtIL0 = 0.0;
if(tpTarget > 0.0 && tpTarget < entryOut - bufTp - _Point)
{
tpAtIL0 = tpTarget + bufTp;
rrAtIL0 = (entryOut - tpAtIL0) / risk;
swingTp = tpAtIL0;
}
if(rrAtIL0 > minRR)
tpOut = tpAtIL0;
else
tpOut = entryOut - risk * minRR;
tpOut = (rrAtIL0 > minRR) ? tpAtIL0 : (entryOut - risk * minRR);
}
// Partial close trigger: chỉ kích hoạt khi TP gồng XA HƠN swing TP
// BUY: tpOut > swingTp + _Point → partial khi giá lên đến swingTp
// SELL: tpOut < swingTp - _Point → partial khi giá xuống đến swingTp
double partialTrigger = 0.0;
if(swingTp > 0.0 && InpMssPartialClosePct > 0.0)
{
if(isBuy && tpOut > swingTp + _Point)
partialTrigger = swingTp;
else if(!isBuy && tpOut < swingTp - _Point)
partialTrigger = swingTp;
}
g_ictLowTf.mss.partialTriggerPrice = partialTrigger;
g_ictLowTf.mss.partialCloseDone = false;
// entrySource lưu vào reasonOut để display thấy nguồn entry
reasonOut = entrySource;
entryOut = IctMssEntry_NormalizePrice(sym, entryOut);
slOut = IctMssEntry_NormalizePrice(sym, slOut);
tpOut = IctMssEntry_NormalizePrice(sym, tpOut);
@@ -291,6 +352,79 @@ void IctMss_OnPendingTimeout(const string sym, const ulong ticket, const int hou
TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES));
}
// Chốt 50% volume + dời SL về BE khi giá đạt swing iL0/iH0 (chỉ áp dụng khi TP gồng xa hơn swing)
void IctMssEntry_CheckPartialClose(const string sym)
{
if(InpMssPartialClosePct <= 0.0)
return;
if(g_ictLowTf.mss.partialCloseDone)
return;
if(g_ictLowTf.mss.partialTriggerPrice <= 0.0)
return;
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)
continue;
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double openPx = PositionGetDouble(POSITION_PRICE_OPEN);
const double volume = PositionGetDouble(POSITION_VOLUME);
const double curTp = PositionGetDouble(POSITION_TP);
const double bid = SymbolInfoDouble(sym, SYMBOL_BID);
const double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
const double trig = g_ictLowTf.mss.partialTriggerPrice;
bool reached = false;
if(ptype == POSITION_TYPE_BUY)
reached = (bid >= trig - _Point);
else if(ptype == POSITION_TYPE_SELL)
reached = (ask <= trig + _Point);
if(!reached)
return;
// Tính volume cần đóng (1 nửa, đã normalize step)
const double pct = MathMax(0.0, MathMin(100.0, InpMssPartialClosePct)) / 100.0;
const double vClose = IctMssEntry_NormalizeVolume(sym, volume * pct);
if(vClose <= 0.0 || vClose >= volume)
return;
g_ictMssTrade.SetExpertMagicNumber(InpMssMagic);
if(!g_ictMssTrade.PositionClosePartial(ticket, vClose))
{
if(InpMssLogJournal)
PrintFormat("[ICT2026/MSS] PartialClose fail #%I64u vol=%.2f — %d %s",
ticket, vClose, g_ictMssTrade.ResultRetcode(),
g_ictMssTrade.ResultRetcodeDescription());
return;
}
// Dời SL về BE (entry price). Giữ TP cũ.
if(!g_ictMssTrade.PositionModify(ticket, openPx, curTp))
{
if(InpMssLogJournal)
PrintFormat("[ICT2026/MSS] BE move fail #%I64u — %d %s",
ticket, g_ictMssTrade.ResultRetcode(),
g_ictMssTrade.ResultRetcodeDescription());
}
g_ictLowTf.mss.partialCloseDone = true;
if(InpMssLogJournal)
PrintFormat("[ICT2026/MSS] PartialClose #%I64u %.0f%% (%.2f→%.2f lot) @ %.2f | SL→BE %.2f | TP %.2f",
ticket, InpMssPartialClosePct, volume, volume - vClose,
(ptype == POSITION_TYPE_BUY ? bid : ask),
openPx, curTp);
return;
}
}
void IctMssEntry_CheckPendingTimeout(const string sym)
{
if(InpMssPendingExpireHours <= 0)
@@ -447,6 +581,7 @@ void IctMssEntry_Update(const string sym)
IctMssEntry_CheckEodCancel(sym);
IctMssEntry_CheckPendingTimeout(sym);
IctMssEntry_CheckPartialClose(sym);
if(g_ictLowTf.mss.phase == ICT_MSS_IDLE && g_ictLowTf.mss.pendingTicket > 0)
{
@@ -456,27 +591,31 @@ void IctMssEntry_Update(const string sym)
if(!InpMssTradeEnabled)
{
if(g_ictLowTf.mss.phase >= ICT_MSS_M5_FVG)
if(g_ictLowTf.mss.phase >= ICT_MSS_CHOCH)
IctMss_JournalEntryBlock("InpMssTradeEnabled=false");
return;
}
if(!g_ictIntraday.isAllowTrade || g_ictLowTf.mss.phase < ICT_MSS_M5_FVG ||
g_ictLowTf.mss.m5FvgId == 0)
// Cho phép entry từ CHOCH trở lên (không bắt buộc M5 FVG):
// ComputeLevels sẽ chọn entry giữa M5 FVG (nếu có) và MSS keyLV (luôn có sau lock)
if(!g_ictIntraday.isAllowTrade ||
g_ictLowTf.mss.phase < ICT_MSS_CHOCH ||
!g_ictLowTf.mss.chochLocked ||
g_ictLowTf.mss.chochKeyLevel <= 0.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_ictLowTf.mss.phase >= ICT_MSS_H1_TOUCH)
{
if(!g_ictIntraday.isAllowTrade)
IctMss_JournalEntryBlock("AllowTrade=false");
else if(g_ictLowTf.mss.phase < ICT_MSS_M5_FVG)
else if(g_ictLowTf.mss.phase < ICT_MSS_CHOCH)
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
else if(g_ictLowTf.mss.m5FvgId == 0)
IctMss_JournalEntryBlock("Chưa có M5 FVG cho entry");
else
IctMss_JournalEntryBlock("Chờ MSS lock (chochKeyLevel)");
}
return;
}
@@ -491,11 +630,16 @@ void IctMssEntry_Update(const string sym)
return;
}
const int mIdx = IctConfirmFvg_FindById(g_ictLowTf.mss.m5FvgId);
if(mIdx < 0)
return;
// M5 FVG là OPTIONAL: nếu có thì compare với MSS keyLV để chọn entry SL gần hơn
IctFvgZone m5;
m5.side = ICT_FVG_NONE;
if(g_ictLowTf.mss.m5FvgId > 0)
{
const int mIdx = IctConfirmFvg_FindById(g_ictLowTf.mss.m5FvgId);
if(mIdx >= 0)
m5 = g_ictConfirmFvgZones[mIdx];
}
const IctFvgZone m5 = g_ictConfirmFvgZones[mIdx];
double entry = 0.0, sl = 0.0, tp = 0.0;
string lvlReason = "";
if(!IctMssEntry_ComputeLevels(sym, m5, entry, sl, tp, lvlReason))
@@ -507,7 +651,8 @@ void IctMssEntry_Update(const string sym)
return;
}
const bool isBuy = (m5.side == ICT_FVG_BULL);
const bool isBuy = (g_ictLowTf.mss.chochBias == ICT_BIAS_BULL);
const string entrySource = lvlReason; // ComputeLevels trả entrySource qua reasonOut
const double vol = IctMssEntry_NormalizeVolume(sym,
IctMssEntry_VolumeForRisk(sym, isBuy, entry, sl));
if(vol <= 0.0)
@@ -527,8 +672,8 @@ void IctMssEntry_Update(const string sym)
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);
g_ictLowTf.mss.displayReason = StringFormat("Limit %.2f SL %.2f TP %.2f | RR %.1f | %s",
entry, sl, tp, rr, entrySource);
return;
}
IctMssEntry_CancelTicket(g_ictLowTf.mss.pendingTicket);
@@ -550,8 +695,8 @@ void IctMssEntry_Update(const string sym)
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);
g_ictLowTf.mss.displayReason = StringFormat("Sell/Buy limit %.2f | SL %.2f | TP %.2f RR%.1f | %s",
entry, sl, tp, rr, entrySource);
}
#endif
+69 -20
View File
@@ -148,8 +148,9 @@ bool IctMss_HasFvgPriceTouch(const string sym, IctFvgZone &h1)
return (IctMss_GetFvgTouchTime(sym, h1) > 0);
}
// Touch hợp lệ cho POI mới = touch xảy ra SAU thời điểm lệnh trước đóng.
// Tránh pick H1 FVG đã có firstTouch từ trước khi lệnh cũ TP/SL.
// Touch hợp lệ cho POI mới = touch xảy ra SAU thời điểm lệnh trước đóng + đạt FILL threshold theo PD position.
// - FVG nằm hẳn trong vùng đúng chiều → InpFvgTouchFillPure (25%)
// - FVG straddle equilibrium → InpFvgTouchFillMixed (50%)
bool IctMss_HasFreshFvgTouch(const string sym, IctFvgZone &h1)
{
const datetime tTouch = IctMss_GetFvgTouchTime(sym, h1);
@@ -157,6 +158,12 @@ bool IctMss_HasFreshFvgTouch(const string sym, IctFvgZone &h1)
return false;
if(g_ictMssAfterCloseGuard > 0 && tTouch < g_ictMssAfterCloseGuard)
return false;
const double requiredPct = IctFvg_RequiredTouchFillPct(h1);
if(requiredPct >= 100.0 - 1e-6)
return false;
if(h1.maxFillRatio * 100.0 < requiredPct - 1e-9)
return false;
return true;
}
@@ -393,20 +400,24 @@ bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf,
if(!IctMss_UpdateLiveM5Swings(sym, bias, tTouch))
return false;
if(g_ictLowTf.mss.liveL0Time < tTouch && g_ictLowTf.mss.liveH0Time < tTouch)
return false;
datetime breakT = 0;
if(bias == ICT_BIAS_BEAR)
{
// Pullback bullish phải có CẤU TRÚC ĐẦY ĐỦ: 2 đỉnh (H0 & H1) đều SAU touch
// → tránh case mới có 1 đỉnh từ pullback mới, H1 còn dính sóng cũ
if(g_ictLowTf.mss.liveH1Price <= 0.0 ||
g_ictLowTf.mss.liveH0Time < tTouch ||
g_ictLowTf.mss.liveH1Time < tTouch)
return false;
if(!IctMss_FindMssBreakBar(sym, tf, g_ictLowTf.mss.liveL0Price,
g_ictLowTf.mss.liveL0Time, true, breakT))
return false;
if(breakT < tTouch)
return false;
// SL trên cao nhất giữa H0 và H1 (cap toàn pullback bull) + buffer ATR (cộng ở MssEntry)
// SL trên cao nhất giữa H0 và H1 (cap toàn pullback bull) + buffer (cộng ở MssEntry)
double slSwingHi = g_ictLowTf.mss.liveH0Price;
if(g_ictLowTf.mss.liveH1Price > slSwingHi)
slSwingHi = g_ictLowTf.mss.liveH1Price;
@@ -417,13 +428,20 @@ bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf,
}
else if(bias == ICT_BIAS_BULL)
{
// Pullback bearish phải có CẤU TRÚC ĐẦY ĐỦ: 2 đáy (L0 & L1) đều SAU touch
// → tránh case mới có 1 đáy từ pullback mới, L1 còn dính sóng cũ
if(g_ictLowTf.mss.liveL1Price <= 0.0 ||
g_ictLowTf.mss.liveL0Time < tTouch ||
g_ictLowTf.mss.liveL1Time < tTouch)
return false;
if(!IctMss_FindMssBreakBar(sym, tf, g_ictLowTf.mss.liveH0Price,
g_ictLowTf.mss.liveH0Time, false, breakT))
return false;
if(breakT < tTouch)
return false;
// SL dưới thấp nhất giữa L0 và L1 (đáy toàn pullback bear) + buffer ATR (trừ ở MssEntry)
// SL dưới thấp nhất giữa L0 và L1 (đáy toàn pullback bear) + buffer (trừ ở MssEntry)
double slSwingLo = g_ictLowTf.mss.liveL0Price;
if(g_ictLowTf.mss.liveL1Price > 0.0 && g_ictLowTf.mss.liveL1Price < slSwingLo)
slSwingLo = g_ictLowTf.mss.liveL1Price;
@@ -437,6 +455,7 @@ bool IctMss_TryLockMss(const string sym, const ENUM_TIMEFRAMES tf,
g_ictLowTf.mss.chochTime = breakT;
g_ictLowTf.mss.chochLocked = true;
g_ictLowTf.mss.chochBias = bias;
return true;
}
@@ -765,14 +784,25 @@ void IctMss_Update(const string sym)
const datetime tTouch = IctMss_GetFvgTouchTime(sym, g_ictFvgZones[hIdx]);
const bool staleTouch = (tTouch > 0 && g_ictMssAfterCloseGuard > 0 &&
tTouch < g_ictMssAfterCloseGuard);
g_ictLowTf.mss.displayReason = staleTouch ?
StringFormat("POI #%I64u [%.0f%.0f] — chờ touch mới (sau lệnh trước)",
g_ictFvgZones[hIdx].id,
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper) :
StringFormat("POI #%I64u [%.0f%.0f] dist %.0f pts — chờ chạm",
g_ictFvgZones[hIdx].id,
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper,
dist / _Point);
const double reqPct = IctFvg_RequiredTouchFillPct(g_ictFvgZones[hIdx]);
const double curPct = g_ictFvgZones[hIdx].maxFillRatio * 100.0;
if(staleTouch)
g_ictLowTf.mss.displayReason = StringFormat(
"POI #%I64u [%.0f%.0f] — chờ touch mới (sau lệnh trước)",
g_ictFvgZones[hIdx].id,
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper);
else if(tTouch > 0 && curPct < reqPct)
g_ictLowTf.mss.displayReason = StringFormat(
"POI #%I64u [%.0f%.0f] fill %.0f%% / cần %.0f%% — chờ vào sâu hơn",
g_ictFvgZones[hIdx].id,
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper,
curPct, reqPct);
else
g_ictLowTf.mss.displayReason = StringFormat(
"POI #%I64u [%.0f%.0f] dist %.0f pts — chờ chạm",
g_ictFvgZones[hIdx].id,
g_ictFvgZones[hIdx].lower, g_ictFvgZones[hIdx].upper,
dist / _Point);
return;
}
@@ -816,14 +846,33 @@ void IctMss_Update(const string sym)
const datetime tTouch = g_ictLowTf.mss.h1TouchTime;
if(IctMss_UpdateLiveM5Swings(sym, g_ictDailyBias.bias, tTouch))
{
if(g_ictDailyBias.bias == ICT_BIAS_BEAR)
// Check cấu trúc pullback đầy đủ (2 đỉnh/đáy đều sau touch)
const bool fullStruct = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ?
(g_ictLowTf.mss.liveH1Price > 0.0 &&
g_ictLowTf.mss.liveH0Time >= tTouch &&
g_ictLowTf.mss.liveH1Time >= tTouch) :
(g_ictLowTf.mss.liveL1Price > 0.0 &&
g_ictLowTf.mss.liveL0Time >= tTouch &&
g_ictLowTf.mss.liveL1Time >= tTouch);
if(!fullStruct)
{
g_ictLowTf.mss.displayReason = (g_ictDailyBias.bias == ICT_BIAS_BEAR) ?
"Chờ pullback đủ cấu trúc (2 đỉnh H0&H1 sau touch)" :
"Chờ pullback đủ cấu trúc (2 đáy L0&L1 sau touch)";
}
else if(g_ictDailyBias.bias == ICT_BIAS_BEAR)
g_ictLowTf.mss.displayReason = StringFormat(
"Chờ MSS↓ phá L0=%.2f | H0=%.2f (M5 bull)",
g_ictLowTf.mss.liveL0Price, g_ictLowTf.mss.liveH0Price);
"Chờ MSS↓ phá L0=%.2f | H0=%.2f H1=%.2f (M5 bull)",
g_ictLowTf.mss.liveL0Price,
g_ictLowTf.mss.liveH0Price,
g_ictLowTf.mss.liveH1Price);
else
g_ictLowTf.mss.displayReason = StringFormat(
"Chờ MSS↑ phá H0=%.2f | L0=%.2f (M5 bear)",
g_ictLowTf.mss.liveH0Price, g_ictLowTf.mss.liveL0Price);
"Chờ MSS↑ phá H0=%.2f | L0=%.2f L1=%.2f (M5 bear)",
g_ictLowTf.mss.liveH0Price,
g_ictLowTf.mss.liveL0Price,
g_ictLowTf.mss.liveL1Price);
}
else
g_ictLowTf.mss.displayReason = "M5 trong FVG — chờ pivot L0/H0";
+13
View File
@@ -582,6 +582,19 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias();
- Hiển thị 2 dòng cuối panel: counts + WR/Ravg/Net
- Recompute trên `OnInit` và sau mỗi `OnTradeTransaction` close
### v1.164 — Entry chọn M5 FVG vs MSS keyLV theo SL gần nhất
- **Bỏ yêu cầu phải có M5 FVG**: gating mở từ `phase >= ICT_MSS_CHOCH` (sau MSS lock) — không chờ M5_FVG
- Thêm field `chochBias` trong `IctMssState` (lưu side BEAR/BULL khi `TryLockMss` thành công)
- `IctMssEntry_ComputeLevels`: tính 2 candidate entries, chọn cái có risk = `|entry SL|` nhỏ hơn:
- **A) M5 FVG limit** (nếu có): `IctMssEntry_LimitPrice(m5Zone)`
- **B) MSS keyLV**: `chochKeyLevel` (L0 phá cho BEAR, H0 phá cho BULL)
- Lọc validity: limit phải đúng phía thị trường (`entry < ask` cho BUY, `entry > bid` cho SELL), risk > 0
- Nếu có FVG nhưng entry@FVG cho risk lớn hơn entry@MSS → chọn MSS
- Nếu chưa có FVG → chọn MSS ngay khi MSS lock
- DisplayReason in nguồn entry: `"M5 FVG (risk X < MSS Y)"` hoặc `"MSS keyLV (risk X ≤ FVG Y)"` / `"MSS keyLV (no M5 FVG)"`
- Trên mỗi tick: nếu candidate mới tốt hơn (M5 FVG xuất hiện sau MSS) → cancel limit cũ + replace
### v1.160 — Pending timeout 4h → reset chờ FVG mới
- Input: `InpMssPendingExpireHours = 4` (mặc định, 0 = không timeout)
+11 -5
View File
@@ -224,11 +224,14 @@ struct IctMssState
datetime m5FvgTime;
double slSwingPrice;
bool chochLocked;
ENUM_ICT_BIAS chochBias;
ulong pendingTicket;
datetime pendingPlacedTime;
double pendingEntry;
double pendingSl;
double pendingTp;
double partialTriggerPrice;
bool partialCloseDone;
double liveL0Price;
double liveH0Price;
datetime liveL0Time;
@@ -253,11 +256,14 @@ struct IctMssState
m5FvgTime = 0;
slSwingPrice = 0.0;
chochLocked = false;
pendingTicket = 0;
pendingPlacedTime = 0;
pendingEntry = 0.0;
pendingSl = 0.0;
pendingTp = 0.0;
chochBias = ICT_BIAS_NONE;
pendingTicket = 0;
pendingPlacedTime = 0;
pendingEntry = 0.0;
pendingSl = 0.0;
pendingTp = 0.0;
partialTriggerPrice = 0.0;
partialCloseDone = false;
liveL0Price = 0.0;
liveH0Price = 0.0;
liveL0Time = 0;
Binary file not shown.