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

交易者生活窍门: 利用 defines (#define) 融合 ForEach·进阶篇

第 2/3 篇

◍ 正文

<span class="preprocessor">#define </span><span style="color:rgb(226, 8, 0);">ForEachPosition</span>(<span style="background-color:rgb(216, 232, 194);">ticket</span>,i) <span class="functions">HistorySelect</span>(<span class="number">0</span>,<span class="functions">TimeCurrent</span>());&nbsp;&nbsp;&nbsp;&nbsp;\ &nbsp;&nbsp; <span class="keyword">ulong</span> ticket=<span class="functions">PositionGetTicket</span>(<span class="number">0</span>);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\ &nbsp;&nbsp; <span class="keyword">int</span> <span style="background-color:rgb(255, 242, 153);">po_</span>total=<span class="functions">PositionsTotal</span>();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\ &nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">1</span>;i&lt;=po_total;i++,ticket=<span class="functions">PositionGetTicket</span>(i-<span class="number">1</span>))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">//+------------------------------------------------------------------+</span> <span class="comment">//| 搜索历史交易循环&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n

用脚本把全市场点差摊开看

想快速摸清当前 MT5 账户里各个品种的点差水平,没必要手动一个个切图表看报价。写一段启动脚本,在 OnStart 里遍历交易品种,把每个符号的卖价买价差换算成点数打印出来,几十个品种几秒内就能扫完。 下面这段代码直接在 MT5 脚本环境里跑,核心是先取 SYMBOL_ASK 和 SYMBOL_BID 相减得到绝对价差,再除以 SYMBOL_POINT 归一化成「点」。同时把价格精度位数和单点价值一并输出,方便横向比谁滑点成本更高。 void OnStart() { //--- 遍历所有可见交易品种(symbol 为品种名,index 为序号) ForEachSymbolBack(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); //--- 输出数据:序号. 品种 点差=XX点 (精度位, point=值) Print(index,". ",symbol," spread=",str_spread," points (", digits," digits",", point=",str_point,")"); } /* 简单输出

  • USDJPY spread=5 points (3 digits, point=0.001)
  • USDCHF spread=8 points (5 digits, point=0.00001)
  • GBPUSD spread=9 points (5 digits, point=0.00001)
  • EURUSD spread=2 points (5 digits, point=0.00001)

*/ } 从上面这段示例输出能直接读出:同环境里 EURUSD 点差仅 2 点,而 GBPUSD 跑到 9 点,USDCHF 也有 8 点。外汇和贵金属杠杆高、点差会随流动性跳变,实盘前扫一遍这类数据,可能帮你避开在点差扩张时段去碰成本最烂的那几个品种。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- 
   ForEachSymbolBack(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=class="str">"cmt">/* 简单输出
   class="num">3. USDJPY spread=class="num">5 points(class="num">3 digits, point=class="num">0.001)
   class="num">2. USDCHF spread=class="num">8 points(class="num">5 digits, point=class="num">0.00001)
   class="num">1. GBPUSD spread=class="num">9 points(class="num">5 digits, point=class="num">0.00001)
   class="num">0. EURUSD spread=class="num">2 points(class="num">5 digits, point=class="num">0.00001)
*/
  }

「把 MQL4 账户与行情函数搬进 MQL5 的坑」

做 MQL4 到 MQL5 的迁移,第一道坎是账户和行情相关的旧函数。原 MQL4 里的 AccountXXXX、MarketInfo、状态检查以及预定义变量,需要靠四个头文件接住:AccountInfo.mqh、MarketInfo.mqh、Check.mqh、Predefined.mqh,都要丢进 [数据文件夹]\MQL5\Include\SimpleCall\。实际文件夹里一共七个转换文件,但 IndicatorsMQL4.mqh 和 IndicatorsMQL5.mqh 不能同包含,只能二选一,所以封装出了 SimpleCallMQL4.mqh 和 SimpleCallMQL5.mqh 两个总头。 拿 MACD Sample 举例最直观:把 .mq4 改扩展名为 .mq5 扔进 Experts,直接编译会爆 59 个错误加 1 个警告;接上 SimpleCallMQL4.mqh 后再编,降到 39 错 1 警;用 Ctrl+H 把 Bars 换成 Bars(Symbol(),Period())、Point 换成 Point(),还剩 35 个错,全是交易函数,本文先不碰。 账户函数映射有两条路:要么走 AccountInfoDouble/Integer/String,要么用 CAccountInfo 类方法。注意 AccountFreeMarginMode 在 MQL5 没有等价物,检测到就向日志发警告并返回 NaN,别在实盘逻辑里依赖它。类型上也有差异,比如 AccountNumber 在 MQL4 是 int,MQL5 里 Login 是 long。 MarketInfo 的麻烦在于类型被拆了:MQL4 里它是 double,MQL5 对应 SymbolInfoInteger(long)和 SymbolInfoDouble(double)。像 MODE_DIGITS 就得写成 (double)SymbolInfoInteger(symbol,SYMBOL_DIGITS),硬转型不可避免。 几个语义歧义得手动兜底。MODE_TRADEALLOWED 在 MQL4 是 bool,MQL5 有五种交易模式枚举,只在 DISABLED 时返回 false,其余部分限制或全开都返回 true 并打警告。MODE_SWAPTYPE 从 4 个值扩到 9 个,无对应项的回 NaN 加警告;MODE_PROFITCALCMODE 在 MQL5 压根不算盈利,只能借用 ENUM_SYMBOL_CALC_MODE 近似,外汇对外汇、CFD 对 CFD 这样硬套。

◍ 把 MQL4 状态检查与预定义变量搬进 MQL5 的坑

做 EA 迁移时,MQL4 里那套状态检查函数(Digits、Point、IsConnected、IsDemo 等)在 MQL5 里得逐个找对应。文件 [数据文件夹]\MQL5\Include\SimpleCall\Check.mqh 已经做了转换:IsConnected 对应 TerminalInfoInteger(TERMINAL_CONNECTED),IsDemo 从 AccountInfoInteger(ACCOUNT_TRADE_MODE) 取枚举值,IsDllsAllowed 则要看 TERMINAL_DLLS_ALLOWED 的优先级高于 MQL_DLLS_ALLOWED。 IsLibrariesAllowed、IsOptimization、IsTesting、IsTradeAllowed 在 MQL5 里直接用 MQLInfoInteger 取对应标志位;IsTradeContextBusy 在 MQL5 无意义,统一返回 false。IsVisualMode 对应 MQL_VISUAL_MODE,但 MQL5 程序有调试、代码分析、测试、优化四种模式,别拿老框架硬套。 Digits 和 Point 最麻烦:MQL4 文本里可能同时出现 Digits 和 Digits()、Point 和 Point(),用 #define 没法把无括号版映射到函数调用,只能留着手工处理。Predefined.mqh 里用 #define 把 _Digits、_Period、_Symbol 等预定义变量转成 MQL5 函数,唯独 _RandomSeed 存的是伪随机发生器状态,Bars 也留待手改。 Ask 和 Bid 不能直接宏替换成 SymbolInfoDouble,因为品种可能无报价导致值未定义。更稳的做法是自定义 GetAsk()/GetBid() 走 SymbolInfoTick(),每次调用拿最新买卖价和最后成交时间。 时间序列数组 Open[]、Close[] 这类在 MQL5 要用类重载 [] 运算符实现,iClose 等 iXXX 函数也需在 Predefined.mqh 里自行声明。下面这段从老 MACD 样例摘的包含头,能看到 OP_BUY/OP_SELL 被重定义成 ORDER_TYPE_BUY/SELL,并引了 SimpleCallMQL4.mqh——开 MT5 把这段塞进迁移工程,能直接验证宏映射是否漏了你的订单类型。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                MACD Sample.mq4 |
class=class="str">"cmt">//|           版权所有 class="num">2005-class="num">2014, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                        http://www.mql4.com |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright   "class="num">2005-class="num">2014, MetaQuotes 软件公司"
class="macro">#class="kw">property link        "http:class=class="str">"cmt">//www.mql4.com"
class=class="str">"cmt">//---
class="macro">#include <SimpleCall\SimpleCallMQL4.mqh>
class=class="str">"cmt">//---
class="kw">input class="type">class="kw">double TakeProfit    =class="num">50;
class=class="str">"cmt">//---
class="macro">#define  OP_BUY                    ORDER_TYPE_BUY
class="macro">#define  OP_SELL                   ORDER_TYPE_SELL
class=class="str">"cmt">//--- 返回当前帐户的余额值

用宏把 MQL4 账户函数搬进 MQL5

在把老 EA 从 MQL4 迁到 MQL5 时,账户类函数名全变了,直接编译会报一堆未定义。最省事的做法是用 #define 把旧名字映射到新的 AccountInfoDouble / AccountInfoInteger / AccountInfoString 接口,无参数宏和带参宏都支持,最多可带 8 个参数。 下面这组宏覆盖了余额、信用、经纪商名、账户货币、净值、可用保证金、杠杆、已用保证金和账户名。例如 #define AccountBalance(void) AccountInfoDouble(ACCOUNT_BALANCE) 让旧代码里的 AccountBalance() 直接返回双精度余额值,不需要改调用处。 AccountFreeMarginCheck 没法用一行宏解决,因为要按品种和方向算挂单后剩余保证金。函数先根据买卖方向取 SYMBOL_ASK 或 SYMBOL_BID,再调 OrderCalcMargin 算占用,最后用当前 ACCOUNT_FREEMARGIN 减掉它返回。注意若 OrderCalcMargin 失败会返回 EMPTY_VALUE,剩余保证金可能算错,实盘前要在 MT5 里用不同品种测一遍。 AccountFreeMarginMode 在 MQL5 没有对应物,上面实现直接弹 Alert 并返回 NaN。若你的旧逻辑依赖这个模式值,迁移时要重写风控分支,别假设它能给出 0 或 1。外汇和贵金属杠杆波动大,这类账户函数返回值要结合实时保证金水平判断,高风险下数字会快速漂移。

MQL5 / C++
class="macro">#define identifier expression                     class=class="str">"cmt">// 无参数形式
class="macro">#define identifier(par1,... par8) expression      class=class="str">"cmt">// 参数形式
class=class="str">"cmt">//--- 返回当前帐户的余额值
class="macro">#define  AccountBalance(class="type">void)      AccountInfoDouble(ACCOUNT_BALANCE)
class=class="str">"cmt">//--- 返回当前帐户的信用值
class="macro">#define  AccountCredit(class="type">void)       AccountInfoDouble(ACCOUNT_CREDIT)
class=class="str">"cmt">//--- 返回当前帐户注册的经纪公司名称
class="macro">#define  AccountCompany(class="type">void)      AccountInfoString(ACCOUNT_COMPANY)
class=class="str">"cmt">//--- 返回当前帐户的货币名称
class="macro">#define  AccountCurrency(class="type">void)     AccountInfoString(ACCOUNT_CURRENCY)
class=class="str">"cmt">//--- 返回当前账户的净值
class="macro">#define  AccountEquity(class="type">void)       AccountInfoDouble(ACCOUNT_EQUITY)
class=class="str">"cmt">//--- 返回当前帐户的可用保证金
class="macro">#define  AccountFreeMargin(class="type">void)   AccountInfoDouble(ACCOUNT_MARGIN_FREE)
class=class="str">"cmt">//--- 返回指定订单打开后剩余的可用保证金
class=class="str">"cmt">//---    按当前账户的当前价格计算
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 price=class="num">0.0;
   ENUM_ORDER_TYPE trade_operation=(ENUM_ORDER_TYPE)cmd;
   if(trade_operation==ORDER_TYPE_BUY)
      price=SymbolInfoDouble(symbol,SYMBOL_ASK);
   if(trade_operation==ORDER_TYPE_SELL)
      price=SymbolInfoDouble(symbol,SYMBOL_BID);
class=class="str">"cmt">//---
   class="type">class="kw">double margin_check=EMPTY_VALUE;
   class="type">class="kw">double margin=EMPTY_VALUE;
   margin_check=(!OrderCalcMargin(trade_operation,symbol,volume,price,margin))?EMPTY_VALUE:margin;
class=class="str">"cmt">//---
   class="kw">return(AccountInfoDouble(ACCOUNT_FREEMARGIN)-margin_check);
  }
class=class="str">"cmt">//--- 返回当前账户上开单的可用保证金计算模式
class="type">class="kw">double AccountFreeMarginMode(class="type">void)
  {
   class="type">class="kw">string text="MQL4 functions \"AccountFreeMarginMode()\" has no analogs in MQL5. Returned \"NAN - not a number\"";
   Alert(text);
   Print(text);
   class="kw">return(class="type">class="kw">double("nan"));
  }
class=class="str">"cmt">//--- 返回当前账户的杠杆
class="macro">#define  AccountLeverage(class="type">void)     (class="type">int)AccountInfoInteger(ACCOUNT_LEVERAGE)
class=class="str">"cmt">//--- 返回当前帐户的保证金
class="macro">#define  AccountMargin(class="type">void)       AccountInfoDouble(ACCOUNT_MARGIN)
class=class="str">"cmt">//--- 返回当前帐户名称
class="macro">#define  AccountName(class="type">void)         AccountInfoString(ACCOUNT_NAME)
class=class="str">"cmt">//--- 返回当前帐号

「把账户与品种计算模式翻译成可调用宏」

在 MT5 里直接拿账户属性,最干净的做法是用 AccountInfoInteger / Double / String 包一层宏。下面这组定义把登录号、浮盈、服务器名、强平参数一次性封装,实盘里拿来写风控脚本比反复调 AccountInfo 直观。 #define AccountNumber(void) (int)AccountInfoInteger(ACCOUNT_LOGIN) // 取当前账户登录号,转成 int #define AccountProfit(void) AccountInfoDouble(ACCOUNT_PROFIT) // 返回账户浮动利润 #define AccountServer(void) AccountInfoString(ACCOUNT_SERVER) // 返回已连接服务器名称 #define AccountStopoutLevel(void) (int)AccountInfoDouble(ACCOUNT_MARGIN_SO_SO) // 强平级别数值 int AccountStopoutMode(void) { ENUM_ACCOUNT_STOPOUT_MODE stopout_mode=(ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); if(stopout_mode==ACCOUNT_STOPOUT_MODE_PERCENT) return(0); return(1); } // 返回 0 表示按百分比强平,1 表示按资金绝对额强平 外汇和贵金属保证金品种的高风险在于:强平模式若判错,仓位测算会整体偏移。用上面 AccountStopoutMode 先确认是百分比还是金额,再决定止损距离算法。 品种层面的利润计算模式也能映射成老 MQL4 风格编号。MODE_PROFITCALCMODE 对应 SYMBOL_TRADE_CALC_MODE,外汇返回 0、CFD 返回 1、期货返回 2;遇未知模式会弹 Alert 并返回 nan。写跨品种 EA 时,先读这个数再分流开仓逻辑,能少踩不少坑。 case MODE_PROFITCALCMODE: { ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE); switch(profit_calc_mode) { case SYMBOL_CALC_MODE_FOREX: return((double)0); case SYMBOL_CALC_MODE_FUTURES: return((double)2); case SYMBOL_CALC_MODE_CFD: return((double)1); default : { string text="MQL4 MODE_PROFITCALCMODE returned MQL5 "+EnumToString(profit_calc_mode); Alert(text); Print(text); return(double("nan")); } } } case MODE_MARGINCALCMODE: { ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE); switch(profit_calc_mode) {

MQL5 / C++
class="macro">#define  AccountNumber(class="type">void)        (class="type">int)AccountInfoInteger(ACCOUNT_LOGIN)
class=class="str">"cmt">//--- 返回当前帐户的利润值
class="macro">#define  AccountProfit(class="type">void)        AccountInfoDouble(ACCOUNT_PROFIT)
class=class="str">"cmt">//--- 返回连接的服务器名称
class="macro">#define  AccountServer(class="type">void)        AccountInfoString(ACCOUNT_SERVER)
class=class="str">"cmt">//--- 返回强制平仓级别的数值
class="macro">#define  AccountStopoutLevel(class="type">void)  (class="type">int)AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)
class=class="str">"cmt">//--- 返回强制平仓级别的计算模式
class="type">int     AccountStopoutMode(class="type">void)
  {
   ENUM_ACCOUNT_STOPOUT_MODE stopout_mode=(ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE);
   if(stopout_mode==ACCOUNT_STOPOUT_MODE_PERCENT)
     class="kw">return(class="num">0);
   class="kw">return(class="num">1);
  }
class=class="str">"cmt">/*
SYMBOL_SWAP_MODE_INTEREST_CURRENT
隔夜利息计算按计算时的金融产品价格年度 % 计算 (银行模式为每年 class="num">360 天)
SYMBOL_SWAP_MODE_INTEREST_OPEN
隔夜利息按品种开仓价格的年度 % 计算 (银行模式为每年 class="num">360 天)
*/
...
class="macro">#define MODE_PROFITCALCMODE   class="num">1000 class=class="str">"cmt">//SYMBOL_TRADE_CALC_MODE
class="macro">#define MODE_MARGINCALCMODE   class="num">1001 class=class="str">"cmt">//SYMBOL_TRADE_CALC_MODE
...
            case MODE_PROFITCALCMODE:
              {
               ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE);
               class="kw">switch(profit_calc_mode)
                 {
                  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_PROFITCALCMODE returned MQL5 "+EnumToString(profit_calc_mode);
                     Alert(text);
                     Print(text);
                     class="kw">return(class="type">class="kw">double("nan"));
                    }
                 }
              }
            case MODE_MARGINCALCMODE:
              {
               ENUM_SYMBOL_CALC_MODE profit_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_CALC_MODE);
               class="kw">switch(profit_calc_mode)
                 {

常见问题

用 ForEach 宏把循环头和判断包起来,之后每次只需要写 ForEach(Order) { ... } 即可,省去手写 for 和 Select 的重复劳动。
写个脚本用宏遍历 SymbolsTotal 和 SymbolInfoInteger 抓点差,跑一次就能在日志里摊开看,找出点差异常放大的品种。
可以,把代码贴给小布,它能对照 MQL5 账户函数差异点出宏里漏改的字段,并给出可直接抄的替换写法。
用 #define AccountEquity() AccountInfoDouble(ACCOUNT_EQUITY) 这类宏做名称映射,老代码基本不用动就能编译过。
用宏把 SymbolInfoDouble(_Symbol, SYMBOL_BID) 定义成 Bid,其余引用处保持原样,改一处全局生效。