diff --git a/NANDR_ORB_OB_EA.mq5 b/NANDR_ORB_OB_EA.mq5 index 72245f1..13e21e0 100644 --- a/NANDR_ORB_OB_EA.mq5 +++ b/NANDR_ORB_OB_EA.mq5 @@ -5,7 +5,7 @@ //| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector | //+------------------------------------------------------------------+ #property copyright "NANDR" -#property version "1.35" +#property version "1.37" #property strict #include @@ -682,8 +682,9 @@ void UpdateRetestState(int sessIdx) 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); + g_GlobalBiasDir = -1; // price swept the level and closed below — day bias now bearish + PrintFormat("NANDR EA: [%s] Bullish sweep reversal at orbHigh %.2f — flipping to BEARISH [DayBias=%d]", + g_Sessions[sessIdx].name, orbHigh, g_GlobalBiasDir); } // Full invalidation: closed below mid else if(c0 < orbMid && c1 > orbMid) @@ -701,8 +702,9 @@ void UpdateRetestState(int sessIdx) 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); + g_GlobalBiasDir = 1; // price swept the level and closed above — day bias now bullish + PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH [DayBias=%d]", + g_Sessions[sessIdx].name, orbLow, g_GlobalBiasDir); } // Full invalidation: closed above mid else if(c0 > orbMid && c1 < orbMid) @@ -1055,8 +1057,9 @@ void CheckRetestEntriesTick() { dir = -1; g_Sessions[s].breakoutDir = -1; - PrintFormat("NANDR EA: [%s] Tick sweep reversal below orbHigh %.2f → SELL", - g_Sessions[s].name, orbHigh); + g_GlobalBiasDir = -1; // tick sweep below orbHigh — day bias flips bearish + PrintFormat("NANDR EA: [%s] Tick sweep reversal below orbHigh %.2f → SELL [DayBias=%d]", + g_Sessions[s].name, orbHigh, g_GlobalBiasDir); } } // Mid retest: prev close above mid, wick dipped to mid @@ -1086,8 +1089,9 @@ void CheckRetestEntriesTick() { dir = 1; g_Sessions[s].breakoutDir = 1; - PrintFormat("NANDR EA: [%s] Tick sweep reversal above orbLow %.2f → BUY", - g_Sessions[s].name, orbLow); + g_GlobalBiasDir = 1; // tick sweep above orbLow — day bias flips bullish + PrintFormat("NANDR EA: [%s] Tick sweep reversal above orbLow %.2f → BUY [DayBias=%d]", + g_Sessions[s].name, orbLow, g_GlobalBiasDir); } } // Mid retest: prev close below mid, wick came back up to mid @@ -1297,13 +1301,16 @@ void ManageOpenTrades() double fullLots = g_Position.Volume(); double closeLots = NormalizeLot(fullLots * InpPartialCloseRatio); double minLot = SymbolInfoDouble(g_Symbol, SYMBOL_VOLUME_MIN); - if(closeLots >= minLot) + // Ensure we leave at least minLot remaining so the position stays open + double remaining = NormalizeLot(fullLots - closeLots); + if(closeLots >= minLot && remaining >= minLot) { - bool partResult = (posDir > 0) - ? g_Trade.Sell(closeLots, g_Symbol, 0, 0, 0, - StringFormat("NANDR|PartialClose|%.0fpips", InpBreakevenTrigPips)) - : g_Trade.Buy(closeLots, g_Symbol, 0, 0, 0, - StringFormat("NANDR|PartialClose|%.0fpips", InpBreakevenTrigPips)); + // PositionClosePartial reduces the existing position by closeLots WITHOUT + // opening a new opposite trade. This is correct for both netting and hedging + // accounts. Using g_Trade.Sell/Buy here would open a new hedge position + // instead of reducing the original one — that was the previous bug. + g_Trade.SetComment(StringFormat("NANDR|PartialClose|%.0fpips", InpBreakevenTrigPips)); + bool partResult = g_Trade.PositionClosePartial(ticket, closeLots); if(partResult) { MarkPartialClosed(ticket); @@ -1683,6 +1690,101 @@ void RemoveAllObjects() ChartRedraw(0); } +//+------------------------------------------------------------------+ +//| Reconstruct ORB levels from historical bars on startup | +//| Handles mid-day EA restarts — finds today's session open bars | +//| in price history and restores High/Low so sessions show ARMED. | +//+------------------------------------------------------------------+ +void ReconstructORBFromHistory() +{ + MqlDateTime dtNow; + TimeToStruct(TimeCurrent(), dtNow); + + for(int s = 0; s < g_SessionCount; s++) + { + if(!g_Sessions[s].enabled || g_Sessions[s].orbComplete) continue; + + // Build this session's open datetime for today + MqlDateTime dtSess = dtNow; + dtSess.hour = g_Sessions[s].startHour; + dtSess.min = g_Sessions[s].startMin; + dtSess.sec = 0; + datetime sessOpenTime = StructToTime(dtSess); + + // Only reconstruct if the opening bar has fully closed + long barSecs = (long)g_OrbBarsNeeded * (int)InpOrbTimeframe * 60; + if(TimeCurrent() < sessOpenTime + barSecs) continue; + + // Find the bar index for this session open time + int barIdx = iBarShift(g_Symbol, PERIOD_CURRENT, sessOpenTime, false); + if(barIdx < 1) continue; // bar[0] is the live bar + + // Verify exact time match (session bar must exist in chart history) + if(iTime(g_Symbol, PERIOD_CURRENT, barIdx) != sessOpenTime) continue; + + // Accumulate ORB range across all required bars (M1: 15 bars, M5: 3, M15: 1) + double h = iHigh(g_Symbol, PERIOD_CURRENT, barIdx); + double l = iLow(g_Symbol, PERIOD_CURRENT, barIdx); + for(int b = 1; b < g_OrbBarsNeeded; b++) + { + int idx = barIdx - b; + if(idx < 1) break; + h = MathMax(h, iHigh(g_Symbol, PERIOD_CURRENT, idx)); + l = MathMin(l, iLow(g_Symbol, PERIOD_CURRENT, idx)); + } + + g_Sessions[s].orbHigh = h; + g_Sessions[s].orbLow = l; + g_Sessions[s].orbBarCount = g_OrbBarsNeeded; + g_Sessions[s].orbStartTime = sessOpenTime; + g_Sessions[s].orbComplete = true; + + PrintFormat("NANDR EA: [%s] ORB reconstructed from history. High=%.2f Low=%.2f", + g_Sessions[s].name, h, l); + if(InpShowORBLines) DrawORBLines(s); + } +} + +//+------------------------------------------------------------------+ +//| Sync daily state from deal history on startup/restart | +//| Restores g_DayStartEquity so the daily loss limit persists across| +//| EA restarts, and syncs g_TodayTrades to prevent double-counting. | +//+------------------------------------------------------------------+ +void SyncDailyState() +{ + datetime dayStart = iTime(g_Symbol, PERIOD_D1, 0); + if(dayStart == 0) return; + HistorySelect(dayStart, TimeCurrent()); + + int deals = HistoryDealsTotal(); + double closedPnL = 0; + int tradeOpens = 0; + + for(int i = 0; i < deals; i++) + { + ulong ticket = HistoryDealGetTicket(i); + if(HistoryDealGetInteger(ticket, DEAL_MAGIC) != InpMagicNumber) continue; + if(HistoryDealGetString(ticket, DEAL_SYMBOL) != g_Symbol) continue; + + long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY); + if(entry == DEAL_ENTRY_IN) + tradeOpens++; + if(entry == DEAL_ENTRY_OUT) + closedPnL += HistoryDealGetDouble(ticket, DEAL_PROFIT) + + HistoryDealGetDouble(ticket, DEAL_SWAP) + + HistoryDealGetDouble(ticket, DEAL_COMMISSION); + } + + // Reconstruct day-start equity: current balance minus today's already-realized P&L. + // This ensures the daily loss limit check works correctly after a mid-day restart. + double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); + g_DayStartEquity = currentBalance - closedPnL; + g_TodayTrades = tradeOpens; + + PrintFormat("NANDR EA: Daily state synced. DayStartEquity=%.2f TodayTrades=%d ClosedPnL=%.2f", + g_DayStartEquity, g_TodayTrades, closedPnL); +} + //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ @@ -1770,10 +1872,18 @@ int OnInit() if(g_SessionCount == 0) Print("NANDR EA: WARNING — No sessions enabled. EA will not trade. Enable at least one session."); - // Daily init + // Daily init — set conservative defaults; SyncDailyState() will correct them from history g_DayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); g_LastDayReset = TimeCurrent(); + // Sync trade count and day-start equity from today's deal history + // (handles mid-day restarts so daily loss limits carry over correctly) + SyncDailyState(); + + // Restore ORB levels from historical bars (handles mid-day restarts + // where the session opening bars have already closed before EA loaded) + ReconstructORBFromHistory(); + PrintFormat("NANDR EA: Initialized. Symbol=%s OrbBarsNeeded=%d BreakoutConf=%d OBPivot=%d Sessions=%d", g_Symbol, g_OrbBarsNeeded, g_BreakoutConfBars, g_OBPivotLength, g_SessionCount); @@ -1828,28 +1938,22 @@ void OnTick() // Daily reset CheckDailyReset(); - if(g_TradingHalted) - { - DrawDashboard(); - return; - } - - // ORB accumulation + // Always update ORB and OB state regardless of halt status. + // This keeps session levels current for display and ensures breakouts are detected + // as soon as trading resumes (e.g. next day after a daily-loss halt). UpdateORBSessions(); - - // Order Block updates ScanOrderBlocks(); MitigateOrderBlocks(); - - // Breakout detection DetectBreakouts(); + UpdateClosedTrades(); + DrawDashboard(); + + // Skip trade execution only when halted — state tracking continues above + if(g_TradingHalted) + return; // Entry signals (direct breakout / limit orders on new bar) CheckEntrySignals(); - - // Update closed trade stats & dashboard - UpdateClosedTrades(); - DrawDashboard(); } //+------------------------------------------------------------------+