fix: EA restarted at 14:36 — all ORB sessions lost (sessions showed [WAIT]), g_TradingHalted = true blocked UpdateORBSessions() on new bars, Daily loss halt didn't persist across restarts, g_GlobalBiasDir never flipped on sweep reversals, SELL 0.02 is a wrongly-opened hedge position

This commit is contained in:
Naji El Chemaly
2026-06-09 23:31:16 +03:00
parent 16df67c6fc
commit 8f2aaeef33
+135 -31
View File
@@ -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 <Trade\Trade.mqh>
@@ -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();
}
//+------------------------------------------------------------------+