The dream of every EA developer is simple: write once, run everywhere. With MetaTrader 4 and 5 coexisting in the trading ecosystem, maintaining two separate codebases is a nightmare. The solution lies in conditional compilation and abstraction layers that hide platform differences behind a unified interface.
1. Conditional Compilation Macros
MetaEditor defines platform-specific macros that let the compiler choose the right code path:
```cpp
// Platform detection macros
#ifdef __MQL4__
// Code that only compiles in MT4
#define PLATFORM_NAME "MetaTrader 4"
#endif
#ifdef __MQL5__
// Code that only compiles in MT5
#define PLATFORM_NAME "MetaTrader 5"
#endif
// Unified loop macro - MT4 and MT5 have different position/order structures
#ifdef __MQL4__
#define ForEachPositionDown(i) for(int i = OrdersTotal()-1; i >= 0; i--)
#define SelectPositionByIndex(i) OrderSelect(i, SELECT_BY_POS, MODE_TRADES)
#else
#define ForEachPositionDown(i) for(int i = PositionsTotal()-1; i >= 0; i--)
#define SelectPositionByIndex(i) PositionSelectByTicket(PositionGetTicket(i))
#endif
```
2. Unified Buy/Sell Functions
The core abstraction: a single `Buy()` function that works identically on both platforms:
```cpp
ulong Buy(double volume, double sl, double tp, ulong magic, string symbol, string comment) {
// Validate volume first
if(!CheckVolumeValue(volume, symbol)) return 0;
double price = SymbolInfoDouble(symbol, SYMBOL_ASK);
#ifdef __MQL4__
int ticket = OrderSend(symbol, OP_BUY, volume, price, 3, sl, tp, comment, magic, 0, clrNONE);
return (ticket > 0) ? ticket : 0;
#endif
#ifdef __MQL5__
#include
static CTrade trade;
trade.SetExpertMagicNumber(magic);
trade.SetDeviationInPoints(10);
if(trade.Buy(volume, symbol, 0, sl, tp, comment)) {
return trade.ResultOrder();
}
return 0;
#endif
}
ulong Sell(double volume, double sl, double tp, ulong magic, string symbol, string comment) {
if(!CheckVolumeValue(volume, symbol)) return 0;
double price = SymbolInfoDouble(symbol, SYMBOL_BID);
#ifdef __MQL4__
int ticket = OrderSend(symbol, OP_SELL, volume, price, 3, sl, tp, comment, magic, 0, clrNONE);
return (ticket > 0) ? ticket : 0;
#endif
#ifdef __MQL5__
static CTrade trade;
trade.SetExpertMagicNumber(magic);
if(trade.Sell(volume, symbol, 0, sl, tp, comment)) {
return trade.ResultOrder();
}
return 0;
#endif
}
```
3. Volume Validation Helper
Critical for avoiding order rejection on both platforms:
```cpp
bool CheckVolumeValue(double volume, string symbol) {
double minlot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double maxlot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double lotstep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(volume < minlot || volume > maxlot) return false;
// Check step alignment - common source of ORDER_VOLUME_INVALID errors
double remainder = fmod(volume - minlot, lotstep);
if(fabs(remainder) > 1e-10) return false;
return true;
}
```
4. Universal Position Closing
Batch close positions by magic number and symbol:
```cpp
bool CloseAllPositions(ulong magic, string symbol) {
bool success = true;
ForEachPositionDown(i) {
if(!SelectPositionByIndex(i)) continue;
#ifdef __MQL4__
if(OrderMagicNumber() != magic) continue;
if(OrderSymbol() != symbol && symbol != "") continue;
double closePrice = (OrderType() == OP_BUY) ?
SymbolInfoDouble(OrderSymbol(), SYMBOL_BID) :
SymbolInfoDouble(OrderSymbol(), SYMBOL_ASK);
if(!OrderClose(OrderTicket(), OrderLots(), closePrice, 10, clrNONE)) {
Print("Close failed: ", GetLastError());
success = false;
}
#endif
#ifdef __MQL5__
ulong ticket = PositionGetInteger(POSITION_TICKET);
if(PositionGetInteger(POSITION_MAGIC) != magic) continue;
if(PositionGetString(POSITION_SYMBOL) != symbol && symbol != "") continue;
CTrade trade;
if(!trade.PositionClose(ticket)) {
Print("Close failed: ", trade.ResultRetcodeDescription());
success = false;
}
#endif
}
return success;
}
```
5. Getting Position Data Uniformly
A struct that abstracts position information:
```cpp
struct SPositionInfo {
ulong ticket;
string symbol;
double volume;
double openPrice;
double currentPrice;
double profit;
int type; // 0=buy, 1=sell
ulong magic;
};
SPositionInfo GetPositionInfo(int index) {
SPositionInfo info;
ZeroMemory(info);
#ifdef __MQL4__
if(!OrderSelect(index, SELECT_BY_POS, MODE_TRADES)) return info;
info.ticket = OrderTicket();
info.symbol = OrderSymbol();
info.volume = OrderLots();
info.openPrice = OrderOpenPrice();
info.type = (OrderType() == OP_BUY) ? 0 : 1;
info.profit = OrderProfit();
info.magic = OrderMagicNumber();
info.currentPrice = (info.type == 0) ?
SymbolInfoDouble(info.symbol, SYMBOL_BID) :
SymbolInfoDouble(info.symbol, SYMBOL_ASK);
#endif
#ifdef __MQL5__
if(!PositionSelectByTicket(PositionGetTicket(index))) return info;
info.ticket = PositionGetInteger(POSITION_TICKET);
info.symbol = PositionGetString(POSITION_SYMBOL);
info.volume = PositionGetDouble(POSITION_VOLUME);
info.openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
info.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 0 : 1;
info.profit = PositionGetDouble(POSITION_PROFIT);
info.magic = PositionGetInteger(POSITION_MAGIC);
info.currentPrice = (info.type == 0) ?
SymbolInfoDouble(info.symbol, SYMBOL_BID) :
SymbolInfoDouble(info.symbol, SYMBOL_ASK);
#endif
return info;
}
```
6. Complete Cross-Platform EA Template
```cpp
//+------------------------------------------------------------------+
//| CrossPlatformEA.mq4/mq5 - Works on both MT4 and MT5 |
//+------------------------------------------------------------------+
#property copyright "CrossPlatform Developer"
#property version "1.00"
input double LotSize = 0.1;
input int MagicNumber = 12345;
#ifdef __MQL5__
#include
CTrade g_trade;
#endif
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
#ifdef __MQL5__
g_trade.SetExpertMagicNumber(MagicNumber);
g_trade.SetDeviationInPoints(10);
#endif
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime == lastBarTime) return;
lastBarTime = currentBarTime;
// Strategy logic here - use closed bar data only
if(GetBuySignal()) {
Buy(LotSize, 0, 0, MagicNumber, _Symbol, "CrossEA");
}
}
//+------------------------------------------------------------------+
//| Unified Buy function |
//+------------------------------------------------------------------+
ulong Buy(double volume, double sl, double tp, ulong magic, string symbol, string comment) {
if(!CheckVolumeValue(volume, symbol)) return 0;
double price = SymbolInfoDouble(symbol, SYMBOL_ASK);
#ifdef __MQL4__
int ticket = OrderSend(symbol, OP_BUY, volume, price, 10, sl, tp, comment, magic, 0, clrNONE);
return (ticket > 0) ? ticket : 0;
#endif
#ifdef __MQL5__
g_trade.SetExpertMagicNumber(magic);
if(g_trade.Buy(volume, symbol, 0, sl, tp, comment))
return g_trade.ResultOrder();
return 0;
#endif
}
```
Reference: MQL5 Documentation, "MQL4/MQL5 Cross Compatibility" (mql5.com/docs); "Universal Trading Functions" (mql5.com/blogs).