From 90f97606ea735cedae98f9044fc610a85055355b Mon Sep 17 00:00:00 2001 From: Garfield Date: Tue, 11 Aug 2026 15:44:41 -0400 Subject: [PATCH] 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. --- OrdersEA_Smart_Grid_BO.mq5 | 47 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/OrdersEA_Smart_Grid_BO.mq5 b/OrdersEA_Smart_Grid_BO.mq5 index fc75b88..fbd0f00 100755 --- a/OrdersEA_Smart_Grid_BO.mq5 +++ b/OrdersEA_Smart_Grid_BO.mq5 @@ -5,12 +5,12 @@ //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Garfield Heron" #property link "https://fetcherpay.com" -#property version "5.7" +#property version "5.8" #include #include -#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