加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

MQL5 编程基础:时间 1

(2016-04-25 22:18:58)
分类: 工作修行

简介

MQL5 提供了大量处理时间的简单函数,学习起来不难。需要用到日期与时间的任务范围却非常小。主要的任务包括:

  • 在某给定时间点执行特定操作(图 1)。可以是每天的同一时间、一天中某个给定的时间、一周中给定的某一天,或只是于某个给定的日期与时间执行。

    https://c.mql5.com/2/5/001.gif
    图 1. 时间点

  • 在某个给定时间范围内(时段)启用或禁用特定操作。如此则可以在一天中加入一个时段(每天从一个时间点到另一个时间点),于一周的某天启用/禁用特定操作,从一周中某一天的某个给定时间到一周中另一天的某个给定时间的时间段,以及只是属于某指定日期与时间范围中的操作。

    https://c.mql5.com/2/5/002.gif
    图 2. 时间范围

实际情况中,时间的使用相当复杂。而困难,都是与时间测量的特殊性以及 EA 交易和指标操作的环境有关:

  • 图表上因缺少价格变动而缺少的柱。如时间框架较短,就尤其明显:M1、M5 甚至是 M15。较长的时间框架上也能发现缺少的柱。

  • 某些交易中心的报价还包括实际上属于周一的周日柱。

  • 周末。周一之前是周五,而不是周日。周五之后是周一,而不是周六。

  • 除周日柱外,有些交易中心还提供连续报价,其中包括整个周末。尽管价格活动与工作日相比很少,但整个周末确实是有活动的。

  • 交易服务器与本地计算机(交易者的计算机和交易终端)之间的时区差异。不同交易中心的服务器时间可能有所不同。

  • 夏令时。

本文将从一些关于时间的一般理论讲起。之后,我们继续研究处理时间的标准 MQL5 函数,同时讲解一些编程技巧,通过处理实际问题实现圆熟。

本文写得很长,所以刚刚开始研究 MQL5 的编程新手很难一次掌握。最好能够投入三天或更长的时间。

 

时间测量的特殊性

我们先暂且岔开这个话题,来谈谈天文学。众所周知,地球围绕着太阳转动,同时又绕着自身轴线转动。地轴相对其围绕着太阳的轨道有少许的倾斜。地球绕其轴在天文(天体、地球)坐标中转动完整一圈所需的时间,即所谓的天文或恒星日。

地球上的普通人(相对天文学家而言)都不太关心恒星日。更重要的是昼夜的更替。一天一夜周期所需的时间被称为太阳日。从地球北极的上方看太阳系(图 3),就能看到地球在绕着自身轴线旋转的同时,还逆时针围绕着太阳转动。因此,为了绕着地轴相对于太阳完成一整圈的旋转,地球就必须转动 360 度多一点。如此一来,太阳日就要比恒星日长一点。

https://c.mql5.com/2/5/003.gif
图 3. 地球绕其自轴旋转与围绕太阳旋转的方向(从地球北极上方看)

出于方便和准确考虑,太阳日被作为时间测量的基础。一个太阳日划分为 24 小时,一个小时又持续 60 分钟,如此等等。而恒星日则是 23 时 56 分 4 秒长。地轴相对其轨道面的略微倾斜,使得地球生灵们感受到明显的四季变化。

一年内的太阳日数量并不是个整数。实际上是稍微多一点——365 天零 6 小时。正因如此,日历才要做出周期性调整,每四年加上 1 天,也就是 2 月 29 日(闰年)。但是,这一调整又不是完全精确(稍微多加了点时间),所以有些年份,尽管是 4 的倍数,也并非闰年。以 "00" 为结尾的年份(100 的倍数)不进行日历调整。还不止如此。

如果某年同时是 100 和 400 的倍数,那么该年份即被视为闰年,且日历必须调整。1900 年是 4 和 100 的倍数,但却不是 400 的倍数,所以它不是闰年。2000 年是 4、100 和 400 的倍数,所以它是闰年。是 4 和 100 倍数的下一个年份是 2100 年,但因为它不是 400 的倍数,所以也不会是闰年。之后,本文的读者也就不会认为每一个成 4 倍数的年份都是闰年了。下一个是 100 的倍数、同时又是闰年的年份为 2400 年。

 

时区

地球绕其轴线转动产生了日夜交替。地球的不同地方,可能是白天或黑夜,同时又可能是一天中的任何时间。因为一天有 24 个小时,将地球的周长划分为 24 部分,每部分 15 度,即所谓的时区。出于方便考虑,时区的界限并不总是遵循经线,而是按照行政地域划分的界限:国界、地区等。

参照点为本初子午线,即格林威治子午线。因其穿过伦敦格林威治区而得名。格林威治标准时间向格林威治子午线两侧各延伸 7.5 度。由格林威治标准时间向东测出 12 个时区(从 +1 到 +12),由格林威治标准时间向西亦测出 12 个时区(从 -1 到 -12)。事实上,时区 -12 与 +12 之间仅有 7.5 度宽,而非 15 度。时区 -12 与 +12 分别位于 180 度子午线的左右两侧,即所谓的“国际日期变更线”。

假设格林威治现在是正午 (12:00),它是 -12 时区的 00:00,即一天的开始;而 - 24:00 即 +12 时区次日的 00:00。任何其它时间也都一样——尽管钟表上的时间完全一致,但日历中的日期却不同。实际上,时区 -12 并不使用,而是以 +12 替代。同样,时区 -11 亦被时区 +13 取代。而这很可能与经济关系的特殊性相关:比如说,处于时区 +13 的萨摩亚独立国与日本有牢固的经济关系,所以与日本时间更接近被认为更加方便。

此外,还有 -04:30 和 +05:45 之类的非常规时区。如您感觉好奇,可以到 Windows 的时间设置里查看所有时区的列表。

 

夏令时

世界上有许多国家将其时钟前拨一小时,作为一项“夏令时”惯例,目的则是更有效地利用日光及节能。全球约有 80 个国家遵守“夏令时”,其它国家则并不如此。一些大规模采用“夏令时”的国家中,也有部分地区选择退出这一做法(包括美国)。遵守“夏令时”的多是重要、经济发达的国家和地区:几乎所有的欧洲国家(包括德国、英国、瑞士)、美国(纽约、芝加哥)、澳大利亚(悉尼)以及新西兰(威灵顿)。日本不遵守“夏令时”。2011 年 3 月 27 日,俄罗斯最后一次将其时钟前拨了一个小时,但却再也没有在 10 月份调回标准时间。自那以后,俄罗斯正式退出了“夏令时”。

更改为“夏令时”的流程各国都有不同。在美国,改时是在三月份第二个星期日当地时间 02:00 进行,并在十一月的第一个星期日的 02:00 改回。而在欧洲,改为夏令时是在三月份最后一个星期日的 02:00,而改回标准时间则是在十月份最后一个星期日的 03:00。在所有的欧洲国家中,改为夏令时都是同时进行,而不是设定为当地时间。比如伦敦 02:00 时、柏林 03:00 时,要根据时区确定。当伦敦 03:00、柏林 04:00 (如此等等)时,时钟就会改回标准时间。

澳大利亚和新西兰位于南半球,当其夏天来临时,北半球迎来冬天。所以,澳大利亚是在十月的第一个星期日改为“夏令时”,并在四月份的第一个星期日改回标准时间。很难更准确地说明澳大利亚改为“夏令时”的时间,因为该国不同地区一直都没能就开始和结束日期达成一致。新西兰是在九月份的最后一个星期日的 02:00 改为“夏令时”,并在四月份的第一个星期日的 03:00 改回标准时间。

 

时间标准

上文说过,太阳日被作为时间测量的基础采用,而格林威治标准时间则被作为时间标准采用,所以其它时区都以此时间为准。格林威治标准时间通常缩写为 GMT。

然而,由于已经确定地球转动时会有些微的不均匀,所以用原子钟来测量时间和 UTC (协调世界时)已成为了新的时间标准。当前,UTC 充当着全世界的主要时间标准,是所有时区时间测量的基础,且针对“夏令时”做出了必要的调整。UTC 不受“夏令时”约束。

由于基于太阳日的 GMT 与基于原子钟的 UTC 并不完全相同,所以 UTC 和 GMT 大约每 500 天就会积累出 1 秒钟左右的时间差。为此,有时会在6 月 30 日或 12 月 31 日进行一次 1 秒的调整。

 

日期与时间格式

日期格式因国而异。比如说,俄罗斯的习惯是先写日期,然后是月份和年。日期中的数字分别用圆点隔开,比如 01.12.2012 - 2012 年 12 月 1 日。而在美国,日期的格式就是月/日/年,日期中的数字是用一个斜杠 "/" 隔开。除了圆点和斜杠 "/" 外,有些格式标准还可能采用 破折号 "-" 来分隔日期中的数字。而表示时间时,使用冒号“:”分隔小时、分钟、秒,比如 12:15:30,表示 12 时 15 分 30 秒。

指明日期和时间格式有一种简单的方法。比如说,"dd.mm.yyyy" 是指先写日期(由两位数字构成的月份中的日期;如果是从 1 到 9 号,则在前面加一个 0),然后是月份(需要由两位数字构成),最后是由四位数字构成的年份。"d-m-yy" 是指先写日期(可以是一位的数字),然后是月份(允许一位数字),最后是两位数字构成的年份,比如 1/12/12 即指 2012 年 12 月 1 日。日、月、年的值中间都由一个破折号 "-" 隔开。

时间与日期之间用一个空格隔开。时间格式用 "h" 代表小时、"m" 代表分钟而 "s" 代表秒钟,同时指定所需的位数。比如说,"hh:mi:ss" 是指首先写小时(1 到 9 要在值前面加个 0),然后是分钟(需要 2 位),最后是秒(2 位),其中的小时、分钟和秒值之间都用一个冒号隔开。

以程序员的角度来看,被视为最准确的日期和时间格式则为 "yyyy.mm.dd hh:mi:ss"。对带有利用这种标记法写下的日期的字符串进行排序时,可以很方便地按时间顺序排列。假设您每天都将信息存储到文本文件中,并保存在同一个文件夹中。如果您用该格式命名文件,会方便文件夹中文件的分类和按序排列。

现在,我们已经搞定了理论部分,开始实施吧。

 

MQL5 中的时间

在 MQL5 中,时间按照从 1970 年 1 月 1 日(所谓的 Unix 时间戳)起流逝的秒数进行测量。要存储时间,我们采用 datetime 类型变量。datetime 型变量的最小值为 0 (对应着时间戳开始的日期),而最大值为 32 535 244 799 (对应着 3000 年 12 月 31 日的 23:59:59)。

 

确定当前服务器时间

要确定当前时间,我们采用 TimeCurrent() 函数。它会返回已知最新的服务器时间:


datetime tm=TimeCurrent();
//--- output result
Alert(tm);

同样的服务器时间被用于指定图表中各柱的时间。已知最新服务器时间,是在 Market Watch (市场报价)窗口中打开的任何交易品种价格方面最后一次变化的时间。如果 Market Watch 窗口中仅有 EURUSD,则 TimeCurrent() 函数将返回 EURUSD 的最后一次价格变化。由于 Market Watch 窗口通常会显示相当数量的交易品种,所以此函数基本上都是返回当前的服务器时间。但是,由于价格在周末不会变动,所以由此函数返回的值将与实际的服务器时间有很大不同。

如您需要按某特定交易品种查找服务器时间(最后一次价格变动的时间),您可以使用 SymbolInfoInteger() 函数,该函数带有SYMBOL_TIME 标识符:


datetime tm=(datetime)SymbolInfoInteger(_Symbol,SYMBOL_TIME);
//--- output result
Alert(tm);

 

确定当前本地时间

本地时间(由用户 PC 的时钟显示)由 TimeLocal() 函数确定:


datetime tm=TimeLocal();
//--- output result
Alert(tm);

实际情况中,在编制 EA 和指标程序时,大都会采用服务器时间。本地时间在提醒和日志项中十分方便:对于用户来讲,将某个消息或条目时间戳与 PC 时钟显示的时间进行对比,可更方便地查看该消息或条目是多久以前登记的。但是,此 MetaTrader 5 终端会利用 Alert() 和 Print() 函数,自动向输出的消息和日志项添加一个时间戳。因此,只有极少见的情况下,才会产生使用 TimeLocal() 函数的需求。

 

时间输出

请注意,上述代码中的 tm 变量值,是利用 Alert() 函数输出的。也就是说,该值是以一种易读的格式显示,比如 "2012.12.05 22:31:57"。这是由于 Alert() 函数将传递给它的自变量转换成了 string 类型(使用 Print()、Comment() 函数和输出为文本与 csv 文件时也会出现这种情况)。在生成一条包含 datetime 类型变量值的文本信息的过程中,类型转换由您自己负责。如您需要得到格式化的时间,请转换为字符串类型;或者如果您需要的是数值,则先转换为 long 类型,再转换为字符串类型:


datetime tm=TimeCurrent();
//--- output result
Alert("Formatted: "+(string)tm+", in seconds: "+(string)(long)tm);

由于 long 和 ulong 类型变量的值范围涵盖了 datetime 型变量值的范围,所以亦可将其用于存储时间;但这种情况下,要输出格式化的时间,您需要将 long 类型转换为 datetime 类型,然后将其转换为 string 类型;而如果您要输出一个数值,则只要将其转换为 string 类型即可:


long tm=TimeCurrent();
//--- output result
Alert("Formatted: "+(string)(datetime)tm+", in seconds: "+(string)tm);

 

时间格式化

时间格式化曾在“MQL5 编程基础:字符串”专门讲解将各种变量转换为字符串的章节中研究过。我们在这里简单提几个要点。除了转换类型外,MQL5 还提供一个允许您在将日期和时间转换为字符串时指定其格式的函数 - TimeToString() 函数:


datetime tm=TimeCurrent();
string str1="Date and time with minutes: "+TimeToString(tm);
string str2="Date only: "+TimeToString(tm,TIME_DATE);
string str3="Time with minutes only: "+TimeToString(tm,TIME_MINUTES);
string str4="Time with seconds only: "+TimeToString(tm,TIME_SECONDS);
string str5="Date and time with seconds: "+TimeToString(tm,TIME_DATE|TIME_SECONDS);
//--- output results
Alert(str1);
Alert(str2);
Alert(str3);
Alert(str4);
Alert(str5);

TimeToString() 函数可被应用于 datetime 、long、ulong 类型变量以及其它的一些 integer 型变量,只是它们不能用于存储时间。

利用 StringFormat() 函数格式化某文本消息时,您需要注意类型转换。

  • 将时间存储为 datetime 类型变量时:

    
    datetime tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Formatted: %s, in seconds: %I64i",(string)tm,tm);
    //--- output result
    Alert(str);
    
  • 将时间存储为 long 类型变量时:

    
    long tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Formatted: %s, in seconds: %I64i",(string)(datetime)tm,tm);
    //--- output result
    Alert(str);
    
  • 或是采用 TimeToString() 函数:

    
    datetime tm=TimeCurrent();
    //--- generating a string
    string str=StringFormat("Date: %s",TimeToString(tm,TIME_DATE));
    //--- output result
    Alert(str);
    

 

将时间转换为数字。加减时间

要将某格式化的日期(字符串)转换为数字(自时间戳记开始起流逝的秒数),则要利用 StringToTime() 函数:


datetime tm=StringToTime("2012.12.05 22:31:57");
//--- output result
Alert((string)(long)tm);

作为上述代码的结果,以秒计算的输出时间将是 "1354746717",与下述日期对应 "2012.12.05 22:31:57"。

只要时间以数字形式呈现,各种相关操作就都变得简单方便了,比如说,您可以查找过去和未来的日期和时间。由于时间按秒测量,您要添加一个以秒为单位的时间段。只要知道一分钟有 60 秒、一个小时为 60 分钟或 3600 秒,计算任何时间段的时长都不会太难。

在当前时间的基础上减去或加上一个小时(3600 秒),您就会得到一个小时之前或之后的时间:


datetime tm=TimeCurrent();
datetime ltm=tm-3600;
datetime ftm=tm+3600;
//--- output result
Alert("Current: "+(string)tm+", an hour ago: "+(string)ltm+", in an hour: "+(string)ftm);

传递给 StringToTime() 函数的数据无需是完整的。而且,您还可以传递不带时间的日期,或是不带日期的时间。如您传递不带时间的日期,此函数会返回指定日期 00:00:00 时的值:


datetime tm=StringToTime("2012.12.05");
//--- output result
Alert(tm);

如您只传递时间,此函数会返回与当前日期的指定时间对应的值:


datetime tm=StringToTime("22:31:57");
//--- output result
Alert((string)tm);

也可以不带秒数传递时间。如您传递日期,唯一能够传递的时间分量就是小时了。实际情况中很少有这种需求。但如果您感到好奇,请自行随意试验。

 

日期与时间分量

要确定各日期与时间分量(年、月、日等)的值,我们利用 TimeToStruct() 函数和 MqlDateTime 结构。此结构通过引用传递给该函数。执行此函数后,此结构会被传递给它的日期的各个分量值填充:


datetime    tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
//--- output date components
Alert("Year: "        +(string)stm.year);
Alert("Month: "      +(string)stm.mon);
Alert("Day: "      +(string)stm.day);
Alert("Hour: "        +(string)stm.hour);
Alert("Minute: "     +(string)stm.min);
Alert("Second: "    +(string)stm.sec);
Alert("Day of the week: "+(string)stm.day_of_week);
Alert("Day of the year: "  +(string)stm.day_of_year);

注意:除日期分量外,此结构还包含几个附加字段:星期几 (the day_of_week field) 和该年的第几天 (day_of_year)。星期几从 0 开始计数(0 - 周日,1 - 周一,以此类推)。该年的第几天也从零开始计数。其它值遵循普遍认可的计数顺序(月份从 1 开始计数,日数也是)。

还有另一种调用 TimeCurrent() 函数的方式。MqlDateTime 类型结构通过引用传递给该函数。执行此函数后,该结构则填有当前日期的各个分量:


MqlDateTime stm;
datetime tm=TimeCurrent(stm);
//--- output date components
Alert("Year: "        +(string)stm.year);
Alert("Month: "      +(string)stm.mon);
Alert("Day: "      +(string)stm.day);
Alert("Hour: "        +(string)stm.hour);
Alert("Minute: "     +(string)stm.min);
Alert("Second: "    +(string)stm.sec);
Alert("Day of the week: "+(string)stm.day_of_week);
Alert("Day of the year: "  +(string)stm.day_of_year);

TimeLocal() 函数亦可通过这种方式调用。

 

由其分量生成日期

您也可以反过来,把 MqlDateTime 结构转换为 datetime 类型。为此,我们使用 StructToTime() 函数。

我们来确定一下刚好是一个月前的时间。各月的天数有所不同。或者 30 天,或者 31 天,而且二月份还可能是 28 或 29 天长。之前讲过的加减时间法,也因此不太合适了。所以,我们将日期分解为多个分量,将月份值减 1;而且如果月份值为 1,我们则将其设置为 12,并将年份值减 1:


datetime tm=TimeCurrent();
MqlDateTime stm;
TimeToStruct(tm,stm);
if(stm.mon==1)
  {
   stm.mon=12;
   stm.year--;
  }
else
  {
   stm.mon--;
  }
datetime ltm=StructToTime(stm);
//--- output result
Alert("Current: "+(string)tm+", a month ago: "+(string)ltm);

 

确定柱时间

开发某指标时,MetaEditor 会自动创建两个 OnCalculate() 函数版本中的一种。

版本 1:


int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return(rates_total);
  }

版本 2:


int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(rates_total);
  }

此函数的第一个版本的参数中包括 time[] 数组,而该数组中的元素则包含所有柱的时间。

使用第二个版本时,以及编制 EA 程序、安排对于从指标到其它时间框架上各柱时间的访问时,我们采用 CopyTime() 函数。此函数存在三种版本。在所有版本中,前两个函数参数都是定义交易品种,以及确定柱时间所本的图表时间框架。最后一个参数则定义一个数组,该数组根据所用的函数版本,存储返回值以及两个中间参数。

CopyTime - 版本 1 您需要指定柱索引及待复制元素的数量:


//--- variables for function parameters
int start = 0; // bar index
int count = 1; // number of bars
datetime tm[]; // array storing the returned bar time
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert(tm[0]);

此例所示为复制 D1 时间框架上最后一个柱的时间的实施。我们使用的函数版本,取决于传递给它的参数的类型。上例介绍了 int 类型变量的使用,这就意味着,我们需要按照指定柱数的柱编号获取时间。

采用 CopyTime() 函数时,各柱是从右到左、从零开始计数。针对所获值(最后一个参数)采用的一个动态数组,由 CopyTime() 函数自己即可缩放为所需大小。您也可以采用一个静态数组,但这种情况下,数组大小必须与所需元素数量(第 4 个参数的值)严格对应。

一次性由多个柱取得时间时,重要的是了解返回数组中的元素顺序。尽管图表中的柱是从指定柱开始从右到左计数,但数组元素却是从左到右排列的:


//--- variables for function parameters
int start = 0; // bar index
int count = 2; // number of bars
datetime tm[]; // array storing the returned bar time
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert("Today: "+(string)tm[1]+", yesterday: "+(string)tm[0]);

作为此代码的结果,昨日柱的时间会被存储到 tm 数组的 0 元素,而第 1 个元素中则将包含今日柱的时间。

某些情况下,让数组中的时间按照与图表中柱计数一样顺序来排列,似乎更方便一些。而这里的 ArraySetAsSeries() 函数可以达到这种效果:


//--- variables for function parameters
int start = 0; // bar index
int count = 2; // number of bars
datetime tm[]; // array storing the returned bar time
ArraySetAsSeries(tm,true); // specify that the array will be arranged in reverse order
//--- copy time 
CopyTime(_Symbol,PERIOD_D1,start,count,tm);
//--- output result
Alert("Today: "+(string)tm[0]+", yesterday: "+(string)tm[1]);

现在,今日柱的时间在索引为 0 的元素中,而昨日柱的时间则在索引为 1 的元素中。

CopyTime - 版本 2。这里,调用 CopyTime() 函数时,我们需要指定复制开始的柱时间,以及待复制的柱数。此版本将适用于确定某个包含较低时间框架柱的较高时间框架的时间:


//--- get the time of the last bar on M5
int m5_start=0; 
int m5_count=1;
datetime m5_tm[];
CopyTime(_Symbol,PERIOD_M5,m5_start,m5_count,m5_tm);
//--- determine the bar time on H1 that contains the bar on M5
int h1_count=1;
datetime h1_tm[];
CopyTime(_Symbol,PERIOD_H1,m5_tm[0],h1_count,h1_tm);
//--- output result
Alert("The bar on М5 with the time "+(string)m5_tm[0]+" is contained in the bar on H1 with the time "+(string)h1_tm[0]);

如果您需要确定某较低时间框架柱的时间,作为某较高时间框架柱的起点,则情况更加复杂。带有与较高时间框架柱相同时间的柱,在较低时间框架可能就会丢失。这种情况下,我们就会获取较高时间框架前一柱中包含的较低时间框架的最后一个柱的时间。所以,我们需要确定较低时间框架上柱的数量,并获取下一个柱的时间。

下面就是上述内容的一种待用型函数形式的实施:


bool LowerTFFirstBarTime(string aSymbol,
                         ENUM_TIMEFRAMES aLowerTF,
                         datetime aUpperTFBarTime,
                         datetime& aLowerTFFirstBarTime)
  {
   datetime tm[];
//--- determine the bar time on a lower time frame corresponding to the bar time on a higher time frame 
   if(CopyTime(aSymbol,aLowerTF,aUpperTFBarTime,1,tm)==-1)
     {
      return(false);
     }
   if(tm[0]//--- we got the time of the preceding bar
      datetime tm2[];
      //--- determine the time of the last bar on a lower time frame
      if(CopyTime(aSymbol,aLowerTF,0,1,tm2)==-1)
        {
         return(false);
        }
      if(tm[0]0])
        {
         //--- there is a bar following the bar of a lower time frame  that precedes the occurrence of the bar on a higher time frame
         int start=Bars(aSymbol,aLowerTF,tm[0],tm2[0])-2;
         //--- the Bars() function returns the number of bars from the bar with time tm[0] to
         //--- the bar with time tm2[0]; since we need to determine the index of the bar following 
         //--- the bar with time tm2[2], subtract 2
         if(CopyTime(aSymbol,aLowerTF,start,1,tm)==-1)
           {
            return(false);
           }
        }
      else
        {
         //--- there is no bar of a lower time frame contained in the bar on a higher time frame
         aLowerTFFirstBarTime=0;
         return(true);
        }
     }
//--- assign the obtained value to the variable 
   aLowerTFFirstBarTime=tm[0];
   return(true);
  }

函数参数:

  • aSymbol - 交易品种;
  • aLowerTF - 较低时间框架;
  • aUpperTFBarTime - 某较高时间框架上的柱时间;
  • aLowerTFFirstBarTime - 某较低时间框架的返回值。

在整个代码中,该函数都检查是否已成功调用 CopyTime() 函数;如出错,则返回 false。柱时间是利用 aLowerTFFirstBarTime 参数通过引用返回。

此函数的使用示例如下:


//--- time of the bar on the higher time frame H1
   datetime utftm=StringToTime("2012.12.10 15:00");
//--- variable for the returned value
   datetime val;
//--- function call
   if(LowerTFFirstBarTime(_Symbol,PERIOD_M5,utftm,val))
     {
      //--- output result in case of successful function operation
      Alert("val = "+(string)val);
     }
   else
     {
      //--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
      Alert("Error copying the time");
      return;
     }

如果此函数收到某较高时间框架某个不存在柱的时间,就会出现较高时间框架某个柱中包含的某个较低时间框架上没有柱的情况。这种情况下,此函数返回 true,且时间值 0 被写入 aLowerTFFirstBarTime 变量。如果某较高时间框架的某个柱存在,则每个较低时间框架中始终都至少有一个对应柱。

查找某较高时间框架某个柱中包含的某较低时间框架最后一个柱的时间,稍微简单一些。我们计算某较高时间框架下一个柱的时间,再用所得值来确定某较低时间框架对应柱的时间。如果作为得到的时间与计算得出的较高时间框架时间相等,则我们需要确定较低时间框架前一柱的时间。如果作为得到的时间较小,则我们得到了正确的时间。

下面就是上述内容的一种待用型函数形式的实施:


bool LowerTFLastBarTime(string aSymbol,
                        ENUM_TIMEFRAMES aUpperTF,
                        ENUM_TIMEFRAMES aLowerTF,
                        datetime aUpperTFBarTime,
                        datetime& aLowerTFFirstBarTime)
  {
//--- time of the next bar on a higher time frame
   datetime NextBarTime=aUpperTFBarTime+PeriodSeconds(aUpperTF);
   datetime tm[];
   if(CopyTime(aSymbol,aLowerTF,NextBarTime,1,tm)==-1)
     {
      return(false);
     }
   if(tm[0]==NextBarTime)
     {
      //--- There is a bar on a lower time frame corresponding to the time of the next bar on a higher time frame.
      //--- Determine the time of the last bar on a lower time frame
      datetime tm2[];
      if(CopyTime(aSymbol,aLowerTF,0,1,tm2)==-1)
        {
         return(false);
        }
      //--- determine the preceding bar index on a lower time frame
      int start=Bars(aSymbol,aLowerTF,tm[0],tm2[0]);
      //--- determine the time of this bar
      if(CopyTime(aSymbol,aLowerTF,start,1,tm)==-1)
        {
         return(false);
        }
     }
//--- assign the obtain value to the variable 
   aLowerTFFirstBarTime=tm[0];
   return(true);
  }

函数参数:

  • aSymbol - 交易品种;
  • aUpperTF - 较高时间框架;
  • aLowerTF - 较低时间框架;
  • aUpperTFBarTime - 某较高时间框架上的柱时间;
  • aLowerTFFirstBarTime - 某较低时间框架的返回值。

在整个代码中,该函数都检查是否已成功调用 CopyTime() 函数;如出错,则返回 false。柱时间是利用 aLowerTFFirstBarTime 参数通过引用返回。

此函数的使用示例如下:


//--- time of the bar on the higher time frame H1
datetime utftm=StringToTime("2012.12.10 15:00");
//--- variable for the returned value
datetime val;
//--- function call
if(LowerTFLastBarTime(_Symbol,PERIOD_H1,PERIOD_M5,utftm,val))
  {
//--- output result in case of successful function operation
   Alert("val = "+(string)val);
  }
else
  {
//--- in case of an error, terminate the operation of the function from which the LowerTFFirstBarTime() function is called
   Alert("Error copying the time");
   return;
  }

注意! 假设传递给此函数的时间就是某较高时间框架的精确时间。如果此柱的精确时间未知,我们只知道该柱或某个较高时间框架柱中包含的某个较低时间框架柱某特定价格变动的时间,那么就需要时间标准化。MetaTrader 5 终端中使用的时间框架将日分解为一个整数数量的柱。我们确定自时间戳记开始起的柱数,然后再乘以以秒数计的柱长度:


datetime BarTimeNormalize(datetime aTime,ENUM_TIMEFRAMES aTimeFrame)
  {
   int BarLength=PeriodSeconds(aTimeFrame);
   return(BarLength*(aTime/BarLength));
  }

函数参数:

  • aTime - 时间;
  • aTimeFrame - 时间框架。

此函数的使用示例如下:


//--- the time to be normalized
datetime tm=StringToTime("2012.12.10 15:25");
//--- function call
datetime tm1=BarTimeNormalize(tm,PERIOD_H1);
//--- output result
Alert(tm1);

然而,这不只是时间标准化的方法,也是通过较低时间框架的时间确定较高时间框架时间的另一种方法。

CopyTime - 版本 3。这种情况下,如果调用 CopyTime() 函数,我们则会指定需要由其复制的与柱相关的时间范围。此方法允许我们轻松地获取某较高时间框架柱包含的某较低时间框架的所有柱。


//--- time of the bar start on H1
datetime TimeStart=StringToTime("2012.12.10 15:00");
//--- the estimated time of the last bar on
//--- M5
datetime TimeStop=TimeStart+PeriodSeconds(PERIOD_H1)-PeriodSeconds(PERIOD_M5);
//--- copy time
datetime tm[];
CopyTime(_Symbol,PERIOD_M5,TimeStart,TimeStop,tm);
//--- output result 
Alert("Bars copied: "+(string)ArraySize(tm)+", first bar: "+(string)tm[0]+", last bar: "+(string)tm[ArraySize(tm)-1]);

 


0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有