Run the same Expert Advisor on the same symbol and dates in two modes of the MetaTrader 5 Strategy Tester and you can get two different reports. Usually neither is broken. They simulate different things, and the difference tells you how much your EA depends on what happens inside a bar.
This guide goes through what each mode feeds your EA, according to MetaQuotes' own help pages, where spread and execution delays come in, and an EA written so the choice of mode doesn't change its signals. Then a routine that uses the fast modes for development and the slow ones for the verdict.
The five modes
| Mode | What the tester feeds the EA | Speed | Use it for |
|---|---|---|---|
| Every tick based on real ticks | The ticks your broker recorded, with the real bid and ask | Slowest, and the tick history is downloaded first | Final checks, scalpers, anything sensitive to spread or to the order of prices inside a bar |
| Every tick | Ticks generated from minute bars | Slow | EAs that manage trades inside the bar when real ticks aren't available |
| 1 minute OHLC | The open, high, low and close of each minute bar | Fast | Developing and optimizing EAs that don't need intrabar detail |
| Open prices only | The bars of the testing timeframe; OnTick() runs at each bar's open only | Fastest | EAs that act only on a new bar, and first rough optimizations |
| Math calculations | No market data; only OnInit(), OnTester() and OnDeinit() run | Instant | Pure calculations |
How MT5 builds ticks from minute bars
In the generated modes, the tester starts from minute bars, not from ticks. The help page on tick generation describes the method:
- A minute bar with a tick volume of 1 becomes one tick at the close. With a volume of 2, a tick at the open and then one at the close.
- With 3 or more, the tester places reference points (at most 11, not counting the open) along the shadows and the body, and fills the gaps between them with intermediate ticks.
- In 1 minute OHLC, a bar with enough volume becomes just its four prices.
- The Ask is the Bid plus the spread stored with that minute bar, so the spread is fixed inside each minute.
The real-tick mode replays what the broker recorded, spread changes included. Where a minute has no tick data, the tester generates ticks for it as in Every tick. Ticks that fall in a minute missing from the minute history are dropped, because MT5 treats minute data as the more reliable of the two.
The consequence: in the generated modes, an EA that reacts inside the minute (a scalper, a tight trailing stop, a stop and a target that both sit inside one minute bar) trades on a price path MT5 constructed, not the one that happened.
Spread is where backtests and live accounts disagree most
With one spread value per minute bar, the spikes you see at news releases or at the daily rollover mostly don't exist in a generated test. With real ticks they do, as far as your broker recorded them.
What a few points of spread are worth depends on the stop. Using the break-even formula from the trading fee calculator:
| Stop / target (points) | Extra cost | Win rate needed, no cost | With the extra cost |
|---|---|---|---|
| 300 / 600 | 10 points | 33.3% | 34.4% |
| 300 / 600 | 20 points | 33.3% | 35.6% |
| 50 / 75 | 10 points | 40.0% | 48.0% |
A swing EA barely notices 10 points. A scalper with a 50-point stop needs to win 8 percentage points more often to stand still.
Two ways to make a test stricter about spread:
- Filter in the EA. Read
SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)before entering and skip signals when the spread is wider than you'd accept, as the EA below does. Run it in real-tick mode and the journal shows how many signals the filter skipped. - Charge more. The tester's advanced settings have a Commission section where fees can be set per lot or per deal, in money, percent or points. Adding a few points per lot is a blunt but honest way to see whether the EA survives a wider spread than the history shows.
The settings tab itself has no spread field.
Execution delays
The tester can delay trade requests to imitate the time between your terminal and the broker:
- No delay: orders fill at the requested price, with no requotes. This is the default and the most flattering.
- Random delay: the help page describes a random delay of 0 to 9 seconds, with a smaller chance of 9 to 18 seconds. The price can move in the meantime.
- Fixed delay: a value you choose, or the ping the terminal measured to your broker's server.
Delays apply only to trades the EA sends, and Random delay can't be combined with Open prices only. A strategy that only works with no delay is a strategy that needs perfect fills.
Make the EA give the same signals in every mode
In Open prices only, OnTick() runs once per bar, at the open, and stops or pending orders can trigger at prices other than they would have in reality. An EA that reads the bar still forming, or checks conditions on every tick, will give different signals from one mode to the next.
The fix is a pattern: act on the first tick of a new bar, and read only closed bars (bar 1 and bar 2). Signals are then the same in every mode, and only the fills of stops and targets change. That lets you optimize quickly in a fast mode and confirm in real ticks without the logic itself moving.
#include <Trade/Trade.mqh>
input int FastPeriod = 20; // fast EMA
input int SlowPeriod = 50; // slow EMA
input double Lots = 0.10;
input int StopPoints = 300; // stop-loss distance in points
input int TakePoints = 600; // take-profit distance in points
input int MaxSpread = 20; // skip entries when the spread is wider (points)
input int MinTrades = 100; // OnTester scores 0 below this many trades
input ulong Magic = 260111;
CTrade trade;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
datetime lastBar = 0;
long spreadSum = 0;
int spreadCount = 0;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
trade.SetExpertMagicNumber(Magic);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(spreadCount > 0)
PrintFormat("Average spread at entry: %.1f points over %d entries", (double)spreadSum / spreadCount, spreadCount);
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
}
// True on the first tick of a new bar: the EA then acts the same way in every tick mode.
bool IsNewBar()
{
datetime t = iTime(_Symbol, _Period, 0);
if(t == lastBar)
return false;
lastBar = t;
return true;
}
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == (long)Magic)
return true;
}
return false;
}
void OnTick()
{
if(!IsNewBar())
return;
// Signals use closed bars only (bar 1 and bar 2), never the bar still forming.
double fast[], slow[];
if(CopyBuffer(fastHandle, 0, 1, 2, fast) != 2 || CopyBuffer(slowHandle, 0, 1, 2, slow) != 2)
return;
bool crossUp = fast[0] <= slow[0] && fast[1] > slow[1]; // [0] = bar 2, [1] = bar 1
bool crossDown = fast[0] >= slow[0] && fast[1] < slow[1];
if(HasPosition() || (!crossUp && !crossDown))
return;
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(spread > MaxSpread)
{
PrintFormat("Signal skipped: spread %d points is above %d", (int)spread, MaxSpread);
return;
}
spreadSum += spread;
spreadCount++;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(crossUp)
trade.Buy(Lots, _Symbol, ask, NormalizeDouble(ask - StopPoints * _Point, _Digits), NormalizeDouble(ask + TakePoints * _Point, _Digits), "EMA cross long");
else
trade.Sell(Lots, _Symbol, bid, NormalizeDouble(bid + StopPoints * _Point, _Digits), NormalizeDouble(bid - TakePoints * _Point, _Digits), "EMA cross short");
}
// Custom optimization criterion: profit factor, but only with enough trades to mean something.
double OnTester()
{
if(TesterStatistics(STAT_TRADES) < MinTrades)
return 0.0;
return TesterStatistics(STAT_PROFIT_FACTOR);
}
What each part is for:
- IsNewBar() keeps the EA to one decision per bar.
- CopyBuffer(handle, 0, 1, 2, …) copies bars 1 and 2, never bar 0. The arrays aren't set as series, so index 0 holds the older bar.
- MaxSpread skips signals when the spread is too wide, and OnDeinit() prints the average spread at entry. Compare it between modes: if real ticks show a much wider average than Every tick, the generated test was too kind.
- OnTester() returns the profit factor only when there were at least MinTrades trades. Pick "Custom max" as the optimization criterion and the optimizer can't crown a setting that made four lucky trades.
The EA compiles in MetaEditor with 0 errors and 0 warnings. It's a test harness for the ideas in this guide, not a strategy to trade.
Why live results still differ
| Cause | In the tester | Live |
|---|---|---|
| Spread | One value per minute (generated modes) or the recorded ticks | Widens at news, at rollover and in thin markets |
| Fills | At the requested price unless you add delays | Market orders can fill worse; a stop becomes a market order once triggered |
| Latency | Zero unless you set a delay | Your PC or VPS to the broker's server, plus the broker's own processing |
| Price history | Your broker's history for that symbol | Your live broker's feed, which another broker's history doesn't match |
| Server time | Daily bars and session filters follow the broker's server time | Same, so a change of broker can shift every daily bar |
| Stop and freeze levels | Taken from the symbol's settings | Enforced by the broker, which can reject orders too close to the price |
| Downtime | None | Restarts, disconnections, VPS outages |
A testing routine
- Develop and optimize in 1 minute OHLC, or Open prices only if the EA acts on new bars only.
- Set Forward to 1/3 so the optimizer's favorite is checked on the most recent third of the period, which it never saw. Our guide to walk-forward analysis explains why this matters so much.
- Re-run the chosen settings in Every tick based on real ticks.
- Run it again with Random delay, and once more with extra commission in points to imitate a wider spread.
- If it survives all of that, forward-test on a demo account for several weeks.
- Count how many settings you tried and check the best result against luck with the deflated Sharpe ratio calculator.
For more checks before going live, see the MQL5 Expert Advisor risk checklist, and how to install an Expert Advisor on MT5 if you're starting out.
Sources
- MetaTrader 5 Help: Tick generation modes: how ticks are generated from minute bars, the fixed spread per minute bar, real ticks.
- MetaTrader 5 Help: Strategy Tester settings: modes, execution delays, the Forward option, commission settings.
- MQL5 Reference: TesterStatistics() and the OnTester() event handler.
Build an EA that tests cleanly
Describe your strategy in plain English and Edgoro's MT5 Expert Advisor generator writes the MQL5 with a magic number, risk inputs and new-bar logic, ready for the Strategy Tester.
Backtests describe the past under simplified conditions. Nothing here is investment advice, and past or simulated results don't guarantee future returns. Trading involves substantial risk of loss.
Frequently asked questions
Which MT5 modelling mode is the most accurate?
Every tick based on real ticks, because it replays the ticks your broker recorded, with their real bid, ask and spread. It is also the slowest. Minutes with no tick data are filled in with generated ticks.
What is the difference between Every tick and 1 minute OHLC?
Every tick generates many ticks inside each minute bar from up to 11 reference points. 1 minute OHLC uses only the open, high, low and close of each minute. Both use one spread value per minute bar.
Can I set the spread in the MT5 Strategy Tester?
The settings tab has no spread field. In the generated modes the spread comes from each minute bar of the history; in real-tick mode it is the recorded spread. To test a wider spread, add a commission in points in the advanced settings or filter entries by spread in the EA.
Why are my MT5 backtest results different from live trading?
The usual causes are spread that widens live, slippage and delays the test didn't include, a different broker's price history, server time differences and stop levels enforced by the broker. Test in real-tick mode with delays and extra costs to narrow the gap.
Why does Open prices only give different results?
In that mode OnTick() runs only at each bar's open, and stops or pending orders can trigger at different prices. An EA that acts only on a new bar and reads closed bars gives the same signals in every mode; only the fills change.