EA State Loss on Restart: What MetaTrader Erases and How to Persist It
The Failure Pattern
Most EA reliability discussions focus on broker edge cases, slippage, and backtest-to-live gaps. The restart failure is quieter — and in my experience, more consistently expensive.
The pattern: an EA runs correctly for months on a VPS. A routine platform update or maintenance event restarts MetaTrader at 3am. The EA reconnects, finds its in-memory state empty, and treats the session as a fresh start. On a basket recovery EA, this means re-opening the baseline position on a basket already running. The exposure doubles.
This is not a logic bug. The EA’s logic is correct within any single session. The problem is what it forgets between sessions.
What MQL Resets on Every Restart
MQL has three distinct memory tiers:
Tick-scoped state: Local variables declared inside OnTick() or any called function. Exist for one execution cycle, then discarded. No accumulation, no persistence.
Session-scoped state: static variables and globally declared variables and arrays. These survive across OnTick() calls within a session — they accumulate, update, and persist as long as the terminal runs. When MetaTrader closes — planned restart, crash, or broker disconnect — they reset to their declared initial values. static int counter = 0 returns to 0. static bool locked = false returns to false.
Persistent state: Anything explicitly written to external storage — a file via FileOpen() / FileWrite() , or a GlobalVariable entry via GlobalVariableSet() . Lives on disk, not in process memory. Survives terminal restarts.
The practical rule: any value that builds up across multiple ticks or trades belongs in persistent storage if it needs to survive a restart.
Five Failure Patterns
Not every state loss causes visible damage immediately. Five categories reliably produce costly symptoms.
Position tracking array: If the EA maintains a list of its own open trades — ticket numbers, lots, entry prices, sequence relationships — that array is empty on restart. An empty array looks identical to “no open trades.” If the entry logic is “open when the array is empty,” the EA executes.
Trailing stop reference price: The EA tracks the session high to calculate where the trailing stop sits. On restart, that reference resets. The EA now trails from current price instead of the actual high-water mark — either moving the stop prematurely or abandoning the trail.
Recovery sequence step: A martingale or grid EA tracks which lot multiplier applies next. A restart resets the counter to step 1 regardless of where the sequence was. The EA applies the wrong lot size — under-sizing or over-sizing the next position.
Cooldown timer: After a losing trade, many EAs enforce a lockout period using TimeCurrent() comparisons against a stored start time. On restart, that stored time is gone. The lockout no longer exists.
Profit lock level: EAs that trail a profit target store the lock level in a static variable. On restart, the lock resets to its default. The EA can then trail the stop below a profit level it had already committed to protecting.
Two Persistence Mechanisms
MQL provides two native options. The right choice depends on data structure.
GlobalVariables — GlobalVariableSet() / GlobalVariableGet() : MetaTrader’s built-in key-value store, backed by a terminal-managed file. Best for 1–5 independent scalar values — sequence step, high-water mark price, lock level, cooldown expiry timestamp.
File I/O — FileOpen() , FileWrite() , FileRead() : Full control over format. Write a CSV row per open position with ticket, lots, entry price, and sequence step. Read it back in OnInit() and reconstruct the in-memory state. More code. Complete flexibility.
Use GlobalVariables for small sets of scalar values. Use file I/O when the state is structured — position lists, multi-attribute trade sequences, anything that does not fit in a double .
The OnInit() Restore Pattern
Persistence only works if the EA reads it back. OnInit() is the correct location — it runs once per session, before the first OnTick() fires. The sequence:
- Attempt to load from GlobalVariable or file
- If found: reconcile against the live order pool. Read live positions via OrderSelect() . Cross-reference every saved ticket. Remove entries not present in the live pool.
- If not found: first run or state was cleared. Initialise from defaults.
The reconcile step is what most implementations skip. If the saved state references a trade that closed during the downtime, acting on that stale data produces incorrect exposure calculations. Always cross-reference by ticket before the EA acts.
What Not to Persist
Two categories feel like EA state but should not be persisted.
Indicator values: Always stale the moment the terminal reconnects. Recalculate from the buffer in OnTick() .
Open P&L: Changes with every tick. Calculate from live positions.
A useful test: “Would this value change if the market moved while the terminal was off?” If yes, do not persist it — derive it fresh on restart.
Test Before You Trust
With positions open on a demo account, close MetaTrader completely, reopen it, and observe what the EA does on the first few ticks after reconnect. This test surfaces state loss issues that no backtest will catch, because backtests run in a single continuous session where restarts do not occur.


