MQL5时间处理从入门到实战
掌握datetime、时区与跨周期时间函数
为什么时间处理在MQL5中容易踩坑
MQL5提供了大量处理时间的简单函数,但实际应用中困难重重。主要难点并不在于函数本身,而在于时间测量的特殊性与EA、指标运行环境。比如短周期图表上可能因缺少价格变动而缺柱;部分交易中心报价包含周日柱甚至整个周末连续报价;周末无报价时周一直接接续周五;不同交易服务器时区不同;还有夏令时调整。这些都会导致单纯比较时钟时间出错。
常见任务有两类:一是在某给定时间点执行操作,如每天同一时间、一周某天或指定日期;二是在某时间范围内启用禁用操作,如日内时段、跨天时段或指定日期范围。理解底层机制才能写出健壮代码。
时间测量的理论基础
地球绕轴自转产生恒星日(23时56分4秒),相对太阳自转产生太阳日(24小时),后者为民用时间基础。一年约365.25天,故每四年闰一日,但百年不闰、四百年再闰。1900非闰、2000闰、2100不闰、2400闰。
时区以本初子午线(格林威治)为0区,向东向西各12区,日期变更线在±12区之间。部分国家采用半时区如+05:45。夏令时约80国遵守,欧美多在3月改、10/11月回,南半球相反;俄罗斯2011后永久不退。UTC基于原子钟,不受夏令时,与GMT偶有1秒差。
日期格式各国不同:俄式dd.mm.yyyy,美式mm/dd/yy。程序最宜用yyyy.mm.dd hh:mi:ss,便于字符串排序。MQL5中时间自1970.01.01 Unix时间戳起按秒计,类型为datetime,范围0至32535244799(3000年底)。
MQL5核心时间函数与当前时间获取
确定当前服务器时间用TimeCurrent(),返回Market Watch中任一品种最后价格变动时间,基本等同服务器时间,但周末无变动时会停于周五。若需指定品种最后变动时间,用SymbolInfoInteger(_Symbol,SYMBOL_TIME)。本地时间用TimeLocal(),多用于提醒与日志,但终端Alert/Print会自动加时间戳,故较少直接用。
datetime tm=TimeCurrent(); Alert(tm);
datetime tm=(datetime)SymbolInfoInteger(_Symbol,SYMBOL_TIME); Alert(tm);
datetime tm=TimeLocal(); Alert(tm);
输出时Alert等函数自动将datetime转为可读串如2012.12.05 22:31:57。若自行拼字符串,需显式转换:(string)tm得格式化, (long)tm得秒数。
datetime tm=TimeCurrent();
Alert("Formatted: "+(string)tm+", in seconds: "+(string)(long)tm);
时间格式化与字符串互转
TimeToString()可指定TIME_DATE、TIME_MINUTES、TIME_SECONDS等标志格式化。StringToTime()将字符串转为秒数,支持不完整日期如仅日期或仅时间。加减时间即加减秒数,如±3600为一小时。
datetime tm=StringToTime("2012.12.05 22:31:57");
Alert((string)(long)tm);
datetime tm=TimeCurrent();
datetime ltm=tm-3600;
datetime ftm=tm+3600;
Alert("Current: "+(string)tm+", an hour ago: "+(string)ltm+", in an hour: "+(string)ftm);
日期分量拆解与重构
TimeToStruct()配合MqlDateTime结构取年月中日时分秒、星期(0=周日)和年中天(0基)。TimeCurrent(stm)亦可填结构。StructToTime()反向由分量生成时间,适合计算上月同日等。
datetime tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
Alert("Year: "+(string)stm.year);
Alert("Day of week: "+(string)stm.day_of_week);
datetime tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
if(stm.mon==1){stm.mon=12;stm.year--;}else{stm.mon--;}
datetime ltm=StructToTime(stm);
Alert("a month ago: "+(string)ltm);
获取K线柱时间与跨周期对齐
指标OnCalculate第一版直接收time[]数组。EA或其它周期需用CopyTime(),有三版:按索引+数量、按起始时间+数量、按时间范围。柱从右零计数,数组默认左旧右新,ArraySetAsSeries可反排。跨周期取低周期首柱需注意高周期柱时间在低周期可能缺失,文附LowerTFFirstBarTime等函数处理。
int start=0; int count=2; datetime tm[];
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
Alert("Today: "+(string)tm[1]+", yesterday: "+(string)tm[0]);
datetime BarTimeNormalize(datetime aTime,ENUM_TIMEFRAMES aTF){
int BarLength=PeriodSeconds(aTF);
return(BarLength*(aTime/BarLength));
}
日始、周始与流逝时间计算
日始= (tm/86400)*86400(整数除截尾)。流逝秒= tm%86400。周始稍复杂,因时间戳始于周四且各国周起始不同,需加3或4日修正再整除604800后减回。文中WeekStartTime、SecondsFromWeekStart已封装。自年/月始周数可用YearStartTime/MonthStartTime配合通用函数。
datetime tm=TimeCurrent();
tm=(tm/86400)*86400;
Alert("Day start: "+(string)tm);
long WeekStartTime(datetime aTime,bool aStartsOnMonday=false){
long tmp=aTime; long C=aStartsOnMonday?259200:345600;
tmp+=C; tmp=(tmp/604800)*604800; tmp-=C; return(tmp);
}
实战:时段、时间点与支点指标
日内时段TimeSession将起止时分转秒比当前日秒;跨午夜时结束小于开始则双判。时间点TimeCross检查指定时分是否被跨过,防报价间隙漏触发。支点指标Pivot1用NewDay判日始;Pivot2用NewCustomDay支持用户定义日并忽略周末柱。文中附Session/TimePoint/TradeWeekDays/SessionWeek等指标。
bool TimeSession(int sh,int sm,int eh,int em,datetime aT){
int S=3600*sh+60*sm, E=3600*eh+60*em; aT=aT%86400;
if(E<S){ if(aT>=S||aT<E) return(true);}
else{ if(aT>=S&&aT<E) return(true);}
return(false);
}
bool TimeCross(int h,int m,datetime c,datetime p){
datetime pt=h*3600+m*60; c=c%86400; p=p%86400;
if(c<p){ if(c>=pt||p<pt) return(true);}
else{ if(c>=pt&&p<pt) return(true);}
return(false);
}
依赖本机设置的函数与回测特殊性
TimeTradeServer()周末也返真服务器时间;TimeGMT()依本机时钟算UTC;TimeDaylightSavings()返夏令修正;TimeGMTOffset()返时区秒差。本地时间=TimeGMT()-Offset()-DST。策略测试器中TimeCurrent与报价流同步,无真实时区/DST模拟,测跨时区EA需注意。
datetime tm=TimeTradeServer(); Alert(tm); int val=TimeGMTOffset(); Alert(val);