From 38a6ceec69a02046d5b3d32da8a754fafcbaec90 Mon Sep 17 00:00:00 2001 From: Naji El Chemaly Date: Tue, 9 Jun 2026 00:09:56 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20UpdateRetestState()=20=E2=80=94=20bar-cl?= =?UTF-8?q?ose=20only,=20state=20management=20(sweep=20flip=20+=20invalida?= =?UTF-8?q?tion),=20no=20trade=20firing,=20CheckOBRetestEntries()=20Merged?= =?UTF-8?q?=20into=20CheckRetestEntriesTick(),=20CheckRetestEntriesTick()?= =?UTF-8?q?=20called=20on=20every=20tick,=20before=20if(!g=5FIsNewBar)=20r?= =?UTF-8?q?eturn,=20The=20old=20single=20condition=20is=20replaced=20by=20?= =?UTF-8?q?a=203-pattern=20check=20covering=20all=20valid=20entry=20config?= =?UTF-8?q?urations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NANDR_ORB_OB_EA.mq5 | 238 ++++++++++++++++++++++++++++---------------- 1 file changed, 152 insertions(+), 86 deletions(-) diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index 77e28a8..b37049f 100644 --- a/NANDR_ORB_OB_EA.mq5 +++ b/NANDR_ORB_OB_EA.mq5 @@ -5,7 +5,7 @@ //| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector | //+------------------------------------------------------------------+ #property copyright "NANDR" -#property version "1.32" +#property version "1.33" #property strict #include @@ -608,15 +608,16 @@ void DetectBreakouts() } //+------------------------------------------------------------------+ -//| Detect retest for sessions that had a breakout | +//| Update retest state on bar close: sweep reversals + invalidation | +//| Called once per new bar. Does NOT fire any trades. | //+------------------------------------------------------------------+ -bool DetectRetest(int sessIdx, int &dir) +void UpdateRetestState(int sessIdx) { - if(!g_Sessions[sessIdx].inBreakout) return false; + if(!g_Sessions[sessIdx].inBreakout) return; - // Use bar[1] (just-closed bar) as the retest candidate and bar[2] as context. - double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar - double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // bar before it + // Use bar[1] (just-closed) and bar[2] for bar-close confirmation. + double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); + double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1); double l0 = iLow(g_Symbol, PERIOD_CURRENT, 1); @@ -624,31 +625,18 @@ bool DetectRetest(int sessIdx, int &dir) double orbLow = g_Sessions[sessIdx].orbLow; double orbMid = (orbHigh + orbLow) / 2.0; - // ---------------------------------------------------------------- - // BULLISH BREAKOUT (breakoutDir == 1) - // ---------------------------------------------------------------- if(g_Sessions[sessIdx].breakoutDir == 1) { - // Standard: prev bar above ORB high, current bar wick dips to ORB high, closes back above - bool retestHigh = (c1 > orbHigh) && (l0 <= orbHigh) && (c0 >= orbHigh); - // Mid retest: price dipped to ORB mid, closes back above mid - bool retestMid = (c1 > orbMid) && (l0 <= orbMid) && (c0 > orbMid); - if(retestHigh || retestMid) { dir = 1; return true; } - - // Sweep reversal: wick swept BELOW ORB high (back into range), closed between orbMid and orbHigh. - // Sell-side liquidity grab above orbHigh failed → flip to bearish. + // Sweep reversal: bullish breakout failed — bar closed below orbHigh → flip to bearish bool sweepRev = (c1 > orbHigh) && (l0 < orbHigh) && (c0 < orbHigh) && (c0 >= orbMid); if(sweepRev) { g_Sessions[sessIdx].breakoutDir = -1; PrintFormat("NANDR EA: [%s] Bullish sweep reversal at orbHigh %.2f — flipping to BEARISH", g_Sessions[sessIdx].name, orbHigh); - dir = -1; - return true; } - // Full invalidation: closed below mid - if(c0 < orbMid && c1 > orbMid) + else if(c0 < orbMid && c1 > orbMid) { PrintFormat("NANDR EA: [%s] Failed bullish retest (closed below mid %.2f)", g_Sessions[sessIdx].name, orbMid); @@ -656,33 +644,18 @@ bool DetectRetest(int sessIdx, int &dir) g_Sessions[sessIdx].breakoutDir = 0; } } - // ---------------------------------------------------------------- - // BEARISH BREAKOUT (breakoutDir == -1) - // ---------------------------------------------------------------- else if(g_Sessions[sessIdx].breakoutDir == -1) { - // Standard: prev bar below ORB low, current bar wick ticks back to ORB low, closes back below - bool retestLow = (c1 < orbLow) && (h0 >= orbLow) && (c0 <= orbLow); - // Mid retest: price bounced back up to ORB mid, closes back below mid - bool retestMid = (c1 < orbMid) && (h0 >= orbMid) && (c0 < orbMid); - if(retestLow || retestMid) { dir = -1; return true; } - - // Sweep reversal: wick swept ABOVE ORB low (back into range), closed between orbLow and orbMid. - // Buy-side liquidity sweep below orbLow failed → flip to bullish. - // This is the "liquidity sweep + reversal" pattern (e.g. 18:15 retest after 17:45 breakout). - // After this flip, subsequent mid retests are detected as BUY signals (Entry 2 scenario). + // Sweep reversal: bearish breakout failed — bar closed above orbLow → flip to bullish bool sweepRev = (c1 < orbLow) && (h0 > orbLow) && (c0 > orbLow) && (c0 < orbHigh); if(sweepRev) { g_Sessions[sessIdx].breakoutDir = 1; PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH", g_Sessions[sessIdx].name, orbLow); - dir = 1; - return true; } - // Full invalidation: closed above mid - if(c0 > orbMid && c1 < orbMid) + else if(c0 > orbMid && c1 < orbMid) { PrintFormat("NANDR EA: [%s] Failed bearish retest (closed above mid %.2f)", g_Sessions[sessIdx].name, orbMid); @@ -690,8 +663,6 @@ bool DetectRetest(int sessIdx, int &dir) g_Sessions[sessIdx].breakoutDir = 0; } } - - return false; } //+------------------------------------------------------------------+ @@ -980,39 +951,138 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot } //+------------------------------------------------------------------+ -//| Standalone OB retest entries (Entry 3 type: 18:00 Bear OB low) | -//| Fires when price retests the boundary of an active OB zone and | -//| closes back on the rejection side — independent of ORB sessions.| +//| Tick-based retest entries — fires at the wick, not on bar close | +//| Covers both ORB session retests and OB boundary retests. | +//| Uses live bar[0] high/low + last closed bar[1] for confirmation. | +//| Direction: determined by breakoutDir for ORB, OB type for OBs. | +//| Sweep reversals: detected via ask/bid relative to the level. | //+------------------------------------------------------------------+ -void CheckOBRetestEntries() +void CheckRetestEntriesTick() { - if(!InpUseOBRetestEntry) return; if(g_TradingHalted) return; if(g_TodayTrades >= InpMaxTradesPerDay) return; - if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return; - double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar - double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // previous closed bar - double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1); - double l0 = iLow(g_Symbol, PERIOD_CURRENT, 1); + double h_cur = iHigh(g_Symbol, PERIOD_CURRENT, 0); // current bar live high so far + double l_cur = iLow(g_Symbol, PERIOD_CURRENT, 0); // current bar live low so far + double c1 = iClose(g_Symbol, PERIOD_CURRENT, 1); // last closed bar close + double open0 = iOpen(g_Symbol, PERIOD_CURRENT, 0); // current bar open + double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(g_Symbol, SYMBOL_BID); + double tol = PipsToPrice(1.0); // 1-pip tolerance for direction detection - // --- Bearish OB retest → SELL --- - // Setup: price was below OB bottom (already rejected), wick retraces back up - // to OB bottom, candle closes back below it → confirmed rejection sell + // ── ORB Session Retest Entries ────────────────────────────────── + // Fires at the moment the wick touches the ORB level, not one bar later. + // Direction uses ask/bid to distinguish continuation vs sweep reversal: + // bid <= level + tol → price is AT or below the level → continuation in breakout dir + // ask >= level - tol → price has passed back through the level → sweep reversal, flip dir + if(InpWaitForRetest) + { + for(int s = 0; s < g_SessionCount; s++) + { + if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue; + if(!g_Sessions[s].inBreakout) continue; + if(g_Sessions[s].tradesThisSession >= InpMaxRetestsPerSession) continue; + if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) continue; + if(InpBreakoutExpireBars > 0 && g_Sessions[s].breakoutBarsAgo >= InpBreakoutExpireBars) continue; + + double orbHigh = g_Sessions[s].orbHigh; + double orbLow = g_Sessions[s].orbLow; + double orbMid = (orbHigh + orbLow) / 2.0; + int dir = 0; + double orbLevel = 0; + + if(g_Sessions[s].breakoutDir == 1) // Bullish + { + // Standard: prev close above orbHigh, wick dipped to orbHigh + if(c1 > orbHigh && l_cur <= orbHigh) + { + orbLevel = orbHigh; + // ask still above orbHigh → support held → BUY + // ask dropped below orbHigh → sweep failed → SELL, flip to bearish + if(ask >= orbHigh - tol) + dir = 1; + else + { + dir = -1; + g_Sessions[s].breakoutDir = -1; + PrintFormat("NANDR EA: [%s] Tick sweep reversal below orbHigh %.2f → SELL", + g_Sessions[s].name, orbHigh); + } + } + // Mid retest: prev close above mid, wick dipped to mid + else if(c1 > orbMid && l_cur <= orbMid) + { + orbLevel = orbMid; + if(ask >= orbMid - tol) + dir = 1; + else + { + dir = -1; + g_Sessions[s].breakoutDir = -1; + } + } + } + else if(g_Sessions[s].breakoutDir == -1) // Bearish + { + // Standard: prev close below orbLow, wick came back up to orbLow + if(c1 < orbLow && h_cur >= orbLow) + { + orbLevel = orbLow; + // bid still below orbLow → resistance held → SELL (continuation) + // bid moved above orbLow → sweep reversal → BUY, flip to bullish + if(bid <= orbLow + tol) + dir = -1; + else + { + dir = 1; + g_Sessions[s].breakoutDir = 1; + PrintFormat("NANDR EA: [%s] Tick sweep reversal above orbLow %.2f → BUY", + g_Sessions[s].name, orbLow); + } + } + // Mid retest: prev close below mid, wick came back up to mid + else if(c1 < orbMid && h_cur >= orbMid) + { + orbLevel = orbMid; + if(bid <= orbMid + tol) + dir = -1; + else + { + dir = 1; + g_Sessions[s].breakoutDir = 1; + } + } + } + + if(dir == 0) continue; + + double obTop = 0, obBottom = 0; + IsOBNearLevel(orbLevel, dir, obTop, obBottom); + if(InpOBRequireConf && obTop == 0) continue; + OpenTrade(dir, s, orbLevel, obTop, obBottom); + } + } + + if(!InpUseOBRetestEntry) return; + + // ── Bearish OB Retest → SELL at wick touch ─────────────────────── + // Fires the moment the current bar's live high reaches the OB bottom. + // prev bar[1] (or current open) must have been at/below OB bottom. for(int i = 0; i < g_BearOBCount; i++) { if(!g_BearOBs[i].active || g_BearOBs[i].traded) continue; + if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) break; + if(g_TodayTrades >= InpMaxTradesPerDay) break; double obBottom = g_BearOBs[i].bottom; - double obTop = g_BearOBs[i].top; + double obTop2 = g_BearOBs[i].top; - bool retest = (c1 < obBottom) // prev bar was below OB bottom (breakdown confirmed) - && (h0 >= obBottom) // this bar's wick touched the OB bottom - && (c0 < obBottom); // closed back below OB bottom (rejection confirmed) - if(!retest) continue; + bool prevBelow = (c1 <= obBottom || open0 <= obBottom); + bool wickTouched = (h_cur >= obBottom); + if(!prevBelow || !wickTouched) continue; - double entry = SymbolInfoDouble(g_Symbol, SYMBOL_BID); - double sl = NormalizeDouble(obTop + PipsToPrice(2.0), + double entry = bid; + double sl = NormalizeDouble(obTop2 + PipsToPrice(2.0), (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); double slPips = PriceToPips(MathAbs(entry - sl)); double tp = CalcTP(-1, entry, sl); @@ -1025,45 +1095,43 @@ void CheckOBRetestEntries() g_TodayTrades++; g_BearOBs[i].traded = true; PrintFormat("NANDR EA: OB Retest SELL. Entry=%.2f SL=%.2f TP=%.2f OB=[%.2f-%.2f]", - entry, sl, tp, obBottom, obTop); + entry, sl, tp, obBottom, obTop2); if(InpShowTradeLabels) DrawTradeLabel(-1, entry, sl, tp); } else PrintFormat("NANDR EA: OB Retest SELL failed. Error=%d", GetLastError()); - break; // One OB entry per bar + break; } - if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return; - - // --- Bullish OB retest → BUY --- - // Setup: price was above OB top, wick dips into OB top zone, closes back above + // ── Bullish OB Retest → BUY at wick touch ──────────────────────── for(int i = 0; i < g_BullOBCount; i++) { if(!g_BullOBs[i].active || g_BullOBs[i].traded) continue; + if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) break; + if(g_TodayTrades >= InpMaxTradesPerDay) break; - double obTop = g_BullOBs[i].top; - double obBottom = g_BullOBs[i].bottom; + double obTop3 = g_BullOBs[i].top; + double obBottom2 = g_BullOBs[i].bottom; - bool retest = (c1 > obTop) // prev bar was above OB top (breakout confirmed) - && (l0 <= obTop) // this bar's wick touched the OB top - && (c0 > obTop); // closed back above OB top (rejection confirmed) - if(!retest) continue; + bool prevAbove = (c1 >= obTop3 || open0 >= obTop3); + bool wickTouched = (l_cur <= obTop3); + if(!prevAbove || !wickTouched) continue; - double entry = SymbolInfoDouble(g_Symbol, SYMBOL_ASK); - double sl = NormalizeDouble(obBottom - PipsToPrice(2.0), + double entry = ask; + double sl = NormalizeDouble(obBottom2 - PipsToPrice(2.0), (int)SymbolInfoInteger(g_Symbol, SYMBOL_DIGITS)); double slPips = PriceToPips(MathAbs(entry - sl)); double tp = CalcTP(1, entry, sl); double lots = CalcLotSize(slPips); bool result = g_Trade.Buy(lots, g_Symbol, 0, sl, tp, - StringFormat("NANDR|OBRetest|BUY|%.2f", obTop)); + StringFormat("NANDR|OBRetest|BUY|%.2f", obTop3)); if(result) { g_TodayTrades++; g_BullOBs[i].traded = true; PrintFormat("NANDR EA: OB Retest BUY. Entry=%.2f SL=%.2f TP=%.2f OB=[%.2f-%.2f]", - entry, sl, tp, obBottom, obTop); + entry, sl, tp, obBottom2, obTop3); if(InpShowTradeLabels) DrawTradeLabel(1, entry, sl, tp); } else @@ -1128,13 +1196,9 @@ void CheckEntrySignals() } else { - // Retest-based entry: wait for price to pull back to ORB level - int retestDir = 0; - if(!DetectRetest(s, retestDir)) continue; - double obTop = 0, obBottom = 0; - IsOBNearLevel(orbLevel, retestDir, obTop, obBottom); - if(InpOBRequireConf && obTop == 0) continue; - OpenTrade(retestDir, s, orbLevel, obTop, obBottom); + // Retest entries fire tick-based in CheckRetestEntriesTick(). + // Here we only update state: sweep reversals and failed retest cancellation. + UpdateRetestState(s); } } } @@ -1668,6 +1732,9 @@ void OnTick() ManageOpenTrades(); CheckRiskLimits(); + // Retest entries fire on tick (at wick touch), not on bar close + CheckRetestEntriesTick(); + if(!g_IsNewBar) return; // Daily reset @@ -1689,9 +1756,8 @@ void OnTick() // Breakout detection DetectBreakouts(); - // Entry signals + // Entry signals (direct breakout / limit orders on new bar) CheckEntrySignals(); - CheckOBRetestEntries(); // Update closed trade stats & dashboard UpdateClosedTrades();