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:
+135
-31
@@ -5,7 +5,7 @@
|
|||||||
//| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector |
|
//| Based on: ORB-All-Sessions.pine + LuxAlgo Order Block Detector |
|
||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
#property copyright "NANDR"
|
#property copyright "NANDR"
|
||||||
#property version "1.35"
|
#property version "1.37"
|
||||||
#property strict
|
#property strict
|
||||||
|
|
||||||
#include <Trade\Trade.mqh>
|
#include <Trade\Trade.mqh>
|
||||||
@@ -682,8 +682,9 @@ void UpdateRetestState(int sessIdx)
|
|||||||
if(sweepRev)
|
if(sweepRev)
|
||||||
{
|
{
|
||||||
g_Sessions[sessIdx].breakoutDir = -1;
|
g_Sessions[sessIdx].breakoutDir = -1;
|
||||||
PrintFormat("NANDR EA: [%s] Bullish sweep reversal at orbHigh %.2f — flipping to BEARISH",
|
g_GlobalBiasDir = -1; // price swept the level and closed below — day bias now bearish
|
||||||
g_Sessions[sessIdx].name, orbHigh);
|
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
|
// Full invalidation: closed below mid
|
||||||
else if(c0 < orbMid && c1 > orbMid)
|
else if(c0 < orbMid && c1 > orbMid)
|
||||||
@@ -701,8 +702,9 @@ void UpdateRetestState(int sessIdx)
|
|||||||
if(sweepRev)
|
if(sweepRev)
|
||||||
{
|
{
|
||||||
g_Sessions[sessIdx].breakoutDir = 1;
|
g_Sessions[sessIdx].breakoutDir = 1;
|
||||||
PrintFormat("NANDR EA: [%s] Bearish sweep reversal at orbLow %.2f — flipping to BULLISH",
|
g_GlobalBiasDir = 1; // price swept the level and closed above — day bias now bullish
|
||||||
g_Sessions[sessIdx].name, orbLow);
|
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
|
// Full invalidation: closed above mid
|
||||||
else if(c0 > orbMid && c1 < orbMid)
|
else if(c0 > orbMid && c1 < orbMid)
|
||||||
@@ -1055,8 +1057,9 @@ void CheckRetestEntriesTick()
|
|||||||
{
|
{
|
||||||
dir = -1;
|
dir = -1;
|
||||||
g_Sessions[s].breakoutDir = -1;
|
g_Sessions[s].breakoutDir = -1;
|
||||||
PrintFormat("NANDR EA: [%s] Tick sweep reversal below orbHigh %.2f → SELL",
|
g_GlobalBiasDir = -1; // tick sweep below orbHigh — day bias flips bearish
|
||||||
g_Sessions[s].name, orbHigh);
|
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
|
// Mid retest: prev close above mid, wick dipped to mid
|
||||||
@@ -1086,8 +1089,9 @@ void CheckRetestEntriesTick()
|
|||||||
{
|
{
|
||||||
dir = 1;
|
dir = 1;
|
||||||
g_Sessions[s].breakoutDir = 1;
|
g_Sessions[s].breakoutDir = 1;
|
||||||
PrintFormat("NANDR EA: [%s] Tick sweep reversal above orbLow %.2f → BUY",
|
g_GlobalBiasDir = 1; // tick sweep above orbLow — day bias flips bullish
|
||||||
g_Sessions[s].name, orbLow);
|
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
|
// Mid retest: prev close below mid, wick came back up to mid
|
||||||
@@ -1297,13 +1301,16 @@ void ManageOpenTrades()
|
|||||||
double fullLots = g_Position.Volume();
|
double fullLots = g_Position.Volume();
|
||||||
double closeLots = NormalizeLot(fullLots * InpPartialCloseRatio);
|
double closeLots = NormalizeLot(fullLots * InpPartialCloseRatio);
|
||||||
double minLot = SymbolInfoDouble(g_Symbol, SYMBOL_VOLUME_MIN);
|
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)
|
// PositionClosePartial reduces the existing position by closeLots WITHOUT
|
||||||
? g_Trade.Sell(closeLots, g_Symbol, 0, 0, 0,
|
// opening a new opposite trade. This is correct for both netting and hedging
|
||||||
StringFormat("NANDR|PartialClose|%.0fpips", InpBreakevenTrigPips))
|
// accounts. Using g_Trade.Sell/Buy here would open a new hedge position
|
||||||
: g_Trade.Buy(closeLots, g_Symbol, 0, 0, 0,
|
// instead of reducing the original one — that was the previous bug.
|
||||||
StringFormat("NANDR|PartialClose|%.0fpips", InpBreakevenTrigPips));
|
g_Trade.SetComment(StringFormat("NANDR|PartialClose|%.0fpips", InpBreakevenTrigPips));
|
||||||
|
bool partResult = g_Trade.PositionClosePartial(ticket, closeLots);
|
||||||
if(partResult)
|
if(partResult)
|
||||||
{
|
{
|
||||||
MarkPartialClosed(ticket);
|
MarkPartialClosed(ticket);
|
||||||
@@ -1683,6 +1690,101 @@ void RemoveAllObjects()
|
|||||||
ChartRedraw(0);
|
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 |
|
//| OnInit |
|
||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
@@ -1770,10 +1872,18 @@ int OnInit()
|
|||||||
if(g_SessionCount == 0)
|
if(g_SessionCount == 0)
|
||||||
Print("NANDR EA: WARNING — No sessions enabled. EA will not trade. Enable at least one session.");
|
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_DayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||||
g_LastDayReset = TimeCurrent();
|
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",
|
PrintFormat("NANDR EA: Initialized. Symbol=%s OrbBarsNeeded=%d BreakoutConf=%d OBPivot=%d Sessions=%d",
|
||||||
g_Symbol, g_OrbBarsNeeded, g_BreakoutConfBars, g_OBPivotLength, g_SessionCount);
|
g_Symbol, g_OrbBarsNeeded, g_BreakoutConfBars, g_OBPivotLength, g_SessionCount);
|
||||||
|
|
||||||
@@ -1828,28 +1938,22 @@ void OnTick()
|
|||||||
// Daily reset
|
// Daily reset
|
||||||
CheckDailyReset();
|
CheckDailyReset();
|
||||||
|
|
||||||
if(g_TradingHalted)
|
// Always update ORB and OB state regardless of halt status.
|
||||||
{
|
// This keeps session levels current for display and ensures breakouts are detected
|
||||||
DrawDashboard();
|
// as soon as trading resumes (e.g. next day after a daily-loss halt).
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ORB accumulation
|
|
||||||
UpdateORBSessions();
|
UpdateORBSessions();
|
||||||
|
|
||||||
// Order Block updates
|
|
||||||
ScanOrderBlocks();
|
ScanOrderBlocks();
|
||||||
MitigateOrderBlocks();
|
MitigateOrderBlocks();
|
||||||
|
|
||||||
// Breakout detection
|
|
||||||
DetectBreakouts();
|
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)
|
// Entry signals (direct breakout / limit orders on new bar)
|
||||||
CheckEntrySignals();
|
CheckEntrySignals();
|
||||||
|
|
||||||
// Update closed trade stats & dashboard
|
|
||||||
UpdateClosedTrades();
|
|
||||||
DrawDashboard();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//+------------------------------------------------------------------+
|
//+------------------------------------------------------------------+
|
||||||
|
|||||||
Reference in New Issue
Block a user