交易者生活窍门: 利用 defines (#define) 融合 ForEach(基础篇)
📘

交易者生活窍门: 利用 defines (#define) 融合 ForEach(基础篇)

第 1/3 篇

用 #define 把 MQL4 习惯带进 MT5

从 MQL4 切到 MQL5,最别扭的往往不是语法本身,而是旧代码里一堆顺手写的 MQL4 函数名和宏。MetaEditor 其实早就留了口子:靠预处理器 #define,你可以把老写法直接映射成 MQL5 的等价调用,不用逐行重写。 Vladimir Karputov 在 2018-04-12 发的这篇笔记,到统计时点有 12163 次浏览、62 条评论,说明从 MQL4 迁移过来的人确实卡在这一步。核心思路就一句:用 #define 给 MQL5 的函数起一个你肌肉记忆里的 MQL4 名字。 比如你以前习惯写 OrderSend(...),在 MQL5 里下单要走 CTrade 类,那就可以在头文件里 define 一层薄封装,让老代码编译通过。代价是可读性对纯 MQL5 程序员略怪,但对你自己回迁策略库,能省掉大半夜的机械替换。开 MT5 新建一个 mqh,先 define 三个最常用的旧函数,跑通一个小 EA 再说。

◍ 用宏定义把循环写懒一点

MT5 里遍历市场观察、历史订单、图表对象几乎逃不开 for 循环,MetaEditor 自带的 snippets 只覆盖最基础的写法,遇到‘市场观察里每个品种跑一遍’这种需求还得手敲。想少写重复代码,可以绕开官方 snippet,直接用 #define 造自己的伪循环宏。 最开始容易踩的坑:下面这段宏在 i=0 首次迭代时 s 根本没初始化,按 F5 调试会打出 ‘1. (null)’ 然后才正常出 GBPUSD、USDCHF 等。 #define ForEachSymbol(s,i) string s; int total=SymbolsTotal(true); for(int i=0;i<total;i++,s=SymbolName(i,true)) 根子在 for 语句的执行顺序:expression1 做初始化,expression2 每次判断,expression3 在‘每次迭代之后’才算。所以把 s 的赋值挪到 i=1 起步、并在定义时先给 s 赋 SymbolName(0,true) 就能修好: #define ForEachSymbol(s,i) string s=SymbolName(0,true); int total=SymbolsTotal(true); for(int i=1;i<total;i++,s=SymbolName(i,true)) 修完后 OnStart 里写 ForEachSymbol(symbol,index){PrintFormat("%d. %s",index,s);} 就能干净列出品种。同样套路可扩到对象、订单、仓位,宏字符串太长时用反斜杠 \ 换行切断,编译器仍当一行看。 宏不是万灵药:变量被藏进宏里且外部可见,多个宏并存时必须给各自的 total 加前缀避免冲突;编译器也不会像函数那样给参数提示。另外 HistorySelect() 失败返回 false 时,订单遍历宏不会报错也只是空跑——外汇和贵金属自动化高风险,这类静默失败要在调用处自己兜底。

MQL5 / C++
class="macro">#define ForEachSymbol(s,i)  class="type">class="kw">string s; class="type">int total=SymbolsTotal(true); for(class="type">int i=class="num">0;i<total;i++,s=SymbolName(i,true))

class="type">void OnStart()
  {
   ForEachSymbol(symbol,index)
     {
      PrintFormat("%d. %s",index,symbol);
     }
  }

class=class="str">"cmt">// 调试输出片段:
class=class="str">"cmt">// class="num">1. (null)
class=class="str">"cmt">// class="num">2. GBPUSD
class=class="str">"cmt">// class="num">3. USDCHF
class=class="str">"cmt">// class="num">4. USDJPY

class=class="str">"cmt">// 修正版:
class="macro">#define ForEachSymbol(s,i)  class="type">class="kw">string s=SymbolName(class="num">0,true); class="type">int total=SymbolsTotal(true); for(class="type">int i=class="num">1;i<total;i++,s=SymbolName(i,true))

「用宏批量扫一遍市场与图表对象」

在 MT5 脚本里逐个处理交易品种或图表对象,最忌手写一堆 for 循环把代码撑得又臭又长。用宏把遍历逻辑封装掉,主函数里只留真正关心的数据打印,可读性立刻不一样。 下面这段先定义了 ForEachSymbol 宏,在 OnStart 里直接按 index 打印品种名。注意注释里写 index+1 由 index 替代,意思是宏内部已经用 0 基的 index 充当序号,不需要再手动加一。 [CODE] void OnStart() { //--- ForEachSymbol(symbol,index) { PrintFormat("%d. %s",index,symbol); // index+1 由 index 替代 } } [/CODE] 真正有含金量的是带点差测算的版本:用 SymbolInfoDouble 取 SYMBOL_ASK 和 SYMBOL_BID 相减得到绝对点差,再除以 SYMBOL_POINT 换算成点数,并用 SymbolInfoInteger 取小数位。回测环境实测输出如 EURUSD spread=3 points (5 digits)、USDSEK spread=150 points,不同品种点差量级差异极大,外汇贵金属的高风险部分就藏在流动性和点差里。 [CODE] void OnStart() { //--- ForEachSymbol(symbol,index) { //--- 准备数据 double spread=SymbolInfoDouble(symbol,SYMBOL_ASK)-SymbolInfoDouble(symbol,SYMBOL_BID); double point=SymbolInfoDouble(symbol,SYMBOL_POINT); long digits=SymbolInfoInteger(symbol,SYMBOL_DIGITS); string str_spread=DoubleToString(spread/point,0); string str_point=DoubleToString(point,digits); //--- 显示数据 Print(index,". ",symbol," spread=",str_spread," points (", digits," digits",", point=",str_point,")"); } } [/CODE] 图表上的画线、箭头也能如法炮制。ForEachObject 宏借助 ObjectsTotal 和 ObjectName 把当前图所有图形对象名挨个吐出来,方便你写清理脚本或批量改样式,而不用对着对象列表手动点。 [CODE] #define ForEachObject(name,i) string name=ObjectName(0,0); int total=ObjectsTotal(0); for(int i=1;i<=total;i++,name=ObjectName(0,i-1)) void OnStart() { //--- ForEachObject(objectname,index) { Print(index,": objectname=\"",objectname,"\", objecttype=", [/CODE] 开 MT5 新建脚本,把带点差那段直接贴进去跑,可能你会发现模拟账户里 USDSEK 点差常年三位数,这种品种做日内大概率被手续费吃掉。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   ForEachSymbol(symbol,index)
     {
      PrintFormat("%d. %s",index,symbol); class=class="str">"cmt">// index+class="num">1 由 index 替代
     }
  }

class="type">void OnStart()
  {
class=class="str">"cmt">//---
   ForEachSymbol(symbol,index)
     {
      class=class="str">"cmt">//--- 准备数据
      class="type">class="kw">double spread=SymbolInfoDouble(symbol,SYMBOL_ASK)-SymbolInfoDouble(symbol,SYMBOL_BID);
      class="type">class="kw">double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      class="type">long digits=SymbolInfoInteger(symbol,SYMBOL_DIGITS);
      class="type">class="kw">string str_spread=DoubleToString(spread/point,class="num">0);
      class="type">class="kw">string str_point=DoubleToString(point,digits);
      class=class="str">"cmt">//--- 显示数据
      Print(index,". ",symbol," spread=",str_spread," points(",
            digits," digits",", point=",str_point,")");
     }
  }

class="macro">#define ForEachObject(name,i)  class="type">class="kw">string name=ObjectName(class="num">0,class="num">0); class="type">int total=ObjectsTotal(class="num">0); for(class="type">int i=class="num">1;i<=total;i++,name=ObjectName(class="num">0,i-class="num">1))
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   ForEachObject(objectname,index)
     {
      Print(index,": objectname=\"",objectname,"\", objecttype=",

用宏把图表对象和订单持仓遍历写薄

在 MT5 脚本里逐个翻对象、订单、持仓,原生 for 循环写起来啰嗦且容易下标错。用预处理器宏把遍历封装掉,主逻辑就能只剩一层 Print,可读性直接上一个台阶。 下面这段宏把图表对象遍历收成一行:ForEachObject 先取 ObjectsTotal 总数,再从索引 1 开始把 ObjectName(0,i-1) 逐个塞进 name。实际跑出来可能看到『1: objectname="H1 Arrow 61067", objecttype=OBJ_ARROW_UP』直到第 4 个 STDDEVCHANNEL 趋势通道,说明宏确实把 4 类挂件都扫到了。 订单侧同理,ForEachOrder 先 HistorySelect(0,TimeCurrent()) 拉历史,再用 OrdersTotal 和 OrderGetTicket(i) 走遍。样例输出里出现『#13965457 CADJPY ORDER_TYPE_SELL_LIMIT』和『#14246567 AUDNZD ORDER_TYPE_SELL_LIMIT』,证明限价空单能被稳定枚举。外汇与贵金属杠杆高,历史订单扫描仅作仓位核对,不构成任何方向建议。 持仓宏 ForEachPosition 结构一致,PositionsTotal 配 PositionGetTicket(i-1)。把这三组宏存进常用库,下次写 EA 调试时直接套,省下的时间够你多跑两组品种波动回测。

MQL5 / C++
class="macro">#define ForEachObject(name,i)  class="type">class="kw">string name=ObjectName(class="num">0,class="num">0); \
  class="type">int ob_total=ObjectsTotal(class="num">0); \
  for(class="type">int i=class="num">1;i<=ob_total;i++,name=ObjectName(class="num">0,i-class="num">1))
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="macro">#define ForEachPosition(ticket,i) HistorySelect(class="num">0,TimeCurrent());  \
  class="type">class="kw">ulong ticket=PositionGetTicket(class="num">0);                                \
  class="type">int po_total=PositionsTotal();                                    \
  for(class="type">int i=class="num">1;i<=po_total;i++,ticket=PositionGetTicket(i-class="num">1))

◍ 用宏把持仓和历史记录遍历写薄

在 MT5 里逐个翻持仓、成交、历史订单,原生 API 要写三套差不多的循环,很容易抄错序号。用宏把遍历逻辑收口,脚本主体就能只剩一两行 Print,可读性直接上来。 下面这段把持仓遍历封装成 ForEachPosition,实际跑出来会按索引打出品种和持仓 ID,例如「1: AUDCAD postionID #13234934」一直到第 4 个 EURUSD 持仓,说明当前池子里混着交叉盘和直盘。 历史成交用 ForEachDeal 宏,先 HistorySelect(0,TimeCurrent()) 拉全量,再靠 HistoryDealGetTicket 取每笔成交的 ticket,顺手用 DEAL_ORDER 把关联下单号也打出来,排查对冲或分批平仓时很实用。 历史订单另有 ForEachHistoryOrder 宏,结构同上只是换 HistoryOrderGetTicket。外汇和贵金属杠杆高,这类脚本只做本地诊断,别拿它当信号源,回测结论也仅代表过去数据。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   ForEachPosition(positionid,index)
     {
       Print(index,": ",PositionGetString(POSITION_SYMBOL)," postionID #",positionid);
     }
class=class="str">"cmt">/* 简单输出
   class="num">1: AUDCAD postionID #class="num">13234934
   class="num">2: EURNZD postionID #class="num">13443909
   class="num">3: AUDUSD postionID #class="num">14956799
   class="num">4: EURUSD postionID #class="num">14878673
*/
class="macro">#define ForEachDeal(ticket,i) HistorySelect(class="num">0,TimeCurrent());\
   class="type">class="kw">ulong ticket=HistoryDealGetTicket(class="num">0);\
   class="type">int total=HistoryDealsTotal();\
   for(class="type">int i=class="num">1;i<=total;i++,ticket=HistoryDealGetTicket(i-class="num">1))
class="type">void OnStart()
  {
   class=class="str">"cmt">//---
   ForEachDeal(dealticket,index)
     {
       Print(index,": deal #",dealticket,",  order ticket=",
             HistoryDealGetInteger(dealticket,DEAL_ORDER));
     }
  }
class="macro">#define ForEachHistoryOrder(ticket,i) HistorySelect(class="num">0,TimeCurrent());\
   class="type">class="kw">ulong ticket=HistoryOrderGetTicket(class="num">0);\
   class="type">int total=HistoryOrdersTotal();\
   for(class="type">int i=class="num">1;i<=total;i++,ticket=HistoryOrderGetTicket(i-class="num">1))
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   ForEachHistoryOrder(historyorderticket,index)
     {
       Print(index,": #",historyorderticket);
     }
  }

「用宏把市场、图表、订单一次遍历完」

在 MT5 里批量扫描市场观察、图表对象和订单时,反复写 for 循环既啰嗦又容易下标写错。下面三个宏把常见遍历封装成单行调用,直接在函数里展开就能用。 ForEachSymbol 从 SymbolName(0,true) 取第一个品种,再用 SymbolsTotal(true) 拿到可见品种总数,从 i=1 跑到 os_total-1。注意它跳过了 i=0 的初始 symbol 赋值,实际是从第二个品种开始循环。 ForEachObject 针对当前图表主窗口:ObjectsTotal(0) 返回对象数,ObjectName(0,i-1) 在每次迭代往前偏移一位取名字。这里 i 从 1 到 ob_total,循环体里 name 已经指向第 i 个对象。 ForEachOrder 先 HistorySelect(0,TimeCurrent()) 把历史与当前订单载入,再用 OrdersTotal() 和 OrderGetTicket(i) 逐个拿 ticket。外汇与贵金属杠杆高,跑这类全量扫描前先确认品种和订单量,避免在大行情时占用主线程导致延迟。 把这三段贴进 EA 头文件,后续写策略只需写 ForEachSymbol(s,i){ /* 对 s 处理 */ },省下的代码行数可观。

MQL5 / C++
class="macro">#define ForEachSymbol(symbol,i) class="type">class="kw">string symbol=SymbolName(class="num">0,true); \
   class="type">int os_total=SymbolsTotal(true); \
   for(class="type">int i=class="num">1;i<os_total;i++,symbol=SymbolName(i,true))

class="macro">#define ForEachObject(name,i) class="type">class="kw">string name=ObjectName(class="num">0,class="num">0); \
   class="type">int ob_total=ObjectsTotal(class="num">0); \
   for(class="type">int i=class="num">1;i<=ob_total;i++,name=ObjectName(class="num">0,i-class="num">1))

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))

常见问题

在 MT5 文件顶部用 #define 给常用数组和函数起 MQL4 风格短名,比如把持仓总数宏定义成老习惯的写法,循环里就能少改脑内映射。
用 #define 把双循环(图表序号×对象序号)包成一个 ForEach 风格宏,后面直接写宏名加花括号体,代码薄一半。
可以,把代码贴给小布,它能指出宏展开后边界条件和未遍历分支,并给出精简建议,省去手动核对。
分别定义三个 ForEach 宏,再在一个函数里顺序调用,每个宏只负责一类实体,避免嵌套混乱,也方便单独调试。
历史池需用 OrdersHistoryTotal 类计数且先 Select 再读字段;宏里加上选中失败跳过,能避免读到空值报错。