StringFormat(). 回顾和现成的例子·进阶篇
📘

StringFormat(). 回顾和现成的例子·进阶篇

第 2/3 篇

把一周掉期与保证金属性一次打进日志

做隔夜仓的人最容易被周三转周四、周四转周五的掉期倍数坑到。MT5 里 SYMBOL_SWAP_LONG / SYMBOL_SWAP_SHORT 只给当天的多空计息比率,而 SYMBOL_SWAP_THURSDAY、SYMBOL_SWAP_FRIDAY、SYMBOL_SWAP_SATURDAY 分别给出周三→周四、周四→周五、周五→周六的应计系数,实际跑下来一周 7 天需要 7×2 个函数才能把多空两侧全部显示出来。 与其逐个调 SymbolInfoXXX,不如封装一个通用函数:传入交易品种和日期,返回描述该日掉期比率的字符串,再封一个打印函数统一输出。顺带把 SYMBOL_MARGIN_INITIAL(开 1 手所需初始保证金)和 SYMBOL_MARGIN_MAINTENANCE(维护保证金,为 0 时回退用初始保证金)也一并打印,前者在市场进入时校验资金,后者在账户状态变动时校验。 锁仓保证金 SYMBOL_MARGIN_HEDGED 有两种算法由经纪商定:指定了初始保证金就按绝对值算锁仓;没指定就按合约大小配合 SYMBOL_TRADE_CALC_MODE 公式算。另一种“按最大持仓”算法直接忽略该字段,分别算多空加权开仓价和换汇率,取较大保证金值。 期权类品种还有一整套希腊字母属性可读取:SYMBOL_PRICE_DELTA 是基础资产动 1 点期权价变动值,SYMBOL_PRICE_THETA 是每天时间损耗点数,SYMBOL_PRICE_VEGA 是波动率动 1% 的变动点数,SYMBOL_PRICE_RHO 是利率动 1% 的敏感度,SYMBOL_PRICE_OMEGA 是期权弹性。外汇贵金属杠杆高,掉期与保证金数值可能因经纪商规则突变,一切以你本地 MT5 终端返回为准。 下面这段是把毫秒时间戳格式化成“年.月.日 时:分:秒.毫秒”的辅助函数,以及取订阅延迟标志的字符串函数骨架,直接抄进 EA 里就能用。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Accept a date in ms, class="kw">return time in Date Time.Msc format          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string TimeMSC(const class="type">long time_msc)
  {
   class="kw">return StringFormat("%s.%.3hu",class="type">class="kw">string((class="type">class="kw">datetime)time_msc / class="num">1000),time_msc % class="num">1000);
   class=class="str">"cmt">/* Sample output:
   class="num">2023.07.class="num">13 class="num">09:class="num">31:class="num">58.177
   */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the flag                                                    |
class=class="str">"cmt">//} of symbol data latency as a class="type">class="kw">string                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string SymbolSubscriptionDelay(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Create a variable to store the error text
   class=class="str">"cmt">//class="type">class="kw">string error_str;

◍ 抓品种订阅延迟与所属板块

在 MT5 里跑自定义诊断前,先确认品种确实挂在行情窗口里。若 SymbolInfoInteger 取 SYMBOL_EXIST 返回假,说明该品种当前不可交易或名称拼错,函数直接带错误文本退出,避免后续取到脏数据。 如果品种存在但没在 MarketWatch 勾选,代码会调 SymbolSelect(symbol,true) 尝试后台订阅;失败则通过 GetLastError 把具体错误码一并打印出来,方便定位是权限问题还是品种隐藏。 订阅延迟字段 SYMBOL_SUBSCRIPTION_DELAY 是布尔量,输出样例如 'Subscription Delay: No' 表示行情直连无中转延迟;返回值为 Yes 时,该品种价格可能比主服务器慢一拍,做剥头皮前要先评估。外汇与贵金属杠杆高,延迟差异会放大滑点风险。 经济板块的读取更有意思:SymbolInfoInteger 取 SYMBOL_SECTOR 得到枚举,再从 EnumToString 结果里截掉前 7 个字符('SECTOR_' 前缀),转小写后首字母大写、下划线换空格,最终得到可读板块名如 'technology'。下面这段是原文核心逻辑,逐行拆完你就能直接抄进 EA 诊断模块。

MQL5 / C++
class=class="str">"cmt">//--- If symbol does not exist, class="kw">return the error text
  if(!SymbolInfoInteger(symbol,SYMBOL_EXIST))
     class="kw">return StringFormat("%s: Error. Symbol &class="macro">#x27;%s&class="macro">#x27; is not exist",__FUNCTION__,symbol);
class=class="str">"cmt">//--- If a symbol is not selected in MarketWatch, try to select it
  if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
     {
     class=class="str">"cmt">//--- If unable to select a symbol in MarketWatch, class="kw">return the error text
     if(!SymbolSelect(symbol,true))
        class="kw">return StringFormat("%s: Failed to select &class="macro">#x27;%s&class="macro">#x27; symbol in MarketWatch. Error %lu",__FUNCTION__,symbol,GetLastError());
     }
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1
  class="type">class="kw">string header="Subscription Delay:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
  class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">bool)SymbolInfoInteger(symbol,SYMBOL_SUBSCRIPTION_DELAY) ? "Yes" : "No");
   class=class="str">"cmt">/* Sample output:
       Subscription Delay: No
   */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display the flag                                                |
class=class="str">"cmt">//| of the symbol data latency in the journal                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SymbolSubscriptionDelayPrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Print the class="kw">property value in the log with a header with the required width and indentation
  Print(SymbolSubscriptionDelay(symbol,header_width,indent));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the economic sector as a class="type">class="kw">string                           |
class=class="str">"cmt">//| a symbol belongs to                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string SymbolSector(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Get the value of the economic sector
  ENUM_SYMBOL_SECTOR symbol_sector=(ENUM_SYMBOL_SECTOR)SymbolInfoInteger(symbol,SYMBOL_SECTOR);
class=class="str">"cmt">//--- "Cut out" the sector name from the class="type">class="kw">string obtained from enum
  class="type">class="kw">string sector=StringSubstr(EnumToString(symbol_sector),class="num">7);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital
  if(sector.Lower())
     sector.SetChar(class="num">0,class="type">class="kw">ushort(sector.GetChar(class="num">0)-0x20));
class=class="str">"cmt">//--- Replace all underscore characters with space in the resulting line
  StringReplace(sector,"_"," ");
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1
  class="type">class="kw">string header="Sector:";

「把品种的行业分类扒进日志」

MT5 里每个交易品种都带 SYMBOL_INDUSTRY 枚举属性,但原始枚举名全是「SYMBOL_INDUSTRY_XXX」前缀,直接打印可读性很差。下面这段函数把枚举转成干净的行业字符串,比如默认输出就是「Industry: Undefined」,外汇类品种大多落在这个值。 核心技巧是先 EnumToString 拿到全名,再用 StringSubstr 从第 9 个字符开始截断前缀(「SYMBOL_INDUSTRY」正好占 9 字符),随后 Lower 转小写、把首字母加 0x20 还原成大写,最后把下划线替换成空格。header 宽度若传 0,则自动取「Industry:」长度 +1,保证多行对齐。 想立刻看自己图表品种归哪类,把 SymbolIndustryPrint(_Symbol) 丢进 EA 的 OnStart 就行。外汇和贵金属品种在高杠杆下归属往往显示为 Undefined,这本身不代表风险等级,但跨品种对冲时得自己建映射表。

MQL5 / C++
class="type">class="kw">string SymbolIndustry(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Get industry type value
   ENUM_SYMBOL_INDUSTRY symbol_industry=(ENUM_SYMBOL_INDUSTRY)SymbolInfoInteger(symbol,SYMBOL_INDUSTRY);
class=class="str">"cmt">//--- "Cut out" the industry type from the class="type">class="kw">string obtained from enum
   class="type">class="kw">string industry=StringSubstr(EnumToString(symbol_industry),class="num">9);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital
   if(industry.Lower())
      industry.SetChar(class="num">0,class="type">class="kw">ushort(industry.GetChar(class="num">0)-0x20));
class=class="str">"cmt">//--- Replace all underscore characters with space in the resulting line
   StringReplace(industry,"_"," ");
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header class="type">class="kw">string + class="num">1
   class="type">class="kw">string header="Industry:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,industry);
   class=class="str">"cmt">/* Sample output:
      Industry: Undefined
   */
  }
class="type">void SymbolIndustryPrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Print the class="kw">property value in the log with a header with the required width and indentation
   Print(SymbolIndustry(symbol,header_width,indent));
  }

读取自定义品种与背景色属性

在 MT5 里判断一个品种是不是自定义符号,核心靠 SYMBOL_CUSTOM 整数属性。下面这段函数把判断结果格式化成带缩进和表头的字符串,默认表头宽度取『Custom symbol:』长度加 1,即 15 字符;若传入 header_width 非零则按指定宽度对齐。 StringFormat 里 %*s 吃缩进空格,%-*s 左对齐表头,%-s 放值。SymbolInfoInteger 返回 true 时输出『Yes』,否则『No』,样例里普通品种跑出来就是『Custom symbol: No』。 背景色走的是 SYMBOL_BACKGROUND_COLOR。当取到的值正好等于 0xFF000000,说明品种在 Market Watch 用的是默认色,直接回『Default』;不然用 ColorToString 转成可读串。这个 0xFF000000 是 MT5 内部默认色常量,写错一位就会把默认色误判成具体颜色。 外汇和贵金属品种大多不是自定义符号,但如果你用脚本批量建了自定义 XAU 合成品种,这类读取函数能帮你快速在日志里核对显示属性。MT5 上新建脚本粘入下方代码,传 EURUSD 进去就能验证输出。

MQL5 / C++
class="type">class="kw">string SymbolCustom(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1
   class="type">class="kw">string header="Custom symbol:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">bool)SymbolInfoInteger(symbol,SYMBOL_CUSTOM) ? "Yes" : "No");
   class=class="str">"cmt">/* Sample output:
   Custom symbol: No
   */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Display the flag of a custom symbol                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SymbolCustomPrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Print the class="kw">property value in the log with a header with the required width and indentation
   Print(SymbolCustom(symbol,header_width,indent));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the background class="type">color used to highlight the Market Watch symbol as a class="type">class="kw">string |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string SymbolBackgroundColor(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1
   class="type">class="kw">string header="Background class="type">color:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Get the symbol background class="type">color in Market Watch
   class="type">color back_color=(class="type">color)SymbolInfoInteger(symbol,SYMBOL_BACKGROUND_COLOR);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
class=class="str">"cmt">//--- If a class="kw">default class="type">color is set for a symbol(0xFF000000), class="kw">return &class="macro">#x27;Default&class="macro">#x27;, otherwise - class="kw">return its class="type">class="kw">string description
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,back_color==0xFF000000 ? "Default" : ColorToString(back_color,true));
   class=class="str">"cmt">/* Sample output:
   Background class="type">color: Default
   */
  }

◍ 把图表模式与存在性打进日志

做符号巡检时,图表是基于 Bid 还是 Last 价格构建,会直接影响你看到的 K 线形态。用 SymbolChartMode() 可以把 SYMBOL_CHART_MODE 转成可读字符串,样例输出就是「Chart mode: Bid」,一眼能确认当前品种报价源。 SymbolChartMode() 里先取枚举再裁掉前 18 个字符(「SYMBOL_CHART_MODE_」长度),转小写后把首字母大写,输出宽度若传 0 则自动按表头长度 +1 处理。这样多品种批量打印时列能对齐,不用来回目测。 SymbolExists() 则只问 SYMBOL_EXIST 这个整数属性,存在回 Yes 不存在回 No。外汇和贵金属市场品种多、重连后可能掉符号,这种存在性检查能帮你避开「品种已不在却还下单」的低级坑,属于 MT5 脚本里的高频保险丝。 这三个 Print 封装函数都不带返回值外的副作用,开 MT5 把代码丢进 EA 的 OnStart 里循环扫你关注的符号,就能直接在专家日志里看到对齐后的属性表。

MQL5 / C++
class="type">void SymbolBackgroundColorPrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Print the class="kw">property value in the log with a header with the required width and indentation
   Print(SymbolBackgroundColor(symbol,header_width,indent));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the price type for building bars as a class="type">class="kw">string              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string SymbolChartMode(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Get price type used for generating bars
   ENUM_SYMBOL_CHART_MODE symbol_chart_mode=(ENUM_SYMBOL_CHART_MODE)SymbolInfoInteger(symbol,SYMBOL_CHART_MODE);
class=class="str">"cmt">//--- "Cut out" price type from the class="type">class="kw">string obtained from enum
   class="type">class="kw">string chart_mode=StringSubstr(EnumToString(symbol_chart_mode),class="num">18);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital
   if(chart_mode.Lower())
      chart_mode.SetChar(class="num">0,class="type">class="kw">ushort(chart_mode.GetChar(class="num">0)-0x20));
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header class="type">class="kw">string + class="num">1
   class="type">class="kw">string header="Chart mode:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,chart_mode);
   class=class="str">"cmt">/* Sample output:
       Chart mode: Bid
   */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Log the price type for building bars                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SymbolChartModePrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Print the class="kw">property value in the log with a header with the required width and indentation
   Print(SymbolChartMode(symbol,header_width,indent));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the flag                                                  |
class=class="str">"cmt">//| indicating that a symbol with this name exists                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string SymbolExists(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header class="type">class="kw">string + class="num">1
   class="type">class="kw">string header="Exists:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">bool)SymbolInfoInteger(symbol,SYMBOL_EXIST) ? "Yes" : "No");
   /* Sample output:
   Exists: Yes

「把交易品种状态打进日志里」

在 MT5 里写监控脚本时,确认某个品种是否真实存在、有没有被加进市场报价窗口、以及是否处于可见状态,这三件事直接决定后续下单或取 Tick 会不会报 4106 错误。上面这组函数把判断逻辑封装成了可打印的字符串,省得每次都手写 SymbolInfoInteger 的宏。 SymbolExists 返回是否存在,SymbolSelected 读 SYMBOL_SELECT 判断是否在市场报价中被选中,SymbolVisible 读 SYMBOL_VISIBLE 判断是否在报价列表里可见。三者都带 header_width 和 indent 参数,传 0 时表头宽度自动取文字长度加 1,比如 'Selected:' 长度为 9,默认宽度就是 10。 调用对应的 Print 版本,日志里会直接出 'Selected: Yes' 或 'Visible: No' 这种单行结果。实盘前拿 EURUSD 跑一遍,若输出 Selected: No,说明品种没选进报价窗口,EA 的 OrderSend 大概率会失败,得先 SymbolSelect(symbol,true) 拉进来。外汇和贵金属杠杆高,自动化判断品种状态只是防错第一步,真金白银下单前仍要人工核对品种权限与风险。

MQL5 / C++
class="type">void SymbolExistsPrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Print the class="kw">property value in the log with a header with the required width and indentation
   Print(SymbolExists(symbol,header_width,indent));
  }
class="type">class="kw">string SymbolSelected(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Define the header text and the width of the header field
class=class="str">"cmt">//--- If the header width is passed to the function equal to zero, then the width will be the size of the header line + class="num">1
   class="type">class="kw">string header="Selected:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Return the class="kw">property value with a header having the required width and indentation
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">bool)SymbolInfoInteger(symbol,SYMBOL_SELECT) ? "Yes" : "No");
  }
class="type">void SymbolSelectedPrint(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
   Print(SymbolSelected(symbol,header_width,indent));
  }
class="type">class="kw">string SymbolVisible(const class="type">class="kw">string symbol,const class="type">uint header_width=class="num">0,const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Visible:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
   class="kw">return StringFormat("%*s%-*s%-s",indent,"",w,header,(class="type">bool)SymbolInfoInteger(symbol,SYMBOL_VISIBLE) ? "Yes" : "No");
  }

常见问题

用 StringFormat() 拼好格式串,把 SymbolInfoInteger 的保证金字段和 SymbolInfoDouble 的掉期字段一起传入,一次 Print 即可看到结果。
通过 SymbolInfoInteger 取 SYMBOL_SUBSCRIBE_DELAY 和对应板块枚举,再用 StringFormat 组合输出,延迟和板块就进日志了。
可以,小布能按品种页自动汇总行业分类、掉期与保证金等属性并生成可读日志,你不用自己写拼接代码。
用 SymbolInfoInteger 取背景色相关标识,配合 StringFormat 转成颜色值文本,直接 Print 就能在日志里看到。
读 SYMBOL_EXIST 与交易模式字段,用 StringFormat 拼成状态说明,存在且可交易时才输出正常,否则标出异常。