MQL5中的结构及其数据打印方法·进阶篇
🧱

MQL5中的结构及其数据打印方法·进阶篇

(2/3)· 从MqlDateTime到MqlBookInfo,逐个拆字段与打印函数,告别瞎猜结构体内容

偏理论进阶 第 2/3 篇
很多人拿到MqlTick或MqlBookInfo只会整体Print,结果终端刷出一堆看不懂的数字。其实每个预定义结构都有专属的字段提取与格式化打印方式,理清层级就能直接读出订单簿价格和分时毫秒时间。

用 CopyTime 把 K 线时间拉进数组

在 MT5 里想做基于时间的策略判断,第一步是把历史 K 线的时间戳取出来。MQL5 的 CopyTime 能按品种、周期、起始位置和数量,把时间直接塞进 datetime 数组,比自己解析结构体省事得多。 下面这段脚本只干一件事:取当前品种当前周期最近 10 根 BAR 的开盘时间,打印成可读表格。注意示例输出里 GBPUSD H1 在 2023-07-17 从 07:00 到 16:00 连续 10 根,day_of_week 全是 1(周一),day_of_year 为 197,说明那段时间没有跳空缺失。 调用前先用 ResetLastError 清错误码,CopyTime 返回复制根数,小于 0 就说明失败,这时候用 GetLastError 拿具体错误号,别让脚本静默崩掉。外汇和贵金属行情受数据面影响大,时间轴偶尔会因休市断开,跑之前先确认品种交易时段。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//--- Declare a time array
   class="type">class="kw">datetime array[];
class=class="str">"cmt">//--- Copy the time of the last class="num">10 bars to the array
   ResetLastError();
   if(CopyTime(Symbol(),Period(),class="num">0,class="num">10,array)<class="num">0)
     {
       PrintFormat("CopyTime() failed. Error %s",(class="type">class="kw">string)GetLastError());
       class="kw">return;
     }
class=class="str">"cmt">//--- Display the header and the time data array of the last class="num">10 bars using the standard ArrayPrint()
   PrintFormat("Time data of the last class="num">10 bars %s %s(ArrayPrint):",Symbol(),StringSubstr(EnumToString(Period()),class="num">7));
   MqlDateTimePrint(array);

◍ 把时间结构翻译成可读标签

MT5 里 MqlDateTime 结构体抓出来的字段都是数字,直接打印出来可读性很差。上面那段注释里的样本就很典型:2023 年 7 月 17 日 14/15/16 点,day_of_week 全是 1,对应周一,但光看数字你根本分不清周几。 DayWeek() 这个函数用 EnumToString 把 day_of_week 转成枚举名,再 Lower() 变小写、把首字母加 0x20 抬成大写,最后吐出 'Wednesday' 这种格式。注意 0x20 是 ASCII 里大小写差的偏移量,硬改首字符比调 TextTransform 更省开销。 Month() 则是老实的 switch 映射,1 到 12 对应 January 到 December,默认落 'Undefined'。样本里 7 月直接返回 'July',在日志里拼时间串时比数字直观太多。 外汇和贵金属行情受时段驱动明显,用这两个函数给回测日志打'周几+月份'标签,能更快肉眼筛出季节性异动。打开 MT5 把代码贴进脚本跑一遍,看 Print(DayWeek(dt)+' '+Month(dt)) 的输出是否符合预期。

MQL5 / C++
class="type">class="kw">string DayWeek(class="type">MqlDateTime &date_time)
  {
class=class="str">"cmt">//--- Define a week day name
   class="type">class="kw">string dw=EnumToString((ENUM_DAY_OF_WEEK)date_time.day_of_week);
class=class="str">"cmt">//--- Convert all obtained symbols to lower case and replace the first letter from small to capital
   if(dw.Lower())
      dw.SetChar(class="num">0,class="type">class="kw">ushort(dw.GetChar(class="num">0)-0x20));
class=class="str">"cmt">//--- Return a resulting class="type">class="kw">string
   class="kw">return dw;
  }

class="type">class="kw">string Month(class="type">MqlDateTime &date_time)
  {
class=class="str">"cmt">//--- Define a month name
   class="kw">switch(date_time.mon)
     {
      case  class="num">1  :  class="kw">return "January";
      case  class="num">2  :  class="kw">return "February";
      case  class="num">3  :  class="kw">return "March";
      case  class="num">4  :  class="kw">return "April";
      case  class="num">5  :  class="kw">return "May";
      case  class="num">6  :  class="kw">return "June";
      case  class="num">7  :  class="kw">return "July";
      case  class="num">8  :  class="kw">return "August";
      case  class="num">9  :  class="kw">return "September";
      case class="num">10  :  class="kw">return "October";
      case class="num">11  :  class="kw">return "November";
      case class="num">12  :  class="kw">return "December";
      class="kw">default  :  class="kw">return "Undefined";
     }
  }

「把 MqlDateTime 拆成可读字符串」

在 MT5 里做日志诊断或面板输出时,MqlDateTime 结构体的原生字段可读性很差。下面这组函数把年、月、日、时分别格式化成带缩进和表头的字符串,直接贴进 EA 的调试模块就能用。 以 MqlDateTimeYear 为例:header 写死为 "Year:",当 header_width 传 0 时,字段宽度自动取表头长度+1,避免对齐错位。StringFormat 里 %*s 吃缩进空格,%-*s 左对齐表头,%-lu 输出无符号长整型年份。示例跑出来就是「Year: 2023」这种形态。 月份函数多一步:先用 Month(date_time) 取英文月名,再拼进 %02lu (%s),所以你会看到「Month: 07 (July)」。日函数同理用 %02lu 补零,时函数表头定为 "Hour:" 沿用同一套缩进逻辑。外汇和贵金属行情受杠杆与跳空影响大,这类时间拆解只解决显示问题,不预示任何波动方向。

MQL5 / C++
class="type">class="kw">string MqlDateTimeYear(class="type">MqlDateTime &date_time,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="Year:";
  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%-lu",indent,"",w,header,date_time.year);
  class=class="str">"cmt">/* Sample output:
     Year: class="num">2023
  */
  }
class="type">class="kw">string MqlDateTimeMonth(class="type">MqlDateTime &date_time,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="Month:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
class=class="str">"cmt">//--- Get a month name
  class="type">class="kw">string mn=Month(date_time);
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%02lu (%s)",indent,"",w,header,date_time.mon,mn);
  class=class="str">"cmt">/* Sample output:
     Month: class="num">07 (July)
  */
  }
class="type">class="kw">string MqlDateTimeDay(class="type">MqlDateTime &date_time,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="Day:";
  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%02lu",indent,"",w,header,date_time.day);
  class=class="str">"cmt">/* Sample output:
     Day: class="num">19
  */
  }
class="type">class="kw">string MqlDateTimeHour(class="type">MqlDateTime &date_time,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="Hour:";

把时间结构拆成对齐字符串

做日志或面板输出时,MqlDateTime 里的时、分、秒、星期若直接拼字符串,位数不对齐会很难肉眼扫。下面这组函数统一用 StringFormat 的 %02lu 把小时、分钟、秒格式化成两位,例如 Hour: 08、Minutes: 41、Seconds: 23,零填充避免 8 点和 18 点错位。 header_width 传 0 时,字段宽自动取 header 文本长度 +1;传具体值则强制列宽,配合 indent 做缩进,多行表头就能竖着对齐。星期函数多一个 descr 开关,输出形如 Day of week: Wednesday (3),同时给名称和数字序号。 外汇与贵金属行情在周中时段流动性差异明显,用 day_of_week 标记样本日有助于回测时区分周一跳空与周三密集波段,但这类时间标签只描述状态,不预示方向,实盘高杠杆品种仍可能瞬时滑点。

MQL5 / C++
class="type">class="kw">string MqlDateTimeMin(class="type">MqlDateTime &date_time,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="Minutes:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
  class="kw">return StringFormat("%*s%-*s%02lu",indent,"",w,header,date_time.min);
  class=class="str">"cmt">/* Sample output:
      Minutes: class="num">41
  */
  }
class="type">class="kw">string MqlDateTimeSec(class="type">MqlDateTime &date_time,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="Seconds:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
  class="kw">return StringFormat("%*s%-*s%02lu",indent,"",w,header,date_time.sec);
  class=class="str">"cmt">/* Sample output:
      Seconds: class="num">23
  */
  }
class="type">class="kw">string MqlDateTimeDayWeek(class="type">MqlDateTime &date_time,class="kw">const class="type">uint header_width=class="num">0,class="kw">const class="type">uint indent=class="num">0,class="type">bool descr=true)
  {
  class="type">class="kw">string header="Day of week:";
  class="type">uint w=(header_width==class="num">0 ? header.Length()+class="num">1 : header_width);
  class="type">class="kw">string dw=DayWeek(date_time);
  class="kw">return StringFormat("%*s%-*s%-s(%-lu)",indent,"",w,header,dw,date_time.day_of_week);
  class=class="str">"cmt">/* Sample output:
      Day of week: Wednesday(class="num">3)
  */
  }

◍ 把 K 线时间拆成可读字符串

MT5 里 datetime 是整数秒,直接 Print 出来对人眼不友好。下面这段把 MqlDateTime 结构字段拼成「Wed, July 19, 2023, 08:41:23」这种格式,回测或实时日志里一眼能定位哪根 BAR。 MqlDateTimeDayYear 负责输出「Day of year: 199」这类年内第几天,header 宽度可传参,不传就按表头长度+1 自动算。DateTime 函数则组合星期缩写、月份名、日、年、时分秒,格式串里 %02lu 保证个位日/时补零。 OnStart 里用 CopyTime 抓当前品种当前周期最近 10 根 BAR 的时间,TimeToStruct 转结构后逐根打印。实跑样本输出是 GBPUSD H1 从 2023.07.19 02:00 到 11:00 的十根小时线,证明 PERIOD_CURRENT 下数组下标 0 是最早那根而非最新。 外汇与贵金属杠杆高,这类时间解析只解决「看得见」,不预示波动方向,复制去 MT5 跑一遍验证你的时区与经纪商服务器时间是否一致。

MQL5 / C++
class="type">class="kw">string MqlDateTimeDayYear(class="type">MqlDateTime &date_time,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="Day of year:";
   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%-lu",indent,"",w,header,date_time.day_of_year);
   class=class="str">"cmt">/* Sample output:
       Day of year: class="num">199
   */
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the date as a class="type">class="kw">string from the class="type">MqlDateTime structure       |
class=class="str">"cmt">//| in the DW, Month DD, YYYY, HH:MM:SS format                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string DateTime(class="type">MqlDateTime &date_time)
  {
class=class="str">"cmt">//--- Get the month and the first three characters of a week day
   class="type">class="kw">string mn=Month(date_time);
   class="type">class="kw">string dw=StringSubstr(DayWeek(date_time),class="num">0,class="num">3);
class=class="str">"cmt">//--- Return a class="type">class="kw">string in the DW, Month DD, YYYY, HH:MM:SS format
   class="kw">return StringFormat("%s, %s %02lu, %lu, %02lu:%02lu:%02lu",dw,mn,date_time.day,date_time.year,date_time.hour,date_time.min,date_time.sec);
   class=class="str">"cmt">/* Sample output:
       Wed, July class="num">19, class="num">2023, class="num">08:class="num">41:class="num">23
   */
  }
class="type">void OnStart()
  {
   class="type">class="kw">datetime array[];
   class="type">MqlDateTime adt[];
   if(CopyTime(Symbol(),PERIOD_CURRENT,class="num">0,class="num">10,array)==class="num">10)
     {
      class="type">int total=(class="type">int)array.Size();
      if(ArrayResize(adt,total)==total)
        {
         for(class="type">int i=class="num">0;i<total;i++)
           {
            ResetLastError();
            if(!TimeToStruct(array[i],adt[i]))
              Print("TimeToStruct failed. Error: ",GetLastError());
            PrintFormat("%s %s [%02u] %s",Symbol(),StringSubstr(EnumToString(Period()),class="num">7),i,DateTime(adt[i]));
           }
        }
     }
   class=class="str">"cmt">/* Sample output:
       GBPUSD H1 [class="num">00] Wed, July class="num">19, class="num">2023, class="num">02:class="num">00:class="num">00
       GBPUSD H1 [class="num">01] Wed, July class="num">19, class="num">2023, class="num">03:class="num">00:class="num">00
       GBPUSD H1 [class="num">02] Wed, July class="num">19, class="num">2023, class="num">04:class="num">00:class="num">00
       GBPUSD H1 [class="num">03] Wed, July class="num">19, class="num">2023, class="num">05:class="num">00:class="num">00
       GBPUSD H1 [class="num">04] Wed, July class="num">19, class="num">2023, class="num">06:class="num">00:class="num">00
       GBPUSD H1 [class="num">05] Wed, July class="num">19, class="num">2023, class="num">07:class="num">00:class="num">00
       GBPUSD H1 [class="num">06] Wed, July class="num">19, class="num">2023, class="num">08:class="num">00:class="num">00
       GBPUSD H1 [class="num">07] Wed, July class="num">19, class="num">2023, class="num">09:class="num">00:class="num">00
       GBPUSD H1 [class="num">08] Wed, July class="num">19, class="num">2023, class="num">10:class="num">00:class="num">00
       GBPUSD H1 [class="num">09] Wed, July class="num">19, class="num">2023, class="num">11:class="num">00:class="num">00
   */
  }

「把时间结构打印成可读日志」

做 EA 调试时,MqlDateTime 原始结构直接 Print 出来是一堆字段,肉眼对时间容易看错。下面这个函数用 short_entry 开关切换两种输出:true 时压成一行「Wed, July 19, 2023, 08:41:23」,false 时按字段分行并带缩进和对齐宽度。 函数默认 header_width=0、indent=0,调用时传 14 和 2 就能让「Year / Month / Day of week」等表头占 14 字符宽、整体缩进 2 格,日志里竖排对齐,扫一眼就知道当前经纪商时间和本地时区差多少。 OnStart 里先 TimeCurrent(dt) 抓当前时间,再分别用 true / false 调两次。样本输出里 false 模式能打出 Day of year: 199,做季节性波动统计或跨年回测切分时会用到这个字段。 外汇和贵金属行情受跨期 session 影响大,时间日志错了可能导致止损挂在错误时段,这类工具函数建议每次改完时区参数都实跑一遍确认。

MQL5 / C++
class="type">void MqlDateTimePrint(class="type">MqlDateTime &date_time,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=class="str">"cmt">//--- If it is a class="type">class="kw">short entry, log the date and time in the DW, Month DD, YYYY, HH:MM:SS format
   if(short_entry)
      Print(DateTime(date_time));
   class=class="str">"cmt">/* Sample output:
      Wed, July class="num">19, class="num">2023, class="num">08:class="num">41:class="num">23
   */
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 
      class="type">class="kw">string res=StringFormat("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
                              MqlDateTimeYear(date_time,header_width,indent),
                              MqlDateTimeMonth(date_time,header_width,indent),
                              MqlDateTimeDay(date_time,header_width,indent),
                              MqlDateTimeHour(date_time,header_width,indent),
                              MqlDateTimeMin(date_time,header_width,indent),
                              MqlDateTimeSec(date_time,header_width,indent),
                              MqlDateTimeDayWeek(date_time,header_width,indent),
                              MqlDateTimeDayYear(date_time,header_width,indent)
                             );
      class=class="str">"cmt">//--- Display the obtained class="type">class="kw">string in the journal
      Print(res);
     }
   class=class="str">"cmt">/* Sample output:
      Year: class="num">2023
      Month: class="num">07 (July)
      Day: class="num">19
      Hour: class="num">09
      Minutes: class="num">32
      Seconds: class="num">25
      Day of week: Wednesday(class="num">3)
      Day of year: class="num">199
   */
  }
class="type">void OnStart()
  {
   class="type">MqlDateTime dt;
   TimeCurrent(dt);
   MqlDateTimePrint(dt,true);
   MqlDateTimePrint(dt,false,class="num">14,class="num">2);
   class=class="str">"cmt">/* Sample output:
      Wed, July class="num">19, class="num">2023, class="num">09:class="num">33:class="num">56
        Year:        class="num">2023
        Month:       class="num">07 (July)
        Day:         class="num">19
        Hour:        class="num">09
        Minutes:     class="num">33
        Seconds:     class="num">56
        Day of week: Wednesday(class="num">3)
        Day of year: class="num">199
   */
  }
把结构巡检交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到MqlTick与MqlBookInfo的实时解析,不必自己写脚本轮询终端。

常见问题

可通过TimeCurrent配合结构赋值后直接访问st.day_of_year,或用文中辅助函数封装后Print,注意该字段从1月开始计数为1到366。
多数经纪商的外汇报价不提供真实成交量,RealVolume仅在支持交易所数据的品种返回非零值,外汇贵金属属高风险杠杆品种,看TickVolume更现实。
枚举值BOOK_BID与BOOK_ASK分别对应买方与卖方深度,遍历结构体数组时按类型分流即可分别打印价格与交易量。
可以,小布盯盘品种页已解析last_time_msc字段并以本地时区呈现,省去你在MQL5里手动转换时间戳的重复劳动。
flags用于指示如买价更新、卖价更新或最后价变动等事件位,可用位运算逐位判断后映射为文字再打印。