Summary: MT4到MT5的EA迁移绝非简单重编译。本文详解OrderSend改为Position/Order双轨模型、MarketInfo全部替换为SymbolInfo系列、时间函数统一为TimeTradeServer,并提供完整迁移检查清单。




# MT4到MT5的EA迁移:核心差异与代码改造指南

为什么不能直接重编译?



将Expert Advisor从MT4(MQL4)迁移到MT5,最常被误解的就是“简单重编译一下就能跑”。这是完全错误的。MT5采用了完全不同的交易架构:支持多品种回测、内置对冲模式,以及最重要的——订单(挂单)与持仓(市价单)的彻底分离

在MQL4中,从开仓到平仓,一个市价单始终被称为“订单”。但在MQL5中,挂单(Orders)和持仓(Positions)是两个截然不同的对象,各有各的管理函数。

核心差异对比:交易模型完全不同



| 操作 | MQL4方式 | MQL5方式 |
|:---|:---|:---|
| 开市价单 | `OrderSend()` | `CTrade.PositionOpen()` |
| 平仓 | `OrderClose()` | `CTrade.PositionClose()` |
| 修改止损止盈 | `OrderModify()` | `CTrade.PositionModify()` |
| 下挂单 | `OrderSend()` | `OrderSend()`(参数不同) |
| 获取开仓价 | `OrderOpenPrice()` | `PositionGetDouble(POSITION_PRICE_OPEN)` |

1. OrderSend函数改造:MQL5的新范式



MQL4中的开仓写法:


```cpp
int ticket = OrderSend(
Symbol(), // 品种
OP_BUY, // 买入
lotSize, // 手数
Ask, // 价格
3, // 滑点
stopLoss, // 止损
takeProfit, // 止盈
"EA订单", // 注释
magicNumber, // 魔术号
0, // 有效期
clrGreen // 箭头颜色
);
```

MQL5中的开仓写法:



MQL5不再使用单一的OrderSend处理市价单,而是使用`CTrade`类的`PositionOpen()`方法。需要先构建交易请求,再发送。

```cpp
#include // 必须引入交易类库

CTrade trade;

// 配置交易参数
trade.SetExpertMagicNumber(magicNumber);
trade.SetDeviation(3); // 滑点设置

// 获取当前报价
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = stopLoss;
double tp = takeProfit;

// 发送开仓指令
bool success = trade.PositionOpen(
_Symbol, // 品种
ORDER_TYPE_BUY, // 交易方向
lotSize, // 手数
price, // 开仓价
sl, // 止损
tp, // 止盈
"EA订单" // 注释
);

if(success)
{
ulong ticket = trade.ResultOrder(); // 获取订单号(注意是ulong类型)
Print("开仓成功,订单号:", ticket);
}
else
{
Print("开仓失败,错误码:", trade.ResultRetcode());
}
```

2. MarketInfo函数的完全替换方案



MQL5彻底移除了`MarketInfo()`,统一使用`SymbolInfoDouble()`、`SymbolInfoInteger()`和`SymbolInfoDouble()`系列函数。

| MQL4 | MQL5替代写法 |
|:---|:---|
| `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)` |
| `MarketInfo(Symbol(), MODE_LOTSTEP)` | `SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)` |
| `MarketInfo(Symbol(), MODE_FREEZINGLEVEL)` | `(int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL)` |

带错误检查的取值示例:
```cpp
// MQL5版本 – 带完整验证
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(ask == 0)
{
Print("获取ASK价格失败,错误:", GetLastError());
return;
}
```

3. 时间函数的迁移



MQL5引入了一个重要区别:交易服务器时间本地计算机时间不再混用。

| MQL4 | MQL5 | 说明 |
|:---|:---|:---|
| `TimeCurrent()` | `TimeTradeServer()` 或 `TimeCurrent()` | MQL5仍支持TimeCurrent()但推荐用前者 |
| `Time[0]`(数组) | `iTime(_Symbol, PERIOD_CURRENT, 0)` | 用于序列访问 |
| `CopyTime()` | `CopyTime()`(功能更强大) | 两者都支持 |
| 无 | `TimeLocal()` | 本机时间(不推荐用于交易逻辑) |

最佳实践 – 使用交易服务器时间:
```cpp
// MQL5 – EA逻辑推荐写法
datetime currentTime = TimeTradeServer();
```

4. 持仓与挂单的选择机制



在MQL4中,`OrdersTotal()`循环遍历一切。在MQL5中,必须区分遍历对象:

| 目的 | MQL5函数 |
|:---|:---|
| 遍历当前持仓 | `PositionsTotal()` + `PositionSelect()` / `PositionGetTicket()` |
| 遍历当前挂单 | `OrdersTotal()` + `OrderSelect()` |
| 遍历历史记录 | `HistorySelect()` + `HistoryDealsTotal()` |

MQL5 – 获取当前所有持仓:
```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)
{
// 处理这个持仓
Print("持仓单号:", ticket, ",开仓价:", openPrice);
}
}
}
```

5. 完整迁移检查清单



在尝试编译之前,请逐项核对:

  • [ ] 将所有市价单的`OrderSend()`改为`CTrade.PositionOpen()` / `PositionClose()`

  • [ ] 将所有`MarketInfo()`改为`SymbolInfoDouble/Integer()`系列

  • [ ] 将`Bid` / `Ask`全局变量替换为`SymbolInfoDouble(_Symbol, SYMBOL_BID/ASK)`

  • [ ] 更新挂单逻辑(`OrderSend`仍可用于挂单,但参数结构不同)

  • [ ] 将`OrderModify()`改为`trade.PositionModify()`或`trade.OrderModify()`

  • [ ] 将时间函数统一:EA内部逻辑使用`TimeTradeServer()`

  • [ ] 添加`#include `头文件

  • [ ] 在策略测试器中使用可视化模式测试,观察订单流转

  • [ ] 验证滑点处理,使用`trade.SetDeviation()`设置


  • 常见踩坑点



    踩坑1:ECN经纪商与止损止盈
    部分经纪商(尤其是ECN模式)在开仓请求中直接附带止损止盈会被拒绝。解决方案是先以0止损止盈开仓,确认成交后立即用`OrderModify()`或`PositionModify()`附加止损止盈。

    踩坑2:Point变量不再可靠
    MQL5中`Point`仍然存在,但使用`SymbolInfoDouble(_Symbol, SYMBOL_POINT)`更加可靠,特别是处理指数、股票等非常规交易品种时。

    踩坑3:手数步长验证
    MQL5中必须检查`SYMBOL_VOLUME_STEP`,并对计算出的手数进行取整:
    ```cpp
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    double roundedLot = MathRound(lotSize / lotStep) * lotStep;
    ```

    总结对照表



    | 组件 | MQL4方式 | MQL5方式 |
    |:---|:---|:---|
    | 市价单开仓 | `OrderSend()` | `CTrade.PositionOpen()` |
    | 修改止损止盈 | `OrderModify()` | `CTrade.PositionModify()` |
    | 获取报价 | `Bid`, `Ask`全局变量 | `SymbolInfoDouble()` |
    | 获取时间 | `TimeCurrent()` | `TimeTradeServer()` |
    | 品种信息 | `MarketInfo()` | `SymbolInfoDouble/Integer()` |

    MT4到MT5的迁移绝非简单的语法调整,而是交易逻辑模型的重构。熟悉MQL4单一订单模型的开发者,需要花时间理解MQL5中订单与持仓的双轨制。CTrade类提供了更清晰的对象化封装,但学习曲线确实存在。强烈建议在实盘运行前,使用MT5策略测试器的可视化模式进行充分的验证。

    ---
    参考来源:
    1. MQL5官方文档 – 从MQL4迁移指南
    2. MQL5社区论坛 – ECN经纪商OrderSend错误130处理方案(2025)
    3. MetaQuotes – MQL4与MQL5功能对比(2026)
    4. MQL5官方书籍 – 交易操作编程
    ```