MQL5中的结构及其数据打印方法·进阶篇
(2/3)· 从MqlDateTime到MqlBookInfo,逐个拆字段与打印函数,告别瞎猜结构体内容
用 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 拿具体错误号,别让脚本静默崩掉。外汇和贵金属行情受数据面影响大,时间轴偶尔会因休市断开,跑之前先确认品种交易时段。
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)) 的输出是否符合预期。
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:" 沿用同一套缩进逻辑。外汇和贵金属行情受杠杆与跳空影响大,这类时间拆解只解决显示问题,不预示任何波动方向。
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 标记样本日有助于回测时区分周一跳空与周三密集波段,但这类时间标签只描述状态,不预示方向,实盘高杠杆品种仍可能瞬时滑点。
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 跑一遍验证你的时区与经纪商服务器时间是否一致。
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 影响大,时间日志错了可能导致止损挂在错误时段,这类工具函数建议每次改完时区参数都实跑一遍确认。
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 */ }