Your EA backtests profitably, compiles without warnings, and runs on your demo account without crashing. None of that means it is production-ready. Most EAs that “work fine” in backtest share the same five structural failures — missing error handling, no state persistence, hardcoded assumptions, ignored execution feedback, and no defensive guards — and recognizing them before going live saves traders real money. After rescuing dozens of EAs over 14 years, I can tell you exactly what is hiding inside most “working” EAs, because it is almost always the same five things.
This is not a theoretical list. These are patterns I encounter in the majority of rescue projects that arrive at my desk — EAs that compiled, backtested, and even ran on demo for a while before something went wrong in live trading. At barmenteros FX, roughly 4 out of 5 rescue projects over the past three years — across 30+ code reviews — share at least three of these five failures. If you run an EA on a live account, or plan to, here is what to check before your backtest results become expensive fiction.
Failure #1 — Silent OrderSend: No Error Handling on Trade Execution
The most common structural failure in EAs I rescue is the absence of error handling on trade execution calls. The EA calls `OrderSend()` in MQL4 or `trade.PositionOpen()` in MQL5 and never checks what happened next. It assumes the position opened, updates its internal state, and moves on.
In the Strategy Tester, this works perfectly. Every order fills instantly at the requested price. There are no requotes, no insufficient margin events, no “trade context busy” conditions, no broker rejections. The backtest is a world where every trade request succeeds.
Live markets are different. Orders fail. They fail for reasons the Strategy Tester never simulates:
- Requotes during fast price movement — the price moved between the EA’s request and the broker’s execution.
- Insufficient margin — the account does not have enough free margin for the requested lot size, especially after a drawdown.
- Trade context busy — another operation (manual trade, another EA) is already using the trade thread.
- Broker-side rejection — the order violates a trading rule (lot size, symbol restriction, session hours).
When an EA does not check the return value of `OrderSend()`, it does not know the order failed. It increments its internal counter, adjusts its grid level, or marks its entry as “filled” — based on a position that does not exist.
I worked on a client’s grid EA that opened recovery positions based on a counter. Every `OrderSend()` call incremented the grid level, regardless of the return value. In backtest: perfect grid spacing. In live trading: requotes during volatile sessions caused missed fills. The counter incremented anyway. The EA calculated its next recovery level based on phantom grid positions that never existed. The average entry price in the EA’s memory diverged from the actual average entry on the account.
The fix is mechanical but non-negotiable. Every `OrderSend()` call must:
- Capture the return value (ticket number in MQL4, boolean in MQL5)
- Verify the result — if it fails, call `
GetLastError()` and log the error code - Only update internal state after confirmed execution
- Implement retry logic with backoff for recoverable errors (requotes, trade context busy)
This is not defensive programming. This is the minimum standard for code that handles real money.
Failure #2 — Amnesia: No State Persistence Across Restarts
The second pattern I find in nearly every rescue project is what I call amnesia: the EA stores all its state in runtime variables — `static` variables, global-scope variables, or class members — and assumes the terminal will never restart.
Terminals restart constantly. MetaTrader updates, VPS reboots, power outages, broker-side disconnects, manual restarts to clear a stuck chart. Every restart fires `OnDeinit()` followed by `OnInit()`. Every runtime variable resets to its initial value. The EA wakes up with no memory of what it was doing.
The consequences depend on what the EA tracks:
- Grid/martingale EAs lose their current multiplier level. A martingale EA mid-drawdown at 8x lot size restarts at 1x. The losing chain is still open, but the EA now opens a new base-size position instead of the recovery position. Exposure doubles without the recovery logic that was supposed to manage it.
- Trailing stop EAs forget which positions they have already trailed. They re-scan, and depending on implementation, either skip trailing (if they check a “trailed” flag that reset to false) or re-trail from scratch (resetting the stop loss backward).
- Session-based EAs lose their daily trade count. An EA configured to take maximum three trades per day restarts at lunch and opens three more trades in the afternoon session.
I worked on a martingale EA where the current multiplier was tracked in a `static int` variable. The client reported that during a drawdown, after a weekend terminal restart, the EA opened a new base lot position while the losing chain was still running. The multiplier had reset to 1. The recovery structure collapsed.
The fix required two changes:
- Persist critical state — write the multiplier, cycle count, and reference ticket to a file or to MetaTrader’s global variable pool (which survives restarts, unlike `
static` variables). - Reconciliation on startup — on every `
OnInit()`, scan existing orders via `OrdersTotal()`, match them against persisted state, and rebuild the EA’s internal model from live data. If persisted state conflicts with actual open positions, trust the broker’s data.
State persistence is not a feature. It is a structural requirement for any EA that tracks more than “is my entry condition true right now.”
Failure #3 — Magic Numbers: Hardcoded Assumptions That Break Silently
The third failure is hardcoded assumptions about the trading environment. Lot sizes, pip values, symbol digit counts, spread thresholds, and session times baked directly into the source code instead of queried from the broker at runtime.
This pattern has a specific failure mode: the EA works perfectly on the developer’s setup and breaks on the client’s. Work on EURUSD, break on XAUUSD. Work on a 5-digit broker, break on a 3-digit one. Work on one account type, break on another with different lot step or minimum volume. These are the symbol-specific assumptions that cause silent failures — and I have documented the five most dangerous ones in a dedicated analysis.
Concrete examples I have fixed:
- `
Point * 10`for pip calculation. This is correct for 5-digit forex pairs (where 1 pip = 10 points). It is wrong for gold (where point and pip relationships differ), wrong for indices, and wrong for 4-digit brokers where 1 pip = 1 point. The EA’s stop loss and take profit distances are off by a factor of 10 on any symbol that does not match the assumption. - Hardcoded lot size 0.01. The EA always trades 0.01 lots. On brokers with a minimum lot of 0.10, `
OrderSend()` fails on every call. The EA does not report the error (see Failure #1). The trader sees no positions opening and assumes the strategy has no signals. - Fixed spread threshold. The EA checks `
if(spread < 20)` before entering. Whether 20 means points or pips depends on the broker’s digit count — a check the EA does not make. On a 5-digit broker, 20 points is 2 pips (reasonable). On a 4-digit broker, 20 points is 20 pips (almost never passes). The EA goes silent.

The fix for all of these is the same principle: query symbol properties at runtime, never assume them.
| Property | MQL4 Query | Common Hardcode |
|---|---|---|
| Pip size | `Point * MathPow(10, Digits % 2)` | `Point * 10` |
| Min lot | `MarketInfo(Symbol(), MODE_MINLOT)` | `0.01` |
| Lot step | `MarketInfo(Symbol(), MODE_LOTSTEP)` | `0.01` |
| Stop level | `MarketInfo(Symbol(), MODE_STOPLEVEL)` | Not checked |
| Spread | `MarketInfo(Symbol(), MODE_SPREAD)` | Fixed value |
Every value in the right column has broken an EA I have rescued. Every value in the middle column would have prevented the failure.
Failure #4 — Fire and Forget: Ignoring Execution Feedback
Failure #1 covers `OrderSend()`. This failure extends to everything that happens after the position is open. `OrderModify()`, `OrderClose()`, `OrderDelete()` — each of these can fail, and in most EAs I rescue, none of them are checked.
The EA sends a modification request and moves on. It does not verify whether the broker accepted the change. Its internal model of the account state diverges from reality one ignored failure at a time.
The most common version of this I see is trailing stop logic. The EA calculates a new stop loss, calls `OrderModify()`, and updates its internal state to reflect the new SL. But the broker rejected the modification because the new stop loss violates the minimum stop distance (`MODE_STOPLEVEL`). Error 130: invalid stops.
The EA does not check. It logs “trailing active” or “SL updated.” The stop loss has not moved. The trader sees the trailing indicator on the chart, assumes protection is active, and goes to sleep. The position runs past the intended stop because the actual stop loss was never modified.
I fixed a client’s trailing EA where this exact scenario played out over weeks. The EA was supposed to trail by 30 pips. The broker’s stop level was 50 points (5 pips on a 5-digit broker). When the trail distance dropped below the stop level, every modify call was rejected. The EA logged success messages because it logged *intent*, not *result*.
The fix has two parts:
- Pre-validate before calling. Calculate the effective stop loss after accounting for the broker’s stop level constraint. If the new SL violates the minimum distance, skip the call entirely — do not send a request you know will fail.
- Log effective values, not intent. After computing the constrained SL, log what will actually be sent: “SL modify: requested=1.08450, effective=1.08420 (stop level enforced), current=1.08350.” When debugging from logs, you need to see what happened, not what was planned.
This applies to every trade operation. `OrderClose()` fails during high volatility. `OrderDelete()` fails if the pending order already triggered. Every operation that interacts with the broker can return failure, and every failure that goes unchecked creates a gap between what the EA believes and what the account actually holds.
Failure #5 — No Defensive Guards: Array Bounds, Division by Zero, Empty History
The fifth failure is the absence of basic defensive checks — the guard clauses that prevent crashes, infinite values, and undefined behavior when market data does not look like the developer expected.
In the Strategy Tester, these problems rarely trigger. History is preloaded. Bars are plentiful. Price data is clean. ATR returns sensible values. Every array has enough elements.
In live trading, the conditions are different:
- Chart open with insufficient history. The EA accesses `
Close[100]` on a chart that has loaded only 50 bars so far. In MQL4, this returns 0.0 silently. Every calculation based on that value is now wrong. - Thin market with zero-range bars. ATR during off-hours or on illiquid symbols can calculate to zero. If the EA uses ATR as a denominator for position sizing or stop loss distance, the result is infinity.
- Timeframe switch or symbol change. Bars count changes. Indicator handles invalidate. Buffers that were full are now empty or contain stale data.
I worked on an EA that calculated position size as `riskAmount / (stopLossPips * pipValue)`. The stop loss was derived from ATR. During a news spike, ATR on the M1 timeframe collapsed to effectively zero on a single bar. The division produced infinity. `NormalizeDouble()` did not catch it — it normalized infinity to a very large number. The `OrderSend()` call submitted a lot size of 9,999. The broker rejected it on lot size limits.
The EA survived only because the broker had server-side protection. On a broker without volume limits, that single unguarded division would have blown the account.
The fix is not complex, but it must be systematic:
- Check `
Bars`before any array access. If `Bars < requiredBars`, return early and log the condition. - Check every denominator before division. If the value could ever be zero — and with market data, it can — guard it explicitly.
- Validate indicator values before use. `
iATR()` returns zero when history is insufficient. `iCustom()` can return `EMPTY_VALUE`. Check before calculating.
These are not polish. They are not “nice to have.” In production, every unguarded assumption is a latent crash waiting for a specific market condition to trigger it.
The Pattern Behind the Five Failures
All five failures share a root cause: the EA was tested only in the environment where everything works.
The Strategy Tester is a controlled lab. Orders fill instantly. The terminal never restarts. Symbol properties match the developer’s setup. History is complete. Price data is clean. Every external operation succeeds. It is the perfect environment for verifying strategy logic and the worst environment for verifying production readiness.
Live trading is the opposite. It is the environment where things fail. Orders get rejected. Terminals restart. Brokers have different configurations. Markets go thin. Data has gaps. Every external interaction can return an unexpected result.
Production-ready code is code that assumes failure is normal. Every trade request can be rejected. Every stored value can be lost. Every assumption about the broker can be wrong. The five failures in this article are five ways that assumption is violated.
Here is a diagnostic you can apply to any EA right now — yours, one you bought, or one a developer delivered:

| # | Question | “No” Means |
|---|---|---|
| 1 | Does the EA check return values on every `OrderSend`, `OrderModify`, and `OrderClose` call? | Silent execution failures will corrupt internal state |
| 2 | Does the EA persist its critical state and reconcile on startup? | Any terminal restart can cause duplicate trades or abandoned recovery logic |
| 3 | Does the EA query symbol properties (`MODE_MINLOT`, `MODE_STOPLEVEL`, `Digits`) at runtime instead of using hardcoded values? | It will break on a different broker, symbol, or account type |
| 4 | Does the EA validate execution feedback and log effective values, not just intent? | Its internal model will drift from the broker’s actual state |
| 5 | Does the EA guard against zero denominators, insufficient bar counts, and empty indicator values? | A specific market condition will produce wrong values or crash the EA |
If you answered “no” to any of these, your EA has a structural gap that no backtest result can reveal. A profitable equity curve on a flawed foundation is not an asset. It is a liability with a delayed fuse.



Leave a Reply