← Back to Blog
Still on MT4? What Actually Changes When You Port an EA to MT5

Still on MT4? What Actually Changes When You Port an EA to MT5

An MQL4 expert advisor won't run on MetaTrader 5. What a port actually changes: orders vs positions, hedging vs netting accounts, indicator handles and CopyBuffer, and how to prove the port trade by trade in the Strategy Tester.

An MQL4 expert advisor won't run on MT5. It has to be ported, and porting is closer to rewiring the trading layer than a find-and-replace job. It's a bit like putting a Honda engine into a Ford pickup. It's worth raising now because the brokers keep moving. MetaQuotes published a case study on 7 October saying more than 60% of Axi's customers are now on MetaTrader 5. The good news is that most of the change is in how the code talks to orders and indicators, not in the strategy itself.


Why brokers are moving

Axi's stated reasons are almost all on the broker's side of the fence. The case study lists finer-grained access management, more automation of routine admin, flexibility in running its access servers, regional infrastructure, and resilience and disaster recovery. Owais Anwer, Axi's Global Head of Technology Operations, puts it as needing "technology that can scale without creating unnecessary operational complexity".

Note that this is a MetaQuotes press release about a MetaQuotes customer, so read it as one. It gives no timeline for the migration and no measured results, just the headline figure and the reasons.

None of those reasons are about your EA. But when a broker's clients are mostly on MT5, the MT4 side gets less attention over time, and sooner or later the question becomes whether your EA comes along. So here's what a port actually involves.


Orders, positions and deals

This is the big one, and it's where most ports go wrong.

In MT4 everything is an order. Open trades and pending orders sit in one pool, and you walk it with OrdersTotal() and OrderSelect(). A buy is an order with a ticket number, and you close it by closing that ticket.

MT5 splits this into three things:

  • Orders are requests to trade: a market order on its way, or a pending order waiting at a price.
  • Deals are the fills that actually happened.
  • Positions are what you're holding as a result.

So "how many trades am I in?" means looking at positions, not orders. An MQL4 loop like this:

for(int i = OrdersTotal() - 1; i >= 0; i--)
{
   if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
   if(OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
   // manage the trade
}

becomes something like this in MQL5:

for(int i = PositionsTotal() - 1; i >= 0; i--)
{
   ulong ticket = PositionGetTicket(i);
   if(ticket == 0) continue;
   if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
   if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
   // manage the position
}

Pending orders now live in a separate list, walked with OrdersTotal() and OrderGetTicket(). Code that used to find both in one loop needs two.

Hedging vs netting accounts

MT5 accounts come in two flavours, and your EA has to know which one it's on.

On a hedging account you can hold a long and a short on the same symbol at once, as separate positions. That's the behaviour MT4 traders are used to.

On a netting account there is only ever one position per symbol. Buy 0.10 lots while you're short 0.10 and you don't get a second trade. You get flat. An EA written for MT4 that opens a hedge, or stacks several entries and closes them individually, will do something quite different on a netting account, and it won't throw an error to tell you.

So check it at startup:

int OnInit()
{
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      Print("This EA expects a hedging account.");
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

Better to refuse to start than to quietly trade a different strategy.

Sending orders

OrderSend() in MQL5 takes a request structure rather than a list of arguments, and the result comes back in a second structure. You can fill those in by hand, but for most EAs the standard CTrade class is less fiddly:

#include <Trade\Trade.mqh>
CTrade trade;

// in OnInit
trade.SetExpertMagicNumber(MagicNumber);

// when the entry condition is true
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(!trade.Buy(Lots, _Symbol, ask, sl, tp, "EMA cross"))
   Print("Buy failed: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());

Note that Ask, Bid, Close[] and Time[] don't exist in MQL5. Prices come from SymbolInfoDouble() or SymbolInfoTick(), and bar data from functions like iClose() or CopyRates(). A surprising amount of porting is just chasing those down.


Indicators are handles now

In MQL4, calling an indicator function gives you a value straight away:

double fastEMA = iMA(NULL, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 1);

In MQL5 the same function gives you a handle, created once, and you copy values out of it when you need them:

int fastHandle = INVALID_HANDLE;

int OnInit()
{
   fastHandle = iMA(_Symbol, _Period, 10, 0, MODE_EMA, PRICE_CLOSE);
   if(fastHandle == INVALID_HANDLE) return INIT_FAILED;
   return INIT_SUCCEEDED;
}

void OnTick()
{
   double buf[];
   if(CopyBuffer(fastHandle, 0, 1, 1, buf) != 1) return; // data not ready yet
   double fastEMA = buf[0]; // value on the last closed bar
}

Two gotchas here.

Create the handle once, in OnInit(). Calling iMA() on every tick, the way MQL4 code does, creates handles you never release. It works for a while, then it doesn't.

Watch the indexing. In MT4, bar 0 is the current bar and bar 1 is the one before, always. In MQL5, arrays you copy into are indexed oldest-first unless you call ArraySetAsSeries(buf, true). Copy three bars without setting that and buf[0] is the oldest of the three, not the newest. That one flips every crossover test in the EA. Hey presto, a strategy that goes long when it should go short, and compiles perfectly.

Also check the return value of CopyBuffer(). Early in a test, or just after a timeframe change, the indicator may not have calculated yet. CopyBuffer() tells you it couldn't copy, and the EA should wait a tick rather than trade on nothing.


Testing the port

A port isn't finished when it compiles. It's finished when it takes the same trades for the same reasons.

The MT5 Strategy Tester has a mode called "Every tick based on real ticks", which uses the broker's recorded tick history where they supply it. MT4's "Every tick" mode generates ticks from minute data instead. So even a perfect port will show some differences between the two testers, because the price path inside each bar isn't the same.

That's why comparing net profit between the old and new versions isn't a useful test on its own. The numbers will differ, and you won't know whether it's the code or the data. Compare individual trades instead.

  1. Run both versions over the same short period, a few weeks, on the same symbol and timeframe.
  2. Export or list the trades from each. Line them up by entry time.
  3. For every trade that's in one list and not the other, find out why. Was the signal true on that bar in both? Did one version see a different price for the stop?
  4. Check the logs, not just the report. Print the indicator values and the decision at each signal while you're testing. You can strip it back out later.
  5. Test on the account type you'll trade. If the live account is netting, test on netting. If it's hedging, test on hedging.

If the trades match and the leftover differences trace back to tick data, the port is right. If a trade is missing and you can't say why, something in the trading layer is still MQL4-shaped.


What you get once you're there

Once the EA is on MT5, there's more available than MT4 will ever offer. The Strategy Tester is built to test multi-symbol EAs, which MT4's tester never really was. MetaQuotes has also been adding machine-learning plumbing to MQL5. Build 5800 expanded CUDA support for ONNX models across recent NVIDIA GPU generations and added OpenBLAS methods for L1 trend filtering. Build 6230, from 24 September, added support for importing .NET CoreCLR 8+ libraries into MQL5 programs, according to the release notes.

Note that none of that matters until the basic port is right. Get the orders, positions and indicator handles behaving first, then add new things.

Port the trading layer first, then prove it one trade at a time.


If you need expert help porting an MT4 EA to MT5, reach out to TradingCoders and tell us what you're building.