轻松快捷开发 MetaTrader 程序的函数库(第九部分):与 MQL4 的兼容性 - 准备数据(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库(第九部分):与 MQL4 的兼容性 - 准备数据(基础篇)

第 1/3 篇

把 MQL4 老代码接进 MT5 前的准备

在 MT5 里复用 MQL4 历史程序,第一道坎不是语法转换,而是数据结构对齐。MT4 的数组默认从索引 0 起、按时间升序存价;MT5 的时序与缓冲机制不同,直接拷函数往往跑出错位信号。 官方示例文发布于 2019 年 8 月 1 日 12:58,至今在站内累计 2,796 次查看、9 条讨论,说明兼容性坑一直有人踩。与其重写,不如先建一层适配函数库。 实操上,开 MT5 后先建 CExpert 基类壳,把 MT4 的 OrderSend 参数映射进 MT5 的 CTrade,再补时间序列转换。外汇与贵金属杠杆高,映射错一根 K 线就可能触发异常平仓,验证时先用模拟盘跑。

「把函数库搬到 MQL4 上」

前面几篇已经给 MetaTrader 5 和 MetaTrader 4 的跨平台函数库备好了底层工具:一套用户用例功能,能快速读取对冲账户与净持结算账户里任意订单、持仓的数据;还有一套事件跟踪机制,覆盖下单、撤单、挂单激活、开平仓以及修改动作。 现在要做的,是让这套函数库真正兼容 MQL4——因为接下来要写交易类,而函数库必须在 MQL5 和 MQL4 两边都能跑。本文就从这步兼容性改造切入。 外汇与贵金属交易本身高风险,跨平台代码若有一侧事件映射错了,可能直接引发重复下单,动手前建议在 MT5/MT4 双终端各开一个模拟账户对照验证。

◍ 把 MQL5 函数库硬塞进 MT4 会撞上什么墙

想把现成的 MQL5 函数库搬到 MetaTrader 4,第一步是把整个库文件夹拷到 \MQL4\Include\DoEasy,再把测试 EA 另存为 *.mq4 丢进 \MQL4\Experts\TestDoEasy 对应文章分卷目录。右键编译 Include 目录,你会直接看到超过两千个编译错误——这不是手滑,是结构性冲突。 翻一遍报错就能发现,绝大多数来自 MQL5 的常量与枚举,MT4 的 MQL4 编译器根本不认。除此之外还有一批“函数缺失”类错误,意思是某些 MQL5 原生方法得用 MQL4 自己的函数重写逻辑补上。 订单系统才是真正的分水岭。MQL4 的历史订单列表给的数据少得多,连成交记录都没有,没法像 MQL5 那样直接从终端列表捞订单和成交。可行做法是:把在场订单和历史订单列表做逻辑比对,用差异反推实际发生的事件,单独给 MQL4 写一套事件处理。外汇与贵金属杠杆交易高风险,移植前先开 MT4 拿 Part09 的库跑一次编译,数清楚你这份库到底爆了几千个错。

把 MQL5 函数库塞进 MT4 编译器的实操

在 DoEasy 根目录新建 ToMQL4.mqh 包含文件,把 MQL4 需要的常量与枚举都塞进去,并在 Defines.mqh 最开头用 #include 把它拉进来。这样整个函数库在 MT4 编译期就能读到这些定义,不用改每一处调用。 编译后错误列表会先爆出 MQL4 不认 DEAL_TYPE_CREDIT 这类成交属性。查 MQL5 参考的成交属性表:DEAL_TICKET 是 long 型独有编号,DEAL_TIME_MSC 是自 1970-01-01 起的毫秒 long,DEAL_TYPE 属 ENUM_DEAL_TYPE。把表里 ENUM_DEAL_TYPE 的 19 个成员(BUY/SELL/BALANCE/CREDIT 一直到 DEAL_TAX)全部抄进 ToMQL4.mqh,再编译错误数就掉一截。 MQL4 原本就有 ENUM_ORDER_TYPE,不能硬加 ORDER_TYPE_BUY_STOP_LIMIT。MQL5 里这常量值是 6,MQL4 里 6 是余额、7 是信贷,所以改用宏替换,挂在 ORDER_TYPE_CLOSE_BY+1 和 +2 上。但 switch 里用了枚举类型导致值 9、10 走不到,把 order_type 强转 int 就通了。 部分 MQL5 专有方法(如 NewDealEventHedge、NewDealEventNetto)用 #ifdef __MQL5__ / #else / #endif 包起来,MT4 侧直接跳过;账户类型检查在 MT4 里恒返回 true,因为 MT4 全是 hedge 账户,货币小数位直接返回 2。 余额操作在 MQL5 是成交,在 MQL4 是 ORDER_TYPE_BALANCE(6) 和 ORDER_TYPE_CREDIT(7) 订单。新建 CHistoryBalance 类继承 COrder,在 ToMQL4.mqh 补两个 order reason,HistoryCollection 里用条件编译只包含该类。COrder 的 OrderPositionID 在 MT4 改返票据,订单状态对删除挂单返 ORDER_STATE_CANCELED,未成交量对仓位返 0。做完这些 MT4 能零错误编译,但只是兼容第一阶段。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]

「账户事件枚举与 MQL4 兼容头文件」

在跨版本 EA 里,先要把账户上可能发生的交易事件列清楚。下面这段枚举把挂单成交、删单、信用注入、额外扣费等都归到 TRADE_EVENT 里,其中 TRADE_EVENT_ACCOUNT_CREDIT 直接映射 DEAL_TYPE_CREDIT,注释标明对应数值为 3,顺序不能动,否则和底层成交类型对不上。 为了能在 MT4 终端里跑原本按 MQL5 写的逻辑,代码用 #ifdef __MQL4__ 包裹了一行 #include "ToMQL4.mqh"。也就是说,只有编译目标为 MQL4 时才引入这个桥接头文件,MT5 环境下直接跳过,避免重复定义。 ToMQL4.mqh 内部自己又用 #ifdef __MQL4__ 重定义了 ENUM_DEAL_TYPE,把 MQL5 的 DEAL_TYPE_BUY、DEAL_TYPE_SELL 一直到 DEAL_DIVIDEND 共 16 个成交类型补齐。开 MT5 写跨平台代码时,照这个结构抄一份头文件,能省掉大量版本判断的脏活。

MQL5 / C++
class="macro">#ifdef __MQL4__
class="macro">#include "ToMQL4.mqh"
class="macro">#endif
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT = class="num">0,                 class=class="str">"cmt">// No trading event
   TRADE_EVENT_PENDING_ORDER_PLASED,         class=class="str">"cmt">// Pending order placed
   TRADE_EVENT_PENDING_ORDER_REMOVED,        class=class="str">"cmt">// Pending order removed
class=class="str">"cmt">//--- enumeration members matching the ENUM_DEAL_TYPE enumeration members
class=class="str">"cmt">//--- (constant order below should not be changed, no constants should be added/deleted)
   TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,    class=class="str">"cmt">// Charging credit(class="num">3)
   TRADE_EVENT_ACCOUNT_CHARGE,                       class=class="str">"cmt">// Additional charges
  };
class="macro">#ifdef __MQL4__
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
  };
class="macro">#endif

◍ 在 MQL4 里补齐 MQL5 的成交与持仓枚举

把 MQL5 的成交类型整套搬回 MQL4,靠的是 #ifdef __MQL4__ 包裹的 ENUM_DEAL_TYPE。这个枚举列出了 18 个成员,从 DEAL_TYPE_BUYDEAL_TYPE_SELLDEAL_DIVIDEND_FRANKEDDEAL_TAX,覆盖了普通买卖、资金类调整以及税务分红等账户事件。 持仓方向则单独用 ENUM_POSITION_TYPE 定义,只含 POSITION_TYPE_BUYPOSITION_TYPE_SELL 两个值,和 MT5 原生结构保持一致。 在 MT4 上编译含这段头文件的 EA 时,这些枚举才会生效;切到 MT5 环境因 __MQL4__ 未定义而被整段跳过。外汇与贵金属杠杆交易风险较高,用这类跨平台兼容层做回测时,要先确认经纪商成交回报里 DEAL_TAX 等字段是否真有数据,避免空枚举干扰统计。

MQL5 / C++
class="macro">#ifdef __MQL4__
enum ENUM_DEAL_TYPE
  {
  DEAL_TYPE_BUY,
  DEAL_TYPE_SELL,
  DEAL_TYPE_BALANCE,
  DEAL_TYPE_CREDIT,
  DEAL_TYPE_CHARGE,
  DEAL_TYPE_CORRECTION,
  DEAL_TYPE_BONUS,
  DEAL_TYPE_COMMISSION,
  DEAL_TYPE_COMMISSION_DAILY,
  DEAL_TYPE_COMMISSION_MONTHLY,
  DEAL_TYPE_COMMISSION_AGENT_DAILY,
  DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
  DEAL_TYPE_INTEREST,
  DEAL_TYPE_BUY_CANCELED,
  DEAL_TYPE_SELL_CANCELED,
  DEAL_DIVIDEND,
  DEAL_DIVIDEND_FRANKED,
  DEAL_TAX
  };
enum class="type">ENUM_POSITION_TYPE
  {
  POSITION_TYPE_BUY,
  POSITION_TYPE_SELL
  };
class="macro">#endif

常见问题

先梳理老代码里用到的账户、成交、持仓相关枚举和函数,确认新平台是否原生支持,缺的就补头文件或重写兼容层,别直接丢进编译器。
老平台没有新平台的成交与持仓枚举类型,编译器会找不到定义;需要在老平台里手动补齐对应枚举和事件常量才能过编。
小布可以读取你的老代码文件,自动标出缺失的枚举和兼容头文件缺口,并给出补齐清单,省去你一行行比对。
建一个兼容头文件,把新平台的账户事件枚举常量按老平台语法重定义一遍,并在主代码前 include 进去即可。
用老平台的订单数组自行模拟持仓结构,或在头文件里把持仓枚举映射为订单状态组合,保证函数库调用不空指针。