Optimize setup SL/TP base on spread onl, no atr for stable

This commit is contained in:
Bell
2026-05-26 03:44:09 +07:00
parent 19c5ac149a
commit 8f4a3df295
11 changed files with 219 additions and 83 deletions
+29 -2
View File
@@ -3,8 +3,8 @@
//| BOS = Continue | CHoCH = Reversal |
//+------------------------------------------------------------------+
#property copyright "ICT 2026"
#property version "1.151"
#property description "ICT2026 MSS | SL = swing + ATR + spread | TP = iL0/iH0 + ATR 2×spread (dễ khớp)"
#property version "1.163"
#property description "ICT2026 MSS | OnlyStatsMode: skip render + in stats mỗi lệnh close + tổng kết deinit"
#include <ICT2026/Config.mqh>
#include <ICT2026/DailyBias.mqh>
@@ -12,6 +12,7 @@
#include <ICT2026/LowTfTrend.mqh>
#include <ICT2026/Panel.mqh>
#include <ICT2026/Draw.mqh>
#include <ICT2026/Stats.mqh>
//+------------------------------------------------------------------+
int OnInit()
@@ -29,6 +30,12 @@ int OnInit()
IctMssStats_Recompute(_Symbol, InpMssMagic);
if(InpOnlyStatsMode)
{
PrintFormat("[ICT2026] OnlyStatsMode = ON — skip render/draw, chỉ in stats khi deinit");
return INIT_SUCCEEDED;
}
if(biasOk || intraOk)
{
IctEaState_Refresh(_Symbol);
@@ -49,6 +56,13 @@ int OnInit()
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IctMssStats_Recompute(_Symbol, InpMssMagic);
PrintFormat("[ICT2026] === FINAL %s | %s ===",
IctMssStats_LineCounts(), IctMssStats_LinePerf());
if(InpOnlyStatsMode)
return;
IctPanel_Clear();
IctDraw_Clear();
IctFvgDraw_DeleteAll();
@@ -71,6 +85,9 @@ void OnTick()
IctLowTfTrend_TickRefresh(_Symbol);
if(InpOnlyStatsMode)
return;
if(refresh || !panelBoot)
{
IctPanel_Render(_Symbol);
@@ -81,6 +98,8 @@ void OnTick()
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(InpOnlyStatsMode)
return;
if(id == CHARTEVENT_CHART_CHANGE)
{
IctPanel_Render(_Symbol);
@@ -117,6 +136,14 @@ void OnTradeTransaction(const MqlTradeTransaction &trans,
IctMss_OnPositionClosed(_Symbol, reason, net);
IctMssStats_Recompute(_Symbol, InpMssMagic);
if(InpOnlyStatsMode)
{
PrintFormat("[ICT2026] %s | %s",
IctMssStats_LineCounts(), IctMssStats_LinePerf());
return;
}
IctEaState_Refresh(_Symbol);
IctPanel_Render(_Symbol);
IctMssDraw_Render(_Symbol);
+7 -7
View File
@@ -19,6 +19,9 @@ input int InpIntradaySwingRange = 2; // Pivot intraday
input int InpIntradaySwingLookback = 120; // Lookback quét pivot H1
input int InpIntradayRecentBars = 80; // Chỉ dùng pivot trong N bar gần nhất
input group "══ Test mode ══"
input bool InpOnlyStatsMode = false; // true = skip toàn bộ render/draw + journal verbose (chạy tester nhanh, chỉ in stats cuối)
input group "══ Hiển thị ══"
input bool InpDrawPanel = true; // Panel bias góc trên trái
input int InpPanelX = 12;
@@ -77,13 +80,10 @@ 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 ENUM_TIMEFRAMES InpMssSlAtrTf = PERIOD_M5; // TF tính ATR cho buffer SL
input ENUM_TIMEFRAMES InpMssTpAtrTf = PERIOD_M5; // TF tính ATR cho buffer TP
input int InpMssBufferAtrPeriod = 14; // Period ATR cho buffer SL/TP
input double InpMssSlAtrMult = 2.0; // SL: buffer ngoài max(H0,H1)/min(L0,L1) (× ATR M5) — tránh spike đỉnh
input double InpMssMinRR = 1.5; // TP tối thiểu (× risk entry→SL) — block trade nếu iL0/iH0 không đạt
input double InpMssTpAtrBuffer = 2.0; // TP buffer trước iL0/iH0 (× ATR M5) — chốt trước vùng cản
input int InpMssPendingExpireHours = 24; // Hết hạn pending (giờ, 0=không)
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 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)
input int InpMssEodHour = 23; // Giờ EOD theo SERVER time (24h, vd EET broker = 23h ≈ 16:00 ET DST)
+2
View File
@@ -96,6 +96,8 @@ bool IctDraw_BuildConfirmSwings(const string sym, IctSwingSet &sw)
void IctDraw_Render(const string sym)
{
if(InpOnlyStatsMode)
return;
if(!InpDrawChartLabels)
{
IctDraw_Clear();
+2
View File
@@ -59,6 +59,8 @@ void IctFvgDraw_HLine(const string name, const datetime t1, const datetime t2,
void IctFvgDraw_Render(const string sym)
{
if(InpOnlyStatsMode)
return;
if(!InpDrawFvgZones)
{
IctFvgDraw_DeleteAll();
+4
View File
@@ -22,6 +22,8 @@ bool IctMss_IsEntryReadyReason(const string reason)
void IctMss_JournalPipeline(const string reason)
{
if(InpOnlyStatsMode)
return;
if(!InpMssLogJournal)
return;
if(IctMss_IsEntryReadyReason(reason))
@@ -35,6 +37,8 @@ void IctMss_JournalPipeline(const string reason)
void IctMss_JournalEntryBlock(const string reason)
{
if(InpOnlyStatsMode)
return;
if(!InpMssLogJournal)
return;
if(reason == g_ictMssEntryJournalLast)
+2
View File
@@ -92,6 +92,8 @@ void IctMssDraw_ResolveChoch(const IctMssState &mss,
void IctMssDraw_Render(const string sym)
{
if(InpOnlyStatsMode)
return;
if(!InpDrawMssChoch)
{
IctMssDraw_DeleteAll();
+127 -64
View File
@@ -55,22 +55,6 @@ double IctMssEntry_VolumeForRisk(const string sym, const bool isBuy,
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)
@@ -81,13 +65,18 @@ double IctMssEntry_LimitPrice(const IctFvgZone &zone)
}
bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
double &entryOut, double &slOut, double &tpOut)
double &entryOut, double &slOut, double &tpOut,
string &reasonOut)
{
entryOut = slOut = tpOut = 0.0;
reasonOut = "";
entryOut = IctMssEntry_LimitPrice(m5Zone);
if(entryOut <= 0.0)
{
reasonOut = "M5 FVG side không xác định";
return false;
}
const bool isBuy = (m5Zone.side == ICT_FVG_BULL);
const ENUM_ICT_BIAS bias = isBuy ? ICT_BIAS_BULL : ICT_BIAS_BEAR;
@@ -100,26 +89,22 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
if(g_ictLowTf.mss.slSwingPrice > 0.0)
swingSl = g_ictLowTf.mss.slSwingPrice;
else
{
reasonOut = "Chưa có swing H0/L0 M5 cho SL";
return false;
}
}
// SL dùng ATR M5 (sát swing M5, không quá rộng để giữ risk thấp + RR đạt minRR)
// TP dùng ATR H1 (trung bình sóng H1 đang đánh, buffer rõ ràng trên chart)
double atrSl = IctMssEntry_Atr(sym, InpMssSlAtrTf, InpMssBufferAtrPeriod);
if(atrSl <= 0.0)
atrSl = IctMssEntry_Atr(sym, InpConfirmTf, InpFvgAtrPeriod);
double atrTp = IctMssEntry_Atr(sym, InpMssTpAtrTf, InpMssBufferAtrPeriod);
if(atrTp <= 0.0)
atrTp = atrSl;
// SL: cộng spread (stop trigger theo ask/bid — bù để khỏi bị quét sớm)
// TP: trừ 2×spread (TP gần entry hơn để dễ khớp — không cần giá hit chính xác iL0/iH0)
// Buffer SL/TP thuần spread — ổn định, không lệ thuộc ATR động
// SL = swing + InpMssSlSpreadMult × spread (cộng ra ngoài đỉnh/đáy)
// TP = iL0/iH0 ± InpMssTpSpreadMult × spread (chốt trước vùng cản để dễ khớp)
const double spreadNow = MathMax(0.0,
SymbolInfoDouble(sym, SYMBOL_ASK) -
SymbolInfoDouble(sym, SYMBOL_BID));
const double spreadUnit = (spreadNow > 0.0) ? spreadNow : _Point;
const double bufSl = ((atrSl > 0.0) ? atrSl * InpMssSlAtrMult : 100.0 * _Point) + spreadNow;
const double bufTp = ((atrTp > 0.0) ? atrTp * InpMssTpAtrBuffer : 50.0 * _Point) + 2.0 * spreadNow;
const double bufSl = InpMssSlSpreadMult * spreadUnit;
const double bufTp = InpMssTpSpreadMult * spreadUnit;
const double minRR = MathMax(1.0, InpMssMinRR);
// ── TP target = iL0 (BEAR) / iH0 (BULL): sóng H1, m5 chỉ để entry
@@ -131,47 +116,54 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
else if(!isBuy && iSw.hasL0)
tpTarget = iSw.l0.price;
// ── 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;
if(risk <= _Point)
return false;
if(tpTarget > 0.0 && tpTarget > entryOut + bufTp + _Point)
tpOut = tpTarget - bufTp;
else
tpOut = entryOut + risk * minRR; // fallback nếu iH0 không có hoặc quá gần
const double rrAchieved = (tpOut - entryOut) / risk;
if(rrAchieved < minRR - 0.01)
{
g_ictLowTf.mss.displayReason = StringFormat(
"RR=%.2f < min %.2f (entry=%.2f SL=%.2f TP=%.2f) — chốt iH0 không đủ",
rrAchieved, minRR, entryOut, slOut, tpOut);
reasonOut = StringFormat("Risk≤0 (SL %.5f ≥ entry %.5f)", slOut, entryOut);
return false;
}
double tpAtIH0 = 0.0;
double rrAtIH0 = 0.0;
if(tpTarget > 0.0 && tpTarget > entryOut + bufTp + _Point)
{
tpAtIH0 = tpTarget - bufTp;
rrAtIH0 = (tpAtIH0 - entryOut) / risk;
}
if(rrAtIH0 > minRR)
tpOut = tpAtIH0;
else
tpOut = entryOut + risk * minRR;
}
else
{
slOut = swingSl + bufSl;
const double risk = slOut - entryOut;
if(risk <= _Point)
return false;
if(tpTarget > 0.0 && tpTarget < entryOut - bufTp - _Point)
tpOut = tpTarget + bufTp;
else
tpOut = entryOut - risk * minRR; // fallback nếu iL0 không có hoặc quá gần
const double rrAchieved = (entryOut - tpOut) / risk;
if(rrAchieved < minRR - 0.01)
{
g_ictLowTf.mss.displayReason = StringFormat(
"RR=%.2f < min %.2f (entry=%.2f SL=%.2f TP=%.2f) — chốt iL0 không đủ",
rrAchieved, minRR, entryOut, slOut, tpOut);
reasonOut = StringFormat("Risk≤0 (SL %.5f ≤ entry %.5f)", slOut, entryOut);
return false;
}
double tpAtIL0 = 0.0;
double rrAtIL0 = 0.0;
if(tpTarget > 0.0 && tpTarget < entryOut - bufTp - _Point)
{
tpAtIL0 = tpTarget + bufTp;
rrAtIL0 = (entryOut - tpAtIL0) / risk;
}
if(rrAtIL0 > minRR)
tpOut = tpAtIL0;
else
tpOut = entryOut - risk * minRR;
}
entryOut = IctMssEntry_NormalizePrice(sym, entryOut);
@@ -179,9 +171,17 @@ bool IctMssEntry_ComputeLevels(const string sym, const IctFvgZone &m5Zone,
tpOut = IctMssEntry_NormalizePrice(sym, tpOut);
if(isBuy && (slOut >= entryOut - _Point || tpOut <= entryOut + _Point))
{
reasonOut = StringFormat("SL/TP sai phía (BUY entry=%.5f SL=%.5f TP=%.5f)",
entryOut, slOut, tpOut);
return false;
}
if(!isBuy && (slOut <= entryOut + _Point || tpOut >= entryOut - _Point))
{
reasonOut = StringFormat("SL/TP sai phía (SELL entry=%.5f SL=%.5f TP=%.5f)",
entryOut, slOut, tpOut);
return false;
}
return true;
}
@@ -267,6 +267,59 @@ void IctMss_OnEodCancel(const string sym, const ulong ticket)
ticket, h1Id, m5Id);
}
void IctMss_OnPendingTimeout(const string sym, const ulong ticket, const int hours)
{
const ulong m5Id = g_ictLowTf.mss.m5FvgId;
const ulong h1Id = g_ictLowTf.mss.h1FvgId;
if(ticket > 0 && OrderSelect(ticket))
g_ictMssTrade.OrderDelete(ticket);
IctMssEntry_MarkM5FvgUsed(sym, m5Id);
IctMss_ResetState();
// Guard giống OnPositionClosed: chỉ accept H1 FVG có touch sau timeout
g_ictMssAfterCloseGuard = TimeCurrent();
g_ictLowTf.mss.displayReason = StringFormat(
"Timeout %dh pending #%I64u | M5 FVG #%I64u → Used | chờ touch FVG mới",
hours, ticket, m5Id);
if(InpMssLogJournal)
PrintFormat("[ICT2026/MSS] Pending timeout %dh #%I64u | H1 #%I64u | M5 #%I64u → Used | guard=%s | reset → WAIT_FVG_TOUCH",
hours, ticket, h1Id, m5Id,
TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES));
}
void IctMssEntry_CheckPendingTimeout(const string sym)
{
if(InpMssPendingExpireHours <= 0)
return;
if(g_ictLowTf.mss.pendingTicket == 0)
return;
if(g_ictLowTf.mss.pendingPlacedTime == 0)
return;
if(IctMssEntry_HasOpenPositionMagic(sym))
return;
const ulong ticket = g_ictLowTf.mss.pendingTicket;
if(!OrderSelect(ticket))
{
// Pending đã biến mất (broker expire / cancel manual) mà chưa thành position
// → vẫn coi như timeout, reset state để chờ FVG mới
IctMss_OnPendingTimeout(sym, 0, InpMssPendingExpireHours);
return;
}
const datetime placed = g_ictLowTf.mss.pendingPlacedTime;
const int elapsedSec = (int)(TimeCurrent() - placed);
const int timeoutSec = InpMssPendingExpireHours * 3600;
if(elapsedSec < timeoutSec)
return;
IctMss_OnPendingTimeout(sym, ticket, InpMssPendingExpireHours);
}
void IctMssEntry_CheckEodCancel(const string sym)
{
if(!InpMssCancelPendingEod)
@@ -316,13 +369,19 @@ void IctMss_OnPositionClosed(const string sym, const long reason, const double n
IctMssEntry_MarkM5FvgUsed(sym, m5Id);
IctMss_ResetState();
// Guard: chặn pipeline pick H1 FVG có touch trước thời điểm này.
// Phải đợi touch mới (FVG mới hoặc re-touch) thì mới setup MSS + entry mới.
g_ictMssAfterCloseGuard = TimeCurrent();
g_ictLowTf.mss.displayReason = StringFormat(
"Đóng %s (net %.2f) | M5 FVG #%I64u → Used | chờ POI mới",
"Đóng %s (net %.2f) | M5 FVG #%I64u → Used | chờ touch FVG mới",
rt, netProfit, m5Id);
if(InpMssLogJournal)
PrintFormat("[ICT2026/MSS] Close (%s, net=%.2f) | H1 #%I64u | M5 #%I64u → Used | reset → WAIT_FVG_TOUCH",
rt, netProfit, h1Id, m5Id);
PrintFormat("[ICT2026/MSS] Close (%s, net=%.2f) | H1 #%I64u | M5 #%I64u → Used | guard=%s | reset → WAIT_FVG_TOUCH",
rt, netProfit, h1Id, m5Id,
TimeToString(g_ictMssAfterCloseGuard, TIME_DATE | TIME_MINUTES));
}
bool IctMssEntry_CancelTicket(const ulong ticket)
@@ -387,6 +446,7 @@ void IctMssEntry_Update(const string sym)
s_hadPosition = nowPosition;
IctMssEntry_CheckEodCancel(sym);
IctMssEntry_CheckPendingTimeout(sym);
if(g_ictLowTf.mss.phase == ICT_MSS_IDLE && g_ictLowTf.mss.pendingTicket > 0)
{
@@ -437,10 +497,12 @@ void IctMssEntry_Update(const string sym)
const IctFvgZone m5 = g_ictConfirmFvgZones[mIdx];
double entry = 0.0, sl = 0.0, tp = 0.0;
if(!IctMssEntry_ComputeLevels(sym, m5, entry, sl, tp))
string lvlReason = "";
if(!IctMssEntry_ComputeLevels(sym, m5, entry, sl, tp, lvlReason))
{
g_ictLowTf.mss.displayReason = StringFormat("%s | chờ H0/L0 M5 cho SL",
IctMssPhaseText(g_ictLowTf.mss.phase));
g_ictLowTf.mss.displayReason = StringFormat("%s | %s",
IctMssPhaseText(g_ictLowTf.mss.phase),
lvlReason);
IctMss_JournalEntryBlock(g_ictLowTf.mss.displayReason);
return;
}
@@ -483,10 +545,11 @@ void IctMssEntry_Update(const string sym)
return;
}
g_ictLowTf.mss.pendingTicket = ticket;
g_ictLowTf.mss.pendingEntry = entry;
g_ictLowTf.mss.pendingSl = sl;
g_ictLowTf.mss.pendingTp = tp;
g_ictLowTf.mss.pendingTicket = ticket;
g_ictLowTf.mss.pendingPlacedTime = TimeCurrent();
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);
}
+29 -5
View File
@@ -10,6 +10,11 @@
#include <ICT2026/StructureCore.mqh>
#include <ICT2026/Swing.mqh>
// Guard: sau khi 1 lệnh đóng (TP/SL/manual), pipeline KHÔNG được pick H1 FVG
// nào có firstTouch trước thời điểm này. Phải đợi 1 touch mới (FVG mới hoặc
// FVG cũ được re-touched) thì mới tiếp tục setup MSS + entry.
datetime g_ictMssAfterCloseGuard = 0;
string IctMssPhaseText(const ENUM_ICT_MSS_PHASE ph)
{
switch(ph)
@@ -143,6 +148,18 @@ 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.
bool IctMss_HasFreshFvgTouch(const string sym, IctFvgZone &h1)
{
const datetime tTouch = IctMss_GetFvgTouchTime(sym, h1);
if(tTouch <= 0)
return false;
if(g_ictMssAfterCloseGuard > 0 && tTouch < g_ictMssAfterCloseGuard)
return false;
return true;
}
bool IctMss_HasH1FvgRetest(const string sym, IctFvgZone &h1)
{
return IctMss_HasFvgPriceTouch(sym, h1);
@@ -741,14 +758,21 @@ void IctMss_Update(const string sym)
g_ictLowTf.mss.h1FvgId = g_ictFvgZones[hIdx].id;
if(!IctMss_HasFvgPriceTouch(sym, g_ictFvgZones[hIdx]))
if(!IctMss_HasFreshFvgTouch(sym, g_ictFvgZones[hIdx]))
{
const double px = SymbolInfoDouble(sym, SYMBOL_BID);
const double dist = IctMss_DistancePriceToFvg(px, g_ictFvgZones[hIdx]);
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);
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);
return;
}
+2
View File
@@ -103,6 +103,8 @@ void IctPanel_SetLine(const long ch, const int index, const int yOffset,
void IctPanel_Render(const string sym)
{
if(InpOnlyStatsMode)
return;
if(!InpDrawPanel)
{
IctPanel_Clear();
+9 -1
View File
@@ -582,6 +582,14 @@ 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.160 — Pending timeout 4h → reset chờ FVG mới
- Input: `InpMssPendingExpireHours = 4` (mặc định, 0 = không timeout)
- Tracked `pendingPlacedTime` trong `IctMssState`
- Mỗi tick (`IctMssEntry_CheckPendingTimeout`): nếu `TimeCurrent() - pendingPlacedTime ≥ N×3600s` hoặc order biến mất (broker expire) → `OrderDelete` + `MarkM5FvgUsed` + `IctMss_ResetState` + set `g_ictMssAfterCloseGuard`
- Hành vi giống TP/SL close: reset về `WAIT_FVG_TOUCH`, chỉ accept H1 FVG có touch sau timeout
- Bỏ qua nếu đang có position magic (chỉ tác động lên pending)
### v1.140 — Hủy pending cuối phiên Mỹ (EOD)
- Input: `InpMssCancelPendingEod` (true), `InpMssEodHour` (23), `InpMssEodMinute` (0) — **server time**
@@ -721,7 +729,7 @@ ENUM_ICT_BIAS ICT2026_GetDailyBias();
### 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
- SL: trên **max(H0,H1)** M5 (bear) / dưới **min(L0,L1)** M5 (bull) + `InpMssSlSpreadMult`×spread
- 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
+6 -4
View File
@@ -225,6 +225,7 @@ struct IctMssState
double slSwingPrice;
bool chochLocked;
ulong pendingTicket;
datetime pendingPlacedTime;
double pendingEntry;
double pendingSl;
double pendingTp;
@@ -252,10 +253,11 @@ struct IctMssState
m5FvgTime = 0;
slSwingPrice = 0.0;
chochLocked = false;
pendingTicket = 0;
pendingEntry = 0.0;
pendingSl = 0.0;
pendingTp = 0.0;
pendingTicket = 0;
pendingPlacedTime = 0;
pendingEntry = 0.0;
pendingSl = 0.0;
pendingTp = 0.0;
liveL0Price = 0.0;
liveH0Price = 0.0;
liveL0Time = 0;