Metodologia de teste de qualidade de dados

 

Após uma longa e saudável discussão no tópico Indices Futuros (https://www.mql5.com/pt/forum/16229), resolvemos iniciar um novo tópico com a finalidade de desenvolver um script "comunitário" (vou me apoderar do termo do Paulo Oliveira :-P) que possa ser utilizado para testar a qualidade dos dados recebidos pelo terminal MetaTrader.

Esse script tem a finalidade de atestar uma qualidade mínima para as cotações recebidas do servidor de dados, uma vez que dados precisos e confiáveis são fundamentais não apenas para a realização de backtests/forward tests, mas principalmente para o desenvolvimento de estratégias e indicadores baseadas em ticks e, mais importante, para termos certeza de que dados recebidos e utilizados pelo terminal estão mesmo sincronizados com o servidor de negociações.

A metodologia

A metodologia de testes (sugerida por mim, mas completamente aberta para inclusão de novos requisitos) é descrita abaixo:

1) O teste deve ser realizado ao longo de um intervalo de tempo de, no mínimo, cinco minutos;

2) Um log (de preferência em formato TXT - sugestão minha) deve ser gerado para futuras comparações;

3) O teste pode ser realizado por dois ou mais computadores diferentes, desde que eles estejam conectados ao mesmo servidor de dados (fundamental);

4) O teste deve ser realizado por computadores conectados na mesma rede de internet para evitar problemas de diferentes latências;

5) O teste pode ser realizado por duas ou mais instâncias do MetaTrader instalados na mesma máquina, desde que a mesma tenha suficiente poder de processamento e memória para tal;

5.1.) O teste também pode ser realizado por duas máquinas diferentes, desde que respeitadas as condições 1 a 4, descritas acima; 

6) O mesmo script (a ser disponibilizado neste tópico) deve ser utilizado por todas as máquinas envolvidas no teste.

Informações a serem testadas/gravadas

O nosso intuito com esse teste é analisar, basicamente, se cinco informações são recebidas na mesma quantidade e com o mesmo conteúdo por diferentes máquinas. São elas:

a) hora/minuto/segundo de recebimento do tick (vou incluir no script a opção de inclusão de milisegundos, entretanto essa última informação não é recebida do servidor de dados, mas baseada na hora da máquina local);

b) preço do último tick;

c) volume do último tick;

d) oferta de compra disponível no mercado (bid) no momento do último tick (incluída no script por necessidade minha);

e) oferta de venda disponível no mercado (ask) no momento do último tick (também incluída no script por necessidade minha);

Como comparar os logs

Os logs poderão ser abertos e visualmente comparados pelo próprio MetaEditor, uma vez que o arquivo gerado ficará disponível dentro da pasta Files. A imagem abaixo ilustra um exemplo de log gerado pelo script:


Entretanto, o ideal é copiar e colar todos os valores dos logs gerados do arquivo TXT para o Excel e colocá-los um ao lado do outro para comparação, tanto no que diz respeito à quantidade de ticks recebidos como também quanto à qualidade (ou seja, se eles são todos iguais) dos mesmos. Um exemplo é demonstrado abaixo:

 

O script

Para que os testes possam ser realizados de forma independente e homogênea por qualquer pessoa, um script padrão deve disponibilizado. Segue abaixo minha sugestão de script para realização dos testes:

#property version       "1.000"
#property description   "Script usado para gravar ticks de um ativo."
//╔══════════════════════════════════════════════════════════════════╗
//║ ENUMS ---------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
//--- Delimitador
enum DMod 
  {
   Tab   =0,   // Tab
   Com   =1,   // Vírgula
   SCl   =2,   // Ponto-e-vírgula
  };
//--- Informação de tempo (timestamp)
enum TMod 
  {
   Std   =0,   // Padrão
   Msc   =1,   // Com milisegundos
   Anl   =2,   // Para fácil análise
  };
//╔══════════════════════════════════════════════════════════════════╗
//║ INPUTS --------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
input DMod Delimiter  =0;
input TMod TimeStamp  =0;
//╔══════════════════════════════════════════════════════════════════╗
//║ IMPORTS -------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
//--- Usando kernel32.dll para timing em milisegundos
#import  "kernel32.dll"
     bool   GetLocalTime(int& st[]);
#import 
//--- Variáveis
      int      filehandle, SystemTime[4], wMilliSec;
      string   filename, time_stamp, mls, tick_ask, tick_bid, tick_last, tick_volume;
//╔══════════════════════════════════════════════════════════════════╗
//║ ON INIT -------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
int OnInit()
  { 
//---
      filename = TimeToString(TimeLocal(),TIME_DATE)+"_"+_Symbol+".txt";

      if (Delimiter==1) filehandle = FileOpen(filename,FILE_WRITE|FILE_TXT,',');
      else if (Delimiter==2) filehandle = FileOpen(filename,FILE_WRITE|FILE_TXT,';');
      else filehandle = FileOpen(filename,FILE_WRITE|FILE_TXT,'\t');
//---
      if (filehandle!=INVALID_HANDLE) 
         {
            FileWrite(filehandle,"Time","Symbol","Ask","Bid","Last","Volume");
            Print ("Início da exportação.");         
         }
      else Print("Erro de gerenciamento de arquivo! Erro = ",GetLastError());
//--- 
return(0);
  }
//╔══════════════════════════════════════════════════════════════════╗
//║ ON TICK -------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
void OnTick()
  {  
//--- Verificando sincronismo com o servidor de dados
   bool synchronized=false;
   int attempts=0;
//--- Cinco tentativas de sincronização
   while(attempts<5)
     {
      if(SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED))
        {
         synchronized=true;
         break;
        }
      attempts++;
      Sleep(10);
     }
//--- Caso esteja sincronizado
   if(synchronized)
     {
//--- Informação do tempo, com base no servidor de dados
      time_stamp   = TimeToString(TimeTradeServer(),TIME_DATE|TIME_SECONDS);
//--- Opção de incluir milisegundos
      if (TimeStamp==1 && GetLocalTime(SystemTime))
         {
//--- Obtendo milisegundos do tempo do sistema local
            wMilliSec   = SystemTime[3]>>16;
//--- Ajuste
            time_stamp=time_stamp+StringFormat(".%03d",wMilliSec);
         } 
//--- Opção de timestamp para fácil comparação
      else if (TimeStamp==2) 
         {
            static uint first_tick_time= GetTickCount();
            uint new_tick_time=GetTickCount()-first_tick_time;
            time_stamp= IntegerToString(new_tick_time);
         } 
//--- Escrevendo os ticks
      tick_ask    = DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      tick_bid    = DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      tick_last   = DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_LAST),_Digits);
      tick_volume = IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_VOLUME));
//--- Gravando os ticks
      if (filehandle!=INVALID_HANDLE)
         {    
            Print(time_stamp,"  ",tick_ask,"   ",tick_bid,"   ",tick_last,"   ",tick_volume);
            FileWrite(filehandle, time_stamp, _Symbol, tick_ask, tick_bid, tick_last, tick_volume);
         }
      else  Print("Gravação de TXT falhou! Erro = ",GetLastError());
     }
  }

Espero agora as contribuições de vocês!

Abraços,
Malacarne 

 
Malacarne:


Espero agora as contribuições de vocês!

Abraços,
Malacarne 

Malacarne, muito bom, já temos um script aberto, mas como comentei no outro tópico considero importante termos um EA fazendo o teste apenas em memória, evitando qualquer overhead ou latência de acesso ao disco, que pode ser uma das causas dos problemas.

Nesse caso, aproveitando teu trabalho e disponibilização, sugiro termos os dois sistemas (script + EA), sendo que o EA eu posso ficar responsável e publicar aqui assim que possível, para análise e uso de todos.

 
figurelli:

Malacarne, muito bom, já temos um script aberto, mas como comentei no outro tópico considero importante termos um EA fazendo o teste apenas em memória, evitando qualquer overhead ou latência de acesso ao disco, que pode seu uma das causas de problemas.

Nesse caso, aproveitando teu trabalho e disponibilização, sugiro termos os dois sistemas (script + EA), sendo que o EA eu posso ficar responsável e publicar aqui assim que possível, para análise e uso de todos.

Se puder postar algo a respeito, será de imensa valia! Só para constar, no meu servidor uso apenas discos solid state (SSD), mas concordo com você que pode haver latência também de acesso ao disco.

Aguardo também o resultado dos teus testes!

Abraços,
Malacarne 

 

Se eu entendi bem, o script acima destina-se a dados de Tick a partir de diferentes plataformas / user, depois de ser capaz de comparar e avaliar a qualidade dos dados entregues por MT5.

Mercado está agora fechada, por isso não posso fazer algum teste, mas aqui estão algumas observações:

Todas as novas cotações que são recebidas enquanto o programa está rodando são ignoradas até que a execução da função OnTick() esteja concluída. Após isso, a função rodará somente após uma nova cotação ser recebida. O evento NewTick é gerado independentemente da negociação automática ser permitida ou não (botão "AutoTrading Permite/Proíbe"). A proibição de negociação automática significa somente que o envio de solicitações de negociação a partir de um Expert Advisor não é permitido, enquanto o Expert Advisor continua trabalhando.

Então, se o seu objetivo é o de verificar Tick, você tem que voltar de OnTick () o mais rápido possível. Que não é o que o seu código faz.

  • Qual é o objetivo dessa parte do código? Está a gravar Tick recebido real, SERIES_SYNCHRONIZED é sobre dados históricos (TimeSeries), então t sua parte do código não é necessário.
 //--- Verificando sincronismo com o servidor de dados
   bool synchronized= false ;
   int attempts= 0 ;
//--- Cinco tentativas de sincronização
   while (attempts< 5 )
     {
       if ( SeriesInfoInteger ( _Symbol , _Period , SERIES_SYNCHRONIZED ))
        {
         synchronized= true ;
         break ;
        }
      attempts++;
       Sleep ( 10 );
     }

É recomendável usar SymbolInfoTick() se a função for usada para obter informações sobre o último tick. É bom possível que nenhuma cotação tenha aparecido ainda desde que o terminal se conectou a uma conta de negociação. Em tal situação, o valor solicitado será indefinido

Na maioria dos casos, é suficiente usar a função SymbolInfoTick() permitindo a um usuário receber os valores dos preços Compra, Venda, Último, Volume e a hora da chegada do último tick através de uma única chamada.

  • Você não tem para Imprimir () alguma coisa ou até mesmo usar operação de arquivo que são muito lentos. Como Figurelli escreveu, você tem que fazer o processo na memória, e só depois gravar em um log.

Vou postar mais quando o mercado estará aberto.

 
angevoyageur:

Se eu entendi bem, o script acima destina-se a dados de Tick a partir de diferentes plataformas / user, depois de ser capaz de comparar e avaliar a qualidade dos dados entregues por MT5.

Mercado está agora fechada, por isso não posso fazer algum teste, mas aqui estão algumas observações:

Então, se o seu objetivo é o de verificar carrapatos, você tem que voltar de OnTick () o mais rápido possível. Que não é o que o seu código faz.

  • Qual é o objetivo dessa parte do código? Está a gravar carrapato recebido real, SERIES_SYNCHRONIZED é sobre dados históricos (TimeSeries), então t sua parte do código não é necessário.
  • Você não tem para Imprimir () alguma coisa ou até mesmo usar operação de arquivo que são muito lentos. Como Figurelli escreveu, você tem que fazer o processo na memória, e só depois gravar em um log.

Vou postar mais quando o mercado estará aberto.

Perfeito Alain, já estou trabalhando em um EA que processa o tick da forma mais rápida possível em memória, com a opção de coletar preços ou não (quando coletar vou usar SymbolInfoTick como recomendado), assim que terminar e testar em backtesting publico o fonte aqui.
 
figurelli :
Perfeito Alain, já estou trabalhando em um EA que processa o tick da forma mais rápida possível em memória, com a opção de coletar preços ou não (quando coletar vou usar SymbolInfoTick como recomendado), assim que terminar e testar em backtesting publico o fonte aqui.
Bom, eu vou esperar o seu trabalho.
 

Segue o EA de testes, com opção de coletar apenas ticks ou ticks/preços.

No final desse post tem um anexo com o arquivo do EA completo (tick_count.mq5) para facilitar a instalação e operação.

Note que é necessário optar por "Enable Tick Price"=true para coletar ticks/preços. O default é false, ou seja, coletar apenas ticks (teoricamente o mais rápido possível).

// input parameters
input bool enable_price=false; // Enable Tick Price

// global
int tick=0,min=0,minuto=-1,cont=0,cont_max=0,cont_min=10000,avg=0,cont_tick=0;
double avgAsk=0,avgBid=0;

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   MqlTick realTick;
   MqlDateTime dt;
   TimeCurrent(dt);
   if (tick>0) {
      cont++; tick++;
      if (enable_price==true) {
         if (SymbolInfoTick(Symbol(),realTick)) {
            cont_tick++;
            avgAsk+=realTick.ask;
            avgBid+=realTick.bid;
         }
      }
   }
   if (dt.min!=minuto) {
      if (cont_tick>0) {
         avgAsk/=cont_tick;
         avgBid/=cont_tick;
      }
      else {
         avgAsk=0; avgBid=0;
      }
      cont_tick=0;
      minuto=dt.min;
      if (cont>cont_max) cont_max=cont; if (cont<cont_min) cont_min=cont;
      if (min>0) avg=tick/min;
      Comment("Tick Count - ",Symbol()," - Minutes: ",min," >> Ticks: ",tick," - Cont: ",cont," - Min: ",cont_min," - Max: ",cont_max,
         " - Avg.Tick: ",avg," (Avg.Ask: ",DoubleToString(avgAsk,5),", Avg.Bid: ",DoubleToString(avgBid,5),")");
      if (tick==0) tick++;
      min++; cont=0; avgAsk=0; avgBid=0;
   }
//---
  }

 

 Exemplo de tela de coleta de ticks/preços:

 

Obs: a contagem só inicia após o começo de um novo minuto no servidor, dessa forma é possível ativar vários EAs simultâneos (dentro do mesmo minuto) que eles irão começar a contar no mesmo momento. 

Arquivos anexados:
 
Pessoal, segue uma sugestão de próximos passos agora que temos um Script+EA, para comecar os testes na segunda-feira:

1) Começar pelo EA rodando em 2 gráficos com WINZ13 e dois gráficos com PETR4, e 2 plataformas MT5 com o mesmo perfil e na mesma máquina, já antes da abertura do pregão para os 4 EAs descritos começarem a coletar do mesmo ponto.

2) Se o comentário na tela não for eficaz para o diagnóstico, posso alterar o código para repetir os testes na terça-feira, mas gravando históricos em disco de hora em hora e coletando em períodos onde não ocorrerem as gravações.

3) Da mesma forma que 2) sugiro o Malacarne alterar o Script com as sugestões do Alain, como por exemplo retirar a rotina de sincronismo do gráfico para aumentar velocidade. Isso nos permite ter duas abordagens de coleta independentes para comparação.

Bem é isso, agora é aguardar segunda-feira mas hoje à noite com a abertura do Forex já vou inicar os testes do EA utilizando o servidor MetaQuotes-Demo.
 
figurelli :
Pessoal, segue uma sugestão de próximos passos agora que temos um Script+EA, para comecar os testes na segunda-feira:

1) Começar pelo EA rodando em 2 gráficos com WINZ13 e dois gráficos com PETR4, e 2 plataformas MT5 com o mesmo perfil e na mesma máquina, já antes da abertura do pregão para os 4 EAs descritos começarem a coletar do mesmo ponto.

2) Se o comentário na tela não for eficaz para o diagnóstico, posso alterar o código para repetir os testes na terça-feira, mas gravando históricos em disco de hora em hora e coletando em períodos onde não ocorrerem as gravações.

3) Da mesma forma que 2) sugiro o Malacarne alterar o Script com as sugestões do Alain, como por exemplo retirar a rotina de sincronismo do gráfico para aumentar velocidade. Isso nos permite ter duas abordagens de coleta independentes para comparação.

Bem é isso, agora é aguardar segunda-feira mas hoje à noite com a abertura do Forex já vou inicar os testes do EA utilizando o servidor MetaQuotes-Demo.

Desculpe, mas a tradução não é muito clara. Você recomenda para abrir cartas para (WINZ13, PETR4) em duas plataformas diferentes? É no mesmo computador?

Se algumas pessoas estão começando a testar, você está sugerindo para coletar dados durante toda a sessão? Das 10.00 às 17,00 para PETR4 e de 09.00 a 18.00 para WINZ13 (horário do servidor), tanto quanto eu posso ver.

 

Senhores, obrigado pelas contribuições! Após as sugestões de vocês, vou enviar em anexo o código modificado do script para gravação de ticks.

Entretanto, após analisar o Expert Advisor do Figurelli, tenho algumas críticas a fazer:

1) vi que o mesmo faz a contagem de ticks com plotagem visual das informações, mas não gera logs para futuras comparações; talvez fosse interessante fazer a escrita dos dados recebidos pelo menos a cada um minuto no disco (o que acredito não deva impactar a performance do expert advisor) ou, na pior das hipóteses, ao término do EA.

2) outra crítica que faço é o fato de que o mesmo apenas analisar a quantidade de ticks recebidos, mas não faz uma análise da qualidade dos mesmos; vi que é feito um cálculo de média para os ticks, cuja finalidade para ser sincero ainda não compreendi; lembrando que ao fazer o cálculo da média perde-se muito a qualidade da informação em si...

No mais, o intuito desse post é justamente esse: gerar uma metodologia que tenha um consenso mínimo entre os membros do fórum para que possamos, de forma conjunta, analisar a qualidade dos dados que estamos recebendo e utilizando no terminal MetaTrader.

Segue abaixo o código modificado e o arquivo.

#property version       "1.001"
#property description   "Script usado para gravar ticks de um ativo."
//╔══════════════════════════════════════════════════════════════════╗
//║ GLOBAL VARIABLES ----------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
int      filehandle, SystemTime[4], wMilliSec;
datetime tick_time;
double   tick_ask, tick_bid, tick_last;
ulong    tick_volume;
string   filename, time_stamp, mls;
//╔══════════════════════════════════════════════════════════════════╗
//║ ON INIT -------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
int OnInit()
  { 
//---
      filename = TimeToString(TimeLocal(),TIME_DATE)+"_"+_Symbol+".txt";
      filehandle = FileOpen(filename,FILE_WRITE|FILE_TXT,'\t');
//---
      if (filehandle!=INVALID_HANDLE) 
         {
            FileWrite(filehandle,"Time","Symbol","Ask","Bid","Last","Volume");
            Print ("Início da exportação.");         
         }
      else Print("Erro de gerenciamento de arquivo! Erro = ",GetLastError());
//--- 
return(0);
  }
//╔══════════════════════════════════════════════════════════════════╗
//║ ON TICK -------------------------------------------------------- ║
//╚══════════════════════════════════════════════════════════════════╝
void OnTick()
  {  
   MqlTick last_tick;
   if(SymbolInfoTick(_Symbol,last_tick))
     {
//--- Definindo os ticks
      tick_time   = last_tick.time;
      tick_ask    = last_tick.ask;
      tick_bid    = last_tick.bid;
      tick_last   = last_tick.last;
      tick_volume = last_tick.volume;
     }
//--- Gravando os ticks
   if(filehandle!=INVALID_HANDLE)
     {    
       //Print(tick_time,"  ",tick_ask,"   ",tick_bid,"   ",tick_last,"   ",tick_volume);
       FileWrite(filehandle, tick_time, _Symbol, tick_ask, tick_bid, tick_last, tick_volume);
     }
   else Print("Gravação de TXT falhou! Erro = ",GetLastError());
  }
Arquivos anexados:
 

Olá Figurelli e Malacarne,

Acho que ambas as abordagens são complementares, e eu sugiro que chegar a um acordo para avançar para algum teste com seus EAs atuais (ver posts acima). Se nós temos pelo menos 3 pessoas a coleta de dados, podemos obter primeiras pistas, antes de continuar a melhorar, se necessário.

Razão: