fix: ORB range = opening price only, Strict filter structurally impossible for ORB breakouts

This commit is contained in:
Naji El Chemaly
2026-06-05 14:26:04 +03:00
parent 9abe4d4ef6
commit 0003108ba4
+76 -30
View File
@@ -84,8 +84,10 @@ input double InpOBProximityPips = 50.0; // OB Proximity (pips)
// --- Entry Settings --- // --- Entry Settings ---
input group "═══ Entry Settings ═══" input group "═══ Entry Settings ═══"
input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MARKET; // Entry Mode input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MARKET; // Entry Mode
input bool InpUseStrictFilter = true; // Use Strict Breakout Filter input bool InpUseStrictFilter = false; // Use Strict Breakout Filter (false=simple close-cross)
input bool InpWaitForRetest = false; // Wait for ORB Retest Before Entry
input int InpBreakoutExpireBars = 8; // Bars Before Breakout Expires (0=never)
// --- Lot Size & Risk --- // --- Lot Size & Risk ---
input group "═══ Lot Size & Risk ═══" input group "═══ Lot Size & Risk ═══"
@@ -438,28 +440,26 @@ bool IsWithinOrbWindow(const SSession &sess, datetime barTime)
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
//| Update ORB for all sessions on new bar | //| Update ORB for all sessions on new bar |
//| IMPORTANT: always reads bar[1] (the just-CLOSED candle) so the |
//| true high/low is captured. bar[0] at new-bar time = open only. |
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+
void UpdateORBSessions() void UpdateORBSessions()
{ {
datetime barTime = iTime(g_Symbol, PERIOD_CURRENT, 0); datetime bar1Time = iTime(g_Symbol, PERIOD_CURRENT, 1);
double barHigh = iHigh(g_Symbol, PERIOD_CURRENT, 0); double bar1High = iHigh(g_Symbol, PERIOD_CURRENT, 1);
double barLow = iLow(g_Symbol, PERIOD_CURRENT, 0); double bar1Low = iLow(g_Symbol, PERIOD_CURRENT, 1);
MqlDateTime dt;
TimeToStruct(barTime, dt);
for(int s = 0; s < g_SessionCount; s++) for(int s = 0; s < g_SessionCount; s++)
{ {
if(!g_Sessions[s].enabled) continue; if(!g_Sessions[s].enabled) continue;
// Session open bar starts accumulation // Session open bar just closed: bar[1] time matches session start
if(IsSessionOpenBar(g_Sessions[s], barTime)) if(IsSessionOpenBar(g_Sessions[s], bar1Time))
{ {
// Reset session state for new day's ORB g_Sessions[s].orbHigh = bar1High;
g_Sessions[s].orbHigh = barHigh; g_Sessions[s].orbLow = bar1Low;
g_Sessions[s].orbLow = barLow;
g_Sessions[s].orbBarCount = 1; g_Sessions[s].orbBarCount = 1;
g_Sessions[s].orbStartTime = barTime; g_Sessions[s].orbStartTime = bar1Time;
g_Sessions[s].orbComplete = (g_OrbBarsNeeded == 1); g_Sessions[s].orbComplete = (g_OrbBarsNeeded == 1);
g_Sessions[s].breakoutDir = 0; g_Sessions[s].breakoutDir = 0;
g_Sessions[s].inBreakout = false; g_Sessions[s].inBreakout = false;
@@ -467,17 +467,20 @@ void UpdateORBSessions()
g_Sessions[s].breakoutBarsAgo = 0; g_Sessions[s].breakoutBarsAgo = 0;
g_Sessions[s].tradesThisSession= 0; g_Sessions[s].tradesThisSession= 0;
if(g_Sessions[s].orbComplete)
PrintFormat("NANDR EA: [%s] ORB complete. High=%.2f Low=%.2f",
g_Sessions[s].name, bar1High, bar1Low);
if(InpShowORBLines) if(InpShowORBLines)
DrawORBLines(s); DrawORBLines(s);
continue; continue;
} }
// Still accumulating (M5 or M1) // Still accumulating (M5 or M1): append bar[1] closed data to range
if(!g_Sessions[s].orbComplete && g_Sessions[s].orbBarCount > 0 if(!g_Sessions[s].orbComplete && g_Sessions[s].orbBarCount > 0
&& IsWithinOrbWindow(g_Sessions[s], barTime)) && IsWithinOrbWindow(g_Sessions[s], bar1Time))
{ {
g_Sessions[s].orbHigh = MathMax(g_Sessions[s].orbHigh, barHigh); g_Sessions[s].orbHigh = MathMax(g_Sessions[s].orbHigh, bar1High);
g_Sessions[s].orbLow = MathMin(g_Sessions[s].orbLow, barLow); g_Sessions[s].orbLow = MathMin(g_Sessions[s].orbLow, bar1Low);
g_Sessions[s].orbBarCount++; g_Sessions[s].orbBarCount++;
if(g_Sessions[s].orbBarCount >= g_OrbBarsNeeded) if(g_Sessions[s].orbBarCount >= g_OrbBarsNeeded)
{ {
@@ -489,7 +492,11 @@ void UpdateORBSessions()
DrawORBLines(s); DrawORBLines(s);
} }
// Track breakout bar age // Track breakout bar age (only while breakout is active)
// NOTE: incremented here before DetectBreakouts, so newly-detected
// breakouts start at 0 here but will be seen as 0 in CheckEntrySignals
// because DetectBreakouts resets it to 0 AFTER this runs.
// The entry guard uses <=1 to handle both cases safely.
if(g_Sessions[s].inBreakout) if(g_Sessions[s].inBreakout)
g_Sessions[s].breakoutBarsAgo++; g_Sessions[s].breakoutBarsAgo++;
} }
@@ -924,20 +931,59 @@ void CheckEntrySignals()
{ {
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; if(g_Sessions[s].tradesThisSession >= InpMaxPosPerSession) continue;
if(!g_Sessions[s].inBreakout) continue;
int dir = 0; // --- Breakout expiry ---
if(!DetectRetest(s, dir)) continue; if(InpBreakoutExpireBars > 0
&& g_Sessions[s].breakoutBarsAgo >= InpBreakoutExpireBars)
double obTop = 0, obBottom = 0; {
bool obFound = IsOBNearLevel( PrintFormat("NANDR EA: [%s] Breakout expired after %d bars. Resetting.",
(dir > 0) ? g_Sessions[s].orbHigh : g_Sessions[s].orbLow, g_Sessions[s].name, g_Sessions[s].breakoutBarsAgo);
dir, obTop, obBottom); g_Sessions[s].inBreakout = false;
g_Sessions[s].breakoutDir = 0;
// If OB required and not found, skip g_Sessions[s].inRetest = false;
if(InpOBRequireConf && !obFound) continue; continue;
}
int dir = g_Sessions[s].breakoutDir;
double orbLevel = (dir > 0) ? g_Sessions[s].orbHigh : g_Sessions[s].orbLow; double orbLevel = (dir > 0) ? g_Sessions[s].orbHigh : g_Sessions[s].orbLow;
OpenTrade(dir, s, orbLevel, obTop, obBottom);
// --- LIMIT mode: place order immediately on breakout bar ---
if(InpEntryMode == ENTRY_LIMIT)
{
// Place once — on the bar breakout is first detected (age 0 or 1 due to update order)
if(g_Sessions[s].breakoutBarsAgo <= 1)
{
double obTop = 0, obBottom = 0;
IsOBNearLevel(orbLevel, dir, obTop, obBottom);
if(InpOBRequireConf && obTop == 0) continue;
OpenTrade(dir, s, orbLevel, obTop, obBottom);
}
continue;
}
// --- MARKET mode ---
if(!InpWaitForRetest)
{
// Direct entry on the bar the breakout is confirmed (age 0 or 1)
if(g_Sessions[s].breakoutBarsAgo <= 1)
{
double obTop = 0, obBottom = 0;
IsOBNearLevel(orbLevel, dir, obTop, obBottom);
if(InpOBRequireConf && obTop == 0) continue;
OpenTrade(dir, s, orbLevel, obTop, obBottom);
}
}
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);
}
} }
} }