English Русский 中文 Español Deutsch 日本語
ZigZag universal

ZigZag universal

MetaTrader 5Exemplos | 15 novembro 2016, 09:46
9 040 0
Dmitry Fedoseev
Dmitry Fedoseev

Conteúdo

Introdução

O Zigzag é um dos indicadores mais populares entre os usuários MetaTrader 5. Até à data, tem sido desenvolvidas uma muitas variantes deste indicador. No entanto, algumas delas são demasiado lentas, daí que sejam inadequadas para a criação de Experts Advisors. Outras retornam permanentemente erros, o que torna difícil usá-las mesmo para acompanhamento visual. É importante dizer que mesmo os indicadores que trabalham rápido e sem erros têm dificuldades ao serem usados para desenvolvimento de Experts ou outros indicadores. Na verdade, o Zigzag não permite extrair e interpretar facilmente suas leituras.


Fig. 1. Indicador ZigZag

Neste artigo, vamos refletir sobre o que é necessário para a construção do Zigzag, vamos falar sobre diferentes maneiras de construí-lo e, finalmente, vamos tirar conclusões e obter um algoritmo. Na base desse algoritmo será criado um indicador universal que vai permitir mediante a janela propriedades selecionar diferentes tipos de ZigZag.

Durante sua criação vamos usar programação orientada a objetos. Também vamos dar origem a algumas classes base para as diferentes fases de criação do ZigZag, cada uma delas será projetada para várias sub-classes. A divisão em classes base e sub-classes será levada a cabo de modo a simplificar a criação de várias novas variantes de ZigZags.

Além da construção do ZigZag, o artigo irá concentrar-se na utilização do indicador resultante para o desenvolvimento de outros indicadores e Expert Advisors. Nossa tarefa consiste em facilitar e acelerar a obtenção de dados a partir do ZigZag e seu uso como parte de outros algoritmos.

Características do indicador ZigZag

O indicador Zigzag consiste numa linha quebrada que liga os preços máximos e mínimos locais. Os iniciantes podem imediatamente pensar: "seria bom comprar nos fundos e vender nos picos!" Claro, essa ideia parece muito tentadora, mas, infelizmente, tal ZigZag parece atraente somente em histórias. Na realidade, a situação é um pouco diferente. Uma vez que a formação de um novo fundo ou pico se torna conhecida apenas depois de um certo número de barras após ela. Na Fig. 2, mostra-se que o indicador do último segmento interrompeu sua formação (mudança), o preço virou e se desloca na direção oposta (ascendente).

 
Fig. 2. ZigZag apontando para baixo e preço com reversão para cima

No entanto, após algumas barras, o preço cai (Fig. 3), e o último segmento do ZigZag continua a ser puxado para baixo.

  
Fig. 3. Preço continuando a se mover para baixo e último segmento do ZigZag seguindo sua formação

Desta vez, o indicador atingiu seu mínimo, mas teremos certeza disto apenas após algumas barras (Fig. 4).

 
Fig. 4. Só após dez barras, o ZigZag elaborou um novo segmento e se tornou conhecida a formação de fundos


A Fig. 4 dá uma explicação mais completa destas particularidades do Zigzag. Nela, os pontos coloridos marcam as barras onde se tornou conhecida a formação de seus picos ou fundos anteriores. Nas barras com um ponto azul, o indicador começou a elaborar novos segmentos para cima, enquanto nas barras com pontos vermelhos, os novos segmentos para baixo.


 Fig. 5. Os pontos vermelhos e azuis marcam as barras em que se tornou conhecida a reversão do ZigZag   

Apesar de essa particularidade, o Zigzag não perde sua popularidade e atrativo. No mínimo, ele facilita muito a análise visual dos gráficos, ajuda a filtrar o ruído e afeta a trajetória básica do movimento do preço. Num sentido mais prático, o indicador pode ser usado para detectar níveis de suporte/resistência e reconhecer padrões. Ele também pode desenhar linhas de tendência, como quaisquer outros instrumentos gráficos de análise técnica. É impossível enumerar todo o que na mente de um trader sofisticado pode surgir quanto à utilização do ZigZag.

Opções de construção do ZigZag

É óbvio que o ZigZag tem dois estados: ele dirigido para cima ou para baixo. Quando a linha do indicador está apontando para cima, nós monitoramos o preço em caso de aparecimento de uma nova alta, e quando a linha "olha" para baixo, estamos à espera de uma nova baixa. Precisamos também de monitorar a implementação de condições, ou seja, uma mudança de direção. Assim, a fim de construir um ZigZag, precisamos de três coisas.

  1. É indispensável obter os dados de origem. 
  2. É necessário formular as condições de alteração da direção da linha.  
  3. É preciso monitorar o surgimento de novos máximos e mínimos.

Os dados de origem podem ser um conjunto, por exemplo, o preço de fechamento da barra, ou dois conjuntos, por exemplo, os preços máximo e mínimo da barra. Se for usado apenas um conjunto de dados, ele pode ser não só o preço de fechamento, mas também praticamente qualquer indicador, quer oscilador, quer média móvel. Na plotagem do ZigZag, de acordo com os dados do indicador, também podem ser utilizados dois conjuntos de dados: um com os dados do indicador, traçado segundo os preços máximos de barras, e o outro, segundo os mínimos. 

As condições de mudança de direção são o ponto mais importante que define os diferentes tipos de ZigZags. Estas condições podem ser muito diferentes. Por exemplo, essas condições podem ser a formação - na barra emergente - de um máximo/mínimo de n-barra. Em outras palavras, se o valor do conjunto de saída - na barra emergente - é o máximo ou mínimo nas últimas n barras, isso determina a direção do ZigZag. De acordo com este princípio funciona o indicador clássico ZigZag. Outro método trata-se do recuo a partir do valor máximo ou mínimo fixado. O tamanho do recuo pode ser medido em pontos (se o conjunto de saída for o preço) ou em unidades convencionais (se se tratar de qualquer indicador). Além desses métodos, é possível determinar a direção empregando qualquer um indicador através de outros recursos como o estocástico ou o ADX. Se o estocástico está acima de 50, significa que o ZigZag está dirigido para cima, caso seja inferior a 50, para baixo. Agora vamos tentar determinar a direção para o ADX: a linha do ZigZag aponta para cima se a linha do PDI está acima da linha do MDI, caso contrário, para baixo. 

Assim, combinando as várias opções de acordo com os pontos 1 e 2, temos um monte de opções diferentes para o Zigzag, porque nada nos impede, segundo o ponto 1, utilizar dados, por exemplo, a partir do RSI, e empregar uma direção determinada pelo estocástico, etc. O ponto 3 é necessário apenas para que o indicador tenha, na verdade, aparência de ZigZag, embora as opções de plotagem possam ser muito variáveis. 

Uma vez que o nosso objetivo é fornecer um indicador universal, precisamos de mais cuidadosamente possível dividir o algoritmo em duas partes: a parte que é idêntica para todos ZigZags (vamos chamá-la de comum), e uma parte dependente do tipo de Zigzag (vamos chamá-la de individual). Na parte individual, são preenchidos os buffers de indicadores com dados de origem (de preço ou indicador) e um outro buffer (o qual determina a direção da linha do ZigZag) é preenchido com os valores 1 ou -1. Estes três buffers são transferidos para a parte comum que, com base neles, plota o próprio indicador.

Para deixar claro isto: primeiro criamos um indicador separado, que usa os preços high/low da barra e muda sua direção de acordo com o máximo/mínimo de n-barra.

ZigZag simples com base em high/low

No editor MetaEditor crie um novo indicador (Menu Principal — Arquivo — Criar ou pressione Ctrl+N). No Assistente de criação de novo indicador, digite "iHighLowZigZag", crie um parâmetro externo "period" (tipo int, valor 12), selecione o manipulador de eventos OnCalculate(...,open,high,low,close), crie um buffer chamado "ZigZag"(tipo Section, cor Red) e três buffers chamados "Direction", "LastHighBar" e "LastLowBar" (tipo line, cor none).

O buffer "ZigZag" será usado para exibir o ZigZag, os buffers restantes serão usados como auxiliares. Na função OnInit(), para todos os buffers auxiliares, ao chamar a função SetIndexBuffer(), substitua o tipo INDICATOR_DATA por INDICATOR_CALCULATIONS. Na parte superior do arquivo, altere o valor Propriedades indicator_plots: defina-o como 1. Após isto, o indicador só vai plotar um buffer "ZigZag" e no gráfico não haverá linhas extras, mas os buffers auxiliares estarão disponíveis para serem chamados a partir da função iCustom()

Em primeiro lugar, na função OnCalculate() calculamos o índice da barra, a partir da qual se deve iniciar o cálculo (variável start), de modo que o cálculo de todas as barras é realizado apenas após a execução do indicador, e no futuro seria apenas calculada cada nova barra. Além disso, inicializamos os elementos iniciais dos buffers:

  int start; // variável para o índice da barra a partir da qual se inicia o cálculo
  if(prev_calculated==0)
    { // na execução
     // inicialização de elementos iniciais de buffers
     DirectionBuffer[0]=0;
     LastHighBarBuffer[0]=0;
     LastLowBarBuffer[0]=0;
     start=1; // cálculo com os seguintes elementos após a inicialização
    }
  else
    { // durante a operação
     start=prev_calculated-1;
    }
}

Agora, o principal ciclo de indicador:

for(int i=start;i<rates_total;i++)
     {

Como descrito acima, a fim de alcançar a universalidade do código, é necessário dividir o código em cálculo de direção do ZigZage o sua plotagem. Em primeiro lugar, vou escrever o código para determinar a direção. Para determinar a direção, usamos as funções ArrayMaximum() e ArrayMinimum (). Se o máximo ou mínimo for detectado na barra calculada, o elemento de buffer Direction é definido como 1 ou -1. Para colocar, em cada barra, informações sobre a direção atual do ZigZag, antes de definir a direção, é preciso tomar o valor do elemento buffer Direction anterior e atribuí-lo ao elemento atual:

// a partir do elemento de buffer anterior obtemos
// valor da direção anteriormente definida
   DirectionBuffer[i]=DirectionBuffer[i-1];

// cálculo da barra inicial para funções
// ArrayMaximum() и ArrayMinimum()
   int ps=i-period+1;
// definição das barras de máximos e mínimos
// intervalo de period barras
   int hb=ArrayMaximum(high,ps,period);
   int lb=ArrayMinimum(low,ps,period);

// se o máximo ou mínimo for detectado
   if(hb==i && lb!=i)
     { // máximo detectado
      DirectionBuffer[i]=1;
     }
   else if(lb==i && hb!=i)
     { // mínimo detectado
      DirectionBuffer[i]=-1;
     }

Preste atenção ao último pedaço de código: nele é revelado o mínimo ou máximo, é feita a verificação para assegurar que na barra atual existe um máximo sem presença de mínimo ou vice-versa: existe um mínimo, mas não há máximo. Às vezes, há muito barras muitos longas, nelas são determinadas ambas as direções. Neste caso, no buffer Direction estará direção previamente determinada.

Geralmente, no terminal MetaTrader5, é possível criar um ZigZag que plote segmentos verticais podendo apresentar duas mudas de direção numa barra. No entanto, neste artigo não consideramos este tipo de ZigZags. 

Continuamos a escrever o código no ciclo principal: o seguinte fragmento será responsável pela plotagem da linha do ZigZag. Assim como fizemos com o buffer Direction, prosseguimos com os outros dois buffers:

LastHighBarBuffer[i]=LastHighBarBuffer[i-1];
LastLowBarBuffer[i]=LastLowBarBuffer[i-1];  

Nestes buffers, são colocados os dados sobre índices das barras a partir do último máximo ou mínimo do ZigZag. Para além do facto de que os índices de tais barras são necessários para a elaboração imediatamente do indicador, estes buffers também facilitam grandemente o processo de chamada de um Expert Advisor a partir do ZigZag. Nós não seremos forçados a iterar barras no ciclo em busca do último vértice.

Certifique-se de limpar o buffer para Zigzag:

ZigZagBuffer[i]=EMPTY_VALUE;  

Isso deve ser feito porque o cálculo completo do indicador não acontece apenas após a inicialização, mas também durante alguns outros eventos, por exemplo, ao carregar o histórico. No buffer, podem ficar dados antigos que distorcem a apresentação da linha do indicador.  

Vamos agora seguir diretamente para a plotagem. Aqui, o algoritmo divide-se em quatro ramos: o início de um novo movimento ascendente, o início de um novo movimento descendente, a continuação do movimento ascendente, a continuação do movimento descendente. Para verificar os valores de direção, na barra calculada e anterior, usamos o operadores switch:

switch((int)DirectionBuffer[i])
  {
   case 1:
      switch((int)DirectionBuffer[i-1])
        {
         case 1:
            // continuação do movimento ascendente
            ...
            break;
         case -1:
            // início de um novo movimento ascendente
            ...
            break;
        }
      break;
   case -1:
      switch((int)DirectionBuffer[i-1])
        {
         case -1:
            // continuação do movimento descendente
            ...
            break;
         case 1:
            // início de um novo movimento descendente    
            ...
            break;
        }
      break;

Resta escrever quatro blocos de código. Damos um olhar mais atento em dois deles: o início de um novo movimento ascendente e para a continuação do movimento ascendente. O início de um novo movimento ascendente acontece quanto, no buffer Direction, o valor muda de -1 para 1. Ao mesmo tempo, plotamos um novo ponto de ZigZag e armazenamos informações sobre o índice da barra em que começou a nova direção:

ZigZagBuffer[i]=high[i];
LastHighBarBuffer[i]=i;

A continuação do movimento é um pouco mais complicado. Verifica-se se o valor na barra atual é superior ao valor máximo previamente conhecido do ZigZag. Se for maior, será necessário deslocar o fim do último segmento, isto é, remover o ponto previamente desenhado e colocar um novo. Nós também mantemos informações sobre a barra, na qual é plotado o novo ponto:

// continuação do movimento ascendente
   if(high[i]>high[(int)LastHighBarBuffer[i]])
     { // novo máximo
      // removemos o ponto antigo do ZigZag
      ZigZagBuffer[(int)LastHighBarBuffer[i]]=EMPTY_VALUE;
      // colocamos um novo ponto
      ZigZagBuffer[i]=high[i];
      // índice da barra com um novo vértice
      LastHighBarBuffer[i]=i;
     }

Isto é tudo. Não se esqueça de fechar o ciclo com um parêntese de fechamento. Resta testar o indicador no testador no modo visual. O indicador totalmente pronto "iHighLowZigZag" pode ser encontrado no programa.

ZigZag simples com base em close

Agora refazemos o indicador que acabou de ser criado para operar segundo o preço close. Não há necessidade de fazer tudo novamente: salvamos o indicador "IHighLowZigZag" com o nome "iCloseZigZag" e substituímos o chamamento das matrizes high e low pelo chamamento da matriz close. Parece que isso é tudo, no entanto o teste mostra anomalias no indicador (Fig. 6).

 
Fig. 6. Trabalho impróprio no Zigzag segundo close refeito a partir de um indicador de high/low

Vamos considerar por que isso está acontecendo. Se o preço high da barra emergente formar um máximo em certo intervalo de barras, sem mudança do preço de fechamento da barra, este máximo permanecerá como máximo. Se o máximo formar um preço de fechamento, como a posterior formação da barra, ele pode ser alterado e o máximo deixará de existir. Ao ser definido um novo máximo/mínimo na mesma direção, será removido o ponto anterior, e aqui temos um problema. O novo máximo foi cancelado, temos um novo ponto start, mas outro ponto start antigo. Então, precisamos restaurar a posição dos antigos pontos. A informação sobre a localização do último extremo está localizada nos buffers: LastHighBarBuffer e LastLowBarBuffer. Destes, nós iremos restaurar os últimos dois pontos. No ciclo principal do indicador, antes do operadpr switch, adicionamos duas linhas de código:

ZigZagBuffer[(int)LastHighBarBuffer[i]]=close[(int)LastHighBarBuffer[i]];
ZigZagBuffer[(int)LastLowBarBuffer[i]]=close[(int)LastLowBarBuffer[i]];  

Após desta modificação, o indicador irá funcionar corretamente. O indicador resultante "iCloseZigZag" pode ser encontrado no anexo do artigo. 

Começando a construir um ZigZag universal

A versatilidade do ZigZag é conseguida pela solução separada de três tarefas:
  1. Preenchimento de buffers de dados de origem. Serão usados dois buffers. Eles são necessários para serem preenchidos com preços high e low. Para obter o ZigZag com base no close ou algum outro indicador, ambos buffers podem ser preenchidos com os mesmos valores. 
  2. Preenchimento do buffer Direction a partir da análise de dados de origem.
  3. Plotagem do ZigZag.
Cada tarefa será resolvida usando uma classe base individual e sub-classes adicionais, o que proporcionará uma escolha de diferentes opções e combinações das mesmas através da janela de propriedades de do indicador.

Classe de dados de origem

Crie o arquivo anexado "CSorceData.mqh", adicione a classe CSorceData nele. Ele se tornará a classe pai. Ele irá conter o método virtual Calculate, semelhante à função OnCalculate() do indicador, mas com algumas mudanças. Para o método são transferidos dois matrizes adicionais: BufferHigh[] e BufferLow[]. Estes buffers são preenchidos com dados, que no futuro serão calculados pelo ZigZag. Como não só o preço pode ser dado de origem, mas também o valor de qualquer outro indicador, precisaremos de verificar a carga do indicador. Para fazer isso, adicionamos o método virtual CheckHandle() (tipo bool):

class CSorceData
  {
private:
public:
   virtual int Calculate(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[],
                         double &BufferHigh[],
                         double &BufferLow[])
     {
      return(0);
     }
   virtual bool CheckHandle()
     {
      return(true);
     }

  };

Agora vamos criar algumas sub-classes. Uma, para o preço high/low:

class CHighLow:public CSorceData
  {
private:
public:
   int Calculate(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[],
                 double &BufferHigh[],
                 double &BufferLow[])
     {
      int start=0;
      if(prev_calculated!=0)
        {
         start=prev_calculated-1;
        }
      for(int i=start;i<rates_total;i++)
        {
         BufferHigh[i]=high[i];
         BufferLow[i]=low[i];
        }
      return(rates_total);
     }
  };

 A segunda será para o preço close. Ela vai diferir apenas pelo código no ciclo:

for(int i=start;i<rates_total;i++)
  {
   BufferHigh[i]=close[i];
   BufferLow[i]=close[i];
  }

Nome desta classe "CClose:public CSorceData". O método CheckHandle() não é usado por agora.

Além disso, criamos um par de classes para obter dados a partir dos indicadores. Nós escolhemos indicadores com um número diferente de parâmetros e diferente localização (num gráfico de preço ou numa janela separada), isto é, o RSI e a média móvel. Sob eles, nós escrevemos nossas classes.

Criamos uma classe para o RSI, cujo nome seja "CRSI:public CSorceData". Na seção private, adicionamos uma variável para identificar o indicador:

   private:
      int m_handle;

Adicionamos um construtor: nele serão transferidos os parâmetros do RSI, e nele será realizada o carregamento do indicador:

void CRSI(int period,ENUM_APPLIED_PRICE price)
  {
   m_handle=iRSI(Symbol(),Period(),period,price);
  }

Agora o método CheckHandle():

bool CheckHandle()
  {
   return(m_handle!=INVALID_HANDLE);
  }

No método Calculate, o ciclo não irá ser utilizado, simplesmente copiamos os buffers:

int to_copy;
   if(prev_calculated==0)
     {
      to_copy=rates_total;
     }
   else
     {
      to_copy=rates_total-prev_calculated;
      to_copy++;
     }

   if(CopyBuffer(m_handle,0,0,to_copy,BufferHigh)<=0)
     {
      return(0);
     }

   if(CopyBuffer(m_handle,0,0,to_copy,BufferLow)<=0)
     {
      return(0);
     }
   return(rates_total);

Repare que em caso de que a cópia falhe (da chamada de função CopyBuffer()) o método retorna 0, no caso de sucesso, rates_total. Isto é feito a fim de, em seguida, ter a oportunidade de efetuar o cálculo do indicador no caso de uma cópia sem sucesso. 

Da mesma forma, criamos uma classe - para a média móvel - chamada "CMA:public CSorceData". As diferenças irão estar apenas no construtor:

void CMA(int period,int shift,ENUM_MA_METHOD method,ENUM_APPLIED_PRICE price)
   {
    m_handle=iMA(Symbol(),Period(),period,shift,method,price);
   }

Os métodos Calculate(), neste caso, resultaram em ser iguais, mas para outros indicadores pode haver algumas diferenças, em particular, com o número de buffers. O arquivo "CSorceData.mqh" - totalmente pronto - pode ser encontrado no anexo do arquivo. Embora façamos uma ressalva dizendo que tudo está totalmente pronto, ele só pode ser considerado arbitrário, uma vez que implica uma nova expansão, adicionando novos sub-métodos para outros indicadores.

Classe de direção

A classe é localizada no arquivo "CZZDirection.mqh", o nome da classe base é "CZZDirection". A classe terá o método virtual Calculate(), em que os parâmetros são enviados para a determinação das barras de cálculo (variáveis​rates_total, prev_calculated), os buffers de dados de origem e buffer para direção. Anteriormente foi mencionado que a direção do ZigZag pode ser identificada segundo o indicador, por conseguinte, proporcionamos a possibilidade de utilizar indicadores. Adicionamos o método virtal CheckHandle():

class CZZDirection
  {
private:
public:
   virtual int Calculate(const int rates_total,
                         const int prev_calculated,
                         double &BufferHigh[],
                         double &BufferLow[],
                         double &BufferDirection[])
     {
      return(0);
     }
   virtual bool CheckHandle()
     {
      return(true);
     }
  };

Agora escrevemos a sub-classe para determinar a direção, como no indicador "iHighLowZigZag". Para determinar a direção deste método, é exigido o parâmetro "period", de modo que na seção private adicionamos a variável m_period e o construtor com o parâmetro do período:

class CNBars:public CZZDirection
  {
private:
   int               m_period;
public:
   void CNBars(int period)
     {
      m_period=period;
     }
   int Calculate(const int rates_total,
                 const int prev_calculated,
                 double &BufferHigh[],
                 double &BufferLow[],
                 double &BufferDirection[]
                 )
     {
      int start;

      if(prev_calculated==0)
        {
         BufferDirection[0]=0;
         start=1;
        }
      else
        {
         start=prev_calculated-1;
        }

      for(int i=start;i<rates_total;i++)
        {

         BufferDirection[i]=BufferDirection[i-1];

         int ps=i-m_period+1;
         int hb=ArrayMaximum(BufferHigh,ps,m_period);
         int lb=ArrayMinimum(BufferLow,ps,m_period);

         if(hb==i && lb!=i)
           { // máximo detectado
            BufferDirection[i]=1;
           }
         else if(lb==i && hb!=i)
           { // mínimo detectado
            BufferDirection[i]=-1;
           }

        }
      return(rates_total);
     }

Criamos outra sub-classe para definição da direções segundo o indicador CCI. A posição do CCI acima de zero corresponderá à direção do ZigZag para cima, a posição abaixo de zero - para baixo:

class CCCIDir:public CZZDirection
   {
private:
    int               m_handle;
public:
    void CCCIDir(int period,ENUM_APPLIED_PRICE price)
      {
       m_handle=iCCI(Symbol(),Period(),period,price);
      }
    bool CheckHandle()
      {
       return(m_handle!=INVALID_HANDLE);
      }
    int Calculate(const int rates_total,
                  const int prev_calculated,
                  double &BufferHigh[],
                  double &BufferLow[],
                  double &BufferDirection[]
                  )
      {
       int start;
       if(prev_calculated==0)
         {
          BufferDirection[0]=0;
          start=1;
         }
       else
         {
          start=prev_calculated-1;
         }

       for(int i=start;i<rates_total;i++)
         {

          BufferDirection[i]=BufferDirection[i-1];

          double buf[1];
          if(CopyBuffer(m_handle,0,rates_total-i-1,1,buf)<=0)return(0);

          if(buf[0]>0)
            {
             BufferDirection[i]=1;
            }
          else if(buf[0]<0)
            {
             BufferDirection[i]=-1;
            }
         }
       return(rates_total);
      }
   };

Para o construtor da classe, são transferidos os parâmetros do CCI e executado seu carregamento. É utilizado o método CheckHandle(), ele deve ser chamado depois que o objeto é criado. No ciclo principal, é executada a verificação do CCI e o preenchimento o buffer BufferDirection.

O arquivo "CZZDirection.mqh" pode ser encontrado no arquivo do arquivo. 

Classe de plotagem

Existem várias opções para plotagem do ZigZag. É possível desenhá-lo sob aforma de uma linha, é possível pintá-lo, colocar pontos nos topos, etc. Neste artigo vamos limitar-nos a uma forma de desenho, mas também vamos criar uma classe base e sub-classe em caso de modificação. A classe será localizada no arquivo "CZZDraw.mqh", nome da classe — "CZZDraw". A classe terá o método virtual Calculate() com os mesmos parâmetros da classe. Além disso, para ele serão transferidas três matrizes para Zigzag: BufferLastHighBar (para o índice do último máximo), BufferLastLowBar (para índice do último mínimo), BufferZigZag (o próprio ZigZag). 

class CZZDraw
  {
private:
public:
   virtual int Calculate(const int rates_total,
                         const int prev_calculated,
                         double &BufferHigh[],
                         double &BufferLow[],
                         double &BufferDirection[],
                         double &BufferLastHighBar[],
                         double &BufferLastLowBar[],
                         double &BufferZigZag[]
                         )
     {
      return(0);
     }
  };
Sub-classe: 
class CSimpleDraw:public CZZDraw
   {
private:
public:
    virtual int Calculate(const int rates_total,
                          const int prev_calculated,
                          double &BufferHigh[],
                          double &BufferLow[],
                          double &BufferDirection[],
                          double &BufferLastHighBar[],
                          double &BufferLastLowBar[],
                          double &BufferZigZag[]
                          )
      {
       int start;
       if(prev_calculated==0)
         {
          BufferLastHighBar[0]=0;
          BufferLastLowBar[0]=0;
          start=1;
         }
       else
         {
          start=prev_calculated-1;
         }

       for(int i=start;i<rates_total;i++)
         {
          BufferLastHighBar[i]=BufferLastHighBar[i-1];
          BufferLastLowBar[i]=BufferLastLowBar[i-1];

          BufferZigZag[i]=EMPTY_VALUE;

          BufferZigZag[(int)BufferLastHighBar[i]]=BufferHigh[(int)BufferLastHighBar[i]];
          BufferZigZag[(int)BufferLastLowBar[i]]=BufferLow[(int)BufferLastLowBar[i]];

          switch((int)BufferDirection[i])
            {
             case 1:
                switch((int)BufferDirection[i-1])
                  {
                   case 1:
                      if(BufferHigh[i]>BufferHigh[(int)BufferLastHighBar[i]])
                        {
                         BufferZigZag[(int)BufferLastHighBar[i]]=EMPTY_VALUE;
                         BufferZigZag[i]=BufferHigh[i];
                         BufferLastHighBar[i]=i;
                        }
                      break;
                   case -1:
                      BufferZigZag[i]=BufferHigh[i];
                      BufferLastHighBar[i]=i;
                      break;
                  }
                break;
             case -1:
                switch((int)BufferDirection[i-1])
                  {
                   case -1:
                      if(BufferLow[i]<BufferLow[(int)BufferLastLowBar[i]])
                        {
                         BufferZigZag[(int)BufferLastLowBar[i]]=EMPTY_VALUE;
                         BufferZigZag[i]=BufferLow[i];
                         BufferLastLowBar[i]=i;
                        }
                      break;
                   case 1:
                      BufferZigZag[i]=BufferLow[i];
                      BufferLastLowBar[i]=i;
                      break;
                  }
                break;
            }
         }
       return(rates_total);
      }
   };
Não vamos examinar esta classe, uma vez que tudo isto já foi descrito em "ZigZag simples segundo high/low" e "ZigZag simples segundo close". O arquivo "CZZDraw.mqh" pode ser encontrado no anexo do arquivo.

Colocando as três classes em conjunto

Finalmente, resta escrever o indicador usando as três classes criadas acima. A classe de dados de origem fornece a possibilidade de usar os dados de preços e os dados do indicador RSI que geralmente trabalha numa sub-janela. Os dados de preços podem ser exibidos numa sub-janela, no entanto, é impossível exibir o indicador RSI no gráfico. Então, vamos criar um indicador para a sub-janela. 

No editor MetaEditor, crie um indicador novo (Menu Principal — Arquivo — Criar ou pressione Ctrl+N). No Assistente de criação de novo indicador, digite "iHighLowZigZag", crie um parâmetro externo "period" (tipo int, valor 12), selecione o manipulador de eventos OnCalculate(...,open,high,low,close), crie so seguintes buffers: 

NomeEstiloCor
HighLineGreen
LowLineGreen
ZigZagSectionRed
DirectionLinenone
LastHighBarLine none 
LastLowBarLinenone
Após criar o novo indicador, conectamos a ele três arquivos com classes: 

#include <CSorceData.mqh>
#include <CZZDirection.mqh>
#include <CZZDraw.mqh>

No indicador deve haver parâmetros para selecionar o tipo de dados de origem e o tipo de definição da direção. Para isso, criamos duas enumerações:

enum ESorce
  {
   Src_HighLow=0,
   Src_Close=1,
   Src_RSI=2,
   Src_MA=3
  };
enum EDirection
  {
   Dir_NBars=0,
   Dir_CCI=1
  };

Criamos dois parâmetros externos desses tipos: 

input ESorce      SrcSelect=Src_HighLow;
input EDirection  DirSelect=Dir_NBars;

Para dados de origem do RSI e MA, são precisos os parâmetros apropriados, bem como para o indicador CCI. Adicionamo-los:

input int                  RSIPeriod   =  14;
input ENUM_APPLIED_PRICE   RSIPrice    =  PRICE_CLOSE;
input int                  MAPeriod    =  14;
input int                  MAShift     =  0;
input ENUM_MA_METHOD       MAMethod    =  MODE_SMA;
input ENUM_APPLIED_PRICE   MAPrice     =  PRICE_CLOSE;
input int                  CCIPeriod   =  14;
input ENUM_APPLIED_PRICE   CCIPrice    =  PRICE_TYPICAL;

Também precisamos um parâmetro para determinar a direção para as n-barras:

input int                  ZZPperiod   =  14;

Agora algo mais interessante, isto é, três ponteiros de acordo com os tipos de classe base (abaixo dos parâmetros externos):

CSorceData * src;
CZZDirection * dir;
CZZDraw * zz;

Na função OnInit, de acordo com a seleção das variáveis SrcSelect e DirSelect, carregamos as sub-classes correspondentes. Сначала SrcSelect:

switch(SrcSelect)
  {
   case Src_HighLow:
      src=new CHighLow();
      break;
   case Src_Close:
      src=new CClose();
      break;
   case Src_RSI:
      src=new CRSI(RSIPeriod,RSIPrice);
      break;
   case Src_MA:
      src=new CMA(MAPeriod,MAShift,MAMethod,MAPrice);
      break;
  }

Após o carregamento, verificamos o identificador:

if(!src.CheckHandle())
  {
   Alert("Erro ao carregar o indicador");
   return(INIT_FAILED);
  }

A seguir, o DirSelect:

switch(DirSelect)
  {
   case Dir_NBars:
      dir=new CNBars(ZZPeriod);
      break;
   case Dir_CCI:
      dir=new CCCIDir(CCIPeriod,CCIPrice);
      break;
  }

Verificação do identificador:

if(!dir.CheckHandle())
  {
   Alert("Erro ao carregar o indicador 2");
   return(INIT_FAILED);
  }

Terceira classe:

zz = new CSimpleDraw();

Na função OnDeinit(), removemos os objetos:

void OnDeinit(const int reason)
  {
   if(CheckPointer(src)==POINTER_DYNAMIC)
     {
      delete(src);
     }
   if(CheckPointer(dir)==POINTER_DYNAMIC)
     {
      delete(dir);
     }
   if(CheckPointer(zz)==POINTER_DYNAMIC)
     {
      delete(zz);
     }
  }

E finalmente, passamos para a função OnCalculate(). Os métodos Calculate() das classes CSorceData e CZZDirection podem retornar 0, por isso verificamos o resultado. Em caso de erro (um valor de 0), também retornamos 0, de modo que no próximo tick aconteça um novo re-cálculo:

int rv;

rv=src.Calculate(rates_total,
                 prev_calculated,
                 time,
                 open,
                 high,
                 low,
                 close,
                 tick_volume,
                 volume,
                 spread,
                 HighBuffer,
                 LowBuffer);

if(rv==0)return(0);

rv=dir.Calculate(rates_total,
                 prev_calculated,
                 HighBuffer,
                 LowBuffer,
                 DirectionBuffer);

if(rv==0)return(0);

zz.Calculate(rates_total,
             prev_calculated,
             HighBuffer,
             LowBuffer,
             DirectionBuffer,
             LastHighBarBuffer,
             LastLowBarBuffer,
             ZigZagBuffer);

return(rates_total);

O indicador "iUniZigZagSW" pode ser encontrado no anexo do artigo.

Opção para o gráfico de preços

No indicador resultante, estão todas as opções disponíveis anteriormente. Eu gostaria de ver o ZigZag no gráfico de preço. Neste caso, é necessário sacrificar a fonte de dados do RSI. Fazemos uma cópia do indicador "iUniZigZag", alteramos as propriedades indicator_separate_window em indicator_chart_window, da enumeração ESorce apagamos a opção Src_RSI, removemos a opção do RSI a partir da função OnInit() e obtemos a opção para opção para o gráfico de preço. O indicador "iUniZigZag" pronto pode ser encontrado no anexo do artigo.   

Opção para price

Para o terminal MetaTrader, é possível criar indicadores que funcionem não de acordo com dados de origem estritamente definidos, mas sim de acordo com qualquer outro indicador localizado no gráfico. Esse indicador - ao ser adicionado ao gráfico ou à sub-janela como parâmetro "aplicar a" - deve escolher "os dados do indicador anterior" ou "os dados do primeiro indicador". Refazemos indicador "iUniZigZagSW" de modo que possa ser "jogado" para outro indicador. Salvamos o indicador chamado "iUniZigZagPriceSW" e apagamos tudo o que está conectado com a classe CSorceData, alteramos o tipo de função OnCalculate, e, no início da função, escrevemos o ciclo para preenchimento dos buffers HighBuffer e LowBuffer com os valores da matriz price:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[]
                )
  {
   int start;
   if(prev_calculated==0)
     {
      start=0;
     }
   else
     {
      start=prev_calculated-1;
     }

   for(int i=start;i<rates_total;i++)
     {
      HighBuffer[i]=price[i];
      LowBuffer[i]=price[i];
     }
   int rv;
   rv=dir.Calculate(rates_total,
                    prev_calculated,
                    HighBuffer,
                    LowBuffer,
                    DirectionBuffer);

   if(rv==0)return(0);
   zz.Calculate(rates_total,
                prev_calculated,
                HighBuffer,
                LowBuffer,
                DirectionBuffer,
                LastHighBarBuffer,
                LastLowBarBuffer,
                ZigZagBuffer);
   return(rates_total);
  }

Da mesma forma, você pode criar uma versão que rode a partir de price, no gráfico de preço. Para fazer isso, basta, no indicador "iUniZigZagPriceSW", alterar a propriedade indicator_chart_window para indicator_separate_window. O indicador "iUniZigZagPriceSW" pode ser localizado no anexo, bem como o indicador iUniZigZagPrice — uma variante a partir do price para o gráfico de preço. 

Chamando a partir do Expert Advisor

Normalmente, ao chamar o ZigZag a partir do Expert Advisor, executa-se a busca do último pico ou fundo através do ciclo, a iteração das barras e verificação dos valores no buffer do ZigZag plotado. Tudo isso em conjunto opera de maneira muito lenta. O Zigzag desenvolvido neste artigo tem buffers adicionais que permitem receber rapidamente todos os dados necessários. No buffer DirectionBuffer, estão localizados os dados sobre a direção do último segmento do ZigZag. Nos buffers LastHighBarBuffer e LastLowBarBuffer, estão localizados os índices de barras, nos quais estão marcados o último pico e último fundo. Sabendo o índice da barra - durante a contagem de um lado e o número de barras - podemos calcular o índice da barra durante a contagem do outro lado (no indicador a contagem é de esquerda para a direita, e a função CopyBuffer() funciona com contagem da direita para a esquerda). Com o índice da barra, é possível obter o valor do ZigZag nesta barra.

O seguinte código pode ser usado para obter dados a partir do indicador. Nós experimentamos com o indicador "iUniZigZagSW". Na função OnInit() carregamos o indicador:

handle=iCustom(Symbol(),Period(),"iUniZigZagSW",SrcSelect,
               DirSelect,
               RSIPeriod,
               RSIPrice,
               MAPeriod,
               MAShift,
               MAMethod,
               MAPrice,
               CCIPeriod,
               CCIPrice,
               ZZPeriod);
Na função OnTick(), obtemos a direção e enviamos para os comentários do gráfico:
   string cs="";
// direção
   double dir[1];
   if(CopyBuffer(handle,3,0,1,dir)<=0)
     {
      Print("Erro de obtenção de dados a partir do ZigZag");
      return;
     }
   if(dir[0]==1)
     {
      cs=cs+"Direção para cima";
     }
   if(dir[0]==-1)
     {
      cs=cs+"Direção para baixo";
     }
   Comment(cs,"\n",GetTickCount());

Agora obtemos os valores dos últimos picos/fundos. Se a linha do indicador apontar para cima, obtemos o índice de barras a partir do último pico do buffer LastHighBarBuffer. Em seguida, usamo-la para calcular o índice da barra durante a contagem da direita para a esquerda. Usando este índice, obtém-se o valor do buffer ZigZagBuffer. Podemos ir mais longe: para a mesma barra onde o valor do ZigZag foi recebido, obtemos o valor a partir do buffer LastLowBarBuffer. Este será o índice da barra a partir do fundo anterior, e assim por diante. Agora, alternando tratamento de buffers LastHighBarBuffer e LastLowBarBuffer, podemos recolher dados sobre todos picos/fundos de linha do indicador. Abaixo está um exemplo do código para obter os dois últimos pontos do ZigZag quando ele esta direcionado para cima: 

if(dir[0]==1)
  {
   // índice da barra do último pico durante a contagem de zero à esquerda
   if(CopyBuffer(handle,4,0,1,lhb)<=0)
     {
      Print("Erro de obtenção de dados a partir do ZigZag 2");
      return;
     }
   // índice da barra durante a contagem a partir de zero
   ind=bars-(int)lhb[0]-1;

   // valor do ZigZag na barra ind
   if(CopyBuffer(handle,2,ind,1,zz)<=0)
     {
      Print("Erro de obtenção de dados a partir do ZigZag 3");
      return;
     }
   //===
   // índice de fundo anterior a este pico
   if(CopyBuffer(handle,5,ind,1,llb)<=0)
     {
      Print("Erro de obtenção de dados a partir do ZigZag 4");
      return;
     }
   // índice da barra durante a contagem a partir de zero
   ind=bars-(int)llb[0]-1;

   // valor do ZigZag na barra ind
   if(CopyBuffer(handle,2,ind,1,zz1)<=0)
     {
      Print("Erro de obtenção de dados a partir do ZigZag 5");
      return;
     }

   cs=cs+"\n"+(string)zz1[0]+" "+(string)zz[0];
  }
else if(dir[0]==-1)
  {

  }

Um exemplo completo pode ser encontrado no anexo do Expert Advisor nomeado "eUniZigZagSW". O Expert Advisor apresenta, no comentário do gráfico, uma mensagem sobre a direção do ZigZag, isto é, dois números com os valores dos dois últimos pontos do ZigZag (Fig. 7). A terceira cadeia de caracteres é simplesmente o número retornado pela função GetTickCount(), para deixar claro que o perito trabalha.

 
Fig. 7. No canto esquerdo, as mensagens apresentadas pelo Expert Advisor

Naturalmente, os dados sobre os dois últimos pontos do indicador podem ser obtidos a partir dos buffers LastHighBarBuffer e LastLowBarBuffer, tomando seus valores na primeira ou segunda barra, no entanto o sentido desse exemplo está na extração de dados a partir do número de pontos do ZigZag.

Chamada a partir de outro indicador

Se for necessário, com base num único indicador, fazer outro, então no caso do ZigZag será mais fácil fazer isso sem chamar o indicador através de iCustom(), fazendo uma cópia do mesmo e modificando. Em alguns casos, esta abordagem pode ser justificada (em termos de velocidade e a facilidade de processamento de execução)), em algumas outras, não ((em termos de flexibilidade e versatilidade do código). Os indicadores criados neste artigo permitem acessá-los através da função iCustom durante o desenvolvimento de outros indicadores.

O ZigZag no histórico não é o que era durante a formação desse histórico, no entanto nós temos os buffers LastHighBarBuffer e LastLowBarBuffer, nos quais são armazenados dados e estados intermediários do ZigZag. Para maior clareza, criamos um indicador que desenhe setas ao alterar a direção da linha do indicador (alteração do valor do buffer DirectionBuffer) e marque os pontos nas barras onde foram fixados os novos máximos/mínimos do ZigZag(alteração do valor dos buffers LastHighBarBuffer e LastLowBarBuffer). Não vamos examinar o código desse indicador, no anexo do artigo é possível encontralo com o nome "iUniZigZagSWEvents". O tipo de indicador mostrado na Fig. 8.

 
Fig. 8. Indicador iUniZigZagSWEvents

Conclusão

Como, em vez de fornecer soluções prontas e totalmente concluídas, o artigo é apenas material de estudo, todos os indicadores criados no artigo têm uma escolha mínima de dados de origem e tipos de determinação de direção. No entanto, o processo de criação de indicadores é analisado em grande detalhe, é por isso que após estudar o artigo, você poderá criar por si mesmo as sub-classes desejadas. Além disso, ao tentar obter de zero um indicador universal, surgem dificuldades não só durante a criação, mas também durante sua posterior utilização. Ao adicionar diferentes indicadores na qualidade de fontes de dados ou para definição de direção, na janela de propriedades é necessário adicionar os parâmetros destes indicadores. No final, o número de parâmetros se torna muito grande, e utilizar um indicador assim é muito desconfortável. Para maior ergonomia, será melhor criar indicadores separados usando as classes universais obtidas no artigo.       

Arquivos do aplicativo

  • iHighLowZigZag.mq5 — ZigZag simples com base em high/low.
  • iCloseZigZag.mq5 — ZigZag simples com base em close.
  • CSorceData.mqh — Classe de dados de origem
  • CZZDirection.mqh — Classe de direção do ZigZag.
  • CZZDraw.mqh — Classe de desenho do ZigZag.
  • iUniZigZagSW.mq5 — ZigZag universal para sub-janela.
  • iUniZigZag.mq5 — ZigZag universal para gráfico de preço.
  • iUniZigZagPriceSW.mq5 — ZigZag universal a partir de price para sub-janela.
  • iUniZigZagPrice.mq5 — ZigZag universal a partir de price para gráfico de preço. 
  • eUniZigZagSW — exemplo de chamada do indicador "iUniZigZagSW" a partir do Expert Advisor através da função iCustom().
  • iUniZigZagSWEvents — exemplo de criação de outro indicador com chamada do indicador "iUniZigZagSW" através da função iCustom(). 

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2774

Arquivos anexados |
mql5.zip (18.42 KB)
MQL5 Programações Básicas: Arquivos MQL5 Programações Básicas: Arquivos
Este artigo de orientação prática se concentra em trabalhar com arquivos no MQL5. Ele oferece uma série de tarefas simples, o qual nos permite compreender os conceitos básicos e aprimorar suas habilidades.
Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast(Build 3) Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast(Build 3)
Apresentamos neste artigo a próxima versão da biblioteca Easy And Fast (build 3). Foi corrigido certas falhas e adicionado novos recursos. Para maiores informações leia a continuação do artigo.
Interfaces Gráficas X: O Controle Gráfico Padrão (build 4) Interfaces Gráficas X: O Controle Gráfico Padrão (build 4)
Desta vez, nós vamos discutir o controle gráfico padrão. Ele permitirá criar arrays de objetos gráficos com a possibilidade de sincronizar o deslocamento horizontal. Além disso, nós continuaremos a otimizar o código da biblioteca para reduzir o consumo de recursos do CPU.
Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast (Build 2) Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast (Build 2)
Desde a publicação do artigo anterior da série, a biblioteca Easy And Fast tem recebido algumas funcionalidades novas. A estrutura e o código da biblioteca foram parcialmente otimizados, reduzindo ligeiramente a carga da CPU. Alguns métodos recorrentes em muitas classes de controle foram transferidos para a classe base CElement.