MT4 to MT5 EA Migration: A Practical Guide
Why Migration Isn't Just Recompilation
Moving Expert Advisors from MT4 (MQL4) to MT5 (MQL5) is often mistaken for a simple recompilation. This is fundamentally wrong. MT5 uses a completely different trading architecture: multi-currency backtesting, built-in hedging option, and most importantly, a separation between orders (pending) and positions (market trades).
In MQL4, a market trade is an "order" from open to close. In MQL5, pending orders and market positions are distinct objects, each managed with its own set of functions.
Core Difference: Order Management Model
| Aspect | MQL4 | MQL5 |
|:---|:---|:---|
| Open market trade |
OrderSend() | PositionOpen() || Close market trade |
OrderClose() | PositionClose() || Modify SL/TP |
OrderModify() | PositionModify() || Place pending order |
OrderSend() | OrderSend() (different params) || Open price retrieval |
OrderOpenPrice() | PositionGetDouble(POSITION_PRICE_OPEN) |1. OrderSend to Position/Order Replacement
In MQL4 – Opening a Buy Order:
``
cpp
int ticket = OrderSend(
Symbol(), // symbol
OP_BUY, // operation
lotSize, // volume
Ask, // price
3, // slippage
stopLoss, // stoploss
takeProfit, // takeprofit
"EA Trade", // comment
magicNumber, // magic
0, // expiration
clrGreen // arrow color
);
`
In MQL5 – Opening a Buy Position:
MQL5 replaces OrderSend() with specialized functions. Opening a market trade requires creating a trade request structure and checking the result.
`cpp
#include
CTrade trade;
// Configure trade
trade.SetExpertMagicNumber(magicNumber);
trade.SetDeviation(3); // slippage
// Build request
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = stopLoss;
double tp = takeProfit;
// Send order
bool success = trade.PositionOpen(
_Symbol, // symbol
ORDER_TYPE_BUY, // trade direction
lotSize, // volume
price, // open price
sl, // stop loss
tp, // take profit
"EA Trade" // comment
);
if(success)
{
ulong ticket = trade.ResultOrder(); // Get order ticket
Print("Position opened. Ticket: ", ticket);
}
else
{
Print("PositionOpen failed. Error: ", trade.ResultRetcode());
}
`
2. MarketInfo Replacement: SymbolInfo
MarketInfo() is gone in MQL5. Use SymbolInfoDouble(), SymbolInfoInteger(), and SymbolInfoDouble().
| MQL4 | MQL5 Equivalent |
|:---|:---|
| MarketInfo(Symbol(), MODE_BID) | SymbolInfoDouble(_Symbol, SYMBOL_BID) |
| MarketInfo(Symbol(), MODE_ASK) | SymbolInfoDouble(_Symbol, SYMBOL_ASK) |
| MarketInfo(Symbol(), MODE_POINT) | SymbolInfoDouble(_Symbol, SYMBOL_POINT) |
| MarketInfo(Symbol(), MODE_DIGITS) | (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS) |
| MarketInfo(Symbol(), MODE_STOPLEVEL) | (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) |
| MarketInfo(Symbol(), MODE_SPREAD) | (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) |
| MarketInfo(Symbol(), MODE_MINLOT) | SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) |
| MarketInfo(Symbol(), MODE_MAXLOT) | SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX) |
Example – Getting Ask price with validation:
`cpp
// MQL5 version
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(ask == 0)
{
Print("Failed to get ASK price. Error: ", GetLastError());
return;
}
`
3. Time Function Migration
MQL5 introduces a critical distinction between trade server time and local computer time.
| MQL4 | MQL5 | Note |
|:---|:---|:---|
| TimeCurrent() | TimeTradeServer() or TimeCurrent() | MQL5 still supports TimeCurrent() |
| Time[0] (array) | iTime(_Symbol, PERIOD_CURRENT, 0) | For series access |
| CopyTime() for history | CopyTime() (similar but more features) | Both support CopyTime() |
| N/A | TimeLocal() | Computer time (not recommended for logic) |
Best practice – Use trade server time:
`cpp
// MQL5 – Recommended for EA logic
datetime currentTime = TimeTradeServer();
`
4. Position and Order Selection
In MQL4, you loop through OrdersTotal() for everything. In MQL5, separate loops exist:
| Purpose | MQL5 Function |
|:---|:---|
| Loop through open positions | PositionsTotal() + PositionSelect() / PositionGetTicket() |
| Loop through pending orders | OrdersTotal() + OrderSelect() |
| Loop through closed history | HistorySelect() + HistoryDealsTotal() |
MQL5 – Getting current open positions:
`cpp
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
long magic = PositionGetInteger(POSITION_MAGIC);
if(magic == magicNumber)
{
// Process this position
}
}
}
`
5. Complete Migration Checklist
Before recompiling, verify these items have been addressed:
[ ] Replace all OrderSend() market trades with CTrade.PositionOpen() / PositionClose()
[ ] Replace all MarketInfo() with SymbolInfoDouble/Integer()
[ ] Replace Bid / Ask with SymbolInfoDouble(_Symbol, SYMBOL_BID/ASK)
[ ] Update pending orders: OrderSend() is still used but with different parameter structure
[ ] Replace OrderModify() with trade.PositionModify() or trade.OrderModify()
[ ] Update time functions: prefer TimeTradeServer() over TimeCurrent()
[ ] Add #include for CTrade class
[ ] Test with visual mode in Strategy Tester to see order flow
[ ] Verify slippage and deviation handling using trade.SetDeviation()
Common Pitfalls
Pitfall 1: ECN Brokers and Stop Loss Attachment
Some brokers reject stop loss in the initial request. The workaround is to open the trade with 0 SL/TP, then modify immediately after confirmation .
Pitfall 2: Point vs SymbolInfoDouble
Point still exists in MQL5, but SymbolInfoDouble(_Symbol, SYMBOL_POINT) is more reliable, especially for symbols with unusual point values.
Pitfall 3: Lot Step Validation
Always check SYMBOL_VOLUME_STEP and round lot sizes accordingly:
`cpp
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double roundedLot = MathRound(lotSize / lotStep) * lotStep;
`
Summary
| Component | MQL4 Approach | MQL5 Approach |
|:---|:---|:---|
| Market trade | OrderSend() | CTrade.PositionOpen() |
| SL/TP modification | OrderModify() | CTrade.PositionModify() |
| Quotes | Bid, Ask | SymbolInfoDouble() |
| Time | TimeCurrent() | TimeTradeServer() |
| Symbol info | MarketInfo() | SymbolInfoDouble/Integer()` |Migration requires rewriting core trade management logic. The CTrade class provides a cleaner abstraction, but the learning curve is steep for developers accustomed to MQL4's simpler model. Always test migrated EAs in demo mode before live deployment.
References: