← BlogBacktesting
Backtesting

Why an MT5 EA That Works in Strategy Tester Fails on a Live Account

We’ve watched an EA produce a clean MT5 equity curve, then spend a week on demo consuming CPU.

Why an MT5 EA That Works in Strategy Tester Fails on a Live Account

Why tester success proves less than traders think

We’ve watched an EA produce a clean MT5 equity curve, then spend a week on demo consuming CPU. Strategy logic had not changed. Its operating environment had.

Diagnostic flow separating an absent live signal from an order rejection or changed execution result
Diagnostic flow separating an absent live signal from an order rejection or changed execution result

“MT5 strategy tester works but EA fails on live account” is a deployment diagnosis problem, not a strategy verdict. The tester models prices, execution rules, and account conditions in a controlled run. A demo or live terminal runs against a broker’s current symbol catalogue, contract specification, permissions, session state, incoming ticks, and trade server.

A profitable test can fail two ways:

  • The EA never reaches its entry condition live.
  • The EA reaches it, submits a request, and receives a rejection or different execution outcome.

Treat these as separate faults. Re-optimising before separating them turns a deployment issue into a month of fictional research.

The MT5 Strategy Tester documentation covers testing modes, generated ticks, real ticks, spreads, and multi-currency history. That matters. Historical simulation still cannot reproduce a live trade-server response.

Tester assumptions versus demo and live conditions

The tester gives an EA a repeatable environment. That helps find defects and compare variants. It also removes moving parts that matter after deployment.

AreaStrategy TesterDemo or live terminalWhat to check
PricesHistorical or generated tick sequenceIncoming broker quotesTick arrival, spread behaviour, symbol suffix
ContractTest-time symbol settingsCurrent broker contract specificationVolume step, stops level, filling mode, trade mode
AccountConfigured test deposit/leverageActual account rules and permissionsMargin mode, hedging/netting, EA permissions
ExecutionSimulated request handlingTrade server validation and executionRetcode, deviation, market session, slippage
TimingFast historical event streamReal chart-symbol ticks and terminal event queueOnTick, timer use, bar-close guards
SymbolsTester history loaded for runMarket Watch selection and terminal synchronizationSymbolSelect, history, multi-symbol readiness

We do not expect identical fills, spreads, or slippage between a backtest and live trading. We do expect an EA to report why it did not trade. Silence is a missing diagnostic.

How to Diagnose an MT5 EA That Works in Tester but Fails Live

Run this sequence. Stop when evidence identifies the fault. Each stage narrows the problem without changing strategy inputs.

  1. Confirm EA initialization and chart attachment.
  2. Compare account and symbol identity with the test.
  3. Validate tick history and testing mode.
  4. Verify all required symbols are selected and synchronized.
  5. Log terminal, EA, account, and symbol trade permissions.
  6. Log every order result and server retcode.
  7. Test OnTick, timer, and bar-close assumptions.
  8. Validate in tester, demo, then at minimum practical live size.

1. Confirm initialization before examining entries

Start with OnInit(). Print chart symbol, timeframe, account server, magic number, configured symbols, and every input affecting execution. A failed include, invalid input, expired licence check, or early return can make an EA look inert while trade logic never runs.

Confirm Algo Trading is enabled globally and chart-level EA trading permission is enabled. The MQL5 trade-permission reference distinguishes terminal, program, account, and symbol-level checks. One green button does not overrule every other gate. Trading terminals enjoy gates. It gives them something to do on Friday evening.

Print a heartbeat from OnTick() and, if used, OnTimer(). One log line separates “no price event arrived” from “price event arrived but no entry qualified.”

2. Compare account and symbol identity, not only visible chart name

EURUSD in a backtest may become EURUSD.a, EURUSDm, or another broker-specific symbol on the deployment account. Gold reveals this quickly: XAUUSD may have different volume limits, stops distance, contract size, or trading sessions between accounts.

Check these items against the testing environment:

  • Exact symbol string and suffix
  • Digits and point size
  • Minimum, maximum, and step volume
  • Stop-distance and freeze-level rules
  • Trade mode: full, long-only, short-only, close-only, or disabled
  • Filling mode and order expiration rules
  • Contract size, tick size, tick value, leverage, and margin model
  • Netting versus hedging account mode

Use SymbolInfoInteger, SymbolInfoDouble, and SymbolInfoString to log symbol properties. The MQL5 Symbol Properties reference lists the full property set.

A “0.10 lots” signal can fail because the live symbol accepts increments of 0.01, 0.1, or 1.0. A stop loss can fail because current SYMBOL_TRADE_STOPS_LEVEL places it too close. These are contract mismatches, not strategy failures.

3. Audit real ticks before trusting optimisation

A test built on unsuitable data can produce entries impossible under the intended price path. MT5 offers generated-tick modes and real-tick mode. Use real ticks for execution-sensitive logic, especially stops, break-even moves, scalping thresholds, or intrabar indicators.

An MT5 real ticks backtest does not reproduce future execution. It forces the EA through recorded bid/ask movement instead of a simplified path. That better tests conditions dependent on tick order.

Audit before optimising:

  • Testing mode selected for each run
  • Real-tick availability across the test period
  • Spread treatment and sensitivity
  • Missing history or suspiciously short periods
  • Symbol specification used at test time
  • Whether the EA reads bid, ask, last price, or bar values

The tester documentation also notes multi-currency requirements: required symbols must be selected in Market Watch and data loaded. Historical data has gaps often enough to ruin a beautiful theory. It has never felt guilty about it.

4. Make multi-symbol dependencies explicit

OnTick() fires only when the chart symbol receives a new quote. The MQL5 OnTick documentation states that a NewTick event belongs to the EA’s attached chart symbol, not every symbol it reads.

An EA attached to EUR/USD can inspect XAU/USD, but an XAU/USD tick does not directly call that EA’s OnTick(). If the strategy expects gold to trigger an immediate EUR/USD decision, use deliberate scheduling: timer polling, chart attachment strategy, or state carried to the next chart-symbol event.

For each external symbol:

  1. Call SymbolSelect(symbol, true) during initialization.
  2. Confirm selection and synchronization.
  3. Confirm a current tick exists before calculations.
  4. Log failed reads with GetLastError().
  5. Use the same broker symbol names in inputs and code.

The Strategy Tester can synchronize a foreign symbol after first access, but live readiness remains a terminal-state question. A string in an input field does not mean price data is ready.

5. Log permissions and server return codes

A Boolean from a CTrade method only shows that its local request stage passed. Inspect final server results through ResultRetcode() and ResultRetcodeDescription() after every request. The CTrade reference documents both accessors; the trade-server return-code list names outcomes.

Add a narrow diagnostic wrapper before blaming the entry rule:

#include <Trade/Trade.mqh>

CTrade trade;

void LogTradeEnvironment(const string symbol)
{
   ResetLastError();
   bool selected = SymbolSelect(symbol, true);

   PrintFormat(
      "env symbol=%s selected=%s sync=%s symbol_trade_mode=%d "
      "terminal_trade=%s ea_trade=%s account_ea=%s err=%d",
      symbol,
      selected ? "true" : "false",
      SymbolIsSynchronized(symbol) ? "true" : "false",
      (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE),
      TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) ? "true" : "false",
      MQLInfoInteger(MQL_TRADE_ALLOWED) ? "true" : "false",
      AccountInfoInteger(ACCOUNT_TRADE_EXPERT) ? "true" : "false",
      GetLastError()
   );
}

bool BuyWithDiagnostics(const string symbol, const double volume)
{
   ResetLastError();
   bool accepted = trade.Buy(volume, symbol);

   PrintFormat(
      "buy symbol=%s volume=%.2f accepted=%s retcode=%u reason=%s err=%d",
      symbol,
      volume,
      accepted ? "true" : "false",
      trade.ResultRetcode(),
      trade.ResultRetcodeDescription(),
      GetLastError()
   );

   return accepted;
}

The permission calls use documented terminal, MQL program, account, and symbol state. A nonzero retcode gives the next action:

  • TRADE_RETCODE_TRADE_DISABLED: inspect terminal, EA, account, or symbol permission.
  • TRADE_RETCODE_MARKET_CLOSED: inspect trading session and test timing.
  • TRADE_RETCODE_INVALID_VOLUME: align volume with symbol minimum and step.
  • TRADE_RETCODE_INVALID_STOPS: respect current stops level.
  • TRADE_RETCODE_NO_MONEY: examine margin, contract size, and account settings.
  • TRADE_RETCODE_REQUOTE, TRADE_RETCODE_PRICE_CHANGED, or TRADE_RETCODE_TIMEOUT: inspect price handling, deviation, and retry policy.

6. Guard timing, bars, and event queues

Bar-close logic fails live when code treats a forming bar as closed. Tester speed can hide this because the EA processes a known historical sequence without startup gaps or quiet-market waits.

Two-panel candlestick comparison showing a false forming-bar signal versus completed-bar confirmation
Two-panel candlestick comparison showing a false forming-bar signal versus completed-bar confirmation

Use a new-bar guard based on the current bar’s opening time. Read indicator values from the completed bar where strategy rules require it. Log bar timestamp, signal value, and decision. A log line with shift=0 versus shift=1 settles arguments faster than a fresh optimisation run.

OnTick() does not queue every tick indefinitely. MQL5 documents that if a NewTick event is queued or being processed, another NewTick event is not added to the application queue. Keep OnTick() short. Move slow scans, file work, and broad symbol polling out of the price-critical path.

Timers need the same discipline. OnTimer runs after EventSetTimer() or EventSetMillisecondTimer() creates a timer. Log timer creation in OnInit(), clear it with EventKillTimer() in OnDeinit(), and do not assume a timer compensates for absent symbol data.

Use staged validation instead of a single leap

We use three environments because each answers a different question.

  1. Strategy Tester: Does logic behave across enough market regimes, with real ticks where execution path matters?
  2. Demo account: Does the EA initialize, receive data, calculate, submit requests, and handle current contract rules?
  3. Small live deployment: Does behaviour remain observable under real account permissions and execution conditions?

The live stage is operational validation, not a performance certificate. Keep volume small enough that a logging error costs embarrassment, not a risk meeting with ourselves.

Monitor from the first deployed order. Our MT5 backtesting workflow covers testing foundations. After deployment, monitor EA performance after deployment to track equity and trade behaviour under live conditions. Equity Tracker for MT5 records what opened, what closed, and what equity did while the Journal accumulated evidence.

The deployment rule

A tester result proves the EA survived that test. It does not prove live readiness.

Log symbol identity, contract properties, permissions, current data, entry decisions, and server retcodes before changing strategy logic. The first rejected trade or missing tick usually explains more than another thousand optimisation passes.

Peace of MindTrade with confidence
60-Day RefundOn every paid product
Secure PaymentPayPal · MQL5
Real Support< 2h reply, 6 languages