研究PrintFormat()并应用现成的示例(基础篇)
📘

研究PrintFormat()并应用现成的示例(基础篇)

第 1/3 篇

◍ 把 PrintFormat 直接当现成模板用

在 MT5 里把数值打到日志或监测器屏幕,平时用 Print 就够了;但一旦要输出带精度、对齐、多类型混合的复杂内容,手写格式串很容易出错。Artyom Trishkin 在 2024 年 2 月 8 日发布的示例帖拿到 1098 次查看、27 条互动,说明这类「即插即用」格式化需求在实盘代码里很高频。 与其每次翻 MQL5 帮助去拼 %d、%f、%s,不如先吃透 PrintFormat 的调用规则,再把验证过的模板存进自己的代码库。本文系列要做的,就是拆清这个函数怎么传参、转义符怎么排,给你一套贴进 EA 就能跑的片段。 外汇与贵金属行情跳动快、点值小数位多,日志里数字挤成一团会拖慢排查效率;用格式化输出把关键变量对齐,是降低盯盘调试成本的直接手段,但任何技术改进都不消除交易本身的高风险。

「账户属性打印的模块划分」

要把 MT5 账户信息结构化输出到日志,先得理清 PrintFormat() 这套接口覆盖的属性维度。MQL5 把账户属性拆成整数型、真实型、字符串型三大类,分别对应 AccountInfoInteger、AccountInfoDouble、AccountInfoString 三个取数函数。 整数类里能直接拿到的有账号、账户类型、杠杆、最大订单数、最小抵押品模式、是否允许交易、是否允许 EA、保证金计算模式、货币小数位、FIFO 强平标志、是否允许反向仓。这些大多是开关量或枚举值,用 %d 或 %s 配合 AccountInfoInteger 转换即可。 真实型属性才是盯盘时最常看的:余额、信用、浮动利润、净值、已用保证金、空闲资金、保证金水平百分比、追保线、强平线、挂单预留保证金、最低净值保留额、资产规模、负债、冻结佣金。其中保证金水平(%)和强平线是风控核心,外汇与贵金属杠杆高,这几个值跳变往往先于价格异动。 字符串类只负责文本标识:客户名、服务器名、存款货币、服务公司名。最后还有一个把所有账户数据一次性刷进日志的聚合函数,以及从分时属性起始的毫秒时间戳,方便做会话级延迟统计。

把日志从乱流变成表格

MT5 里跑的 EA 或脚本,状态和环境信息都会落到『专家』标签页的日志中。少量 Print() 输出还好读,一旦一次甩出几十条带不同描述长度的数据,肉眼检索基本报废。 MQL5 的 Print() 能把字符串或转成字符串的值直接打到日志,短记录够用。但数据一多、描述长短不一又挤在同一行,就成了一坨连读都费劲的字符流。 PrintFormat() 就是为此存在的:它按你给的格式说明,把字符和数值排成固定宽度的标题列 + 对齐的数据列,日志瞬间变成可扫读的表格。外汇和贵金属行情跳变快、EA 事件密集,这种格式化能直接降低你盯盘时漏看信号的概率。 开 MT5 随便建个脚本,把十来个长短不一的标签用 PrintFormat("%-12s: %d", name, val) 打出来,和裸 Print() 对比一下就懂差距。

◍ PrintFormat 的格式串到底怎么拼

MT5 里的 PrintFormat() 接收一个格式字符串加一组对应数据,按格式串里的占位符把数据塞进日志。占位符都以 % 开头,后面跟 flags、宽度、精度、尺寸和类型说明符,中间可以夹任意普通文本。例如 "%-10s%-20ld" 这种写法,意思是先左对齐输出一个不超过 10 字符的字符串,再左对齐输出一个 20 字符宽的有符号整数。 格式串从左到右扫,碰到第一个 % 规范就消耗后面第一个参数,第二个规范消耗第二个参数,依次类推。类型说明符(type)是必填项,比如 d 是有符号十进制、f 是定点实数、s 是字符串;如果只想在文本里写个百分号本身,得写 %%。 几个 flags 直接影响可读性:减号让内容在字段内左对齐;空格让正数前补一个空格,这样和负数带负号的值能在同一列视觉对齐,不会错位一个字符;0 在整数前补零,但一旦设了精度(如 %04.d)就会被忽略。宽度若写星号 *,则从参数列表里取一个数字当宽度,做动态表格很方便。 精度对不同类型的含义不一样:f 和 e 控制小数点后位数且末位四舍五入;d/i/u 控制最少显示几位、不足左补零;s 控制最多显示几个字符、超长截断。拿 g 格式说,精度指的是最大有效位数而非小数位——精度设 4、digits() 返回 5 时,可能只看到 1.273 这样的三位小数,因为第四位有效数落在小数点前。 下面这段可直接丢进 MT5 脚本跑,验证左对齐与字段宽度的展开行为:header 赋 "10characters"(10 字符刚好不撑开),value1 转 int 走 %-20ld、value2 转 long 走 %-20lld,两列各占 20 字符且贴左边缘;若 header 超过 10 字符,字段会沿文本长度自动展开而非截断。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare the variables to be printed
   class="type">class="kw">string   header="";   class=class="str">"cmt">// Data header
   class="type">class="kw">double   value1=class="num">0;    class=class="str">"cmt">// First value
   class="type">class="kw">double   value2=class="num">0;    class=class="str">"cmt">// Second value

class=class="str">"cmt">//--- First line with a header of less than class="num">10 characters and data in class="num">20-character fields
class=class="str">"cmt">//--- The header and data are pressed to the left edge of their field
   header="10characters";
   value1=class="num">10000000;
   value2=class="num">20000000;
   PrintFormat("%-10s%-20ld%-20lld",header,(class="type">int)value1,(class="type">long)value2);

class=class="str">"cmt">//--- Second line with a header of more than class="num">10 characters and data in class="num">20-character fields

「PrintFormat 左对齐与符号位的字段控制」

在 MT5 脚本里用 PrintFormat 做日志排版,靠格式串里的 %-16s、%-+20ld 这类标记就能把表头和数据钉死在固定宽度内。%- 表示左对齐,数字代表字段占多少字符;+ 强制给有符号整数显示正负号,方便扫日志时一眼区分多空盈亏方向。 下面这段把 header 放在 16 字符宽左对齐字段,value1 用 %-+20ld 左对齐且带符号,value2 用 %-20lld 左对齐无符号,输出里 Header1 对应 -10000000 与 20000000,Header2 对应 +10000000 与 -20000000(注:原文示例将 value2 以 long long 打印,负号显示取决于实际类型转换,验证时以终端输出为准)。 如果第三列想右对齐并补前导零,把格式串改成 %020lld 即可,20 表示总宽 20 位、不足补 0。外汇与贵金属行情跳动快、点差滑点风险高,这类格式化只服务于本地诊断输出,不构成任何方向判断。

MQL5 / C++
class="type">void OnStart()
  {
  class=class="str">"cmt">//--- Declare the variables to be printed
   class="type">class="kw">string  header="";  class=class="str">"cmt">// Data header
   class="type">class="kw">double  value1=class="num">0;   class=class="str">"cmt">// First value
   class="type">class="kw">double  value2=class="num">0;   class=class="str">"cmt">// Second value
  
  class=class="str">"cmt">//--- The first line with a header in a class="num">16-character field and data in class="num">20-character fields
  class=class="str">"cmt">//--- The header and data are pressed to the left edge.
  class=class="str">"cmt">//--- Data values of the second field are displayed with +/- signs
   header="Header1";
   value1=-class="num">10000000;
   value2=class="num">20000000;
   PrintFormat("%-16s%-+20ld%-20lld",header,(class="type">int)value1,(class="type">long)value2);
  
  class=class="str">"cmt">//--- The second line with a header in a class="num">16-character field and data in class="num">20-character fields
  class=class="str">"cmt">//--- The header and data are pressed to the left edge.
  class=class="str">"cmt">//--- Data values of the second field are displayed with +/- signs
   header="Header2";
   value1=class="num">10000000;
   value2=-class="num">20000000;
   PrintFormat("%-16s%-+20ld%-20lld",header,(class="type">int)value1,(class="type">long)value2);
  
   class=class="str">"cmt">/* Sample output:
      Header1         -class="num">10000000             class="num">20000000
      Header2         +class="num">10000000             -class="num">20000000
   */
  }

用 PrintFormat 对齐与进制切换看清数值

MT5 里 PrintFormat 的格式化串直接决定日志可读性。第一组例子用 "%-16s%- 20ld% 20lld":header 占 16 字符靠左,value1 转 int 占 20 字符靠左且正数前补空格,value2 转 long 占 20 字符靠右同样补空格。跑出来 Header1 行是 -10000000 配 20000000、Header2 行是 10000000 配 -20000000,正负和左右边缘一眼能分。 第二组把同一对 10000 用不同进制打:"%-16s(DEC) %-16ld(OCT) %-#16lo" 让第三栏走八进制并补 0 前缀,输出 (OCT) 023420;换 "%-16s(hex) %-#16lx(HEX) %-#16lX" 则第二栏 0x2710、第三栏 0X2710,十六进制大小写前缀差别也在日志里落地。 开 MT5 新建脚本把下面代码贴进 OnStart,编译跑一遍,对比终端里 DEC/OCT/HEX 三栏宽度是否如输出样例对齐;外汇和贵金属行情数值跨度大,这类对齐在批量打印 tick 或挂单偏移时更容易看出异常,但交易本身仍属高风险,格式化只帮你看数不替你下单。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare the variables to be printed
   class="type">class="kw">string  header="";   class=class="str">"cmt">// Data header
   class="type">class="kw">double  value1=class="num">0;    class=class="str">"cmt">// First value
   class="type">class="kw">double  value2=class="num">0;    class=class="str">"cmt">// Second value
   
class=class="str">"cmt">//--- The first line with a header in a class="num">16-character field and data in class="num">20-character fields
class=class="str">"cmt">//--- The header and data of the second field are pressed to the left edge.
class=class="str">"cmt">//--- Data values of the third field are pressed to the right edge
class=class="str">"cmt">//--- For the second and third fields from the left, a space is added for positive values.
   header="Header1";
   value1=-class="num">10000000;
   value2=class="num">20000000;
   PrintFormat("%-16s%- 20ld% 20lld",header,(class="type">int)value1,(class="type">long)value2);
   
class=class="str">"cmt">//--- The second line with a header in a class="num">16-character field and data in class="num">20-character fields
class=class="str">"cmt">//--- The header and data of the second field are pressed to the left edge.
class=class="str">"cmt">//--- Data values of the third field are pressed to the right edge
class=class="str">"cmt">//--- For the second and third fields from the left, a space is added for positive values.
   header="Header2";
   value1=class="num">10000000;
   value2=-class="num">20000000;
   PrintFormat("%-16s%- 20ld% 20lld",header,(class="type">int)value1,(class="type">long)value2);
   
   class=class="str">"cmt">/* Sample output:
       Header1      -class="num">10000000                    class="num">20000000
       Header2       class="num">10000000                   -class="num">20000000
   */
   }
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare the variables to be printed
   class="type">class="kw">string  header="";   class=class="str">"cmt">// Data header
   class="type">class="kw">double  value1=class="num">0;    class=class="str">"cmt">// First value
   class="type">class="kw">double  value2=class="num">0;    class=class="str">"cmt">// Second value
   
class=class="str">"cmt">//--- First line with a header and data in class="num">16-character fields
class=class="str">"cmt">//--- The header and data of all fields are pressed to the left edge.
class=class="str">"cmt">//--- Data values of the second field are displayed in decimal format
class=class="str">"cmt">//--- Data values of the third field are displayed in octal format with class="num">0 added before the value
   header="Header1";
   value1=class="num">10000;
   value2=class="num">10000;
   PrintFormat("%-16s(DEC) %-16ld(OCT) %-class="macro">#16lo",header,(class="type">uint)value1,(class="type">uint)value2);
   
class=class="str">"cmt">//--- Second line with header and data in class="num">16-character fields
class=class="str">"cmt">//--- The header and data of all fields are pressed to the left edge.
class=class="str">"cmt">//--- Data values of the second field are displayed in hexadecimal format with 0x added before the value
class=class="str">"cmt">//--- Data values of the third field are displayed in hexadecimal format with 0X added before the value
   header="Header2";
   value1=class="num">10000;
   value2=class="num">10000;
   PrintFormat("%-16s(hex) %-class="macro">#16lx(HEX) %-class="macro">#16lX",header,(class="type">uint)value1,(class="type">uint)value2);
   
   class=class="str">"cmt">/* Sample output:
       Header1(DEC) class="num">10000         (OCT) class="num">023420
       Header2(hex) 0x2710        (HEX) 0X2710
   */
   }

常见问题

先把账户属性打印按模块拆好,再直接替换示例里的格式串和变量名,跑一次看日志是否对齐即可。
%-15s 是左对齐占 15 宽度的字符串,%+d 是带符号位显示的正负整数,照着拼就能控制字段排版。
可以,小布能读取你的输出流并按字段对齐重排,把乱流日志直接转成表格视图方便核对。
在同一个格式串里写 %d 和 %x 两次引用该变量,分别按十进制和十六进制输出就能对比看清。
在 % 和宽度数字之间加负号,例如 %-10d 即左对齐占 10 位,符号位控制写在与宽度同段即可。