diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index 3b4d67b..77e28a8 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.31" +#property version "1.32" #property strict #include @@ -89,6 +89,7 @@ input bool InpUseStrictFilter = false; // Use Strict Breakout input bool InpWaitForRetest = true; // Wait for ORB Retest Before Entry input int InpBreakoutExpireBars = 20; // Bars Before Breakout Expires (0=never) input bool InpUseOBRetestEntry = true; // OB Retest Entry (enter on OB boundary retest) +input int InpMaxRetestsPerSession = 3; // Max Retest Entries Per Session (per breakout) // --- Lot Size & Risk --- input group "═══ Lot Size & Risk ═══" @@ -614,60 +615,80 @@ bool DetectRetest(int sessIdx, int &dir) if(!g_Sessions[sessIdx].inBreakout) return false; // Use bar[1] (just-closed bar) as the retest candidate and bar[2] as context. - // This mirrors Pine Script: decisions only on completed candles, never on bar[0] - // which is only the opening price at new-bar time. double c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // bar before it double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1); double l0 = iLow(g_Symbol, PERIOD_CURRENT, 1); - double orbMid = (g_Sessions[sessIdx].orbHigh + g_Sessions[sessIdx].orbLow) / 2.0; + double orbHigh = g_Sessions[sessIdx].orbHigh; + double orbLow = g_Sessions[sessIdx].orbLow; + double orbMid = (orbHigh + orbLow) / 2.0; - // Bullish retest: standard ORB high retest OR mid-level retest + // ---------------------------------------------------------------- + // BULLISH BREAKOUT (breakoutDir == 1) + // ---------------------------------------------------------------- if(g_Sessions[sessIdx].breakoutDir == 1) { - // Standard: previous bar above ORB high, current bar wick dips to ORB high, closes back above - bool retestHigh = (c1 > g_Sessions[sessIdx].orbHigh) - && (l0 <= g_Sessions[sessIdx].orbHigh) - && (c0 >= g_Sessions[sessIdx].orbHigh); - // Mid retest: price bounced back down to ORB mid after breakout, closes back above mid - bool retestMid = (c1 > orbMid) - && (l0 <= orbMid) - && (c0 > orbMid); + // 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. + 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) + { + PrintFormat("NANDR EA: [%s] Failed bullish retest (closed below mid %.2f)", + g_Sessions[sessIdx].name, orbMid); + g_Sessions[sessIdx].inBreakout = false; + g_Sessions[sessIdx].breakoutDir = 0; + } } - // Bearish retest: standard ORB low retest OR mid-level retest + // ---------------------------------------------------------------- + // BEARISH BREAKOUT (breakoutDir == -1) + // ---------------------------------------------------------------- else if(g_Sessions[sessIdx].breakoutDir == -1) { - // Standard: previous bar below ORB low, current bar wick ticks back to ORB low, closes back below - bool retestLow = (c1 < g_Sessions[sessIdx].orbLow) - && (h0 >= g_Sessions[sessIdx].orbLow) - && (c0 <= g_Sessions[sessIdx].orbLow); - // Mid retest: price bounced above ORB low up to the mid, closes back below mid - // This captures entries like a 14:45 retest of the opening-day ORB mid after the break - bool retestMid = (c1 < orbMid) - && (h0 >= orbMid) - && (c0 < orbMid); + // 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; } - } - // Failed retest — threshold raised to orbMid so a simple bounce above orbLow/below orbHigh - // does NOT cancel the breakout prematurely; only a close through the mid invalidates it. - if(g_Sessions[sessIdx].breakoutDir == 1 - && c0 < orbMid && c1 > orbMid) - { - PrintFormat("NANDR EA: [%s] Failed bullish retest (closed below mid %.2f)", - g_Sessions[sessIdx].name, orbMid); - g_Sessions[sessIdx].inBreakout = false; - g_Sessions[sessIdx].breakoutDir = 0; - } - else if(g_Sessions[sessIdx].breakoutDir == -1 - && c0 > orbMid && c1 < orbMid) - { - PrintFormat("NANDR EA: [%s] Failed bearish retest (closed above mid %.2f)", - g_Sessions[sessIdx].name, orbMid); - g_Sessions[sessIdx].inBreakout = false; - g_Sessions[sessIdx].breakoutDir = 0; + // 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). + 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) + { + PrintFormat("NANDR EA: [%s] Failed bearish retest (closed above mid %.2f)", + g_Sessions[sessIdx].name, orbMid); + g_Sessions[sessIdx].inBreakout = false; + g_Sessions[sessIdx].breakoutDir = 0; + } } return false; @@ -906,7 +927,7 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot { if(g_TradingHalted) return; if(g_TodayTrades >= InpMaxTradesPerDay) return; - if(g_Sessions[sessIdx].tradesThisSession >= InpMaxPosPerSession) return; + if(g_Sessions[sessIdx].tradesThisSession >= InpMaxRetestsPerSession) return; if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return; double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK); @@ -942,8 +963,10 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot { g_TodayTrades++; g_Sessions[sessIdx].tradesThisSession++; - g_Sessions[sessIdx].inBreakout = false; - g_Sessions[sessIdx].inRetest = false; + // Do NOT clear inBreakout — the ORB level remains valid for subsequent retests + // until the breakout expires, fails, or the daily reset. This allows the EA to + // take multiple retest entries on the same level (e.g. 18:45 AND 20:45 retests). + // Re-entry is naturally gated by CountOpenPositions() checked at the top of OpenTrade. PrintFormat("NANDR EA: Trade opened. Dir=%s Lots=%.2f Entry=%.2f SL=%.2f TP=%.2f Session=%s", (dir > 0 ? "BUY" : "SELL"), lots, entry, sl, tp, g_Sessions[sessIdx].name); if(InpShowTradeLabels) @@ -1057,7 +1080,9 @@ void CheckEntrySignals() for(int s = 0; s < g_SessionCount; s++) { if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue; - if(g_Sessions[s].tradesThisSession >= InpMaxPosPerSession) continue; + // Guard: allow up to InpMaxRetestsPerSession retest entries on the same breakout level. + // Previously used InpMaxPosPerSession (=1) which permanently blocked the second retest. + if(g_Sessions[s].tradesThisSession >= InpMaxRetestsPerSession) continue; if(!g_Sessions[s].inBreakout) continue; // --- Breakout expiry ---