An Expert Advisor built for MetaTrader 4 won't run in MetaTrader 5. The compiled .ex4 file can't be loaded at all, and pasting the .mq4 source into MetaEditor for MT5 usually produces a long list of errors. The syntax is close, since both languages look like C++, but the way MT5 handles trades and indicator data is different enough that a port means rewriting the parts that touch the market.
This guide explains the three differences behind most of the errors, gives a conversion table for the functions you'll meet most, converts a complete EA, and lists the problems that compile fine and only show up in testing. The example EA is for learning the conversion, not a strategy recommendation.
Why MQL4 code doesn't compile in MT5
1. The trading model. In MT4, everything is an order: a pending order, and an open trade, which is a market order that filled. MT5 separates three things: orders are requests, deals are executions, and positions are the resulting exposure. The MQL4 loop over OrdersTotal() that finds your open trades becomes a loop over PositionsTotal().
MT5 accounts also come in two types. On a hedging account you can hold several positions on one symbol, as in MT4. On a netting account there is one position per symbol, and a sell order against a long position reduces, closes or reverses it.
2. Indicators. In MQL4, iMA(...) returns a number. In MQL5 it returns a handle, created once, and you copy values out of it with CopyBuffer().
3. Prices and bars. MQL4's predefined Bid, Ask, Close[], Time[], Bars, Point and Digits don't exist in MQL5. You get prices from SymbolInfoDouble() or SymbolInfoTick(), bar data from iClose(), iTime() or CopyRates(), and point size and digits from _Point and _Digits.
Conversion table
| MQL4 | MQL5 |
|---|---|
extern int MaPeriod = 14; | input int InpMaPeriod = 14; |
init(), start(), deinit() | OnInit(), OnTick(), OnDeinit() |
Bid, Ask | SymbolInfoDouble(_Symbol, SYMBOL_BID), SYMBOL_ASK |
Close[1], High[1], Time[0] | iClose(_Symbol, _Period, 1), iHigh(...), iTime(_Symbol, _Period, 0) |
Bars | Bars(_Symbol, _Period) |
Point, Digits | _Point, _Digits |
iMA(NULL, 0, 20, 0, MODE_EMA, PRICE_CLOSE, 1) | Handle from iMA(_Symbol, _Period, 20, 0, MODE_EMA, PRICE_CLOSE), then CopyBuffer() |
OrderSend(Symbol(), OP_BUY, ...) | CTrade::Buy(), or OrderSend() with MqlTradeRequest |
OrdersTotal() + OrderSelect(i, SELECT_BY_POS) | PositionsTotal() + PositionGetTicket(i) |
OrderMagicNumber(), OrderSymbol() | PositionGetInteger(POSITION_MAGIC), PositionGetString(POSITION_SYMBOL) |
OrderClose(ticket, lots, price, slippage) | CTrade::PositionClose(ticket) |
OrderModify(ticket, price, sl, tp, 0) | CTrade::PositionModify(ticket, sl, tp) |
OrderProfit() | PositionGetDouble(POSITION_PROFIT) |
OrdersHistoryTotal() | HistorySelect(from, to) + HistoryDealsTotal() |
MarketInfo(Symbol(), MODE_LOTSTEP) | SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP) |
MarketInfo(Symbol(), MODE_STOPLEVEL) | SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) |
AccountBalance(), AccountEquity() | AccountInfoDouble(ACCOUNT_BALANCE), ACCOUNT_EQUITY |
Hour(), DayOfWeek(), TimeHour(t) | TimeToStruct(t, dt), then dt.hour, dt.day_of_week |
IsTradeAllowed() | TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) and MQLInfoInteger(MQL_TRADE_ALLOWED) |
RefreshRates() | Not needed: read fresh prices when you need them |
GetLastError() after OrderSend | trade.ResultRetcode(), the trade server's return code |
Recent MQL4 builds already accept OnInit(), OnTick() and OnDeinit(), so many MT4 EAs use them. The function names are the easy part.
An MQL4 EA to convert
A typical MT4 EA: an EMA cross on closed bars, a fixed stop and target in points, trading only between two server hours, one trade at a time.
extern double Lots = 0.10;
extern int FastPeriod = 12;
extern int SlowPeriod = 26;
extern int StopPoints = 300;
extern int TakePoints = 600;
extern int StartHour = 8;
extern int EndHour = 20;
extern int Magic = 4505;
datetime lastBar = 0;
int CountOwnOrders()
{
int count = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
count++;
return count;
}
void OnTick()
{
if(Time[0] == lastBar) return;
lastBar = Time[0];
if(Hour() < StartHour || Hour() > EndHour) return;
double fast1 = iMA(NULL, 0, FastPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double fast2 = iMA(NULL, 0, FastPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
double slow1 = iMA(NULL, 0, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
double slow2 = iMA(NULL, 0, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE, 2);
if(CountOwnOrders() > 0) return;
if(fast1 > slow1 && fast2 <= slow2)
{
int t = OrderSend(Symbol(), OP_BUY, Lots, Ask, 10, Ask - StopPoints * Point, Ask + TakePoints * Point, "MA cross", Magic, 0, clrGreen);
if(t < 0) Print("Buy failed: ", GetLastError());
}
else if(fast1 < slow1 && fast2 >= slow2)
{
int t = OrderSend(Symbol(), OP_SELL, Lots, Bid, 10, Bid + StopPoints * Point, Bid - TakePoints * Point, "MA cross", Magic, 0, clrRed);
if(t < 0) Print("Sell failed: ", GetLastError());
}
}
The MQL5 version
#include <Trade/Trade.mqh>
input double InpLots = 0.10; // Lots
input int InpFastPeriod = 12; // Fast EMA
input int InpSlowPeriod = 26; // Slow EMA
input int InpStopPoints = 300; // Stop-loss (points)
input int InpTakePoints = 600; // Take-profit (points)
input int InpStartHour = 8; // First trading hour (server time)
input int InpEndHour = 20; // Last trading hour (server time)
input ulong InpMagic = 4505; // Magic number
CTrade trade;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, InpFastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, InpSlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
trade.SetExpertMagicNumber(InpMagic);
trade.SetDeviationInPoints(10);
trade.SetTypeFillingBySymbol(_Symbol);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
IndicatorRelease(fastHandle);
IndicatorRelease(slowHandle);
}
// MQL4: OrdersTotal() + OrderSelect(). MQL5: positions, filtered by symbol and magic number.
int CountOwnPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket != 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == (long)InpMagic)
count++;
}
return count;
}
void OnTick()
{
// MQL4: Time[0]. MQL5: iTime().
static datetime lastBar = 0;
datetime barTime = iTime(_Symbol, _Period, 0);
if(barTime == lastBar)
return;
lastBar = barTime;
// MQL4: Hour(). MQL5: TimeToStruct().
MqlDateTime now;
TimeToStruct(TimeCurrent(), now);
if(now.hour < InpStartHour || now.hour > InpEndHour)
return;
// MQL4: iMA(..., shift). MQL5: copy bars 1 and 2 from the handle.
double fast[], slow[];
ArraySetAsSeries(fast, true);
ArraySetAsSeries(slow, true);
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];
bool crossDown = fast[0] < slow[0] && fast[1] >= slow[1];
if(CountOwnPositions() > 0)
return;
if(crossUp)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = NormalizeDouble(ask - InpStopPoints * _Point, _Digits);
double tp = NormalizeDouble(ask + InpTakePoints * _Point, _Digits);
if(!trade.Buy(InpLots, _Symbol, ask, sl, tp, "MA cross"))
PrintFormat("Buy failed: %u %s", trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
else if(crossDown)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = NormalizeDouble(bid + InpStopPoints * _Point, _Digits);
double tp = NormalizeDouble(bid - InpTakePoints * _Point, _Digits);
if(!trade.Sell(InpLots, _Symbol, bid, sl, tp, "MA cross"))
PrintFormat("Sell failed: %u %s", trade.ResultRetcode(), trade.ResultRetcodeDescription());
}
}
What changed, line by line
- Inputs.
externbecomesinput. The comment after each input becomes its label in the EA's settings. - Indicator handles. The two
iMAhandles are created once inOnInit, checked againstINVALID_HANDLEand released inOnDeinit. Creating handles on every tick is a classic, slow mistake. - Shifts.
CopyBuffer(handle, 0, 1, 2, buf)copies two values starting at bar 1, the last closed bar. WithArraySetAsSeries,buf[0]is bar 1 andbuf[1]is bar 2, the same bars the MQL4 code read with shifts 1 and 2. - New-bar check and hours.
Time[0]becomesiTime(), andHour()becomes aMqlDateTimefilled byTimeToStruct(). Both are still in the broker's server time. - Counting trades. The order loop becomes a position loop filtered by symbol and magic number; see MQL5 magic numbers explained for why the filter matters.
- Sending orders.
OrderSend()with eleven arguments becomestrade.Buy()ortrade.Sell(). Slippage becomesSetDeviationInPoints(), and the filling mode, which MQL4 never asked about, is set from the symbol's allowed modes. - Errors.
GetLastError()after a failedOrderSendbecomes the trade server's return code,ResultRetcode(), with a readable description. The codes are listed in MQL5 OrderSend errors explained.
Fixed lots mirror the original. For real use, size each trade from risk with an MQL5 lot-size function.
Problems that compile fine
- Netting accounts. An MT4 EA that holds a buy and a sell at the same time can't do that on a netting account: the second order offsets the first. Check the account type with
AccountInfoInteger(ACCOUNT_MARGIN_MODE)and design for it. - Filling modes. Sending an order with a filling type the symbol doesn't support returns error 10030.
SetTypeFillingBySymbol()avoids it. - Stop distance. The broker's minimum stop distance still applies, now read from
SYMBOL_TRADE_STOPS_LEVEL. Stops that are too close fail with 10016. - History. Closed trades are deals, not orders. To total today's profit, select the history with
HistorySelect()and sum the profit, commission and swap of the deals that closed positions (DEAL_ENTRY_OUT). - Partial closes.
OrderClose()with fewer lots becomesPositionClosePartial(). - Chart objects.
ObjectCreate()takes a chart ID as its first argument and a subwindow number after the object type, and object properties are set withObjectSetInteger(),ObjectSetDouble()andObjectSetString(). - Points and pips. Unchanged, but worth checking: on 5-digit quotes, one pip is 10 points. If the MT4 inputs were in pips, the MQL5 inputs should say so, or be converted.
Custom indicators and iCustom()
If your MT4 EA reads a custom indicator with iCustom(), that indicator has to be converted too: MT5 can't load .ex4 files, and an indicator's MQL4 source has the same kinds of differences as an EA's. Once you have an MQL5 version, the call changes shape like every other indicator. iCustom() returns a handle, created once, with the indicator's inputs passed in order after its name, and you read its buffers with CopyBuffer(), using the buffer index the indicator assigns with SetIndexBuffer(). If you don't have the source of a purchased MT4 indicator, ask the vendor for an MT5 version, or rebuild its logic from its documentation and compare the two on the same chart before your EA depends on it.
Testing the conversion
- Compile in MetaEditor with zero errors, and read the warnings.
- Run the MT5 version in the Strategy Tester with visual mode on, and check that entries happen on the bars where the EMAs cross.
- If you still have MT4, run the original on the same symbol and period and compare a handful of trades. Expect some differences: the two terminals use different history, and MT5's "Every tick based on real ticks" mode is more detailed than MT4's tester.
- Forward-test on a demo account for a few weeks before anything else. How to install an Expert Advisor on MT5 covers compiling, attaching and the journal.
Sources and further reading
- Migrating from MQL4 to MQL5 — MetaQuotes' article on the function-by-function differences.
- MQL5 Reference: Trade functions — orders, positions, deals and history.
- MQL5 Reference: CTrade class — the trade class used in the example.
- MQL5 Reference: iMA() — indicator handles and their buffers.
Try it with Edgoro
Edgoro's MT5 EA generator writes Expert Advisors in MQL5 only; it doesn't write MQL4. Describe what your MT4 EA does, or paste its code as a reference, and you get an MQL5 version with risk inputs and a magic number, compile-checked in MetaEditor before you see it. It's part of the trading bots option. A clean compile shows the code builds, not that the strategy works, so test it on a demo account first.
Converted code can behave differently from the original; test both before relying on either. Trading involves substantial risk of loss.