了解如何在MQL5中处理日期和时间·进阶篇
(2/3)· 搞懂datetime与MqlDateTime后,如何用事件与过滤器把时间写进交易逻辑
用 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。外汇与贵金属杠杆高、滑点突变频繁,定时策略若碰上停盘窗口可能漏触发,上机前先在策略测试器跑一轮看实际间隔。
<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")。外汇与贵金属杠杆高、滑点突变频繁,新柱判定仅作触发参考,实盘下单前仍须自测品种流动性。
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 就能在任意周期验证。
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 再比。外汇和贵金属杠杆高,时间窗只控节奏不控方向,实盘前务必用策略测试器验时区偏移。
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 的非法输入保护。
外汇和贵金属杠杆高、滑点跳空频繁,时段过滤只解决“何时不做”,不解决“做了就赚”,实盘前请用策略测试器按不同月份验证窗口边界。
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