fix: Sweep Reversal Logic, Removed inBreakout = false from OpenTrade(), Added InpMaxRetestsPerSession = 3 parameter

This commit is contained in:
Naji El Chemaly
2026-06-08 22:25:54 +03:00
parent 8951881098
commit 5c7409bbe0
+70 -45
View File
@@ -5,7 +5,7 @@
//| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector | //| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
#property copyright "NANDR" #property copyright "NANDR"
#property version "1.31" #property version "1.32"
#property strict #property strict
#include <Trade\Trade.mqh> #include <Trade\Trade.mqh>
@@ -89,6 +89,7 @@ input bool InpUseStrictFilter = false; // Use Strict Breakout
input bool InpWaitForRetest = true; // Wait for ORB Retest Before Entry input bool InpWaitForRetest = true; // Wait for ORB Retest Before Entry
input int InpBreakoutExpireBars = 20; // Bars Before Breakout Expires (0=never) input int InpBreakoutExpireBars = 20; // Bars Before Breakout Expires (0=never)
input bool InpUseOBRetestEntry = true; // OB Retest Entry (enter on OB boundary retest) 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 --- // --- Lot Size & Risk ---
input group "═══ 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; if(!g_Sessions[sessIdx].inBreakout) return false;
// Use bar[1] (just-closed bar) as the retest candidate and bar[2] as context. // 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 c0 = iClose(g_Symbol, PERIOD_CURRENT, 1); // just-closed bar
double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // bar before it double c1 = iClose(g_Symbol, PERIOD_CURRENT, 2); // bar before it
double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1); double h0 = iHigh(g_Symbol, PERIOD_CURRENT, 1);
double l0 = iLow(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) if(g_Sessions[sessIdx].breakoutDir == 1)
{ {
// Standard: previous bar above ORB high, current bar wick dips to ORB high, closes back above // Standard: prev bar above ORB high, current bar wick dips to ORB high, closes back above
bool retestHigh = (c1 > g_Sessions[sessIdx].orbHigh) bool retestHigh = (c1 > orbHigh) && (l0 <= orbHigh) && (c0 >= orbHigh);
&& (l0 <= g_Sessions[sessIdx].orbHigh) // Mid retest: price dipped to ORB mid, closes back above mid
&& (c0 >= g_Sessions[sessIdx].orbHigh); bool retestMid = (c1 > orbMid) && (l0 <= orbMid) && (c0 > orbMid);
// Mid retest: price bounced back down to ORB mid after breakout, closes back above mid
bool retestMid = (c1 > orbMid)
&& (l0 <= orbMid)
&& (c0 > orbMid);
if(retestHigh || retestMid) { dir = 1; return true; } 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) else if(g_Sessions[sessIdx].breakoutDir == -1)
{ {
// Standard: previous bar below ORB low, current bar wick ticks back to ORB low, closes back below // Standard: prev bar below ORB low, current bar wick ticks back to ORB low, closes back below
bool retestLow = (c1 < g_Sessions[sessIdx].orbLow) bool retestLow = (c1 < orbLow) && (h0 >= orbLow) && (c0 <= orbLow);
&& (h0 >= g_Sessions[sessIdx].orbLow) // Mid retest: price bounced back up to ORB mid, closes back below mid
&& (c0 <= g_Sessions[sessIdx].orbLow); bool retestMid = (c1 < orbMid) && (h0 >= orbMid) && (c0 < orbMid);
// 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);
if(retestLow || retestMid) { dir = -1; return true; } if(retestLow || retestMid) { dir = -1; return true; }
}
// Failed retest — threshold raised to orbMid so a simple bounce above orbLow/below orbHigh // Sweep reversal: wick swept ABOVE ORB low (back into range), closed between orbLow and orbMid.
// does NOT cancel the breakout prematurely; only a close through the mid invalidates it. // Buy-side liquidity sweep below orbLow failed → flip to bullish.
if(g_Sessions[sessIdx].breakoutDir == 1 // This is the "liquidity sweep + reversal" pattern (e.g. 18:15 retest after 17:45 breakout).
&& c0 < orbMid && c1 > orbMid) // After this flip, subsequent mid retests are detected as BUY signals (Entry 2 scenario).
{ bool sweepRev = (c1 < orbLow) && (h0 > orbLow) && (c0 > orbLow) && (c0 < orbHigh);
PrintFormat("NANDR EA: [%s] Failed bullish retest (closed below mid %.2f)", if(sweepRev)
g_Sessions[sessIdx].name, orbMid); {
g_Sessions[sessIdx].inBreakout = false; g_Sessions[sessIdx].breakoutDir = 1;
g_Sessions[sessIdx].breakoutDir = 0; PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH",
} g_Sessions[sessIdx].name, orbLow);
else if(g_Sessions[sessIdx].breakoutDir == -1 dir = 1;
&& c0 > orbMid && c1 < orbMid) return true;
{ }
PrintFormat("NANDR EA: [%s] Failed bearish retest (closed above mid %.2f)",
g_Sessions[sessIdx].name, orbMid); // Full invalidation: closed above mid
g_Sessions[sessIdx].inBreakout = false; if(c0 > orbMid && c1 < orbMid)
g_Sessions[sessIdx].breakoutDir = 0; {
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; return false;
@@ -906,7 +927,7 @@ void OpenTrade(int dir, int sessIdx, double orbLevel, double obTop, double obBot
{ {
if(g_TradingHalted) return; if(g_TradingHalted) return;
if(g_TodayTrades >= InpMaxTradesPerDay) 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; if(CountOpenPositions() + CountPendingOrders() >= InpMaxPosPerSession) return;
double ask = SymbolInfoDouble(g_Symbol, SYMBOL_ASK); 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_TodayTrades++;
g_Sessions[sessIdx].tradesThisSession++; g_Sessions[sessIdx].tradesThisSession++;
g_Sessions[sessIdx].inBreakout = false; // Do NOT clear inBreakout — the ORB level remains valid for subsequent retests
g_Sessions[sessIdx].inRetest = false; // 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", 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); (dir > 0 ? "BUY" : "SELL"), lots, entry, sl, tp, g_Sessions[sessIdx].name);
if(InpShowTradeLabels) if(InpShowTradeLabels)
@@ -1057,7 +1080,9 @@ void CheckEntrySignals()
for(int s = 0; s < g_SessionCount; s++) for(int s = 0; s < g_SessionCount; s++)
{ {
if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue; 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; if(!g_Sessions[s].inBreakout) continue;
// --- Breakout expiry --- // --- Breakout expiry ---