交易者生活窍门: 利用 defines (#define) 融合 ForEach·综合运用
把 MQL4 保证金算法映射到 MQL5 的坑
在把老 MQL4 指标移植到 MT5 时,保证金计算模式常需要手动对齐。上面这段 switch 把外汇、期货、CFD 三种模式分别映射到 0、2、1 三个 double 值,default 分支则弹窗报警并返回 NaN,避免静默出错。 遇到未覆盖的 profit_calc_mode,代码会拼出 'MQL4 MODE_MARGINCALCMODE returned MQL5 …' 的提示并同时 Alert 和 Print。你在 MT5 里跑这段,若终端突然弹警告,说明品种用了非标准计价模式,得回代码补 case。 同文件里还用宏封装了运行环境检查:IsConnected 取 TERMINAL_CONNECTED,IsDemo 比对 ACCOUNT_TRADE_MODE_DEMO,IsDllsAllowed 看 TERMINAL_DLLS_ALLOWED。外汇和贵金属杠杆高、波动猛,EA 上线前用这些宏先卡一道环境,可能省掉一半莫名不成交的工单。
case SYMBOL_CALC_MODE_FOREX: class="kw">return((class="type">class="kw">double)class="num">0); case SYMBOL_CALC_MODE_FUTURES: class="kw">return((class="type">class="kw">double)class="num">2); case SYMBOL_CALC_MODE_CFD: class="kw">return((class="type">class="kw">double)class="num">1); class="kw">default : { class="type">class="kw">string text="MQL4 MODE_MARGINCALCMODE returned MQL5 "+EnumToString(profit_calc_mode); Alert(text); Print(text); class="kw">return(class="type">class="kw">double("nan")); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check.mqh | class=class="str">"cmt">//| Copyright class="num">2018, MetaQuotes Software Corp. | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2018, MetaQuotes Software Corp." class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class="macro">#class="kw">property version "class="num">1.003" class=class="str">"cmt">//--- check connection between client and server class="macro">#define IsConnected(class="type">bool)TerminalInfoInteger(TERMINAL_CONNECTED) class=class="str">"cmt">//--- check if Expert Advisor runs on demo account class="macro">#define IsDemo(class="type">bool)(AccountInfoInteger(ACCOUNT_TRADE_MODE)==(ENUM_ACCOUNT_TRADE_MODE)ACCOUNT_TRADE_MODE_DEMO) class=class="str">"cmt">//--- check if DLL function calls are allowed for Expert Advisors class="macro">#define IsDllsAllowed(class="type">bool)TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) class=class="str">"cmt">//--- check if Expert Advisors are allowed to run class="macro">#define IsExpertEnabled(class="type">bool)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) class=class="str">"cmt">//--- check if library function calls are allowed for Expert Advisors class="macro">#define IsLibrariesAllowed(class="type">bool)MQLInfoInteger(MQL_DLLS_ALLOWED) class=class="str">"cmt">//--- check if Expert Advisor runs in Strategy Tester optimization mode class="macro">#define IsOptimization(class="type">bool)MQLInfoInteger(MQL_OPTIMIZATION) class=class="str">"cmt">//--- check if Expert Advisor runs in test mode
◍ 把环境宏和报价函数揉进EA骨架
MT5里有一批预定义宏,本质是给 MQLInfoInteger / TerminalInfoString / SymbolInfoTick 等底层调用套了层短名。比如 IsTesting 展开就是 (bool)MQLInfoInteger(MQL_TESTER),用来判断当前是否在策略测试器里跑;IsTradeAllowed 对应 MQL_TRADE_ALLOWED,确认终端是否放行自动交易。把这些写成 #define,EA里就能直接用 if(IsTradeAllowed) 而不必每次拼长函数。 _Digits、_Point、_Symbol、_Period 这类下划线变量也同理:_Digits 等价于 Digits(),返回当前品种小数位(EURUSD 通常是 5,XAUUSD 常见 2);_Point 是 Point(),即 1 点的绝对值。注意原文里 IsTradeContextBusy 被直接 #define 成 false——这是旧版兼容写法,实盘里终端已不再用忙标志阻塞交易上下文,但复制老代码时要知道它永远返回假。 Ask 和 Bid 的定义值得细看。原文先给出用 SymbolInfoTick 取 tick 的 GetAsk/GetBid 函数,再被 #define Ask SymbolInfoDouble(__Symbol,SYMBOL_ASK) 覆盖。两者都能拿到卖价买价,但后者少一次结构体构造,tick 方式能顺带拿 last/volume。 [CODE] #define IsTesting (bool)MQLInfoInteger(MQL_TESTER) //--- 检查智能交易系统是否允许交易, 且交易前后关联是否繁忙 #define IsTradeAllowed (bool)MQLInfoInteger(MQL_TRADE_ALLOWED) //--- 返回有关交易前后关联的信息 #define IsTradeContextBusy false //--- 检查智能交易系统是否在可视模式下进行测试 #define IsVisualMode (bool)MQLInfoInteger(MQL_VISUAL_MODE) //--- 返回拥有客户终端的公司名称 #define TerminalCompany TerminalInfoString(TERMINAL_COMPANY) //--- 返回客户端名称 #define TerminalName TerminalInfoString(TERMINAL_NAME) //--- 返回客户端启动的目录 #define TerminalPath TerminalInfoString(TERMINAL_PATH) //+------------------------------------------------------------------+ //--- _Digits 变量存储小数点后的位数, #define _Digits Digits() //--- _Point 变量包含报价货币中当前品种的点数大小 #define _Point Point() //--- _LastError 变量包含最后一个错误的代码 #define _LastError GetLastError() //--- _Period 变量包含当前图表的时间帧数值 #define _Period Period() //--- _StopFlag 变量包含程序停止的标志 #define _StopFlag IsStopped() //--- _Symbol 变量包含当前图表的品种名称 #define _Symbol Symbol() //--- _UninitReason 变量包含程序未初始化原因的代码 #define _UninitReason UninitializeReason() //--- 当前品种最后知道的卖方价格 (ask 价格) #define Ask GetAsk() //--- 当前品种最后知道的买方价格 (报价, bid 价格) #define Bid GetBid() double GetAsk() { MqlTick tick; SymbolInfoTick(Symbol(),tick); return(tick.ask); } double GetBid() { MqlTick tick; SymbolInfoTick(Symbol(),tick); return(tick.bid); } #define Ask SymbolInfoDouble(__Symbol,SYMBOL_ASK) [/CODE] 逐行拆一下关键行:第1行 IsTesting 用 MQLInfoInteger 查 MQL_TESTER 状态,强转 bool;IsTradeContextBusy 直接宏成 false,是历史包袱。_Digits 映射到 Digits() 拿小数位,_Point 映射到 Point() 拿点值。GetAsk 函数里先声明 MqlTick tick,再 SymbolInfoTick 填充最新行情,返回 tick.ask;GetBid 对称返回 tick.bid。最后一行 #define Ask 又被重定义为 SymbolInfoDouble(__Symbol,SYMBOL_ASK),编译时后面的定义覆盖前面的函数宏。 外汇和贵金属杠杆高、滑点跳空频繁,用 SymbolInfoTick 还是 SymbolInfoDouble 取价,在流动性断裂时可能拿到不同时间戳的报价。开 MT5 新建一个 EA,把上面宏整段贴进头部,在 OnTick 里 Print(_Symbol, " ", _Digits, " ", Ask, " ", Bid),跑EURUSD和XAUUSD各一分钟,就能验证小数位和点值差异。
class="macro">#define IsTesting(class="type">bool)MQLInfoInteger(MQL_TESTER) class=class="str">"cmt">//--- 检查智能交易系统是否允许交易, 且交易前后关联是否繁忙 class="macro">#define IsTradeAllowed(class="type">bool)MQLInfoInteger(MQL_TRADE_ALLOWED) class=class="str">"cmt">//--- 返回有关交易前后关联的信息 class="macro">#define IsTradeContextBusy class="kw">false class=class="str">"cmt">//--- 检查智能交易系统是否在可视模式下进行测试 class="macro">#define IsVisualMode(class="type">bool)MQLInfoInteger(MQL_VISUAL_MODE) class=class="str">"cmt">//--- 返回拥有客户终端的公司名称 class="macro">#define TerminalCompany TerminalInfoString(TERMINAL_COMPANY) class=class="str">"cmt">//--- 返回客户端名称 class="macro">#define TerminalName TerminalInfoString(TERMINAL_NAME) class=class="str">"cmt">//--- 返回客户端启动的目录 class="macro">#define TerminalPath TerminalInfoString(TERMINAL_PATH) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- _Digits 变量存储小数点后的位数, class="macro">#define _Digits Digits() class=class="str">"cmt">//--- _Point 变量包含报价货币中当前品种的点数大小 class="macro">#define _Point Point() class=class="str">"cmt">//--- _LastError 变量包含最后一个错误的代码 class="macro">#define _LastError GetLastError() class=class="str">"cmt">//--- _Period 变量包含当前图表的时间帧数值 class="macro">#define _Period Period() class=class="str">"cmt">//--- _StopFlag 变量包含程序停止的标志 class="macro">#define _StopFlag IsStopped() class=class="str">"cmt">//--- _Symbol 变量包含当前图表的品种名称 class="macro">#define _Symbol Symbol() class=class="str">"cmt">//--- _UninitReason 变量包含程序未初始化原因的代码 class="macro">#define _UninitReason UninitializeReason() class=class="str">"cmt">//--- 当前品种最后知道的卖方价格 (ask 价格) class="macro">#define Ask GetAsk() class=class="str">"cmt">//--- 当前品种最后知道的买方价格 (报价, bid 价格) class="macro">#define Bid GetBid() class="type">class="kw">double GetAsk() { class="type">MqlTick tick; SymbolInfoTick(Symbol(),tick); class="kw">return(tick.ask); } class="type">class="kw">double GetBid() { class="type">MqlTick tick; SymbolInfoTick(Symbol(),tick); class="kw">return(tick.bid); } class="macro">#define Ask SymbolInfoDouble(__Symbol,SYMBOL_ASK)
「宏定义能塞进整个类」
MQL5 的 #define 不只是替换常量,参数形式 #define identifier(par1,... par8) expression 允许把一段带参表达式整体替换,甚至能把一个完整类塞进宏体里。
上面例子里 SeriesVolume(Volume,T) 第一次被定义为字面量 159,在 OnStart 中 long a=SeriesVolume(Volume,long); 展开后就是 long a=159;,编译期直接固化。
同一宏名后面被重定义为 class CVolume { ... },用反斜杠续行包裹了一个只读下标运算符:用 CopyTickVolume 取第 i 根 K 线的tick成交量,成功返回 long 值,失败返回 -1。这种写法在 MT5 里可能让序列访问语法更像数组,但宏重定义会覆盖前面的值,调试时容易看错展开结果。
外汇与贵金属杠杆高,这类底层封装若返回 -1 没处理,策略可能误判成交量缺失为真实零值,实盘概率上会放大滑点风险。开 MT5 新建脚本粘下面代码,能直接看宏展开差异。
class="macro">#define identifier(par1,... par8) expression class=class="str">"cmt">// 参数形式 class="macro">#define SeriesVolume(Volume,T) class="num">159 class=class="str">"cmt">//+-----------------------------------------------------------------+ class=class="str">"cmt">//| 脚本程序 start 函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">long a=SeriesVolume(Volume,class="type">long); } class="type">long a=class="num">159; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Test_en.mq5 | class=class="str">"cmt">//| 版权所有 class="num">2012, 公司名称 | class=class="str">"cmt">//| http://www.companyname.net | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define SeriesVolume(Volume,T) class CVolume \ { \ class="kw">public: \ T class="kw">operator[](class="kw">const class="type">int i) class="kw">const \ { \ class="type">long val[class="num">1]; \ if(CopyTickVolume(Symbol(),Period(),i,class="num">1,val)==class="num">1)\ class="kw">return(val[class="num">0]); \ else \ class="kw">return(-class="num">1); \ } \
用类封装把 Tick 成交量拉进脚本
在 MT5 脚本里直接读 Volume[i] 往往拿不到实时 tick 量,更稳的做法是把取数逻辑包进一个类,重载 operator[] 让调用端像数组一样用。下面这段把 CVolume 类挂上全局对象 Volume,OnStart 里 Print(Volume[4]) 就能打出倒数第 5 根 K 线的 tick 成交量。 类内部用 CopyTickVolume(Symbol(),Period(),i,1,val) 按索引拷 1 根的量到局部数组;返回 1 表示成功,直接返回 val[0],失败则返回 -1。这样写比每次手动调 API 更干净,也方便在多个脚本里复用。 实盘里外汇和贵金属波动大、滑点频繁,tick 成交量只是市场活跃度的概率性参考,不能单独当成方向信号;开 MT5 新建脚本粘下面代码,改 Print 里的下标就能验证不同柱子的量。
class CVolume { class="kw">public: class="type">long class="kw">operator[](class="kw">const class="type">int i) class="kw">const { class="type">long val[class="num">1]; if(CopyTickVolume(Symbol(),Period(),i,class="num">1,val)==class="num">1) class="kw">return(val[class="num">0]); else class="kw">return(-class="num">1); }; }; CVolume Volume; class=class="str">"cmt">//--- SeriesVolume(Volume,class="type">long) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 脚本程序 start 函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { Print(Volume[class="num">4]); }
◍ 用 iClose 封装跨周期取价与指标模式常量
在 MT5 脚本里直接写 Volume[4] 只能拿到当前图表第 5 根柱(索引从 0 计)的成交量,换品种或时间帧就抓瞎。下面这段把取收盘价逻辑包成了一个 iClose 函数,指定 symbol、timeframe 和 shift 就能拉到任意品种的闭合价。 代码里的 CopyClose 只拷 1 根柱到 val 数组,copied>0 才赋值,否则用 GetLastError 把错误码打进日志。实盘跑外汇或贵金属前,建议先在非交易时段用 Print(iClose("XAUUSD",PERIOD_M15,0)) 验证返回是不是正常双精度数,这类品种点差跳变时可能返回 0.0 或 NaN。 后面那一串 #define 是把旧版 MQL4 的 MODE_ 宏在 MQL5 里重新映射:比如 MODE_MAIN 是 0、MODE_SIGNAL 是 1,一目均衡里 MODE_TENKANSEN=1、MODE_KIJUNSEN=2 直到 MODE_CHIKOUSPAN=5。自己写指标缓冲区索引时直接套这些宏,比裸写数字可读性强,也方便以后改参数。 别把 NaN 当正常价用 代码末尾用 double NaN=double("nan") 造了个非数,若 CopyClose 失败返回 0.0 而不是 NaN,你在 EA 里拿它算均线不会报错但会污染序列。更稳的做法是失败分支直接返回 NaN,调用层用 MathIsValidNumber 拦掉。
class="type">void OnStart() { Print(Volume[class="num">4]); } class=class="str">"cmt">//--- 返回指定品种、时间帧和偏移柱线的收盘价 class="type">class="kw">double iClose( class="type">class="kw">string symbol, class=class="str">"cmt">// 品种 ENUM_TIMEFRAMES timeframe, class=class="str">"cmt">// 时间帧 class="type">int shift class=class="str">"cmt">// 偏移 ) { class="type">class="kw">double result=class="num">0.0; class=class="str">"cmt">//--- class="type">class="kw">double val[class="num">1]; ResetLastError(); class="type">int copied=CopyClose(symbol,timeframe,shift,class="num">1,val); if(copied>class="num">0) result=val[class="num">0]; else Print(__FUNCTION__,": CopyClose error=",GetLastError()); class=class="str">"cmt">//--- class="kw">return(result); } class="type">class="kw">double NaN=class="type">class="kw">double("nan"); class="macro">#define MODE_MAIN class="num">0 class="macro">#define MODE_SIGNAL class="num">1 class="macro">#define MODE_PLUSDI class="num">1 class="macro">#define MODE_MINUSDI class="num">2 class="macro">#define MODE_GATORJAW class="num">1 class="macro">#define MODE_GATORTEETH class="num">2 class="macro">#define MODE_GATORLIPS class="num">3 class="macro">#define MODE_UPPER class="num">1 class="macro">#define MODE_LOWER class="num">2 class="macro">#define MODE_TENKANSEN class="num">1 class="macro">#define MODE_KIJUNSEN class="num">2 class="macro">#define MODE_SENKOUSPANA class="num">3 class="macro">#define MODE_SENKOUSPANB class="num">4 class="macro">#define MODE_CHIKOUSPAN class="num">5 class="macro">#define MODE_OPEN class="num">0
「重定义价格模式常量避开系统冲突」
在 MT5 的自定义头文件里直接沿用 MQL5 内置的 MODE_LOW、MODE_HIGH 等宏,容易和终端预定义常量撞车。上面这段头文件把低频用的取值改成 10001、10002、10005,等于在用户层重新划了一块编号空间。
注释里保留的 #define MODE_CLOSE 3 和 #define MODE_VOLUME 4 仍是原值,说明只有低/高/时间三类被抬到万级区间,收盘与成交量维持原生映射。开 MT5 新建一个 EA 头文件,把这段贴进去编译,若之前报过 'MODE_LOW redefinition' 类错误,现在应能过。
外汇与贵金属杠杆高,这类底层常量改错会导致取价返回空值,实盘前务必在策略测试器用历史数据跑一遍 iOpen/iHigh 调用验证。
class=class="str">"cmt">//class="macro">#define MODE_LOW class="num">1 class=class="str">"cmt">//class="macro">#define MODE_HIGH class="num">2 class="macro">#define MODE_CLOSE class="num">3 class="macro">#define MODE_VOLUME class="num">4 class=class="str">"cmt">//class="macro">#define MODE_TIME class="num">5 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Header.mqh | class=class="str">"cmt">//| 版权所有 class="num">2018, MetaQuotes 软件公司 | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司" class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class=class="str">"cmt">//--- class="macro">#define MODE_LOW class="num">10001 class="macro">#define MODE_HIGH class="num">10002 class="macro">#define MODE_TIME class="num">10005
宏迁移方案的取舍与余味
把 MQL4 风格 EA 搬进 MetaTrader 5,核心就是挂上重载函数与预定义变量的头文件,再用 #define 做字符串替换。实测附带的 ForEachOrder 宏在回测中每多挂一个指标,测试速度会出现可感知的下降,这是宏方案绕不开的代价。 缺点里最致命的是 #define 宏替代无法调试,变量藏在宏后面还可能冲突;优点则是代码量骤减、宏以红色高亮便于辨识。下方代码里 ForEachOrder 混用了 HistorySelect 与 OrdersTotal,社区已有指出:若只处理未结订单本不需要 HistorySelect,这类坑正好印证了不可调试的隐患。 #define ForEachSymbol(s,i) string s=SymbolName(0,true); int os_total=SymbolsTotal(true); for(int i=1;i<os_total;i++,s=SymbolName(i,true)) #define ForEachOrder(ticket,i) HistorySelect(0,TimeCurrent()); ulong ticket=OrderGetTicket(0); int or_total=OrdersTotal(); for(int i=1;i<or_total;i++,ticket=OrderGetTicket(i)) 本人仍倾向传统 MQL5 写法,宏只是过渡期的生活窍门。习惯 MQL4 的人借这套头文件大概率能跨过平台切换的心理门槛,但外汇与贵金属交易杠杆高、风险大,实盘前务必在 MT5 策略测试器里先跑通再谈信任。
class="macro">#define ForEachSymbol(s,i) class="type">class="kw">string s=SymbolName(class="num">0,true); class="type">int os_total=SymbolsTotal(true); for(class="type">int i=class="num">1;i<os_total;i++,s=SymbolName(i,true)) class="macro">#define ForEachOrder(ticket,i) HistorySelect(class="num">0,TimeCurrent()); class="type">class="kw">ulong ticket=OrderGetTicket(class="num">0); class="type">int or_total=OrdersTotal(); for(class="type">int i=class="num">1;i<or_total;i++,ticket=OrderGetTicket(i)) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 脚本程序启动功能 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- ForEachOrder(orderticket,index) { Print(index,": #",orderticket," ",OrderGetString(ORDER_SYMBOL)," ", EnumToString((ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE))); } class=class="str">"cmt">/* 输出示例 class="num">1: class="num">13965457 CADJPY ORDER_TYPE_SELL_LIMIT class="num">2: class="num">14246567 AUDNZD ORDER_TYPE_SELL_LIMIT */ }