English Русский 中文 Español Deutsch 日本語
O poder do ZigZag (parte I). Desenvolvimento da classe base do indicador

O poder do ZigZag (parte I). Desenvolvimento da classe base do indicador

MetaTrader 5Exemplos | 8 março 2019, 07:22
5 162 0
Anatoli Kazharski
Anatoli Kazharski

Conteúdo


Introdução

Em um dos artigos anteriores, eu mostrei como um indicador como o Índice de Força Relativa (RSI) pode ser apresentado. Em uma de suas versões, o resultado obtido pode ser usado para receber simultaneamente os sinais para as condições de tendência e lateralizada. O indicador provavelmente carece de apenas uma coisa — a capacidade de definir o comportamento dos preços, o que também pode ser muito importante para decidir quando negociar e quando parar de negociar. 

Muitos pesquisadores simplesmente ignoram ou não prestam atenção o suficiente para determinar o comportamento do preço. Ao mesmo tempo, são usados métodos complexos, que muitas vezes são “caixas pretas”, como aprendizado de máquina ou redes neurais. A questão mais importante que surge nesse caso é quais dados enviar para o treinamento de um determinado modelo. Neste artigo, nós vamos expandir as ferramentas para esses estudos. Você descobrirá como selecionar os símbolos mais apropriados para negociação antes de procurar os parâmetros ideais. Para conseguir isso, nós usaremos uma versão modificada do indicador ZigZag e o código da classe que simplifica significativamente a obtenção e o trabalho com dados de indicadores pertencentes a esse tipo.

Nesta série de artigos, nós vamos implementar:

  • uma versão modificada do indicador ZigZag
  • uma classe para obter os dados do ZigZag
  • um EA para testar o processo de obtenção dos dados
  • indicadores que definem o comportamento do preço
  • um EA com uma interface gráfica para coletar as estatísticas de comportamento do preço
  • um EA seguidor dos sinais do ZigZag


Versão estendida do indicador ZigZag

Geralmente, os indicadores do tipo ZigZag são construídos com base nas máximas e mínimas das barras, sem considerar o spread. Este artigo apresenta uma versão modificada, na qual um spread é considerado na construção dos segmentos para os pontos extremos do ZigZag inferior. Supõe-se que os negócios devem ser realizados dentro do canal de preços no sistema de negociação. Isso é importante, pois muitas vezes acontece do preço de compra (ask) ser significativamente maior do que o de venda (bid). Por exemplo, isso pode acontecer durante a noite. Por isso, seria errado construir um indicador apenas com base nos preços de venda (bid). Afinal de contas, não faz sentido construir os pontos extremos inferiores do indicador baseados na mínima das barras se não houver a possibilidade de comprar a esses preços. É claro que o spread pode ser levado em conta nas condições de negociação, mas é melhor quando tudo é imediatamente visível no gráfico. Isso simplifica o desenvolvimento da estratégia de negociação, já que tudo é inicialmente mais plausível.

Além disso, você também pode querer ver todos os pontos em que os valores extremos do ZigZag foram atualizados. Neste caso, a imagem fica ainda mais completa. Agora vamos considerar o código do indicador. Nós vamos nos deter apenas nos recursos e funções básicas.

Nós vamos precisar de dois buffers do indicador para construir os segmentos. Uma é para as máximas (maior máxima), enquanto a outra é para mínimas (maior mínima). Eles devem ser exibidos como uma única linha no gráfico. Portanto, nós vamos precisar de seis buffers do indicador, cinco dos quais serão exibidos no gráfico.

Vamos listar todos os buffers do indicador:

  • Preço de Ask mínimo. Os valores mínimos do ZigZag devem ser baseados neles
  • Preço de Bid máximo. Os valores máximos do ZigZag devem ser baseados neles
  • Máximas
  • Mínimas
  • Todos as máximas detectadas de um segmento ascendente
  • Todas as mínimas detectadas de um segmento descendente

#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots   5
//---
#property indicator_color1  clrRed
#property indicator_color2  clrCornflowerBlue
#property indicator_color3  clrGold
#property indicator_color4  clrOrangeRed
#property indicator_color5  clrSkyBlue

//--- buffers do indicador:
double low_ask_buffer[];    // Preço mínimo de Ask
double high_bid_buffer[];   // Preço máximo de Bid
double zz_H_buffer[];       // Máximas
double zz_L_buffer[];       // Mínmias
double total_zz_h_buffer[]; // Todas as máximas
double total_zz_l_buffer[]; // Todas as mínimas

Vamos adicionar a capacidade de definir o número de barras (NumberOfBars) nos parâmetros de entrada, a fim de construir as linhas do indicador. Zero significa que todos os dados presentes no gráfico devem ser usados. O parâmetro MinImpulseSize define o número de pontos, pelo qual o preço deve desviar do último valor extremo para começar a construir um segmento direcionado de forma oposta. Além disso, vamos adicionar a capacidade de definir quais buffers do indicador devem ser exibidos no gráfico, bem como a cor dos segmentos do ZigZag, como parâmetros adicionais.

//--- Parâmetros de entrada
input int   NumberOfBars   =0;       // Número de barras
input int   MinImpulseSize =100;     // Pontos mínimos em um raio
input bool  ShowAskBid     =false;   // Exibir ask/bid
input bool  ShowAllPoints  =false;   // Exibir todos os pontos
input color RayColor       =clrGold; // Cor do raio

No nível global, declare as variáveis auxiliares necessárias para calcular os valores extremos. Nós precisamos salvar os índices dos pontos extremos calculados anteriormente, rastrear a direção do segmento atual, bem como salvar os preços mínimos de compra (ask) e o máximo de venda (bid).

//--- Variáveis ZZ
int    last_zz_max  =0;
int    last_zz_min  =0;
int    direction_zz =0;
double min_low_ask  =0;
double max_high_bid =0;

A função FillAskBidBuffers() é usada para preencher os buffers do indicador para a mínima do preço de ask e a máxima do preço de bid. Para o buffer de bid, salve os valores do array High, enquanto que para o buffer de ask, salve os valores do array Low considerando o spread.

//+-------------------------------------------------------------------+
//| Preencher os buffers da Máxima de Bid e mínima de Ask do indicador|
//+-------------------------------------------------------------------+
void FillAskBidBuffers(const int i,const datetime &time[],const double &high[],const double &low[],const int &spread[])
  {
//--- Sai se a data inicial não for atingida
   if(time[i]<first_date)
      return;
//---
   high_bid_buffer[i] =high[i];
   low_ask_buffer[i]  =low[i]+(spread[i]*_Point);
  }

A função FillIndicatorBuffers() serve para definir os pontos extremos do ZigZag. Os cálculos são realizados somente a partir da data especificada dependendo do número de barras definidas no parâmetro de entrada MinImpulseSize. O programa insere o bloco de código apropriado de acordo com a direção do segmento definida durante a chamada de função anterior.

As seguintes condições são verificadas para definir a direção:

  • A direção atual do segmento ascendente
    • O Bid máximo atual excede a última máximo:
      • Se essa condição for atendida, (1) reconfigure a máxima anterior, (2) lembre-se do índice do array de dados atual e (3) atribua o valor atual do Bid máximo aos elementos atuais dos buffers do indicador.
      • Se essa condição não for atendida, a direção do segmento mudou e está na hora de verificar as condições de formação do valor extremo inferior:
        • O Ask mínimo atual é menor que a última máxima
        • A distância entre o Ask mínimo atual e a última máxima do ZigZag excede o limite especificado (MinImpulseSize).
          • Se essas condições forem atendidas, (1) lembre-se do índice atual do array de dados, (2) salve a nova direção do segmento (descendente) na variável e (3) atribua o valor atual do Ask mínimo aos elementos atuais dos buffers do indicador.
  • A direção atual do segmento é para baixo
    • O Ask mínimo atual é menor que a última mínima:
      • Se esta condição for satisfeita, (1) resete a mínima anterior, (2) lembre-se do índice atual do array de dados e (3) atribua o valor atual de Ask mínimo aos elementos atuais dos buffers do indicador.
      • Se esta condição não for atendida, a direção do segmento mudou e é hora de verificar as condições de formação do valor extremo superior:
        • O Bid máximo atual excede a última mínima
        • A distância entre o Bid máximo atual e a última mínima do ZigZag excede o limite especificado (MinImpulseSize).
          • Se essas condições forem atendidas, (1) lembre-se do índice atual do array de dados, (2) salve a nova direção do segmento (ascendente) na variável e (3) atribua o valor atual de Bid máximo aos elementos atuais dos buffers do indicador.

O código da função FillIndicatorBuffers() pode ser visto em detalhes abaixo:

//+------------------------------------------------------------------+
//| Preenche os buffers do indicador ZZ                              |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   if(time[i]<first_date)
      return;
//--- Se ZZ se move para cima
   if(direction_zz>0)
     {
      //--- No caso de uma nova alta
      if(high_bid_buffer[i]>=max_high_bid)
        {
         zz_H_buffer[last_zz_max] =0;
         last_zz_max              =i;
         max_high_bid             =high_bid_buffer[i];
         zz_H_buffer[i]           =high_bid_buffer[i];
         total_zz_h_buffer[i]     =high_bid_buffer[i];
        }
      //--- Se a direção mudou (para baixo)
      else
        {
         if(low_ask_buffer[i]<max_high_bid && 
            fabs(low_ask_buffer[i]-zz_H_buffer[last_zz_max])>MinImpulseSize*_Point)
           {
            last_zz_min          =i;
            direction_zz         =-1;
            min_low_ask          =low_ask_buffer[i];
            zz_L_buffer[i]       =low_ask_buffer[i];
            total_zz_l_buffer[i] =low_ask_buffer[i];
           }
        }
     }
//--- Se ZZ se move para baixo
   else
     {
      //--- No caso de uma nova baixa
      if(low_ask_buffer[i]<=min_low_ask)
        {
         zz_L_buffer[last_zz_min] =0;
         last_zz_min              =i;
         min_low_ask              =low_ask_buffer[i];
         zz_L_buffer[i]           =low_ask_buffer[i];
         total_zz_l_buffer[i]     =low_ask_buffer[i];
        }
      //--- Se a direção mudou (para cima)
      else
        {
         if(high_bid_buffer[i]>min_low_ask && 
            fabs(high_bid_buffer[i]-zz_L_buffer[last_zz_min])>MinImpulseSize*_Point)
           {
            last_zz_max          =i;
            direction_zz         =1;
            max_high_bid         =high_bid_buffer[i];
            zz_H_buffer[i]       =high_bid_buffer[i];
            total_zz_h_buffer[i] =high_bid_buffer[i];
           }
        }
     }
  }

O código da função principal do indicador é exibido abaixo. O indicador é calculado apenas pelas barras formadas. Depois disso, são definidos (1) os arrays e as variáveis como zero, (2) o número de barras para cálculo e o índice inicial. Inicialmente, os dados de todos os elementos dos buffers do indicador são calculados e, após isso, são calculados somente os dados da última barra. Depois de realizar os cálculos e verificações preliminares, os buffers do indicador são calculados e preenchidos.

//+------------------------------------------------------------------+
//| Função de iteração do indicador personalizado                    |
//+------------------------------------------------------------------+
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[])
  {
//--- Evitar o cálculo a cada tick
   if(prev_calculated==rates_total)
      return(rates_total);
//--- Se este é o primeiro cálculo
   if(prev_calculated==0)
     {
      //--- Define os buffers do indicador para zero
      ZeroIndicatorBuffers();
      //--- Define as variáveis para zero
      ZeroIndicatorData();
      //--- Verifica a quantidade de dados disponíveis
      if(!CheckDataAvailable())
         return(0);
      //--- Se forem especificados mais dados para copiar, o valor atual é usado
      DetermineNumberData();
      //--- Define a plotagem de barras para cada símbolo a partir de 
      DetermineBeginForCalculate(rates_total);
     }
   else
     {
      //--- Calcula o último valor apenas
      start=prev_calculated-1;
     }
//--- Preenche os buffers do indicador High Bid e Low Ask
   for(int i=start; i<rates_total; i++)
      FillAskBidBuffers(i,time,high,low,spread);
//--- Preenche os buffers do indicador com dados
   for(int i=start; i<rates_total-1; i++)
      FillIndicatorBuffers(i,time);
//--- Retorna o tamanho do array de dados
   return(rates_total);
  }

O indicador no EURUSD D1 é exibido abaixo:

 Fig. 1. Indicador ZigZag modificado no EURUSD D1

Fig. 1. Indicador ZigZag modificado no EURUSD D1

A próxima captura de tela exibe o indicador no EURMXN M5. Aqui nós podemos ver o spread crescendo significativamente à noite. No entanto, o indicador é calculado considerando o spread.

 Fig. 2. Indicador ZigZag modificado no EURMXN M5

Fig. 2. Indicador ZigZag modificado no EURMXN M5

Na próxima seção, nós consideraremos um código da classe com métodos que nos ajudam a obter todos os dados necessários para definir o comportamento atual do preço.


Classe para obter os dados do indicador ZigZag

O preço se move de forma caótica e imprevisível. Movimentos lateralizados, quando o preço muda frequentemente de direção, podem ser repentinamente substituídos por longas tendências unidirecionais, sem retrocessos. É necessário monitorar sempre o estado atual, mas também é importante ter ferramentas para uma interpretação correta do comportamento do preço. Isto pode ser alcançado pelo código da classe CZigZagModule com todos os métodos necessários para trabalhar com os dados do ZigZag. Vamos ver como isso funciona.

Como nós podemos trabalhar com várias instâncias da classe simultaneamente, por exemplo, com dados do ZigZag de diferentes períodos de tempo, talvez seja necessário visualizar os segmentos obtidos usando as linhas de tendência de cores diferentes. Portanto, nós incluímos o arquivo ChartObjectsLines.mqh da biblioteca padrão para o arquivo da classe CZigZagModule. A partir deste arquivo, nós vamos precisar da classe CChartObjectTrend para trabalhar com as linhas de tendência. A cor das linhas de tendência pode ser especificada pelo método público CZigZagModule::LinesColor(). A cor cinza (clrGray) é definida por padrão.

//+------------------------------------------------------------------+
//|                                                 ZigZagModule.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsLines.mqh>
//+------------------------------------------------------------------+
//| Classe para a obtenção de dados do indicador ZigZag              |
//+------------------------------------------------------------------+
class CZigZagModule
  {
protected:
   //--- linhas de segmento
   CChartObjectTrend m_trend_lines[];

   //--- Cor das linhas segmentadas
   color             m_lines_color;
   //---
public:
                     CZigZagModule(void);
                    ~CZigZagModule(void);
   //---
public:
   //--- Cor da linha
   void              LinesColor(const color clr) { m_lines_color=clr; }
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_lines_color(clrGray)
  {
// ...
  }
//+------------------------------------------------------------------+
//| Destrutor                                                        |
//+------------------------------------------------------------------+
CZigZagModule::~CZigZagModule(void)
  {
  }

Antes de obter os dados do indicador ZigZag, nós precisamos definir o número de valores extremos necessários para o trabalho. Para conseguir isso, nós devemos chamar o método CZigZagModule::CopyExtremums(). Os arrays dinâmicos separados foram declarados para armazenar (1) as extremidades do preços, (2) índices das barras extremas, (3) o tempo de suas barras e o (4) número de segmentos para construção das linhas de tendência em um gráfico. O tamanho dos arrays é definido no mesmo método.

O número de segmentos é calculado automaticamente a partir do número de extremos especificados. Por exemplo, se nós passarmos 1 para o método CZigZagModule::CopyExtremums(), nós recebemos os dados em uma máxima e mínima. Neste caso, ele é apenas um segmento do indicador ZigZag. Se um valor maior que 1 for passado, o número de segmentos é sempre igual ao número de extremos copiados multiplicado por 2 menos 1. Em outras palavras, o número de segmentos sempre será ímpar:

  • Um extremo - 1 segmento
  • Dois extremos - 3 segmentos
  • Três extremos - 5 segmentos, etc.
class CZigZagModule
  {
protected:
   int               m_copy_extremums;    // Número de máximas/mínimas salvas
   int               m_segments_total;    // Número de segmentos
   //--- Preços extremos
   double            m_zz_low[];
   double            m_zz_high[];
   //--- Índices das barras extremas
   int               m_zz_low_bar[];
   int               m_zz_high_bar[];
   //--- Horário das barras extremas
   datetime          m_zz_low_time[];
   datetime          m_zz_high_time[];
   //---
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_copy_extremums(1),
                                     m_segments_total(1)
  {
   CopyExtremums(m_copy_extremums);
  }

//+------------------------------------------------------------------+
//| Número de extremos para trabalhar                                |
//+------------------------------------------------------------------+
void CZigZagModule::CopyExtremums(const int total)
  {
   if(total<1)
      return;
//---
   m_copy_extremums =total;
   m_segments_total =total*2-1;
//---
   ::ArrayResize(m_zz_low,total);
   ::ArrayResize(m_zz_high,total);
   ::ArrayResize(m_zz_low_bar,total);
   ::ArrayResize(m_zz_high_bar,total);
   ::ArrayResize(m_zz_low_time,total);
   ::ArrayResize(m_zz_high_time,total);
   ::ArrayResize(m_trend_lines,m_segments_total);
  }

Antes de nós começarmos a trabalhar com os dados do indicador ZigZag, coloque-os nos arrays da classe descrita acima para um uso mais conveniente. Nós vamos precisar de campos auxiliares para serem usados como contadores de extremos. 

Para obter os dados, nós precisamos do método CZigZagModule::GetZigZagData(). É necessário passar ele o array contendo os dados iniciais do indicador ZigZag junto com o array de tempo. Estes dados de origem podem ser obtidos usando as funções CopyBuffer() e CopyTime(). Antes de obter os valores necessários dos dados de origem, todos os campos e arrays devem ser redefinidos. Em seguida, obtenha o número especificado dos extremos (1) dos preços, (2) dos índices das barras e (3) do horário. 

A direção do segmento atual é definida no final do método. Aqui, se o horário do segmento da máxima atual exceder o da mínima, a direção é para cima. Caso contrário, é para baixo.

class CZigZagModule
  {
protected:
   int               m_direction;         // Direção
   int               m_counter_lows;      // Contador da Mínima
   int               m_counter_highs;     // Contado da Máxima
   //---
public:
   //--- Obtém os dados
   void              GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[]);
   //--- Redefine a estrutura
   void              ZeroZigZagData(void);
  };
//+------------------------------------------------------------------+
//| Obtém os dados do ZigZag                                         |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[])
  {
   int h_total =::ArraySize(zz_h);
   int l_total =::ArraySize(zz_l);
   int total   =h_total+l_total;
//--- Redefine as variáveis do ZZ
   ZeroZigZagData();
//--- Mover ao longo dos valores copiados de ZZ em um loop
   for(int i=0; i<total; i++)
     {
      //--- Se o número necessário de máximas e mínimas do ZZ já for recebido, saia do loop
      if(m_counter_highs==m_copy_extremums && m_counter_lows==m_copy_extremums)
         break;
      //--- Gerencia o avanço além do array
      if(i>=h_total || i>=l_total)
         break;
      //--- Preenche o array contendo o valor da máxima até que a quantia necessária seja copiada
      if(zz_h[i]>0 && m_counter_highs<m_copy_extremums)
        {
         m_zz_high[m_counter_highs]      =zz_h[i];
         m_zz_high_bar[m_counter_highs]  =i;
         m_zz_high_time[m_counter_highs] =time[i];
         //---
         m_counter_highs++;
        }
      //--- Preenche o array contendo o valor da mínima até que o valor necessário seja copiado
      if(zz_l[i]>0 && m_counter_lows<m_copy_extremums)
        {
         m_zz_low[m_counter_lows]      =zz_l[i];
         m_zz_low_bar[m_counter_lows]  =i;
         m_zz_low_time[m_counter_lows] =time[i];
         //---
         m_counter_lows++;
        }
     }
//--- Define a direção do movimento do preço
   m_direction=(m_zz_high_time[0]>m_zz_low_time[0])? 1 : -1;
  }

Agora que os dados foram recebidos, nós podemos considerar outros métodos dessa classe. Para obter os preços extremos, índices da barra extrema e o horário das barras em que esses extremos foram formados, basta chamar o método apropriado (veja o código abaixo) especificando um índice extremo. Apenas o código do método CZigZagModule::LowPrice() é fornecido aqui como exemplo, uma vez que são quase idênticos.

class CZigZagModule
  {
public:
   //--- Preço dos extremos por um índice especificado
   double            LowPrice(const int index);
   double            HighPrice(const int index);
   //--- Índice de uma barra extrema por um índice especificado
   int               LowBar(const int index);
   int               HighBar(const int index);
   //--- Horário de uma barra extrema por um índice especificado
   datetime          LowTime(const int index);
   datetime          HighTime(const int index);
  };
//+------------------------------------------------------------------+
//| Valor da mínima pelo índice especificado                         |
//+------------------------------------------------------------------+
double CZigZagModule::LowPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_low))
      return(0.0);
//---
   return(m_zz_low[index]);
  }

Se você precisar obter o tamanho do segmento, chame o método CZigZagModule::SegmentSize() especificando o índice do segmento como o único parâmetro. Dependendo se o índice especificado for um valor par ou ímpar, os índices extremos são definidos de acordo com o tamanho do segmento calculado. Se o valor do índice for par, os índices extremos combinam entre si e não precisam ser calculados dependendo da direção do segmento.

class CZigZagModule
  {
public:
   //--- Tamanho do segmento por um índice especificado
   double            SegmentSize(const int index);
  };
//+------------------------------------------------------------------+
//| Retorna o tamanho do segmento pelo índice                        |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentSize(const int index)
  {
   if(index>=m_segments_total)
      return(-1);
//---
   double size=0;
//--- Se o valor for par
   if(index%2==0)
     {
      int i=index/2;
      size=::fabs(m_zz_high[i]-m_zz_low[i]);
     }
//--- Se o valor for ímpar
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      size=::fabs(m_zz_high[h]-m_zz_low[l]);
     }
//---
   return(size);
  }

O método CZigZagModule::SegmentsSum() é usado para obter a soma de todos os segmentos. Tudo aqui é simples, já que o método CZigZagModule::SegmentSize() descrito acima é chamado quando ele passa ao longo de todos os segmentos em um loop.

class CZigZagModule
  {
public:
   //--- Soma de todos os segmentos
   double            SegmentsSum(void);
  };
//+------------------------------------------------------------------+
//| Tamanho total de todos os segmentos                              |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentsSum(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_segments_total; i++)
      sum+=SegmentSize(i);
//---
   return(sum);
  }

Além disso, talvez seja necessário obter a soma de todos os segmentos direcionados apenas para cima ou para baixo. O código para segmentos ascendentes é exibido abaixo como um exemplo. Tudo depende da direção do segmento atual. Se for direcionado para cima, os índices atuais são usados em um loop para os cálculos. Se a direção atual for para baixo, o cálculos devem ser iniciados a partir do primeiro índice com um deslocamento de um elemento para trás para as máximas. Se você quiser obter a soma de todos os segmentos direcionados para baixo, use o mesmo método, tendo uma única diferença se a direção atual for para cima, na qual o deslocamento acontece para as mínimas.

class CZigZagModule
  {
public:
   //--- Soma dos segmentos direcionados (1) para cima e (2) para baixo
   double            SumSegmentsUp(void);
   double            SumSegmentsDown(void);
  };
//+------------------------------------------------------------------+
//| Retorna o tamanho de todos os segmentos ascendentes              |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsUp(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()>0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i-1]-m_zz_low[i]);
        }
     }
//---
   return(sum);
  }

Pode ser útil obter uma taxa percentual das somas de segmentos unidirecionais para o número total de segmentos no conjunto. Para conseguir isso, use os métodos CZigZagModule::PercentSumSegmentsUp() e CZigZagModule::PercentSumSegmentsDown(). Eles permitem obter a diferença percentual destas taxas — o método CZigZagModule::PercentSumSegmentsDifference(), que por sua vez pode nos mostrar a direção do preço atual (tendência). Se a diferença é insignificante, então o preço flutua uniformemente em ambas as direções (lateralizado). 

class CZigZagModule
  {
public:
   //--- Taxa percentual dos segmentos somados ao número total de todos os segmentos no conjunto
   double            PercentSumSegmentsUp(void);
   double            PercentSumSegmentsDown(void);
   //--- Diferença entre as somas dos segmentos
   double            PercentSumSegmentsDifference(void);
  };
//+------------------------------------------------------------------+
//| Retorna o percentual da soma de todos os segmentos de alta       |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsUp(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsDown()/sum*100);
  }
//+------------------------------------------------------------------+
//| Retorna o percentual da soma de todos os segmentos de baixa      |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDown(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsUp()/sum*100);
  }
//+------------------------------------------------------------------+
//| Retorna a diferença da soma de todos os segmentos em percentual  |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDifference(void)
  {
   return(::fabs(PercentSumSegmentsUp()-PercentSumSegmentsDown()));
  }

Para definir o comportamento do preço, nós precisamos de métodos para obter a duração dos segmentos separados e todo o conjunto resultante. O método CZigZagModule::SegmentBars() serve para obter o número de barras no segmento especificado. A lógica do código do método é a mesma que a do método CZigZagModule::SegmentSize() para obter o tamanho do segmento. Portanto, não faz sentido fornecer o seu código aqui. 

Para obter o número total de barras no conjunto de dados obtidos, use o método CZigZagModule::SegmentsTotalBars(). Aqui, são definidos os índices da barra inicial e final no conjunto e retornado a diferença. O método CZigZagModule::SegmentsTotalSeconds() segue o mesmo princípio. A única diferença é que ele retorna o número de segundos no conjunto. 

class CZigZagModule
  {
public:
   //--- Número de barras em um segmento especificado
   int               SegmentBars(const int index);
   //--- (1) Número de barras e (2) segundos no conjunto de segmentos
   int               SegmentsTotalBars(void);
   long              SegmentsTotalSeconds(void);
  };
//+------------------------------------------------------------------+
//| Número de barras de todos os segmentos                           |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentsTotalBars(void)
  {
   int begin =0;
   int end   =0;
   int l     =m_copy_extremums-1;
//---
   begin =(m_zz_high_bar[l]>m_zz_low_bar[l])? m_zz_high_bar[l] : m_zz_low_bar[l];
   end   =(m_zz_high_bar[0]>m_zz_low_bar[0])? m_zz_low_bar[0] : m_zz_high_bar[0];
//---
   return(begin-end);
  }
//+------------------------------------------------------------------+
//| Número de segundos de todos os segmentos                         |
//+------------------------------------------------------------------+
long CZigZagModule::SegmentsTotalSeconds(void)
  {
   datetime begin =NULL;
   datetime end   =NULL;
   int l=m_copy_extremums-1;
//---
   begin =(m_zz_high_time[l]<m_zz_low_time[l])? m_zz_high_time[l] : m_zz_low_time[l];
   end   =(m_zz_high_time[0]<m_zz_low_time[0])? m_zz_low_time[0] : m_zz_high_time[0];
//---
   return(long(end-begin));
  }

Muitas vezes, pode ser necessário descobrir a faixa de preços dentro do conjunto de dados observado. Para essa finalidade, a classe apresenta métodos para obter os extremos mínimo e máximo, bem como a diferença entre eles (faixa de preço).

class CZigZagModule
  {
public:
   //--- Valores da (1) mínima e (2) máxima do conjunto
   double            LowMinimum(void);
   double            HighMaximum(void);
   //--- Faixa de preço
   double            PriceRange(void);
  };
//+------------------------------------------------------------------+
//| Valor mínimo do conjunto                                         |
//+------------------------------------------------------------------+
double CZigZagModule::LowMinimum(void)
  {
   return(m_zz_low[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Valor máximo do conjunto                                         |
//+------------------------------------------------------------------+
double CZigZagModule::HighMaximum(void)
  {
   return(m_zz_high[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| Faixa de preço                                                   |
//+------------------------------------------------------------------+
double CZigZagModule::PriceRange(void)
  {
   return(HighMaximum()-LowMinimum());
  }

Ainda há outro conjunto de métodos da classe CZigZagModule que permite receber valores como:

  • SmallestSegment() – retorna o menor segmento nos dados obtidos.
  • LargestSegment() – retorna o maior segmento nos dados obtidos. 
  • LeastNumberOfSegmentBars() – retorna o menor número de barras em um segmento nos dados obtidos.
  • MostNumberOfSegmentBars() – retorna o maior número de barras em um segmento nos dados obtidos.

A classe já possui os métodos para obtenção do tamanho dos segmentos e o número de barras do segmento pelo índice especificado. Portanto, será fácil entender o código dos métodos da lista acima. Todos eles são diferentes apenas nos métodos chamados dentro deles, portanto, eu vou fornecer os códigos de apenas dois deles — CZigZagModule::SmallestSegmen() e CZigZagModule::MostNumberOfSegmentBars().

class CZigZagModule
  {
public:
   //--- O menor segmento do conjunto
   double            SmallestSegment(void);
   //--- O maior segmento do conjunto
   double            LargestSegment(void);
   //--- O menor número de barras de um segmento no conjunto
   int               LeastNumberOfSegmentBars(void);
   //--- O maior número de barras de um segmento no conjunto 
   int               MostNumberOfSegmentBars(void);
  };
//+------------------------------------------------------------------+
//| O menor segmento do conjunto                                     |
//+------------------------------------------------------------------+
double CZigZagModule::SmallestSegment(void)
  {
   double min_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      min_size=(size<min_size)? size : min_size;
     }
//---
   return(min_size);
  }
//+------------------------------------------------------------------+
//| O maior número de barras de um segmento no conjunto              |
//+------------------------------------------------------------------+
int CZigZagModule::MostNumberOfSegmentBars(void)
  {
   int max_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      max_bars=(bars>max_bars)? bars : max_bars;
     }
//---
   return(max_bars);
  }

Ao buscar por padrões, nós podemos precisar definir quanto um segmento específico difere em tamanho (em %) do anterior. Para resolver essas tarefas, use o método CZigZagModule::PercentDeviation().

class CZigZagModule
  {
public:
   //--- Desvio em porcentagem
   double            PercentDeviation(const int index);
  };
//+------------------------------------------------------------------+
//| Desvio em percentagem                                            |
//+------------------------------------------------------------------+
double CZigZagModule::PercentDeviation(const int index)
  {
   return(SegmentSize(index)/SegmentSize(index+1)*100);
  }

Agora vamos ver como visualizar os dados obtidos e usar a classe CZigZagModule nos projetos personalizados.


Visualização do conjunto de dados obtidos

Depois de receber os identificadores do indicador ZigZag de diferentes períodos de tempo, nós podemos visualizar segmentos no gráfico atual no qual o EA é iniciado. Vamos usar os objetos gráficos do tipo linha de tendência para visualização. O método privado CZigZagModule::CreateSegment() é usado para criar objetos. Ele recebe o índice do segmento e o sufixo (parâmetro opcional) usado para formar um nome exclusivo do objeto gráfico para evitar duplicações no caso de você precisar exibir os dados do indicador do ZigZag com parâmetros diferentes e de diferentes períodos gráficos. 

Os métodos públicos CZigZagModule::ShowSegments() e CZigZagModule::DeleteSegments() permitem exibir e remover objetos gráficos.

class CZigZagModule
  {
public:
   //--- (1) Exibe e (2) exclui os objetos
   void              ShowSegments(const string suffix="");
   void              DeleteSegments(void);
   //---
private:
   //--- Cria objetos
   void              CreateSegment(const int segment_index,const string suffix="");
  };
//+------------------------------------------------------------------+
//| Exibe os segmentos ZZ em um gráfico                              |
//+------------------------------------------------------------------+
void CZigZagModule::ShowSegments(const string suffix="")
  {
   for(int i=0; i<m_segments_total; i++)
      CreateSegment(i,suffix);
  }
//+------------------------------------------------------------------+
//| Remove os segmentos                                              |
//+------------------------------------------------------------------+
void CZigZagModule::DeleteSegments(void)
  {
   for(int i=0; i<m_segments_total; i++)
     {
      string name="zz_"+string(::ChartID())+"_"+string(i);
      ::ObjectDelete(::ChartID(),name);
     }
  }

Os métodos para exibir os comentários em um gráfico foram adicionados à classe para obter rapidamente as informações básicas sobre os dados dos indicadores obtidos. O código do método que mostra brevemente os dados do indicador calculado é exibido abaixo.

 class CZigZagModule
  {
public:
   //--- Comentário no gráfico
   void              CommentZigZagData();
   void              CommentShortZigZagData();
  };
//+------------------------------------------------------------------+
//| Exibe os dados do ZigZag como um comentário no gráfico           |
//+------------------------------------------------------------------+
void CZigZagModule::CommentShortZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2)+
                  "\n---\n"+
                  "SmallestSegment(): "+::DoubleToString(SmallestSegment()/_Point,0)+"\n"+
                  "LargestSegment(): "+::DoubleToString(LargestSegment()/_Point,0)+"\n"+
                  "LeastNumberOfSegmentBars(): "+string(LeastNumberOfSegmentBars())+"\n"+
                  "MostNumberOfSegmentBars(): "+string(MostNumberOfSegmentBars());
//---
   ::Comment(comment);
  }

Vamos desenvolver um aplicativo para receber e visualizar os dados obtidos.


EA para testar os resultados obtidos

Vamos desenvolver um EA de teste simples para receber e visualizar os dados do indicador ZigZag. Nós não vamos realizar as verificações adicionais para simplificar o código ao máximo possível. O principal objetivo do exemplo é demonstrar o próprio princípio de obtenção dos dados. 

Inclua o arquivo que contém a classe CZigZagModule para o arquivo do EA e declare sua instância. Existem dois parâmetros externos que permitem especificar o número de extremos a serem copiados e a distância mínima para formar um novo segmento do indicador ZigZag. No nível global, nós também declaramos os arrays dinâmicos para obter os dados de origem e uma variável para o manipulador do indicador. 

//+------------------------------------------------------------------+
//|                                                    TestZZ_01.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ZigZagModule.mqh>
CZigZagModule zz_current;

//--- Parâmetros de entrada
input int CopyExtremum   =3;
input int MinImpulseSize =0;

//--- Arrays para os dados iniciais
double   l_zz[];
double   h_zz[];
datetime t_zz[];

//--- Manipulador do indicador ZZ
int zz_handle_current=WRONG_VALUE;

Na função OnInit(), nós (1) recebemos o manipulador do indicador, (2) definimos o número de extremos para formar os dados finais e a cor das linhas de segmento do conjunto obtido, bem como (3) definimos a ordem de indexação reversa para os arrays de dados de origem.

//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Caminho para o indicador ZZ
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- Obtém o manipulador do indicador
   zz_handle_current=::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,true,true);
//--- Define a cor para os segmentos e o número de extremos para obtê-lo
   zz_current.LinesColor(clrRed);
   zz_current.CopyExtremums(CopyExtremum);
//--- Define a ordem de indexação reversa (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

Na função OnTick(), nós obtemos os dados do indicador pelo seu manipulador e o horário de abertura das barras. Então, preparamos os dados finais chamando o método CZigZagModule::GetZigZagData(). Finalmente, nós visualizamos os segmentos dos dados obtidos do indicador ZigZag e exibimos esses dados no gráfico como um comentário.

//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- Obtém os dados de origem
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
//--- Obtém os dados finais
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
//--- Visualiza os segmentos em um gráfico
   zz_current.ShowSegments();
//--- Exibe os valores dos dados no gráfico como um comentário
   zz_current.CommentZigZagData();
  }

Se nós executarmos o EA no testador de estratégia no modo de visualização, nós veremos o seguinte. 5 extremos de alta e baixa foram obtidos nesse caso. Como resultado, 9 segmentos foram destacados em vermelho no gráfico.

 Fig. 3. Demonstração no modo de visualização (um ZigZag)

Fig. 3. Demonstração no modo de visualização (um ZigZag)

Se nós precisarmos obter os dados do indicador ZigZag de diferentes períodos de tempo ao mesmo tempo, o código do EA de teste deverá ser um pouco aprimorado. Vamos considerar um exemplo quando você precisa obter os dados de três períodos gráficos. Neste caso, você precisa declarar três instâncias da classe CZigZagModule. O primeiro período gráfico é retirado do gráfico atual no qual o EA foi iniciado. Deixe dois outros ser, por exemplo, M15 e H1.

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_current;
CZigZagModule zz_m15;
CZigZagModule zz_h1;

Cada indicador tem sua própria variável para obter o manipulador:

//--- Manipulador do indicador ZZ
int zz_handle_current =WRONG_VALUE;
int zz_handle_m15     =WRONG_VALUE;
int zz_handle_h1      =WRONG_VALUE;

Em seguida, na função OnInit(), nós recebemos os manipuladores de forma separada para cada indicador, definindo cores e o número de extremos:

//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Caminho para o indicador ZZ
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- Obtemos os manipuladores do indicador
   zz_handle_current =::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_m15     =::iCustom(_Symbol,PERIOD_M15,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_h1      =::iCustom(_Symbol,PERIOD_H1,zz_path,10000,MinImpulseSize,false,false);
//--- Define a cor dos segmentos
   zz_current.LinesColor(clrRed);
   zz_m15.LinesColor(clrCornflowerBlue);
   zz_h1.LinesColor(clrGreen);
//--- Define o número de extremos para receber
   zz_current.CopyExtremums(CopyExtremum);
   zz_m15.CopyExtremums(CopyExtremum);
   zz_h1.CopyExtremums(CopyExtremum);
//--- Define a ordem de indexação invertida (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

Os dados são recebidos na função OnTick() como é exibido acima para cada instância do indicador ZigZag de forma separada. Os comentários de apenas um indicador podem ser exibidos no gráfico. Nesse caso, nós olhamos os dados brevemente para o período gráfico atual do indicador.

//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
   zz_current.ShowSegments("_current");
   zz_current.CommentShortZigZagData();
//---
   ::CopyTime(_Symbol,PERIOD_M15,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_m15,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_m15,3,0,copy_total,l_zz);
   zz_m15.GetZigZagData(h_zz,l_zz,t_zz);
   zz_m15.ShowSegments("_m15");
//---
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,3,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
  }

É assim que ele se parece:

 Fig. 4. Demonstração no modo de visualização (três ZigZags)

Fig. 4. Demonstração no modo de visualização (três ZigZags)

Nós podemos ver que os extremos dos indicadores a partir de períodos gráficos maiores são deslocados ligeiramente para a esquerda. A razão é que os topos e fundos são definidos pelo tempo de abertura das barras do período em que o manipulador foi recebido. 


Retomando o desenvolvimento da classe CZigZagModule

Olhando para os resultados já obtidos, pode-se pensar que eles são suficientes para completar o trabalho com o indicador ZigZag. Mas, na verdade, esse não é o caso. Nós precisamos continuar o desenvolvimento do código da classe CZigZagModule preenchendo-o com novos métodos úteis. 

Até agora, nós obtivemos os dados do indicador ZigZag a partir da barra mais recente e se aprofundando nos dados históricos. No entanto, talvez seja necessário nós obtermos também os dados em uma faixa de tempo específica. Para conseguir isso, vamos escrever outro método CZigZagModule::GetZigZagData() com um conjunto diferente de parâmetros. Nesta versão, nós receberemos os dados iniciais dentro do método, portanto, nós precisaremos do manipulador do indicador, símbolo, período gráfico e o intervalo de tempo (datas de início e término) como parâmetros.

Mais adiante, nós precisamos contar o número de máximas e mínimas nos dados obtidos de forma separada. Nesse caso, o número de extremos para o trabalho futuro deve ser definido pela quantidade mínima entre esses contadores

O método de mesmo nome CZigZagModule::GetZigZagData() com outro conjunto de parâmetros é chamado aqui no final. Nós consideramos esse conjunto acima, enquanto descrevemos como os arrays contendo os dados de origem devem ser passados como parâmetros para a obtenção dos dados finais.

class CZigZagModule
  {
private:
   //--- Arrays para a obtenção dos dados de origem
   double            m_zz_lows_temp[];
   double            m_zz_highs_temp[];
   datetime          m_zz_time_temp[];
   //---
public:
   //--- Obtém os dados
   void              GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time);
  };
//+------------------------------------------------------------------+
//| Obtém os dados de ZZ do manipulador passado                      |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time)
  {
//--- Obtém os dados de origem
   ::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
   ::CopyBuffer(handle,2,start_time,stop_time,m_zz_highs_temp);
   ::CopyBuffer(handle,3,start_time,stop_time,m_zz_lows_temp);
//--- Contadores
   int lows_counter  =0;
   int highs_counter =0;
//--- Contagem das máximas
   int h_total=::ArraySize(m_zz_highs_temp);
   for(int i=0; i<h_total; i++)
     {
      if(m_zz_highs_temp[i]>0)
         highs_counter++;
     }
//--- Contagem das mínimas
   int l_total=::ArraySize(m_zz_lows_temp);
   for(int i=0; i<l_total; i++)
     {
      if(m_zz_lows_temp[i]>0)
         lows_counter++;
     }
//--- Obtém o número de extremos
   int copy_extremums=(int)::fmin((double)highs_counter,(double)lows_counter);
   CopyExtremums(copy_extremums);
//--- Mover ao longo dos valores copiados de ZZ em um loop
   GetZigZagData(m_zz_highs_temp,m_zz_lows_temp,m_zz_time_temp);
  }

Use os métodos CZigZagModule::SmallestMinimumTime() e CZigZagModule::LargestMaximumTime() para obter o horário dos extremos da menor mínima e da maior máxima no conjunto de dados obtidos. 

class CZigZagModule
  {
public:
   //--- Horário da menor mínima
   datetime          SmallestMinimumTime(void);
   //--- Horário da maior máxima
   datetime          LargestMaximumTime(void);
  };
//+------------------------------------------------------------------+
//| Horário da menor mínima                                          |
//+------------------------------------------------------------------+
datetime CZigZagModule::SmallestMinimumTime(void)
  {
   return(m_zz_low_time[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Horário da maior máxima                                          |
//+------------------------------------------------------------------+
datetime CZigZagModule::LargestMaximumTime(void)
  {
   return(m_zz_high_time[::ArrayMaximum(m_zz_high)]);
  }

Além disso, vamos expandir a lista de métodos para trabalhar com os segmentos do ZigZag. Pode ser conveniente obter vários valores em variáveis passadas por referência de uma só vez. A classe apresenta três desses métodos:

  • SegmentBars() retorna os índices de barra inicial e final de um segmento especificado.
  • SegmentPrices() retorna os preços inicial e final de um segmento especificado.
  • SegmentTimes() retorna a hora inicial e final de um segmento especificado.

Uma estrutura semelhante está presente em outros métodos considerados anteriormente, portanto, apenas um código de exemplo é fornecido abaixo. 

class CZigZagModule
  {
public:
   //--- Retorna as barras inicial e final de um segmento especificado
   bool              SegmentBars(const int index,int &start_bar,int &stop_bar);
   //--- Retorna os preços inicial e final de um segmento especificado
   bool              SegmentPrices(const int index,double &start_price,double &stop_price);
   //--- Retorna as horas de início e término de um segmento especificado
   bool              SegmentTimes(const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| Retorna as barras inicial e final de um segmento especificado    |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentBars(const int index,int &start_bar,int &stop_bar)
  {
   if(index>=m_segments_total)
      return(false);
//--- No caso de um número par
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_bar =(Direction()>0)? m_zz_low_bar[i] : m_zz_high_bar[i];
      stop_bar  =(Direction()>0)? m_zz_high_bar[i] : m_zz_low_bar[i];
     }
//--- No caso de um número ímpar
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_bar =m_zz_high_bar[h];
         stop_bar  =m_zz_low_bar[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_bar =m_zz_low_bar[l];
         stop_bar  =m_zz_high_bar[h];
        }
     }
//---
   return(true);
  }

Suponha que nós temos um gráfico em M5 e recebemos os dados de H1. Nós buscamos por padrões a partir do período gráfico H1 e precisamos definir o comportamento do preço de um determinado segmento do ZigZag a partir do período H1 sob o atual. Em outras palavras, nós queremos saber como o segmento especificado foi formado em um período gráfico menor.

Como mostrado na seção anterior, os extremos dos segmentos de períodos gráficos maiores são exibidos no período atual pelo horário de abertura dos períodos gráficos maiores. Nós já temos o método CZigZagModule::SegmentTimes() retornando a hora inicial e final de um segmento especificado. Se nós usarmos esse intervalo de tempo para obter os dados do ZigZag a partir de um período gráfico menor, na maioria dos casos nós teremos muitos segmentos redundantes realmente pertencentes a outros segmentos de um período gráfico maior. Vamos escrever mais um método CZigZagModule::SegmentTimes() com outro conjunto de parâmetros no caso de ser necessária uma maior precisão. Além disso, nós precisaremos de vários métodos auxiliares privados para receber os (1) dados de origem e os (2) índices dos valores mínimos e máximos nos arrays passados. 

class CZigZagModule
  {
private:
   //--- Copia os dados de origem para os arrays passados
   void              CopyData(const int handle,const int buffer_index,const string symbol,
                              const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                              double &zz_array[],datetime &time_array[]);
   //--- Retorna o índice dos valores (1) mínimo e (2) máximo do array passado
   int               GetMinValueIndex(double &zz_lows[]);
   int               GetMaxValueIndex(double &zz_highs[]);
  };
//+------------------------------------------------------------------+
//| Copia os dados de origem para os arrays passados                 |
//+------------------------------------------------------------------+
void CZigZagModule::CopyData(const int handle,const int buffer_index,const string symbol,
                             const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                             double &zz_array[],datetime &time_array[])
  {
   ::CopyBuffer(handle,buffer_index,start_time,stop_time,zz_array);
   ::CopyTime(symbol,period,start_time,stop_time,time_array);
  }
//+------------------------------------------------------------------+
//| Retorna o índice do valor máximo do array passado                |
//+------------------------------------------------------------------+
int CZigZagModule::GetMaxValueIndex(double &zz_highs[])
  {
   int    max_index =0;
   double max_value =0;
   int total=::ArraySize(zz_highs);
   for(int i=0; i<total; i++)
     {
      if(zz_highs[i]>0)
        {
         if(zz_highs[i]>max_value)
           {
            max_index =i;
            max_value =zz_highs[i];
           }
        }
     }
//---
   return(max_index);
  }
//+------------------------------------------------------------------+
//| Retorna o índice do valor mínimo do array passado                |
//+------------------------------------------------------------------+
int CZigZagModule::GetMinValueIndex(double &zz_lows[])
  {
   int    min_index =0;
   double min_value =INT_MAX;
   int total=::ArraySize(zz_lows);
   for(int i=0; i<total; i++)
     {
      if(zz_lows[i]>0)
        {
         if(zz_lows[i]<min_value)
           {
            min_index =i;
            min_value =zz_lows[i];
           }
        }
     }
//---
   return(min_index);
  }

Outro método CZigZagModule::SegmentTimes() é implementado para receber o horário de início e término de um segmento especificado, considerando um período gráfico menor. Isso requer alguma explicação. Os seguintes parâmetros são passados para o método:

  • handle — manipulador do indicador ZigZag de um período gráfico menor.
  • highs_buffer_index — índice do buffer do indicador contendo os extremos máximos.
  • lows_buffer_index — índice do buffer do indicador contendo os extremos mínimos.
  • symbol — símbolo de menor período gráfico.
  • period — maior período gráfico.
  • in_period — menor período gráfico.
  • index — índice de segmento de maior período gráfico.

Valores de retorno dos parâmetros passados por referência:

  • start_time — horário de início do segmento considerando um período gráfico menor.
  • stop_time — horário final do segmento considerando um período gráfico menor.

Primeiro, nós precisamos obter o horário de abertura da primeira e da última barras de um segmento especificado. Para fazer isso, chame o primeiro método CZigZagModule::SegmentTimes() descrito acima. 

Em seguida, use o método CZigZagModule::CopyData() para receber os dados sobre o horário das barras e extremos. Dependendo da direção do segmento, nós obtemos os dados em uma determinada sequência. No caso da direção ascendente, nós primeiro obtemos os dados sobre as mínimas do ZigZag de período gráfico menor que fazem parte do segmento da primeira barra em um período gráfico maior. Depois disso, nós obtemos os dados sobre as máximas do ZigZag de período gráfico menor que fazem parte do último segmento da barra em um período gráfico maior. No caso da direção descendente, a sequência de ações é invertida. Primeiro, nós precisamos obter os dados sobre as máximas seguidas de informações sobre as mínimas. 

Depois de receber os dados de origem, encontre o índices de valores máximos e mínimos. Usando esses índices, você pode descobrir o horário de início e término do segmento analisado em um período gráfico menor.

class CZigZagModule
  {
public:
   //--- Retorna o horário de início e término de um segmento especificado considerando um período gráfico menor
   bool              SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                  const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                  const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| Retorna o horário de início e término de um segmento especificado|
//| considerando o período gráfico menor                             |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                 const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                 const int index,datetime &start_time,datetime &stop_time)
  {
//--- Obtém o horário sem considerar o período gráfico atual
   datetime l_start_time =NULL;
   datetime l_stop_time  =NULL;
   if(!SegmentTimes(index,l_start_time,l_stop_time))
      return(false);
//---
   double   zz_lows[];
   double   zz_highs[];
   datetime zz_lows_time[];
   datetime zz_highs_time[];
   datetime start =NULL;
   datetime stop  =NULL;
   int      period_seconds=::PeriodSeconds(period);
//--- Obtém os dados de origem no caso da direção ascendente
   if(SegmentDirection(index)>0)
     {
      //--- Dados na primeira barra do período gráfico maior
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
      //--- Dados na última barra do período gráfico maior
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
     }
//--- Obtém os dados de origem no caso da direção descendente
   else
     {
      //--- Dados na primeira barra do período gráfico maior
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
      //--- Dados na última barra do período gráfico maior
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
     }
//--- Busca o índice do valor máximo
   int max_index =GetMaxValueIndex(zz_highs);
//--- Busca pelo índice do valor mínimo
   int min_index =GetMinValueIndex(zz_lows);
//--- Obtém o horário de início e término do segmento
   start_time =(SegmentDirection(index)>0)? zz_lows_time[min_index] : zz_highs_time[max_index];
   stop_time  =(SegmentDirection(index)>0)? zz_highs_time[max_index] : zz_lows_time[min_index];
//--- Bem sucedido
   return(true);
  }

Agora vamos escrever um EA para testes. O período atual é M5. Use-o para iniciar o EA no modo de visualização do testador de estratégia. Nós vamos receber os dados a partir de H1, assim como do período gráfico atual. O código do EA é semelhante ao considerado anteriormente, então eu mostrarei apenas o funcionamento do conteúdo da OnTick() aqui.

Primeiro, nós obteremos os dados de H1 usando o primeiro método e exibiremos os segmentos no gráfico para maior clareza. Em seguida, obtenha os dados do ZigZag do período gráfico atual (M5) no intervalo de tempo do terceiro (índice 2) segmento do ZigZag em H1. Para fazer isso, obtenha o início e o fim do segmento considerando o período gráfico atual.

Então, obtenha os dados para o período gráfico atual usando o segundo método e também exiba os segmentos no gráfico para garantir que tudo esteja bem.

//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- Primeiro método para obtenção dos dados
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   int      segment_index =2;
   int      start_bar     =0;
   int      stop_bar      =0;
   double   start_price   =0.0;
   double   stop_price    =0.0;
   datetime start_time    =NULL;
   datetime stop_time     =NULL;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//---
   zz_h1.SegmentBars(segment_index,start_bar,stop_bar);
   zz_h1.SegmentPrices(segment_index,start_price,stop_price);
   zz_h1.SegmentTimes(segment_index,start_time,stop_time);
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,segment_index,start_time_in,stop_time_in);
   
//--- Segundo método para obtenção dos dados
   zz_current.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current.ShowSegments("_current");
   
//--- Exibe os dados nos comentários do gráfico
   string comment="Current direction : "+string(zz_h1.Direction())+"\n"+
                  "\n---\n"+
                  "Direction > segment["+string(segment_index)+"]: "+string(zz_h1.SegmentDirection(segment_index))+
                  "\n---\n"+
                  "Start bar > segment["+string(segment_index)+"]: "+string(start_bar)+"\n"+
                  "Stop bar > segment["+string(segment_index)+"]: "+string(stop_bar)+
                  "\n---\n"+
                  "Start price > segment["+string(segment_index)+"]: "+::DoubleToString(start_price,_Digits)+"\n"+
                  "Stop price > segment["+string(segment_index)+"]: "+::DoubleToString(stop_price,_Digits)+
                  "\n---\n"+
                  "Start time > segment["+string(segment_index)+"]: "+::TimeToString(start_time,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time > segment["+string(segment_index)+"]: "+::TimeToString(stop_time,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Start time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(start_time_in,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(stop_time_in,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Extremums copy: "+string(zz_current.CopyExtremums())+"\n"+
                  "SmallestMinimumTime(): "+string(zz_current.SmallestMinimumTime())+"\n"+
                  "LargestMaximumTime(): "+string(zz_current.LargestMaximumTime());
//---
   ::Comment(comment);
  }

Segue abaixo como ele se parece:

 Fig. 5. Recebendo os dados dentro do segmento especificado

Fig. 5. Recebendo os dados dentro do segmento especificado

Em seguida, desenvolva mais um EA para receber os dados dos três segmentos de um período gráfico maior.

Nós agora devemos declarar quatro instâncias da classe CZigZagModule no início do arquivo. Um deles é destinado ao período gráfico maior (H1), enquanto os três restantes destinam-se ao período atual. Neste caso, nós realizamos os testes no M5. 

CZigZagModule zz_h1;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

Para maior clareza, os segmentos do menor período gráfico dentro dos segmentos do maior serão exibidos em cores diferentes:

//--- Define a cor do segmento
   zz_current0.LinesColor(clrRed);
   zz_current1.LinesColor(clrLimeGreen);
   zz_current2.LinesColor(clrMediumPurple);
   zz_h1.LinesColor(clrCornflowerBlue);

Na função OnTick(), nós recebemos primeiro os dados do período gráfico em H1 e, em seguida, obtemos os dados do período gráfico menor para o primeiro, segundo e terceiro segmentos em sequência. Exibe os dados em cada grupo dos segmentos obtidos de período gráfico menor e no período gráfico maior de forma separada nos comentário do gráfico. Nesse caso, essa é a diferença entre as taxas percentuais das somas do segmento. Ele pode ser obtido usando o método CZigZagModule::PercentSumSegmentsDifference(). 

//+------------------------------------------------------------------+
//| Função Tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- Primeiro método para obtenção dos dados
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Dado do primeiro segmento
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current0.ShowSegments("_current0");
//--- Dado do segundo segmento
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current1.ShowSegments("_current1");
//--- Dado do terceiro segmento
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current2.ShowSegments("_current2");
//--- Exibe os dados nos comentários do gráfico
   string comment="H1: "+::DoubleToString(zz_h1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[0]: "+::DoubleToString(zz_current0.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[1]: "+::DoubleToString(zz_current1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[2]: "+::DoubleToString(zz_current2.PercentSumSegmentsDifference(),2);
//---
   ::Comment(comment);
  }

Aqui está como ele fica no gráfico:

 Fig. 6. Recebendo os dados dentro dos três segmentos especificados

Fig. 6. Recebendo os dados dentro dos três segmentos especificados

Essa abordagem fornece oportunidades adicionais para analisar a natureza do comportamento do preço dentro dos padrões. Suponha que nós definimos o padrão em H1 e analisamos como o preço se comportou dentro de cada segmento. Os métodos da classe CZigZagModule permitem obter todas as propriedades dos extremos e segmentos, tais como:

  • Preço, horário e índice de uma barra de cada extremo separado.
  • Tamanho de cada segmento separado.
  • Duração de cada segmento em barras.
  • Tamanho da faixa de preço de todo o conjunto de segmentos obtido.
  • Toda a duração de formação do conjunto de segmentos (em barras).
  • Soma dos segmentos unidirecionais.
  • Proporções das somas dos segmentos de direção oposta, etc. 

Esse conjunto básico pode ser usado como ponto de partida para o desenvolvimento de vários parâmetros personalizados para a criação de indicadores. Os testes mostrarão quais benefícios podem ser derivados disso. Este site contém vários artigos que podem ser úteis na condução de sua própria pesquisa sobre o assunto. 


Conclusão

A ideia de que o ZigZag não é adequado para gerar sinais de negociação é amplamente difundida em fóruns de negociação. Este é um grande equívoco. De fato, nenhum outro indicador fornece tanta informação para determinar a natureza do comportamento do preço. Agora você tem uma ferramenta que lhe permite obter facilmente todos os dados necessários do indicador ZigZag para uma análise mais detalhada.

Nos próximos artigos da série, eu mostrarei quais indicadores podem ser desenvolvidos usando a classe CZigZagModule, bem como os EAs para obter estatísticas sobre diferentes símbolos do indicador ZigZag e a verificação de algumas estratégias de negociação baseadas no ZigZag que podem ser desenvolvidas.

Nome do arquivo Comentário
MQL5\Indicators\Custom\ZigZag\ExactZZ_Plus.mq5 Indicador ZigZag modificado
MQL5\Experts\ZigZag\TestZZ_01.mq5 EA para testar um único conjunto de dados
MQL5\Experts\ZigZag\TestZZ_02.mq5 EA para testar três conjuntos de dados de diferentes períodos gráficos
MQL5\Experts\ZigZag\TestZZ_03.mq5 EA para testar a aquisição dos dados dentro de um segmento de período de tempo maior especificado
MQL5\Experts\ZigZag\TestZZ_04.mq5 EA para testar a obtenção dos dados dentro de três segmentos de período de tempo maior especificados


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

Arquivos anexados |
MQL5.zip (17.45 KB)
Estudo de técnicas de análise de velas (parte I): Verificação de padrões existentes Estudo de técnicas de análise de velas (parte I): Verificação de padrões existentes
Neste artigo, nós vamos considerar os padrões populares de velas e tentaremos descobrir se eles ainda são relevantes e eficazes nos mercados atuais. A análise de velas ou candlestick apareceu há mais de 20 anos e desde então se tornou bem popular. Muitos traders consideram as velas japoneses a forma de visualização de preços de ativos mais conveniente e fácil de compreender.
Aplicação prática das correlações na negociação Aplicação prática das correlações na negociação
Neste artigo, nós analisaremos o conceito de correlação entre variáveis, bem como os métodos para o cálculo dos coeficientes de correlação e seu uso prático na negociação. A Correlação é uma relação estatística entre duas ou mais variáveis aleatórias (ou quantidades que podem ser consideradas aleatórias com algum grau aceitável de precisão). Mudanças em uma ou mais variáveis levam a mudanças sistemáticas em outras variáveis relacionadas.
O poder do ZigZag (parte II). Exemplos de recebimento, processamento e exibição de dados O poder do ZigZag (parte II). Exemplos de recebimento, processamento e exibição de dados
Na primeira parte do artigo, eu descrevi um indicador ZigZag modificado e uma classe para receber os dados desses tipos de indicadores. Aqui, eu mostrarei como desenvolver indicadores baseados nessas ferramentas e escrever um EA para testes que apresentem operações de acordo com os sinais formados pelo indicador ZigZag. Como complemento, o artigo apresentará uma nova versão da biblioteca EasyAndFast para o desenvolvimento de interfaces gráficas do usuário.
Utilitário de seleção e navegação em MQL5 e MQL4: Adição da busca automática de padrões e exibição dos símbolos detectados Utilitário de seleção e navegação em MQL5 e MQL4: Adição da busca automática de padrões e exibição dos símbolos detectados
Neste artigo, nós continuamos expandindo os recursos do utilitário para coleta e navegação através dos símbolos. Desta vez, nós criaremos novas guias exibindo apenas os símbolos que satisfazem alguns dos parâmetros necessários e descobriremos como adicionar facilmente guias personalizadas com as regras de classificação necessárias.