交易者生活窍门: 利用 defines (#define) 融合 ForEach(基础篇)
用 #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 时,订单遍历宏不会报错也只是空跑——外汇和贵金属自动化高风险,这类静默失败要在调用处自己兜底。
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 点差常年三位数,这种品种做日内大概率被手续费吃掉。
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 调试时直接套,省下的时间够你多跑两组品种波动回测。
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。外汇和贵金属杠杆高,这类脚本只做本地诊断,别拿它当信号源,回测结论也仅代表过去数据。
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 处理 */ },省下的代码行数可观。
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))