Compare commits

...
2 Commits
Author SHA1 Message Date
garfield 90f97606ea v5.8: self-heal gridPlaced from real broker state at OnInit
Fixes the duplicate-grid bug found 2026-08-11: gridPlaced (and the
rest of SaveGridState's GlobalVariable-backed state) never reaches
disk in this environment -- confirmed no .gvr file exists anywhere
under the MT5 data dir. Every full container restart mid-cycle wiped
it back to false while a grid was still resting live on the broker,
and OnTick would place a brand new duplicate grid on top of the old,
uncancelled one. Found 189 stacked pending orders account-wide before
cleanup (CancelAllPendingNow.mq5), one symbol alone had 29 spanning
24+ hours of normal cycles -- this has likely been happening on every
watchdog restart for weeks, not just from that day's testing.

Adds HasLiveGridPresence(): checks actual OrdersTotal()/PositionsTotal()
for this symbol+magic. At OnInit, if gridPlaced reads false but real
orders or positions already exist, trust reality over the flag and
recover gridPlaced=true -- so a restart mid-cycle can no longer cause
a duplicate placement, regardless of whether GlobalVariable persistence
ever gets fixed underneath. Deployed and recompiled live (all 14 EAs
confirmed reinitialized as v5.8); not yet validated against a live
container-restart cycle given the risk of disrupting the account
further right after cleanup -- logic mirrors the existing broker-state
query pattern already used in OnTick's monitor branch.
2026-08-11 15:44:41 -04:00
garfield 36ae668706 Add CancelAllPendingNow: companion cleanup script to CloseAllNow
Needed today: root-caused the "trades are losing" report to a second,
deeper persistence bug -- gridPlaced (and the rest of SaveGridState's
GlobalVariable-based state) never reaches disk in this environment at
all (no .gvr file exists anywhere under the MT5 data dir). It survives
a same-process EA reinit (input edit) fine, but a full container
restart loses it completely, so every restart made every EA think no
grid was resting and place a fresh one on top of whatever was already
there, uncancelled. Confirmed via ~29 stacked pending orders on some
symbols spanning 24+ hours of normal hourly cycles, not just last
night's restarts -- this has likely been happening on every watchdog
restart for weeks.

Ran it today: cancelled 177 stacked/duplicate pending orders fleet-
wide, 0 remaining. Open positions untouched -- each EA's own OnTick
"Cycle closed?" check self-heals from an empty pending book and places
exactly one clean grid on its next tick.
2026-08-11 10:46:28 -04:00
2 changed files with 90 additions and 2 deletions
+45
View File
@@ -0,0 +1,45 @@
//+------------------------------------------------------------------+
//| CancelAllPendingNow.mq5 |
//| One-shot utility: cancel every pending order on the account. |
//| Open positions are left alone -- SL/trail/EA management is |
//| untouched. Companion to CloseAllNow.mq5 (which is the reverse: |
//| closes positions, leaves pendings). Written 2026-08-11 to clean |
//| up duplicate grid orders stacked by the gridPlaced GlobalVariable|
//| not surviving container restarts (see vault note on the |
//| persistence bug) -- each restart made every EA think no grid was|
//| resting and place a brand new one on top of the old, un- |
//| cancelled one. Safe to run any time: any EA that still has zero |
//| orders/positions after this will self-heal and place exactly |
//| one clean fresh grid on its own next tick (see OnTick's |
//| "Cycle closed?" branch). |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Garfield Heron"
#property version "1.0"
#property script_show_inputs false
#include <Trade\Trade.mqh>
void OnStart()
{
CTrade trade;
ulong start = GetTickCount64();
int sweep = 0, cancelled = 0;
while(!IsStopped() && GetTickCount64() - start < 600000)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if(ticket == 0) continue;
if(trade.OrderDelete(ticket))
cancelled++;
}
if(OrdersTotal() == 0) break;
sweep++;
if(sweep % 5 == 1)
Print("CancelAllPendingNow: sweep ", sweep, " — ", OrdersTotal(), " still pending, retrying...");
Sleep(3000);
}
Print("CancelAllPendingNow: DONE — cancelled ", cancelled, ", remaining ", OrdersTotal(),
". Equity=", DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2),
" Balance=", DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2));
}
+45 -2
View File
@@ -5,12 +5,12 @@
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Garfield Heron"
#property link "https://fetcherpay.com"
#property version "5.7"
#property version "5.8"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#define VERSION "Version 5.7 Smart Grid Breakout BO MT5"
#define VERSION "Version 5.8 Smart Grid Breakout BO MT5"
#define MAX_TRADES 600
#define MAX_LOG_TRADES 1200
@@ -1078,6 +1078,35 @@ int CountPendingOrders(int type)
return count;
}
// True if this symbol+magic already has any pending order or open position
// on the broker, regardless of what gridPlaced (GlobalVariable-backed,
// found 2026-08-11 to never actually reach disk in this environment) says.
// Used at OnInit to recover from a restart wiping that flag to false while
// a grid is still resting live -- without this check, every restart mid-
// cycle placed a brand new duplicate grid on top of the old one (see vault
// "2026-08-11 Duplicate Grid Orders" note; found 189 stacked pending
// orders account-wide, one symbol alone had 29 spanning 24+ hours).
bool HasLiveGridPresence()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if(ticket == 0) continue;
if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
if(OrderGetInteger(ORDER_MAGIC) != MagicNum) continue;
return true;
}
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNum) continue;
return true;
}
return false;
}
void CancelAllOrders(string reason)
{
int cancelled = 0;
@@ -1351,6 +1380,20 @@ int OnInit()
LoadGridState();
// Self-heal (2026-08-11): gridPlaced is GlobalVariable-backed and was
// found to never actually reach disk in this environment (no .gvr file
// anywhere under the MT5 data dir) — so a full container restart mid-
// cycle silently resets it to false while the grid is still resting
// live on the broker, and the code below would place a brand new
// duplicate grid on top of it. Trust reality over the flag: if this
// symbol+magic already has orders or positions out, we already have a
// grid, no matter what LoadGridState() just said.
if(!gridPlaced && HasLiveGridPresence())
{
PrintS("Self-heal: found existing orders/positions on init — recovering gridPlaced=true (see 2026-08-11 duplicate-grid fix)");
gridPlaced = true;
}
// GridHigh/GridLow are NOT persisted (only lastPivotCalcDate is) — if a
// prior process already recalculated today before this restart, the
// date-match guard alone would skip CalculatePivotPoints() and leave the