A practical, balanced comparison of MQL4 and MQL5 for building an Expert Advisor: the trade APIs, hedging vs netting, backtesting, platform availability, and migration. This is educational material only, not financial advice; automated trading carries a real risk of loss, and any code here should be studied and tested on a demo account before it goes anywhere near a live one.
If you are starting a new Expert Advisor today, the mql4 vs mql5 decision usually resolves in favour of MQL5. It is the language MetaQuotes actively develops, MT5 is what most brokers now push new clients toward, and the newer platform gives you a multi-threaded strategy tester, more timeframes, an economic calendar, and a proper object-oriented standard library. But "usually" is not "always," and picking the wrong one for your situation costs you weeks of rework.
The honest framing is this: MQL4 and MQL5 are close cousins, not the same language with a version bump. They share C-like syntax, the same event handlers (OnInit, OnTick, OnDeinit), and most indicator functions. Where they diverge sharply is the trade layer and the position model — and that is exactly the part of an EA that carries risk. A misunderstanding there is how people end up with orders that have no stop-loss or fills they never checked.
This article compares the two on the things that actually change how you write and test an EA. To be clear up front: neither language makes a strategy profitable. The language is plumbing. A well-coded EA in either dialect can still lose money, and no amount of clean syntax substitutes for a tested edge and disciplined risk control. Treat everything below as a way to choose your tools, not as a promise about results.
This is the biggest practical difference. In MQL4, you send orders through a single positional function, OrderSend(), using operation constants like OP_BUY and OP_SELL. You inspect open orders by looping with OrderSelect() and reading OrderType(), OrderStopLoss(), and so on. It is compact but easy to get wrong, because every parameter is positional — get the argument order wrong and you can silently pass a price where a lot size belongs.
Here is a minimally correct MQL4 buy: it gates on a new bar, reads the signal from the last closed bar (shift 1), checks there is no existing position for its magic number, sizes from an input, sets a real stop-loss, and verifies the result.
4
// MQL4 — new-bar gated buy with a real stop and result check
input double InpLots = 0.10;
input int InpStopPts = 400; // stop distance in points
input int InpMagic = 20240517;
datetime g_lastBar = 0;
void OnTick()
{
datetime barTime = iTime(_Symbol, _Period, 0);
if(barTime == g_lastBar) return; // one action per bar
g_lastBar = barTime;
// read a CONFIRMED signal from the last closed bar (shift 1)
double maFast = iMA(_Symbol,_Period,10,0,MODE_EMA,PRICE_CLOSE,1);
double maSlow = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE,1);
bool wantBuy = (maFast > maSlow);
// check for an existing position with our magic
bool haveOrder = false;
for(int i = OrdersTotal()-1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderSymbol()==_Symbol && OrderMagicNumber()==InpMagic)
haveOrder = true;
}
if(wantBuy && !haveOrder)
{
double ask = Ask;
double sl = ask - InpStopPts * _Point; // non-zero stop-loss
int ticket = OrderSend(_Symbol, OP_BUY, InpLots, ask, 10,
sl, 0, "emaCross", InpMagic, 0, clrBlue);
if(ticket < 0)
Print("OrderSend failed, error = ", GetLastError());
}
}MQL5 replaces the positional call with a request struct, MqlTradeRequest, filled field-by-field and sent via OrderSend() — or, far more commonly, through the CTrade helper class from the standard library. Order types are named constants (ORDER_TYPE_BUY, ORDER_TYPE_SELL), and you confirm a fill by checking the return code equals TRADE_RETCODE_DONE. The field-named approach is more verbose but much harder to fumble, because nothing is positional. We'll show a full CTrade version in the next section.
MQL4 is effectively a hedging model: every OrderSend creates its own independent order (ticket), and you can hold a long and a short on the same symbol at the same time. Many MQL4 strategies quietly rely on this — grids, partial scale-outs, and "buy and sell simultaneously" constructions all assume orders don't net against each other.
MQL5 supports both netting and hedging, but the account is configured one way or the other, and the default for many broker account types is netting. Under netting there is at most one net position per symbol: buy 1 lot then buy another and you hold 2 lots as a single position; buy 1 lot then sell 1 lot and you are flat. This is the single most common thing that breaks a naive MQL4-to-MQL5 port — logic that expected two separate tickets suddenly collapses into one net position, and stop-loss / take-profit bookkeeping that tracked individual orders no longer maps.
In MQL5 you also stop talking about "orders" as the live thing and start talking about positions. You check exposure with PositionSelect() / PositionsTotal() and read PositionGetDouble(POSITION_SL), not OrderStopLoss(). Here is the MQL5 equivalent of the earlier EA using CTrade, correct on every safety point. Note that MQL5 indicator handles are created once in OnInit() — creating them on every tick or bar leaks handles and is a common porting mistake:
5
// MQL5 — CTrade, new-bar gated, closed-bar signal, verified result
#include <Trade\Trade.mqh>
input double InpLots = 0.10;
input int InpStopPts = 400;
input ulong InpMagic = 20240517;
CTrade trade;
datetime g_lastBar = 0;
int g_hFast = INVALID_HANDLE;
int g_hSlow = INVALID_HANDLE;
int OnInit()
{
trade.SetExpertMagicNumber(InpMagic); // unique, non-zero magic
// create indicator handles ONCE, not per tick
g_hFast = iMA(_Symbol,_Period,10,0,MODE_EMA,PRICE_CLOSE);
g_hSlow = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE);
if(g_hFast == INVALID_HANDLE || g_hSlow == INVALID_HANDLE)
{
Print("iMA handle creation failed, error = ", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
if(g_hFast != INVALID_HANDLE) IndicatorRelease(g_hFast);
if(g_hSlow != INVALID_HANDLE) IndicatorRelease(g_hSlow);
}
void OnTick()
{
datetime t = iTime(_Symbol, _Period, 0);
if(t == g_lastBar) return; // new bar only
g_lastBar = t;
// confirmed signal from the last CLOSED bar (shift 1)
double maFast[1], maSlow[1];
if(CopyBuffer(g_hFast,0,1,1,maFast) < 1) return;
if(CopyBuffer(g_hSlow,0,1,1,maSlow) < 1) return;
bool wantBuy = (maFast[0] > maSlow[0]);
// existing position for THIS symbol + magic?
bool havePos = false;
if(PositionSelect(_Symbol))
if(PositionGetInteger(POSITION_MAGIC) == (long)InpMagic)
havePos = true;
if(wantBuy && !havePos)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = ask - InpStopPts * _Point; // real, non-zero stop
if(!trade.Buy(InpLots, _Symbol, ask, sl, 0.0, "emaCross"))
Print("Buy() send failed, retcode = ", trade.ResultRetcode());
else if(trade.ResultRetcode() != TRADE_RETCODE_DONE)
Print("Not filled, retcode = ", trade.ResultRetcode());
}
}Notice these two examples are labelled and kept strictly in one dialect each — you should never mix OP_BUY / OrderSelect (MQL4) with CTrade / PositionSelect / CopyBuffer (MQL5) in the same file. If you find yourself doing that, something has gone wrong in a port.
The strategy tester is a real reason to prefer MT5. The MT4 tester is single-threaded and single-symbol, and its modelling of ticks inside a bar is limited. The MT5 tester is multi-threaded, supports multi-symbol and multi-currency testing, offers a true tick-by-tick mode ("Every tick based on real ticks" when your broker provides that data), and includes a genetic optimiser that parallelises across cores. If your strategy touches more than one instrument, or you care about realistic intrabar fills, MT5's tester is a meaningful upgrade.
A caution that people learn the hard way: a good backtest is not evidence of future profit. Optimisation can fit noise, spreads and slippage in the tester rarely match live conditions, and real-tick data quality varies by broker. Use the tester to check that your logic behaves as designed and to stress-test risk — not to chase the highest equity curve. Then forward-test on a demo account before risking real capital. Automated systems can and do lose money in live markets even after a clean backtest.
On the indicator side, the languages differ in shape more than capability. MQL4 indicator calls return a value directly (iMA(...,shift)), which reads cleanly. MQL5 uses a two-step handle pattern: create a handle once (iMA(...) returns an int handle, ideally in OnInit), then CopyBuffer() the values you want into an array — as shown above. It is more code, but it separates indicator setup from per-tick reads and plays better with the newer engine. Both give you the standard toolkit (MA, RSI, MACD, Bands, custom indicators); the ergonomics just differ.
MT5 also bundles extras MT4 lacks natively: depth of market, an economic calendar accessible from code, more built-in timeframes, and partial support for exchange-traded instruments. Whether those matter depends entirely on what you are building. For a plain FX trend EA, you may never touch them.
Availability should weigh heavily. Ask your actual broker which platform they support, and — for MT5 — whether the account you'd open is netting or hedging, because that changes how you write the EA. MetaQuotes has steered new business to MT5 for years, and some brokers no longer offer new MT4 accounts at all; others still run large MT4 books. There is also a large existing ecosystem of MQL4 code and community indicators, which is a genuine reason people stay on MT4 even now.
Migration is a rewrite, not a recompile. You cannot compile MQL4 source in MetaEditor for MT5 and expect it to work — the trade calls, the position model, and the indicator access are all different. Budget for reworking the order layer (OrderSend positional → CTrade/MqlTradeRequest), rethinking any hedging-dependent logic for a possible netting account, and converting every indicator call to the handle-plus-CopyBuffer pattern (creating handles in OnInit, not per tick). Re-verify stops and fill checks line by line; that is where silent bugs hide. If you have an audited, working MQL4 EA and no need for MT5's features, a stable system you understand can be worth more than a risky port.
A simple decision guide:
If you do port between the two, the highest-risk lines are the order-send calls and the stop-loss handling. Reviewing them carefully — by hand, or with a checker such as the ForexCodes EA Auditor or its Pine→MQL / English→MQL tools — is worth the time, because a mistake there is the kind that only shows up on a live account. Whichever language you choose, keep the safety basics non-negotiable: a real stop on every order, a verified fill, one action per bar, signals read from closed bars, and a unique magic number.
Educational content only, not financial advice. Trading carries a genuine risk of loss; validate any EA thoroughly on a demo account before considering live capital.
Educational only — not financial advice. Trading involves substantial risk of loss.