diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index e2d377b..e1116a3 100644 --- a/NANDR_ORB_OB_EA.mq5 +++ b/NANDR_ORB_OB_EA.mq5 @@ -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); + } } }