从 MQL4 迁移到 MQL5(基础篇)
从 MQL4 切到 MQL5 要先认清楚的事
很多老 MT4 用户第一次打开 MT5 就直接照抄 EA 代码,结果编译都过不了。MQL5 不是 MQL4 的升级补丁,而是把订单、持仓、历史统统拆成了不同对象模型,语法层面也换了套规则。 一个很实在的数据:这篇官方迁移示例最早发布于 2013 年 12 月 6 日,至今在站内仍有 22077 次浏览、123 条讨论,说明坑一直有人踩。外汇和贵金属杠杆交易本身高风险,拿旧逻辑硬套新环境,爆仓概率只会增加。 建议你在 MT5 里先新建一个空白 EA,把原来 MQL4 的 OrderSend 逻辑注释掉,对照 MQL5 的 CTrade 类重写下单部分,跑回测看成交是否一致。
◍ 为什么老 MQL4 代码值得搬进 MT5
不少做外汇、贵金属的交易员手里都攒着几年前的 MQL4 指标和 EA,直接丢进 MetaTrader 5 是跑不起来的——两套语言在订单处理、数组索引和事件模型上都不兼容。 手动把整套逻辑用 MQL5 重写一遍成本很高,尤其涉及复杂风控的老策略,稍不留神就改坏了原始信号。 比较务实的做法是找一份对照式的迁移样例:同一段功能,左边 MQL4 怎么写、右边 MQL5 怎么写,照着改能省掉大量试错时间。 外汇与贵金属杠杆交易本身高风险,迁移后务必在策略测试器里用历史数据跑一遍,确认下单函数和滑点处理没被改写歪。
「MT5 周期常量与 MQL4 迁移坑点」
MT5 图表里新增了 M2、M3、M4、M6、M10、M12、H2、H3、H6、H8、H12 这些非标准周期,老 MQL4 代码直接搬过来大概率跑歪。根因在周期常量的数值定义变了:MQL4 里 PERIOD_H1 等于 60(即 60 分钟),而 MQL5 里 PERIOD_H1 的数值是 16385,跟分钟数彻底脱钩。
如果你的 EA 或指标里硬编码了 60 代表小时图,在 MT5 里不会报错但会映射到错误周期,回测结果可能完全失真。要确定某个周期实际多少分钟,别再自己除常量,直接调 PeriodSeconds 然后除以 60 才稳。
下面这段迁移函数把 MQL4 旧数值和 MT5 新数值都收进 switch,复制进 MT5 脚本就能把老周期参数无缝转成新枚举。注意 16385~16396 这段是 MT5 的 H1~H12 内部编码,43200 在 MQL4 是月线、在 MT5 里走 default 会更安全。
ENUM_TIMEFRAMES TFMigrate(class="type">int tf) { class="kw">switch(tf) { case class="num">0: class="kw">return(PERIOD_CURRENT); case class="num">1: class="kw">return(PERIOD_M1); case class="num">5: class="kw">return(PERIOD_M5); case class="num">15: class="kw">return(PERIOD_M15); case class="num">30: class="kw">return(PERIOD_M30); case class="num">60: class="kw">return(PERIOD_H1); case class="num">240: class="kw">return(PERIOD_H4); case class="num">1440: class="kw">return(PERIOD_D1); case class="num">10080: class="kw">return(PERIOD_W1); case class="num">43200: class="kw">return(PERIOD_MN1); case class="num">2: class="kw">return(PERIOD_M2); case class="num">3: class="kw">return(PERIOD_M3); case class="num">4: class="kw">return(PERIOD_M4); case class="num">6: class="kw">return(PERIOD_M6); case class="num">10: class="kw">return(PERIOD_M10); case class="num">12: class="kw">return(PERIOD_M12); case class="num">16385: class="kw">return(PERIOD_H1); case class="num">16386: class="kw">return(PERIOD_H2); case class="num">16387: class="kw">return(PERIOD_H3); case class="num">16388: class="kw">return(PERIOD_H4); case class="num">16390: class="kw">return(PERIOD_H6); case class="num">16392: class="kw">return(PERIOD_H8); case class="num">16396: class="kw">return(PERIOD_H12); case class="num">16408: class="kw">return(PERIOD_D1); case class="num">32769: class="kw">return(PERIOD_W1); case class="num">49153: class="kw">return(PERIOD_MN1); class="kw">default: class="kw">return(PERIOD_CURRENT); } }
把 MQL4 常量补回 MT5 环境
MT5 内核没有内置 MQL4 那套订单与图表常量,直接搬老 EA 代码会在编译期报未定义符号。最省事的做法是单独建一个 initMQ4.mqh 头文件,把缺的宏一次性 #define 进去,以后任何迁移项目 #include 即可。 下面这段头文件覆盖了 6 种订单操作类型(OP_BUY=0 到 OP_SELLSTOP=5)、账户信息模式(MODE_BID=9、MODE_ASK=10、MODE_SPREAD=13 等)、以及图表与排序参数。注意 MODE_LOW=1、MODE_HIGH=2 这类数值和 MT4 文档一致,硬写进宏能保住老逻辑里的数组索引不偏移。 外汇与贵金属杠杆高、点差跳变频繁,用 MODE_STOPLEVEL=14 和 MODE_FREEZELEVEL=33 前务必在策略测试器里打印实际返回值,不同券商这两项可能差出 2~3 个点。 代码逐行拆解: //+--- 开头三行是文件头注释,标明来源与版权,无执行意义 #property copyright "keiji" 与 #property copyright "DC2008" 重复声明版权,编译器取最后一个 #property link 填了社区站点链接,仅元数据 //---- 声明常量 注释分隔 #define OP_BUY 0 //买:市价多单操作码 #define OP_SELL 1 //卖:市价空单操作码 #define OP_BUYLIMIT 2 //BUY LIMIT 挂单类型码 #define OP_SELLLIMIT 3 //SELL LIMIT 挂单类型码 #define OP_BUYSTOP 4 //BUY STOP 挂单类型码 #define OP_SELLSTOP 5 //SELL STOP 挂单类型码 #define MODE_OPEN 0 //选中持仓的开放价格模式 #define MODE_CLOSE 3 //选中持仓的平仓价格模式 #define MODE_VOLUME 4 //成交量(Tick 计) #define MODE_REAL_VOLUME 5 //真实成交量 #define MODE_TRADES 0 //当前持仓池 #define MODE_HISTORY 1 //历史成交池 #define SELECT_BY_POS 0 //按持仓列表位置选取 #define SELECT_BY_TICKET 1 //按订单号选取 #define DOUBLE_VALUE 0 //双精度类型标识 #define FLOAT_VALUE 1 //单精度类型标识 #define LONG_VALUE INT_VALUE //长整型映射,INT_VALUE 需他处定义 #define CHART_BAR 0 //折线图模式 #define CHART_CANDLE 1 //蜡烛图模式 #define MODE_ASCEND 0 //升序排列 #define MODE_DESCEND 1 //降序排列 #define MODE_LOW 1 //最低价序列 #define MODE_HIGH 2 //最高价序列 #define MODE_TIME 5 //时间序列 #define MODE_BID 9 //买价 #define MODE_ASK 10 //卖价 #define MODE_POINT 11 //点值 #define MODE_DIGITS 12 //报价小数位 #define MODE_SPREAD 13 //点差 #define MODE_STOPLEVEL 14 //最小止损距离 #define MODE_LOTSIZE 15 //标准手大小 #define MODE_TICKVALUE 16 //每跳价值 #define MODE_TICKSIZE 17 //每跳尺寸 #define MODE_SWAPLONG 18 //多单隔夜息 #define MODE_SWAPSHORT 19 //空单隔夜息 #define MODE_STARTING 20 //经纪商开盘时间 #define MODE_EXPIRATION 21 //订单到期时间 #define MODE_TRADEALLOWED 22 //是否允许交易 #define MODE_MINLOT 23 //最小手数 #define MODE_LOTSTEP 24 //手数步长 #define MODE_MAXLOT 25 //最大手数 #define MODE_SWAPTYPE 26 //利息计算类型 #define MODE_PROFITCALCMODE 27 //盈利计算模式 #define MODE_MARGINCALCMODE 28 //保证金计算模式 #define MODE_MARGININIT 29 //初始保证金 #define MODE_MARGINMAINTENANCE 30 //维持保证金 #define MODE_MARGINHEDGED 31 //对冲保证金 #define MODE_MARGINREQUIRED 32 //开仓所需保证金 #define MODE_FREEZELEVEL 33 //冻结 level #define EMPTY -1 //空值标记
class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| InitMQL4.mqh | class=class="str">"cmt">//| Copyright DC2008 | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------ class="macro">#class="kw">property copyright "keiji" class="macro">#class="kw">property copyright "DC2008" class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//---- 声明常量 class="macro">#define OP_BUY class="num">0 class=class="str">"cmt">//买 class="macro">#define OP_SELL class="num">1 class=class="str">"cmt">//卖 class="macro">#define OP_BUYLIMIT class="num">2 class=class="str">"cmt">//BUY LIMIT 挂单类型 class="macro">#define OP_SELLLIMIT class="num">3 class=class="str">"cmt">//SELL LIMIT 挂单类型 class="macro">#define OP_BUYSTOP class="num">4 class=class="str">"cmt">//BUY STOP 挂单类型 class="macro">#define OP_SELLSTOP class="num">5 class=class="str">"cmt">//SELL STOP 挂单类型 class=class="str">"cmt">//--- class="macro">#define MODE_OPEN class="num">0 class="macro">#define MODE_CLOSE class="num">3 class="macro">#define MODE_VOLUME class="num">4 class="macro">#define MODE_REAL_VOLUME class="num">5 class="macro">#define MODE_TRADES class="num">0 class="macro">#define MODE_HISTORY class="num">1 class="macro">#define SELECT_BY_POS class="num">0 class="macro">#define SELECT_BY_TICKET class="num">1 class=class="str">"cmt">//--- class="macro">#define DOUBLE_VALUE class="num">0 class="macro">#define FLOAT_VALUE class="num">1 class="macro">#define LONG_VALUE INT_VALUE class=class="str">"cmt">//--- class="macro">#define CHART_BAR class="num">0 class="macro">#define CHART_CANDLE class="num">1 class=class="str">"cmt">//--- class="macro">#define MODE_ASCEND class="num">0 class="macro">#define MODE_DESCEND class="num">1 class=class="str">"cmt">//--- class="macro">#define MODE_LOW class="num">1 class="macro">#define MODE_HIGH class="num">2 class="macro">#define MODE_TIME class="num">5 class="macro">#define MODE_BID class="num">9 class="macro">#define MODE_ASK class="num">10 class="macro">#define MODE_POINT class="num">11 class="macro">#define MODE_DIGITS class="num">12 class="macro">#define MODE_SPREAD class="num">13 class="macro">#define MODE_STOPLEVEL class="num">14 class="macro">#define MODE_LOTSIZE class="num">15 class="macro">#define MODE_TICKVALUE class="num">16 class="macro">#define MODE_TICKSIZE class="num">17 class="macro">#define MODE_SWAPLONG class="num">18 class="macro">#define MODE_SWAPSHORT class="num">19 class="macro">#define MODE_STARTING class="num">20 class="macro">#define MODE_EXPIRATION class="num">21 class="macro">#define MODE_TRADEALLOWED class="num">22 class="macro">#define MODE_MINLOT class="num">23 class="macro">#define MODE_LOTSTEP class="num">24 class="macro">#define MODE_MAXLOT class="num">25 class="macro">#define MODE_SWAPTYPE class="num">26 class="macro">#define MODE_PROFITCALCMODE class="num">27 class="macro">#define MODE_MARGINCALCMODE class="num">28 class="macro">#define MODE_MARGININIT class="num">29 class="macro">#define MODE_MARGINMAINTENANCE class="num">30 class="macro">#define MODE_MARGINHEDGED class="num">31 class="macro">#define MODE_MARGINREQUIRED class="num">32 class="macro">#define MODE_FREEZELEVEL class="num">33 class=class="str">"cmt">//--- class="macro">#define EMPTY -class="num">1
◍ 从 MQL4 预定义变量到 MQL5 的取数路径
老 MQL4 脚本里随手写个 Ask、Bars、Close 就能跑,搬进 MQL5 环境会直接编译报错——这些预定义变量在第五版里大多被收编成函数或结构体字段,不再允许裸用。 具体对应关系很直接:询价和出价改用 SymbolInfoTick 填 MqlTick 结构后取 .ask / .bid;柱数用 Bars(_Symbol,_Period) 拿;收盘价、最高价、最低价、开盘价、时间、 tick 成交量都要先声明数组,ArraySetAsSeries 设成时间序列,再用 CopyClose / CopyHigh / CopyLow / CopyOpen / CopyTime / CopyTickVolume 从图表拉数据。 _Digits 和 _Point 是少数保留的内建宏,分别给当前品种小数位和点值,比如欧美对通常 _Digits=5、_Point=0.00001,调止损距离时这两个值会频繁参与计算。外汇与贵金属杠杆高,点值误用可能让仓位风险放大数倍,参数务必在策略测试器里先核一遍。 下面这段是等价改写的最小骨架,复制进 MT5 的 EA 或脚本就能编译验证:
class="type">class="kw">double Ask class="type">MqlTick last_tick; SymbolInfoTick(_Symbol,last_tick); class="type">class="kw">double Ask=last_tick.ask; class="type">int Bars class="type">int Bars=Bars(_Symbol,_Period); class="type">class="kw">double Bid class="type">MqlTick last_tick; SymbolInfoTick(_Symbol,last_tick); class="type">class="kw">double Bid=last_tick.bid; class="type">class="kw">double Close[] class="type">class="kw">double Close[]; class="type">int count; class=class="str">"cmt">// 复制元素的数量 ArraySetAsSeries(Close,true); CopyClose(_Symbol,_Period,class="num">0,count,Close); class="type">int Digits class="type">int Digits=_Digits; class="type">class="kw">double High[] class="type">class="kw">double High[]; class="type">int count; class=class="str">"cmt">// 复制元素的数量 ArraySetAsSeries(High,true); CopyHigh(_Symbol,_Period,class="num">0,count,High); class="type">class="kw">double Low[] class="type">class="kw">double Low[]; class="type">int count; class=class="str">"cmt">// 复制元素的数量 ArraySetAsSeries(Low,true); CopyLow(_Symbol,_Period,class="num">0,count,Low); class="type">class="kw">double Open[] class="type">class="kw">double Open[]; class="type">int count; class=class="str">"cmt">// 复制元素的数量 ArraySetAsSeries(Open,true); CopyOpen(_Symbol,_Period,class="num">0,count,Open); class="type">class="kw">double Point class="type">class="kw">double Point=_Point; class="type">class="kw">datetime Time[] class="type">class="kw">datetime Time[]; class="type">int count; class=class="str">"cmt">// 复制元素的数量 ArraySetAsSeries(Time,true); CopyTime(_Symbol,_Period,class="num">0,count,Time); class="type">class="kw">double Volume[] class="type">long Volume[]; class="type">int count; class=class="str">"cmt">// 复制元素的数量 ArraySetAsSeries(Volume,true); CopyTickVolume(_Symbol,_Period,class="num">0,count,Volume);
「从 Account 系列函数看账户信息读取的迁移」
早期 MT4 的 AccountBalance、AccountEquity 这类函数直接返回账户数值,到了 MT5 被统一收进 AccountInfoDouble / AccountInfoInteger / AccountInfoString 三个接口,靠枚举区分字段。比如余额走 ACCOUNT_BALANCE,净值走 ACCOUNT_EQUITY,杠杆是 ACCOUNT_LEVERAGE 返回的整型。 这种改法把原先十几个独立函数压成三类查询,写 EA 时不用记那么多名字,但老代码迁移要逐行替换。外汇和贵金属交易本身杠杆风险高,读到的 AccountLeverage 若为 500,只是经纪商给的上限,不代表实际安全仓位。 AccountFreeMarginCheck 值得单独看:它接收品种、方向、手数三个参数,返回按当前价建仓后剩多少可用预付款。比起 AccountFreeMargin 的静态值,这个函数能提前算穿仓概率,挂单前调一下更稳。 下面这段是两类函数的对照写法,左边 MT4 风格,右边 MT5 枚举式,复制到 MT5 编译器里改函数名就能验证账户字段是否一致返回。
class="type">class="kw">double AccountBalance() class="type">class="kw">double AccountInfoDouble(ACCOUNT_BALANCE) class="type">class="kw">double AccountCredit() class="type">class="kw">double AccountInfoDouble(ACCOUNT_CREDIT) class="type">class="kw">string AccountCompany() class="type">class="kw">string AccountInfoString(ACCOUNT_COMPANY) class="type">class="kw">string AccountCurrency() class="type">class="kw">string AccountInfoString(ACCOUNT_CURRENCY) class="type">class="kw">double AccountEquity() class="type">class="kw">double AccountInfoDouble(ACCOUNT_EQUITY) class="type">class="kw">double AccountFreeMargin() class="type">class="kw">double AccountInfoDouble(ACCOUNT_FREEMARGIN) class="type">class="kw">double AccountFreeMarginCheck(class="type">class="kw">string symbol, class="type">int cmd, class="type">class="kw">double volume) class="type">class="kw">double AccountFreeMarginMode() class="type">int AccountLeverage() class="type">int AccountInfoInteger(ACCOUNT_LEVERAGE) class="type">class="kw">double AccountMargin() class="type">class="kw">double AccountInfoDouble(ACCOUNT_MARGIN) class="type">class="kw">string AccountName() class="type">class="kw">string AccountInfoString(ACCOUNT_NAME) class="type">int AccountNumber() class="type">int AccountInfoInteger(ACCOUNT_LOGIN) class="type">class="kw">double AccountProfit() class="type">class="kw">double AccountInfoDouble(ACCOUNT_PROFIT) class="type">class="kw">string AccountServer() class="type">class="kw">string AccountInfoString(ACCOUNT_SERVER) class="type">int AccountStopoutLevel() class="type">class="kw">double AccountInfoDouble(ACCOUNT_MARGIN_SO_SO) class="type">int AccountStopoutMode() class="type">int AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE)