Escrita de indicadores de bolsa com controle de volume usando o indicador delta como exemplo

Tapochun | 22 outubro, 2018

Sumário

Introdução

Como é bem conhecido, o terminal MetaTrader 5 mostra dois tipos de volumes:

No terminal, o volume real é simplesmente indicado como "Volume". Estaremos interessados nele, pois juntamente com o surgimento do acesso ao histórico de ticks e do feed de operações no terminal também surgiu a possibilidade de escrever indicadores de bolsa. Com a ajuda deles, você pode ver o volume e a frequência das transações num determinado momento, a quantidade de vendedores ou de compradores num intervalo de tempo específico, quer dizer, pode-se ver o que está acontecendo nos bastidores. Isso significa que agora você pode separar o volume em componentes. Esses dados podem melhorar significativamente a precisão de suas previsões de negociação, embora seja um pouco mais difícil escrever um indicador desse tipo corretamente do que um habitual. Este artigo abordará em detalhes a escrita de indicadores de bolsa, bem como as particularidades de seu funcionamento e teste. Como exemplo, será escrito um indicador delta (de diferença) entre o volume de compras e o de vendas (eles formam o volume real). À medida que o indicador é criado, também serão descritas as regras para trabalhar com o fluxo de ticks.

No entanto, deve ser lembrado que o volume real está disponível apenas em mercados centralizados (de bolsa). Ou seja, ele é a priori indisponível para o mercado Forex, uma vez que se trata de um mercado de balcão. Consideraremos os volumes reais tomando o mercado de derivativos da bolsa de moscou (FORTS) como exemplo. Se até este momento você não estava familiarizado com FORTS, eu recomendo que leia o artigo sobre princípios de precificação.

Para quem é projetado este artigo

Recentemente no fórum do site mql5.com têm preguntado muito sobre os dados de ticks. Essa funcionalidade é relativamente nova e está melhorando constantemente. Em primeiro lugar, este artigo é para programadores que já sabem como escrever indicadores e que querem aprimorar suas habilidades em escrever programas para o MetaTrader 5. Em segundo lugar, o artigo será do interesse de muitos traders que querem dominar o mercado de ações e que gostam de analisar usando o indicador delta e/ou indicadores de ticks semelhantes.

1. Preparação. Seleção do servidor

Paradoxalmente, o desenvolvimento do indicador deve começar com a escolha de um servidor de negociação. Um pré-requisito para o funcionamento preciso de indicadores de bolsa é que os servidores das corretoras devem ser atualizados. Infelizmente, as versões dos servidores das corretoras não são transmitidas e nem sempre é possível entender imediatamente se estão sendo recebidos dados exatos.

O livro de ofertas nos ajudará a entender se o servidor está atualizado ou não. Para abri-lo, você precisa clicar no botão na forma de tabela ao lado do nome do instrumento no canto superior esquerdo da tela (se não for exibido, verifique se nas propriedades do gráfico (F8) a caixa ao lado da opção "Mostrar botões de negociação rápida" estiver marcada ou usar tecla de atalho Alt+B. Após abrir o livro de ofertas, você precisa clicar no botão "Mostrar numa tabela todas as negociações". Além disso, clicando com o botão direito do mouse na tabela, verifique que o filtro de volume mínimo não está definido.

Se o servidor não for atualizado, ele transmitirá no livro de ofertas as transações de direção indeterminada. Vamos examinar com mais detalhes o que é isso. Cada transação tem um iniciador que pode ser um comprador ou um vendedor. Consequentemente, ela deve indicar claramente a propriedade da transação "compra" ou "venda". Se a transação tiver uma direção incerta (é marcada como N/A no livro de ofertas), será afetada a precisão de delta (a diferença entre os volumes de compras e vendas), que nosso indicador calculará. A variante atualizada e a não atualizada do livro de ofertas são presentadas abaixo (Fig. 1):

  

Fig.1.Variante atualizada (esquerda) e antiga (direita) do livro de ofertas.

Regra №1. Verifique se o servidor está atualizado.

Também sugiro realmente escolher um servidor com baixa latência (ping). Quanto baixo for, mais rápido o seu terminal poderá trocar informações com o servidor da corretora. Se você olhar com cuidado a Fig. 1, notará que a plataforma MetaTrader 5 transmite transações com precisão de milissegundos - quanto menor o ping, mais rápido receberá informações sobre as transações para processá-las com rapidez. Você pode verificar o ping do servidor atual (e alterar o servidor, se necessário) no canto inferior direito da janela do terminal:

Fig. 2. O atraso para o servidor selecionado é de 30,58 milissegundos.

Também repare que o terminal do cliente deve ser atualizado para a versão 1881 ou posterior. Nessa versão, todos os erros - conhecidos até agora - com dados de ticks já foram corrigidos.

2. Métodos para obter o histórico de ticks. Formato MqlTick

Escolhido o servidor que fornecerá o histórico de ticks correto, enfrentamos a questão de como obter esse histórico. Em linguagem MQL5, para fazer isso, são introduzidas duas funções:

Para nosso indicador, precisamos dessas duas funções. Elas permitem obter ticks no formato Mqltick. Essa estrutura armazena hora, preços, volume e exatamente quais dados foram alterados com o tick atual. Também será preciso considerar que se pode obter um histórico de ticks de três tipos. O tipo é determinado pelo sinalizador:

Para nossos propósitos, precisamos de um fluxo de ticks de negociação (COPY_TICKS_TRADE). Na descrição da função CopyTicks podem ser encontradas mais informações sobre o tipo de tick.

Na estrutura MqlTick, analisaremos os valores dos seguintes campos:

Para simplificar, nosso indicador obterá todos os ticks de certa vela, verá o volume de compra e o de venda e exibirá a diferença desses volumes (delta) como um histograma. Se houver mais compradores na vela, o gráfico de barras será azul; se houver mais vendedores - vermelho. Tudo é bem simples!

3. Primeira inicialização. Cálculo do histórico

O objeto principal para trabalhar com ticks em nosso indicador será CTicks _ticks (arquivo Ticks_article.mqh). Através dele, realizaremos todas as operações com ticks.

O trabalho do indicador será dividido em dois blocos: bloco de cálculo do histórico e bloco de cálculo em tempo real

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- Verificando a primeira inicialização
   if(prev_calculated>0)                    // Se não for a primeira inicialização
     {
      // Bloco №2
     }
   else                                     // Se for a primeira inicialização
     {
      // Bloco №1
     }
//---
   return( rates_total );
  }

Ao iniciar o indicador pela primeira vez ou pressionar o botão "atualizar" no terminal (Bloco №1), devemos calcular o indicador com base no histórico. Inicialmente, planejei fazer uma função de cálculo universal que seria usada tanto no cálculo do histórico como nos cálculos em tempo real. No entanto, para simplificar e aumentar a velocidade dos cálculos, finalmente decidi fazer o contrário. Primeiro, com base nas barras geradas é calculado o histórico (CalculateHistoryBars()) e, em seguida, é calculada a barra atual (CalculateCurrentBar()). Todas essas ações são descritas abaixo:

//--- 1. Inicializamos os buffers de indicador com valores iniciais
BuffersInitialize(EMPTY_VALUE);
//--- 2. Redefinimos os valores dos parâmetros de re-controle
_repeatedControl=false;
_controlNum=WRONG_VALUE;
//--- 3. Redefinimos o tempo da barra na qual os ticks serão armazenados (pressionando o botão "atualizar")
_ticks.SetTime(0);
//--- 4. Definimos o momento para começar a carregar os ticks das barras formadas
_ticks.SetFrom(inpHistoryDate);
//--- 5. Verificamos o início do carregamento
if(_ticks.GetFrom()<=0)                 // Se o momento não estiver definido
   return(0);                           // Saímos
//--- 6. Definimos o fim do carregamento do histórico das barras formadas
_ticks.SetTo( long( time[ rates_total-1 ]*MS_KOEF - 1 ) );
//--- 7. Carregamos o histórico das barras formadas
if(!_ticks.GetTicksRange())             // Se não for bem-sucedido
   return(0);                           // Saíamos com erro
//--- 8. Cálculo do histórico nas barras formadas
CalculateHistoryBars( rates_total, time, volume );
//--- 9. Redefinimos o tempo da barra na qual os ticks serão armazenados
_ticks.SetTime(0);
//--- 10. Definimos o momento para começar a carregar os ticks da última barra
_ticks.SetFrom( long( time[ rates_total-1 ]*MS_KOEF ) );
//--- 11. Definimos o momento para finalizar o carregamento dos ticks da última barra
_ticks.SetTo( long( TimeCurrent()*MS_KOEF ) );
//--- 12. Carregamos o histórico da barra atual
if(!_ticks.GetTicksRange())             // Se não for bem-sucedido
   return(0);                           // Saíamos com erro
//--- 13. Redefinimos o momento do fim da cópia
_ticks.SetTo( ULONG_MAX );
//--- 14. Lembramo-nos da hora do último tick do histórico recebido
_ticks.SetFrom();
//--- 15. Cálculo da barra atual
CalculateCurrentBar( true, rates_total, time, volume );
//--- 16. Definimos o número de ticks para cópias posteriores em tempo real
_ticks.SetCount(4000);

O código do indicador está muito bem comentado, por isso vou abordar apenas os pontos principais.

Ponto 3. "Redefinimos o tempo da barra na qual os ticks serão armazenados". Um objeto que trabalha com ticks contém o tempo de abertura da vela em que são registrados seus ticks correspondentes. Ao clicar no botão "Atualizar" no terminal, o indicador é recalculado desde o início. Assim, para registrar corretamente os ticks na vela necessária, é preciso redefinir seu tempo.

Ponto 4. "Definimos o momento para começar a carregar os ticks das barras formadas". Obter um histórico de ticks pode ser uma operação demorada. Portanto, é necessário permitir que o usuário especifique de forma independente a data de início de seu carregamento. Para fazer isso, o indicador tem o parâmetro inpHistoryDate. Quando o valor é zero, o histórico é carregado desde o começo do dia atual. Nesse protótipo do método SetFrom(datetime), o tempo é transmitido em segundos. Como já mencionado acima, primeiro são calculadas as barras do indicador.

Ponto 5. "Verificamos se o início do carregamento está certo". Verificação do valor obtido no ponto 4.

Ponto 6. "Definimos o fim do carregamento do histórico das barras formadas". O fim do carregamento do histórico das barras formadas é o milésimo de segundo anterior antes da abertura da vela atual (rates_total-1). Neste caso, o final do carregamento é de tipo long. Ao passar um parâmetro para um método, é preciso indicar explicitamente que o tipo long é transferido se a classe também tiver um método para o qual o parâmetro é passado com o tipo datetime. No caso do método SetTo(), a classe não implementa sua sobrecarga com um argumento datetime, mas é melhor garantir e passar explicitamente o parâmetro de tipo long.

Ponto 7. "Carregamos o histórico das barras formadas". O histórico é obtido usando a função GetTicksRange(), que é um wrapper para a função CopyTicksRange() com a adição de verificações de erro. Se ocorrerem erros durante o carregamento, no próximo tick será solicitado novamente todo o histórico. Você pode aprender mais sobre essa e outras funções para trabalhar com ticks no arquivo Ticks_article.mqh anexado. 

Ponto 8. "Cálculo do histórico com base nas barras formadas". O cálculo com base nas barras formadas será descritos em mais detalhes no parágrafo correspondente do artigo.

Pontos 9-12. Cálculo de barras concluído. É preciso calcular a vela atual. Aqui é definido o intervalo para copiar e receber os ticks da vela atual.

Ponto 13. "Redefinimos o momento do fim da cópia". No futuro, continuaremos a receber ticks usando o objeto _ticks, no entanto, nós os receberemos não de um momento para outro, como fizemos ao carregar o histórico, mas, sim, do momento da chegada do último tick ao final de todo o histórico disponível. É por isso que é melhor jogar fora o momento do fim da cópia, uma vez que, ao calcular em tempo real, não precisaremos mais dele.

Ponto 14. "Lembramo-nos da hora do último tick do histórico recebido". Vamos precisar do tempo do último tick do histórico recebido no futuro como o momento do início da cópia dos dados em tempo real.

Ponto 15. "Cálculo da barra atual". O cálculo da barra atual também será descrito numa parte separada do artigo e terá diferenças importantes em relação ao método de cálculo das barras formadas.

Ponto 16. "Definimos o número de ticks para cópias posteriores em tempo real". Antes obtínhamos ticks com a ajuda da função CopyTicksRange(), envolvida com o método GetTicksRange(), agora em tempo real usamos a função CopyTicks(), envolvida com o método GetTicks(). O método SetCount() define o número de ticks para solicitações subsequentes. 4000 é escolhido porque o terminal armazena 4096 ticks de cada símbolo para maior rapidez ao pedir e acessar esses ticks. Mesmo que seja definindo um valor de 100 ou de 4000, não será afetada a velocidade de geração de ticks (~ 1 ms)

Vamos nos debruçar sobre as funções de cálculo.

4. Função de cálculo do histórico com base nas barras formadas

A função em si é a seguinte:

//+------------------------------------------------------------------+
//| Função para cálculo das barras do histórico formadas             |
//+------------------------------------------------------------------+
bool CalculateHistoryBars(const int rates_total,    // Número de barras contadas
                          const datetime& time[],   // Matriz de tempo de abertura de barras
                          const long& volume[]      // Matriz de valores do volume real
                          )
  {
//--- Volumes totais
   long sumVolBuy=0;
   long sumVolSell=0;
//--- Número da barra para armazenar no buffer
   int bNum=WRONG_VALUE;
//--- Obtemos o número de ticks na matriz
   const int limit=_ticks.GetSize();
//--- Ciclo para todos os ticks
   for(int i=0; i<limit && !IsStopped(); i++)
     {
      //--- Definimos a vela em que serão registrados os ticks
      if(_ticks.IsNewCandle(i))                         // Se o início da formação da próxima vela
        {
         //--- Verificamos se o número da vela formada (completada) está registrado
         if(bNum>=0) // Se o número estiver registrado
           {
            //--- Verificamos se os valores de volume estão registrados
            if(sumVolBuy>0 || sumVolSell>0) // Se todos os parâmetros estiverem registrados
              {
               //--- Controlamos o volume total da vela
               VolumeControl(false,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
            //--- Colocamos os valores nos buffers
            DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
           }
         //--- Redefinimos os volumes da vela anterior
         sumVolBuy=0;
         sumVolSell=0;
         //--- Definimos o número da vela que corresponde. ao tempo de sua abertura
         bNum=_ticks.GetNumByTime(false);
         //--- Verificamos se o número está certo
         if(bNum>=rates_total || bNum<0) // Se o número estiver incorreto     
           {
            //--- Saímos sem calcular o histórico
            return( false );
           }
        }
      //--- Adicionamos o volume no tick ao componente requerido
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);
     }
//--- Verificamos se os valores dos volumes da última vela estão registrados
   if(sumVolBuy>0 || sumVolSell>0) // Se todos os parâmetros estiverem registrados
     {
      //--- Controlamos o volume total da vela
      VolumeControl(false,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
     }
//--- Colocamos os valores nos buffers
   DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
//--- Cálculo concluído
   return( true );
  }

A essência desta função é classificar pelas velas formadas do timeframe atual os ticks recebidos; obter a diferença nos volumes de compras e de vendas de cada vela e inserir os valores obtidos de volumes e de deltas nos buffers de indicador.

Como mencionado anteriormente, inicialmente foi planejado fazer uma função comum para calcular histórico e para cálculos em tempo real. No entanto, ao adicionar a função de cálculo do histórico com base nas barras formadas, buscamos vários objetivos:

Abaixo será dada uma descrição completa do algoritmo de cálculo e das características de trabalho com histórico de ticks.

5. Função para calcular a vela atual

No código do indicador, a função CalculateCurrentBar() deve receber mais atenção.

//+------------------------------------------------------------------+
//| Função para cálculo da vela atual                                |
//+------------------------------------------------------------------+
void CalculateCurrentBar(const bool firstLaunch,   // Sinalizador da primeira inicialização da função
                         const int rates_total,    // Número de barras contadas
                         const datetime& time[],   // Matriz de tempo de abertura de barras
                         const long& volume[]      // Matriz de valores do volume real
                         )
  {
//--- Volumes totais
   static long sumVolBuy=0;
   static long sumVolSell=0;
//--- Número da barra para armazenar no buffer
   static int bNum=WRONG_VALUE;
//--- Verificamos o sinalizador da primeira inicialização
   if(firstLaunch)                                 // Se a primeira inicialização
     {
      //--- Redefinimos os parâmetros estáticos
      sumVolBuy=0;
      sumVolSell=0;
      bNum=WRONG_VALUE;
     }
//--- Obtemos o número do penúltimo tick na matriz
   const int limit=_ticks.GetSize()-1;
//--- Hora do tick 'limit'
   const ulong limitTime=_ticks.GetFrom();
//--- Ciclo para todos os ticks (excluindo o último)
   for(int i=0; i<limit && !IsStopped(); i++)
     {
      //--- 1. Comparamos o tempo do tick i com o tempo do tick 'limit' (verificação da conclusão do ciclo)
      if( _ticks.GetTickTimeMs( i ) == limitTime ) // Se o tempo do tick for igual ao tempo do tick limite
         return;                                   // Saímos
      //--- 2. Verificamos se começou a se formar a vela que está ausente no gráfico.
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())                // Se a vela se começou a se formar
        {
         //--- Verificamos o log
         if(inpLog)
            Print(__FUNCTION__,": ATENÇÃO! Tick futuro ["+GetMsToStringTime(_ticks.GetTickTimeMs(i))+"]. Hora do tick "+TimeToString(_ticks.GetTickTime(i))+
                  ", time[ rates_total-1 ]+PerSec() = "+TimeToString(time[rates_total-1]+PeriodSeconds()));
         //--- 2.1. Definimos (corrigimos) a hora da próxima solicitação de ticks
         _ticks.SetFrom(_ticks.GetTickTimeMs(i));
         //--- Saímos
         return;
        }
      //--- 3. Definimos a vela em que são registrados os ticks.
      if(_ticks.IsNewCandle(i))                    // Se a próxima vela começar a se formar
        {
         //--- 3.1. Verificamos se o número da vela formada (concluída) está registrado.
         if(bNum>=0)                               // Se o número estiver registrado
           {
            //--- Verificamos se os valores de volume estão registrados
            if(sumVolBuy>0 || sumVolSell>0)        // Se todos os parâmetros estiverem registrados
              {
               //--- 3.1.1. Controlamos o volume total da vela
               VolumeControl(true,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
           }
         //--- 3.2. Redefinimos os volumes da vela anterior
         sumVolBuy=0;
         sumVolSell=0;
         //--- 3.3. Lembramo-nos do número da vela atual
         bNum=rates_total-1;
        }
      //--- 4. Adicionamos o volume no tick ao componente requerido
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);
      //--- 5. Colocamos os valores nos buffers
      DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
     }
  }

Ela é semelhante à função CalculateHistoryBars() anterior, mas possui suas próprias particularidades. Vamos examiná-las com mais detalhes. Abaixo está um protótipo da função:

//+------------------------------------------------------------------+
//| Função para calcular a vela atual                                |
//+------------------------------------------------------------------+
void CalculateCurrentBar(const bool firstLaunch,   // Sinalizador da primeira inicialização da função
                         const int rates_total,    // Número de barras contadas
                         const datetime& time[],   // Matriz de tempo de abertura de barras
                         const long& volume[]      // Matriz de valores do volume real
                         )

Imediatamente, notamos que CalculateCurrentBar() será usada em dois casos, nomeadamente ao calcular o histórico da vela atual na primeira execução e ao calcular em tempo real. O sinalizador firstLaunch será responsável por selecionar o modo de cálculo. A única diferença entre os modos é que, durante a primeira inicialização, serão redefinidas para zero as variáveis ​​estáticas contendo as somas de volumes de compras e de vendas, bem como o número de velas nos buffers em que esses valores e seu delta serão registrados. Mais uma vez vou enfatizar que no indicador são usados apenas volumes reais!

//--- Volumes totais
   static long sumVolBuy=0;
   static long sumVolSell=0;
//--- Número da barra para armazenar no buffer
   static int bNum=WRONG_VALUE;
//--- Verificamos o sinalizador da primeira inicialização
   if(firstLaunch)                                 // Se a primeira inicialização
     {
      //--- Zeramos os valores dos volumes
      sumVolBuy=0;
      sumVolSell=0;
      //--- Redefinimos o número da vela
      bNum=WRONG_VALUE;
     }

Após a declaração de variáveis ​​estáticas, são obtidos o número e a hora do último tick na matriz:

//--- Obtemos o número do último tick na matriz
   const int limit=_ticks.GetSize()-1;
//--- Hora do tick 'limit'
   const ulong limitTime=_ticks.GetFrom();

O número será o limitador do ciclo de iteração de ticks. Definimos a condição de que o último tick não participará dos cálculos. Mas por quê? O fato é que os ticks podem vir em pacotes se uma ordem de mercado é preenchida com várias ordens limitadas de contrapartes diferentes. Um pacote de ticks (transações) consiste em operações concluídas num mesmo momento (em milissegundos) e que são do mesmo tipo (compra ou venda) (Fig. 3). Só para constar, repare que vários pacotes de ticks podem ser exibidos no terminal num milésimo de segundo e que a bolsa transmite transações com precisão de nanossegundos. Isso pode ser verificado iniciando o script test_getTicksRange anexado ao artigo.

Fig. 3. Um pacote de ticks (ordem a mercado de compra iniciou 4 transações de 26 lotes).

Para calcular corretamente um volume, é necessário calcular um pacote de ticks por vez e somente quando ele é completamente transferido para o terminal, ou seja, quando é disponível uma transação que é concluída num momento posterior (Fig. 4).


Fig. 4. Transação no momento .373, momento do cálculo do pacote .334.

Sim, exatamente isso. Não podemos contar com o fato de o pacote chegar ao terminal completamente até que a próxima transação não esteja disponível, já que um pacote pode ser transferido para o terminal em partes. Agora eu não vou me debruçar sobre isso em mais detalhes, simplesmente confie em mim. A partir disso, podemos formular a regra №2:

Regra №2. Pacotes de carrapatos devem ser calculados só após recebido o próximo tick depois deste pacote.

No ponto 13 do algoritmo da primeira execução, salvamos o tempo do último tick recebido. Agora vamos usá-lo, escrevendo na variável limitTime.

Em seguida, avançamos diretamente para o ciclo de cálculo de ticks:

//--- 1. Comparamos o tempo do tick i com o tempo do tick 'limit' (verificação da conclusão do ciclo)
      if( _ticks.GetTickTimeMs( i ) == limitTime ) // Se o tempo do tick for igual ao tempo do tick limite
         return;                                   // Saímos

Ponto 1. "Comparamos o tempo do tick com o tempo do último tick". Como mencionado acima, o último tick não é levado em conta nos cálculos, uma vez que o cálculo é apenas para os pacotes de ticks formados. Mas também sabemos que o último pacote de ticks não pôde ser completamente copiado. Portanto, temos que excluir do cálculo não apenas o último tick recebido, mas também todos os ticks do seu pacote (se houver algum).

//--- 2. Verificamos se começou a se formar a vela que está ausente no gráfico.
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())

Ponto 2. "Verificamos se começou a se formar a vela que está ausente no gráfico". Entendo que isso possa soar um pouco estranho. Como pode uma vela que não está no gráfico começar a se formar, você se perguntará. Para responder a essa pergunta, é necessário entender certa peculiaridade ao processar/receber dados de ticks no terminal. Após uma longa comunicação com os desenvolvedores na central de serviços, posso dizer o seguinte:  

Os ticks no terminal são coletados num fluxo separado, independentemente do trabalho de indicadores ou de EAs. As velas são construídas em outro fluxo — no fluxo de execução de indicadores. Esses fluxos não estão sincronizados entre si. Após o tick ser aplicado à vela, é calculado o indicador. Ao acontecer isso, nenhum tick é ignorado. Por isso, ao chamar a função CopyTicks(), é possível obter dados de ticks mais recentes do que aqueles que já foram aplicados a barras.

Na prática, isso significa que ao calcular a vela rates_total-1, o indicador pode obter os ticks da seguinte vela que ainda não foi formada (o tick ainda não foi aplicado a ela). Para evitar essa situação (e evitar o erro de ficar fora da matriz), é preciso adicionar essa verificação.

Regra 3. É necessário levar em conta que podem ser obtidos ticks que ainda não apareceram no gráfico de velas.

Se os ticks do "futuro" (a vela de ticks ainda não foi formada) tiverem sido detectados, devemos reescrever o momento no tempo a partir do qual ocorrerá a próxima solicitação de ticks (ponto 2.1.). E, claro, sair imediatamente do ciclo e da função, aguardando a chegada de um novo tick e a formação de uma nova vela no gráfico:

//--- 2. Verificamos se começou a se formar a vela que está ausente no gráfico.
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())                // Se a vela se começou a se formar
        {
         //--- 2.1. Definimos (corrigimos) a hora da próxima solicitação de ticks
         _ticks.SetFrom(_ticks.GetTickTimeMs(i));
         //--- Saímos
         return;
        }

O algoritmo a seguir coincide quase completamente com a função CalculateHistoryBars(). Vamos considerá-lo em mais detalhes.

//--- 3. Definimos a vela em que são registrados os ticks.
      if(_ticks.IsNewCandle(i))

Ponto 3. Definimos a vela na qual são escritos os ticks. Aqui, são comparados o tempo do tick i e o tempo da abertura da vela na qual são escritos os ticks. Se o tempo do tick i ficar fora do limite da vela, o tempo de abertura da vela muda e é ativado o algoritmo usado para preparar da análise da próxima vela:

//--- 3. Definimos a vela em que são registrados os ticks.
      if(_ticks.IsNewCandle(i))                    //Se for o começo da formação da próxima vela
        {
         //--- 3.1. Verificamos se o número da vela formada (concluída) está registrado.
         if(bNum>=0)                               // Se o número estiver escrito
           {
            //--- Verificamos se os valores de volume estão registrados
            if(sumVolBuy>0 || sumVolSell>0)        // Se todos os parâmetros estiverem escritos
              {
               //--- 3.1.1. Controlamos o volume total da vela
               VolumeControl(true,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
           }
         //--- 3.2. Redefinimos os volumes da vela anterior
         sumVolBuy=0;
         sumVolSell=0;
         //--- 3.3. Lembramo-nos do número da vela atual
         bNum=rates_total-1;
        }

Ponto 3.1. Verificamos se o número da vela formada está registrado. No modo de cálculo de histórico (primeira inicialização), essa verificação impedirá o acesso a matrizes de tempo e de volume com um índice incorreto (-1). Em seguida, é verificado se na vela foram feitas transações. Se não houver transações, não será necessário o controle de volume.

Ponto 3.1.1. Controlamos o volume total. No procedimento VolumeControl(), não só são somados os volumes Buy e Sell, acumulados por nosso indicador para a vela, mas também eles são comparados com o "volume de controle", ou seja, com o volume real transferido diretamente da bolsa (com o valor da matriz Volume[] da vela formada). Se o volume da bolsa corresponder com o volume acumulado, passamos para cálculos adicionais. Se os volumes não corresponderem... paramos. Como é que os volumes não coincidem, você se perguntará. São o mesmo volume total. Acontece que um é calculado em nosso indicador e o outro veio da bolsa de valores. Volumes devem coincidir! 

Isso mesmo, os volumes devem coincidir. Esta regra deve ser seguida para todas as velas. Afinal, o que nosso indicador faz é:

Parece não ser nada complicado. Mas em tempo real (para a vela rates_total-1), é necessário lembrar aqueles "ticks do futuro” (regra 3) quando chega o tick da vela nova que ainda não se formou no gráfico. Essa particularidade deixa sua marca no controle do volume! Neste momento do processamento do tick, o indicador ainda não conterá o valor do volume atualizado na matriz volume[] (o valor será alterado novamente). Dessa forma, não poderemos comparar corretamente o volume coletado pelo indicador e o volume da matriz volume[]. Na prática, o volume de controle volume[rates_total-1] às vezes não coincidirá com a soma dos volumes (sumVolBuy+sumVolSell) coletados pelo indicador. Nesse caso, o procedimento VolumeControl() fornece duas soluções para esse problema:

  1. Recálculo do volume da vela e sua comparação com o valor de controle obtido através da função CopyRealVolume();
  2. Se a primeira opção não resolver o problema, o indicador de controle de volume é ajustado no momento da formação de uma vela nova.

Assim, o primeiro método tenta resolver o problema de controle antes da formação de uma vela nova, enquanto o segundo deve resolver o problema após a formação de uma vela nova.

Ponto 3.2. "Redefinimos os volumes da vela anterior". Após a formação de uma vela nova, redefinimos os contadores de volume para zero.

Ponto 3.3. "Lembramo-nos do número da vela atual". Outra vantagem é a divisão das funções calculadas: uma para as barras formadas e outra para a vela atual. O número da vela atual sempre é = rates_total-1.

//--- 4. Adicionamos o volume no tick ao componente requerido
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);

Ponto 4. Adicionamos o volume do tick ao volume total. Primeiro, usando o sinalizador do tick analisado, descobrimos quais dados foram alterados:

//+------------------------------------------------------------------+
//| Adicionamos o volume do tick ao volume total                     |
//+------------------------------------------------------------------+
void AddVolToSum(const MqlTick &tick,        // Parâmetros do tick verificado
                 long& sumVolBuy,            // Volume total de compras (out)
                 long& sumVolSell            // Volume total de vendas (out)
                )
  {
//--- Verificamos a direção do tick
   if(( tick.flags&TICK_FLAG_BUY)==TICK_FLAG_BUY && ( tick.flags&TICK_FLAG_SELL)==TICK_FLAG_SELL) // Se o tick estiver em ambos os sentidos
        Print(__FUNCTION__,": ERRO! Tick '"+GetMsToStringTime(tick.time_msc)+"' de sentido desconhecido!");
   else if(( tick.flags&TICK_FLAG_BUY)==TICK_FLAG_BUY)   // Se for um tick de compra
        sumVolBuy+=(long)tick.volume;
   else if(( tick.flags&TICK_FLAG_SELL)==TICK_FLAG_SELL) // Se for um tick de venda
        sumVolSell+=(long)tick.volume;
   else                                                  // Se não for um tick de negociação
        Print(__FUNCTION__,": ERRO! Tick '"+GetMsToStringTime(tick.time_msc)+"' não é de negociação!");
  }

Aqui, novamente, quero focar na Regra 1. Se o trabalho ocorrer num servidor que transmite transações de sentido desconhecido, será impossível determinar se foi o comprador ou o vendedor quem iniciou a transação. O log exibirá os erros correspondentes. Se o iniciador for definido, o volume será adicionado ao volume total de compras ou de vendas. Se o sinalizador não contiver dados sobre o iniciador da transação, também será recebido um erro.

//--- 5. Colocamos os valores nos buffers
      DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);

Ponto 5. Colocamos os valores nos buffers. No procedimento DisplayValues(), é controlado o índice dos buffers de indicador (para fazer isso, transferimos o número da linha de chamada para a função), é calculado o delta e são registrados o delta, os volumes de compras e os de vendas nos buffers.

6. Cálculo em tempo real

Descrevemos o algoritmo do bloco de cálculos em tempo real:

//--- 1. Verificamos a formação de uma barra nova
if(rates_total>prev_calculated) // Se a nova barra
  {
   //--- Inicializamos os índices de buffers rates_total-1 com valores vazios
   BuffersIndexInitialize(rates_total-1,EMPTY_VALUE);
   //--- 2. Verificamos se é necessário controlar o volume na barra rates_total-2
   if(_repeatedControl && _controlNum==rates_total-2)
     {
      //--- 3. Verificamos novamente
      RepeatedControl(false,_controlNum,time[_controlNum]);
     }
   //--- 4. Redefinimos os valores de everificação
   _repeatedControl=false;
   _controlNum=WRONG_VALUE;
  }
//--- 5. Carregamos os novos ticks
if(!_ticks.GetTicks() )               // Se não for bem-sucedido
   return( prev_calculated );         // Saímos com erro
//--- 6. Lembramo-nos da hora do último tick do histórico recebido
_ticks.SetFrom();
//--- 7. Cálculo em tempo real
CalculateCurrentBar(false,rates_total,time,volume);

Ponto 1. Verificamos a formação de uma barra nova. Essa verificação é muito importante, uma vez que no ponto 2.1.1. esclarecemos que se não realizássemos esse controle na função de cálculo principal no procedimento de controle de volume (cálculo em tempo real), ele deveria ser feito no momento da formação de uma barra nova. Agora é justamente esse momento!

Ponto 2. Verificamos se é necessário controlar o volume na barra rates_total-2. Se o sinalizador de controle repetido estiver definido e esse controle tiver de ser feito na vela rates_total-2 (recém concluída), realizamos um duplo contole (ponto 3).

Ponto 3. Verificamos novamente. Como mencionado anteriormente, durante o procedimento de reverificação, são recebidos todos os ticks da vela, é determinado o volume de compras e de vendas, é calculado o delta e são comparados os valores dos volumes com o valor de controle.

Ponto 5. Carregamos novos ticks. Recebemos ticks a partir do momento da chegada do último tick na inicialização anterior do indicador. Ao calcular em tempo real, os ticks são obtidos usando a função GetTicks() que usa a função CopyTicks().

Ponto 6. Lembramo-nos do tempo do último tick. Esse é a hora do último tick obtido no ponto 5. ou após o cálculo do histórico. Na próxima inicialização do indicador, o histórico de ticks será solicitado a partir desse momento.

Ponto 7. Cálculo em tempo real. Como mencionado anteriormente, o procedimento CalculateCurrentBar() é usado no cálculo do histórico e no cálculo em tempo real. O sinalizador firstLaunch é responsável por isso, neste caso, ele é definido como false.

7. Particularidades do trabalho no testador de estratégias

Ao usar o testador de estratégias, é necessário lembrar que ele é um programa separado com sua própria funcionalidade. Mesmo que um testador possa fazer o mesmo que um terminal, isso não significa que ele funciona da mesma maneira que um terminal. Esse tipo de situação (nesta etapa de desenvolvimento do testador) ocorre com programas que usam dados de ticks. Apesar do fato de que o indicador é calculado corretamente (o controle de volume é bem-sucedido), o indicador no testador é calculado de maneira um pouco diferente. A peculiaridade novamente está no processamento de pacotes de ticks.

Se, no terminal, puderem ocorrer várias transações num tick (ou seja, se for possível receber vários ticks), cada tick no testador será recebido separadamente, mesmo se houver muitos ticks consecutivos de um pacote. Isso pode ser verificado executando o indicador de teste test_tickPack a partir do aplicativo. Isso será algo assim:

2018.07.13 10:00:00   OnCalculate: 4 ticks recebidos. [0] = 2018.07.13 10:00:00.564, [3] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: 2 ticks recebidos. [0] = 2018.07.13 10:00:00.571, [1] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: 3 ticks recebidos. [0] = 2018.07.13 10:00:00.571, [2] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: 4 ticks recebidos. [0] = 2018.07.13 10:00:00.571, [3] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: 5 ticks recebidos. [0] = 2018.07.13 10:00:00.571, [4] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: 6 ticks recebidos. [0] = 2018.07.13 10:00:00.571, [5] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: 7 ticks recebidos. [0] = 2018.07.13 10:00:00.571, [6] = 2018.07.13 10:00:00.572 FLAG_BUY

Você mesmo pode fazer esse experimento, necessariamente no modo "Cada tick baseado em ticks reais", pressionando F12. Os ticks serão adicionados estritamente um por um! Na realidade, esse pacote pode entrar no terminal em partes ou inteiro, mas muito improvavelmente - um por um. Isso nem é bom nem mau, você só precisa aceitar essa particularidade como um fato e lembrá-la.

Fim do artigo

Assim, fica concluía a descrição do algoritmo de construção do indicador. Ela mostrou as nuances e dificuldades que tive que enfrentar ao escrever indicadores de ticks, assim como muitos outros usuários. Espero que minha experiência seja útil para a comunidade e que, com base nela, sejam implementados mais e mais programas usando dados de ticks, impulsionando o desenvolvimento da plataforma MetaTrader 5. Caros leitores, se vocês conhecerem outras particularidades ao trabalhar com ticks de negociação, ou se eu estiver em algum lugar errado, escrevam, terei prazer em discutir esse tópico.

Como resultado, temos a seguinte imagem. Coluna azul — predominância de compradores na vela, coluna vermelha — predominância de vendedores.


Fig. 5. Indicador delta no instrumento RTS-6.18.

A estimativa de volumes reais abre grandes horizontes para analisar o mercado de ações e permite entender melhor os movimentos de preços. Este indicador é apenas uma pequena parte do que pode ser criado a partir da análise de dados de ticks. A criação de indicadores de bolsa baseados em volumes reais é uma tarefa bastante viável. Espero que este artigo o ajude na criação desses indicadores e melhore sua negociação.

Para aqueles que estão interessados no indicador em si, sua versão aprimorada poderá ser encontrada em breve na seção "Produtos" do meu perfil. Boa sorte com seus trades!

Arquivos usados no artigo

Nome do arquivoTipoDescrição
 1. Delta_article.mq5 Arquivo do indicador Implementação do indicador delta
 2. Ticks_article.mqh Arquivo da classe Classe auxiliar para trabalhar com dados de ticks
 3. test_getTicksRange.mq5 Arquivo do script Script de teste verificar se é possível receber vários pacotes de ticks num milissegundo
 4. test_tickPack.mq5 Arquivo do indicador Indicador de teste para verificar o recebimento de ticks no testador