了解如何在MQL5中处理日期和时间(基础篇)
「在 MQL5 里把时间真正用起来」
MQL5 的时间系统以 1970-01-01 为纪元,用 datetime 类型以秒为单位累计,跟 Unix 时间戳一致。直接在代码里写 datetime t = D'2024.04.02 09:44'; 就能构造一个具体时刻,比用整数硬凑更不易出错。 想拿当前服务器时间用 TimeCurrent(),本地时间用 TimeLocal(),两者在跨时区回测时经常差出几个小时,不区分清楚会导致入场逻辑偏移。下面这段演示了怎么取当前时间并拆出小时数: [CODE]datetime now = TimeCurrent(); int hour = TimeHour(now); Print("Server hour: ", hour);[/CODE] TimeHour() 返回 0–23 的整型,配合 TimeDay、TimeMonth 可以做时段过滤,比如只在伦敦时段(hour>=7 && hour<16)允许交易。外汇与贵金属杠杆高、跳空频繁,时段过滤只是降低风险暴露概率,不等于规避了所有风险。 开 MT5 按 F4 建个脚本把上面三行贴进去编译运行,终端 Experts 标签里能看到服务器小时数打印,验证你账户服务器时区到底是哪。
class="type">class="kw">datetime now = TimeCurrent(); class="type">int hour = TimeHour(now); Print("Server hour: ", hour);
为什么时间处理是 MQL5 交易系统的地基
在 MT5 里,时间不是附属信息,而是驱动逻辑的核心变量。无论是择时开仓、限制交易窗口,还是按 K 线刷新触发计算,都绕不开对日期与时间的精确控制。很多策略回测和实盘表现脱节,根源常出在时区或定时器理解错误。 本篇会拆开讲清几个硬核组件:datetime 原始类型怎么存、MqlDateTime 结构怎么拆字段、OnTimer 事件怎么周期性跑任务,以及三个可直接抄进 EA 的实战应用——NewBar 判新柱、时间过滤器、tradeAtTime 定点交易。 外汇与贵金属杠杆高、波动快,时间错配可能让策略在错误时段成交,风险完全由使用者自担。把这套时间机制吃透,你才有能力在 MT5 里验证自己的择时假设。
◍ 用 datetime 把时间当数字算
MT5 里的 datetime 本质是 Unix 时间戳,即从 1970.01.01 00:00 GMT 起算的秒数。比如 2023.01.01 00:00:00 GMT 对应 1672531200 秒,一天固定是 86400 秒,一个月约 2629743 秒——这些常数直接决定了你做加减时的偏移量。 写时间常量用 D'yyyy.mm.dd hh:mm:ss' 或 D'dd.mm.yyyy hh:mm:ss' 都行,编译器认两种。下面这段声明值得直接贴进 MT5 看警告:D'' 会被填成编译时刻,D'12:30:27' 只剩时分秒时也会自动补上编译日期,容易埋坑。 [CODE] datetime NY=D'2015.01.01 00:00'; // 2015年起始时刻 datetime d1=D'1980.07.19 12:30:27'; // 年 月 日 时 分 秒 完整写法 datetime d2=D'19.07.1980 12:30:27'; // 与 d1 等价,日.月.年格式 datetime d3=D'19.07.1980 12'; // 等价 D'1980.07.19 12:00:00' datetime d4=D'01.01.2004'; // 等价 D'01.01.2004 00:00:00' datetime compilation_date=__DATE__; // 编译日期 datetime compilation_date_time=__DATETIME__; // 编译日期与时间 datetime compilation_time=__DATETIME__-__DATE__; // 编译时间(差值) datetime warning1=D'12:30:27'; // 等价 D'[编译日期] 12:30:27' datetime warning2=D''; // 等价 __DATETIME__ [/CODE] 比较两段输入时间,只需各自建 datetime 变量后在 OnTick 里用 > < 判断。下方代码挂上 EA,把 inpDate1 设成 2023.09.01、inpDate2 设成 2023.09.30,跑出来必打印“Input two is a recent time”;对调后则出“Input one is a recent time”。 [CODE] input datetime inpDate1 = D'2023.09.01 00:00'; input datetime inpDate2 = D'2023.09.30 00:00'; void OnTick() { if(inpDate1>inpDate2) { Print("Input one is a recent time"); } else if(inpDate1<inpDate2) { Print("Input two is a recent time"); } } [/CODE] 加减法就是纯秒数运算:oldDate 为 D'2023.09.01 00:00' 时,oldDate+86400 得到次日同时刻,oldDate-86400 回退一天。外汇与贵金属行情受时段跳空影响,用这种偏移算历史窗口时,要留意周末和节假导致的时间断层。 转字符串用 TimeToString,反向解析用 StringToTime,两者在把日志时间转成可读格式、或把界面输入转回时间戳时高频用到。开 MT5 新建脚本跑一遍这两个函数,比看文档直观得多。
class="type">class="kw">datetime NY=D&class="macro">#x27;class="num">2015.01.class="num">01 class="num">00:class="num">00&class="macro">#x27;; class="type">class="kw">datetime d1=D&class="macro">#x27;class="num">1980.07.class="num">19 class="num">12:class="num">30:class="num">27&class="macro">#x27;; class="type">class="kw">datetime d2=D&class="macro">#x27;class="num">19.07.class="num">1980 class="num">12:class="num">30:class="num">27&class="macro">#x27;; class="type">class="kw">datetime d3=D&class="macro">#x27;class="num">19.07.class="num">1980 class="num">12&class="macro">#x27;; class="type">class="kw">datetime d4=D&class="macro">#x27;class="num">01.01.class="num">2004&class="macro">#x27;; class="type">class="kw">datetime compilation_date=__DATE__; class="type">class="kw">datetime compilation_date_time=__DATETIME__; class="type">class="kw">datetime compilation_time=__DATETIME__-__DATE__; class="type">class="kw">datetime warning1=D&class="macro">#x27;class="num">12:class="num">30:class="num">27&class="macro">#x27;; class="type">class="kw">datetime warning2=D&class="macro">#x27;&class="macro">#x27;; class="kw">input class="type">class="kw">datetime inpDate1 = D&class="macro">#x27;class="num">2023.09.class="num">01 class="num">00:class="num">00&class="macro">#x27;; class="kw">input class="type">class="kw">datetime inpDate2 = D&class="macro">#x27;class="num">2023.09.class="num">30 class="num">00:class="num">00&class="macro">#x27;; class="type">void OnTick() { if(inpDate1>inpDate2) { Print("Input one is a recent time"); } else if(inpDate1<inpDate2) { Print("Input two is a recent time"); } }
「用时间戳做加减与互转」
MT5 里 datetime 本质是从 1970-01-01 算起的秒数,直接加减整数就是在挪日期。下面这段在 OnTick 里给 oldDate(2023.09.01) 加 86400 秒得到次日,给 newDate(2023.09.30) 减 86400 秒得到前日,Print 出来的两个值差了 29 天整数倍,验证时间轴是连续秒计数。 input datetime newDate = D'2023.09.30 00:00'; void OnTick() { datetime addToDate = oldDate+86400; Print("Result of date addition - ",addToDate); datetime subtractFromDate = newDate-86400; Print("Result of date subtraction - ",subtractFromDate); } 另一段演示字符串与时间戳互转:oldDate 用 TimeToString 变成可读文本,newDate 这种 "2023.09.30 00:00" 字符串用 StringToTime 反向解析回 datetime。做跨周期回测或把 CSV 里的时间列喂给指标时,这套转换是必过的关卡。 input datetime oldDate = D'2023.09.01 00:00'; input string newDate = "2023.09.30 00:00"; void OnTick() { string newOldDate = TimeToString(oldDate); datetime newNewDate = StringToTime(newDate); Print("Time To String - ",newOldDate); Print("String To Time - ",newNewDate); } 外汇与贵金属行情受跳空影响,用秒级加减推演历史 K 线位置时,周末和节假日不计入报价但时间戳仍连续,回测里可能偏离实盘,属高风险盲区。
class="kw">input class="type">class="kw">datetime newDate = D&class="macro">#x27;class="num">2023.09.class="num">30 class="num">00:class="num">00&class="macro">#x27;; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">class="kw">datetime addToDate = oldDate+class="num">86400; Print("Result of date addition - ",addToDate); class="type">class="kw">datetime subtractFromDate = newDate-class="num">86400; Print("Result of date subtraction - ",subtractFromDate); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| dateTimeConvert.mq5 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">class="kw">datetime oldDate = D&class="macro">#x27;class="num">2023.09.class="num">01 class="num">00:class="num">00&class="macro">#x27;; class="kw">input class="type">class="kw">string newDate = "class="num">2023.09.class="num">30 class="num">00:class="num">00"; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">class="kw">string newOldDate = TimeToString(oldDate); class="type">class="kw">datetime newNewDate = StringToTime(newDate); Print("Time To String - ",newOldDate); Print("String To Time - ",newNewDate); } class=class="str">"cmt">//+------------------------------------------------------------------+
拆开 datetime 的内部骨架
MQL5 里那个看着像黑盒的 datetime,其实可以整块拆进 MqlDateTime 结构里读。这个结构有 8 个 int 字段:年、月、日、时、分、秒,外加星期几和年内第几天(1 月 1 日记为 0)。 要把一个 datetime 值灌进去,用 TimeToStruct(dt, struct) 就行,返回布尔表示转换是否成功;反过来 StructToTime(struct) 只吃结构、吐回带秒数的 datetime。下面这段 EA 把 D'2023.10.10 07:07:07' 拆开打印,跑完能在日志看到 year: 2023、month: 10、day: 10、hour: 7、minute: 7、second: 7,day_of_week 是 2(周二),day_of_year 是 282。 边界兜底有点反直觉:月或日输入小于 1 时默认补 01,大于上限则月封顶 12、日封顶 31;小时限在 0–23,分钟限在 0–59。写时间解析逻辑时别假设用户输入都合法,靠这个默认规则可能掩盖脏数据。 外汇与贵金属行情受杠杆与跳空影响,时间解析错乱会让止损或定时触发逻辑偏移,实盘前务必在 MT5 策略测试器用非常规时间输入跑一遍上面代码验证兜底行为。
class="kw">struct class="type">MqlDateTime { class="type">int year; class=class="str">"cmt">// Year class="type">int mon; class=class="str">"cmt">// Month class="type">int day; class=class="str">"cmt">// Day class="type">int hour; class=class="str">"cmt">// Hour class="type">int min; class=class="str">"cmt">// Minutes class="type">int sec; class=class="str">"cmt">// Seconds class="type">int day_of_week; class=class="str">"cmt">// Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) class="type">int day_of_year; class=class="str">"cmt">// Day number of the year(January 1st is assigned the number value of zero) }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="type">MqlDateTime.mq5 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">class="kw">datetime dtData = D&class="macro">#x27;class="num">2023.10.class="num">10 class="num">07:class="num">07:class="num">07&class="macro">#x27;; class="type">void OnTick() { class="type">MqlDateTime timeStruct; TimeToStruct(dtData,timeStruct); class="type">int year = timeStruct.year; Print("year: ",year); Print("====================="); class="type">int month = timeStruct.mon; Print("month: ",month); Print("====================="); class="type">int day = timeStruct.day; Print("day: ",day); Print("====================="); class="type">int hour = timeStruct.hour; Print("hour: ",hour); Print("====================="); class="type">int minute = timeStruct.min; Print("minute: ",minute); Print("====================="); class="type">int second = timeStruct.sec; Print("second: ",second); Print("====================="); class="type">int dayofWeek = timeStruct.day_of_week; Print("dayofWeek: ",dayofWeek); Print("====================="); class="type">int dayofYear = timeStruct.day_of_year; Print("dayofYear: ",dayofYear); }