了解如何在MQL5中处理日期和时间·进阶篇
⏱️

了解如何在MQL5中处理日期和时间·进阶篇

(2/3)· 搞懂datetime与MqlDateTime后,如何用事件与过滤器把时间写进交易逻辑

偏理论 第 2/3 篇
很多交易者把时间当背景参数,只在图表上肉眼看。其实MQL5里时间是可以被编译、比较和触发的变量,写错格式就全盘失效。本篇接着基础概念,把时间真正接进程序骨架。

用 OnTimer 做固定间隔触发

想让一段逻辑按固定秒数反复跑,而不是每次 tick 都执行,就得靠 OnTimer 事件。它本质是个无参 void 函数,只在你用 EventSetTimer 设好间隔后才会被终端周期性调用。 具体做法是在 OnInit 里用 EventSetTimer(seconds) 声明周期。下面这段把间隔设成 60,意味着 OnTimer 里的代码每 60 秒被执行一次,适合做定时巡检或分批发单。 [CODE] <span class="keyword">int</span> <span class="functions">OnInit</span>() { <span class="functions">EventSetTimer</span>(<span class="number">60</span>); } [/CODE] 代码逐行拆解:第 1 行定义 OnInit 初始化函数,返回 int;第 2 行调用 EventSetTimer 并传入 60,告诉 MT5 终端每 60 秒触发一次 OnTimer;第 3 行函数体结束。 要停掉这个循环,不能在 OnTimer 里自杀,得在 OnDeinit 或类析构里调 EventKillTimer。外汇与贵金属杠杆高、滑点突变频繁,定时策略若碰上停盘窗口可能漏触发,上机前先在策略测试器跑一轮看实际间隔。

MQL5 / C++
<span class="keyword">class="type">int</span> <span class="functions">OnInit</span>()
{
<span class="functions">EventSetTimer</span>(<span class="number">class="num">60</span>);
}

◍ 用 CNewBar 类抓新柱信号

把「新柱生成」做成可复用的 include 文件,是 MT5 里省事的做法。核心是一个 CNewBar 类,私有变量 time[] 存当前柱时间、lastTime 存上一根柱时间,对外只暴露 newBarCheck(symbol, timeFrame) 这个布尔函数。 函数里先 ArraySetAsSeries(time, true) 把数组按时间倒序排,再用 CopyTime 取起始位 0、数量 2 的开盘时间。lastTime 为 0 判定为首次检查,跳过误报;只有 time[0] 大于 lastTime 且非首次,才把 newBar 置真并更新 lastTime。 下面这段是类的完整骨架,直接存成 newBar.mqh 就能被 EA 用 #include 调走。逐行看:class 声明两个私有 datetime 数组;构造函数里只做序列化;newBarCheck 内部 firstCheck/newBar 默 false,CopyTime 拉两根柱,首次不报新柱,后续时间戳跳变才返回 true。 EA 侧只要声明 CNewBar 对象,在 OnTick 里调 newBarCheck(_Symbol, _Period),返回真就 Print("A new bar painted, you can trade")。外汇与贵金属杠杆高、滑点突变频繁,新柱判定仅作触发参考,实盘下单前仍须自测品种流动性。

MQL5 / C++
class CNewBar
  {
class="kw">private:
   class="type">class="kw">datetime          time[], lastTime;
class="kw">public:
   class="type">void              CNewBar();
   class="type">bool              newBarCheck(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeFrame);
  };
class="type">void CNewBar::CNewBar(class="type">void)
 {
   ArraySetAsSeries(time,true);
  }
class="type">bool CNewBar::newBarCheck(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeFrame)
  class="type">bool firstCheck = class="kw">false;
  class="type">bool newBar = class="kw">false;
CopyTime(symbol,timeFrame,class="num">0,class="num">2,time);
  if(lastTime == class="num">0)
    {
      firstCheck = true;
    }
  if(time[class="num">0] > lastTime)
    {
      if(firstCheck == class="kw">false)
      {
        newBar = true;
      }
      lastTime = time[class="num">0];
    }
class="kw">return(newBar);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                newBar.mqh |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CNewBar
  {
class="kw">private:
   class="type">class="kw">datetime          time[], lastTime;
class="kw">public:
   class="type">void              CNewBar();
   class="type">bool              newBarCheck(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeFrame);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CNewBar::CNewBar(class="type">void)
 {
   ArraySetAsSeries(time,true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CNewBar::newBarCheck(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeFrame)
  {
   class="type">bool firstCheck = class="kw">false;
   class="type">bool newBar = class="kw">false;
   CopyTime(symbol,timeFrame,class="num">0,class="num">2,time);
   if(lastTime == class="num">0)
     {
       firstCheck = true;
     }

「用 K 线闭合信号卡住首根新柱」

在 MT5 里做价格行为策略,最怕把上一根柱的收盘价误当成新信号反复触发。上面这段逻辑核心就一件事:靠 time[0] 是否大于上次记录的 lastTime,来判断当前是不是刚画出一根新 K 线。 CNewBar 这个类把判断封进了 newBarCheck(Symbol(), Period()),返回 true 才允许把 barShift 设为 1,也就是只去读刚闭合那根柱。若 newBarTrade 开关为 false,这段检查直接跳过,策略会退化为每 tick 都当新柱处理——回测里容易瞬间多出 30%~50% 的无效下单。 OnTick 里每跳都跑一遍 newBarCheck,命中就 Print 一句提示。实盘外汇和贵金属波动大、滑点高,光有“新柱”信号远远不够,必须再叠过滤条件;这套代码只是把触发时机钉死,复制进 EA 后你改 Symbol/Period 就能在任意周期验证。

MQL5 / C++
  if(time[class="num">0] > lastTime)
    {
      if(firstCheck == class="kw">false)
      {
        newBar = true;
      }
      lastTime = time[class="num">0];
    }
  class="kw">return(newBar);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <dateTime.mqh>
CNewBar NewBar;
class="type">bool newBarTrade=true;
  class="type">bool newBar=true;
  class="type">int barShift=class="num">0;
  if(newBarTrade==true)
    {
      newBar = NewBar.newBarCheck(Symbol(),Period());
      barShift=class="num">1;
    }
  if(newBar==true)
    {
      Print("A new bar painted, you can trade");
    }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                            newBar.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <dateTime.mqh>
CNewBar NewBar;
class="type">bool newBarTrade=true;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  class="type">bool newBar=true;
  class="type">int barShift=class="num">0;
  if(newBarTrade==true)
    {
      newBar = NewBar.newBarCheck(Symbol(),Period());
      barShift=class="num">1;
    }
  if(newBar==true)
    {
      Print("A new bar painted, you can trade");
    }
  }

用时间窗卡住 EA 的交易节奏

想在 MT5 里让 EA 只在特定时段下单,最干净的做法是写一个独立的时间过滤器类,而不是把判断逻辑散落在每个信号里。下面这套 CTimeFilter 类接收起始时间、结束时间和是否用本地时区三个参数,默认按服务器时间跑。 类里 timeCheck 先拦脏数据:如果 startTime >= endTime,直接弹 Alert("Error: Invalid Time input") 并 return false,避免把时间窗写反还继续交易。随后按 localTime 开关决定取 TimeLocal() 还是 TimeCurrent(),再判断 currentTime 是否落在 [startTime, endTime) 半开区间,命中则 timeFilterActive 置 true。 [CODE] class CTimeFilter { public: bool timeCheck(datetime startTime, datetime endTime, bool localTime = false); }; if(startTime >= endTime) { Alert("Error: Invalid Time input"); return(false); } datetime currentTime; if(localTime == true) { currentTime = TimeLocal(); } else currentTime = TimeCurrent(); bool timeFilterActive = false; if(currentTime >= startTime && currentTime < endTime) { timeFilterActive = true; } return(timeFilterActive); [/CODE] 上面代码逐行拆:class 定义公有的 timeCheck;首条 if 拦反转时间窗并报警返回;datetime currentTime 存当前刻;localTime 真则用本地钟否则用服务器钟;bool 初值 false;半开区间判断激活;最后吐回状态。 EA 侧只要 #include 这个文件,挂两个 input datetime(示例默认 2023.10.10 10:00 到 17:00),全局建 CTimeFilter filter,OnTick 里 filter.timeCheck(...) 拿到布尔后 Print 对应中英文状态即可。跑起来只有三种结果:窗内显 active、窗外显 inactive、参数写反显错误。 不挂 include 也能干——用四个 input int 存起止时分,OnTick 里 MqlDateTime 接 TimeCurrent,StructToTime 转出 timeStart/timeEnd 再比。外汇和贵金属杠杆高,时间窗只控节奏不控方向,实盘前务必用策略测试器验时区偏移。

MQL5 / C++
class CTimeFilter
  {
class="kw">public:
   class="type">bool timeCheck(class="type">class="kw">datetime startTime, class="type">class="kw">datetime endTime, class="type">bool localTime = class="kw">false);
  };
   if(startTime >= endTime)
    {
      Alert("Error: Invalid Time class="kw">input");
      class="kw">return(class="kw">false);
    }
   class="type">class="kw">datetime currentTime;
   if(localTime == true)
    {
      currentTime = TimeLocal();
    }
   else
      currentTime = TimeCurrent();
   class="type">bool timeFilterActive = class="kw">false;
   if(currentTime >= startTime && currentTime < endTime)
    {
      timeFilterActive = true;
    }
   class="kw">return(timeFilterActive);
class CTimeFilter
  {
class="kw">public:
   class="type">bool timeCheck(class="type">class="kw">datetime startTime, class="type">class="kw">datetime endTime, class="type">bool localTime = class="kw">false);
  };
class="type">bool CTimeFilter::timeCheck(class="type">class="kw">datetime startTime, class="type">class="kw">datetime endTime, class="type">bool localTime = class="kw">false)
  {
   if(startTime >= endTime)
    {
      Alert("Error: Invalid Time class="kw">input");
      class="kw">return(class="kw">false);
    }
   class="type">class="kw">datetime currentTime;
   if(localTime == true)
    {
      currentTime = TimeLocal();
    }
   else
      currentTime = TimeCurrent();
   class="type">bool timeFilterActive = class="kw">false;
   if(currentTime >= startTime && currentTime < endTime)
    {
      timeFilterActive = true;
    }
   class="kw">return(timeFilterActive);
  }
class="macro">#include <dateTime.mqh>
class="kw">input class="type">class="kw">datetime StartTime = D&class="macro">#x27;class="num">2023.10.class="num">10 class="num">10:class="num">00&class="macro">#x27;;
class="kw">input class="type">class="kw">datetime EndTime = D&class="macro">#x27;class="num">2023.10.class="num">10 class="num">17:class="num">00&class="macro">#x27;;
CTimeFilter filter;
class="type">bool timeFilterActive = filter.timeCheck(StartTime,EndTime,class="kw">false);
   if(timeFilterActive == true)
    {
      Print("Trading is active based on time filter");
    }
     else Print("Trading is inactive based on time filter");

◍ 把时间窗口塞进EA的两种写法

做外汇或贵金属策略时,按交易时段开关逻辑是很常见的需求,但很多人直接写死 datetime 字符串,回测一换日期就失效。下面两段 MT5 代码给出了两种落地方式,都围绕 10:00–17:00 这个示例窗口,你可以直接开 MT5 新建脚本粘进去跑。 第一段用了 CTimeFilter 类封装,StartTime 和 EndTime 是具体日期时间(示例为 2023.10.10 10:00 到 17:00),适合单次回测;后面又补了一段用 MqlDateTime 结构体按 hour/min 拼时间的写法,不绑死日期,每天同段生效。 第二段 timeFilterApp2 把第一段里重复的结构体拼装直接收进 OnTick,去掉了类依赖,输入参数只剩四个整型。逻辑核心都是 TimeCurrent() >= timeStart && TimeCurrent() < timeEnd,并注意了 TimeStartHour >= TimeEndHour 的非法输入保护。 外汇和贵金属杠杆高、滑点跳空频繁,时段过滤只解决“何时不做”,不解决“做了就赚”,实盘前请用策略测试器按不同月份验证窗口边界。

MQL5 / C++
class=class="str">"cmt">//|                                                                 timeFilterApp.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <dateTime.mqh>
class="kw">input class="type">class="kw">datetime StartTime = D&class="macro">#x27;class="num">2023.10.class="num">10 class="num">10:class="num">00&class="macro">#x27;;
class="kw">input class="type">class="kw">datetime EndTime = D&class="macro">#x27;class="num">2023.10.class="num">10 class="num">17:class="num">00&class="macro">#x27;;
CTimeFilter filter;
class="type">void OnTick()
  {
   class="type">bool timeFilterActive = filter.timeCheck(StartTime,EndTime,class="kw">false);
   if(timeFilterActive == true)
     {
       Print("Trading is active based on time filter");
     }
     else Print("Trading is inactive based on time filter");
  }
class="kw">input class="type">int TimeStartHour =
class="num">10 ;
class="kw">input class="type">int TimeStartMin = class="num">0;
class="kw">input class="type">int TimeEndHour = class="num">17;
class="kw">input class="type">int TimeEndMin =
class="num">0 ;
class="type">MqlDateTime structTime;
TimeCurrent(structTime);
   structTime.sec = class="num">0;
   structTime.hour = TimeStartHour;
   structTime.min = TimeStartMin;
class="type">class="kw">datetime timeStart = StructToTime(structTime);
   structTime.hour = TimeEndHour;
   structTime.min = TimeEndMin;
class="type">class="kw">datetime timeEnd = StructToTime(structTime);
class="type">bool isTime = TimeCurrent() >= timeStart && TimeCurrent() < timeEnd;
   if(TimeStartHour >= TimeEndHour)
     {
       Print("Error: Invalid Time class="kw">input");
     }
   if(isTime==true)
     {
       Print("Trading is active based on time filter");
     }
   else
      Print("Trading is inactive based on time filter");
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 timeFilterApp2.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">int TimeStartHour = class="num">10;
class="kw">input class="type">int TimeStartMin = class="num">0;
class="kw">input class="type">int TimeEndHour = class="num">17;
class="kw">input class="type">int TimeEndMin = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class="type">MqlDateTime structTime;
   TimeCurrent(structTime);
   structTime.sec = class="num">0;
   structTime.hour = TimeStartHour;
   structTime.min = TimeStartMin;
   class="type">class="kw">datetime timeStart = StructToTime(structTime);
   structTime.hour = TimeEndHour;
   structTime.min = TimeEndMin;
   class="type">class="kw">datetime timeEnd = StructToTime(structTime);
   class="type">bool isTime = TimeCurrent() >= timeStart && TimeCurrent() < timeEnd;
   if(TimeStartHour >= TimeEndHour)
     {
       Print("Error: Invalid Time class="kw">input");
     }
   if(isTime==true)
     {
       Print("Trading is active based on time filter");
     }
   else
让小布替你跑这套
这些时间触发与过滤的诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到时段分布与重算提示,你只管判断信号质量。

常见问题

datetime存的是Unix秒数,适合比较和加减;MqlDateTime拆成年月日时分秒字段,适合做日历类判断和人工可读输出,两者常互相转换。
可以,小布盯盘的品种页会标注活跃时段与异常重算,你对照代码里的过滤器逻辑就能快速定位是否漏了时区或周末处理。
OnTimer按系统节拍走,精度稳但和K线无关;NewBar靠柱体切换,贴合价格行为。定时交易倾向用OnTimer,顺K线触发则用NewBar。
是,MT5服务器多用UTC,写死本地时间会偏移数小时,导致策略在错误窗口成交,概率上显著降低回测与实盘一致性。