组织数据存储

这一章节是讲述获得、存储和索取价格数据的(时间序列)。

从交易服务器中获得数据

在MT5客户端的价格数据执行之前,必须先收到然后经过处理,为了接收数据,要确定MT5交易服务器已经确定连接,数据根据终端要求以微小字节形式接收。

服务器的机械装置是参考数据的,并不依靠最初的要求-使用者通过操作图表或者MQL5语言程序方式。

储存媒介数据

从服务器重接收的数据自动打开或存储在HCC媒介信息中,每个象征数据都以分类文件夹 terminal_directory\bases\server_name\history\symbol_name的形式记录。例如,关于EURUSD的数据从MetaQuotes-Demo服务器中接收,但会存储在terminal_directory\bases\MetaQuotes-Demo\history\EURUSD\中。

数据以.hcc的扩展名方式书写到文件中,每个文件存储分钟字节型数据一年,例如,2009年的数据文件包含2009年所有的分钟字节型数据,这些文件为所有时间架框提供价格数据的,他们并不能直接接入。

在媒介数据外获得必要的时间架构

媒介HCC文件以源数据类型使用,它在HC板式中以建立价格数据的要求来使用的,HC板式的数据是事件序列为快速接入的最大化准备,他们根据图表或MQL5程序的要求来构建,数据的成交量不能超过图表字节的最大值,数据长期存储使用hc扩展文件。

为了节约资源,在时间架构里的数据如果需要的话以ram格式存储,如果不能长时间调用,会从RAM中释放并保存到文件夹中,对于每个时间序列来说,数据的存贮无论是否已经有为另一时间架构准备的备用数据,形式规则和接入数据对于所有时间架构来说都是一样的。那就意味着,尽管在HCC中存储了一分钟数据,HCC数据的可行性并不意味着在同成交量中M1时间架构的可行性。

从服务器中接收新数据会调用所有时间架构HC板式的价格数据自动更新,也能导致所有指标的重新计算,那样可以暗中输出数据来计算。

参数“图表中的最大字节”

“图表中的最大字节”常量限制可行性图表、指标和MQL5程序中HC板式的字节数,对于所有可行性时间序列和服务器来说是有效的,首先,为了节约电脑资源。

当建立有关该常量一个较大值时,需要记住的是,如果深度价格数据对于小的时间架构来说是可行的,用来存储时间序列和指标缓冲区的内存可以是上百个字节,并且达到RAM对客户端程序的限制(2Gb、MS视窗32字节)。

在客户端重启之后,“图表中最大字节”的改变开始生效,该参量的转变原因并不是递交给服务器额外的数据,也不是形成额外的时序列字节,额外价格数据从服务器上要求,时序列的更新于新的限制有关,以防无数据图表卷轴或者数据被MQL5程序调用。

服务器要求的数据成交量与时间架构的“图表中最大字节”的字节要求数量有关,该参数限制并不严格,多数情况下,可利用的字节数字比当前参量值还多。

数据有效性

如果这些数据在MQL5程序中以图表或者使用的形式出现,HCC版式中的数据或者为使用HC版式而准备的数据并不能完全可用。

当MQL5程序访问价格数据或者指示值时,他们的可行性是一个确切的时间值或者有保留地从一个确定的时间点开始,事实上,为了节约资源,必要数据的复制并不全部存储在MT5中,只是给出了终端数据库。

所有时间架构的价格历史都从普通的HCC版本数据建立,然后从服务器更新系统更新,重新计算指标,由于这种数据接入能够关闭,甚至这些数据在之前都能使用。

终端数据和服务器数据的同步性 #

自从MQL5程序可以以符号和时间架构的形式调用数据信息,就存在在终端必要的时序列数据没有建立的可能性,或者必要的价格数据没有与交易服务器同步化,因此很难预测潜在时间。

使用潜在循环计算式并不是最好的解决方法,这一事件的唯一例外是脚本,因为有事件要处理,他们没有交替运算方法的选择。对于此种算法的常规指标,和其他潜在循环动作并没有被推荐使用,因为导致所有指标的计算终止,和其他价格数据操作失败。

对于EA交易和指标来说,使用 相等模式 来处理最好不过,在处理OnTick() 或 OnCalculate() 事件时,必要时序列的接受数据是失败的,此时需要离开事件处理器,在下一个处理器调用的时候适当接入。

脚本添加历史的范例

示例是,从交易服务器中接收脚本处理预选对象的请求,脚本已经为运行预选对象图表做了准备;时间架构不能操作,因为如上所述,价格数据是从交易服务器中以一分钟数据形式大包而来的,当时任何预选时序列都在构造中。

记录下所有数据脚本的操作来当成一个分离函数CheckLoadHistory(symbol, timeframe, start_date):

int CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period,datetime start_date)
  {
  }

CheckLoadHistory() 函数是一种能被任何操作调用的通用函数(EA交易、脚本或指标);此前它需要三种输入参量:符号名称,周期和开始日期来表明你所需的价格历史开始运行。

在调用缺失的历史之前,在函数代码中插入格,首先,应该确保符号名称和周期值是正确的:

   if(symbol==NULL || symbol==""symbol=Symbol();
   if(period==PERIOD_CURRENT)     period=Period();

确定该符号在市场观测窗口的可见性,如下,当向交易服务器发出请求时,符号的历史就可用,如果在市场观测中没有这样的符号,使用 SymbolSelect() 函数可以添加。

   if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
     {
      if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOLreturn(-1);
      SymbolSelect(symbol,true);
     }

现在我们能够收到可用历史交易的起始时间,也许,起始输入函数值,传递到CheckLoadHistory(),是可行性历史,而后交易服务器的请求就不需要了。为了获得第一时间的符号序列,可用使用 SeriesInfoInteger() 函数的 SERIES_FIRSTDATE 修饰语。

   SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date);
   if(first_date>0 && first_date<=start_date) return(1);

下一项重要的检测就是从函数调用检验程序类型,注释,与指标同时发送时序列的更新请求,该种调用更新是不符合要求的。指标中与符号序列相同的要求数据受历史数据约束,因此,固定发生的概率偏高,检验该项目可以使用MQL5InfoInteger()函数中的 MQL5_PROGRAM_TYPE 修饰语。

   if(MQL5InfoInteger(MQL5_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
      return(-4);

如果所有的检测都成功通过,最后要涉及的就是交易服务器,首先找到起始日期,在HCC版式下的分钟日期就可行。使用功能函数SeriesInfoInteger()中SERIES_TERMINAL_FIRSTDATE 修饰语可以调用该值,也可与start_date 常数值做比较。

   if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
     {
      //--- 有建立时间序列的加载数据
      if(first_date>0)
        {
         //--- 强制创建时间序列
         CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times);
         //--- 检测日期
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(2);
        }
     }

如果所有的检测都执行通过,却仍然有 CheckLoadHistory() 函数,意味着有必要要求交易服务器调回丢失价格代码,首先,返回“图表中最大字节”值,使用TerminalInfoInteger() 函数:

  int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);

需要阻止额外调回数据,在服务器中找回字符历史的第一个日期(忽略周期),使用已知函数SeriesInfoInteger() 的修饰语 SERIES_SERVER_FIRSTDATE

   datetime first_server_date=0;
   while(!SeriesInfoInteger(symbol,PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date) && !IsStopped())
      Sleep(5);

该请求是异步操作,函数在回收时调用时间晚了5毫秒,直到first_server_date 变量接收到一个值,或者该执行周期被用户终止 (IsStopped() 在此情形下会返回真值),在起始日期指明正确值,就从向交易服务器要求价格数据时开始。

   if(first_server_date>start_date) start_date=first_server_date;
   if(first_date>0 && first_date<first_server_date)
      Print("Warning: first server date ",first_server_date," for ",
symbol," does not match to first series date ",first_date);

如果服务器的起始日期是 first_server_date 比HCC版式的符号 first_date 起始日期早,在分类账户中,类似进入将会被输出。

现在向交易服务器发送一个丢失价格数据的请求,该请求以循环的模式开始填满全部函数。

   while(!IsStopped())
     {
      //1. 等候如同HHC一样重新建立时间序列与中间历史记录的同步化
      //2. 接收这个时间序列的当前n个柱数
      //    如果柱大于图表中的最大柱,可以退出,工作结束
      //3. 获得重建时间序列开始日期的初始日期并且与开始日期相比较
      //    如果初始日期低于开始日期,则退出,工作结束
      //4. 从服务器请求历史记录的新部分-从最近有效标有'bars'的柱中启动的100柱 
     }

使用已知手段可以实行前三种方法。

   while(!IsStopped())
     {
      //--- 1.等待直到时间序列重建程序结束
      while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped())
         Sleep(5);
      //--- 2.请求多少柱
      int bars=Bars(symbol,period);
      if(bars>0)
        {
         //--- 超过图表中所画的柱,退出
         if(bars>=max_bars) return(-2); 
         //--- 3. 返回时间序列的当前开始日期
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            // 开始日期早于要求日期,任务完成
            if(first_date>0 && first_date<=start_date) return(0);
        }
      //4. 从服务器请求历史记录的新部分-从最近有效标有'bars'的柱中启动的100柱
     }

剩下第四步-索取历史,不能直接查阅服务器,如果在HCC板式中的历史不足,任何Copy-function的启动要求都发往服务器,first_date 变量的第一个起始日期是简明的,也有对请求等级的衡量标准,最快的方法是使用 CopyTime() 函数。

当调用函数从时序列复制数据时,应该标记起始参量(字节的数字,复制价格数据的起始),这些都应该在服务器历史中找到,如果只有100字节,从500个指标中复制300字节是无效的,该请求会被视为错误出现不能执行,例如,交易服务器中没有额外历史记录加载。

这就是我们从起始字节指标复制100字节的原因,它会从交易服务器中顺利下载丢失的历史数据,事实上,下载的往往比要求的100字节多一点,服务器也会发送大一些的历史数据。

   int copied=CopyTime(symbol,period,bars,100,times);

复制操作之后,需要分析复制元素号码,如果尝试失败,copied值等于空值并且计数值fail_cnt增加到1,在100次尝试失败后,函数操作就会停止。

int fail_cnt=0;
...
   int copied=CopyTime(symbol,period,bars,100,times);
   if(copied>0)
     {
      //--- 检测数据
      if(times[0]<=start_date)  return(0);  // 复制值更小,准备完毕
      if(bars+copied>=max_bars) return(-2); // 柱多于图表中所画的柱,准备完毕
      fail_cnt=0;
     }
   else
     {
      //--- 成功的话失败尝试少于100
      fail_cnt++;
      if(fail_cnt>=100) return(-5);
      Sleep(10);
     }
 

因此,不仅当前情况的恰当处理在函数中可以实施,而且还能返回结束代码,在调用CheckLoadHistory() 函数后可以直接解决获得额外信息,例如,这种方法:

   int res=CheckLoadHistory(InpLoadedSymbol,InpLoadedPeriod,InpStartDate);
   switch(res)
     {
      case -1 : Print("Unknown symbol ",InpLoadedSymbol);                     break;
      case -2 : Print("More requested bars than can be drawn in the chart"); break;
      case -3 : Print("Execution stopped by user");                          break;
      case -4 : Print("Indicator mustn't load its own data");                break;
      case -5 : Print("Loading failed");                                     break;
      case  0 : Print("All data loaded");                                    break;
      case  1 : Print("Already available data in timeseries are enough");    break;
      case  2 : Print("Timeseries is built from available terminal data");   break;
      default : Print("Execution result undefined");
     }

函数的全部代码能在脚本示例中找到,显示出正确的接入操作与请求结果正在处理。

代码:

//+------------------------------------------------------------------+
//|                                              TestLoadHistory.mq5 |
//|                         Copyright 2000-2024, MetaQuotes Ltd. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.02"
#property script_show_inputs
//--- 输入参量
input string          InpLoadedSymbol="NZDUSD";   // 被加载的交易品种
input ENUM_TIMEFRAMES InpLoadedPeriod=PERIOD_H1;  // 被加载的周期
input datetime        InpStartDate=D'2006.01.01'; // 开始日期
//+------------------------------------------------------------------+
//| 脚本程序启动函数                                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
   Print("Start load",InpLoadedSymbol+","+GetPeriodName(InpLoadedPeriod),"from",InpStartDate);
//---
   int res=CheckLoadHistory(InpLoadedSymbol,InpLoadedPeriod,InpStartDate);
   switch(res)
     {
      case -1 : Print("Unknown symbol ",InpLoadedSymbol);             break;
      case -2 : Print("Requested bars more than max bars in chart"); break;
      case -3 : Print("Program was stopped");                        break;
      case -4 : Print("Indicator shouldn't load its own data");      break;
      case -5 : Print("Load failed");                                break;
      case  0 : Print("Loaded OK");                                  break;
      case  1 : Print("Loaded previously");                          break;
      case  2 : Print("Loaded previously and built");                break;
      default : Print("Unknown result");
     }
//---
   datetime first_date;
   SeriesInfoInteger(InpLoadedSymbol,InpLoadedPeriod,SERIES_FIRSTDATE,first_date);
   int bars=Bars(InpLoadedSymbol,InpLoadedPeriod);
   Print("First date ",first_date," - ",bars," bars");
//---
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period,datetime start_date)
  {
   datetime first_date=0;
   datetime times[100];
//--- 检测交易品种 & 周期
   if(symbol==NULL || symbol==""symbol=Symbol();
   if(period==PERIOD_CURRENT)     period=Period();
//--- 检测市场报价是否选定了交易品种
   if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
     {
      if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOLreturn(-1);
      SymbolSelect(symbol,true);
     }
//--- 检测数据是否存在
   SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date);
   if(first_date>0 && first_date<=start_date) return(1);
//--- 如果是指标的话不需要加载自己的数据
   if(MQL5InfoInteger(MQL5_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
      return(-4);
//--- 第二尝试
   if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
     {
      //--- 有建立时间序列加载的数据
      if(first_date>0)
        {
         //--- 强制创建时间序列
         CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times);
         //--- 检测日期
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(2);
        }
     }
//--- 图表中来自程序端选项的最大柱
   int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);
//--- 加载交易品种历史记录信息
   datetime first_server_date=0;
   while(!SeriesInfoInteger(symbol,PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date) && !IsStopped())
      Sleep(5);
//--- 为加载确定开始日期
   if(first_server_date>start_date) start_date=first_server_date;
   if(first_date>0 && first_date<first_server_date)
      Print("Warning: first server date ",first_server_date," for ",symbol,
            " does not match to first series date ",first_date);
//--- 逐步地加载数据
   int fail_cnt=0;
   while(!IsStopped())
     {
      //--- 等待建立时间序列
      while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped())
         Sleep(5);
      //--- 要求建成的柱
      int bars=Bars(symbol,period);
      if(bars>0)
        {
         if(bars>=max_bars) return(-2);
         //--- 请求初始日期
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(0);
        }
      //--- 复制部分强制数据加载
      int copied=CopyTime(symbol,period,bars,100,times);
      if(copied>0)
        {
         //--- 数据检测
         if(times[0]<=start_date)  return(0);
         if(bars+copied>=max_bars) return(-2);
         fail_cnt=0;
        }
      else
        {
         //--- 失败尝试少于100
         fail_cnt++;
         if(fail_cnt>=100) return(-5);
         Sleep(10);
        }
     }
//--- 停止
   return(-3);
  }
//+------------------------------------------------------------------+
//| 返回该周期的字符串值                                                |
//+------------------------------------------------------------------+
string GetPeriodName(ENUM_TIMEFRAMES period)
  {
   if(period==PERIOD_CURRENTperiod=Period();
//---
   switch(period)
     {
      case PERIOD_M1:  return("M1");
      case PERIOD_M2:  return("M2");
      case PERIOD_M3:  return("M3");
      case PERIOD_M4:  return("M4");
      case PERIOD_M5:  return("M5");
      case PERIOD_M6:  return("M6");
      case PERIOD_M10return("M10");
      case PERIOD_M12return("M12");
      case PERIOD_M15return("M15");
      case PERIOD_M20return("M20");
      case PERIOD_M30return("M30");
      case PERIOD_H1:  return("H1");
      case PERIOD_H2:  return("H2");
      case PERIOD_H3:  return("H3");
      case PERIOD_H4:  return("H4");
      case PERIOD_H6:  return("H6");
      case PERIOD_H8:  return("H8");
      case PERIOD_H12return("H12");
      case PERIOD_D1:  return("Daily");
      case PERIOD_W1:  return("Weekly");
      case PERIOD_MN1return("Monthly");
     }
//---
   return("unknown period");
  }