From 317de52f9b91787c6d373db54ac53e1cc79a6fc6 Mon Sep 17 00:00:00 2001 From: Bell Date: Sun, 8 Mar 2026 06:45:32 +0700 Subject: [PATCH] =?UTF-8?q?MSS:=20=C4=90nag=20b=E1=BB=8B=20miss=20l?= =?UTF-8?q?=E1=BB=87nh=20do=20thi=E1=BA=BFu=20sweep=20(c=C3=B3=20th?= =?UTF-8?q?=E1=BB=83=20conflict=20trends=20c=E1=BA=A7n=20ki=E1=BB=83m=20tr?= =?UTF-8?q?a=20l=E1=BA=A1i=20khi=20c=C3=B3=20mss)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Experts/EA_ICT_CL.mq5 | 1154 +++++++++++++++++++++++++++++++++-------- 1 file changed, 952 insertions(+), 202 deletions(-) diff --git a/Experts/EA_ICT_CL.mq5 b/Experts/EA_ICT_CL.mq5 index 90ba2f7..d10ffd1 100644 --- a/Experts/EA_ICT_CL.mq5 +++ b/Experts/EA_ICT_CL.mq5 @@ -1,5 +1,5 @@ //+------------------------------------------------------------------+ -//| ICT EA – FVG Edition | +//| ICT EA – FVG Edition (MQL5) v3.0 | //| Architecture : BiasTF(D1) + MiddleTF(H1) + TriggerTF(M5) | //| State Machine: IDLE → WAIT_TOUCH → WAIT_TRIGGER → IN_TRADE | //| | @@ -8,14 +8,25 @@ //| TOUCHED → giá chạm vào gap (bid inside) | //| USED → case0=expired / case1=broken / case2=triggered | //| | -//| MSS (Market Structure Shift): | -//| Xảy ra khi bar[1].close phá vỡ KeyLevel của trend hiện tại | -//| MiddleTF: vẽ marker M_ / TriggerTF: vẽ marker T_ | -//| TriggerTF MSS = điều kiện case 2 entry | +//| MSS (Market Structure Shift) – v3 definition: | +//| MSS hợp lệ = Liquidity Sweep + Structure Break | +//| | +//| Bullish MSS (MiddleTF UP): | +//| 1. Sweep: wick < L0, close > L0 (quét sell-side liq) | +//| 2. Break: close > H0 (phá vỡ swing high → đảo chiều lên) | +//| | +//| Bearish MSS (MiddleTF DOWN): | +//| 1. Sweep: wick > H0, close < H0 (quét buy-side liq) | +//| 2. Break: close < L0 (phá vỡ swing low → đảo chiều xuống) | +//| | +//| Không có sweep → MSS không hợp lệ, bỏ qua | +//| | +//| TriggerTF MSS chỉ nhận thuận chiều MiddleTF trend | //| | //| Drawing: objects KHÔNG BAO GIỜ bị xóa, chỉ update | //+------------------------------------------------------------------+ -#property strict +#property copyright "Bell's ICT EA" +#property version "3.00" #define MAX_FVG_POOL 30 @@ -44,6 +55,9 @@ input int InpFVGScanBars = 50; // MiddleTF bars to scan input double InpFVGMinBodyPct = 60.0; // Mid-candle min body % input int InpTriggerMaxBars = 30; // Trigger timeout (TriggerTF bars) +input long InpMagicNumber = 20250308; // EA magic number +input int InpSlippage = 5; // Max slippage (points) + input bool InpDebugLog = true; // Journal logging input bool InpDebugDraw = true; // Chart drawing @@ -82,38 +96,52 @@ struct BiasContext datetime lastBarTime; }; -// Shared for both MiddleTF and TriggerTF. -// Includes MSS tracking: recorded each time bar[1].close breaks the KeyLevel. +//---------------------------------------------------------------------- +// TFTrendContext – Swing structure + MSS + associated sweep +// +// v3: MSS hợp lệ yêu cầu có Liquidity Sweep trước đó. +// Khi MSS được ghi nhận, sweep info cũng được lưu kèm +// để vẽ cả 2 thành 1 pattern hoàn chỉnh. +//---------------------------------------------------------------------- struct TFTrendContext { // ── Swing structure ──────────────────────────────────────────── - MarketDir trend; - double h0, h1, l0, l1; - int idxH0, idxH1, idxL0, idxL1; - double keyLevel; // L0 (uptrend) or H0 (downtrend) - datetime lastBarTime; // Guard: recalculate only on new bar + MarketDir trend; // DIR_UP / DIR_DOWN / DIR_NONE + double h0, h1, l0, l1; // 2 swing highs + 2 swing lows gần nhất + int idxH0, idxH1, idxL0, idxL1; // Bar indices tương ứng + double keyLevel; // L0 (uptrend) hoặc H0 (downtrend) + datetime lastBarTime; // Guard: chỉ recalc trên bar mới // ── MSS tracking ─────────────────────────────────────────────── - // lastMssTime : open time of the bar whose close broke keyLevel - // lastMssLevel : the key level that was broken - // lastMssBreak : DIR_UP = close > KL (bear→bull flip) - // DIR_DOWN = close < KL (bull→bear flip) - datetime lastMssTime; - double lastMssLevel; - MarketDir lastMssBreak; + datetime lastMssTime; // Open time cây nến break structure + double lastMssLevel; // Key level bị phá (giá break qua) + MarketDir lastMssBreak; // DIR_UP = bull MSS, DIR_DOWN = bear MSS + + // ── v3: Sweep đi kèm MSS (chỉ có giá trị khi lastMssTime > 0) ─ + datetime mssSweepTime; // Thời gian cây nến sweep + double mssSweepLevel; // Swing level bị quét (L0 hoặc H0) + double mssSweepWick; // Wick extreme (low cho bull, high cho bear) }; -struct FVGRecord // Direct array access only – no & ref +struct FVGRecord { int id; FVGStatus status; - int usedCase; // 0=expired 1=broken 2=triggered + int usedCase; // 0=expired 1=broken 2=triggered MarketDir direction; double high, low, mid; datetime createdTime; - datetime touchTime; // Tick time when bid first entered gap + datetime touchTime; datetime usedTime; - MarketDir triggerTrendAtTouch; // M5 trend at touch, used for case2 detect + MarketDir triggerTrendAtTouch; // M5 trend tại thời điểm touch +}; + +struct TriggerFVGRecord +{ + bool valid; + MarketDir direction; + double high, low, mid; + datetime createdTime; }; struct TriggerContext @@ -128,8 +156,9 @@ struct TriggerContext struct OrderPlan { bool valid; - int direction; + int direction; // +1 buy, -1 sell double entry, stopLoss, takeProfit, lot; + int parentFVGId; }; struct DailyRiskContext @@ -139,6 +168,25 @@ struct DailyRiskContext bool limitHit; }; +//---------------------------------------------------------------------- +// v3: Liquidity Sweep event (internal tracking, KHÔNG vẽ riêng) +// +// Sweep = wick quét qua swing rồi close lại phía trong: +// Bullish: low < L0 && close > L0 → quét sell-side liquidity +// Bearish: high > H0 && close < H0 → quét buy-side liquidity +// +// Sweep chỉ dùng làm prerequisite cho valid MSS. +// Khi MSS xảy ra, sweep info được copy vào TFTrendContext. +//---------------------------------------------------------------------- +struct LiqSweepEvent +{ + bool valid; + MarketDir direction; // DIR_UP = bull sweep, DIR_DOWN = bear + double sweptLevel; // Swing level bị quét + double extremeWick; // Wick thực tế (low cho bull, high cho bear) + datetime time; // Open time cây nến sweep +}; + //==================================================== // GLOBALS //==================================================== @@ -157,17 +205,39 @@ int g_NextFVGId = 0; int g_ActiveFVGIdx = -1; int g_TradeBarIndex = -1; +TriggerFVGRecord g_TrigFVG; +OrderPlan g_OrderPlan; +ulong g_PendingTicket = 0; + +// v3: Sweep tracking (internal, prerequisite cho MSS) +LiqSweepEvent g_LastLiqSweep; + // Chart object name prefixes -// SW_ : MiddleTF swing arrows/labels/key-level lines -// TS_ : TriggerTF swing arrows/labels/key-level lines -// MSS_ : MSS markers (both TFs, distinguished by M/T inside name) -// FVGP_ : FVG pool rectangles / midlines / labels -// DBG_ : Debug panel labels -const string SW_PREFIX = "SW_"; -const string TS_PREFIX = "TS_"; -const string MSS_PREFIX = "MSS_"; -const string FVGP_PREFIX = "FVGP_"; -const string DBG_PREFIX = "DBG_"; +const string SW_PREFIX = "SW_"; // MiddleTF swing arrows/labels +const string TS_PREFIX = "TS_"; // TriggerTF swing arrows/labels +const string MSS_PREFIX = "MSS_"; // MSS markers (cả sweep + break) +const string FVGP_PREFIX = "FVGP_"; // MiddleTF FVG rectangles +const string TFVG_PREFIX = "TFVG_"; // TriggerTF FVG rectangles +const string ORD_PREFIX = "ORD_"; // v3: Order visualization +const string DBG_PREFIX = "DBG_"; // Debug panel labels + +//+------------------------------------------------------------------+ +//| MQL5 HELPER: iBarShift replacement | +//+------------------------------------------------------------------+ +int MyBarShift(string symbol, ENUM_TIMEFRAMES tf, datetime time, bool exact = false) +{ + datetime arr[]; + int maxCopy = MathMin(Bars(symbol, tf), 5000); // Giới hạn 5000 bars tránh quá tải + int copied = CopyTime(symbol, tf, 0, maxCopy, arr); + if (copied <= 0) return -1; + + for (int i = copied - 1; i >= 0; i--) // Duyệt từ mới → cũ + { + if (arr[i] <= time) + return copied - 1 - i; // Chuyển array index → bar shift + } + return exact ? -1 : copied - 1; +} //+------------------------------------------------------------------+ //| SECTION 1 – SWING HELPERS | @@ -264,52 +334,170 @@ void UpdateBiasContext() //---------------------------------------------------------------------- // UpdateTFTrendContext // -// Called once per bar on each TF. Does two things: +// Gọi mỗi bar mới trên tf. Thực hiện 3 việc TRƯỚC khi re-scan swing: // -// 1. MSS CHECK (before re-scan): -// Compares bar[1].close against the CURRENT keyLevel. -// If close breaks below L0 (uptrend) or above H0 (downtrend): -// → records MSS event in ctx.lastMssTime / lastMssLevel / lastMssBreak -// MSS objects are drawn separately in DrawMSSMarkers(). +// 1a. TriggerTF: Detect Liquidity Sweep (wick quét swing) +// 1b. MSS check: close phá keyLevel +// - MiddleTF: ghi nhận mọi MSS +// - TriggerTF v3: CHỈ ghi nhận nếu: +// (a) thuận chiều MiddleTF trend +// (b) có Sweep trước đó (g_LastLiqSweep hợp lệ) +// 2. Re-scan swing structure → update trend/keyLevel // -// 2. SWING RE-SCAN: -// Finds new H0/H1/L0/L1 from bar data and resolves trend. -// keyLevel may change after re-scan (new L0 or H0). -// -// Note: MSS is detected BEFORE re-scan so we capture the old keyLevel -// that was broken, not the new one computed after the flip. +// Tất cả detection dùng OLD swing levels (trước re-scan). //---------------------------------------------------------------------- void UpdateTFTrendContext(ENUM_TIMEFRAMES tf, int lookback, TFTrendContext &ctx) { datetime t0 = iTime(_Symbol, tf, 0); - if (t0 == ctx.lastBarTime) return; + if (t0 == ctx.lastBarTime) return; // Chỉ chạy 1 lần/bar ctx.lastBarTime = t0; - // ── 1. MSS check against existing key level ───────────────────── - if (ctx.trend != DIR_NONE && ctx.keyLevel > 0) + double bar1C = iClose(_Symbol, tf, 1); // Bar vừa đóng: close + double bar1H = iHigh (_Symbol, tf, 1); // high + double bar1L = iLow (_Symbol, tf, 1); // low + double bar1O = iOpen (_Symbol, tf, 1); // open + datetime bar1T = iTime (_Symbol, tf, 1); // time + + // ══════════════════════════════════════════════════════════════════ + // 1a. TriggerTF: Liquidity Sweep detection (TRƯỚC MSS check) + // + // Bullish sweep (H1 UP): wick < L0 && close > L0 + // → Giá quét dưới swing low, hút stop-loss bên sell + // → Rồi đóng lại phía trên → reversal signal + // + // Bearish sweep (H1 DOWN): wick > H0 && close < H0 + // → Giá quét trên swing high, hút stop-loss bên buy + // → Rồi đóng lại phía dưới → reversal signal + // + // Sweep KHÔNG được vẽ riêng. Chỉ dùng làm prerequisite cho MSS. + // ══════════════════════════════════════════════════════════════════ + if (tf == InpTriggerTF && g_MiddleTrend.trend != DIR_NONE) { - double c1 = iClose(_Symbol, tf, 1); - datetime t1 = iTime (_Symbol, tf, 1); - - bool mssHit = - (ctx.trend == DIR_UP && c1 < ctx.keyLevel) || // bull→bear: close below L0 - (ctx.trend == DIR_DOWN && c1 > ctx.keyLevel); // bear→bull: close above H0 - - if (mssHit && t1 != ctx.lastMssTime) + if (g_MiddleTrend.trend == DIR_UP && ctx.l0 > 0) // Bullish setup { - ctx.lastMssTime = t1; - ctx.lastMssLevel = ctx.keyLevel; - ctx.lastMssBreak = (ctx.trend == DIR_UP) ? DIR_DOWN : DIR_UP; + if (bar1L < ctx.l0 // Wick quét dưới L0 + && bar1C > ctx.l0 // Close đóng lại trên L0 + && bar1T != g_LastLiqSweep.time) // Chưa ghi nhận cây này + { + g_LastLiqSweep.valid = true; + g_LastLiqSweep.direction = DIR_UP; // Bull sweep = quét sell-side + g_LastLiqSweep.sweptLevel = ctx.l0; // Mức swing bị quét + g_LastLiqSweep.extremeWick = bar1L; // Wick thấp nhất + g_LastLiqSweep.time = bar1T; - if (InpDebugLog) - PrintFormat("[%s MSS] %s | KL=%.5f | close=%.5f | bar=%s", - EnumToString(tf), - (ctx.lastMssBreak == DIR_UP) ? "Bear→Bull ▲" : "Bull→Bear ▼", - ctx.lastMssLevel, c1, TimeToString(t1)); + if (InpDebugLog) + PrintFormat("[SWEEP ▲] below L0=%.5f | wick=%.5f close=%.5f | %s", + ctx.l0, bar1L, bar1C, TimeToString(bar1T)); + } + } + else if (g_MiddleTrend.trend == DIR_DOWN && ctx.h0 > 0) // Bearish setup + { + if (bar1H > ctx.h0 // Wick quét trên H0 + && bar1C < ctx.h0 // Close đóng lại dưới H0 + && bar1T != g_LastLiqSweep.time) + { + g_LastLiqSweep.valid = true; + g_LastLiqSweep.direction = DIR_DOWN; // Bear sweep = quét buy-side + g_LastLiqSweep.sweptLevel = ctx.h0; + g_LastLiqSweep.extremeWick = bar1H; // Wick cao nhất + g_LastLiqSweep.time = bar1T; + + if (InpDebugLog) + PrintFormat("[SWEEP ▼] above H0=%.5f | wick=%.5f close=%.5f | %s", + ctx.h0, bar1H, bar1C, TimeToString(bar1T)); + } } } - // ── 2. Re-scan swing structure ─────────────────────────────────── + // ══════════════════════════════════════════════════════════════════ + // 1b. MSS check (dùng OLD keyLevel, trước re-scan) + // + // Uptrend: keyLevel = L0 → MSS khi close < L0 (bearish break) + // Downtrend: keyLevel = H0 → MSS khi close > H0 (bullish break) + // + // v3 TriggerTF: MSS chỉ hợp lệ khi: + // (a) breakDir thuận chiều MiddleTF (VD: H1 UP → chỉ nhận bull MSS) + // (b) Có Liquidity Sweep trước đó cùng hướng + // (bull MSS cần bull sweep, bear MSS cần bear sweep) + // (c) Sweep xảy ra TRƯỚC cây MSS (sweep.time < bar1T) + // + // MiddleTF: ghi nhận mọi MSS (không cần sweep filter) + // ══════════════════════════════════════════════════════════════════ + if (ctx.trend != DIR_NONE && ctx.keyLevel > 0) + { + bool mssHit = + (ctx.trend == DIR_UP && bar1C < ctx.keyLevel) || // Bull→Bear: close < L0 + (ctx.trend == DIR_DOWN && bar1C > ctx.keyLevel); // Bear→Bull: close > H0 + + if (mssHit && bar1T != ctx.lastMssTime) + { + MarketDir breakDir = (ctx.trend == DIR_UP) ? DIR_DOWN : DIR_UP; + + // ── Determine if this MSS should be recorded ─────────────── + bool recordMss = true; + string rejectReason = ""; + + if (tf == InpTriggerTF) + { + // Filter (a): thuận chiều MiddleTF + if (g_MiddleTrend.trend != DIR_NONE && breakDir != (MarketDir)g_MiddleTrend.trend) + { + recordMss = false; + rejectReason = "ngược chiều H1"; + } + + // Filter (b): phải có sweep trước đó, cùng hướng + if (recordMss) + { + bool hasSweep = + g_LastLiqSweep.valid && // Sweep tồn tại + g_LastLiqSweep.direction == breakDir && // Cùng hướng với MSS + g_LastLiqSweep.time < bar1T; // Sweep xảy ra TRƯỚC MSS + + if (!hasSweep) + { + recordMss = false; + rejectReason = "chưa có sweep"; + } + } + } + + // ── Record hoặc skip MSS ────────────────────────────────── + if (recordMss) + { + ctx.lastMssTime = bar1T; // Thời gian cây nến MSS + ctx.lastMssLevel = ctx.keyLevel; // Key level bị phá + ctx.lastMssBreak = breakDir; // Hướng break + + // v3: Lưu sweep info kèm MSS (cho TriggerTF) + if (tf == InpTriggerTF && g_LastLiqSweep.valid) + { + ctx.mssSweepTime = g_LastLiqSweep.time; // Copy sweep → MSS context + ctx.mssSweepLevel = g_LastLiqSweep.sweptLevel; + ctx.mssSweepWick = g_LastLiqSweep.extremeWick; + } + + if (InpDebugLog) + PrintFormat("[%s MSS ✓] %s | KL=%.5f | close=%.5f | sweep@%s | bar=%s", + EnumToString(tf), + (breakDir == DIR_UP) ? "Bear→Bull ▲" : "Bull→Bear ▼", + ctx.lastMssLevel, bar1C, + (tf == InpTriggerTF) ? TimeToString(g_LastLiqSweep.time, TIME_MINUTES) : "n/a", + TimeToString(bar1T)); + } + else if (InpDebugLog) + { + PrintFormat("[%s MSS ✗] %s bị skip: %s | KL=%.5f | close=%.5f", + EnumToString(tf), + (breakDir == DIR_UP) ? "▲" : "▼", + rejectReason, ctx.keyLevel, bar1C); + } + } + } + + // ══════════════════════════════════════════════════════════════════ + // 2. Re-scan swing structure + // ══════════════════════════════════════════════════════════════════ double h0, h1, l0, l1; int idxH0, idxH1, idxL0, idxL1; if (!ScanSwingStructure(tf, lookback, h0, h1, idxH0, idxH1, l0, l1, idxL0, idxL1)) @@ -400,7 +588,11 @@ void ResetToIdle(string reason = "") PrintFormat("[RESET→IDLE] %s", reason); g_ActiveFVGIdx = -1; g_TradeBarIndex = -1; + g_PendingTicket = 0; ZeroMemory(g_Trigger); + ZeroMemory(g_TrigFVG); + ZeroMemory(g_OrderPlan); + ZeroMemory(g_LastLiqSweep); // v3: reset sweep khi cycle mới TransitionTo(EA_IDLE); } @@ -426,12 +618,6 @@ bool IsFVGInPool(datetime created) //+------------------------------------------------------------------+ //| SECTION 6 – FVG POOL: SCAN & REGISTER | -//| | -//| Called once per MiddleTF bar (static guard). | -//| Scans InpFVGScanBars window. Determines initial status: | -//| P1 → case1 (close through gap) → USED(1) | -//| P2 → touch (wick entered gap) → TOUCHED | -//| P3 → default → PENDING / USED(expired) | //+------------------------------------------------------------------+ void ScanAndRegisterFVGs() @@ -493,7 +679,7 @@ void ScanAndRegisterFVGs() rec.createdTime = created; int rightBar = i - 1; - // P1: case1 – H1 close punched through gap + // P1: case1 – close punched through gap bool c1Hit = false; datetime c1T = 0; for (int j = rightBar - 1; j >= 1; j--) { @@ -534,14 +720,6 @@ void ScanAndRegisterFVGs() //+------------------------------------------------------------------+ //| SECTION 7 – FVG POOL: UPDATE STATUSES (every tick) | -//| | -//| Priority per FVG: | -//| 1. Case1 : H1 bar[1] close through gap → USED(1) | -//| 2. Expire: PENDING age > limit → USED(0) | -//| 3. Touch : bid entered gap zone → TOUCHED | -//| 4. Case2 : TOUCHED + TriggerTF MSS confirmed → USED(2) | -//| (wasOpposing at touch, nowAligned) | -//| 5. Timeout: TOUCHED too long → release active FVG (keep TOUCHED)| //+------------------------------------------------------------------+ void UpdateFVGStatuses() @@ -554,7 +732,7 @@ void UpdateFVGStatuses() { if (g_FVGPool[i].status == FVG_USED) continue; - // ── 1. Case1 ───────────────────────────────────────────────────── + // ── 1. Case1: H1 close phá xuyên qua FVG → broken ────────────── bool c1 = (g_FVGPool[i].direction == DIR_UP && midC1 < g_FVGPool[i].low) || (g_FVGPool[i].direction == DIR_DOWN && midC1 > g_FVGPool[i].high); if (c1) @@ -570,7 +748,7 @@ void UpdateFVGStatuses() if (g_FVGPool[i].status == FVG_PENDING) { - // ── 2. Expire ──────────────────────────────────────────────────── + // ── 2. Expire: quá thời hạn ───────────────────────────────────── int age = (int)(TimeCurrent() - g_FVGPool[i].createdTime); if (age > InpFVGMaxAliveMin * 60) { @@ -581,7 +759,7 @@ void UpdateFVGStatuses() PrintFormat("[FVG #%d] USED(expired) | age=%dmin", g_FVGPool[i].id, age / 60); continue; } - // ── 3. Touch ───────────────────────────────────────────────────── + // ── 3. Touch: bid vào vùng gap ─────────────────────────────────── bool touched = (g_FVGPool[i].direction == DIR_UP && bid <= g_FVGPool[i].high) || (g_FVGPool[i].direction == DIR_DOWN && bid >= g_FVGPool[i].low); if (touched) @@ -597,10 +775,7 @@ void UpdateFVGStatuses() } else if (g_FVGPool[i].status == FVG_TOUCHED) { - // ── 4. Case2 – TriggerTF MSS ───────────────────────────────────── - // Condition: at touch the TriggerTF trend was OPPOSING the FVG direction - // (retracement confirmed), and NOW it has FLIPPED to align - // (MSS confirmed on TriggerTF → entry signal) + // ── 4. Case2: TriggerTF MSS confirmed → entry signal ──────────── bool wasOpposing, nowAligned; if (g_FVGPool[i].direction == DIR_UP) { wasOpposing = (g_FVGPool[i].triggerTrendAtTouch != DIR_UP); nowAligned = (g_TriggerTrend.trend == DIR_UP); } @@ -635,6 +810,212 @@ void UpdateFVGStatuses() } } +//+------------------------------------------------------------------+ +//| SECTION 7B – TRIGGER TF FVG SCANNER | +//+------------------------------------------------------------------+ + +bool ScanTriggerTFFVG(datetime touchTime, datetime mssTime, MarketDir dir) +{ + ZeroMemory(g_TrigFVG); + + int barTouch = MyBarShift(_Symbol, InpTriggerTF, touchTime); + int barMss = MyBarShift(_Symbol, InpTriggerTF, mssTime); + + if (barTouch < 0 || barMss < 0) return false; + if (barTouch <= barMss + 2) return false; // Cần ít nhất 3 bars giữa 2 mốc + + if (InpDebugLog) + PrintFormat("[TFVG SCAN] touchBar=%d mssBar=%d dir=%s | scanning %d bars on %s", + barTouch, barMss, EnumToString(dir), barTouch - barMss, EnumToString(InpTriggerTF)); + + for (int i = barMss + 2; i < barTouch; i++) // Scan từ gần MSS về phía touch + { + double leftH = iHigh (_Symbol, InpTriggerTF, i + 1); + double leftL = iLow (_Symbol, InpTriggerTF, i + 1); + double rightH = iHigh (_Symbol, InpTriggerTF, i - 1); + double rightL = iLow (_Symbol, InpTriggerTF, i - 1); + double midO = iOpen (_Symbol, InpTriggerTF, i); + double midC = iClose(_Symbol, InpTriggerTF, i); + double gH = 0, gL = 0; + + if (dir == DIR_UP) + { + if (leftH >= rightL) continue; // Không có gap + if (midC <= midO) continue; // Mid candle phải bullish + gL = leftH; gH = rightL; + } + else + { + if (leftL <= rightH) continue; + if (midC >= midO) continue; // Mid candle phải bearish + gH = leftL; gL = rightH; + } + + if (gH - gL < _Point) continue; // Gap quá nhỏ + + g_TrigFVG.valid = true; + g_TrigFVG.direction = dir; + g_TrigFVG.high = gH; + g_TrigFVG.low = gL; + g_TrigFVG.mid = (gH + gL) / 2.0; + g_TrigFVG.createdTime = iTime(_Symbol, InpTriggerTF, i - 1); + + if (InpDebugLog) + PrintFormat("[TFVG FOUND] %s [%.5f–%.5f] mid=%.5f | bar=%d %s", + EnumToString(dir), gL, gH, g_TrigFVG.mid, i, + TimeToString(g_TrigFVG.createdTime)); + return true; + } + + if (InpDebugLog) + Print("[TFVG SCAN] No TriggerTF FVG found between touch and MSS"); + return false; +} + +//+------------------------------------------------------------------+ +//| SECTION 7C – ORDER PLAN & EXECUTION (MQL5 native) | +//+------------------------------------------------------------------+ + +double CalcLotFromRisk(double entry, double sl) +{ + double balance = AccountInfoDouble(ACCOUNT_BALANCE); + double riskMoney = balance * InpRiskPercent / 100.0; + double slPips = MathAbs(entry - sl) / _Point; + if (slPips < 1) return 0; + + double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); + double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); + if (tickValue <= 0 || tickSize <= 0) return 0; + + double pipValue = tickValue * (_Point / tickSize); + double rawLot = riskMoney / (slPips * pipValue); + + double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + if (lotStep <= 0) lotStep = 0.01; + + rawLot = MathFloor(rawLot / lotStep) * lotStep; + rawLot = MathMax(minLot, MathMin(maxLot, rawLot)); + + return NormalizeDouble(rawLot, 2); +} + +bool BuildOrderPlan(int fvgId, MarketDir dir) +{ + ZeroMemory(g_OrderPlan); + + if (!g_TrigFVG.valid) return false; + + double entry = NormalizeDouble(g_TrigFVG.mid, _Digits); // Entry = mid TriggerTF FVG + double sl, tp; + + if (dir == DIR_UP) + { + sl = g_TriggerTrend.l0; // SL dưới TriggerTF swing low + if (sl <= 0 || sl >= entry) return false; + sl = NormalizeDouble(sl - 2 * _Point, _Digits); // Buffer 2 points + double riskDist = entry - sl; + tp = NormalizeDouble(entry + InpRiskReward * riskDist, _Digits); // TP = entry + 2R + } + else + { + sl = g_TriggerTrend.h0; // SL trên TriggerTF swing high + if (sl <= 0 || sl <= entry) return false; + sl = NormalizeDouble(sl + 2 * _Point, _Digits); + double riskDist = sl - entry; + tp = NormalizeDouble(entry - InpRiskReward * riskDist, _Digits); + } + + double lot = CalcLotFromRisk(entry, sl); + if (lot <= 0) return false; + + g_OrderPlan.valid = true; + g_OrderPlan.direction = (dir == DIR_UP) ? 1 : -1; + g_OrderPlan.entry = entry; + g_OrderPlan.stopLoss = sl; + g_OrderPlan.takeProfit = tp; + g_OrderPlan.lot = lot; + g_OrderPlan.parentFVGId = fvgId; + + if (InpDebugLog) + PrintFormat("[ORDER PLAN] %s | entry=%.5f SL=%.5f TP=%.5f lot=%.2f | R=%.1f%% RR=%.1f | FVG#%d", + (dir == DIR_UP) ? "BUY LIMIT" : "SELL LIMIT", + entry, sl, tp, lot, InpRiskPercent, InpRiskReward, fvgId); + return true; +} + +ulong ExecuteLimitOrder() +{ + if (!g_OrderPlan.valid) return 0; + + ENUM_ORDER_TYPE cmd = (g_OrderPlan.direction > 0) + ? ORDER_TYPE_BUY_LIMIT + : ORDER_TYPE_SELL_LIMIT; + + string comment = StringFormat("ICT_FVG#%d", g_OrderPlan.parentFVGId); + + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + if (cmd == ORDER_TYPE_BUY_LIMIT && g_OrderPlan.entry >= ask) + { + if (InpDebugLog) + PrintFormat("[ORDER] BUY LIMIT rejected: entry %.5f >= ask %.5f → skip", + g_OrderPlan.entry, ask); + return 0; + } + if (cmd == ORDER_TYPE_SELL_LIMIT && g_OrderPlan.entry <= bid) + { + if (InpDebugLog) + PrintFormat("[ORDER] SELL LIMIT rejected: entry %.5f <= bid %.5f → skip", + g_OrderPlan.entry, bid); + return 0; + } + + MqlTradeRequest request; + MqlTradeResult result; + ZeroMemory(request); + ZeroMemory(result); + + request.action = TRADE_ACTION_PENDING; + request.symbol = _Symbol; + request.volume = g_OrderPlan.lot; + request.type = cmd; + request.price = g_OrderPlan.entry; + request.sl = g_OrderPlan.stopLoss; + request.tp = g_OrderPlan.takeProfit; + request.deviation = (ulong)InpSlippage; + request.magic = InpMagicNumber; + request.comment = comment; + request.type_filling = ORDER_FILLING_RETURN; + request.type_time = ORDER_TIME_GTC; + + if (!OrderSend(request, result)) + { + PrintFormat("[ORDER] ❌ OrderSend failed | retcode=%u | %s %.2f @ %.5f SL=%.5f TP=%.5f", + result.retcode, + (cmd == ORDER_TYPE_BUY_LIMIT) ? "BUY_LIMIT" : "SELL_LIMIT", + g_OrderPlan.lot, g_OrderPlan.entry, + g_OrderPlan.stopLoss, g_OrderPlan.takeProfit); + return 0; + } + + if (result.retcode != TRADE_RETCODE_DONE && result.retcode != TRADE_RETCODE_PLACED) + { + PrintFormat("[ORDER] ❌ Server rejected | retcode=%u comment=%s", + result.retcode, result.comment); + return 0; + } + + PrintFormat("[ORDER] ✅ %s ticket=%llu | %.2f @ %.5f SL=%.5f TP=%.5f | %s", + (cmd == ORDER_TYPE_BUY_LIMIT) ? "BUY_LIMIT" : "SELL_LIMIT", + result.order, g_OrderPlan.lot, g_OrderPlan.entry, + g_OrderPlan.stopLoss, g_OrderPlan.takeProfit, comment); + + return result.order; +} + //+------------------------------------------------------------------+ //| SECTION 8 – BEST FVG SELECTOR | //| Priority: TOUCHED newest > PENDING newest | @@ -711,21 +1092,130 @@ void OnStateWaitTrigger() PrintFormat("[ENTRY SIGNAL] FVG #%d %s [%.5f–%.5f]", g_FVGPool[ai].id, EnumToString(g_FVGPool[ai].direction), g_FVGPool[ai].low, g_FVGPool[ai].high); - // TODO: BuildOrderPlan() → ExecuteOrder() - TransitionTo(EA_IN_TRADE); + + bool hasTFVG = ScanTriggerTFFVG( + g_FVGPool[ai].touchTime, + g_TriggerTrend.lastMssTime, + g_FVGPool[ai].direction); + + if (hasTFVG && BuildOrderPlan(g_FVGPool[ai].id, g_FVGPool[ai].direction)) + { + ulong ticket = ExecuteLimitOrder(); + if (ticket > 0) + { + g_PendingTicket = ticket; + g_TradeBarIndex = Bars(_Symbol, InpTriggerTF); + TransitionTo(EA_IN_TRADE); + } + else + ResetToIdle(StringFormat("FVG #%d order failed", g_FVGPool[ai].id)); + } + else + ResetToIdle(StringFormat("FVG #%d no valid TriggerTF FVG or order plan", g_FVGPool[ai].id)); } else ResetToIdle(StringFormat("FVG #%d used(case%d) during trigger", g_FVGPool[ai].id, g_FVGPool[ai].usedCase)); return; } if (g_FVGPool[ai].status == FVG_PENDING) { TransitionTo(EA_WAIT_TOUCH); return; } - // Still TOUCHED – wait for case2 } void OnStateInTrade() { - if (Bars(_Symbol, InpTriggerTF) <= g_TradeBarIndex) return; - // TODO: Monitor position → ResetToIdle("trade closed") when flat + // ── 1. Kiểm tra pending order còn active không ───────────────── + if (g_PendingTicket > 0) + { + bool foundPending = false; + for (int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if (ticket == g_PendingTicket) + { + foundPending = true; + break; + } + } + + if (!foundPending) + { + // Tìm position tương ứng (limit đã fill) + bool posFound = false; + for (int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong posTicket = PositionGetTicket(i); + if (posTicket == 0) continue; + if (PositionGetInteger(POSITION_MAGIC) == InpMagicNumber && + PositionGetString(POSITION_SYMBOL) == _Symbol) + { + posFound = true; + g_PendingTicket = 0; // Filled → track position + if (InpDebugLog) + PrintFormat("[TRADE] Limit filled → position ticket=%llu", posTicket); + break; + } + } + + if (!posFound) + { + HistorySelect(TimeCurrent() - 86400, TimeCurrent()); + + // Check: lệnh bị hủy? + bool wasCancelled = false; + for (int i = HistoryOrdersTotal() - 1; i >= 0; i--) + { + ulong hTicket = HistoryOrderGetTicket(i); + if (hTicket == g_PendingTicket) + { + long state = HistoryOrderGetInteger(hTicket, ORDER_STATE); + if (state == ORDER_STATE_CANCELED || state == ORDER_STATE_EXPIRED || + state == ORDER_STATE_REJECTED) + wasCancelled = true; + break; + } + } + if (wasCancelled) { ResetToIdle("pending order cancelled/expired"); return; } + + // Check: position đã đóng? + bool dealFound = false; + for (int i = HistoryDealsTotal() - 1; i >= 0; i--) + { + ulong dTicket = HistoryDealGetTicket(i); + if (HistoryDealGetInteger(dTicket, DEAL_MAGIC) == InpMagicNumber && + HistoryDealGetString(dTicket, DEAL_SYMBOL) == _Symbol && + HistoryDealGetInteger(dTicket, DEAL_ENTRY) == DEAL_ENTRY_OUT) + { + double profit = HistoryDealGetDouble(dTicket, DEAL_PROFIT); + if (InpDebugLog) + PrintFormat("[TRADE] Position closed | deal=%llu profit=%.2f", dTicket, profit); + dealFound = true; + break; + } + } + + if (dealFound) ResetToIdle("trade closed"); + else ResetToIdle("pending order lost"); + return; + } + } + return; // Pending still alive → wait + } + + // ── 2. Track open position (pending đã fill) ──────────────────── + bool hasPosition = false; + for (int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong posTicket = PositionGetTicket(i); + if (posTicket == 0) continue; + if (PositionGetInteger(POSITION_MAGIC) == InpMagicNumber && + PositionGetString(POSITION_SYMBOL) == _Symbol) + { + hasPosition = true; + break; + } + } + + if (!hasPosition) + ResetToIdle("trade closed – no position"); } //+------------------------------------------------------------------+ @@ -750,20 +1240,7 @@ void RunStateMachine() //+------------------------------------------------------------------+ //---------------------------------------------------------------------- -// DrawOneSwingPoint -// -// Generic swing-point drawing. Used for both MiddleTF and TriggerTF. -// Parameters: -// prefix – object name prefix ("SW_" or "TS_") -// tf – timeframe whose bar data supplies the time/range -// tag – short label ("H0" / "H1" / "L0" / "L1") -// isHigh – true for swing high, false for swing low -// barIdx – bar index on tf -// price – swing price -// clr – arrow + label color -// isKL – draw a dashed key-level line to bar[0] -// arrowSz – OBJPROP_WIDTH of arrow (2 for MTF, 1 for LTF) -// fontSize – label font size +// DrawOneSwingPoint – generic swing-point drawer //---------------------------------------------------------------------- void DrawOneSwingPoint( string prefix, ENUM_TIMEFRAMES tf, @@ -805,10 +1282,6 @@ void DrawOneSwingPoint( else ObjectDelete(0, klN); } -//---------------------------------------------------------------------- -// DrawMiddleSwingPoints – MiddleTF (H1) H0/H1/L0/L1 -// Colors: H=Aqua / L=Yellow (brighter, thicker – higher TF) -//---------------------------------------------------------------------- void DrawMiddleSwingPoints() { if (!InpDebugDraw) return; @@ -824,11 +1297,6 @@ void DrawMiddleSwingPoints() DrawOneSwingPoint(SW_PREFIX, InpMiddleTF, "L1", false, g_MiddleTrend.idxL1, g_MiddleTrend.l1, C'160,140,0', false, 2, 8); } -//---------------------------------------------------------------------- -// DrawTriggerSwingPoints – TriggerTF (M5) H0/H1/L0/L1 -// Colors: H=Violet / L=Orange (smaller, distinct from H1) -// Key-level line drawn for the active side (L0 in uptrend, H0 in downtrend) -//---------------------------------------------------------------------- void DrawTriggerSwingPoints() { if (!InpDebugDraw) return; @@ -838,56 +1306,50 @@ void DrawTriggerSwingPoints() bool isUp = (g_TriggerTrend.trend == DIR_UP); bool isDown = (g_TriggerTrend.trend == DIR_DOWN); - DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "H0", true, g_TriggerTrend.idxH0, g_TriggerTrend.h0, C'180,100,255', isDown, 1, 7); - DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "H1", true, g_TriggerTrend.idxH1, g_TriggerTrend.h1, C'100,60,160', false, 1, 7); - DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "L0", false, g_TriggerTrend.idxL0, g_TriggerTrend.l0, C'255,160,40', isUp, 1, 7); - DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "L1", false, g_TriggerTrend.idxL1, g_TriggerTrend.l1, C'160,100,20', false, 1, 7); + DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "tH0", true, g_TriggerTrend.idxH0, g_TriggerTrend.h0, C'180,100,255', isDown, 1, 7); + DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "tH1", true, g_TriggerTrend.idxH1, g_TriggerTrend.h1, C'100,60,160', false, 1, 7); + DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "tL0", false, g_TriggerTrend.idxL0, g_TriggerTrend.l0, C'255,160,40', isUp, 1, 7); + DrawOneSwingPoint(TS_PREFIX, InpTriggerTF, "tL1", false, g_TriggerTrend.idxL1, g_TriggerTrend.l1, C'160,100,20', false, 1, 7); } //---------------------------------------------------------------------- -// DrawMSSMarker +// DrawMSSMarker – v3: Vẽ MSS kèm sweep marker // -// Draws a single MSS event on the chart. Objects are named with the -// MSS bar timestamp so they accumulate without overwriting each other. +// Khi MSS hợp lệ trên TriggerTF, vẽ CẢ HAI: +// 1. Sweep: ◆ diamond tại wick extreme + đường ngang tại swept level +// 2. Break: ▲/▼ arrow tại close MSS + đường ngang tại broken key level // -// Objects per MSS: -// MSS__ARR – arrow at close of break candle -// MSS__LBL – text "▲MSS" or "▼MSS" next to arrow -// MSS__KL – horizontal dotted line AT the broken key level -// (spans from break candle to current bar[0]) -// -// mssId : unique string identifier (e.g. "M_1234567890" for MiddleTF) -// tf : timeframe of the MSS candle -// mssTime : open time of the break candle (bar[1] when MSS was detected) -// mssLevel : key level price that was broken -// mssBreak : DIR_UP = broke above (bear→bull) / DIR_DOWN = broke below (bull→bear) +// Cả 2 tạo thành 1 pattern hoàn chỉnh trên chart. //---------------------------------------------------------------------- void DrawMSSMarker( string mssId, ENUM_TIMEFRAMES tf, - datetime mssTime, double mssLevel, MarketDir mssBreak) + datetime mssTime, double mssLevel, MarketDir mssBreak, + datetime sweepTime, double sweepLevel, double sweepWick) { if (!InpDebugDraw || mssTime == 0) return; + // ── MSS break: arrow + label + key level line ───────────────── string arrN = MSS_PREFIX + mssId + "_ARR"; string lblN = MSS_PREFIX + mssId + "_LBL"; string klN = MSS_PREFIX + mssId + "_KL"; - bool isBull = (mssBreak == DIR_UP); // broke UP → bull MSS + bool isBull = (mssBreak == DIR_UP); color clr = isBull ? clrLime : clrTomato; - int shift = iBarShift(_Symbol, tf, mssTime); + int shift = MyBarShift(_Symbol, tf, mssTime); + if (shift < 0) return; double closeAtMss = iClose(_Symbol, tf, shift); - // ── Arrow at close price of MSS candle ───────────────────────────── + // Arrow tại close price cây nến MSS if (ObjectFind(0, arrN) < 0) ObjectCreate(0, arrN, OBJ_ARROW, 0, mssTime, closeAtMss); - ObjectSetInteger(0, arrN, OBJPROP_ARROWCODE, isBull ? 233 : 234); // 233=up 234=down + ObjectSetInteger(0, arrN, OBJPROP_ARROWCODE, isBull ? 233 : 234); ObjectSetInteger(0, arrN, OBJPROP_COLOR, clr); ObjectSetInteger(0, arrN, OBJPROP_WIDTH, 2); ObjectSetInteger(0, arrN, OBJPROP_ANCHOR, isBull ? ANCHOR_TOP : ANCHOR_BOTTOM); ObjectMove(0, arrN, 0, mssTime, closeAtMss); - // ── Text label ───────────────────────────────────────────────────── + // Label "▲MSS" / "▼MSS" double rng = iHigh(_Symbol, tf, shift) - iLow(_Symbol, tf, shift); double lblY = isBull ? closeAtMss - rng * 0.4 : closeAtMss + rng * 0.4; if (ObjectFind(0, lblN) < 0) @@ -898,8 +1360,7 @@ void DrawMSSMarker( ObjectSetInteger(0, lblN, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, lblN, OBJPROP_ANCHOR, isBull ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); - // ── Dotted horizontal line at broken key level ───────────────────── - // Shows WHERE the structure broke – extends to current bar + // Dotted line tại broken key level datetime tEnd = iTime(_Symbol, tf, 0); if (ObjectFind(0, klN) < 0) ObjectCreate(0, klN, OBJ_TREND, 0, mssTime, mssLevel, tEnd, mssLevel); @@ -909,55 +1370,300 @@ void DrawMSSMarker( ObjectSetInteger(0, klN, OBJPROP_RAY_RIGHT, false); ObjectMove(0, klN, 0, mssTime, mssLevel); ObjectMove(0, klN, 1, tEnd, mssLevel); + + // ── v3: Sweep marker (chỉ vẽ nếu sweep data hợp lệ) ────────── + if (sweepTime > 0 && sweepLevel > 0) + { + string swpArrN = MSS_PREFIX + mssId + "_SWP_ARR"; // ◆ diamond tại wick + string swpLblN = MSS_PREFIX + mssId + "_SWP_LBL"; // Label "SWEEP" + string swpLvlN = MSS_PREFIX + mssId + "_SWP_LVL"; // Đường ngang tại swept level + string swpWkN = MSS_PREFIX + mssId + "_SWP_WK"; // Vertical wick line + + color swpClr = isBull ? C'0,210,230' : C'230,80,210'; // Cyan / Magenta + + // ◆ Diamond tại wick extreme + if (ObjectFind(0, swpArrN) < 0) + ObjectCreate(0, swpArrN, OBJ_ARROW, 0, sweepTime, sweepWick); + ObjectSetInteger(0, swpArrN, OBJPROP_ARROWCODE, 4); // 4 = diamond + ObjectSetInteger(0, swpArrN, OBJPROP_COLOR, swpClr); + ObjectSetInteger(0, swpArrN, OBJPROP_WIDTH, 2); + ObjectMove(0, swpArrN, 0, sweepTime, sweepWick); + + // Label "SWEEP" + int swShift = MyBarShift(_Symbol, tf, sweepTime); + double swRng = (swShift >= 0) ? iHigh(_Symbol, tf, swShift) - iLow(_Symbol, tf, swShift) : 10 * _Point; + double swLblY = isBull ? sweepWick - swRng * 0.7 : sweepWick + swRng * 0.7; + if (ObjectFind(0, swpLblN) < 0) + ObjectCreate(0, swpLblN, OBJ_TEXT, 0, sweepTime, swLblY); + ObjectMove(0, swpLblN, 0, sweepTime, swLblY); + ObjectSetString (0, swpLblN, OBJPROP_TEXT, "SWEEP"); + ObjectSetInteger(0, swpLblN, OBJPROP_COLOR, swpClr); + ObjectSetInteger(0, swpLblN, OBJPROP_FONTSIZE, 7); + ObjectSetInteger(0, swpLblN, OBJPROP_ANCHOR, isBull ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); + + // Đường ngang đứt tại swept swing level (từ sweep đến MSS) + if (ObjectFind(0, swpLvlN) < 0) + ObjectCreate(0, swpLvlN, OBJ_TREND, 0, sweepTime, sweepLevel, mssTime, sweepLevel); + ObjectSetInteger(0, swpLvlN, OBJPROP_COLOR, swpClr); + ObjectSetInteger(0, swpLvlN, OBJPROP_STYLE, STYLE_DASHDOTDOT); + ObjectSetInteger(0, swpLvlN, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, swpLvlN, OBJPROP_RAY_RIGHT, false); + ObjectMove(0, swpLvlN, 0, sweepTime, sweepLevel); + ObjectMove(0, swpLvlN, 1, mssTime, sweepLevel); + + // Vertical wick line: swept level → wick extreme + if (ObjectFind(0, swpWkN) < 0) + ObjectCreate(0, swpWkN, OBJ_TREND, 0, sweepTime, sweepLevel, sweepTime, sweepWick); + ObjectSetInteger(0, swpWkN, OBJPROP_COLOR, swpClr); + ObjectSetInteger(0, swpWkN, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, swpWkN, OBJPROP_WIDTH, 2); + ObjectMove(0, swpWkN, 0, sweepTime, sweepLevel); + ObjectMove(0, swpWkN, 1, sweepTime, sweepWick); + } } -//---------------------------------------------------------------------- -// DrawMSSMarkers -// -// Calls DrawMSSMarker for the last recorded MSS on each TF. -// Objects are keyed by timestamp so earlier MSS events persist. -// The key-level line for the most recent MSS updates its right edge -// to bar[0] on every tick (keeping it "live" until the next MSS). -//---------------------------------------------------------------------- void DrawMSSMarkers() { if (!InpDebugDraw) return; - // MiddleTF MSS – prefix "M_" + // MiddleTF MSS – vẽ KHÔNG kèm sweep (MiddleTF không cần sweep) if (g_MiddleTrend.lastMssTime > 0) { - string mid = "M_" + IntegerToString((int)g_MiddleTrend.lastMssTime); + string mid = "M_" + IntegerToString((long)g_MiddleTrend.lastMssTime); DrawMSSMarker(mid, InpMiddleTF, - g_MiddleTrend.lastMssTime, - g_MiddleTrend.lastMssLevel, - g_MiddleTrend.lastMssBreak); + g_MiddleTrend.lastMssTime, g_MiddleTrend.lastMssLevel, g_MiddleTrend.lastMssBreak, + 0, 0, 0); // Không có sweep data } - // TriggerTF MSS – prefix "T_" + // TriggerTF MSS – vẽ KÈM sweep (đã validate trong UpdateTFTrendContext) if (g_TriggerTrend.lastMssTime > 0) { - string tid = "T_" + IntegerToString((int)g_TriggerTrend.lastMssTime); + string tid = "T_" + IntegerToString((long)g_TriggerTrend.lastMssTime); DrawMSSMarker(tid, InpTriggerTF, - g_TriggerTrend.lastMssTime, - g_TriggerTrend.lastMssLevel, - g_TriggerTrend.lastMssBreak); + g_TriggerTrend.lastMssTime, g_TriggerTrend.lastMssLevel, g_TriggerTrend.lastMssBreak, + g_TriggerTrend.mssSweepTime, // Sweep time đi kèm MSS + g_TriggerTrend.mssSweepLevel, // Swept swing level + g_TriggerTrend.mssSweepWick); // Wick extreme } } //---------------------------------------------------------------------- -// DrawFVGPool +// v3: DrawOrderVisualization – TradingView-style Entry/SL/TP // -// Right-edge rules: -// PENDING → live MiddleTF bar[0] -// TOUCHED / USED + touchTime > 0 → pin to exact TriggerTF (M5) candle -// USED case1 (touchTime = 0) → pin to MiddleTF broken candle -// -// Colors: -// PENDING bull=C'0,50,110' bear=C'90,25,0' -// TOUCHED bull=C'0,120,220' bear=C'220,75,0' -// USED c2 C'0,100,0' green -// USED c1 C'70,0,0' red -// USED c0 C'50,50,50' grey +// Layout: +// ┌───────────────────────────┐ ◎ TP 1.23568 +112p (2.0R) +// │ TP zone (dark green) │ +// ├───────────────────────────┤ ▶ BUY LIM 1.23456 | 0.05 lot +// │ SL zone (dark red) │ +// └───────────────────────────┘ ✕ SL 1.23400 -56p +//---------------------------------------------------------------------- +void DrawOrderVisualization() +{ + if (!InpDebugDraw) return; + + string tpZoneN = ORD_PREFIX + "TP_ZONE"; + string slZoneN = ORD_PREFIX + "SL_ZONE"; + string entLineN = ORD_PREFIX + "ENTRY_LINE"; + string slLineN = ORD_PREFIX + "SL_LINE"; + string tpLineN = ORD_PREFIX + "TP_LINE"; + string entLblN = ORD_PREFIX + "ENTRY_LBL"; + string slLblN = ORD_PREFIX + "SL_LBL"; + string tpLblN = ORD_PREFIX + "TP_LBL"; + string infoLblN = ORD_PREFIX + "INFO_LBL"; + + // Nếu không có order plan → xóa tất cả ORD_ objects + if (!g_OrderPlan.valid || g_State == EA_IDLE) + { + ObjectDelete(0, tpZoneN); ObjectDelete(0, slZoneN); + ObjectDelete(0, entLineN); ObjectDelete(0, slLineN); ObjectDelete(0, tpLineN); + ObjectDelete(0, entLblN); ObjectDelete(0, slLblN); ObjectDelete(0, tpLblN); + ObjectDelete(0, infoLblN); + return; + } + + bool isBuy = (g_OrderPlan.direction > 0); + double entry = g_OrderPlan.entry; + double sl = g_OrderPlan.stopLoss; + double tp = g_OrderPlan.takeProfit; + double lot = g_OrderPlan.lot; + + // Time range cho rectangles + datetime tStart = (g_TrigFVG.valid) ? g_TrigFVG.createdTime : iTime(_Symbol, InpTriggerTF, 20); + datetime tEnd = iTime(_Symbol, InpTriggerTF, 0) + PeriodSeconds(InpTriggerTF) * 25; + + // Tính pips & R:R + double slPips = MathAbs(entry - sl) / _Point; + double tpPips = MathAbs(tp - entry) / _Point; + double rr = (slPips > 0) ? tpPips / slPips : 0; + + // Colors + color entryClr = isBuy ? C'33,150,243' : C'255,152,0'; // Blue / Orange + color tpFill = C'15,65,35'; // Dark green fill + color slFill = C'85,15,15'; // Dark red fill + color tpLine = C'38,166,91'; // Green line + color slLine = C'229,57,53'; // Red line + + // ── TP Zone rectangle ────────────────────────────────────────── + double tpTop = (isBuy) ? tp : entry; + double tpBot = (isBuy) ? entry : tp; + if (ObjectFind(0, tpZoneN) < 0) + ObjectCreate(0, tpZoneN, OBJ_RECTANGLE, 0, tStart, tpTop, tEnd, tpBot); + ObjectSetInteger(0, tpZoneN, OBJPROP_COLOR, tpFill); + ObjectSetInteger(0, tpZoneN, OBJPROP_FILL, true); + ObjectSetInteger(0, tpZoneN, OBJPROP_BACK, true); + ObjectMove(0, tpZoneN, 0, tStart, tpTop); + ObjectMove(0, tpZoneN, 1, tEnd, tpBot); + + // ── SL Zone rectangle ────────────────────────────────────────── + double slTop = (isBuy) ? entry : sl; + double slBot = (isBuy) ? sl : entry; + if (ObjectFind(0, slZoneN) < 0) + ObjectCreate(0, slZoneN, OBJ_RECTANGLE, 0, tStart, slTop, tEnd, slBot); + ObjectSetInteger(0, slZoneN, OBJPROP_COLOR, slFill); + ObjectSetInteger(0, slZoneN, OBJPROP_FILL, true); + ObjectSetInteger(0, slZoneN, OBJPROP_BACK, true); + ObjectMove(0, slZoneN, 0, tStart, slTop); + ObjectMove(0, slZoneN, 1, tEnd, slBot); + + // ── Entry line (solid 2px, ray right) ────────────────────────── + if (ObjectFind(0, entLineN) < 0) + ObjectCreate(0, entLineN, OBJ_TREND, 0, tStart, entry, tEnd, entry); + ObjectSetInteger(0, entLineN, OBJPROP_COLOR, entryClr); + ObjectSetInteger(0, entLineN, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, entLineN, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, entLineN, OBJPROP_RAY_RIGHT, true); + ObjectMove(0, entLineN, 0, tStart, entry); + ObjectMove(0, entLineN, 1, tEnd, entry); + + // ── TP line (dashed, ray right) ──────────────────────────────── + if (ObjectFind(0, tpLineN) < 0) + ObjectCreate(0, tpLineN, OBJ_TREND, 0, tStart, tp, tEnd, tp); + ObjectSetInteger(0, tpLineN, OBJPROP_COLOR, tpLine); + ObjectSetInteger(0, tpLineN, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, tpLineN, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, tpLineN, OBJPROP_RAY_RIGHT, true); + ObjectMove(0, tpLineN, 0, tStart, tp); + ObjectMove(0, tpLineN, 1, tEnd, tp); + + // ── SL line (dashed, ray right) ──────────────────────────────── + if (ObjectFind(0, slLineN) < 0) + ObjectCreate(0, slLineN, OBJ_TREND, 0, tStart, sl, tEnd, sl); + ObjectSetInteger(0, slLineN, OBJPROP_COLOR, slLine); + ObjectSetInteger(0, slLineN, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, slLineN, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, slLineN, OBJPROP_RAY_RIGHT, true); + ObjectMove(0, slLineN, 0, tStart, sl); + ObjectMove(0, slLineN, 1, tEnd, sl); + + // ── Entry label ──────────────────────────────────────────────── + datetime lblTime = tEnd; + string entTxt = StringFormat("%s %s | %.2f lot", + isBuy ? "▶ BUY LIM" : "▶ SELL LIM", + DoubleToString(entry, _Digits), lot); + if (ObjectFind(0, entLblN) < 0) + ObjectCreate(0, entLblN, OBJ_TEXT, 0, lblTime, entry); + ObjectMove(0, entLblN, 0, lblTime, entry); + ObjectSetString (0, entLblN, OBJPROP_TEXT, entTxt); + ObjectSetInteger(0, entLblN, OBJPROP_COLOR, entryClr); + ObjectSetInteger(0, entLblN, OBJPROP_FONTSIZE, 8); + ObjectSetInteger(0, entLblN, OBJPROP_ANCHOR, ANCHOR_LEFT); + + // ── TP label ─────────────────────────────────────────────────── + string tpTxt = StringFormat("◎ TP %s +%.0fp (%.1fR)", + DoubleToString(tp, _Digits), tpPips, rr); + if (ObjectFind(0, tpLblN) < 0) + ObjectCreate(0, tpLblN, OBJ_TEXT, 0, lblTime, tp); + ObjectMove(0, tpLblN, 0, lblTime, tp); + ObjectSetString (0, tpLblN, OBJPROP_TEXT, tpTxt); + ObjectSetInteger(0, tpLblN, OBJPROP_COLOR, tpLine); + ObjectSetInteger(0, tpLblN, OBJPROP_FONTSIZE, 8); + ObjectSetInteger(0, tpLblN, OBJPROP_ANCHOR, isBuy ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); + + // ── SL label ─────────────────────────────────────────────────── + string slTxt = StringFormat("✕ SL %s -%.0fp", + DoubleToString(sl, _Digits), slPips); + if (ObjectFind(0, slLblN) < 0) + ObjectCreate(0, slLblN, OBJ_TEXT, 0, lblTime, sl); + ObjectMove(0, slLblN, 0, lblTime, sl); + ObjectSetString (0, slLblN, OBJPROP_TEXT, slTxt); + ObjectSetInteger(0, slLblN, OBJPROP_COLOR, slLine); + ObjectSetInteger(0, slLblN, OBJPROP_FONTSIZE, 8); + ObjectSetInteger(0, slLblN, OBJPROP_ANCHOR, isBuy ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); + + // ── Risk info label (giữa entry và SL) ───────────────────────── + double infoY = (entry + sl) / 2.0; + string infoTxt = StringFormat("Risk %.1f%% | SL %.0fp | TP %.0fp | %.1fR", + InpRiskPercent, slPips, tpPips, rr); + if (ObjectFind(0, infoLblN) < 0) + ObjectCreate(0, infoLblN, OBJ_TEXT, 0, lblTime, infoY); + ObjectMove(0, infoLblN, 0, lblTime, infoY); + ObjectSetString (0, infoLblN, OBJPROP_TEXT, infoTxt); + ObjectSetInteger(0, infoLblN, OBJPROP_COLOR, C'160,160,160'); + ObjectSetInteger(0, infoLblN, OBJPROP_FONTSIZE, 7); + ObjectSetInteger(0, infoLblN, OBJPROP_ANCHOR, ANCHOR_LEFT); +} + +//---------------------------------------------------------------------- +// DrawTriggerFVG – TriggerTF FVG rectangle + midline + label +//---------------------------------------------------------------------- +void DrawTriggerFVG() +{ + if (!InpDebugDraw) return; + if (!g_TrigFVG.valid) return; + + string rectN = TFVG_PREFIX + "RECT"; + string midN = TFVG_PREFIX + "MID"; + string lblN = TFVG_PREFIX + "LBL"; + string entN = TFVG_PREFIX + "ENTRY"; + + datetime tStart = g_TrigFVG.createdTime; + datetime tEnd = iTime(_Symbol, InpTriggerTF, 0); + if (tEnd <= tStart) tEnd = tStart + PeriodSeconds(InpTriggerTF) * 10; + + color fillColor = (g_TrigFVG.direction == DIR_UP) ? C'0,180,100' : C'200,50,80'; + + if (ObjectFind(0, rectN) < 0) + ObjectCreate(0, rectN, OBJ_RECTANGLE, 0, tStart, g_TrigFVG.high, tEnd, g_TrigFVG.low); + ObjectSetInteger(0, rectN, OBJPROP_COLOR, fillColor); + ObjectSetInteger(0, rectN, OBJPROP_FILL, true); + ObjectSetInteger(0, rectN, OBJPROP_BACK, true); + ObjectSetInteger(0, rectN, OBJPROP_WIDTH, 1); + ObjectMove(0, rectN, 0, tStart, g_TrigFVG.high); + ObjectMove(0, rectN, 1, tEnd, g_TrigFVG.low); + + if (ObjectFind(0, midN) < 0) + ObjectCreate(0, midN, OBJ_TREND, 0, tStart, g_TrigFVG.mid, tEnd, g_TrigFVG.mid); + ObjectSetInteger(0, midN, OBJPROP_COLOR, clrWhite); + ObjectSetInteger(0, midN, OBJPROP_STYLE, STYLE_DASHDOT); + ObjectSetInteger(0, midN, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, midN, OBJPROP_RAY_RIGHT, false); + ObjectMove(0, midN, 0, tStart, g_TrigFVG.mid); + ObjectMove(0, midN, 1, tEnd, g_TrigFVG.mid); + + if (ObjectFind(0, lblN) < 0) + ObjectCreate(0, lblN, OBJ_TEXT, 0, tStart, g_TrigFVG.high); + ObjectMove(0, lblN, 0, tStart, g_TrigFVG.high); + string sym = (g_TrigFVG.direction == DIR_UP) ? "▲" : "▼"; + ObjectSetString (0, lblN, OBJPROP_TEXT, StringFormat("tFVG %s [%.5f]", sym, g_TrigFVG.mid)); + ObjectSetInteger(0, lblN, OBJPROP_COLOR, fillColor); + ObjectSetInteger(0, lblN, OBJPROP_FONTSIZE, 7); + ObjectSetInteger(0, lblN, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); + + if (g_OrderPlan.valid) + { + if (ObjectFind(0, entN) < 0) + ObjectCreate(0, entN, OBJ_TREND, 0, tStart, g_OrderPlan.entry, tEnd, g_OrderPlan.entry); + ObjectSetInteger(0, entN, OBJPROP_COLOR, clrGold); + ObjectSetInteger(0, entN, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, entN, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, entN, OBJPROP_RAY_RIGHT, true); + ObjectMove(0, entN, 0, tStart, g_OrderPlan.entry); + ObjectMove(0, entN, 1, tEnd, g_OrderPlan.entry); + } +} + +//---------------------------------------------------------------------- +// DrawOneFVGRecord – MiddleTF FVG rectangle + midline + label //---------------------------------------------------------------------- void DrawOneFVGRecord(int idx) { @@ -968,23 +1674,21 @@ void DrawOneFVGRecord(int idx) string midN = FVGP_PREFIX + "MID_" + sid; string lblN = FVGP_PREFIX + "LBL_" + sid; - // ── Right edge ──────────────────────────────────────────────────── datetime rectEnd; if (g_FVGPool[idx].status == FVG_PENDING) rectEnd = iTime(_Symbol, InpMiddleTF, 0); else if (g_FVGPool[idx].touchTime > 0) { - int shift = iBarShift(_Symbol, InpTriggerTF, g_FVGPool[idx].touchTime); - rectEnd = iTime(_Symbol, InpTriggerTF, shift); + int shift = MyBarShift(_Symbol, InpTriggerTF, g_FVGPool[idx].touchTime); + rectEnd = (shift >= 0) ? iTime(_Symbol, InpTriggerTF, shift) : iTime(_Symbol, InpMiddleTF, 0); } else { - int shift = iBarShift(_Symbol, InpMiddleTF, g_FVGPool[idx].usedTime); - rectEnd = iTime(_Symbol, InpMiddleTF, shift); + int shift = MyBarShift(_Symbol, InpMiddleTF, g_FVGPool[idx].usedTime); + rectEnd = (shift >= 0) ? iTime(_Symbol, InpMiddleTF, shift) : iTime(_Symbol, InpMiddleTF, 0); } if (rectEnd <= g_FVGPool[idx].createdTime) rectEnd = iTime(_Symbol, InpMiddleTF, 0); - // ── Fill color ──────────────────────────────────────────────────── color fillColor; if (g_FVGPool[idx].status == FVG_PENDING) fillColor = (g_FVGPool[idx].direction == DIR_UP) ? C'0,50,110' : C'90,25,0'; else if (g_FVGPool[idx].status == FVG_TOUCHED) fillColor = (g_FVGPool[idx].direction == DIR_UP) ? C'0,120,220' : C'220,75,0'; @@ -992,7 +1696,6 @@ void DrawOneFVGRecord(int idx) else if (g_FVGPool[idx].usedCase == 1) fillColor = C'70,0,0'; else fillColor = C'50,50,50'; - // ── Rectangle ───────────────────────────────────────────────────── if (ObjectFind(0, rectN) < 0) ObjectCreate(0, rectN, OBJ_RECTANGLE, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].high, rectEnd, g_FVGPool[idx].low); @@ -1003,7 +1706,6 @@ void DrawOneFVGRecord(int idx) ObjectMove(0, rectN, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].high); ObjectMove(0, rectN, 1, rectEnd, g_FVGPool[idx].low); - // ── Midpoint dotted line ────────────────────────────────────────── color midColor = (g_FVGPool[idx].status == FVG_USED) ? C'60,60,60' : clrSilver; if (ObjectFind(0, midN) < 0) ObjectCreate(0, midN, OBJ_TREND, 0, @@ -1015,7 +1717,6 @@ void DrawOneFVGRecord(int idx) ObjectMove(0, midN, 0, g_FVGPool[idx].createdTime, g_FVGPool[idx].mid); ObjectMove(0, midN, 1, rectEnd, g_FVGPool[idx].mid); - // ── Label ───────────────────────────────────────────────────────── string sym = (g_FVGPool[idx].direction == DIR_UP) ? "▲" : "▼"; string stTxt = ""; if (g_FVGPool[idx].status == FVG_TOUCHED) stTxt = " [TOUCHED]"; @@ -1055,46 +1756,67 @@ void DrawContextDebug() ObjectSetInteger(0,name,OBJPROP_COLOR, clr); \ ObjectSetString (0,name,OBJPROP_TEXT, txt); - LBL("DBG_HDR", "── ICT EA ──", 10, clrSilver) + LBL("DBG_HDR", "── ICT EA v3 ──", 10, clrSilver) // Bias color cB = (g_Bias.bias==BIAS_UP)?clrLime:(g_Bias.bias==BIAS_DOWN)?clrTomato:(g_Bias.bias==BIAS_SIDEWAY)?clrOrange:clrGray; LBL("DBG_BIAS", StringFormat("Bias : %s", EnumToString(g_Bias.bias)), 34, cB) - // H1 trend + last MSS + // H1 trend color cMT = (g_MiddleTrend.trend==DIR_UP)?clrLime:(g_MiddleTrend.trend==DIR_DOWN)?clrTomato:clrGray; LBL("DBG_MT", StringFormat("H1 : %s KL=%.5f", EnumToString(g_MiddleTrend.trend), g_MiddleTrend.keyLevel), 58, cMT) + + // H1 MSS if (g_MiddleTrend.lastMssTime > 0) { color cMSS = (g_MiddleTrend.lastMssBreak==DIR_UP) ? clrLime : clrTomato; - LBL("DBG_MMSS", StringFormat("H1MSS: %s @ %s", (g_MiddleTrend.lastMssBreak==DIR_UP)?"▲":"▼", TimeToString(g_MiddleTrend.lastMssTime, TIME_MINUTES)), 82, cMSS) + LBL("DBG_MMSS", StringFormat("H1MSS: %s @ %s", (g_MiddleTrend.lastMssBreak==DIR_UP)?"▲":"▼", + TimeToString(g_MiddleTrend.lastMssTime, TIME_MINUTES)), 82, cMSS) } else ObjectDelete(0,"DBG_MMSS"); - // M5 trend + last MSS + // M5 trend color cTT = (g_TriggerTrend.trend==DIR_UP)?clrLime:(g_TriggerTrend.trend==DIR_DOWN)?clrTomato:clrGray; LBL("DBG_TT", StringFormat("M5 : %s KL=%.5f", EnumToString(g_TriggerTrend.trend), g_TriggerTrend.keyLevel), 106, cTT) + + // M5 MSS (v3: đã bao gồm sweep validation) if (g_TriggerTrend.lastMssTime > 0) { color cMSS2 = (g_TriggerTrend.lastMssBreak==DIR_UP) ? clrLime : clrTomato; - LBL("DBG_TMSS", StringFormat("M5MSS: %s @ %s", (g_TriggerTrend.lastMssBreak==DIR_UP)?"▲":"▼", TimeToString(g_TriggerTrend.lastMssTime, TIME_MINUTES)), 130, cMSS2) + string sweepInfo = (g_TriggerTrend.mssSweepTime > 0) + ? StringFormat(" sweep@%s", TimeToString(g_TriggerTrend.mssSweepTime, TIME_MINUTES)) + : ""; + LBL("DBG_TMSS", StringFormat("M5MSS: %s @ %s%s", + (g_TriggerTrend.lastMssBreak==DIR_UP)?"▲":"▼", + TimeToString(g_TriggerTrend.lastMssTime, TIME_MINUTES), + sweepInfo), 130, cMSS2) } else ObjectDelete(0,"DBG_TMSS"); + // Sweep status (internal, không vẽ riêng nhưng show trong panel) + if (g_LastLiqSweep.valid) + { + color cSw = (g_LastLiqSweep.direction == DIR_UP) ? C'0,210,230' : C'230,80,210'; + LBL("DBG_SWEEP", StringFormat("Sweep: %s @%.5f (pending)", + (g_LastLiqSweep.direction==DIR_UP)?"▲":"▼", + g_LastLiqSweep.sweptLevel), 154, cSw) + } + else ObjectDelete(0,"DBG_SWEEP"); + // Daily risk double lostPct = g_DailyRisk.startBalance > 0 ? (g_DailyRisk.startBalance - g_DailyRisk.currentBalance) / g_DailyRisk.startBalance * 100.0 : 0.0; color cR = g_DailyRisk.limitHit?clrRed:(lostPct>InpMaxDailyLossPct*0.7?clrOrange:clrLime); - LBL("DBG_RISK", StringFormat("Risk : %.2f%% / %.2f%%", lostPct, InpMaxDailyLossPct), 154, cR) - LBL("DBG_BAL", StringFormat("Bal : %.2f (start %.2f)", g_DailyRisk.currentBalance, g_DailyRisk.startBalance), 178, clrSilver) - LBL("DBG_LIM", g_DailyRisk.limitHit?"⛔ DAILY LOSS HIT":"✅ Loss OK", 202, g_DailyRisk.limitHit?clrRed:clrLime) + LBL("DBG_RISK", StringFormat("Risk : %.2f%% / %.2f%%", lostPct, InpMaxDailyLossPct), 178, cR) + LBL("DBG_BAL", StringFormat("Bal : %.2f (start %.2f)", g_DailyRisk.currentBalance, g_DailyRisk.startBalance), 202, clrSilver) + LBL("DBG_LIM", g_DailyRisk.limitHit?"⛔ DAILY LOSS HIT":"✅ Loss OK", 226, g_DailyRisk.limitHit?clrRed:clrLime) // State color cS = (g_State==EA_IDLE)?clrSilver:(g_State==EA_WAIT_TOUCH)?clrOrange:(g_State==EA_WAIT_TRIGGER)?clrYellow:clrLime; - LBL("DBG_ST", StringFormat("State: %s", EnumToString(g_State)), 226, cS) + LBL("DBG_ST", StringFormat("State: %s", EnumToString(g_State)), 250, cS) if (g_BlockReason != BLOCK_NONE) - { LBL("DBG_BLK", StringFormat("Block: %s", EnumToString(g_BlockReason)), 250, clrTomato) } + { LBL("DBG_BLK", StringFormat("Block: %s", EnumToString(g_BlockReason)), 274, clrTomato) } else ObjectDelete(0,"DBG_BLK"); // FVG pool @@ -1106,8 +1828,9 @@ void DrawContextDebug() else nU++; } color cP = (nT>0)?clrDeepSkyBlue:(nP>0)?clrDodgerBlue:clrGray; - LBL("DBG_POOL", StringFormat("Pool : P=%d T=%d U=%d (%d/%d)", nP,nT,nU,g_FVGCount,MAX_FVG_POOL), 274, cP) + LBL("DBG_POOL", StringFormat("Pool : P=%d T=%d U=%d (%d/%d)", nP,nT,nU,g_FVGCount,MAX_FVG_POOL), 298, cP) + // Active FVG if (g_ActiveFVGIdx >= 0 && g_ActiveFVGIdx < g_FVGCount) { int ai = g_ActiveFVGIdx; @@ -1115,21 +1838,45 @@ void DrawContextDebug() LBL("DBG_ACT", StringFormat("Act : #%d %s [%.5f–%.5f] %s", g_FVGPool[ai].id, EnumToString(g_FVGPool[ai].direction), g_FVGPool[ai].low, g_FVGPool[ai].high, - EnumToString(g_FVGPool[ai].status)), 298, cA) + EnumToString(g_FVGPool[ai].status)), 322, cA) } else ObjectDelete(0,"DBG_ACT"); + // Pending order + if (g_PendingTicket > 0 && g_OrderPlan.valid) + { + string ordDir = (g_OrderPlan.direction > 0) ? "BUY_LIM" : "SELL_LIM"; + LBL("DBG_ORD", StringFormat("Order: %s #%llu @ %.5f SL=%.5f TP=%.5f", + ordDir, g_PendingTicket, g_OrderPlan.entry, + g_OrderPlan.stopLoss, g_OrderPlan.takeProfit), 346, clrGold) + } + else ObjectDelete(0,"DBG_ORD"); + + // TriggerTF FVG + if (g_TrigFVG.valid) + { + string tDir = (g_TrigFVG.direction == DIR_UP) ? "▲" : "▼"; + LBL("DBG_TFVG", StringFormat("tFVG : %s [%.5f–%.5f] mid=%.5f", + tDir, g_TrigFVG.low, g_TrigFVG.high, g_TrigFVG.mid), 370, C'0,220,150') + } + else ObjectDelete(0,"DBG_TFVG"); + #undef LBL ChartRedraw(0); } +//---------------------------------------------------------------------- +// DrawVisuals – master draw function +//---------------------------------------------------------------------- void DrawVisuals() { - DrawMiddleSwingPoints(); // H1 H0/H1/L0/L1 + key level - DrawTriggerSwingPoints(); // M5 H0/H1/L0/L1 + key level - DrawMSSMarkers(); // MSS markers for both TFs - DrawFVGPool(); // FVG rectangles - DrawContextDebug(); // Info panel + DrawMiddleSwingPoints(); // H1 swing H0/H1/L0/L1 + DrawTriggerSwingPoints(); // M5 swing tH0/tH1/tL0/tL1 + DrawMSSMarkers(); // MSS + sweep (combined, v3) + DrawFVGPool(); // MiddleTF FVG rectangles + DrawTriggerFVG(); // TriggerTF FVG rectangle + DrawOrderVisualization(); // TradingView-style Entry/SL/TP + DrawContextDebug(); // Info panel } //+------------------------------------------------------------------+ @@ -1140,16 +1887,19 @@ int OnInit() { ZeroMemory(g_Bias); ZeroMemory(g_MiddleTrend); ZeroMemory(g_TriggerTrend); ZeroMemory(g_Trigger); ZeroMemory(g_DailyRisk); + ZeroMemory(g_TrigFVG); ZeroMemory(g_OrderPlan); + ZeroMemory(g_LastLiqSweep); for (int i = 0; i < MAX_FVG_POOL; i++) ZeroMemory(g_FVGPool[i]); g_FVGCount = 0; g_NextFVGId = 0; g_ActiveFVGIdx = -1; g_TradeBarIndex = -1; + g_PendingTicket = 0; g_State = EA_IDLE; g_BlockReason = BLOCK_NONE; UpdateAllContexts(); ScanAndRegisterFVGs(); DrawVisuals(); - PrintFormat("✅ ICT EA init | Bias=%s | H1=%s | M5=%s | FVGPool=%d", + PrintFormat("✅ ICT EA v3 init | Bias=%s | H1=%s | M5=%s | FVGPool=%d", EnumToString(g_Bias.bias), EnumToString(g_MiddleTrend.trend), EnumToString(g_TriggerTrend.trend), g_FVGCount); return INIT_SUCCEEDED; @@ -1167,10 +1917,10 @@ void OnTick() void OnDeinit(const int reason) { - // FVGP_* and MSS_* intentionally kept on chart for post-session review + // Giữ lại trên chart: FVGP_, TFVG_, MSS_, ORD_ (post-session review) ObjectsDeleteAll(0, SW_PREFIX); ObjectsDeleteAll(0, TS_PREFIX); ObjectsDeleteAll(0, DBG_PREFIX); ChartRedraw(0); - PrintFormat("ICT EA deinit | reason=%d | pool had %d FVGs", reason, g_FVGCount); + PrintFormat("ICT EA v3 deinit | reason=%d | pool had %d FVGs", reason, g_FVGCount); } \ No newline at end of file