Optimize POI range, SL range
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
+56
-3
@@ -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);
|
||||
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)
|
||||
|
||||
+192
-47
@@ -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,52 +178,54 @@ 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)
|
||||
if(isBuy)
|
||||
{
|
||||
slOut = swingSl - bufSl;
|
||||
const double risk = entryOut - slOut;
|
||||
const double risk = isBuy ? (entryOut - slOut) : (slOut - entryOut);
|
||||
if(risk <= _Point)
|
||||
{
|
||||
reasonOut = StringFormat("Risk≤0 (SL %.5f ≥ entry %.5f)", slOut, entryOut);
|
||||
reasonOut = StringFormat("Risk≤0 (SL %.5f vs entry %.5f)", slOut, entryOut);
|
||||
return false;
|
||||
}
|
||||
|
||||
double tpAtIH0 = 0.0;
|
||||
double rrAtIH0 = 0.0;
|
||||
// Swing-based TP (= TP "tự nhiên" tại iL0/iH0 ± bufTp) — dùng cho partial close trigger
|
||||
double swingTp = 0.0;
|
||||
if(isBuy)
|
||||
{
|
||||
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;
|
||||
}
|
||||
tpOut = (rrAtIL0 > minRR) ? tpAtIL0 : (entryOut - risk * minRR);
|
||||
}
|
||||
|
||||
if(rrAtIL0 > minRR)
|
||||
tpOut = tpAtIL0;
|
||||
else
|
||||
tpOut = 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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
return;
|
||||
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
|
||||
|
||||
@@ -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,11 +784,22 @@ 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)",
|
||||
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) :
|
||||
StringFormat("POI #%I64u [%.0f–%.0f] dist %.0f pts — chờ chạm",
|
||||
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);
|
||||
@@ -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";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
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.
Reference in New Issue
Block a user