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 ---
input group "═══ Entry Settings ═══"
input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MARKET; // Entry Mode
input bool InpUseStrictFilter = true; // Use Strict Breakout Filter
input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MARKET; // Entry Mode
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 ---
input group "═══ Lot Size & Risk ═══"
@@ -438,28 +440,26 @@ bool IsWithinOrbWindow(const SSession &sess, datetime barTime)
//+------------------------------------------------------------------+
//| 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()
{
datetime barTime = iTime(g_Symbol, PERIOD_CURRENT, 0);
double barHigh = iHigh(g_Symbol, PERIOD_CURRENT, 0);
double barLow = iLow(g_Symbol, PERIOD_CURRENT, 0);
MqlDateTime dt;
TimeToStruct(barTime, dt);
datetime bar1Time = iTime(g_Symbol, PERIOD_CURRENT, 1);
double bar1High = iHigh(g_Symbol, PERIOD_CURRENT, 1);
double bar1Low = iLow(g_Symbol, PERIOD_CURRENT, 1);
for(int s = 0; s < g_SessionCount; s++)
{
if(!g_Sessions[s].enabled) continue;
// Session open bar starts accumulation
if(IsSessionOpenBar(g_Sessions[s], barTime))
// Session open bar just closed: bar[1] time matches session start
if(IsSessionOpenBar(g_Sessions[s], bar1Time))
{
// Reset session state for new day's ORB
g_Sessions[s].orbHigh = barHigh;
g_Sessions[s].orbLow = barLow;
g_Sessions[s].orbHigh = bar1High;
g_Sessions[s].orbLow = bar1Low;
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].breakoutDir = 0;
g_Sessions[s].inBreakout = false;
@@ -467,17 +467,20 @@ void UpdateORBSessions()
g_Sessions[s].breakoutBarsAgo = 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)
DrawORBLines(s);
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
&& IsWithinOrbWindow(g_Sessions[s], barTime))
&& IsWithinOrbWindow(g_Sessions[s], bar1Time))
{
g_Sessions[s].orbHigh = MathMax(g_Sessions[s].orbHigh, barHigh);
g_Sessions[s].orbLow = MathMin(g_Sessions[s].orbLow, barLow);
g_Sessions[s].orbHigh = MathMax(g_Sessions[s].orbHigh, bar1High);
g_Sessions[s].orbLow = MathMin(g_Sessions[s].orbLow, bar1Low);
g_Sessions[s].orbBarCount++;
if(g_Sessions[s].orbBarCount >= g_OrbBarsNeeded)
{
@@ -489,7 +492,11 @@ void UpdateORBSessions()
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)
g_Sessions[s].breakoutBarsAgo++;
}
@@ -924,20 +931,59 @@ void CheckEntrySignals()
{
if(!g_Sessions[s].enabled || !g_Sessions[s].orbComplete) continue;
if(g_Sessions[s].tradesThisSession >= InpMaxPosPerSession) continue;
if(!g_Sessions[s].inBreakout) continue;
int dir = 0;
if(!DetectRetest(s, dir)) continue;
double obTop = 0, obBottom = 0;
bool obFound = IsOBNearLevel(
(dir > 0) ? g_Sessions[s].orbHigh : g_Sessions[s].orbLow,
dir, obTop, obBottom);
// If OB required and not found, skip
if(InpOBRequireConf && !obFound) continue;
// --- Breakout expiry ---
if(InpBreakoutExpireBars > 0
&& g_Sessions[s].breakoutBarsAgo >= InpBreakoutExpireBars)
{
PrintFormat("NANDR EA: [%s] Breakout expired after %d bars. Resetting.",
g_Sessions[s].name, g_Sessions[s].breakoutBarsAgo);
g_Sessions[s].inBreakout = false;
g_Sessions[s].breakoutDir = 0;
g_Sessions[s].inRetest = false;
continue;
}
int dir = g_Sessions[s].breakoutDir;
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);
}
}
}