从 MQL4 迁移到 MQL5·进阶篇
(2/3)· 老策略搬进 MT5 最易翻车的不是语法,而是周期数值与预定义变量的映射错位
「数组操作的内置函数清单」
MT5 里处理数组不用自己造轮子,语言层给了一组现成函数。一维数字数组里找某个值用 ArrayBsearch,它返回的是命中元素的位置索引而不是值本身;复制数组用 ArrayCopy,要求两边类型一致,double[]、int[]、datetime[]、color[]、bool[] 之间同类型可拷,返回的是实际复制的元素个数。 时间序列数据有专门的搬移通道。ArrayCopyRates 把当前图表 K 线数据塞进 RateInfo[][6] 这种二维数组,失败返回 -1;ArrayCopySeries 则把 Time[]、Open[] 这类序列数组复制到自定义数组,返回复制数量。注意序列数组本身不能当普通数组随意排,ArraySort 明确禁止直接排序列数组。 维度与容量类函数常被忽略但很实用。ArrayDimension 看数组是几维,ArrayRange 取指定维度元素数,ArrayResize 只改第一维大小,ArraySize 给总元素数。ArrayIsSeries 和 ArrayGetAsSeries 能区分普通数组与时间序列组织方式——前者看是不是那几个内置序列,后者看索引方向是否从尾到头。外汇和贵金属杠杆高,用这些函数批量处理历史数据时务必先校验返回长度,避免越界读取。 下面这段声明覆盖了搜索与复制的核心签名,开 MT5 新建脚本把 ArrayBsearch 和 ArrayCopy 的参数改改就能直观看到索引与复制量变化。
class="type">int ArrayBsearch(class="type">class="kw">double array[], class="type">class="kw">double value, class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0, class="type">int direction=MODE_ASCEND) class="type">int ArrayBsearch(class="type">class="kw">double array[], class="type">class="kw">double searched_value ) class="type">int ArrayCopy(object&dest[], object source[], class="type">int start_dest=class="num">0, class="type">int start_ class="type">int count=WHOLE_ARRAY) class="type">int ArrayCopy(class="type">void dst_array[], class="type">void src_array[], class="type">int dst_start=class="num">0, class="type">int src_start=class="num">0, class="type">int cnt=WHOLE_ARRAY ) class="type">int ArrayCopyRates(class="type">class="kw">double&dest_array[], class="type">class="kw">string symbol=NULL, class="type">int timeframe=class="num">0) class="type">int ArrayCopySeries(class="type">class="kw">double&array[], class="type">int series_index, class="type">class="kw">string symbol=NULL, class="type">int timeframe=class="num">0) class="type">int ArrayCopySeriesMQL4(class="type">class="kw">double &array[], class="type">int series_index, class="type">class="kw">string symbol=NULL, class="type">int tf=class="num">0) { ENUM_TIMEFRAMES timeframe=TFMigrate(tf); class="type">int count=Bars(symbol,timeframe); class="kw">switch(series_index) { case MODE_OPEN:
数组与行情拷贝的兼容层写法
在把旧版指标逻辑搬进 MT5 时,最容易被卡住的就是数组函数签名变了。上面这段兼容层把 MQL4 风格的函数名重新包了一层,让老代码少改几行就能编译过。
以 CopyOpen / CopyLow / CopyHigh / CopyClose 为例,四个 case 都从偏移 0 开始取 count 根 K 线写入 array,默认分支直接 return(0) 表示未匹配到合法模式。
ArrayMaximumMQL4 与 ArrayMinimumMQL4 的坑在于参数顺序:老接口是 (array, count, start),新接口是 (array, start, count),包装函数里做了一次对调,否则取极值的位置会整体偏移。
ArrayInitializeMQL4 只是顺手调了 ArrayInitialize 再返回 ArraySize,纯属为了老脚本里 int n = ArrayInitializeMQL4(...) 这种写法不报错。外汇与贵金属杠杆高,这类兼容代码只解决编译问题,不暗示任何信号胜率。
class="kw">return(CopyOpen(symbol,timeframe,class="num">0,count,array)); case MODE_LOW: class="kw">return(CopyLow(symbol,timeframe,class="num">0,count,array)); case MODE_HIGH: class="kw">return(CopyHigh(symbol,timeframe,class="num">0,count,array)); case MODE_CLOSE: class="kw">return(CopyClose(symbol,timeframe,class="num">0,count,array)); class="kw">default: class="kw">return(class="num">0); } class="kw">return(class="num">0); } class="type">int ArrayDimension( object array[]) class="type">bool ArrayGetAsSeries( object array[]) class="type">bool ArrayGetAsSeries(class="type">void array) class="type">int ArrayInitialize(class="type">class="kw">double &array[], class="type">class="kw">double value) class="type">int ArrayInitializeMQL4(class="type">class="kw">double &array[], class="type">class="kw">double value) { ArrayInitialize(array,value); class="kw">return(ArraySize(array)); } class="type">bool ArrayIsSeries( object array[]) class="type">bool ArrayIsSeries(class="type">void array[]) class="type">int ArrayMaximum(class="type">class="kw">double array[], class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0) class="type">int ArrayMaximumMQL4(class="type">class="kw">double &array[], class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0) { class="kw">return(ArrayMaximum(array,start,count)); } class="type">int ArrayMinimum(class="type">class="kw">double array[], class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0) class="type">int ArrayMinimumMQL4(class="type">class="kw">double &array[], class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0) { class="kw">return(ArrayMinimum(array,start,count)); } class="type">int ArrayRange(object array[], class="type">int range_index) class="type">int ArrayRange(class="type">void array[], class="type">int rank_index ) class="type">int ArrayResize(object &array[], class="type">int new_size) class="type">int ArrayResize(class="type">void array[], class="type">int new_size, class="type">int allocated_size=class="num">0
◍ 数组序列方向与排序的坑
MT5 里 ArraySetAsSeries 决定数组索引 0 是落在时间序列最老还是最新一根 K 线上。传 true 表示按时间倒序(索引 0 为最新),传 false 则是正序,这个设定直接影响你取 Close[0] 拿到的是哪根收盘价。 ArraySize 对 double 数组和 void 数组都有重载,返回元素个数;在 EA 初始化时打印 ArraySize 能快速确认你申请的缓冲区长度是否符合预期,比如申请 100 根却只收到 50 根,往往是复制范围写错。 下面这段自定义 ArraySortMQL4 想用 sort_dir 控制升序降序,但 switch 里每个 case 都没写 break,MODE_ASCEND 会穿透到 MODE_DESCEND 再落到 default,最终永远执行 ArraySetAsSeries(array,true) 然后正序排。这类笔误在回测里不会报错,却会让你的“降序取极值”逻辑静默失效。 外汇与贵金属杠杆高、滑点随机,任何基于数组排序的止损价抓取都要先在策略测试器跑一遍极端行情,确认索引方向没反。
class="type">bool ArraySetAsSeries(class="type">class="kw">double &array[], class="type">bool set) class="type">bool ArraySetAsSeries(class="type">void array[], class="type">bool set ) class="type">int ArraySize( object array[]) class="type">int ArraySize(class="type">void array[]) class="type">int ArraySort(class="type">class="kw">double &array[], class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0, class="type">int sort_dir=MODE_ASCEND) class="type">int ArraySortMQL4(class="type">class="kw">double &array[], class="type">int count=WHOLE_ARRAY, class="type">int start=class="num">0, class="type">int sort_dir=MODE_ASCEND) { class="kw">switch(sort_dir) { case MODE_ASCEND: ArraySetAsSeries(array,true); case MODE_DESCEND: ArraySetAsSeries(array,class="kw">false); class="kw">default: ArraySetAsSeries(array,true); } ArraySort(array); class="kw">return(class="num">0); }
「EA 运行前的环境自检清单」
在 MT5 里跑 EA 或脚本,先确认运行环境再下单,比事后查日志省事。MQL5 提供一组状态函数,覆盖连接、账户类型、权限与测试模式,能在初始化阶段筛掉九成低级故障。 GetLastError 取最后一次错误码后,会把 last_error 存储位清零,所以不要连续调用两次指望拿到同一个码。IsConnected 看主连接,IsDemo 用 AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO 判断演示账户,IsDllsAllowed 对应 TERMINAL_DLLS_ALLOWED,这几个返回值直接决定你的 EA 能否正常发单或加载外部库。 IsOptimization、IsTesting、IsVisualMode 都走 MQLInfoInteger,分别在策略测试优化、回测、可视模式下返回 TRUE。IsTradeAllowed 额外要求交易线程未被占用,若被别的 EA 占着就会 FALSE,这时硬发单可能触发 137 错码。 下面这段把原文里的判断接口摊开,注意 IsDemoMQL4 是手写的演示账户判断,不是系统原生命名,复制时别照抄成系统函数。
class="type">int GetLastError() class="type">int GetLastError() class="type">bool IsConnected() class="type">bool TerminalInfoInteger(TERMINAL_CONNECTED) class="type">bool IsDemo() class="type">bool IsDemoMQL4() { if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO) class="kw">return(true); else class="kw">return(class="kw">false); } class="type">bool IsDllsAllowed() class="type">bool TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) class="type">bool IsExpertEnabled() class="type">bool AccountInfoInteger(ACCOUNT_TRADE_EXPERT) class="type">bool IsLibrariesAllowed() class="type">bool MQLInfoInteger(MQL5_DLLS_ALLOWED) class="type">bool IsOptimization() class="type">bool MQLInfoInteger(MQL5_OPTIMIZATION) class="type">bool IsStopped() class="type">bool IsStopped() class="type">bool IsTesting() class="type">bool MQLInfoInteger(MQL5_TESTING) class="type">bool IsTradeAllowed() class="type">bool MQLInfoInteger(MQL5_TRADE_ALLOWED) class="type">bool IsTradeContextBusy() class="type">bool IsVisualMode() class="type">bool MQLInfoInteger(MQL5_VISUAL_MODE) class="type">int UninitializeReason() class="type">int UninitializeReason()
扒开 MT5 客户端的底牌
想在 EA 里确认当前跑的是哪家券商的终端、叫什么名字、装在哪个目录?MQL5 把这三个信息统一收进了 TerminalInfoString 的枚举参数里,老 MQL4 的 TerminalCompany / TerminalName / TerminalPath 只是它的语法糖。 具体映射关系是:TERMINAL_COMPANY 对应券商公司名,TERMINAL_NAME 对应客户端程序名(通常是 MetaTrader 5),TERMINAL_PATH 对应启动目录的完整路径。写日志或做 broker 白名单时直接调这三个枚举最稳。
class="type">class="kw">string TerminalCompany() class="type">class="kw">string TerminalInfoString(TERMINAL_COMPANY) class="type">class="kw">string TerminalName() class="type">class="kw">string TerminalInfoString(TERMINAL_NAME) class="type">class="kw">string TerminalPath() class="type">class="kw">string TerminalInfoString(TERMINAL_PATH)
class="type">class="kw">string TerminalCompany() class="type">class="kw">string TerminalInfoString(TERMINAL_COMPANY) class="type">class="kw">string TerminalName() class="type">class="kw">string TerminalInfoString(TERMINAL_NAME) class="type">class="kw">string TerminalPath() class="type">class="kw">string TerminalInfoString(TERMINAL_PATH)
◍ 从 MQL4 公共函数到 MT5 的迁移落点
MQL4 里那批常用公共函数在 MT5 并非一一对应,多数被拆进更细的 SymbolInfo 系列或保留同名重载。Alert、Comment、Print、Sleep、MessageBox、PlaySound 这类与图表和终端交互的接口,两边写法几乎一致,参数可任意类型或指定间隔毫秒。 MarketInfo 是迁移时最容易踩坑的一个。MT5 取消了统一的 MarketInfo(symbol,type) 入口,改由 SymbolInfoInteger 取整型字段(如 SYMBOL_DIGITS、SYMBOL_SPREAD),SymbolInfoDouble 取浮点字段(如 SYMBOL_POINT、SYMBOL_TICK_VALUE),实时买卖价直接用 Bid / Ask 全局变量。 下面这段兼容封装把 MQL4 的 MODE_* 常量映射到 MT5 属性,复制进 EA 即可让老代码少改几行。注意 MODE_STARTING、MODE_EXPIRATION、MODE_TRADEALLOWED 在 MT5 环境直接返回 0,老逻辑里若依赖这些标志需另写判断。 外汇与贵金属杠杆高、点差跳变频繁,用 SymbolInfoInteger 取 SYMBOL_SPREAD 做风控阈值时,回测与实盘可能偏差明显,建议先在策略测试器跑一轮再上真仓。
class="type">void Alert(...) class="type">void Alert(argument,...) class="type">void Comment(...) class="type">void Comment(argument,...) class="type">int GetTickCount() class="type">uint GetTickCount() class="type">class="kw">double MarketInfo(class="type">class="kw">string symbol, class="type">int type) class="type">class="kw">double MarketInfoMQL4(class="type">class="kw">string symbol, class="type">int type) { class="kw">switch(type) { case MODE_LOW: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_LASTLOW)); case MODE_HIGH: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_LASTHIGH)); case MODE_TIME: class="kw">return(SymbolInfoInteger(symbol,SYMBOL_TIME)); case MODE_BID: class="kw">return(Bid); case MODE_ASK: class="kw">return(Ask); case MODE_POINT: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_POINT)); case MODE_DIGITS: class="kw">return(SymbolInfoInteger(symbol,SYMBOL_DIGITS)); case MODE_SPREAD: class="kw">return(SymbolInfoInteger(symbol,SYMBOL_SPREAD)); case MODE_STOPLEVEL: class="kw">return(SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL)); case MODE_LOTSIZE: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE)); case MODE_TICKVALUE: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE)); case MODE_TICKSIZE: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE)); case MODE_SWAPLONG: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_SWAP_LONG)); case MODE_SWAPSHORT: class="kw">return(SymbolInfoDouble(symbol,SYMBOL_SWAP_SHORT)); case MODE_STARTING: class="kw">return(class="num">0); case MODE_EXPIRATION: class="kw">return(class="num">0); case MODE_TRADEALLOWED: class="kw">return(class="num">0); case MODE_MINLOT: