Why a Converted MQL4 EA Compiles Clean and Still Trades Differently
A converted Expert Advisor that compiles without a single error is not evidence that the conversion worked. The characteristic MT4 to MT5 failure does not produce anything wrong to find — it produces an absence, something that should have happened and didn't.
That distinction decides which of your tools can help. The compiler, the Strategy Tester and the demo account are all error detectors. This failure class does not produce errors, so a clean compile plus a matching backtest is not weak evidence that a conversion worked — it is the specific pair of signals least able to detect how conversions actually break.
This became more relevant in July 2026. Since build 6060, MetaEditor ships an AI assistant free on the MQL5 Lite plan, and one of the published example prompts is "Convert this MQL4 Expert Advisor to MQL5 using modern language features and the Standard Library." The conversion is now one prompt away. The checking is not.
What token-level conversion genuinely handles
Worth being fair about this first. Automated conversion — a paid converter or the assistant in your editor — handles a real category of EA well: single-signal indicator systems with one entry condition and no state carried between ticks, the large surface of MQL4 calls with direct MQL5 equivalents, and one-position-at-a-time logic with no partial closes and no recovery layer. If that describes your EA, the translation is not where your risk lives.
The argument here is not that these tools translate badly. They translate well. It is that translation is not the risky part, and the way most people verify the result cannot see the risky part at all.
The counting problem
In MQL5, OrdersTotal() returns the number of pending orders. The documentation is explicit: "Do not confuse current pending orders with positions… An order is a request to conduct a transaction, while a position is a result of one or more deals." Open positions are counted by PositionsTotal() . On MT4 the same function name covered both.
So a faithfully recompiled MT4 loop, running on an MT5 account that holds three open positions and no pending orders, correctly iterates over nothing:
The break-even manager this came from held three open positions in live trading and moved none of them. Nothing errored, so nobody opened the logs. Time to detection: indefinite — there is no event to notice. A missing break-even move is not an error, it is an absence, and you only catch an absence if you go looking for it.
The indexing problem, and why it is worse
A moving-average crossover system was migrated by the book. The indicator handle was created correctly. CopyBuffer() was called correctly. The destination array was never set as a series.
The documentation states the consequence directly: "No matter what is the property of the target array — as_series=true or as_series=false. Data will be copied so that the oldest element will be located at the start of the physical memory allocated for the array."
An MQL4 developer reads index 1 as "the previous bar" out of habit. After the copy, index 1 is near the oldest end of the requested range, so the EA compares crossovers that happened days earlier.
Here is why this one costs more than the counting problem. On the chart, the entry arrows looked random. But the wrong-end read applied uniformly to every bar, so the equity curve came out mediocre, not obviously broken. A broken backtest gets investigated. A mediocre one gets re-optimised — and every hour spent tuning parameters against that curve fits them more tightly to the bug.
The assumption problem
A session-filter EA used a tick counter for activation: once 20 ticks had arrived after the session open, it would start evaluating entries. On MT4 that had served as a workable proxy for "the market is actually trading."
Nothing in the converted code was wrong. The proxy was wrong. It assumed one tick meant one price move, and MT5 does not define a tick that way — the MqlTick structure carries flags ( TICK_FLAG_BID , TICK_FLAG_ASK , TICK_FLAG_LAST , TICK_FLAG_VOLUME , TICK_FLAG_BUY , TICK_FLAG_SELL ) because a tick marks a change in any one of those fields. Bid and Ask are flagged separately, and on instruments that carry a last-deal price a tick can arrive with no move in Bid or Ask at all. The counter cleared its threshold before real price movement began, and the EA opened positions in the exact low-liquidity window it had been built to avoid.
It ran two weeks on demo first. Demo did not catch it, because a demo account tests whether the code runs. It does not test whether the assumptions still hold.
A parity test you can run yourself
If the failures are absences, the only way to detect them is to define in advance what should be present:
- Audit the position model first. Netting or hedging on the target account, then confirm your close and modify logic addresses positions the way that account behaves. Failure looks like: logic targeting individual tickets on an account that merges them.
- Grep the converted source for every count and lookup that changed meaning. OrdersTotal() , order-selection calls, predefined price variables, point and digit handling. Failure looks like: a call you cannot explain, which compiled anyway.
- Run a same-period backtest against the MT4 original with a tolerance you set beforehand. Failure looks like: a curve that is merely worse — go find out why instead of re-optimising.
- Log every order call's return code, and log the actions that should have fired. Failure looks like: silence where a confirmation line should be.
- Force the second-position case deliberately. Failure looks like: the first case behaving correctly and the second one not.
Check 4 is the one that catches absences specifically:
Neither a compiler, a tester nor two weeks on a demo account can flag a move that never fired. Only you can — and only if you wrote down what should happen before you went looking.


