本地时间和服务器时间

在 MetaTrader 5 平台上始终有两种类型的时间:本地(客户)时间和服务器(经纪人)时间。

本地时间对应于运行终端的计算机时间,该时间以与真实世界相同的速率持续增加。

服务器时间的流动方式则不同。服务器时间的依据按经纪人的计算机上的时间设定,然而,客户端仅与下次价格变动一同接收到关于服务器时间的信息,这种价格变动被包装进特殊结构,称为分时报价(参见关于 MqlTick的章节),并使用 事件将其传递给 MQL 程序。

因此,仅当市场上的至少一个金融工具(即“市场报价”窗口中选择的金融工具之一)的价格变动之后,终端才能知道更新的服务器时间。最后已知的服务器时间显示在该窗口的标题栏中。如果没有分时报价,则终端中的服务器时间保持静止。这在周末和节假日尤其明显,这时候所有交易所和外汇平台均关闭。

尤其是在星期日,服务器时间将很可能显示为星期五晚上。唯一的例外是 MetaTrader 5 实例,其提供持续交易金融工具(诸如加密数字货币)。然而,即使在这种情况下,在低波动期间,服务器时间也可能明显滞后于本机时间。

本节中所有函数以精确到秒( datetime 类型的时间表示精度)的精度处理时间。

为获取本地和服务器时间,MQL5 API 提供了三个函数:TimeLocalTimeCurrent 以及 TimeTradeServer。三个函数均有两个原型版本:第一个版本以 datetime 类型的值返回时间,第二个版本还通过引用接受和填充带时间分量的 MqlDateTime 结构体。

datetime TimeLocal()

datetime TimeLocal(MqlDateTime &dt)

该函数以 datetime 格式返回本地计算机时间。

务必要注意的是,时间包括夏令时(如果启用)。即 TimeLocal 等于计算机的时区的标准时间,减去修正 TimeDaylightSavings。该公式可条件性地表示如下:

TimeLocal summer() = TimeLocal winter() - TimeDaylightSavings()

其中 TimeDaylightSavings 通常等于 -3600,也就是将时钟前移 1 小时(损失 1 小时)。因此,相对于 UTC,TimeLocal 的夏季时间值大于冬季时间值(在天文时间相等的情况下)。例如,如果在冬季 TimeLocal 等于 UTC+2,则在夏季等于 UTC+3。UTC 可使用 TimeGMT 函数获得。

datetime TimeCurrent()

datetime TimeCurrent(MqlDateTime &dt)

该函数以 datetime 格式返回最后已知服务器时间。这是“市场报价”中所有金融工具列表的上次报价抵达时间。唯一例外是 EA 交易中的 OnTick 事件处理程序,其中该函数将返回已处理的分时报价的时间(即使时间更接近的分时报价已出现在“市场报价”中)。

另外,应注意,MetaTrader 5 中所有图表水平轴上的时间对应于服务器时间(历史习惯)。最后(当前,最右边)条柱包含 TimeCurrent。详情参见 图表 一节。

datetime TimeTradeServer()

datetime TimeTradeServer(MqlDateTime &dt)

该函数返回交易服务器的估计当前时间。不同于 TimeCurrent(如果没有新的报价,其结果可能不会改变),TimeTradeServer 允许你获取持续增加的服务器时间的估计值。计算是基于客户端时区与服务器时区之间的最后已知差值,该差值与当前本地时间相加。

在测试程序中,TimeTradeServer 值始终等于 TimeCurrent

该函数用法示例在 TimeCheck.mq5 脚本中提供。

主函数具有持续不间断记录的所有类型时间的无限循环,直至用户停止该脚本。

void OnStart()
{
   while(!IsStopped())
   {
      PRTF(TimeLocal());
      PRTF(TimeCurrent());
      PRTF(TimeTradeServer());
      PRTF(TimeTradeServerExact());
      Sleep(1000);
   }
}

除了标准函数外,这里还应用了自定义函数 TimeTradeServerExact

datetime TimeTradeServerExact()
{
   enum LOCATION
   {
      LOCAL
      SERVER
   };
   static datetime now[2] = {}, then[2] = {};
   static int shiftInHours = 0;
   static long shiftInSeconds = 0;
   
   // constantly detect the last 2 timestamps here and there
   then[LOCAL] = now[LOCAL];
   then[SERVER] = now[SERVER];
   now[LOCAL] = TimeLocal();
   now[SERVER] = TimeCurrent();
   
   // at the first call we don't have 2 labels yet,
   // needed to calculate the stable difference
   if(then[LOCAL] == 0 && then[SERVER] == 0return 0;
 
   // when the time course is the same on the client and on the server,
   // and the server is not "frozen" due to weekends/holidays,
   // updating difference
   if(now[LOCAL] - now[SERVER] == then[LOCAL] - then[SERVER]
   && now[SERVER] != then[SERVER])
   {
      shiftInSeconds = now[LOCAL] - now[SERVER];
      shiftInHours = (int)MathRound(shiftInSeconds / 3600.0);
      // debug print
      PrintFormat("Shift update: hours: %d; seconds: %lld"shiftInHoursshiftInSeconds);
   }
   
   // NB: The built-in function TimeTradeServer calculates like this:
   //                TimeLocal() - shiftInHours * 3600
   return (datetime)(TimeLocal() - shiftInSeconds);
}

该函数是必需的,因为内置 TimeTradeServer 函数的算法不一定适合所有情况。内置函数找到本地时间和服务器时间之间以小时表示的差值(即时区差值),然后获取服务器时间,以根据该差值对本地时间进行修正。因此,如果客户端和服务器上的分钟数和秒数不同步(这是极有可能的),则服务器时间的标准近似值将显示客户端的分钟数和秒数,而不是服务器的分钟数和秒数。

理想情况下,所有计算机的本地时钟应与全球时间同步,但在现实中会发生偏差。因此,即使二者之间只是出现很小偏差,TimeTradeServer 也无法再以最高精度重现服务器上的时间。

我们在 MQL5 中对同一函数的实现中,不将客户端与服务器时间之间的差值舍入至小时时区。相反,在计算中使用以秒数表示的准确差值。这就是为什么 TimeTradeServerExact 返回的时间其分钟数和秒数象服务器上一样显示。

下面是由该脚本生成的一个日志的示例。

TimeLocal()=2021.09.02 16:03:34 / ok
TimeCurrent()=2021.09.02 15:59:39 / ok
TimeTradeServer()=2021.09.02 16:03:34 / ok
TimeTradeServerExact()=1970.01.01 00:00:00 / ok

可以看到,客户端和服务器的时区相同,但有若干分钟的不同步(为了清晰展示)。在第一次调用中,TimeTradeServerExact 返回 0。此外,用于计算差值的数据将已到达,我们将会看到所有四个时间类型以几秒钟的间隔一致“走动”。

TimeLocal()=2021.09.02 16:03:35 / ok
TimeCurrent()=2021.09.02 15:59:40 / ok
TimeTradeServer()=2021.09.02 16:03:35 / ok
Shift update: hours: 0; seconds: 235
TimeTradeServerExact()=2021.09.02 15:59:40 / ok
TimeLocal()=2021.09.02 16:03:36 / ok
TimeCurrent()=2021.09.02 15:59:41 / ok
TimeTradeServer()=2021.09.02 16:03:36 / ok
Shift update: hours: 0; seconds: 235
TimeTradeServerExact()=2021.09.02 15:59:41 / ok
TimeLocal()=2021.09.02 16:03:37 / ok
TimeCurrent()=2021.09.02 15:59:41 / ok
TimeTradeServer()=2021.09.02 16:03:37 / ok
TimeTradeServerExact()=2021.09.02 15:59:42 / ok
TimeLocal()=2021.09.02 16:03:38 / ok
TimeCurrent()=2021.09.02 15:59:43 / ok
TimeTradeServer()=2021.09.02 16:03:38 / ok
TimeTradeServerExact()=2021.09.02 15:59:43 / ok