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 |
//+------------------------------------------------------------------+
#property copyright "NANDR"
#property version "1.31"
#property version "1.32"
#property strict
#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 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 ---