处理时间(第一部分):基础·进阶篇
「用宏把 MT5 时间拆成交易维度」
在 MT5 里做时段统计或会话过滤,最麻烦的是 datetime 是秒级整数,直接算星期几、日内分钟数容易写错。下面这组预处理宏把常用切片一次性定义好,复制进 EA 或脚本就能直接用。 #define DoWi(t) ((int)(((t-259200)%604800)/86400)) // 周日为0,周一为1,推算依据是 MT5 epoch 起点 1970-01-01 是周四,减 259200 秒(3天)把基准挪到周日。 #define SoD(t) ((int)((t)%86400)) // 取日内已过秒数,范围 0–86399。 #define MoD(t) ((int)(((t)%86400)/60)) // 日内分钟数,00:00 为 0,23:59 为 1439。 #define HoD(t) ((int)(((t)%86400)/3600)) // 日内小时,例如 2018.02.03 17:55 返回 17。 #define BoD(t) ((t)-((t)%86400)) // 当日 00:00 的 datetime,用来对齐 K 线起点。 拿 2021-08-17 15:34 验证:SoD 得到 56040 秒,除以 86400 得 64.86%,即这一天已经走过约三分之二。剩下 86400-56040 = 30360 秒(约 8.43 小时)到午夜。 Print("D'20210817 15:34': ", DoubleToString((double)SoD(D'20210817 15:34')/864.0, 2),"% ",
- - SoD(D'20210817 15:34')," sec left"
); 星期与周起点用 BoW 对齐到周日 00:00:#define BoW(t) ((t)-((t-172800)%604800 - 86400))。若按美国习惯算周数,WoY 函数用 TimeToStruct 取 day_of_year 再偏移,返回可能和券商日历差一周,回测前先打印核对。外汇与贵金属杠杆高,时段切片只解决时间维度,不预示任何方向。
class="macro">#define DoWi(t) ((class="type">int)(((t-class="num">259200)%class="num">604800)/class="num">86400)) class=class="str">"cmt">// (class="type">int)Day of Week Su=class="num">0, Mo=class="num">1,... class="macro">#define DoWs(t) (_WkDy[DoWi(t)]) class=class="str">"cmt">// Day of Week as: Su., Mo., Tu., .... class="macro">#define SoD(t) ((class="type">int)((t)%class="num">86400)) class=class="str">"cmt">// Seconds of Day class="macro">#define SoW(t) ((class="type">int)((t-class="num">259200)%class="num">604800)) class=class="str">"cmt">// Seconds of Week (class="type">class="kw">double)SoD(D&class="macro">#x27;class="num">20210817 class="num">15:class="num">34&class="macro">#x27;) / class="num">864. = class="num">64.86% class="num">86400 - SoD(D&class="macro">#x27;class="num">20210817 class="num">15:class="num">34&class="macro">#x27;) = Print("D&class="macro">#x27;class="num">20210817 class="num">15:class="num">34&class="macro">#x27;: ", DoubleToString((class="type">class="kw">double)SoD(D&class="macro">#x27;class="num">20210817 class="num">15:class="num">34&class="macro">#x27;)/class="num">864.0, class="num">2),"% ", class="num">86400 - SoD(D&class="macro">#x27;class="num">20210817 class="num">15:class="num">34&class="macro">#x27;)," sec left" ); class="macro">#define MoH(t) (class="type">int(((t)%class="num">3600)/class="num">60)) class=class="str">"cmt">// Minute of Hour class="macro">#define MoD(t) ((class="type">int)(((t)%class="num">86400)/class="num">60)) class=class="str">"cmt">// Minute of Day class="num">00:class="num">00=(class="type">int)class="num">0 .. class="num">23:class="num">59=class="num">1439 class="macro">#define ToD(t) ((t)%class="num">86400) class=class="str">"cmt">// Time of Day in Sec(class="type">class="kw">datetime) class="num">86400=class="num">24*class="num">60*class="num">60 class="macro">#define HoW(t) (DoW(t)*class="num">24+HoD(t)) class=class="str">"cmt">// Hour of Week class="num">0..class="num">7*class="num">24 = class="num">0..class="num">168 class="num">0..class="num">5*class="num">24 = class="num">0..class="num">120 class="macro">#define HoD(t) ((class="type">int)(((t)%class="num">86400)/class="num">3600)) class=class="str">"cmt">//Hour of Day: class="num">2018.02.class="num">03 class="num">17:class="num">55 => class="num">17 class="macro">#define rndHoD(t) ((class="type">int)((((t)%class="num">86400)+class="num">1800)/class="num">3600))%class="num">24 class=class="str">"cmt">// rounded Hour of Day class="num">17:class="num">55 => class="num">18 class="macro">#define rndHoT(t) ((t+class="num">1800)-((t+class="num">1800)%class="num">3600)) class=class="str">"cmt">// rounded Hour of Time: class="num">2018.02.class="num">03 class="num">17:class="num">55:class="num">56 => (class="type">class="kw">datetime) class="num">2018.02.class="num">03 class="num">18:class="num">00:class="num">00 class="macro">#define BoD(t) ((t)-((t)%class="num">86400)) class=class="str">"cmt">// Begin of day class="num">17.5 class="num">12:class="num">54 => class="num">17.5. class="num">00:class="num">00:class="num">00 class="macro">#define BoW(t) ((t)-((t-class="num">172800)%class="num">604800 - class="num">86400)) class=class="str">"cmt">// Begin of Week, Su, class="num">00:class="num">00 class="type">MqlDateTime tT; class=class="str">"cmt">// hidden auxiliary variable: the T is a Greek characker, so virtually no danger class="type">int DoY(const class="type">class="kw">datetime t) {TimeToStruct(t,tT);class="kw">return(tT.day_of_year); } class="type">int MoY(const class="type">class="kw">datetime t) {TimeToStruct(t,tT);class="kw">return(tT.mon); } class="type">int YoY(const class="type">class="kw">datetime t) {TimeToStruct(t,tT);class="kw">return(tT.year); } class="type">int WoY(class="type">class="kw">datetime t) class=class="str">"cmt">//Su=newWeek Week of Year = nWeek(t)-nWeeks(class="num">1.1.) CalOneWeek:class="num">604800, Su.class="num">22:class="num">00-Su.class="num">22:class="num">00 = class="num">7*class="num">24*class="num">60*class="num">60 = class="num">604800 { class="kw">return(class="type">int((t-class="num">259200) / class="num">604800) - class="type">int((t-class="num">172800 - DoY(t)*class="num">86400) / class="num">604800) + class="num">1); class=class="str">"cmt">// calculation acc. to USA }
MT5时间函数与经纪商时差的实际坑
在 MT5 里写跟时间相关的逻辑,第一道坎就是 MQL5 自带的那几个时间函数。TimeCurrent() 取的是经纪商报价时间,只管柱线排序和交易时序;TimeTradeServer() 返回交易服务器算出来的时间,但策略测试器里它永远等于 TimeCurrent(),所以想拿它做实况/回测对齐基本没戏。TimeGMT()、TimeLocal() 在测试器里也都被模拟成同一时刻,导致 TimeGMTOffset() 恒为 0,TimeDaylightSaves() 也返回 0——这些只在实盘环境才有意义。 实盘打印能看出真实偏移:2021.08.26 09:25 本地时,MQ 演示账户 TimeLocal 是 09:25、TimeGMT 是 07:25、TimeCurrent 和 TimeTradeServer 都是 10:25,TimeDaylightSavings 为 -3600 秒,TimeGMTOffset 为 -7200 秒。而同一账户在策略测试器跑 2021.06.18 的历史,所有时间函数通通指向 23:54,偏移全清零。 经纪商嘴里的时区也别全信。Alpari 曾说周五服务器时间 23:55 结束、周一 00:05 开(GMT+2/+3 DST),但实际 2021.03.26 欧盟切夏时,最后一根 M1 柱停在 22:54 而非 23:55,差了整整一小时。从 MQ 演示账户 EURUSD M1 的周末边界数据看:2020.10.30 周五最后柱 22:54、11.02 周一第一柱 00:02;2021.03.26 周五 22:54、03.29 周一 00:07——美欧夏冬令切换不同步,会在周五 17:00 纽约闭市到周日 17:00 纽约开市之间,留下 1 小时量价断层。 外汇和贵金属是高杠杆品种,时区错算会直接让跨周末的止损、挂单失效。开 MT5 按 F4 把上面那组 Time* 函数打进 Expert Advisor,分别连实盘和策略测试器跑一周,对比打印值就能确认你经纪商的偏移是不是跟文档说的一样。
◍ 测试器里怎么对齐纽约收盘与周日开市
策略测试器里你手里只有 TimeCurrent(),靠它反推 GMT/UTC 就能算出任意时区的时刻。纽约外汇市场按惯例周五 17:00 闭市、周日 17:00 开市,正常一周运转是 5×24=120 小时连续报价加 48 小时休市。 但实盘数据常出岔子:经纪商演示账户往往周日 24:00(即周一 00:00)才给第一笔报价,而 EET 时区下纽约市场实际周日开市是 23:00。若两个周五之间少了几个小时,要假设缺失发生在周日首报之前,而不是周五最后报价之后——因为周五是算每周收盘价的基准,比周日开门第一小时关键得多。 有个容易踩的坑:如果周五最后一笔价停在 23:00 前,下一笔却是 00:00,那你以为的「周日 23:00-24:00」其实是周五那小时的重演,周日第一小时根本没记录。保守做法是在周末前平掉所有持仓,这样 120 小时后市场再次闭市的时间点一眼可辨。 做历史回测时还得钉死三条:报价时间戳不论写什么都是最新价,不会倒退一小时;经纪商时制政策和历史末段一致;假期只影响美盘开闭,不改变 17:00 这个锚点。外汇和贵金属杠杆高,时区错配会直接扭曲止损触发,跑 MT5 前先拿 2023 年某周 tick 数据核一遍这个缺口。
「别急着下结论」
这套时间处理宏目前只解决了 GMT 换算的静态定义,作者把完整 mqh 挂在 DealingWithTimePart1.mqh(7.18 KB)里,下一篇才会补上动态条件和扩展函数。 有读者在回测时发现,测试器里的时间相关逻辑不会主动提示偏差,而原表格第三行把纽约夏令时标成了正常时间,这类细节直接影响你跨时区策略的成交判定。 作者后来因 MT5 矩阵与向量接口变动,把函数和宏重发到了新地址,并附带两个可跑的校验程序;外汇与贵金属杠杆高、时区错配可能放大滑点,验证前先认准自己经纪商服务器所在地。 真要落地,先开 MT5 把旧 mqh 和新版都编译一遍,用那两个小程序对照本地与 GMT 输出,再决定信哪一套。