Summary: 面向进阶用户的跨平台EA开发实战指南,通过条件编译和抽象层设计,实现一套代码同时编译运行于MT4和MT5。涵盖Buy/Sell/Close统一接口、仓位信息结构体及完整模板代码。




每一位EA开发者的梦想都很简单:一次编写,处处运行。在MT4和MT5共存的交易生态中,维护两套独立的代码库简直是噩梦。解决方案在于条件编译和抽象层设计,将平台差异隐藏在统一接口之后。

1. 条件编译宏定义

MetaEditor定义了平台专属宏,让编译器自动选择正确的代码路径:

```cpp
// 平台检测宏
#ifdef __MQL4__
// 仅在MT4中编译的代码
#define PLATFORM_NAME "MetaTrader 4"
#endif

#ifdef __MQL5__
// 仅在MT5中编译的代码
#define PLATFORM_NAME "MetaTrader 5"
#endif

// 统一循环宏 - MT4和MT5的持仓/订单结构不同
#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. 统一开仓函数Buy/Sell

核心抽象层:一个`Buy()`函数在两个平台上完全一致地工作:

```cpp
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, 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. 手数验证辅助函数

避免在两个平台上因手数问题导致订单被拒:

```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;

// 检查步长对齐 - ORDER_VOLUME_INVALID错误的常见原因
double remainder = fmod(volume - minlot, lotstep);
if(fabs(remainder) > 1e-10) return false;

return true;
}
```

4. 通用平仓函数

按魔术编号和品种批量平仓:

```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("平仓失败: ", 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("平仓失败: ", trade.ResultRetcodeDescription());
success = false;
}
#endif
}
return success;
}
```

5. 统一获取持仓数据

用结构体抽象持仓信息:

```cpp
struct SPositionInfo {
ulong ticket;
string symbol;
double volume;
double openPrice;
double currentPrice;
double profit;
int type; // 0=买单, 1=卖单
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. 完整跨平台EA模板

```cpp
//+------------------------------------------------------------------+
//| CrossPlatformEA.mq4/mq5 - 同时支持MT4和MT5 |
//+------------------------------------------------------------------+
#property copyright "跨平台开发者"
#property version "1.00"

input double LotSize = 0.1;
input int MagicNumber = 12345;

#ifdef __MQL5__
#include
CTrade g_trade;
#endif

//+------------------------------------------------------------------+
//| EA初始化函数 |
//+------------------------------------------------------------------+
int OnInit() {
#ifdef __MQL5__
g_trade.SetExpertMagicNumber(MagicNumber);
g_trade.SetDeviationInPoints(10);
#endif
return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| EA心跳函数 |
//+------------------------------------------------------------------+
void OnTick() {
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);

if(currentBarTime == lastBarTime) return;
lastBarTime = currentBarTime;

// 策略逻辑写在这里 - 仅使用已收盘K线数据
if(GetBuySignal()) {
Buy(LotSize, 0, 0, MagicNumber, _Symbol, "CrossEA");
}
}

//+------------------------------------------------------------------+
//| 统一买入函数 |
//+------------------------------------------------------------------+
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
}
```

参考来源:MQL5官方文档《MQL4/MQL5跨平台兼容性》(mql5.com/docs);《通用交易函数设计》(mql5.com/blogs)。