Organizando Acesso aos Dados

Nesta seção, questões associados a obtenção, armazenamento e solicitação de dados de preços (séries de tempo) são consideradas.

Recebendo Dados de um Servidor de Negociação

Antes de dados de preços ficarem disponíveis no terminal MetraTrader 5, eles devem ser recebidos e processados. Para receber dados, uma conexão com o servidor de negociação MetaTrader 5 deve ser estabelecida. Dados são recebidos na forma de blocos empacotados de barras de um minuto do servidor sob a solicitação de um terminal.

O mecanismo de referência de servidor para solicitar dados não depende de como a solicitação foi iniciada - por um usuário ao navegar em um gráfico ou por meio de um programa na linguagem MQL5.

Armazenando Dados Intermediários

Dados recebidos de um servidor são automaticamente desempacotados e salvos no formato intermediário HCC. Os dados de cada ativo são escritos em uma pasta separada: terminal_directory\bases\server_name\history\symbol_name. Por exemplo, dados sobre EURUSD recebidos do servidor MetaQuotes-Demo serão armazenados em terminal_directory\bases\MetaQuotes-Demo\history\EURUSD\.

Os dados são escritos em arquivos com extensão .hcc. Cada arquivo armazena dados de barras de um minuto para um ano. Por exemplo, o arquivo nomeado 2009.hcc na pasta EURUSD contém barras de um minuto de EURUSD para o ano de 2009. Estes arquivos são usados para preparar dados de preço para todas as janelas de tempo e não são destinados para acesso direto.

Obtendo Dados em uma Janela de Tempo Necessária a partir dos Dados Intermediários

Os arquivo HCC intermediários são usados como fonte de dados para construir dados de preço para janelas de tempo solicitadas no formato HC. Dados de formato HC são séries de tempo que são maximamente preparados para acesso rápido. Eles são criados sob solicitação de um gráfico ou um programa MQL5. O volume de dados não deve exceder o valor do parâmetro "Máx. barras no gráfico". Os dados são armazenados para posterior uso em arquivos com extensão hc.

Para economizar recursos, os dados em uma janela de tempo são armazenados e guardamos em RAM somente se necessário. Se não for chamado por um longo tempo, eles são liberados da RAM e salvos em um arquivo. Para cada janela de tempo, os dados são preparados independentemente se existem dados prontos ou não para outras janelas de tempo. Regras para formação e acesso aos dados são as mesmas para todas as janelas de tempo. Isso significa que apesar da unidade de dados armazenada em HCC ser de um minuto, a disponibilidade de dados HCC não significa a disponibilidade de dados na janela de tempo M1 como HC no mesmo volume.

O recebimento de novos dados de um servidor chama automaticamente a atualização de dados de preço usados em formato HC de todas as janelas de tempo. Isso também leva ao recálculo de todos os indicadores que implicitamente usam estes dados como dados de entrada para cálculos.

Parâmetro "Máx. barras no gráfico"

O parâmetro "Máx bars no gráfico" restringe o número de barras em formato HC disponível para gráficos, indicadores e programas mql5. Isso é válido para todas as janelas de tempo disponíveis e serve, primeiramente, para economizar recursos do computador.

Ao definir um grande valor para este parâmetro, deve ser lembrado que se dados de preço de histórico longo para pequenas janelas de tempo estiverem disponíveis, a memória usada para armazenadas a série de preços e buffers de indicadores podem se tornar centenas de megabytes e alcançar a restrição de RAM do programa terminal cliente (2Gb para aplicativos 32-bit do MS Windows).

A alteração do parâmetro "Máx. barras no gráfico" produz efeito após o terminal cliente ser reiniciado. A alteração deste parâmetro não causa referência automática a um servidor por dados adicionais, e nem formação de barras adicionais de séries de tempo. Dados de preço adicionais são solicitados ao servidor, e séries de preço são atualizadas levando em conta a nova limitação, em caso de rolagem de um gráfico para uma área sem dados, ou quando dados são solicitado por um programas MQL5 .

O volume de dados solicitados ao servidor corresponde ao número solicitado de barras de uma da janela de tempo com o parâmetro "Max. barras em gráfico" levado em consideração. A restrição definida por este parâmetro não é absoluta, e em alguns casos o número de barras disponíveis para uma janela de tempo pode ser um pouco maior que o valor corrente do parâmetro.

Disponibilidade de Dados

A presença de dados no formato HCC ou mesmo no formato preparado HC não significa sempre a absoluta disponibilidade destes dados para serem exibidos em um gráfico ou usados em um programa mql5.

Ao acessar dados de preços ou valores de indicadores a partir de um programa mql5 deve ser lembrado que a sua disponibilidade em um certo momento do tempo ou iniciando a partir de um certo momento de tempo não é garantida. Isso está relacionado com fato de que para economizar recursos, a cópia completa dos dados necessários para um programa mql5 não é armazenada no MetaTrader 5; apenas um acesso direto à base de dados do terminal é fornecida.

O histórico de preços para todas as janelas de tempo é construído a partir de dados comuns em formato HCC, e qualquer atualização dos dados a partir de um servidor conduz à atualização de dados para todas as janelas de tempo e ao recálculo dos indicadores. Devido a isso, o acesso aos dados pode ficar fechado, mesmo se estes dados estiverem disponíveis a um momento atrás.

Sincronização dos Dados do Terminal com os Dados do Servidor #

Já que um programa mql5 pode chamar dados de qualquer ativo e janela de tempo, existe a possibilidade que os dados de uma série de tempo necessária não esteja formado ainda no terminal ou o preço necessário não esteja sincronizado com o servidor de negociação. Neste caso é difícil predizer o tempo de latência.

Algoritmos usando ciclos de latência não são a melhor solução. A única exceção neste caso são os scripts, porque eles não nenhuma escolha de algoritmo alternativo porque eles não tem manipuladores de evento. Para indicadores customizados tais algoritmos, bem como quaisquer outros ciclos de latência são fortemente não recomendados, porque levam a finalização do cálculo de todos os indicadores e qualquer outro manipulador de dados de preço do ativo.

Para Expert Advisor e indicadores, é melhor usar o modelo de eventos de manipulação. Se durante a manipulação dos eventos OnTick() ou OnCalculate(), o recebimento de dados para a janela de tempo requerida falhar, você deve sair do manipulador de evento, confiando na disponibilidade de acesso durante a próxima chamada do manipulador.

Exemplo de um Script para Adicionar Histórico

Vamos considerar um exemplo de um script que executa uma solicitação para receber histórico para o ativo selecionado de um servidor de negociação. O script é projetado para executar em um gráfico de um ativo selecionado; a janela de tempo não importa, porque como foi mencionado acima, dados de preço são recebidos de um servidor de negociação como pacotes de dados de um minuto, a partir dos quais qualquer série de tempo predefinida é construída.

Escreve todas as ações relacionadas a recepção de dados como uma função separada, CheckLoadHistory(symbol, timeframe, start_date):

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

A função CheckLoadHistory() é projetada como uma função universal que pode ser chamada de qualquer programa (Expert Advisor, script ou indicador); e portanto ela solicita três parâmetros de entrada: nome do ativo, período e data de inicio para indicar o começo do histórico de preço que você necessita.

Insira as verificações necessárias no código da função antes de solicitar o histórico faltante. Primeiramente, nós devemos assegurar que o nome do ativo e valor de período estão corretos:

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

Então vamos nos assegurar que o ativo está disponível na janela Observador de Mercado, isto é, o histórico para o ativo estará disponível durante o envio de uma solicitação a um servidor de negociação. Se não houver tal ativo no Observador de Mercado, adicionar ele usando a função SymbolSelect().

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

Agora nós devemos receber a data de início do histórico disponível para o par ativo/período indicado. Talvez, o valor do parâmetro de entrada startdate, passado para CheckLoadHistory(), já esteja disponível no histórico; então a solicitação a um servidor de negociação não é necessária. A fim de obter a primeira data para o ativo-período, a função SeriesInfoInteger() com o modificador SERIES_FIRSTDATE é usada.

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

A próxima importante verificação é o tipo do programa, a partir do qual a função é chamada. Note que enviar uma solicitação de atualização de série de tempo com o mesmo período do indicador, que chama a atualização, não é desejável. O problema de solicitar dados sobre o mesmo ativo-período que o do indicador está relacionado ao fato de que a atualização de dados de histórico é realizada na mesma thread onde o indicador opera. Assim a possibilidade de ocorrência de conflito é alta. Para verificar isso use a função MQL5InfoInteger() com o modificador MQL5_PROGRAM_TYPE.

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

Se todas as verificações tiverem passado com sucesso, faça a última tentativa de acesso sem se referir ao servidor de negociação Primeiramente, descubra a data de início, para qual dados de minuto no formato HCC estão disponíveis. Solicite este valor usando a função SeriesInfoInteger() com o modificador SERIES_TERMINAL_FIRSTDATE e compare novamente ele com o valor do parâmetro start_date.

   if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
     {
      //--- existe dados carregados para construir a série de tempo
      if(first_date>0)
        {
         //--- força a construção da série de tempo
         CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times);
         //--- verifica
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(2);
        }
     }

Se após todas as verificações, a thread de execução estiver ainda no corpo da função CheckLoadHistory(), isso significa que existe uma necessidade de solicitar os dados de preço faltantes ao servidor de negociação. Primeiro, retorne o valor de "Máx. barras no gráfico" usando a função TerminalInfoInteger():

  int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);

Nós precisaremos dele para evitar solicitar dados extra. Então encontre a primeira data no sistema do ativo no servidor de negociação (independentemente do período) usando a já conhecida função SeriesInfoInteger() com o modificador SERIES_SERVER_FIRSTDATE.

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

Já que a solicitação é uma operação assíncrona, a função é chamada no ciclo com uma espera de 5 milissegundos até a variável first_server_date receber um valor, ou a execução do ciclo ser terminada por um usuário (IsStopped() retornará true neste caso). Vamos indicar um valor correto da data de início, começando a partir do qual nós solicitamos dados de preço de um servidor de negociação.

   if(first_server_date>start_date) start_date=first_server_date;
   if(first_date>0 && first_date<first_server_date)
      Print("Aviso: primeira data de servidor ",first_server_date," para",
symbol," não coincide com a primeira data de série ",first_date);

Se a data de início first_server_date do servidor for menor que o data de início first_date do ativo em formato HCC, a correspondente entrada será impressa no diário.

Agora nós estamos prontos para fazer uma solicitação a um servidor de negociação por dados de preço faltantes. Faça a solicitação na forma de um ciclo e comece preenchendo seu corpo:

   while(!IsStopped())
     {
      //1. espere pela sincronização entre a série de tempo reconstruída e o histórico intermediário como HCC
      //2. receba o número corrente de barra n desta série de tempo
      //    se bars for maior que Max_bars_in_char, nós podem sair, o trabalho está terminado,
      //3. obter a data de início first_date na série de tempo reconstruída e comparar ela com strat_date
      //    se first_date for menor que start_date, nós podemos sair, o trabalho está terminado
      //4. solicitar a um servidor uma nova parte do histórico - 100 barras começando da última barra disponível numerada 'bars'
     }

Os primeiros três pontos estão implementados pelos meios já conhecidos.

   while(!IsStopped())
     {
      //--- 1. esperar até o processo de reconstrução da série de tempo acabar
      while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped())
         Sleep(5);
      //--- 2. solicitar quantas barras nós tempos
      int bars=Bars(symbol,period);
      if(bars>0)
        {
         //--- barras mais que podem ser desenhadas no gráfico, sair
         if(bars>=max_bars) return(-2); 
         //--- 3. retorne a data de início corrente na série de tempo
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            // data de início foi anterior a aquela solicitada, tarefa concluída
            if(first_date>0 && first_date<=start_date) return(0);
        }
      //4. Solicitar a um servidor uma nova parte do histórico - 100 barras começando da última barra disponível numerada 'bars'
     }

Sobrou o quarto último ponto - solicitar histórico. Nós não podemos referenciar a um servidor diretamente, mas toda função-Copy inicia automaticamente uma solicitação de envio a um servidor, se o histórico em formato HCC não for suficiente. Já que a hora da primeira data de início na variável first_date é um simples e natural critério para avaliar o grau de execução de uma solicitação, então a forma mais fácil é usar a função CopyTime().

Ao chamar funções que copiam quaisquer dados de séries de tempo, deve ser notado que o parâmetro start (número da barra, começando a partir do qual dados de preço devem ser copiados) deve sempre estar dentro do histórico de terminal disponível. Se você tiver somente 100 barras, é inútil tentar copiar 300 barras começando da barra com o índice 500. Tal solicitação será entendida como um error e não será tratada, isto é, nenhum histórico adicional será carregado de um servidor de negociação.

Esta é a razão porque nós copiaremos 100 barras começando da barra com o índice bars. Isso fornecerá uma carga suave de histórico faltando doe um servidor de negociação. Na verdade, um pouco mais que as 100 barras solicitadas serão carregadas, já que o servidor envia um histórico superdimensionado.

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

Após a operação de cópia, nós devemos analizar o número de elementos copiados. Se a tentativa falhar, então o valor copiado será igual a null e o valor do contador fail_cnt será aumentado em 1. Após 100 tentativas com falha, a operação da função será interrompida.

int fail_cnt=0;
...
   int copied=CopyTime(symbol,period,bars,100,times);
   if(copied>0)
     {
      //--- verifica dados
      if(times[0]<=start_date)  return(0);  // o valor copiado é menor, pronto
      if(bars+copied>=max_bars) return(-2); // as barras são mais do que se pode desenhar no gráfico, pronto
      fail_cnt=0;
     }
   else
     {
      //--- não mais que 100 tentativas falhando em sucessão
      fail_cnt++;
      if(fail_cnt>=100) return(-5);
      Sleep(10);
     }
 

Então, não somente manipulação correta da situação corrente em cada momento de execução está implementada na função, mas também o código de finalização é retornado, que pode ser tratado depois chamando a função CheckLoadHistory() para obtenção de informações adicionais. Por exemplo, desta forma:

   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");
     }

O código completo da função pode ser encontrado no exemplo de um script que mostra a correta organização de acesso de quaisquer dados com a manipulação de resultados de solicitação.

Code:

//+------------------------------------------------------------------+
//|                                              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
//--- parâmetros de entrada
input string          InpLoadedSymbol="NZDUSD";   // Ativo a ser carregado
input ENUM_TIMEFRAMES InpLoadedPeriod=PERIOD_H1;  // Perídio a ser carregado
input datetime        InpStartDate=D'2006.01.01'; // Data de início
//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
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("Primeira data ",first_date," - ",bars," barres");
//---
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CheckLoadHistory(string symbol,ENUM_TIMEFRAMES period,datetime start_date)
  {
   datetime first_date=0;
   datetime times[100];
//--- verifica ativo e período
   if(symbol==NULL || symbol==""symbol=Symbol();
   if(period==PERIOD_CURRENT)     period=Period();
//--- verifica se o ativo está selecionado no Observador de Mercado
   if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
     {
      if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOLreturn(-1);
      SymbolSelect(symbol,true);
     }
//--- verifica se os dados estão presentes
   SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date);
   if(first_date>0 && first_date<=start_date) return(1);
//--- não pede para carregar seus próprios dados se ele for um indicador
   if(MQL5InfoInteger(MQL5_PROGRAM_TYPE)==PROGRAM_INDICATOR && Period()==period && Symbol()==symbol)
      return(-4);
//--- segunda tentativa
   if(SeriesInfoInteger(symbol,PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_date))
     {
      //--- existe dados carregados para construir a série de tempo
      if(first_date>0)
        {
         //--- força a construção da série de tempo
         CopyTime(symbol,period,first_date+PeriodSeconds(period),1,times);
         //--- verifica
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(2);
        }
     }
//--- máximo de barras em um gráfico a partir de opções do terminal
   int max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);
//--- carrega informações de histórico do ativo
   datetime first_server_date=0;
   while(!SeriesInfoInteger(symbol,PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date) && !IsStopped())
      Sleep(5);
//--- corrige data de início para carga
   if(first_server_date>start_date) start_date=first_server_date;
   if(first_date>0 && first_date<first_server_date)
      Print("Aviso: primeira data de servidor ",first_server_date," para ",symbol,
            " não coincide com a primeira data de série ",first_date);
//--- carrega dados passo a passo
   int fail_cnt=0;
   while(!IsStopped())
     {
      //--- espera pela construção da série de tempo
      while(!SeriesInfoInteger(symbol,period,SERIES_SYNCHRONIZED) && !IsStopped())
         Sleep(5);
      //--- pede por construir barras
      int bars=Bars(symbol,period);
      if(bars>0)
        {
         if(bars>=max_bars) return(-2);
         //--- pede pela primeira data
         if(SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE,first_date))
            if(first_date>0 && first_date<=start_date) return(0);
        }
      //--- cópia da próxima parte força carga de dados
      int copied=CopyTime(symbol,period,bars,100,times);
      if(copied>0)
        {
         //--- verifica dados
         if(times[0]<=start_date)  return(0);
         if(bars+copied>=max_bars) return(-2);
         fail_cnt=0;
        }
      else
        {
         //--- não mais que 100 tentativas com falha
         fail_cnt++;
         if(fail_cnt>=100) return(-5);
         Sleep(10);
        }
     }
//--- interrompido
   return(-3);
  }
//+------------------------------------------------------------------+
//| Retorna valor de string do período                               |
//+------------------------------------------------------------------+
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("período desconhecido");
  }