处理时间(第二部分):函数(基础篇)
「用 MQL5 函数把时间戳拆开看」
MQL5 里时间不是给人读的字符串,而是从 1970-01-01 起算的秒数(datetime 类型)。想按本地时区把订单、K线的发生时刻拆成年月日时分秒,得靠 TimeToStruct() 把整数时间戳填进 MqlDateTime 结构。 下面这段把当前服务器时间转成结构并打印,跑在 MT5 脚本里立刻能看到输出。外汇与贵金属市场受杠杆和跳空影响,时间对齐错了,回测和实盘信号可能完全错位,属高风险操作前提。 真正落地时,别只打印——把 struct.time 存进数组,就能按「小时」维度统计某货币对在欧洲盘初的波动率倾向,比肉眼翻图表快得多。
class="type">MqlDateTime class="kw">struct; TimeToStruct(TimeCurrent(), class="kw">struct); Print(class="kw">struct.year, "-", class="kw">struct.mon, "-", class="kw">struct.day, " ", class="kw">struct.hour, ":", class="kw">struct.min, ":", class="kw">struct.sec);
◍ 用全局变量抓住经纪商时移
手工核对经纪商在夏令时切换周里的报价时间差极其繁琐,不如让 PC 自己算。核心思路是在 DealingWithTime.mqh 的宏替换之后、函数声明之前,把时区状态变量直接声明为全局变量,供后续函数反复读写。 DST_USD、DST_EUR、DST_AUD、DST_RUS 这四个整型全局变量存放美国、欧盟、澳大利亚、俄罗斯的实际时移。冬季采用正常时间时它们都为 0,即无时移;进入夏令时后变量持有下一次时间变更的偏移量,程序靠它判断何时重算,避免无谓占用 CPU。 俄罗斯情况特殊,nxtSwitch_RUB 被硬编码为 D'2014.10.26 02:00',说明其切换规则与其他地区不同,后续要单独处理。 OffsetBroker 是全局结构体实例,涵盖外汇市场三个持续开放时段的经纪商时移:USwinEUwin(欧美均冬令)、USsumEUsum(欧美均夏令)、USsumEUwin(美夏令欧冬令),并带 actOffset 实际偏移与 secFxWiWi 等时段秒数,set 标记是否已完成赋值。 别把正态当圣经 这些全局变量是整个时差计算模块的支点,改 DST 初值或 OffsetBroker 字段前,先在 MT5 的全局变量窗口看一眼当前 broker 偏移,避免凭空假设冬令为零。
class=class="str">"cmt">//--- global variables for time switches class="type">int DST_USD=class="num">0, class=class="str">"cmt">// act time shift USA DST_EUR=class="num">0, class=class="str">"cmt">// act time shift EU DST_AUD=class="num">0, class=class="str">"cmt">// act time shift Australia DST_RUS=class="num">0; class=class="str">"cmt">// D&class="macro">#x27;class="num">2014.10.class="num">26 class="num">02:class="num">00&class="macro">#x27;, -class="num">10800, class="type">class="kw">datetime nxtSwitch_USD, class=class="str">"cmt">// date of next class="kw">switch nxtSwitch_EUR, class=class="str">"cmt">// date of next class="kw">switch nxtSwitch_AUD, class=class="str">"cmt">// date of next class="kw">switch nxtSwitch_RUB = D&class="macro">#x27;class="num">2014.10.class="num">26 class="num">02:class="num">00&class="macro">#x27;; class=class="str">"cmt">// Russia s different :( class="kw">struct _OffsetBroker { class="type">int USwinEUwin, class=class="str">"cmt">// US=Winter & EU=Winter USsumEUsum, class=class="str">"cmt">// US=Summer & EU=Summer USsumEUwin, class=class="str">"cmt">// US=Summer & EU=Winter actOffset, class=class="str">"cmt">// actual time offset of the broker secFxWiWi, class=class="str">"cmt">// duration of FX in sec secFxSuSu, class=class="str">"cmt">// duration of FX in sec secFxSuWi, class=class="str">"cmt">// duration of FX in sec actSecFX; class=class="str">"cmt">// actual duration of FX in sec class="type">bool set; class=class="str">"cmt">// are all set? }; _OffsetBroker OffsetBroker;
让 EA 自己算出经纪商时差的函数怎么落地的
在 MT5 里写 EA、指标或脚本时,最麻烦的是不同经纪商对冬令时/夏令时切换的处理不一致。把判定逻辑收进一个 setBokerOffset() 函数,调用方就不需要硬编码时差,而是让程序在运行时自己算出来。 这个函数通常放在包含文件 DealingWithTime.mqh 里,在 Start() 之后、变量清零完毕的位置被调用。它会依次对 EUR、USD、AUD 三个时区调用 nextDST("EUR", D'2020.06.21 14:00') 这类语句,先拿到当前适用的时移,再算出下一次切换时刻。六月处于夏令时,所以下一次切换指向冬季时间。 拿到切换时间后,代码用 CopyTime("EURUSD", PERIOD_H1, nxtSwitch_EUR, 1, arrTme) 取切换前周五最后一根 H1 柱的开盘时间,作为后续计算的锚点。选 EURUSD 是因为它的历史数据最长,纽约 17:00 收盘对应最后一根小时线从 16:00 起算,这个 16:00 就是和经纪商时移比对的基准。 调试模式下(IS_DEBUG_MODE 为真,按 F5 跑脚本)会打印所有细节;正常挂图表只输出关键结果。跑通后日志里可能出现这样一行:US=Winter & EU=Winter = -7200,意思是欧美同处冬令时经纪商偏移为 -7200 秒(即 -2 小时)。散户可直接把这组值填进输入参数,省去每次让 EA 自己重算。 重置 nxtSwitch_USD = nxtSwitch_EUR = nxtSwitch_AUD = 0 这一步不能省,而且宁可多重置几次。因为重算只在‘历史中已过去的周末’做,若遗留较晚的切换时间,可能干扰正确计算。外汇和贵金属市场受时区切换影响大、跳空频繁,用这类函数前建议在策略测试器先验证偏移值。
setBokerOffset(); class="type">class="kw">datetime dateEUR,dateUSD,dateAUD,dateNxt, class=class="str">"cmt">// class="kw">switch date for EU, AU, & US class=class="str">"cmt">// array to copy time class="type">int b; OffsetBroker.USsumEUwin = OffsetBroker.USsumEUsum = OffsetBroker.USwinEUwin = INT_MIN; nxtSwitch_USD = nxtSwitch_EUR = nxtSwitch_AUD = class="num">0; class=class="str">"cmt">// reset variables class=class="str">"cmt">//--- AU, EU & US switches to winter time in class="num">2020 if(IS_DEBUG_MODE) Print("\n2nd half-year class="num">2020 for ",AccountInfoString(ACCOUNT_COMPANY), "DebugMode: ",IS_DEBUG_MODE); nextDST("EUR", D&class="macro">#x27;class="num">2020.06.class="num">21 class="num">14:class="num">00&class="macro">#x27;); class=class="str">"cmt">// EUR: get DST and set next change b = CopyTime("EURUSD",PERIOD_H1,nxtSwitch_EUR,class="num">1,arrTme); class=class="str">"cmt">// get time last 1h bar before class="kw">switch in EU dateEUR = arrTme[class="num">0]; class=class="str">"cmt">// last hour on Friday before the weekend nextDST("USD", D&class="macro">#x27;class="num">2020.06.class="num">21 class="num">14:class="num">00&class="macro">#x27;); class=class="str">"cmt">// USA: get DST and set next change b = CopyTime("EURUSD",PERIOD_H1,nxtSwitch_USD,class="num">1,arrTme); class=class="str">"cmt">// get time last 1h bar before class="kw">switch in USA dateUSD = arrTme[class="num">0]; class=class="str">"cmt">// last hour on Friday before the weekend nextDST("AUD", D&class="macro">#x27;class="num">2020.06.class="num">21 class="num">14:class="num">00&class="macro">#x27;); class=class="str">"cmt">// AUD: get DST and set next change b = CopyTime("EURUSD",PERIOD_H1,nxtSwitch_AUD,class="num">1,arrTme); class=class="str">"cmt">// get time last 1h bar before class="kw">switch in AU dateAUD = arrTme[class="num">0]; class=class="str">"cmt">// last hour on Friday before the weekend
「用周五收盘锚定经纪商时区偏移」
跨夏令时切换做回测或实时信号时,最坑的不是指标算错,而是经纪商服务器时间和本地冬夏令时不一致,导致你以为的「周五收盘」其实差了一两根 H1 棒。上面这段逻辑就是拿 EUR、USD、AUD 三个币种各自的 DST 切换点,反推经纪商在三种组合下的偏移量。 核心做法是先取下一次周末时间点:dateNxt = fmax(nxtSwitch_EUR,nxtSwitch_USD)+WeekInSec,再用 CopyTime 拉取 EURUSD 的 PERIOD_H1 数据,取 arrTme[0] 作为切换前最后一根小时棒。注意这里写死了对 EURUSD 品种取时间,如果你的盯盘品种是 XAUUSD,得把符号名换掉再跑。 chckFriday 被连续调用三次,分别传入 EUR、USD、NXT 标签,目的是把「该周五经纪商偏移」写进 OffsetBroker 结构体。最后用三个 INT_MIN 判断(USsumEUwin / USsumEUsum / USwinEUwin)确认是否都赋值成功,成功才把 OffsetBroker.set 置 true。 跑完若在 MT5 Experts 日志看到 US=Winter & EU=Winter = 某整数,说明你的经纪商冬冬偏移已确认;若打印 Assigning the broker offset went wrong,八成是 DST 函数 nextDST 没拿到当年切换日。外汇与贵金属杠杆品种受时区跳变影响大,偏移算错可能让止损挂在错误时段,实盘前务必用 2021.01.21 这类历史点验证。
dateNxt = fmax(nxtSwitch_EUR,nxtSwitch_USD)+WeekInSec; class=class="str">"cmt">// get the next weekend b = CopyTime("EURUSD",PERIOD_H1,dateNxt,class="num">1,arrTme); class=class="str">"cmt">// get time last 1h bar before the weekend dateNxt = arrTme[class="num">0]; class=class="str">"cmt">// last hour on Friday before the weekend chckFriday(dateEUR,"EUR"); class=class="str">"cmt">// function to determine broker offset for the Friday given chckFriday(dateUSD,"USD"); class=class="str">"cmt">// function to determine broker offset for the Friday given chckFriday(dateNxt,"NXT"); class=class="str">"cmt">// function to determine broker offset for the Friday given if(IS_DEBUG_MODE) Print("\n1st half-year class="num">2021 for ",AccountInfoString(ACCOUNT_COMPANY), "DebugMode: ",IS_DEBUG_MODE); nxtSwitch_USD = nxtSwitch_EUR = nxtSwitch_AUD = class="num">0; nextDST("AUD", D&class="macro">#x27;class="num">2021.01.class="num">21 class="num">14:class="num">00&class="macro">#x27;); class=class="str">"cmt">// AUD: get DST and set next change b = CopyTime("EURUSD",PERIOD_H1,nxtSwitch_AUD,class="num">1,arrTme); class=class="str">"cmt">// get time last 1h bar before class="kw">switch in EU dateAUD = arrTme[class="num">0]; class=class="str">"cmt">// last hour on Friday before the weekend chckFriday(dateUSD,"USD"); class=class="str">"cmt">// function to determine broker offset for the Friday given chckFriday(dateEUR,"EUR"); class=class="str">"cmt">// function to determine broker offset for the Friday given chckFriday(dateNxt,"NXT"); class=class="str">"cmt">// function to determine broker offset for the Friday given nxtSwitch_USD = nxtSwitch_EUR = nxtSwitch_AUD = class="num">0; class=class="str">"cmt">// reset variables for use by a user if(OffsetBroker.USsumEUwin != INT_MIN && OffsetBroker.USsumEUsum != INT_MIN && OffsetBroker.USwinEUwin != INT_MIN ) OffsetBroker.set = true; else OffsetBroker.set = false; if(OffsetBroker.set) Print("\nTime Offset of ",AccountInfoString(ACCOUNT_COMPANY),": ", "\nUS=Winter & EU=Winter(USwinEUwin) = ",OffsetBroker.USwinEUwin, "\nUS=Summer & EU=Summer(USsumEUsum) = ",OffsetBroker.USsumEUsum, "\nUS=Summer & EU=Winter(USsumEUwin) = ",OffsetBroker.USsumEUwin, "\n"); else Print(__FILE__,"[",__LINE__,"] Assigning the broker offset went wrong - somehow."); class="kw">return(OffsetBroker.set);
◍ 摸清经纪商周五时移的判定逻辑
做跨时区策略前,先得搞清楚经纪商服务器时间和纽约、GMT 的关系。chckFriday() 这个函数就是干这个的:它拿周五纽约 16:00(即 tB,经纪商最后一个小时起点)作锚,反推经纪商相对 GMT 的偏移,再按欧美冬夏令组合写进全局结构 OffsetBroker 的对应字段。 欧美切换规则有固定节奏:欧盟每年 10 月最后一个周日、3 月最后一个周日变时;美国是 11 月首个周日、3 月第二个周日。结果就是欧盟比美国早一到两周入冬(美国夏、欧盟冬),或者美国比欧盟早一到两周入夏(同样是 USsumEUwin 这个字段覆盖),反过来「欧盟夏、美国冬」在现实里不会出现,所以代码里根本不用算第四类。 澳大利亚和美国同在 11 月切,春天又跟欧盟同步,没有额外错峰周末;但澳洲圣诞在新夏令,欧盟美国都冬时它会拨快 1 小时。算清特殊周时移后,外汇开盘剩余秒数就能填进 secFxWiWi、secfxsuu、secFxSuWi 和 actSecFX。 打印样例里能看到过渡周的真实痕迹:2020.10.23 欧盟还没切,经纪商钟点 23、hDiff -3;2020.10.30 欧盟已切美国没切,经纪商钟点 22、hDiff -2,本周 FX 结束从正常 143 小时缩到 142 小时。这个 143 是周日 00:00 到周五 23:00 的日历秒数(6*24-1),不是自然周的 120 小时,写 EA 时别弄混。 代码里先重置三个地区的下次切换变量,调 nextDST() 拿 tB 时刻欧美偏移,再用 HoD(tB - SoD(tB) + 16*3600) 把经纪商时间折回纽约 16:00 的钟点——这一步用小时而非秒,纯粹为了方便日志核对。
class="type">int USwinEUwin, class=class="str">"cmt">// US=Winter & EU=Winter USsumEUsum, class=class="str">"cmt">// US=Summer & EU=Summer USsumEUwin, class=class="str">"cmt">// US=Summer & EU=Winter class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| auxiliary function to determine time offset of the broker | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int chckFriday( class="type">class="kw">datetime tB, class=class="str">"cmt">// time Broker: the last hour on Friday class="type">class="kw">string cmt="" class=class="str">"cmt">// text to start the line ) { class="type">int hNY, hGMT, hTC, hDiff; nxtSwitch_AUD = nxtSwitch_USD = nxtSwitch_EUR = class="num">0; class=class="str">"cmt">// reset to be save nextDST("EUR",tB); class=class="str">"cmt">// get the offset for EUR of the time tB given nextDST("USD",tB); class=class="str">"cmt">// get the offset for USA of the time tB given hNY = HoD(tB - SoD(tB) + class="num">16*class="num">3600); class=class="str">"cmt">// get the hour of New York time
把经纪商时差钉死在夏令时切换上
这段逻辑干的事很直接:先算出 GMT 小时与经纪商本地小时的偏差 hDiff,再按美、欧两地夏令时状态把偏移量写进结构体。 hGMT 那行用 tB 减去当日秒数再加上 16*3600 与 NYShift、DST_USD,本质是反推纽约 16 点对应的 GMT 时刻;hTC 只取经纪商时间的小时;两者相减得到 hDiff,单位小时。 三种常态分支覆盖了冬冬、夏夏、美夏欧冬的组合,分别把 hDiff*3600 秒存进 actOffset,并用 SoW(tB) 记录当前 FX 小节剩余秒数。若撞上 DST_USD==0 且 DST_EUR!=0 这种不该出现的状态,直接 Alert 报行号,说明代码作者认为欧美夏令时不会反向错配。 末尾 Print 把纽约→GMT→经纪商→GMT→纽约的闭环打出来,重点标注最后一环纽约时间必须恒为 16 点,否则偏移算错。外汇与贵金属受跨时区流动性切换影响大,时差算错可能让会话时段判断整体偏移,实盘前建议在 MT5 用 Print 输出验证一周内的 actOffset 是否随 DST 平滑跳变。
hGMT = HoD(tB - SoD(tB) + class="num">16*class="num">3600 + NYShift + DST_USD); class=class="str">"cmt">// get the hour of GMT hTC = HoD(tB); class=class="str">"cmt">// get the hour of the time given hDiff = hGMT - HoD(tB); class=class="str">"cmt">// get the difference between GMT and the broker if(DST_USD==class="num">0 && DST_EUR!=class="num">0) class=class="str">"cmt">// this should not occur Alert(__LINE__," ",TOSTR(DST_USD),TOSTR(DST_EUR)," USwin && EUsum"); class=class="str">"cmt">//--- set the broker offset for the various time situations: if(DST_USD+DST_EUR==class="num">0) class=class="str">"cmt">// both in winter(normal) time { OffsetBroker.actOffset = OffsetBroker.USwinEUwin = hDiff*class="num">3600; OffsetBroker.actSecFX = OffsetBroker.secFxWiWi = SoW(tB); } else if(DST_USD == DST_EUR) class=class="str">"cmt">// else both in summer time { OffsetBroker.actOffset = OffsetBroker.USsumEUsum = hDiff*class="num">3600; OffsetBroker.actSecFX = OffsetBroker.secFxSuSu = SoW(tB); } else if(DST_USD!=class="num">0 && DST_EUR==class="num">0) class=class="str">"cmt">// US:summer EU:winter { OffsetBroker.actOffset = OffsetBroker.USsumEUwin = hDiff*class="num">3600; OffsetBroker.actSecFX = OffsetBroker.secFxSuWi = SoW(tB); } class=class="str">"cmt">//--- calc the ring of times NY->GMT->Broker->GMT->NY <= the last NY must always be class="num">16!! Print(cmt,": ",DoWs(tB),TimeToString(tB),": ",TOSTR(hNY),TOSTR(hGMT),TOSTR(hTC),TOSTR(hDiff), " BrokerTime => GMT: ",TimeToString(tB+OffsetBroker.actOffset), " => tNY: ",TimeToString((tB + OffsetBroker.actOffset)-(NYShift + DST_USD)), " End-FX after: ",OffsetBroker.actSecFX/class="num">3600,"h" ); class="kw">return(OffsetBroker.actOffset);