MQL5中的结构及其数据打印方法·综合运用
🧩

MQL5中的结构及其数据打印方法·综合运用

(3/3)·从实时分笔到深度挂单,把四种预定义结构的打印函数一次串成可复用模板

偏理论 第 3/3 篇
很多人只在报错时才去翻结构字段,结果打印出来的 Tick 时间和报价对不上本地时区。把 MqlTick、MqlRates、MqlBookInfo 的取数逻辑混用,终端刷出的数字常常连自己都解释不清。本篇把三套结构的打印方法并到一处,避免你再靠试错拼输出。

MqlTick 怎么一次抓全最新盘口

做价格行为分析时,最怕反复调接口拉盘口。MqlTick 结构就是给交易品种存最后一笔报价用的,一次 SymbolInfoTick() 调用就能把 Ask、Bid、Last、Volume 和毫秒级时间全捞出来,不用在分时历史里翻旧账。 它有个反直觉但实用的点:不管这笔分时相对上一笔变没变,所有字段都会填。比如这毫秒只有卖家报价动了,结构里照样带着上一笔的 Ask 和成交量。你回看任意时刻的正确价格,直接读结构就行。 要分辨到底哪类数据刚动,得看 flags 位。TICK_FLAG_BID 是卖价变,TICK_FLAG_ASK 是买价变,TICK_FLAG_LAST 是成交价变,TICK_FLAG_VOLUME 是量变;TICK_FLAG_BUY 和 TICK_FLAG_SELL 则标这笔分时来自买或卖单。外汇和贵金属波动快、杠杆高,靠 flags 过滤噪声能少踩很多坑。 下面这段是结构原型,字段含义已在行内标清,开 MT5 新建脚本粘进去就能编译验证:

MQL5 / C++
class="kw">struct class="type">MqlTick
  {
   class="type">class="kw">datetime      time;           class=class="str">"cmt">// Last price update time
   class="type">class="kw">double        bid;            class=class="str">"cmt">// Current Bid price
   class="type">class="kw">double        ask;            class=class="str">"cmt">// Current Ask price
   class="type">class="kw">double        last;           class=class="str">"cmt">// Current price of the last trade(Last)
   class="type">class="kw">ulong         volume;         class=class="str">"cmt">// Volume for the current Last price
   class="type">long          time_msc;       class=class="str">"cmt">// Last price update time in milliseconds
   class="type">uint          flags;          class=class="str">"cmt">// Tick flags
   class="type">class="kw">double        volume_real;    class=class="str">"cmt">// Volume for the current Last price
  };

◍ 把分时结构打进日志的两种姿势

MqlTick 结构直接丢给 ArrayPrint() 就能在日志里平铺显示,形式和 MqlDateTime 类似。但裸数据可读性一般:time_msc 是毫秒时间戳、flags 是枚举位,肉眼换算费劲。 想要更有意义的展示,得自己写封装函数,返回带左边距和标题宽度的格式化字符串。默认填充和页边宽度都是 0,即不填充、页边宽度等于表头文本长度加 1。 辅助函数里复用毫秒时间转字符串的逻辑,分别提取 tick.time(秒级)、tick.bid、tick.ask、tick.last、tick.volume、tick.time_msc、tick.flags、tick.volume_real。注意 volume_real 是高精度最后价交易量,Last 为 0 即无成交时,这两个量也都是 0,没必要打印。 下面两段脚本可直接在 MT5 跑。第一段取最新 1 个 tick 用 ArrayPrint 输出,样例里 time_msc 为 1689786169589,对应 2023.07.19 17:02:49、flags=6、bid/ask 差 0.00004。第二段取最近 10 个 tick,样例连续 6 个 tick 的 flags 均为 6,说明这几笔都是常规报价无成交。外汇和贵金属杠杆高,实盘 tick 流可能瞬时空窗,验证时请用模拟盘。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare a variable with the class="type">MqlTick type
   class="type">MqlTick  tick;
class=class="str">"cmt">//--- If failed to get the last tick, display the error message and exit the method
   if(!SymbolInfoTick(Symbol(),tick))
     {
       Print("SymbolInfoTick failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Display the tick using standard ArrayPrint()
class=class="str">"cmt">//--- To do this, declare an array of dimension class="num">1 with type class="type">MqlTick,
class=class="str">"cmt">//--- enter the value of the &class="macro">#x27;tick&class="macro">#x27; variable into it and print it
   class="type">MqlTick array[class="num">1];
   array[class="num">0]=tick;
   Print("Last tick(ArrayPrint):");
   ArrayPrint(array);
  class=class="str">"cmt">/* Sample output:
     Last tick(ArrayPrint):
                [time]   [bid]   [ask] [last] [volume]       [time_msc] [flags] [volume_real]
     [class="num">0] class="num">2023.07.class="num">19 class="num">17:class="num">02:class="num">49 class="num">1.28992 class="num">1.28996 class="num">0.0000       class="num">0 class="num">1689786169589       class="num">6       class="num">0.00000
*/
  }
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare a dynamic array of the class="type">MqlTick type
   class="type">MqlTick  array[];
class=class="str">"cmt">//--- If failed to get the last class="num">10 ticks, display the error message and exit the method
   if(CopyTicks(Symbol(),array,COPY_TICKS_ALL,class="num">0,class="num">10)!=class="num">10)
     {
       Print("CopyTicks failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
   Print("Last class="num">10 tick(ArrayPrint):");
   ArrayPrint(array);
  /* Sample output:
     Last class="num">10 tick(ArrayPrint):
                [time]   [bid]   [ask] [last] [volume]       [time_msc] [flags] [volume_real]
     [class="num">0] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">38 class="num">1.28804 class="num">1.28808 class="num">0.0000       class="num">0 class="num">1689787478461       class="num">6       class="num">0.00000
     [class="num">1] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">38 class="num">1.28806 class="num">1.28810 class="num">0.0000       class="num">0 class="num">1689787478602       class="num">6       class="num">0.00000
     [class="num">2] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">38 class="num">1.28804 class="num">1.28808 class="num">0.0000       class="num">0 class="num">1689787478932       class="num">6       class="num">0.00000
     [class="num">3] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">39 class="num">1.28806 class="num">1.28810 class="num">0.0000       class="num">0 class="num">1689787479210       class="num">6       class="num">0.00000
     [class="num">4] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">39 class="num">1.28807 class="num">1.28811 class="num">0.0000       class="num">0 class="num">1689787479765       class="num">6       class="num">0.00000
     [class="num">5] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">39 class="num">1.28808 class="num">1.28812 class="num">0.0000       class="num">0 class="num">1689787479801       class="num">6       class="num">0.00000
     [class="num">6] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">40 class="num">1.28809 class="num">1.28813 class="num">0.0000       class="num">0 class="num">1689787480240       class="num">6       class="num">0.00000
     [class="num">7] class="num">2023.07.class="num">19 class="num">17:class="num">24:class="num">40 class="num">1.28807 class="num">1.28811 class="num">0.0000       class="num">0 class="num">1689787480288       class="num">6       class="num">0.00000

「把毫秒级报价时间拆成可读字符串」

MT5 的 tick 数据里时间常以毫秒时间戳出现,直接打印出来是 1689787480369 这种长整型,肉眼完全没法对应到 K 线。上面这段辅助函数就是把毫秒和 MqlTick 结构里的字段,格式化成带表头的对齐文本。 TimeMSC() 接收 long 型毫秒时间,先除 1000 强转成 datetime 拿秒级时间,再用取模 1000 拿毫秒尾数,StringFormat 用 %.3hu 补三位小数。样例中 1689787480177 会输出成 2023.07.13 09:31:58.177,精度到毫秒。 MqlTickTime()、MqlTickBid()、MqlTickAsk() 是一组按表头宽度对齐的提取器。以 Bid 为例,先用 SymbolInfoInteger 取 SYMBOL_DIGITS 拿到报价小数位(比如磅美常是 5 位),再按传入或默认的表头宽度用 %-.*f 格式化。样例输出 Bid: 1.29237,和 2023.07.19 17:24:40 那两条 tick 的 1.28809 / 1.28810 同属一个毫秒批次,说明当时盘口在 0.1 点内连续跳动。 外汇和贵金属 tick 刷新频率高、滑点风险大,这类格式化函数只解决可读性问题,不预示任何方向。复制进 MT5 的 EA 或脚本里,把 SymbolInfoInteger 换成你实际观察的品种,就能在日志里直接读出带毫秒的盘口快照。

MQL5 / C++
class="type">class="kw">string TimeMSC(class="kw">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="type">class="kw">string MqlTickTime(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Time:";
   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">class="kw">string)tick.time);
   class=class="str">"cmt">/* Sample output:
     Time: class="num">2023.07.class="num">19 class="num">20:class="num">58:class="num">00
    */
  }
class="type">class="kw">string MqlTickBid(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Bid:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
   class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,tick.bid);
   class=class="str">"cmt">/* Sample output:
     Bid: class="num">1.29237
    */
  }
class="type">class="kw">string MqlTickAsk(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)

把报价字段格式化成对齐字符串

在 MT5 自定义指标或 EA 里打印实时 tick 数据时,最烦的就是各字段长短不一、对不齐。下面这组函数把 Ask、Last、Volume、TimeMSC 统一成带缩进和表头宽度的字符串,直接喂给 Comment() 或 FileWrite() 就能整齐输出。 以 Ask 为例:当 header_width 传 0 时,表头宽度自动取字符串长度加 1,也就是 "Ask:" 占 5 列;indent 控制左侧空格数。样例输出里 Ask: 1.29231 说明 EURUSD 这类 5 位小数品种能正常按 SYMBOL_DIGITS 对齐。 小数位数不是写死的,而是用 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 动态拿,所以同一段代码切到 XAUUSD(通常 2 位)也不会多打零。Volume 用 %-I64u 直接打 unsigned long,Last 为 0 时样例显示 Last: 0.00000,意味着此刻还没有成交tick,属正常空值表现。 外汇与贵金属杠杆高、点差跳变快,tick 级打印仅用于盘后核对或可视化辅助,不能据此直接推断成交优劣。

MQL5 / C++
class="type">class="kw">string MqlTickAsk(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Ask:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Get the number of decimal places
   class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
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%-.*f",indent,"",w,header,dg,tick.ask);
   class=class="str">"cmt">/* Sample output:
       Ask: class="num">1.29231
   */
  }
class="type">class="kw">string MqlTickLast(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Last:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
   class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,tick.last);
   class=class="str">"cmt">/* Sample output:
       Last: class="num">0.00000
   */
  }
class="type">class="kw">string MqlTickVolume(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Volume:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
   class="kw">return StringFormat("%*s%-*s%-I64u",indent,"",w,header,tick.volume);
   class=class="str">"cmt">/* Sample output:
       Volume: class="num">0
   */
  }
class="type">class="kw">string MqlTickTimeMSC(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="TimeMSC:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);

◍ 把 tick 时间戳和旗标拆成可读字符串

做高频足迹图时,MqlTick 里的 time_msc 和 flags 都是原始整型/位掩码,直接打印基本没法看。下面两个辅助函数就是把这些字段格式化成人能扫一眼就懂的文本,header_width 传 0 时自动取表头长度+1 做对齐宽度。 第一个片段用 StringFormat("%*s%-*s%-s", indent, "", w, header, TimeMSC(tick.time_msc)) 输出带缩进的时间戳,样例里跑出来是 Time msc: 2023.07.19 21:21:09.732,毫秒级精度对抓滑点有用。

旗标函数则按位与逐个判断 TICK_FLAG_BID / ASK / LAST / VOLUME / BUY / SELL,用 `` 拼接。例如某 tick 同时含买价和卖价更新,输出就是 `Flags: BIDASK`,比看十进制 flags 值直观太多。

在 MT5 里把这两段塞进你的 EA 调试模块,把 header_width 统一设成 12,多行日志就能上下对齐,肉眼排查报价跳动类型会快不少。外汇与贵金属 tick 流受流动性影响差异大,这类解析仅用于诊断,不构成方向判断。

MQL5 / C++
class="type">class="kw">string header="Time msc:";
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,TimeMSC(tick.time_msc));
class=class="str">"cmt">/* Sample output:
   Time msc: class="num">2023.07.class="num">19 class="num">21:class="num">21:class="num">09.732
*/
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return tick flags as a class="type">class="kw">string                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string MqlTickFlags(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Flags:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Define a variable to describe tick flags
  class="type">class="kw">string flags="";
class=class="str">"cmt">//--- Parse tick flags into components
  if((tick.flags & TICK_FLAG_BID)==TICK_FLAG_BID)
      flags+=(flags.Length()>class="num">0 ? "|" : "")+"BID";
  if((tick.flags & TICK_FLAG_ASK)==TICK_FLAG_ASK)
      flags+=(flags.Length()>class="num">0 ? "|" : "")+"ASK";
  if((tick.flags & TICK_FLAG_LAST)==TICK_FLAG_LAST)
      flags+=(flags.Length()>class="num">0 ? "|" : "")+"LAST";
  if((tick.flags & TICK_FLAG_VOLUME)==TICK_FLAG_VOLUME)
      flags+=(flags.Length()>class="num">0 ? "|" : "")+"VOLUME";
  if((tick.flags & TICK_FLAG_BUY)==TICK_FLAG_BUY)
      flags+=(flags.Length()>class="num">0 ? "|" : "")+"BUY";
  if((tick.flags & TICK_FLAG_SELL)==TICK_FLAG_SELL)
      flags+=(flags.Length()>class="num">0 ? "|" : "")+"SELL";
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,flags);
class=class="str">"cmt">/* Sample output:
   Flags: BID|ASK
*/
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the volume for the Last price as a class="type">class="kw">string                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string MqlTickVolumeReal(class="kw">const class="type">MqlTick &tick,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Volume Real:";

「把逐笔 tick 结构打印成可读日志」

调试 EA 时直接看 MqlTick 原始成员很累,写个 MqlTickPrint 统一输出能省掉大量肉眼解析成本。函数入口给了 short_entry 开关:true 时压成一行,false 时按字段分行并支持缩进与表头宽度。 当 short_entry=true 且 Last 非零,样例输出形如「Tick GBPUSD Time: 2023.07.20 13:57:31.376, Bid: 1.28947, Ask: 1.28951, Last: 1.28947, Vol: 33/33.45, Flags: BID|ASK」;若 Last 为 0 则自动隐藏 Last、Volume、Volume Real 三段,避免刷屏无意义零值。 分行模式靠一组 MqlTickXxx 辅助函数拼字符串,header_width 传 0 时取 header.Length()+1 做对齐,indent 控制左侧空格。外汇与贵金属 tick 流密集,实盘日志建议只在 OnTick 首行或异常分支调用,否则终端可能卡顿。

MQL5 / C++
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%-.2f",indent,"",w,header,tick.volume_real);
class=class="str">"cmt">/* Sample output:
   Volume Real: class="num">0.00
 */
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Logs descriptions of all fields of the class="type">MqlTick structure          |
class=class="str">"cmt">//| If Last==class="num">0, Last, Volume and Volume Real fields are not displayed|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void MqlTickPrint(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlTick &tick,class="kw">const class="type">bool short_entry=true,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0,class="type">int index=WRONG_VALUE)
  {
class=class="str">"cmt">//--- Declare the variable for storing the result
  class="type">class="kw">string res="";
class=class="str">"cmt">//--- Get the number of decimal places
  class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
  class="type">class="kw">string num=(index==WRONG_VALUE ? "" : StringFormat("[%ld] ",index));
class=class="str">"cmt">//--- If it is a class="type">class="kw">short entry, log the tick data in the Symbol TimeMSC, Bid, Ask, Last, Vol/VolR, Flags format
  if(short_entry)
    {
      class=class="str">"cmt">//--- If Last is not zero, display Last, Volume and Volume Real, otherwise they are all zero and there is no point in displaying them
      class="type">class="kw">string last=(tick.last!=class="num">0 ? StringFormat(", Last: %.*f, Vol: %I64u/%.2f",dg,tick.last,tick.volume,tick.volume_real) : "");
      res=StringFormat("%sTick %s Time: %s, Bid: %.*f, Ask: %.*f%s, %s",num,symbol,TimeMSC(tick.time_msc),dg,tick.bid,dg,tick.ask,last,MqlTickFlags(tick));
      Print(res);
    }
  class=class="str">"cmt">/* Sample output(if Last is not zero):
      Tick GBPUSD Time: class="num">2023.07.class="num">20 class="num">13:class="num">57:class="num">31.376, Bid: class="num">1.28947, Ask: class="num">1.28951, Last: class="num">1.28947, Vol: class="num">33/class="num">33.45, Flags: BID|ASK
      Sample output(if Last is zero):
      Tick GBPUSD Time: class="num">2023.07.class="num">20 class="num">13:class="num">59:class="num">33.274, Bid: class="num">1.28956, Ask: class="num">1.28960, Flags: BID|ASK
  */
class=class="str">"cmt">//--- Otherwise
  else
    {
      class=class="str">"cmt">//--- create a class="type">class="kw">string describing all the data of the structure with indents and a given width of the header field 
      res=StringFormat("%s\n%s\n%s%s%s\n%s\n%s%s",
                      MqlTickTime(tick,header_width,indent),
                      MqlTickBid(symbol,tick,header_width,indent),
                      MqlTickAsk(symbol,tick,header_width,indent),
                      (tick.last!=class="num">0 ? "\n"+MqlTickLast(symbol,tick,header_width,indent) : ""),
                      (tick.last!=class="num">0 ? "\n"+MqlTickVolume(tick,header_width,indent) : ""),
                      MqlTickTimeMSC(tick,header_width,indent),
                      MqlTickFlags(tick,header_width,indent),
                      (tick.last!=class="num">0 ? "\n"+MqlTickVolumeReal(tick,header_width,indent) : "")

用 CopyTicks 抓最近 tick 并逐条打印

MT5 里拿实时逐笔数据,核心就是 CopyTicks()。给 Symbol() 传当前品种,配合 COPY_TICKS_ALL 把买卖盘全拉进 MqlTick 动态数组,第四个参数从 0 开始、第五个参数控制条数,比如写 10 就是最近 10 笔。 若返回值不等于请求的条数,说明拷贝失败,直接用 GetLastError() 打出错误码并 return,避免后面空数组越界。GBPUSD 实测样例里,10 笔 tick 的时间戳跨度约 728 毫秒(15:36:29.941 到 15:36:30.669),Bid 从 1.28686 走到 1.28674,短期承压下行的概率偏向增加。 把数组用 for 循环遍历,对每笔调 MqlTickPrint() 即可在日志里看到带序号的明细:时间精确到毫秒、Bid/Ask 及 Flags。外汇与贵金属杠杆高,tick 级波动可能瞬间扫掉止损,上机验证前先开模拟盘跑这套。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare a dynamic array of the class="type">MqlTick type
   class="type">MqlTick  array[];
class=class="str">"cmt">//--- If failed to get the last class="num">10 ticks, display the error message and exit the method
   if(CopyTicks(Symbol(),array,COPY_TICKS_ALL,class="num">0,class="num">10)!=class="num">10)
     {
       Print("CopyTicks failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
   Print("Last class="num">10 tick(MqlTickPrint):");
   for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++)
      MqlTickPrint(Symbol(),array[i],true,class="num">0,class="num">0,i);
  }
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare a dynamic array of the class="type">MqlTick type
   class="type">MqlTick  array[];
class=class="str">"cmt">//--- If the last class="num">4 ticks are not received in the array, display an error message and leave
   if(CopyTicks(Symbol(),array,COPY_TICKS_ALL,class="num">0,class="num">4)!=class="num">4)
     {
       Print("CopyTicks failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
   Print("Last class="num">4 tick(MqlTickPrint):");
   for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++)
     {

◍ 逐笔回看 GBPUSD 的盘口跳动

把最近 4 笔 tick 直接打印出来,比看 K 线更贴近真实成交层。下面这段输出是在 2023.07.20 17:04:51 这一秒内抓到的 GBPUSD 四笔报价。 Bid 从 1.28776 走到 1.28771 再回弹到 1.28772,Ask 同步在 1.28780→1.28775→1.28776 之间摆动,点差稳定锁在 0.4 点(4 位数报价下为 4 个点)。四笔时间间隔分别是 128ms、47ms、302ms,说明这一秒内的流动性并不均匀。 外汇和贵金属属高风险品种,tick 级数据只反映瞬时盘口,不能单独作为方向依据。打开 MT5 把这段打印逻辑接进你的 EA,跑一下 GBPUSD 的 M1 回测,重点看 Time msc 的跳变间隔是否和你 broker 的真实环境一致。

MQL5 / C++
PrintFormat("Tick[%lu] %s:",i,Symbol());
   MqlTickPrint(Symbol(),array[i],false,class="num">14,class="num">2);
   }
 class=class="str">"cmt">/* Sample output:
   Last class="num">4 tick(MqlTickPrint):
   Tick[class="num">0] GBPUSD:
     Time:          class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51
     Bid:           class="num">1.28776
     Ask:           class="num">1.28780
     Time msc:      class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.203
     Flags:         BID|ASK
   Tick[class="num">1] GBPUSD:
     Time:          class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51
     Bid:           class="num">1.28772
     Ask:           class="num">1.28776
     Time msc:      class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.331
     Flags:         BID|ASK
   Tick[class="num">2] GBPUSD:
     Time:          class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51
     Bid:           class="num">1.28771
     Ask:           class="num">1.28775
     Time msc:      class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.378
     Flags:         BID|ASK
   Tick[class="num">3] GBPUSD:
     Time:          class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51
     Bid:           class="num">1.28772
     Ask:           class="num">1.28776
     Time msc:      class="num">2023.07.class="num">20 class="num">17:class="num">04:class="num">51.680
     Flags:         BID|ASK
 */
}

「MqlRates 里装的是每根 K 线的全部底料」

在 MT5 里做价格行为分析,绕不开 MqlRates 这个结构。它把单根 K 线的时间、OHLC、成交量、点差和真实成交量一次性打包,CopyRates 取出来的数组每个元素都是它。 外汇和贵金属属高风险品种,点差字段在多数经纪商处是浮动的,黄金跳空时 spread 可能从常态 15 点瞬间拉到 80 点以上,读它前先确认当前品种有没有真实成交量数据,否则 real_volume 会返回 0。 下面这段是结构定义,逐行看一遍就知道哪几个字段能直接喂给你的 AIGC 特征工程:

MQL5 / C++
class="kw">struct class="type">MqlRates
  {
   class="type">class="kw">datetime time;          class=class="str">"cmt">// period start time
   class="type">class="kw">double   open;          class=class="str">"cmt">// open price
   class="type">class="kw">double   high;          class=class="str">"cmt">// high price for the period
   class="type">class="kw">double   low;           class=class="str">"cmt">// low price for the period
   class="type">class="kw">double   close;         class=class="str">"cmt">// close price
   class="type">long     tick_volume;   class=class="str">"cmt">// tick volume
   class="type">int      spread;        class=class="str">"cmt">// spread
   class="type">long     real_volume;   class=class="str">"cmt">// exchange volume
  };

把 MqlRates 柱体数据读进日志

MqlRates 是 MT5 里承载单根 K 线历史数据的结构,靠 CopyRates() 填充。想抓当前柱,用第一种调用形式、索引 0、复制根数填 1 即可;数组永远是 MqlRates 类型,所以直接用 ArrayPrint() 甩进日志最省事。 但 ArrayPrint() 的输出是横排长表,日志窗口上沿一挡,[time][open][high] 这些列头就看不全,你根本分不清某列数字是 spread 还是 real_volume。GBPUSD H1 上最近一根的样例里,tick_volume 能到 2083、spread 在 7 点左右,裸打印时极易看串列。 自己包一层函数更稳:返回每个字段的中文描述,标题和实际值分开,还能设左缩进和标题宽度。比如标题宽 14、缩进 2 字符,最后 4 根柱就能在日志里排成对齐小表,Time/Open/High/Low/Close/TickVolume/Spread/RealVolume 各归各列。 复制完数据记得当时间序列索引——索引 0 对应当前柱,和图表顺序一致。下面两段脚本分别打印当前 1 根和最近 10 根,直接丢进 MT5 脚本跑就能验证输出差异。外汇与贵金属杠杆高,日志数据仅供结构验证,不构成方向判断。

MQL5 / C++
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>()
  {
<span class="comment">class=class="str">"cmt">//---</span>
   <span class="predefines">class="type">MqlRates</span> array[];
   <span class="keyword">if</span>(<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(),<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">0</span>,<span class="number">class="num">1</span>,array)!=<span class="number">class="num">1</span>)
     {
       <span class="functions">Print</span>(<span class="class="type">class="kw">string">"CopyRates failed, error: "</span>,(<span class="keyword">class="type">class="kw">string</span>)<span class="functions">GetLastError</span>());
       <span class="keyword">class="kw">return</span>;
     }
   <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Current bar "</span>,<span class="functions">Symbol</span>(),<span class="class="type">class="kw">string">" "</span>,<span class="functions">StringSubstr</span>(<span class="functions">EnumToString</span>(<span class="functions">Period</span>()),<span class="number">class="num">7</span>),<span class="class="type">class="kw">string">" (ArrayPrint):"</span>);
   <span class="functions">ArrayPrint</span>(array);
   <span class="comment">class=class="str">"cmt">/* Sample output:
       Current bar GBPUSD H1(ArrayPrint):
                     [time]   [open]   [high]   [low] [close] [tick_volume] [spread] [real_volume]
[class="num">0] class="num">2023.07.class="num">21 class="num">04:class="num">00:class="num">00 class="num">1.28763 class="num">1.28765 class="num">1.28663 class="num">1.28748            class="num">2083         class="num">7             class="num">0
   */</span>
  }
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>()
  {
<span class="comment">class=class="str">"cmt">//---</span>
   <span class="predefines">class="type">MqlRates</span> array[];
   <span class="keyword">if</span>(<span class="functions">CopyRates</span>(<span class="functions">Symbol</span>(),<span class="macro">PERIOD_CURRENT</span>,<span class="number">class="num">0</span>,<span class="number">class="num">10</span>,array)!=<span class="number">class="num">10</span>)
     {
       <span class="functions">Print</span>(<span class="class="type">class="kw">string">"CopyRates failed, error: "</span>,(<span class="keyword">class="type">class="kw">string</span>)<span class="functions">GetLastError</span>());
       <span class="keyword">class="kw">return</span>;
     }
   <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Data of the last class="num">10 bars: "</span>,<span class="functions">Symbol</span>(),<span class="class="type">class="kw">string">" "</span>,<span class="functions">StringSubstr</span>(<span class="functions">EnumToString</span>(<span class="functions">Period</span>()),<span class="number">class="num">7</span>),<span class="class="type">class="kw">string">" (ArrayPrint):"</span>);
   <span class="functions">ArrayPrint</span>(array);
   <span class="comment">/* Sample output:
       Data of the last class="num">10 bars: GBPUSD H1(ArrayPrint):
                     [time]   [open]   [high]   [low] [close] [tick_volume] [spread] [real_volume]
[class="num">0] class="num">2023.07.class="num">20 class="num">20:class="num">00:class="num">00 class="num">1.28530 class="num">1.28676 class="num">1.28512 class="num">1.28641            class="num">2699         class="num">4             class="num">0
[class="num">1] class="num">2023.07.class="num">20 class="num">21:class="num">00:class="num">00 class="num">1.28641 class="num">1.28652 class="num">1.28557 class="num">1.28587            class="num">1726         class="num">3             class="num">0
[class="num">2] class="num">2023.07.class="num">20 class="num">22:class="num">00:class="num">00 class="num">1.28587 class="num">1.28681 class="num">1.28572 class="num">1.28648            class="num">2432         class="num">3             class="num">0
[class="num">3] class="num">2023.07.class="num">20 class="num">23:class="num">00:class="num">00 class="num">1.28648 class="num">1.28683 class="num">1.28632 class="num">1.28665             class="num">768         class="num">4             class="num">0
[class="num">4] class="num">2023.07.class="num">21 class="num">00:class="num">00:class="num">00 class="num">1.28663 class="num">1.28685 class="num">1.28613 class="num">1.28682             class="num">396         class="num">1             class="num">0
[class="num">5] class="num">2023.07.class="num">21 class="num">01:class="num">00:class="num">00 class="num">1.28684 class="num">1.28732 class="num">1.28680 class="num">1.28714             class="num">543         class="num">8             class="num">0</span>

◍ 把 K 线结构体压成可读字符串

在 MT5 里用 MqlRates 抓到的行情是纯数值数组,直接打印就是一堆浮点,肉眼难对账。上面这组 2023.07.21 02:00–05:00 的 GBPCAD 小时线(开 1.28714 / 高 1.28740 / 低 1.28690 / 收 1.28721,成交量 814→2058→3480→18)就是典型 raw 输出,不标头根本分不清哪列是哪列。 MqlRatesTime 只接一个 rates 引用,用 StringFormat("%*s%-*s%-s", indent, "", w, header, (string)rates.time) 把时间按缩进和列宽吐出来;header 写死 "Time:",列宽不传就自动取字符串长度+1。 MqlRatesOpen 多接了 symbol 参数,先 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 拿小数位,再 StringFormat("%*s%-*s%-.*f", …, dg, rates.open) 控制精度——GBPCAD 是 5 位小数,输出就是 1.28812 这种。High 函数开头同构,只是 header 换成 "High:" 并读 rates.high。 实盘接小布盯盘时,把这些函数串进 EA 的调试输出,能直接在日志里对齐每根 K 线的开高收,省掉手动数位的麻烦;外汇和贵金属杠杆高,日志核对出错可能直接放大滑点风险。

MQL5 / C++
class="type">class="kw">string MqlRatesTime(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Time:";
   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">class="kw">string)rates.time);
  }

class="type">class="kw">string MqlRatesOpen(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Open:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Get the number of decimal places
   class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
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%-.*f",indent,"",w,header,dg,rates.open);
  }

class="type">class="kw">string MqlRatesHigh(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Define the header text and the width of the header field

「把 K 线高低收及 tick 量转成对齐字符串」

在 MT5 自定义指标里想把单根 K 线的 High / Low / Close 以及 tick 成交量直接打印成对齐面板,最省事的办法是写一组小函数,按品种小数位自动格式化。下面这三个函数分别处理最高价、最低价和收盘价,逻辑完全一致,只是取的结构体字段不同。 以 MqlRatesHigh 为例:header 写死为 "High:",当调用方没传 header_width(默认 0)时,字段宽取字符串长度加 1,也就是 6;若传了具体值就按传值来。小数位用 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 实时拿,EURUSD 这类常见对是 5 位,XAUUSD 通常是 2 位。 StringFormat 的格式串 "%*s%-*s%-.*f" 里,第一个 %*s 吃 indent 做前导空格,第二个 %-*s 按宽 w 左对齐写表头,最后的 %-.*f 按 dg 位小数输出 rates.high。样例里输出就是 " High: 1.28859"(前方缩进两空格时)。Low 和 Close 函数只是把 header 换成 "Low:" / "Close:" 并取 rates.low / rates.close,样例分别落出 1.28757 与 1.28770。 tick 量函数 MqlRatesTickVolume 不依赖 symbol 参数,因为成交量没有小数位概念,调用时同样可传 header_width 和 indent 控制排版。复制下面整段到 EA 或脚本里,挂 EURUSD 的 M1 棒调一下,就能在日志看到整齐的三行价格加一行量。外汇和贵金属杠杆高,这类格式化只解决显示问题,不涉及任何方向判断。

MQL5 / C++
class="type">class="kw">string MqlRatesHigh(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
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="High:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Get the number of decimal places
  class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
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%-.*f",indent,"",w,header,dg,rates.high);
  class=class="str">"cmt">/* Sample output:
    High: class="num">1.28859
  */
  }
class="type">class="kw">string MqlRatesLow(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
  class="type">class="kw">string header="Low:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
  class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
  class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,rates.low);
  class=class="str">"cmt">/* Sample output:
    Low: class="num">1.28757
  */
  }
class="type">class="kw">string MqlRatesClose(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
  class="type">class="kw">string header="Close:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
  class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
  class="kw">return StringFormat("%*s%-*s%-.*f",indent,"",w,header,dg,rates.close);
  class=class="str">"cmt">/* Sample output:
    Close: class="num">1.28770
  */
  }
class="type">class="kw">string MqlRatesTickVolume(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {

把 MqlRates 字段格式化成可读字符串

在 MT5 里直接 Print 一个 MqlRates 结构,默认输出会带一堆字段,盯盘时反而看不清重点。下面这三个函数把单根 bar 的 Tick Volume、Spread、Real Volume 各自抽出来,按固定宽度拼成一行字符串,方便在日志里横向比对。 若调用时 header_width 传 0,函数内部会用 header 文本长度 +1 作为列宽;比如 "Tick Volume:" 长 12,列宽就是 13。Spread 样例输出为 "Spread: 4",Real Volume 在多数外汇品种上返回 0(经纪商不提供交易所成交量)。 OnStart 里先 CopyRates 拉最近 10 根 bar,ArraySetAsSeries 改成时间序索引,再循环调 MqlRatesPrint 打印。你打开 MT5 策略测试器跑这段,日志里就能看到每根 bar 的精简字段,不用自己解析结构体。 外汇与贵金属杠杆高,实盘前先在 demo 验证输出格式,避免日志刷屏影响排查。

MQL5 / C++
class="type">class="kw">string MqlRatesTickVolume(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Tick Volume:";
   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%-lld",indent,"",w,header,rates.tick_volume);
   class=class="str">"cmt">/* Sample output:
       Tick Volume: class="num">963
   */
  }
class="type">class="kw">string MqlRatesSpread(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Spread:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
   class="kw">return StringFormat("%*s%-*s%-ld",indent,"",w,header,rates.spread);
   class=class="str">"cmt">/* Sample output:
       Spread: class="num">4
   */
  }
class="type">class="kw">string MqlRatesRealVolume(class="kw">const class="type">MqlRates &rates,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   class="type">class="kw">string header="Real Volume:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
   class="kw">return StringFormat("%*s%-*s%-lld",indent,"",w,header,rates.real_volume);
   class=class="str">"cmt">/* Sample output:
       Real Volume: class="num">0
   */
  }
class="type">void OnStart()
  {
   class="type">MqlRates array[];
   if(CopyRates(Symbol(),PERIOD_CURRENT,class="num">0,class="num">10,array)!=class="num">10)
     {
       Print("CopyRates failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
   ArraySetAsSeries(array,true);
   for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++)
      MqlRatesPrint(Symbol(),PERIOD_CURRENT,array[i],true,class="num">0,class="num">0,i);
  }

◍ 用 CopyRates 把最近几根 K 线甩进日志

想快速核对 MT5 当前品种最近几根 H1 bar 的 OHLC、点差和成交量,不用手动翻报价窗口,写一小段脚本直接打印就行。下面这段把最近 4 根 bar 拉进 MqlRates 数组,再逐根输出。 从样例看,GBPUSD H1 在 2023.07.21 14:00 那根 bar 的 Tick Volume 只有 821,而 10:00 那根到了 8883,相差超 10 倍,说明亚盘尾段流动性明显萎缩。外汇和贵金属杠杆高,这种低量时段滑点可能突然放大,下单前最好看一眼真实成交厚度。 ArraySetAsSeries 把数组按时间序倒排,array[0] 就是最新 bar;循环里调用 MqlRatesPrint 时传了 14 和 2,分别控制时间和价格的输出宽度与小数位。你改这两个数就能适配自己的看盘习惯。 实盘跑之前,先把 CopyRates 的返回值判掉——若不等于请求的 4 根就直接 Print 错误码退出,避免后面用空数组算出脏信号。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Copy the last class="num">4 data bars to the class="type">MqlRates array
   class="type">MqlRates array[];
   if(CopyRates(Symbol(),PERIOD_CURRENT,class="num">0,class="num">4,array)!=class="num">4)
     {
       Print("CopyRates failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Set the indexing of the array like a timeseries
   ArraySetAsSeries(array,true);
class=class="str">"cmt">//--- Print class="type">class="kw">short entries in the journal in a loop through the array with the received bar data
   for(class="type">int i=class="num">0;i<(class="type">int)array.Size();i++)
      MqlRatesPrint(Symbol(),PERIOD_CURRENT,array[i],false,class="num">14,class="num">2,i);
  }

「从裸数据看单根 K 线的真实成交流水」

上面这段结构直接把一根 2023.07.21 11:00 的 GBPCAD(或同类货币对)小时 K 线字段全摊开了:开 1.28695、高 1.28745、低 1.28401、收 1.28581,实体 11.4 点、上下影线合计 36.8 点,是典型的探针型波动。 Tick Volume 显示 7440,而 Real Volume 为 0——在 MT5 多数外汇品种上经纪商不传真实成交量,只能靠 tick 数反推参与度,这点做量价分析时不能当真体积用。 Spread 字段为 1,说明该时刻点差极低,若你用 EA 回测这段,滑点设 1~2 即可贴近当时环境;贵金属或交叉盘在高波动时会扩到 10 以上,外汇高风险在于点差跳变会直接吃掉窄止损。 把这段打印逻辑塞进 OnTick 或 OnCalculate,开 MT5 用「预览数据窗口」对照,就能确认自己抓的每根 K 是不是和经纪商给的一致。

MQL5 / C++
class=class="str">"cmt">/*
 Time: class="num">2023.07.class="num">21 class="num">11:class="num">00:class="num">00
 Open: class="num">1.28695
 High: class="num">1.28745
 Low: class="num">1.28401
 Close: class="num">1.28581
 Tick Volume: class="num">7440
 Spread: class="num">1
 Real Volume: class="num">0
 */

用 MqlBookInfo 接住市场深度流

想在 MT5 里读市场深度(DOM),先声明一个 MqlBookInfo 类型变量即可,它专门承载买卖挂单的快照。不过市场深度不是所有品种都给,贵金属与部分外汇交叉盘可能不推送,开 EA 前先在「市场报价」里确认符号属性里的「市场深度」是否可用。 订阅动作必须显式完成:调用 MarketBookAdd() 把品种加进深度监听,退出或卸载 EA 时务必 MarketBookRelease() 取消,否则终端会一直维持该品种的簿记推送,拖慢整体行情响应。 EA 里要落地处理,必须实现 void OnBookEvent() 函数,系统每次深度变动都会触发它,你在里面把 MqlBookInfo 数组读出来就能做价量分析。下面这段结构定义就是数据容器的全貌,逐行拆开看字段含义。 struct 声明 MqlBookInfo 聚合类型,包含四个成员:type 来自 ENUM_BOOK_TYPE 枚举,区分买边或卖边挂单;price 是挂单价格,double 精度;volume 为整型手数(老式字段);volume_real 是高精度真实成交量,double 类型,实盘应以它为准。

MQL5 / C++
class="kw">struct MqlBookInfo
  {
   ENUM_BOOK_TYPE    type;          class=class="str">"cmt">// order type from ENUM_BOOK_TYPE enumeration
   class="type">class="kw">double            price;         class=class="str">"cmt">// price
   class="type">long              volume;        class=class="str">"cmt">// volume
   class="type">class="kw">double            volume_real;   class=class="str">"cmt">// volume with increased accuracy
  };

◍ 把市场深度订单数组打进日志的两种写法

市场深度(Market Book)每次推送都是一整组挂单快照,本质是 MqlBookInfo 结构数组。最省事的做法是直接 ArrayPrint() 把数组丢进日志,但默认输出里 type 字段只显示数值 1 或 2,肉眼没法立刻区分买卖侧。 下面脚本在 OnStart 里先 MarketBookAdd 订阅、再 MarketBookGet 取快照、ArrayPrint 打印、最后 MarketBookRelease 退订。示例输出中 GBPUSD 的快照显示:买侧(type=1)价格从 1.28280 到 1.28273 四档,卖侧(type=2)从 1.28268 到 1.28260 四档,volume_real 与 volume 整数一致。 为了可读性,可以写 MqlBookInfoType() 这类辅助函数,把 ENUM_BOOK_TYPE 枚举转成 'Bid'/'Ask' 字样。核心技巧是用 EnumToString 拿到全名后 StringSubstr 截掉前 10 个字符('BOOK_TYPE_' 长度),再首字母转大写。 打印主模式建议用单行紧凑输出,因为深度是数组不是单笔。若需要肉眼对账,再写第二个函数用表格模式循环打印每笔的 type/price/volume/volume_real。开 MT5 新建脚本贴入下方代码,切到 GBPUSD 横盘时段跑一下,就能看到真实十档挂单分布。外汇与贵金属杠杆高,盘口快照仅反映瞬时流动性,不预示方向。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare an array to store a snapshot of the market depth
   MqlBookInfo array[];
class=class="str">"cmt">//--- If unable to open the market depth and subscribe to its events, inform of that and leave
   if(!MarketBookAdd(Symbol()))
     {
       Print("MarketBookAdd failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- If unable to obtain the market depth entries, inform of that and leave
   if(!MarketBookGet(Symbol(),array))
     {
       Print("MarketBookGet failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Print the header in the journal and the market depth snapshot from the array below
   Print("MarketBookInfo by ",Symbol(),":");
   ArrayPrint(array);
class=class="str">"cmt">//--- If unable to unsubscribe from the market depth, send an error message to the journal
   if(!MarketBookRelease(Symbol()))
     Print("MarketBookRelease failed, error: ",(class="type">class="kw">string)GetLastError());
   class=class="str">"cmt">/* Sample output:
      MarketBookInfo by GBPUSD:
           [type] [price] [volume] [volume_real]
      [class="num">0]      class="num">1 class="num">1.28280       class="num">100     class="num">100.00000
      [class="num">1]      class="num">1 class="num">1.28276        class="num">50      class="num">50.00000
      [class="num">2]      class="num">1 class="num">1.28275        class="num">20      class="num">20.00000
      [class="num">3]      class="num">1 class="num">1.28273        class="num">10      class="num">10.00000
      [class="num">4]      class="num">2 class="num">1.28268        class="num">10      class="num">10.00000
      [class="num">5]      class="num">2 class="num">1.28266        class="num">20      class="num">20.00000
      [class="num">6]      class="num">2 class="num">1.28265        class="num">50      class="num">50.00000
      [class="num">7]      class="num">2 class="num">1.28260       class="num">100     class="num">100.00000
    */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the order type in the market depth as a class="type">class="kw">string             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string MqlBookInfoType(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Get the value of the order type
   ENUM_BOOK_TYPE book_type=book.type;
class=class="str">"cmt">//--- "Cut out" the type from the class="type">class="kw">string obtained from enum
   class="type">class="kw">string type=StringSubstr(EnumToString(book_type),class="num">10);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital
   if(type.Lower())
     type.SetChar(class="num">0,class="type">class="kw">ushort(type.GetChar(class="num">0)-0x20));

「把深度挂单字段拼成可读字符串」

做市场深度(Market Book)面板时,原始 MqlBookInfo 结构体里的 type、price、volume 直接打印会挤成一团。下面这组函数把每个字段单独格式化,统一加缩进和表头宽度,方便在 Comment() 或文件里对齐输出。 type 字段里常带下划线(如 BUY_LIMIT),先用 StringReplace 把 '_' 换成空格,再按传入的 header_width 补空白。若 header_width 传 0,则宽度自动取表头文字长度+1,例如 'Type:' 占 6 字符。 价格函数多一步:用 SymbolInfoInteger(symbol, SYMBOL_DIGITS) 拿小数位,再走 %.*f 控制精度。样例输出 Price: 1.28498,说明当时货币对是 5 位小数报价。外汇与贵金属杠杆高,深度数据仅反映瞬时流动性,不预示方向。 成交量有 volume(整型)和 volume_real(浮点高精度)两版,后者表头写 'Volume Real:',用 %-lld 或对应浮点格式输出。复制下面代码到 MT5 脚本,改 symbol 参数就能看到自己品种的真实盘口字符串。

MQL5 / C++
class="type">class="kw">string MqlBookInfoType(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
class=class="str">"cmt">//--- Replace all underscore characters with space in the resulting line
   StringReplace(type,"_"," ");
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="Type:";
   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,type);
   class=class="str">"cmt">/* Sample output:
       Type: Sell
   */
  }

class="type">class="kw">string MqlBookInfoPrice(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Price:";
   class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Get the number of decimal places
   class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
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%-.*f",indent,"",w,header,dg,book.price);
   class=class="str">"cmt">/* Sample output:
       Price: class="num">1.28498
   */
  }

class="type">class="kw">string MqlBookInfoVolume(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Volume:";
   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%-lld",indent,"",w,header,book.volume);
   class=class="str">"cmt">/* Sample output:
       Volume: class="num">100
   */
  }

class="type">class="kw">string MqlBookInfoVolumeReal(class="kw">const MqlBookInfo &book,class="kw">const class="type">uint header_width=class="num">0,class="kw">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="Volume Real:";

把市场深度结构打印成可读日志

MqlBookInfoPrint 函数负责把 MqlBookInfo 市场深度结构的所有字段输出到 MT5 专家日志。它支持两种模式:short_entry=true 时按单行紧凑格式打印,false 时逐字段带缩进展开。 紧凑模式用格式串 "%-8s%-11s%- *.*f Volume%- 5lld Real%- 8.2f",实测样本输出为 [00] Sell 1.28598 Volume 100 Real 100.00,其中价格小数位由 SymbolInfoInteger(symbol,SYMBOL_DIGITS) 动态决定。 type 字段从枚举名截取:先 EnumToString 取全名,再用 StringSubstr 砍掉前 10 个字符(如 BOOK_TYPE_SELLSELL),转小写后首字母大写,下划线替换成空格,最终显示成 Sell。 展开模式下,函数拼出 Market Book by %s %s: 头,并依次调用 MqlBookInfoType / Price / Volume / VolumeReal 四个辅助函数生成子行;样本可见 BoolInfo by GBPUSD [00]: 后接 Type: Sell 等分段。外汇与贵金属市场深度变动极快,这类日志仅反映调用瞬间的快照,高频品种可能几毫秒后就失真,用于复盘而非实时决策。

MQL5 / C++
class="type">void MqlBookInfoPrint(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book,
                      class="kw">const class="type">bool short_entry=true,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0,class="type">int index=WRONG_VALUE)
  {
  class="type">class="kw">string res="";
  class="type">int dg=(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
  class="type">class="kw">string num=(index==WRONG_VALUE ? "" : StringFormat("[%02ld]",index));
  class="type">class="kw">string type=StringSubstr(EnumToString(book.type),class="num">10);
  if(type.Lower())
     type.SetChar(class="num">0,class="type">class="kw">ushort(type.GetChar(class="num">0)-0x20));
  StringReplace(type,"_"," ");
  if(short_entry)
     {
     res=StringFormat("%-8s%-11s%- *.*f Volume%- 5lld Real%- class="num">8.2f",
                       num,type,dg+class="num">4,dg,book.price,book.volume,book.volume_real);
     Print(res);
     }
  else
     {
     res=StringFormat("Market Book by %s %s:\n%s\n%s\n%s\n%s",symbol,num,
                      MqlBookInfoType(book,header_width,indent),
                      MqlBookInfoPrice(symbol,book,header_width,indent),
                      MqlBookInfoVolume(book,header_width,indent),
                      MqlBookInfoVolumeReal(book,header_width,indent)
                      );
     Print(res);
     }
  }

◍ 把订单簿快照打到日志里的两种写法

做日内剥头皮或盯价差的人,往往需要在 MT5 日志里快速看一眼当前货币对的真实买卖挂单厚度。下面这段脚本用 MarketBookAdd 订阅、MarketBookGet 抓取、MarketBookRelease 退订,把 GBPUSD 的盘口快照以短格式刷出来。 从示例输出能看到,买盘最优价 1.28657 挂了 10 手,卖盘最优价 1.28664 也是 10 手,而更外层卖价 1.28674 有 100 手压着——这种近端薄、远端厚的形态,在伦敦盘初出现时,价格可能倾向在区间内反复蹭。外汇和贵金属杠杆高,盘口瞬间抽单是常态,别把静态快照当成交依据。 短格式靠 MqlBookInfoPrintShort 循环调用逐条打印;如果想对齐成表格,另有 MqlBookInfoPrintTable 支持 header_width 和 indent 参数控制缩进宽度。两者都基于同一个 MqlBookInfo 结构数组,差别只在排版。 开 MT5 新建脚本粘进去,把 Symbol() 换成你要测的 XAUUSD 或 EURUSD,跑完去「专家」标签翻日志,就能验证你经纪商盘口刷新频率到底够不够快。

MQL5 / C++
class="type">void MqlBookInfoPrintShort(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book_array[])
  {
   PrintFormat("Market Book by %s:",symbol);
   for(class="type">int i=class="num">0;i<(class="type">int)book_array.Size();i++)
      MqlBookInfoPrint(symbol,book_array[i],true,class="num">0,class="num">0,i);
  }
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare an array to store a snapshot of the market depth
   MqlBookInfo array[];
class=class="str">"cmt">//--- If unable to open the market depth and subscribe to its events, inform of that and leave
   if(!MarketBookAdd(Symbol()))
     {
       Print("MarketBookAdd failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- If unable to obtain the market depth entries, inform of that and leave
   if(!MarketBookGet(Symbol(),array))
     {
       Print("MarketBookGet failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Print in the journal a snapshot of the market depth from the array in the form of strings
   MqlBookInfoPrintShort(Symbol(),array);
class=class="str">"cmt">//--- If unable to unsubscribe from the market depth, send an error message to the journal
   if(!MarketBookRelease(Symbol()))
      Print("MarketBookRelease failed, error: ",(class="type">class="kw">string)GetLastError());
  }
class="type">void MqlBookInfoPrintTable(class="kw">const class="type">class="kw">string symbol,class="kw">const MqlBookInfo &book_array[],class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0)
  {
   for(class="type">int i=class="num">0;i<(class="type">int)book_array.Size();i++)

「抓一帧市场深度快照到日志」

想在 MT5 里看裸盘口而不依赖 DOM 窗口,可用 MarketBookAdd 订阅当前品种深度,再用 MarketBookGet 把买卖挂单一次性捞进 MqlBookInfo 数组。下面这段脚本跑完会在专家日志里打出一张字符串表,方便你比对盘口厚度与价格层级。 订阅失败先 GetLastError 打印退出,避免空数组误用;取数成功才调用 MqlBookInfoPrintTable(Symbol(),array,14,2),其中 14 是表头宽度、2 是缩进空格。文末用 MarketBookRelease 退订,防止后台继续吃事件。 示例输出里 GBPUSD 的卖单从 1.28627 向下递减到 1.28615,累计挂量 180;买单从 1.28610 向下到 1.28599,累计挂量也是 180。这种对称薄盘在英镑系里常见,遇数据发布可能瞬间抽厚,外汇和贵金属杠杆高,盘口失真概率不低。 把代码贴进 EA 的 OnStart 直接跑,你能立刻在日志看到自己品种的实时八档;若想做价行为分析,可把 array 留着给后续函数算失衡比。

MQL5 / C++
  MqlBookInfoPrint(symbol,book_array[i],false,header_width,indent,i);
  }
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare an array to store a snapshot of the market depth
   MqlBookInfo array[];
class=class="str">"cmt">//--- If unable to open the market depth and subscribe to its events, inform of that and leave
   if(!MarketBookAdd(Symbol()))
     {
       Print("MarketBookAdd failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- If unable to obtain the market depth entries, inform of that and leave
   if(!MarketBookGet(Symbol(),array))
     {
       Print("MarketBookGet failed, error: ",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Print in the journal a snapshot of the market depth from the array in the form of strings
   MqlBookInfoPrintTable(Symbol(),array,class="num">14,class="num">2);
class=class="str">"cmt">//--- If unable to unsubscribe from the market depth, send an error message to the journal
   if(!MarketBookRelease(Symbol()))
     Print("MarketBookRelease failed, error: ",(class="type">class="kw">string)GetLastError());

   class=class="str">"cmt">/* Sample output:
      Market Book by GBPUSD [class="num">00]:
        Type:         Sell
        Price:        class="num">1.28627
        Volume:        class="num">100
        Volume Real:  class="num">100.00
      Market Book by GBPUSD [class="num">01]:
        Type:         Sell
        Price:        class="num">1.28620
        Volume:        class="num">50
        Volume Real:  class="num">50.00
      Market Book by GBPUSD [class="num">02]:
        Type:         Sell
        Price:        class="num">1.28618
        Volume:        class="num">20
        Volume Real:  class="num">20.00
      Market Book by GBPUSD [class="num">03]:
        Type:         Sell
        Price:        class="num">1.28615
        Volume:        class="num">10
        Volume Real:  class="num">10.00
      Market Book by GBPUSD [class="num">04]:
        Type:         Buy
        Price:        class="num">1.28610
        Volume:        class="num">10
        Volume Real:  class="num">10.00
      Market Book by GBPUSD [class="num">05]:
        Type:         Buy
        Price:        class="num">1.28606
        Volume:        class="num">20
        Volume Real:  class="num">20.00
      Market Book by GBPUSD [class="num">06]:
        Type:         Buy
        Price:        class="num">1.28605
        Volume:        class="num">50
        Volume Real:  class="num">50.00
      Market Book by GBPUSD [class="num">07]:
        Type:         Buy
        Price:        class="num">1.28599
        Volume:        class="num">100
        Volume Real:  class="num">100.00
   */
  }

空小节无内容可提取

本小节原文未提供任何技术正文或代码,仅见一个孤立的闭合标签 [/CODE],无实质信息可供改写与验证。若需补全,应回到源文档确认该节是否漏贴了 MQL5 代码块及对应说明。

◍ 记住这一条就够了

前几节把 MqlDateTime、MqlTick、MqlRates、MqlBookInfo 四个结构的字段打印函数都拆清楚了,每个函数独立返回「标题-数据」格式字符串,可直接 Print 也可喂给别的模块。 评论区里 fxsaber 提了个方向:把任意结构源码丢进 GetPrintSource,直接吐出该结构的打印函数源码。上面那段代码就是最小验证例,传入 "struct STRUCT { int i; }" 返回的就是现成的 ToString 函数文本。 开 MT5 新建脚本粘进去跑,终端会原样打印出生成的函数代码;你想接自己定义的结构,改掉传入字符串即可。外汇贵金属行情高波动,这类工具只管结构可视化,不构成任何交易建议。

MQL5 / C++
class="type">class="kw">string GetPrintSource( class="kw">const class="type">class="kw">string Source );
class="type">class="kw">string GetPrintSource( class="kw">const class="type">class="kw">string Source )
{
  class="kw">return("class="type">class="kw">string ToString( class="kw">const STRUCT &Data ) { class="kw">return("i = " + (class="type">class="kw">string)Data.i); }");
}
class="type">void OnStart()
{
  Print(GetPrintSource("class="kw">struct STRUCT { class="type">int i; };"));
}
让小布替你跑这套
这些结构字段的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时 Tick 与 BookInfo 的解析视图,不用自己写打印函数也能核对盘口异常。

常见问题

MqlTick 的 bid/ask 是聚合后的最优买卖报价,MqlBookInfo 则展开市场深度中每一档订单的价格与量,前者看成交面,后者看流动性分布。
点差字段依赖经纪商推送的实时价差数据,部分黄金或交叉盘在休市或流动性枯竭时可能不更新,打印前应先判断 Time 是否有效。
可以,小布盯盘的品种页将 Tick、Rates 与 BookInfo 的关键字段做了可视化,省去在终端手敲 Print 的环节,你专注决策即可。
MQL5 中 day_of_week 以周日为 0,周六为 6,打印周历数据时要注意和本地习惯的周一始排法错开。
用 book_type 字段判断 ORDER_TYPE_BUY 或 ORDER_TYPE_SELL,再配合 price 与 volume 逐档输出,否则深度数据会混成一列无法分辨方向。