处理时间(第二部分):函数(基础篇)
📘

处理时间(第二部分):函数(基础篇)

第 1/3 篇

「用 MQL5 函数把时间戳拆开看」

MQL5 里时间不是给人读的字符串,而是从 1970-01-01 起算的秒数(datetime 类型)。想按本地时区把订单、K线的发生时刻拆成年月日时分秒,得靠 TimeToStruct() 把整数时间戳填进 MqlDateTime 结构。 下面这段把当前服务器时间转成结构并打印,跑在 MT5 脚本里立刻能看到输出。外汇与贵金属市场受杠杆和跳空影响,时间对齐错了,回测和实盘信号可能完全错位,属高风险操作前提。 真正落地时,别只打印——把 struct.time 存进数组,就能按「小时」维度统计某货币对在欧洲盘初的波动率倾向,比肉眼翻图表快得多。

MQL5 / C++
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 偏移,避免凭空假设冬令为零。

MQL5 / C++
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 这一步不能省,而且宁可多重置几次。因为重算只在‘历史中已过去的周末’做,若遗留较晚的切换时间,可能干扰正确计算。外汇和贵金属市场受时区切换影响大、跳空频繁,用这类函数前建议在策略测试器先验证偏移值。

MQL5 / C++
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 这类历史点验证。

MQL5 / C++
  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 的钟点——这一步用小时而非秒,纯粹为了方便日志核对。

MQL5 / C++
  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 平滑跳变。

MQL5 / C++
  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);

常见问题

用拆分函数把时间戳分别取出年、月、日、时、分、秒字段,逐个打印就能直观看到,不用自己算偏移。
写个函数对比同一时刻经纪商时间与本地时间的结构体字段,相减得出偏移小时数,启动时跑一次存下来即可。
可以,小布能直接读取品种页的时区诊断结果,自动标出经纪商时差与夏令时状态,不用你手写函数验证。
周五收盘是全球统一事件,用本地收盘时间对比经纪商收盘时间能排除日常波动,稳定反推出时区偏移。
在切换日前后对比历史收盘锚点偏移量,若偏移突变且非整点规律,就是经纪商做了时移调整。