MQL5 编程基础:时间·综合运用
⏱️

MQL5 编程基础:时间·综合运用

(3/3)· 柱缺失、周日柱与跨时区如何让时间判断翻车,这篇一次讲透

进阶 第 3/3 篇
把服务器时间直接当本地时间用,EA 在周末和夏令时切换夜会错算整段逻辑。短周期图表缺柱时还按固定间隔取时间,信号触发可能跳空漏单。外汇贵金属杠杆高,这类时间 bug 会让策略在极端行情里失控。

「把时间变成可读字符串的几种写法」

MQL5 里把时间转成字符串,核心是两个入口:TimeToString() 专做日期时间格式化,StringFormat() 则在拼复杂文本时顺手做类型转换。TimeToString() 能直接吃 datetime、long、ulong,以及部分 integer 变量——但 integer 本身存不了时间,只是类型兼容。 用 StringFormat() 时最容易踩坑的是类型不匹配。datetime 直接当 %s 会编译报警,必须强转 (string);long 型时间更要先转回 (datetime) 再转 (string),否则出来的不是日期而是个整数秒数。 下面这段代码在 MT5 里跑,会连续弹 8 次 Alert。前 5 次展示 TimeToString 不同掩码:不带参数出「年月日 时分」,TIME_DATE 只出日期,TIME_MINUTES 只出「时:分」,TIME_SECONDS 只出「时:分:秒」,TIME_DATE|TIME_SECONDS 则是日期加秒。后 3 次分别验证 datetime 强转、long 先转 datetime 再转 string、以及 TimeToString 嵌进 StringFormat 的写法。 外汇与贵金属行情受杠杆和跳空影响,高风险,这段代码仅用于熟悉时间格式化逻辑,不涉及任何交易信号。

MQL5 / C++
class="type">class="kw">datetime tm=TimeCurrent();
class="type">class="kw">string str1="Date and time with minutes: "+TimeToString(tm);
class="type">class="kw">string str2="Date only: "+TimeToString(tm,TIME_DATE);
class="type">class="kw">string str3="Time with minutes only: "+TimeToString(tm,TIME_MINUTES);
class="type">class="kw">string str4="Time with seconds only: "+TimeToString(tm,TIME_SECONDS);
class="type">class="kw">string str5="Date and time with seconds: "+TimeToString(tm,TIME_DATE|TIME_SECONDS);
class=class="str">"cmt">//--- output results
Alert(str1);
Alert(str2);
Alert(str3);
Alert(str4);
Alert(str5);
class="type">class="kw">datetime tm=TimeCurrent();
class=class="str">"cmt">//--- generating a class="type">class="kw">string
class="type">class="kw">string str=StringFormat("Formatted: %s, in seconds: %I64i",(class="type">class="kw">string)tm,tm);
class=class="str">"cmt">//--- output result
Alert(str);
class="type">long tm=TimeCurrent();
class=class="str">"cmt">//--- generating a class="type">class="kw">string
class="type">class="kw">string str=StringFormat("Formatted: %s, in seconds: %I64i",(class="type">class="kw">string)(class="type">class="kw">datetime)tm,tm);
class=class="str">"cmt">//--- output result
Alert(str);
class="type">class="kw">datetime tm=TimeCurrent();
class=class="str">"cmt">//--- generating a class="type">class="kw">string
class="type">class="kw">string str=StringFormat("Date: %s",TimeToString(tm,TIME_DATE));
class=class="str">"cmt">//--- output result
Alert(str);

用秒数思维摆弄 MT5 时间

在 MT5 里,时间本质是一个从 1970-01-01 起累计的秒数整数(datetime 类型)。用 StringToTime() 把字符串转成这个数字后,时间的加减就只是普通算术。比如 "2012.12.05 22:31:57" 转换后得到 1354746717,这就是该时刻距时间戳起点的秒数。 知道了 1 小时 = 3600 秒、1 天 = 86400 秒,就能直接对当前时间做偏移。TimeCurrent() 取到的当前时间减去或加上 3600,就分别是上一小时和下一小时的时间点,写条件判断或挂单有效期时非常顺手。 StringToTime() 对入参并不苛刻。只传 "2012.12.05" 会返回那天 00:00:00 的秒数;只传 "22:31:57" 则套用今天日期返回对应时刻。偶尔只传小时也行,但实战里极少用,自己开 MT5 试两行就知道边界在哪。 外汇和贵金属行情受时段影响明显,用这种秒级偏移算会话时段、重叠窗口时,务必先认清杠杆和高波动下的滑点风险,数值只是参考。

MQL5 / C++
class="type">class="kw">datetime tm=StringToTime("class="num">2012.12.class="num">05 class="num">22:class="num">31:class="num">57");
class=class="str">"cmt">//--- output result
Alert((class="type">class="kw">string)(class="type">long)tm);
class="type">class="kw">datetime tm=TimeCurrent();
class="type">class="kw">datetime ltm=tm-class="num">3600;
class="type">class="kw">datetime ftm=tm+class="num">3600;
class=class="str">"cmt">//--- output result
Alert("Current: "+(class="type">class="kw">string)tm+", an hour ago: "+(class="type">class="kw">string)ltm+", in an hour: "+(class="type">class="kw">string)ftm);
class="type">class="kw">datetime tm=StringToTime("class="num">2012.12.class="num">05");
class=class="str">"cmt">//--- output result
Alert(tm);
class="type">class="kw">datetime tm=StringToTime("class="num">22:class="num">31:class="num">57");
class=class="str">"cmt">//--- output result
Alert((class="type">class="kw">string)tm);

◍ 拆解时间分量的两种取法

在 MT5 里拿年月日时分秒,核心就靠 MqlDateTime 结构加 TimeToStruct(),或者让 TimeCurrent()/TimeLocal() 直接把结构当引用参数填进去。两种写法结果一致,后者少写一行调用,节奏更紧凑。 MqlDateTime 里除了常规的年、月、日、时、分、秒,还带 day_of_week 和 day_of_year 两个附加字段。前者 0 代表周日、1 代表周一,依此类推;后者从 0 开始算当年第几天。月份和日期走日常计数(月从 1 起、日也从 1 起),别在星期和年积日上踩错偏移。 下面这段代码给出两种调用对照,直接贴进 EA 或脚本用 Alert 弹出分量值,开 MT5 跑一下就能看到服务器时间被拆开的样子。外汇与贵金属行情受时段切换影响明显,这类时间解析误差可能导致策略在跨日、跨周节点上出现错单,属高风险环节,验证时建议同时比对 TimeLocal() 的本地时区输出。

MQL5 / C++
class="type">class="kw">datetime    tm=TimeCurrent();
class="type">MqlDateTime stm;
TimeToStruct(tm,stm);
class=class="str">"cmt">//--- output date components
Alert("Year: "        +(class="type">class="kw">string)stm.year);
Alert("Month: "       +(class="type">class="kw">string)stm.mon);
Alert("Day: "         +(class="type">class="kw">string)stm.day);
Alert("Hour: "        +(class="type">class="kw">string)stm.hour);
Alert("Minute: "      +(class="type">class="kw">string)stm.min);
Alert("Second: "      +(class="type">class="kw">string)stm.sec);
Alert("Day of the week: "+(class="type">class="kw">string)stm.day_of_week);
Alert("Day of the year: "  +(class="type">class="kw">string)stm.day_of_year);
class="type">MqlDateTime stm;
class="type">class="kw">datetime tm=TimeCurrent(stm);
class=class="str">"cmt">//--- output date components
Alert("Year: "        +(class="type">class="kw">string)stm.year);
Alert("Month: "       +(class="type">class="kw">string)stm.mon);
Alert("Day: "         +(class="type">class="kw">string)stm.day);
Alert("Hour: "        +(class="type">class="kw">string)stm.hour);
Alert("Minute: "      +(class="type">class="kw">string)stm.min);
Alert("Second: "      +(class="type">class="kw">string)stm.sec);
Alert("Day of the week: "+(class="type">class="kw">string)stm.day_of_week);
Alert("Day of the year: "  +(class="type">class="kw">string)stm.day_of_year);

「倒推一个月的日期分量处理」

把 datetime 反向拆成 MqlDateTime 分量后,就能绕开固定天数加减的坑。各月长度不固定,2 月还分 28 或 29 天,直接 tm - 30*86400 在跨月时必然算错。 正确做法是先 TimeToStruct 拆分量,对月份做减一;若当前是 1 月则置为 12 月并把年份减一,再用 StructToTime 合回 datetime。下方代码在 MT5 里跑,Alert 会打出当前时间与恰好一个月前的两个时间戳,读者可直接复制验证。 外汇与贵金属行情受月度数据周期影响明显,这类时间锚点仅用于回测或条件触发,实际杠杆交易仍属高风险,信号失效概率不低。

MQL5 / C++
class="type">class="kw">datetime tm=TimeCurrent();
class="type">MqlDateTime stm;
TimeToStruct(tm,stm);
if(stm.mon==class="num">1)
  {
   stm.mon=class="num">12;
   stm.year--;
  }
else
  {
   stm.mon--;
  }
class="type">class="kw">datetime ltm=StructToTime(stm);
class=class="str">"cmt">//--- output result
Alert("Current: "+(class="type">class="kw">string)tm+", a month ago: "+(class="type">class="kw">string)ltm);

跨周期取柱时间的正确姿势

在 MT5 里写指标时,MetaEditor 通常会自动生成两种 OnCalculate() 之一:带 time[] 数组的版本直接暴露每根柱的时间,不带时间数组的版本(以及 EA 跨周期访问)则要靠 CopyTime() 自己拿。CopyTime() 有 3 个重载,前两个参数永远是品种和图表周期,最后一个参数是接收时间的数组。 第一个版本按柱索引和数量取时间,柱从右到左从 0 计数。下面这段代码复制了 D1 最后一根柱的时间: int start = 0; // 柱索引 int count = 1; // 柱数量 datetime tm[]; // 存放返回时间的数组 CopyTime(_Symbol,PERIOD_D1,start,count,tm); Alert(tm[0]); 动态数组会被函数自动缩放,静态数组则必须和 count 严格对齐。一次取多根时,数组元素是左到右排的:取 2 根的话,tm[0] 是昨日、tm[1] 是今日;想让顺序和图表一致,用 ArraySetAsSeries() 翻转即可。 第二个版本按起始时间和数量取,适合高周期框定低周期范围。但高周期某柱的精确时间,在低周期可能根本不存在对应柱——这时会拿到高周期前一柱里低周期的末根时间。文内给出的两个工具函数(分别查高周期柱内低周期首根/末根时间)都先检查 CopyTime() 返回值,出错返回 false,成功则通过引用写回时间。 若传入的时间不是周期对齐的精确值,得先做标准化:用时间戳算出自周期起点起的柱数,再乘该周期的秒长。这同时也是一种由低周期时间反推高周期时间的办法。第三个 CopyTime() 版本按时间范围取,能直接捞出高周期柱覆盖的全部低周期柱,写 EA 多周期同步时最省事。外汇和贵金属跨周期逻辑受跳空影响,实际表现可能有偏差,建议开 MT5 用上面片段自测。

MQL5 / C++
class="type">int OnCalculate(const class="type">int rates_total,
                     const class="type">int prev_calculated,
                     const class="type">class="kw">datetime &time[],
                     const class="type">class="kw">double &open[],
                     const class="type">class="kw">double &high[],
                     const class="type">class="kw">double &low[],
                     const class="type">class="kw">double &close[],
                     const class="type">long &tick_volume[],
                     const class="type">long &volume[],
                     const class="type">int &spread[])
  {
   class="kw">return(rates_total);
  }
class="type">int OnCalculate(const class="type">int rates_total,
                     const class="type">int prev_calculated,
                     const class="type">int begin,
                     const class="type">class="kw">double &price[])
  {
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//--- variables for function parameters
class="type">int start = class="num">0; class=class="str">"cmt">// bar index
class="type">int count = class="num">1; class=class="str">"cmt">// number of bars
class="type">class="kw">datetime tm[]; class=class="str">"cmt">// array storing the returned bar time
class=class="str">"cmt">//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
class=class="str">"cmt">//--- output result
Alert(tm[class="num">0]);
class=class="str">"cmt">//--- variables for function parameters
class="type">int start = class="num">0; class=class="str">"cmt">// bar index
class="type">int count = class="num">2; class=class="str">"cmt">// number of bars
class="type">class="kw">datetime tm[]; class=class="str">"cmt">// array storing the returned bar time
class=class="str">"cmt">//--- copy time 

◍ 跨周期定位小周期首根K线

在 MT5 里用 CopyTime 抓不同周期 BAR 的开盘时间,是做多周期联动的基础动作。下面这段演示了日线正序与倒序两种取法:先定义 start=0、count=2,申明 datetime 数组 tm,若用 ArraySetAsSeries(tm,true) 把数组倒序,tm[0] 就是昨天、tm[1] 是前天;不倒序时 tm[0] 为今天、tm[1] 为昨天。 想确认某根 M5 棒被哪根 H1 棒包含,可先用 CopyTime(_Symbol,PERIOD_M5,0,1,m5_tm) 取 M5 最新棒时间,再拿这个时间作为起始传给 CopyTime(_Symbol,PERIOD_H1,m5_tm[0],1,h1_tm),返回的 h1_tm[0] 就是覆盖它的 H1 棒时间。 实战里更通用的是写一个 LowerTFFirstBarTime 函数:传入大周期棒时间 aUpperTFBarTime,先用 CopyTime 以小周期取该时间对应的棒,若 tm[0] 小于大周期棒时间说明取到的是前一根,需再取小周期最新棒时间 tm2[0],用 Bars 算中间根数并回退 2 根索引,最终拿到大周期棒开启后小周期的第一根棒时间。外汇与贵金属跨周期策略受跳空与流动性影响,信号失效概率不低,需实盘验证。

MQL5 / C++
class="type">int start = class="num">0; class=class="str">"cmt">// bar index
class="type">int count = class="num">2; class=class="str">"cmt">// number of bars
class="type">class="kw">datetime tm[]; class=class="str">"cmt">// array storing the returned bar time
ArraySetAsSeries(tm,true); class=class="str">"cmt">// specify that the array will be arranged in reverse order
class=class="str">"cmt">//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
class=class="str">"cmt">//--- output result
Alert("Today: "+(class="type">class="kw">string)tm[class="num">0]+", yesterday: "+(class="type">class="kw">string)tm[class="num">1]);
class=class="str">"cmt">//--- get the time of the last bar on M5
class="type">int m5_start=class="num">0;
class="type">int m5_count=class="num">1;
class="type">class="kw">datetime m5_tm[];
CopyTime(_Symbol,PERIOD_M5,m5_start,m5_count,m5_tm);
class=class="str">"cmt">//--- determine the bar time on H1 that contains the bar on M5
class="type">int h1_count=class="num">1;
class="type">class="kw">datetime h1_tm[];
CopyTime(_Symbol,PERIOD_H1,m5_tm[class="num">0],h1_count,h1_tm);
class=class="str">"cmt">//--- output result
Alert("The bar on М5 with the time "+(class="type">class="kw">string)m5_tm[class="num">0]+" is contained in the bar on H1 with the time "+(class="type">class="kw">string)h1_tm[class="num">0]);
class="type">bool LowerTFFirstBarTime(class="type">class="kw">string aSymbol,
ENUM_TIMEFRAMES aLowerTF,
class="type">class="kw">datetime aUpperTFBarTime,
class="type">class="kw">datetime& aLowerTFFirstBarTime)
  {
   class="type">class="kw">datetime tm[];
class=class="str">"cmt">//--- determine the bar time on a lower time frame corresponding to the bar time on a higher time frame 
   if(CopyTime(aSymbol,aLowerTF,aUpperTFBarTime,class="num">1,tm)==-class="num">1)
     {
      class="kw">return(false);
     }
   if(tm[class="num">0]<aUpperTFBarTime)
     {
      class=class="str">"cmt">//--- we got the time of the preceding bar
      class="type">class="kw">datetime tm2[];
      class=class="str">"cmt">//--- determine the time of the last bar on a lower time frame
      if(CopyTime(aSymbol,aLowerTF,class="num">0,class="num">1,tm2)==-class="num">1)
        {
         class="kw">return(false);
        }
      if(tm[class="num">0]<tm2[class="num">0])
        {
         class=class="str">"cmt">//--- there is a bar following the bar of a lower time frame  that precedes the occurrence of the bar on a higher time frame
         class="type">int start=Bars(aSymbol,aLowerTF,tm[class="num">0],tm2[class="num">0])-class="num">2;
         class=class="str">"cmt">//--- the Bars() function returns the number of bars from the bar with time tm[class="num">0] to
class=class="str">"cmt">//--- the bar with time tm2[class="num">0]; since we need to determine the index of the bar following
class=class="str">"cmt">//--- the bar with time tm2[class="num">2], subtract class="num">2
         if(CopyTime(aSymbol,aLowerTF,start,class="num">1,tm)==-class="num">1)
           {
            class="kw">return(false);
           }
        }
      else
        {

「跨周期定位小周期首末根」

多周期联动时常需要确定某根大周期 K 线(如 H1)内部,对应的小周期(如 M5)第一根与最后一根的起始时间。下面两段函数分别处理「首根」与「末根」的回溯逻辑,核心都依赖 CopyTime 对边界时间的抓取。 首根函数 LowerTFFirstBarTime 先算大周期下一根起始时间,再用 CopyTime 从下一根向前取 1 根;若取到的 tm[0] 正好等于下一根时间,说明大周期这根里面没有更小周期 bar 被包含,直接把 aLowerTFFirstBarTime 置 0 并返回 true。否则把 tm[0] 赋给返回变量。 末根函数 LowerTFLastBarTime 思路相反:先确认下一根大周期时间在小周期上确实存在对应 bar,再从当前(0 号)小周期 bar 倒推,用 Bars 算偏移起点,再 CopyTime 取那根的时间写入返回变量。 调用示例里写死了 2012.12.10 15:00 的 H1 棒,对 EURUSD 这类 5 位平台可能得到 val=2012.12.10 14:55:00 附近的 M5 时间;外汇与贵金属跨周期回测存在点差与跳空风险,结果仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证。 别把边界等于号当保险 tm[0]==NextBarTime 这个判断在流动性极差的小周期(如 M1 交叉盘)可能因无成交而取不到精确边界,导致末根定位偏移一根,建议先在 EURUSD M5 跑一遍 Alert 输出对照。

MQL5 / C++
  class=class="str">"cmt">//--- there is no bar of a lower time frame contained in the bar on a higher time frame
  aLowerTFFirstBarTime=class="num">0;
  class="kw">return(true);
   }
  }
class=class="str">"cmt">//--- assign the obtained value to the variable 
  aLowerTFFirstBarTime=tm[class="num">0];
  class="kw">return(true);
  }
class=class="str">"cmt">//--- time of the bar on the higher time frame H1
  class="type">class="kw">datetime utftm=StringToTime("class="num">2012.12.class="num">10 class="num">15:class="num">00");
class=class="str">"cmt">//--- variable for the returned value
  class="type">class="kw">datetime val;
class=class="str">"cmt">//--- function call
  if(LowerTFFirstBarTime(_Symbol,PERIOD_M5,utftm,val))
    {
    class=class="str">"cmt">//--- output result in case of successful function operation
    Alert("val = "+(class="type">class="kw">string)val);
    }
  else
    {
    class=class="str">"cmt">//--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
    Alert("Error copying the time");
    class="kw">return;
    }
class="type">bool LowerTFLastBarTime(class="type">class="kw">string aSymbol,
ENUM_TIMEFRAMES aUpperTF,
ENUM_TIMEFRAMES aLowerTF,
class="type">class="kw">datetime aUpperTFBarTime,
class="type">class="kw">datetime& aLowerTFFirstBarTime)
  {
class=class="str">"cmt">//--- time of the next bar on a higher time frame
  class="type">class="kw">datetime NextBarTime=aUpperTFBarTime+PeriodSeconds(aUpperTF);
  class="type">class="kw">datetime tm[];
  if(CopyTime(aSymbol,aLowerTF,NextBarTime,class="num">1,tm)==-class="num">1)
    {
    class="kw">return(false);
    }
  if(tm[class="num">0]==NextBarTime)
    {
    class=class="str">"cmt">//--- There is a bar on a lower time frame corresponding to the time of the next bar on a higher time frame.
    class=class="str">"cmt">//--- Determine the time of the last bar on a lower time frame
    class="type">class="kw">datetime tm2[];
    if(CopyTime(aSymbol,aLowerTF,class="num">0,class="num">1,tm2)==-class="num">1)
      {
      class="kw">return(false);
      }
    class=class="str">"cmt">//--- determine the preceding bar index on a lower time frame
    class="type">int start=Bars(aSymbol,aLowerTF,tm[class="num">0],tm2[class="num">0]);
    class=class="str">"cmt">//--- determine the time of this bar
    if(CopyTime(aSymbol,aLowerTF,start,class="num">1,tm)==-class="num">1)
      {
      class="kw">return(false);
      }
    }
class=class="str">"cmt">//--- assign the obtain value to the variable 
  aLowerTFFirstBarTime=tm[class="num">0];
  class="kw">return(true);
  }
class=class="str">"cmt">//--- time of the bar on the higher time frame H1
class="type">class="kw">datetime utftm=StringToTime("class="num">2012.12.class="num">10 class="num">15:class="num">00");
class=class="str">"cmt">//--- variable for the returned value
class="type">class="kw">datetime val;
class=class="str">"cmt">//--- function call
if(LowerTFLastBarTime(_Symbol,PERIOD_H1,PERIOD_M5,utftm,val))
  {
class=class="str">"cmt">//--- output result in case of successful function operation
  Alert("val = "+(class="type">class="kw">string)val);
  }
else
  {

把任意时间对齐到周期起点

跨周期取数时,先得把时间戳归到所属 K 线的开盘时刻,否则小周期回看大周期会错位。下面这段 MQL5 用整数除法把传入时间砍到周期边界:H1 下 15:25 会被归成 15:00。 BarTimeNormalize 的核心是 PeriodSeconds 拿到周期秒数,再用 aTime/BarLength 向下取整乘回。对 2012.12.10 15:25 跑 PERIOD_H1,返回的就是 15:00 那根小时 bar 的开盘时间。 实际抓小周期 bar 时,用大周期起点作窗口头、减去一个小周期秒数作窗口尾,CopyTime 就能框出该小时内全部 M5 bar。上面例子里 H1 窗口从 15:00 到 15:55,拷出的 tm 数组大小应为 12,首元素即 15:00、末元素即 15:55。外汇与贵金属波动剧烈,跨周期逻辑写错会直接漏 bar,建议开 MT5 用脚本跑一遍确认 ArraySize 符合预期。

MQL5 / C++
class=class="str">"cmt">//--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
   Alert("Error copying the time");
   class="kw">return;
   }
class="type">class="kw">datetime BarTimeNormalize(class="type">class="kw">datetime aTime,ENUM_TIMEFRAMES aTimeFrame)
  {
   class="type">int BarLength=PeriodSeconds(aTimeFrame);
   class="kw">return(BarLength*(aTime/BarLength));
  }
class=class="str">"cmt">//--- the time to be normalized
class="type">class="kw">datetime tm=StringToTime("class="num">2012.12.class="num">10 class="num">15:class="num">25");
class=class="str">"cmt">//--- function call
class="type">class="kw">datetime tm1=BarTimeNormalize(tm,PERIOD_H1);
class=class="str">"cmt">//--- output result
Alert(tm1);
class=class="str">"cmt">//--- time of the bar start on H1
class="type">class="kw">datetime TimeStart=StringToTime("class="num">2012.12.class="num">10 class="num">15:class="num">00");
class=class="str">"cmt">//--- the estimated time of the last bar on
class=class="str">"cmt">//--- M5
class="type">class="kw">datetime TimeStop=TimeStart+PeriodSeconds(PERIOD_H1)-PeriodSeconds(PERIOD_M5);
class=class="str">"cmt">//--- copy time
class="type">class="kw">datetime tm[];
CopyTime(_Symbol,PERIOD_M5,TimeStart,TimeStop,tm);
class=class="str">"cmt">//--- output result 
Alert("Bars copied: "+(class="type">class="kw">string)ArraySize(tm)+", first bar: "+(class="type">class="kw">string)tm[class="num">0]+", last bar: "+(class="type">class="kw">string)tm[ArraySize(tm)-class="num">1]);

◍ 用整除截尾拿日起点与日内秒数

在 MT5 里算「今天从哪一秒开始」,最省事的办法不是拆时分秒再拼回去,而是直接吃透一天的固定长度:86400 秒。对 integer 型 datetime 做 (tm/86400)*86400,编译器会自动丢掉余数,得到的就是当日 00:00 的秒级时间戳。 一旦中间混进 double 或 float,整数截断就失效,必须用 MathFloor() 先把小数部分砍掉,再乘 86400;结果建议过一道 MathRound() 或 NormalizeDouble() 做正态化,避免浮点残差。实战中处理时间几乎都用整型,若你非得用浮点,大概率算法根基就歪了。 日内已流逝秒数更简单,取模即可:tm%86400。下面这段代码同时演示了日起点获取与 elapsed 输出,注意 Alert 里直接把整型时间戳 cast 成 string 才不会报错。 datetime tm=TimeCurrent(); tm=(tm/86400)*86400; //--- output result Alert("Day start time: "+(string)tm); MathFloor(tm/86400)*86400 MathRound(MathFloor(tm/86400)*86400) datetime tm=TimeCurrent(); long seconds=tm%86400; //--- output result Alert("Time elapsed since the day start: "+(string)seconds+" sec."); 把「秒数转时分秒」封成函数后,回测或实时面板都能直接复用。TimeFromDayStart 用引用参数回写 h/m/s,本身 return 总秒数,逻辑就是三层取模:先除 3600 拿小时,再对 3600 取余除 60 拿分钟,最后对 60 取余拿秒。 int TimeFromDayStart(datetime aTime,int &aH,int &aM,int &aS) { //--- Number of seconds elapsed since the day start (aTime%86400), //--- divided by the number of seconds in an hour is the number of hours aH=(int)((aTime%86400)/3600); //--- Number of seconds elapsed since the last hour (aTime%3600), //--- divided by the number of seconds in a minute is the number of minutes aM=(int)((aTime%3600)/60); //--- Number of seconds elapsed since the last minute aS=(int)(aTime%60); //--- Number of seconds since the day start return(int(aTime%86400)); } datetime tm=TimeCurrent(); int t,h,m,s; t=TimeFromDayStart(tm,h,m,s); //--- output result Alert("Time elapsed since the day start ",t," s, which makes ",h," h, ",m," m, ",s," s "); 判断「是否换日」也用同一思路:time[i]/86400 与 time[i-1]/86400 不等就说明跨了日界。外汇与贵金属行情在日界附近跳空概率偏高,用这行布尔量驱动日内统计重置时,要留意点差扩大与滑点风险。 bool NewDay=(time[i]/86400)!=(time[i-1]/86400);

MQL5 / C++
class="type">class="kw">datetime tm=TimeCurrent();
tm=(tm/class="num">86400)*class="num">86400;
class=class="str">"cmt">//--- output result
Alert("Day start time: "+(class="type">class="kw">string)tm);
MathFloor(tm/class="num">86400)*class="num">86400
MathRound(MathFloor(tm/class="num">86400)*class="num">86400)
class="type">class="kw">datetime tm=TimeCurrent();
class="type">long seconds=tm%class="num">86400;
class=class="str">"cmt">//--- output result
Alert("Time elapsed since the day start: "+(class="type">class="kw">string)seconds+" sec.");
class="type">int TimeFromDayStart(class="type">class="kw">datetime aTime,class="type">int &aH,class="type">int &aM,class="type">int &aS)
  {
class=class="str">"cmt">//--- Number of seconds elapsed since the day start(aTime%class="num">86400),
class=class="str">"cmt">//--- divided by the number of seconds in an hour is the number of hours
   aH=(class="type">int)((aTime%class="num">86400)/class="num">3600);
class=class="str">"cmt">//--- Number of seconds elapsed since the last hour(aTime%class="num">3600),
class=class="str">"cmt">//--- divided by the number of seconds in a minute is the number of minutes 
   aM=(class="type">int)((aTime%class="num">3600)/class="num">60);
class=class="str">"cmt">//--- Number of seconds elapsed since the last minute 
   aS=(class="type">int)(aTime%class="num">60);
class=class="str">"cmt">//--- Number of seconds since the day start
   class="kw">return(class="type">int(aTime%class="num">86400));
  }
class="type">class="kw">datetime tm=TimeCurrent();
class="type">int t,h,m,s;
t=TimeFromDayStart(tm,h,m,s);
class=class="str">"cmt">//--- output result
Alert("Time elapsed since the day start ",t," s, which makes ",h," h, ",m," m, ",s," s ");
class="type">bool NewDay=(time[i]/class="num">86400)!=(time[i-class="num">1]/class="num">86400);

「周起点与周内流逝时间的精确算法」

算周开始不能简单拿时间戳除以 604800。UNIX 时间戳起点是 1970.01.01 周四,而多数市场周一为周首,美加以周日为首,直接取整周数会偏移 3 或 4 天。 修正办法是先把时间往前拨:周一制加 259200 秒(3 天),周日制加 345600 秒(4 天),整除 604800 取整周数后再减回修正量,就能拿到真实的周起始时间戳。WeekStartTime() 返回 long 而非 datetime,因为第一周可能为负数。 拿到周起点后,SecondsFromWeekStart() 用当前时间减它即得流逝秒数。用 TimeToStruct() 拆成日时分秒时,stm.day 要自减 1——月号从 1 计,不减会多算一天。 下面三段是可直接丢进 MT5 脚本跑的核心逻辑,左到右柱索引下 NewWeek 能抓新周第一根:

MQL5 / C++
class="type">long WeekNum(class="type">class="kw">datetime aTime,class="type">bool aStartsOnMonday=false)
  {
class=class="str">"cmt">//--- if the week starts on Sunday, add the duration of class="num">4 days(Wednesday+Tuesday+Monday+Sunday),
class=class="str">"cmt">//    if it starts on Monday, add class="num">3 days(Wednesday, Tuesday, Monday)
   if(aStartsOnMonday)
     {
      aTime+=class="num">259200; class=class="str">"cmt">// duration of three days(class="num">86400*class="num">3)
     }
   else
     {
      aTime+=class="num">345600; class=class="str">"cmt">// duration of four days(class="num">86400*class="num">4)  
     }
   class="kw">return(aTime/class="num">604800);
  }
class="type">bool NewWeek=WeekNum(time[i])!=WeekNum(time[i-class="num">1]);
class="type">long WeekStartTime(class="type">class="kw">datetime aTime,class="type">bool aStartsOnMonday=false)
  {
   class="type">long tmp=aTime;
   class="type">long Corrector;
   if(aStartsOnMonday)
     {
      Corrector=class="num">259200; class=class="str">"cmt">// duration of three days(class="num">86400*class="num">3)
     }
   else
     {
      Corrector=class="num">345600; class=class="str">"cmt">// duration of four days(class="num">86400*class="num">4)
     }
   tmp+=Corrector;
   tmp=(tmp/class="num">604800)*class="num">604800;
   tmp-=Corrector;
   class="kw">return(tmp);
  }  
class="type">long SecondsFromWeekStart(class="type">class="kw">datetime aTime,class="type">bool aStartsOnMonday=false)
  {
   class="kw">return(aTime-WeekStartTime(aTime,aStartsOnMonday));
  }
class="type">long sfws=SecondsFromWeekStart(TimeCurrent());
class="type">MqlDateTime stm;
TimeToStruct(sfws,stm);
stm.day--;
Alert("Time elapsed since the week start "+(class="type">class="kw">string)stm.day+" d, "+(class="type">class="kw">string)stm.hour+" h, "+(class="type">class="kw">string)stm.min+" m, "+(class="type">class="kw">string)stm.sec+" s");

从任意基准日倒推周序号

写周数统计函数时,别只盯 day_of_year。更通用的做法是做一个从给定起始时间算周数的底层函数,再让「年初起」「月初起」这两个需求去调用它,避免重复逻辑。 核心函数 WeekNumFromDate 先把起始时间按 86400 秒(一天)向下取整,抹掉日内偏移;再用 TimeToStruct 取出起始日的星期几。若指定周一为周首,周日(day_of_week=0)会被折成 6,其余减 1,随后用 86400*星期值 作为修正量把时间轴对齐到周首。 最终周数 = (经过时间 + 修正量) / 604800(一周秒数)。YearStartTime 与 MonthStartTime 则把传入时间拆结构后,将日、月或日、时、分、秒字段重置,再 StructToTime 回去拿到基准时间戳。 在 MT5 里接一组历史 K 线时间跑 WeekNumMonth,你会发现跨月那根bar的周序号会从 0 重新计;外汇与贵金属波动受周界切换影响,用这类计数做周级别过滤时须留意跳空风险。

MQL5 / C++
class="type">long WeekNumFromDate(class="type">class="kw">datetime aTime,class="type">class="kw">datetime aStartTime,class="type">bool aStartsOnMonday=false)
  {
   class="type">long Time,StartTime,Corrector;
   class="type">MqlDateTime stm;
   Time=aTime;
   StartTime=aStartTime;
class=class="str">"cmt">//--- determine the beginning of the reference epoch
   StartTime=(StartTime/class="num">86400)*class="num">86400;
class=class="str">"cmt">//--- determine the time that elapsed since the beginning of the reference epoch
   Time-=StartTime;
class=class="str">"cmt">//--- determine the day of the week of the beginning of the reference epoch
   TimeToStruct(StartTime,stm);
class=class="str">"cmt">//--- if the week starts on Monday, numbers of days of the week are decreased by class="num">1,
class=class="str">"cmt">//    and the day with number class="num">0  becomes a day with number class="num">6
   if(aStartsOnMonday)
     {
      if(stm.day_of_week==class="num">0)
        {
         stm.day_of_week=class="num">6;
        }
      else
        {
         stm.day_of_week--;
        }
     }
class=class="str">"cmt">//--- calculate the value of the time corrector 
   Corrector=class="num">86400*stm.day_of_week;
class=class="str">"cmt">//--- time correction
   Time+=Corrector;
class=class="str">"cmt">//--- calculate and class="kw">return the number of the week
   class="kw">return(Time/class="num">604800);
  }
class="type">class="kw">datetime YearStartTime(class="type">class="kw">datetime aTime)
    {
     class="type">MqlDateTime stm;
     TimeToStruct(aTime,stm);
     stm.day=class="num">1;
     stm.mon=class="num">1;
     stm.hour=class="num">0;
     stm.min=class="num">0;
     stm.sec=class="num">0;
     class="kw">return(StructToTime(stm));
    }
class="type">class="kw">datetime MonthStartTime(class="type">class="kw">datetime aTime)
    {
     class="type">MqlDateTime stm;
     TimeToStruct(aTime,stm);
     stm.day=class="num">1;
     stm.hour=class="num">0;
     stm.min=class="num">0;
     stm.sec=class="num">0;
     class="kw">return(StructToTime(stm));
    }
class="type">long WeekNumYear(class="type">class="kw">datetime aTime,class="type">bool aStartsOnMonday=false)
    {
     class="kw">return(WeekNumFromDate(aTime,YearStartTime(aTime),aStartsOnMonday));
    }
class="type">long WeekNumMonth(class="type">class="kw">datetime aTime,class="type">bool aStartsOnMonday=false)
    {
     class="kw">return(WeekNumFromDate(aTime,MonthStartTime(aTime),aStartsOnMonday));
    }

◍ 用 H1 数组搭一个周末测试沙盘

不同交易中心的周末报价差异很大:有的根本没有周末柱,有的只在周日末尾给 4 根柱,有的整周末连续报价,还有的只给周六柱没周日柱。要验证那些处理时间的函数在所有情形下都不出错,与其满网找合适的真账户报价,不如自己造一块可控的试验田。 我们在脚本 sTestArea.mq5 里用 H1 时间框架造这块田,关注周五到周一这四天。H1 下每天 24 根柱,四天最多 96 个元素,数组尺寸封顶 96,绘制时直接贴到图表上对照看。脚本启动第一次跑 OnStart() 时先把数组按四种变体之一填好,之后用类 OnCalculate 的 LikeOnCalculate(rates_total, time[]) 在数组上迭代,函数行为基本等同指标缓冲区,方便把执行过程画出来。 LikeOnCalculate 里可以调 SetMarker(柱索引, 缓冲区行索引, 颜色) 打标记。变体选 2、每根柱下挂两行标记时,柱按星期取色:周五红、周六洋红、周日绿、周一蓝。这样一眼就能看出哪类周末结构下函数跑偏了。外汇与贵金属周末流动性断裂、跳空频繁,这类沙盘只用于逻辑验证,不代表实盘信号可靠。 带着这块沙盘,后面写那些对周末柱必须特殊对待的函数时,就能边写边可视化监控,不用再临时去翻不同中心的演示报价。

「支点指标里的新日判定逻辑」

做支点指标,第一步是算准「新的一天从哪根柱开始」。支点值取前一日收盘、最高、最低三者的平均,所以只要把日界搞错,整条线就会偏移。 简单版假设没有周末柱:把当前时间和前一时间都除以 86400(一天的秒数),商不等就视为新日已开始。 现实里周末可能没柱,也可能只有周日柱。第二版用 TimeToStruct 取星期:周六忽略、周日算新日;周一若紧接周日则跳过,若前面是其他星期则算新日;其余星期一律算新日。 验证用 sTestArea 工具跑 8 次(2 个函数版本 × 4 种柱生成选项),把日开始标成褐色核对。通过后直接挂附带的 Pivot1.mq5 即可,外汇和贵金属波动剧烈,支点仅作概率参考,实盘前请在 MT5 用历史数据自测。

MQL5 / C++
class="type">bool NewDay1(class="type">class="kw">datetime aTimeCur,class="type">class="kw">datetime aTimePre)
  {
   class="kw">return((aTimeCur/class="num">86400)!=(aTimePre/class="num">86400));
  }
class="type">bool NewDay2(class="type">class="kw">datetime aTimeCur,class="type">class="kw">datetime aTimePre)
  {
   class="type">MqlDateTime stm;
class=class="str">"cmt">//--- new day
   if(NewDay1(aTimeCur,aTimePre))
     {
       TimeToStruct(aTimeCur,stm);
       class="kw">switch(stm.day_of_week)
         {
          case class="num">6: class=class="str">"cmt">// Saturday
             class="kw">return(false);
             break;
          case class="num">0: class=class="str">"cmt">// Sunday
             class="kw">return(true);
             break;
          case class="num">1: class=class="str">"cmt">// Monday
             TimeToStruct(aTimePre,stm);
             if(stm.day_of_week!=class="num">0)
               { class=class="str">"cmt">// preceded by any day of the week other than Sunday
                class="kw">return(true);
               }
             else
               {
                class="kw">return(false);
               }
             break;
          class="kw">default: class=class="str">"cmt">// any other day of the week
             class="kw">return(true);
         }
     }
   class="kw">return(false);
  }
class="type">bool NewDay(class="type">class="kw">datetime aTimeCur,class="type">class="kw">datetime aTimePre,class="type">int aVariant=class="num">1)
  {
   class="kw">switch(aVariant)
     {
      case class="num">1:
         class="kw">return(NewDay1(aTimeCur,aTimePre));
         break;
      case class="num">2:
         class="kw">return(NewDay2(aTimeCur,aTimePre));
         break;
     }
   class="kw">return(false);
  }

把交易时段拆成可优化的秒数边界

想让 EA 只在每天固定钟点范围内下单,最直接的做法是把起止时间拆成「时」和「分」两个整型参数,而不是塞一个 "14:00" 字符串。这样在 MT5 策略测试器里就能直接对 aStartHour、aStartMinute 等跑参数优化,不必改代码。 核心算法是把一天切成 86400 秒。起始点算成 3600*时+60*分,结束点同理,当前时间用 aTimeCur%86400 取当天已过秒数,再拿去卡边界。 跨午夜的时段是个坑:比如 23:00 到 01:00,算出来的结束秒数会小于起始秒数。此时判定逻辑要反转成「当前≥起始 或 当前<结束」才放行;不跨午夜就老老实实「≥起始 且 <结束」。外汇和贵金属杠杆高,时段错配可能让订单在流动性最差的时候触发,实盘前务必用历史数据回测确认。 文末附的 Session.mq5 指标不只是测函数用,加载到图表能直观看到时段背景,盯盘时一眼分辨现在是不是允许交易区间。

MQL5 / C++
class="type">bool TimeSession(class="type">int aStartHour,class="type">int aStartMinute,class="type">int aStopHour,class="type">int aStopMinute,class="type">class="kw">datetime aTimeCur)
  {
class=class="str">"cmt">//--- session start time
   class="type">int StartTime=class="num">3600*aStartHour+class="num">60*aStartMinute;
class=class="str">"cmt">//--- session end time
   class="type">int StopTime=class="num">3600*aStopHour+class="num">60*aStopMinute;
class=class="str">"cmt">//--- current time in seconds since the day start
   aTimeCur=aTimeCur%class="num">86400;
   if(StopTime<StartTime)
     {
class=class="str">"cmt">//--- going past midnight
       if(aTimeCur>=StartTime || aTimeCur<StopTime)
         {
          class="kw">return(true);
         }
     }
   else
     {
class=class="str">"cmt">//--- within one day
       if(aTimeCur>=StartTime && aTimeCur<StopTime)
         {
          class="kw">return(true);
         }
     }
   class="kw">return(false);
  }

◍ 抓日内的那个交叉秒

直接拿当前时间跟设定的时分做相等判断基本没用。价格不是按秒准点跳的,网络延迟几秒到几分钟很常见,指定时刻盘口可能一根 tick 都没有。所以判定逻辑得改成:当前 bar 时间 ≥ 目标秒数,且上一根 bar 时间 < 目标秒数,才算真正跨过了那个点。 因为要锁定「日内」的时间点,得把当前时间和前一根时间都折算成「从 00:00 算起的第几秒」。目标时分的算法也一样:小时乘 3600 加分钟乘 60。麻烦点在跨零点——前一根 bar 可能还停留在昨天,取模 86400 后反而比当前秒数大,这时要拆成两种分支分别判断。 下面这个函数就是完整实现。配套指标 TimePoint.mq5 已按此逻辑画出日内时间点信号,读者可丢进 MT5 用任意周期验证:外汇与贵金属杠杆高,信号只提示时间点交叉,不预示方向。

MQL5 / C++
class="type">bool TimeCross(class="type">int aHour,class="type">int aMinute,class="type">class="kw">datetime aTimeCur,class="type">class="kw">datetime aTimePre)
  {
class=class="str">"cmt">//--- specified time since the day start
   class="type">class="kw">datetime PointTime=aHour*class="num">3600+aMinute*class="num">60;
class=class="str">"cmt">//--- current time since the day start
   aTimeCur=aTimeCur%class="num">86400;
class=class="str">"cmt">//--- previous time since the day start
   aTimePre=aTimePre%class="num">86400;
   if(aTimeCur<aTimePre)
     {
class=class="str">"cmt">//--- going past midnight
       if(aTimeCur>=PointTime || aTimePre<PointTime)
         {
          class="kw">return(true);
         }
     }
   else
     {
       if(aTimeCur>=PointTime && aTimePre<PointTime)
         {
          class="kw">return(true);
         }
     }
   class="kw">return(false);
  }

「用户定义日的支点切分逻辑」

把日界从固定 00:00 改成任意时间戳,支点指标就能按交易员自己的会话起点重算。核心靠 TimeCross() 判定时间交叉,但周末柱的处理会直接决定日界是否漂移。 无周末报价时最简单:新的一天从给定时间戳 crossing 处起算;若 broker 在周日只生成少量柱,TimeCross() 会把第一个周日柱误判为日始,这时必须忽略周日,因为报价实际属于周一。 持续周末报价则更麻烦:若用户定义日起点落在周六中间(见图 5,周五红/周六洋红/周日绿/周一蓝),周六一半算周五、周日一半算周一,中间过渡柱无归属。把周末强行等分只会无谓复杂化指标。 最务实的做法是把周六、周日所有柱统一视作周五延伸到周一的同一用户定义日,即凡在周六或周日开始的自定义日均丢弃。下方函数即该规则的实现,随文附带的 Pivot2.mq5 已用此逻辑出指标。 [CODE] bool NewCustomDay(int aHour,int aMinute,datetime aTimeCur,datetime aTimePre) { MqlDateTime stm; if(TimeCross(aHour,aMinute,aTimeCur,aTimePre)) { TimeToStruct(aTimeCur,stm);

if(stm.day_of_week==0stm.day_of_week==6)

{ return(false); } else { return(true); } } return(false); } [/CODE] 代码逐行拆解:函数入参为自定义日的小时、分钟,以及当前与前一根 K 线时间。先声明 MqlDateTime 结构 stm 存拆解结果。调用 TimeCross 判断时间是否跨过设定点;未跨则返回 false 表示非新日。跨过后用 TimeToStruct 把当前时间拆成结构,若 day_of_week 为 0(周日)或 6(周六)直接返回 false 忽略,其余工作日返回 true 确认新日开端。外汇与贵金属周末流动性断裂,用此类自定义日界重算支点时须警惕跳空导致的虚假信号,属高风险操作。

MQL5 / C++
class="type">bool NewCustomDay(class="type">int aHour,class="type">int aMinute,class="type">class="kw">datetime aTimeCur,class="type">class="kw">datetime aTimePre)
  {
   class="type">MqlDateTime stm;
   if(TimeCross(aHour,aMinute,aTimeCur,aTimePre))
     {
      TimeToStruct(aTimeCur,stm);
      if(stm.day_of_week==class="num">0 || stm.day_of_week==class="num">6)
        {
         class="kw">return(false);
        }
      else
        {
         class="kw">return(true);
        }
     }
   class="kw">return(false);
  }

把交易限定在指定星期几

限制 EA 只在特定星期开仓,核心是把时间拆成结构分量再比对开关。直接每次判断都读 input 变量虽然能跑,但反复访问外部参数在高频 OnTick 里偏浪费,更干净的做法是初始化时把七天布尔值塞进数组。 下面这段代码就是典型实现:先声明周日到周六七个 input 布尔量,默认全 true;WeekDays_Init() 在 EA 初始化时把对应值填进 WeekDays[7] 数组,索引 0~6 依次对应周日到周六。 判断函数 WeekDays_Check() 接收 datetime,用 TimeToStruct() 拆出 day_of_week,直接返回数组里那一项。stm.day_of_week 取值 0 是周日、6 是周六,和数组下标天然对齐,省去任何偏移计算。 实盘里若只想做周二到周四的伦敦时段,把其余 input 改 false 即可,MT5 加载指标 TradeWeekDays 后能直观看到过滤效果。外汇与贵金属杠杆高,过滤交易日不消除隔夜跳空风险,参数调整前先在历史数据回测验证。

MQL5 / C++
input class="type">bool Sunday   =true; class=class="str">"cmt">// Sunday
input class="type">bool Monday   =true; class=class="str">"cmt">// Monday
input class="type">bool Tuesday  =true; class=class="str">"cmt">// Tuesday 
input class="type">bool Wednesday=true; class=class="str">"cmt">// Wednesday
input class="type">bool Thursday =true; class=class="str">"cmt">// Thursday
input class="type">bool Friday   =true; class=class="str">"cmt">// Friday
input class="type">bool Saturday =true; class=class="str">"cmt">// Saturday
class="type">bool WeekDays[class="num">7];
class="type">void WeekDays_Init()
  {
   WeekDays[class="num">0]=Sunday;
   WeekDays[class="num">1]=Monday;
   WeekDays[class="num">2]=Tuesday;
   WeekDays[class="num">3]=Wednesday;
   WeekDays[class="num">4]=Thursday;
   WeekDays[class="num">5]=Friday;
   WeekDays[class="num">6]=Saturday;
  }
class="type">bool WeekDays_Check(class="type">class="kw">datetime aTime)
  {
   class="type">MqlDateTime stm;
   TimeToStruct(aTime,stm);
   class="kw">return(WeekDays[stm.day_of_week]);
  }

◍ 圈出跨天的周交易窗口

做外汇或贵金属短线,常要把策略限制在「周二 8:00 到周四 22:30」这类周内固定时段。MQL5 里用基于周起始流逝秒数的思路就能精准框住,不必每次手算 datetime 区间。 核心是把「周几+时分」换算成从周一开始累计的秒数:一天 86400 秒、一小时 3600 秒、一分钟 60 秒。再拿 SecondsFromWeekStart() 取当前时间距周起始的秒数做比对,跨周末(停止早于开始)和周内两种情况分开判断即可。 文末的 SessionWeek.mq5 指标就是套这个函数画的时段带,你在 MT5 里加载后改 aStartDay / aStopDay 等参数,就能直观看到自己定义的周窗口覆盖哪些 K 线。外汇与贵金属杠杆高、跳空频繁,时段过滤只降低噪音,不消除风险。 下面这段是可直接拷进 MT5 用的判定函数,逐行拆完你就明白边界怎么取的。

MQL5 / C++
<span class="keyword">class="type">bool</span> WeekSession(<span class="keyword">class="type">int</span> aStartDay,<span class="keyword">class="type">int</span> aStartHour,<span class="keyword">class="type">int</span> aStartMinute,<span class="keyword">class="type">int</span> aStopDay,<span class="keyword">class="type">int</span> aStopHour,<span class="keyword">class="type">int</span> aStopMinute,<span class="keyword">class="type">class="kw">datetime</span> aTimeCur)
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//--- session start time since the week start</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span> StartTime=aStartDay*<span class="number">class="num">86400</span>+<span class="number">class="num">3600</span>*aStartHour+<span class="number">class="num">60</span>*aStartMinute;
<span class="comment">class=class="str">"cmt">//--- session end time since the week start</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span> StopTime=aStopDay*<span class="number">class="num">86400</span>+<span class="number">class="num">3600</span>*aStopHour+<span class="number">class="num">60</span>*aStopMinute;
<span class="comment">class=class="str">"cmt">//--- current time in seconds since the week start</span>
&nbsp;&nbsp; <span class="keyword">class="type">long</span> TimeCur=SecondsFromWeekStart(aTimeCur,<span class="keyword">false</span>);
&nbsp;&nbsp; <span class="keyword">if</span>(StopTime&lt;StartTime)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- passing the turn of the week</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(TimeCur&gt;=StartTime || TimeCur&lt;StopTime)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="keyword">true</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">else</span>
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- within one week</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(TimeCur&gt;=StartTime &amp;&amp; TimeCur&lt;StopTime)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="keyword">true</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>);
&nbsp;&nbsp;}

「用对时间函数才能拿到真服务器时间」

MQL5 里有一组处理时间的函数:TimeTradeServer()、TimeGMT()、TimeDaylightSavings() 和 TimeGMTOffset(),它们的计算基准都是用户 PC 的时钟与时区设置,而不是经纪商后台。 TimeCurrent() 在周末会暴露出问题——它返回的是周五最后一根报价的时间,而非真实的当前时刻。TimeTradeServer() 则按交易服务器逻辑推算,周末也能拿到正确的服务器时间,写 EA 时拿它做时间判断更稳。 TimeGMT() 依据本机时钟和时区设置反推 GMT,严格说是返回 UTC。TimeDaylightSavings() 取本机“夏令时”修正值(秒),TimeGMTOffset() 取本机时区偏移(秒)。三者关系可还原本地时间:TimeGMT() - TimeGMTOffset() - TimeDaylightSavings() 理论上等于 TimeLocal()。外汇与贵金属杠杆高,时间错判可能造成错单,建议开 MT5 跑一遍下方代码核对本机环境。 代码逐行拆解: .datetime tm=TimeTradeServer(); —— 取交易服务器时间存入 tm .Alert(tm); —— 弹窗输出服务器时间 .datetime tm=TimeGMT(); —— 取 UTC 时间存入 tm .Alert(tm); —— 弹窗输出 UTC .int val=TimeDaylightSavings(); —— 取夏令时修正秒数 .Alert(val); —— 弹窗输出修正值 .int val=TimeGMTOffset(); —— 取时区偏移秒数 .Alert(val); —— 弹窗输出偏移 .datetime tm1=TimeLocal(); —— 取本机本地时间 .datetime tm2=TimeGMT()-TimeGMTOffset()-TimeDaylightSavings(); —— 用 UTC 减偏移和修正得本地时间 .Alert(tm1==tm2); —— 校验两者是否相等,不等说明本机时钟或时区设置有异常

MQL5 / C++
class="type">class="kw">datetime tm=TimeTradeServer();
class=class="str">"cmt">//--- output result
Alert(tm);
class="type">class="kw">datetime tm=TimeGMT();
class=class="str">"cmt">//--- output result
Alert(tm);
class="type">int val=TimeDaylightSavings();
class=class="str">"cmt">//--- output result
Alert(val);
class="type">int val=TimeGMTOffset();
class=class="str">"cmt">//--- output result
Alert(val);
class="type">class="kw">datetime tm1=TimeLocal();
class="type">class="kw">datetime tm2=TimeGMT()-TimeGMTOffset()-TimeDaylightSavings();
class=class="str">"cmt">//--- output result
Alert(tm1==tm2);

闰年与月长计算的轻量封装

做跨月统计或自定义周期回测时,直接用固定 30 天会踩坑:2 月在平年只有 28 天,闰年多一天,错一天就可能让区间求和整段偏移。下面两个函数把这类脏活收口了,复制进 MT5 的 include 或脚本里就能调。 LeapYear() 先按公元纪年规则判定:能被 4 整除但不能被 100 整除的是闰年;能被 400 整除的也是闰年。比如 2000 年返回 true,1900 年返回 false,2024 年返回 true。 DaysInMonth() 对 2 月单独走 LeapYear 分支,返回 28 或 29;其余月份用 (stm.mon-1)%7)%2 做奇偶修正——前 7 个月按 31、30 交替,之后同理,一行算出 30 或 31。 把这两段挂到你的时间工具库,写「过去 N 个自然月涨跌汇总」类指标时,就不用手填月份天数表了。外汇与贵金属波动受日历事件影响明显,这类基础函数虽小,但能降低统计口径出错的概率。

MQL5 / C++
class="type">bool LeapYear(class="type">class="kw">datetime aTime)
  {
   class="type">MqlDateTime stm;
   TimeToStruct(aTime,stm);
class=class="str">"cmt">//--- a multiple of class="num">4 
   if(stm.year%class="num">4==class="num">0)
     {
      class=class="str">"cmt">//--- a multiple of class="num">100
      if(stm.year%class="num">100==class="num">0)
        {
         class=class="str">"cmt">//--- a multiple of class="num">400
         if(stm.year%class="num">400==class="num">0)
           {
            class="kw">return(true);
           }
        }
      class=class="str">"cmt">//--- not a multiple of class="num">100 
      else
        {
         class="kw">return(true);
        }
     }
   class="kw">return(false);
  }
class="type">int DaysInMonth(class="type">class="kw">datetime aTime)
  {
   class="type">MqlDateTime stm;
   TimeToStruct(aTime,stm);
   if(stm.mon==class="num">2)
     {
      class=class="str">"cmt">//--- February
      if(LeapYear(aTime))
        {
         class=class="str">"cmt">//--- February in a leap year 
         class="kw">return(class="num">29);
        }
      else
        {
         class=class="str">"cmt">//--- February in a non-leap year 
         class="kw">return(class="num">28);
        }
     }
   else
     {
      class=class="str">"cmt">//--- other months
      class="kw">return(class="num">31-((stm.mon-class="num">1)%class="num">7)%class="num">2);
     }
  }

◍ 回测里时间函数到底信哪个

策略测试器自己造报价流,TimeCurrent() 的返回值就跟着这套模拟报价走,和真实账户里对服务器时间的语义不同。在回测环境里,TimeTradeServer() 和 TimeLocal() 也都直接对齐 TimeCurrent(),而且它不带时区和夏令时修正。写 EA 若逻辑依赖价格触发而非墙钟,用 TimeCurrent() 最稳,回测结论不会被假时区带偏。 TimeGMT()、TimeDaylightSavings()、TimeGMTOffset() 这三个是读你本机系统时钟的,测试器不会帮你模拟夏令时切换。真要在回测里复现欧美 3 月第二个周日切夏令时、11 月第一个周日切回这种节点,得自己硬编码精确日期去造条件,否则这段时间段的时段过滤可能整段失效。 实际踩坑多发生在亚洲会话:日本全年不实行夏令时,澳大利亚 11 月才进夏令时,和欧美中心时区之间存在数周错位。若 EA 只在欧洲或美洲时段交易,服务器与事件时间差基本恒定,反而比跨亚洲边界的策略更好测。外汇与贵金属杠杆高、滑点和时段流动性突变风险大,回测时间逻辑过关也只是第一步。

「MQL5 时间函数按用途怎么分」

把 MT5 里跟时间打交道的标准函数过一遍,其实能直接切成四类,开 MT5 按 F1 搜函数名就能逐个对照。 第一类是拿当前时间的根函数:TimeCurrent() 取服务器当前时间,TimeLocal() 取本机系统时间,二者差值就是你的本地时区偏移,做跨周期判定时这个差经常坑人。 第二类纯做格式转换:TimeToString() 和 StringToTime() 互转字符串与时间戳,TimeToStruct() 和 StructToTime() 负责时间结构与秒数的来回拆装,写面板显示时间必用。 第三类只管 K 线:CopyTime() 专门拉某品种某范围的柱时间数组,回测里复现历史节奏靠它。 第四类受你电脑设置牵制:TimeTradeServer()、TimeGMT()、TimeDaylightSavings()、TimeGMTOffset() 都跟终端所在的时区/夏令时配置绑定,换 VPS 地域后数值可能跳变,外汇和贵金属交易受此影响大,参数误用会导致信号错位。

画得少,看得清

这套时间函数压缩包把复杂时段逻辑拆成了九个可直接挂载的 mq5 文件加一个 20.52 KB 的 timefuncions.mqh 头文件,从标准日支点 Pivot1 到自定义日 Pivot2,再到周交易日 TradeWeekDays,基本覆盖了手动写时间判断时最易出错的边界。 真要落地,先开 MT5 把 timefuncions.mqh 拖进 Include 目录,再编译 sTestArea.mq5 跑一遍——脚本会打印各时间函数的返回值,你能当场看到 NewDay 在跨服务器时区时的跳变点。 外汇与贵金属杠杆高、滑点随机,任何时段开关仓逻辑都只是概率优势,别把回测里的干净切换当成实盘必然。剩下的,就是你自己把 WeekSession 接进 EA,看哪段行情值得盯。

让小布替你跑这套时区校验
这些时区与柱缺失的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到服务器时间和本地时间的偏差提示,你只管调策略逻辑。

常见问题

版本1按起止时间批量取,版本2按起始加数量取,版本3按末尾回溯取;实盘短周期缺柱时优先用版本3避免错位。
用 TimeCurrent 取分量后比对小时分钟,或先算日始时间再加偏移量,注意服务器时区而非本地时区。
可以,品种页的 AIGC 面板会标出当前服务器时间与本地时间的差,并提示夏令时状态,省去手动核对。
测试器用仿真 tick 生成柱,可能不还原缺柱和周日柱,需对照真实历史数据并隔离时间相关分支单独验证。
备选1按服务器日界切分,备选2显式跳过周日柱并锁定周一为周始,跨交易中心迁移时更稳定。