Edgoro
Platforms

MT5 Strategy Tester modelling modes: every tick, real ticks, 1-minute OHLC, and why backtests differ from live

What each MetaTrader 5 tick mode simulates, how spread and delays enter the test, and an EA pattern that gives the same signals in every mode. With MQL5 code.

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

ModeWhat the tester feeds the EASpeedUse it for
Every tick based on real ticksThe ticks your broker recorded, with the real bid and askSlowest, and the tick history is downloaded firstFinal checks, scalpers, anything sensitive to spread or to the order of prices inside a bar
Every tickTicks generated from minute barsSlowEAs that manage trades inside the bar when real ticks aren't available
1 minute OHLCThe open, high, low and close of each minute barFastDeveloping and optimizing EAs that don't need intrabar detail
Open prices onlyThe bars of the testing timeframe; OnTick() runs at each bar's open onlyFastestEAs that act only on a new bar, and first rough optimizations
Math calculationsNo market data; only OnInit(), OnTester() and OnDeinit() runInstantPure 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 costWin rate needed, no costWith the extra cost
300 / 60010 points33.3%34.4%
300 / 60020 points33.3%35.6%
50 / 7510 points40.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:

  1. 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.
  2. 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

CauseIn the testerLive
SpreadOne value per minute (generated modes) or the recorded ticksWidens at news, at rollover and in thin markets
FillsAt the requested price unless you add delaysMarket orders can fill worse; a stop becomes a market order once triggered
LatencyZero unless you set a delayYour PC or VPS to the broker's server, plus the broker's own processing
Price historyYour broker's history for that symbolYour live broker's feed, which another broker's history doesn't match
Server timeDaily bars and session filters follow the broker's server timeSame, so a change of broker can shift every daily bar
Stop and freeze levelsTaken from the symbol's settingsEnforced by the broker, which can reject orders too close to the price
DowntimeNoneRestarts, disconnections, VPS outages

A testing routine

  1. Develop and optimize in 1 minute OHLC, or Open prices only if the EA acts on new bars only.
  2. 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.
  3. Re-run the chosen settings in Every tick based on real ticks.
  4. Run it again with Random delay, and once more with extra commission in points to imitate a wider spread.
  5. If it survives all of that, forward-test on a demo account for several weeks.
  6. 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

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.

Build it with Edgoro

Describe your strategy in plain English and get a TradingView indicator in Pine Script v6 or a MetaTrader 5 Expert Advisor in minutes. Every first purchase is covered by a 7-day money-back guarantee.

Build your indicator with Edgoro