StringFormat(). 回顾和现成的例子·进阶篇
把一周掉期与保证金属性一次打进日志
做隔夜仓的人最容易被周三转周四、周四转周五的掉期倍数坑到。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 里就能用。
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 诊断模块。
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,这本身不代表风险等级,但跨品种对冲时得自己建映射表。
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 进去就能验证输出。
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 里循环扫你关注的符号,就能直接在专家日志里看到对齐后的属性表。
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) 拉进来。外汇和贵金属杠杆高,自动化判断品种状态只是防错第一步,真金白银下单前仍要人工核对品种权限与风险。
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"); }