MQL5 编程基础:时间·综合运用
(3/3)· 柱缺失、周日柱与跨时区如何让时间判断翻车,这篇一次讲透
- 把时间变成可读字符串的几种写法
- 用秒数思维摆弄 MT5 时间
- 拆解时间分量的两种取法
- 倒推一个月的日期分量处理
- 跨周期取柱时间的正确姿势
- 跨周期定位小周期首根K线
- 跨周期定位小周期首末根
- 把任意时间对齐到周期起点
- 用整除截尾拿日起点与日内秒数
- 周起点与周内流逝时间的精确算法
- 从任意基准日倒推周序号
- 用 H1 数组搭一个周末测试沙盘
- 支点指标里的新日判定逻辑
- 把交易时段拆成可优化的秒数边界
- 抓日内的那个交叉秒
- 用户定义日的支点切分逻辑
- 把交易限定在指定星期几
- 圈出跨天的周交易窗口
- 用对时间函数才能拿到真服务器时间
- 闰年与月长计算的轻量封装
- 回测里时间函数到底信哪个
- MQL5 时间函数按用途怎么分
- 画得少,看得清
「把时间变成可读字符串的几种写法」
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 的写法。 外汇与贵金属行情受杠杆和跳空影响,高风险,这段代码仅用于熟悉时间格式化逻辑,不涉及任何交易信号。
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 试两行就知道边界在哪。 外汇和贵金属行情受时段影响明显,用这种秒级偏移算会话时段、重叠窗口时,务必先认清杠杆和高波动下的滑点风险,数值只是参考。
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() 的本地时区输出。
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 会打出当前时间与恰好一个月前的两个时间戳,读者可直接复制验证。 外汇与贵金属行情受月度数据周期影响明显,这类时间锚点仅用于回测或条件触发,实际杠杆交易仍属高风险,信号失效概率不低。
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 用上面片段自测。
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 根索引,最终拿到大周期棒开启后小周期的第一根棒时间。外汇与贵金属跨周期策略受跳空与流动性影响,信号失效概率不低,需实盘验证。
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 输出对照。
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 符合预期。
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);
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 能抓新周第一根:
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 重新计;外汇与贵金属波动受周界切换影响,用这类计数做周级别过滤时须留意跳空风险。
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 用历史数据自测。
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 指标不只是测函数用,加载到图表能直观看到时段背景,盯盘时一眼分辨现在是不是允许交易区间。
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 用任意周期验证:外汇与贵金属杠杆高,信号只提示时间点交叉,不预示方向。
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==0 | stm.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 确认新日开端。外汇与贵金属周末流动性断裂,用此类自定义日界重算支点时须警惕跳空导致的虚假信号,属高风险操作。
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 后能直观看到过滤效果。外汇与贵金属杠杆高,过滤交易日不消除隔夜跳空风险,参数调整前先在历史数据回测验证。
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 用的判定函数,逐行拆完你就明白边界怎么取的。
<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) { <span class="comment">class=class="str">"cmt">//--- session start time since the week start</span> <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> <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> <span class="keyword">class="type">long</span> TimeCur=SecondsFromWeekStart(aTimeCur,<span class="keyword">false</span>); <span class="keyword">if</span>(StopTime<StartTime) { <span class="comment">class=class="str">"cmt">//--- passing the turn of the week</span> <span class="keyword">if</span>(TimeCur>=StartTime || TimeCur<StopTime) { <span class="keyword">class="kw">return</span>(<span class="keyword">true</span>); } } <span class="keyword">else</span> { <span class="comment">class=class="str">"cmt">//--- within one week</span> <span class="keyword">if</span>(TimeCur>=StartTime && TimeCur<StopTime) { <span class="keyword">class="kw">return</span>(<span class="keyword">true</span>); } } <span class="keyword">class="kw">return</span>(<span class="keyword">false</span>); }
「用对时间函数才能拿到真服务器时间」
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); —— 校验两者是否相等,不等说明本机时钟或时区设置有异常
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 个自然月涨跌汇总」类指标时,就不用手填月份天数表了。外汇与贵金属波动受日历事件影响明显,这类基础函数虽小,但能降低统计口径出错的概率。
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,看哪段行情值得盯。