O poder do ZigZag (parte II). Exemplos de recebimento, processamento e exibição de dados

Anatoli Kazharski | 2 abril, 2019

Conteúdo


Introdução

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 introduzirá uma nova versão da biblioteca EasyAndFast para desenvolvimento de interfaces gráficas do usuário. 

Principais tópicos do artigo:


Indicadores definindo o comportamento de preços

Vamos considerar três indicadores que definem o comportamento do preço. 

A estrutura de código de cada um desses indicadores é a mesma do indicador ZigZag descrito na primeira parte do artigo. Portanto, nós vamos nos deter apenas na função principal (FillIndicatorBuffers) onde os dados são recebidos e os buffers dos indicadores são preenchidos.


Indicador FrequencyChangeZZ

Para o indicador FrequencyChangeZZ , o código da função principal é o mesmo do código exibido abaixo. O índice das barras e o array de tempo são passados para a função. Em seguida, são copiados da hora da barra atual um número necessário do indicador ZigZag e os elementos do array de tempo (dados de origem). Se os dados de origem forem recebidos, os dados finais são solicitados. Depois disso, resta apenas chamar um método que retorne o número de barras no conjunto de segmentos. O resultado é salvo no elemento atual do buffer do indicador.

//+------------------------------------------------------------------+
//| Preenche os buffers do indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         ::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         ::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Obtém os dados ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Salva o número de barras no segmento definido no buffer do indicador
         segments_bars_total_buffer[i]=zz.SegmentsTotalBars();
         break;
        }
     }
  }

Nos parâmetros de entrada do indicador, nós especificaremos o seguinte:

(1) os valores devem ser calculados com base em todos os dados disponíveis,
(2) o desvio mínimo para a formação do novo segmento do indicador ZigZag e
(3) o número de extremos para obtenção dos dados finais.

Todos os indicadores neste artigo terão os mesmos parâmetros.

 Fig. 1. Parâmetros de entrada do indicador

Fig. 1. Parâmetros de entrada do indicador

O FrequencyChangeZZ indicador exibe o gráfico em uma subjanela conforme é exibido abaixo. O indicador ZigZag é carregado no gráfico principal para maior visibilidade. O indicador mostra claramente quando o preço diminui na escolha da direção.  

 Fig. 2. Indicador FrequencyChangeZZ

Fig. 2. Indicador FrequencyChangeZZ


Indicador SumSegmentsZZ

No indicador SumSegmentsZZ, a função principal para obter os dados se parece conforme exibido no trecho de código a seguir. Todo código é o mesmo que no exemplo anterior. A única diferença é que três buffers do indicador são preenchidos aqui para segmentos ascendentes e descendentes separadamente. Mais um buffer é usado para calcular a média desses parâmetros nos valores atuais.

//+------------------------------------------------------------------+
//| Preenche os buffers do indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Obtém os dados ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Obtém os dados por segmentos
         segments_up_total_buffer[i] =zz.SumSegmentsUp();
         segments_dw_total_buffer[i] =zz.SumSegmentsDown();
         segments_average_buffer[i]  =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/2;
         break;
        }
     }
  }

Após anexar o indicador SumSegmentsZZ no gráfico, você verá o resultado como na imagem abaixo. Aqui nós podemos ver que depois que a linha azul excede a linha vermelha, a soma dos segmentos ascendentes é maior que a soma dos descendentes. A situação é invertida se a linha vermelha exceder a linha azul. Somente os experimentos no testador de estratégia podem nos informar se essa é uma fonte confiável de informação sobre a direção futura do preço. À primeira vista, quanto maior a soma dos segmentos unidirecionais exceder a soma dos segmentos opostos, maior a probabilidade de reversão. 

 Fig. 3. Indicador SumSegmentsZZ

Fig. 3. Indicador SumSegmentsZZ


Indicador PercentageSegmentsZZ

Agora, vamos dar uma olhada no indicador PercentageSegmentsZZ. Como no caso anterior, três buffers do indicador devem ser preenchidos na função principal do indicador: um buffer para cada uma das taxas percentuais da soma dos segmentos direcionadas (1) para cima e (2) para baixo, bem como um buffer (3) pela diferença entre esses valores

//+------------------------------------------------------------------+
//| Preenche os buffers do indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Obtém os dados ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Obtém os dados sobre os segmentos
         double sum_up =zz.SumSegmentsUp();
         double sum_dw =zz.SumSegmentsDown();
         double sum    =sum_up+sum_dw;
         //--- Taxa percentual e diferença
         if(sum>0)
           {
            segments_up_total_buffer[i]   =zz.PercentSumSegmentsUp();
            segments_dw_total_buffer[i]   =zz.PercentSumSegmentsDown();
            segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
            break;
           }
        }
     }
  }

O resultado é exibido abaixo. Vamos tentar interpretar isso. Quando a diferença nas taxas percentuais entre as quantidades de segmentos multidirecionais é menor que um determinado limite, isso pode ser considerado como lateralizado. Nesse caso, nós devemos também ter em mente que as taxas devem ser alteradas frequentemente, uma vez que o preço pode mudar em uma direção por um longo tempo, enquanto a diferença é menor do que o nível selecionado pelo otimizador. Nestes casos, nós devemos aplicar os modelos considerando a formação dos padrões em uma determinada sequência.

 Fig. 4. Indicador PercentageSegmentsZZ

Fig. 4. Indicador PercentageSegmentsZZ


Indicador MultiPercentageSegmentsZZ

No artigo anterior, nós demonstramos o EA analisando os dados do indicador ZigZag a partir de períodos de tempo maiores e menores, simultaneamente. Assim, foi possível analisar com mais detalhes como o preço se comportou dentro dos segmentos a partir do período de tempo maior. Em outras palavras, nós definimos como os segmentos de período de tempo maiores se formaram em um período de tempo menor. Vamos ver como esse grupo de parâmetros se parecerá na forma de um indicador separado exibindo esses valores no histórico de preços.

Como no EA do artigo anterior, nós receberemos quatro valores da diferença entre as taxas percentuais das somas do segmento de direção oposta: um valor é para o período de tempo maior e três valores são para o período menor. Os valores são calculados pelos três últimos segmentos do indicador ZigZag no período de tempo maior. As cores dos buffers do indicador serão as mesmas que no EA da parte anterior. Depois disso, nós desenvolveremos um EA para testar o indicador, de modo que seja muito mais fácil entendermos quais dados e para qual período de tempo nós observamos no gráfico.

//--- Número de buffers
#property indicator_buffers 4
#property indicator_plots   4
//--- Cores dos buffers de cor
#property indicator_color1 clrSilver
#property indicator_color2 clrRed
#property indicator_color3 clrLimeGreen
#property indicator_color4 clrMediumPurple

Declaração de quatro instâncias da classe CZigZagModule:

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

Vamos adicionar a capacidade de definir um período de tempo maior para o indicador nos parâmetros de entrada:

input             int NumberOfBars    =0;         // Número de barras para calcular o ZZ
input             int MinImpulseSize  =0;         // Pontos mínimos em um raio
input             int CopyExtremum    =5;         // Copiar os extremos
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Período de tempo maior

A principal função para preencher os buffers do indicador é implementada da seguinte forma. Primeiro, obtenha os dados de origem do maior período de tempo especificado nos parâmetros de entrada. Então obtenha os dados finais e salve o valor do parâmetro. Em seguida, nós obtemos consistentemente os dados sobre os três segmentos do indicador do maior período de tempo. Depois disso, preencha todos os buffers do indicador. Eu tive que desenvolver dois blocos de código separados, para que o indicador pudesse ser calculado corretamente no histórico e na última barra em tempo real/testador

//+------------------------------------------------------------------+
//| Preenche os buffers do indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const int total,const datetime &time[])
  {
   int index=total-i-1;
   int copy_total=1000;
   int h_buff=2,l_buff=3;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Obtém os dados de origem do maior período de tempo
   datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
   CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp);
   CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp);
   CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
//--- Obtém os dados finais do maior período de tempo
   zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
   double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
//--- Dado do primeiro segmento
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Dado do segundo segmento
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Dado do terceiro segmento
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Na última barra
   if(i<total-1)
     {
      buffer_zz_higher_tf[i] =htf_value;
      buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
      buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
      buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
     }
//--- No histórico
   else
     {
      //--- Caso de haver uma nova barra do maior período de tempo
      if(new_bar_time!=t_zz_buffer_temp[0])
        {
         new_bar_time=t_zz_buffer_temp[0];
         //---
         if(i>2)
           {
            int f=1,s=2;
            buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
            buffer_segment_0[i-f]    =buffer_segment_0[i-s];
            buffer_segment_1[i-f]    =buffer_segment_1[i-s];
            buffer_segment_2[i-f]    =buffer_segment_2[i-s];
           }
        }
      else
        {
         buffer_zz_higher_tf[i] =htf_value;
         buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
         buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
         buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
        }
     }
  }

Vamos fazer uma cópia do EA do artigo anterior e adicionar algumas linhas para testar o indicador MultiPercentageSegmentsZZ. Adiciona o parâmetro de entrada para definir um período de tempo maior. Para que o indicador seja exibido durante o teste do EA no modo de visualização do testador, é suficiente para obter o seu manipulador

//--- Parâmetros de entrada
input            uint CopyExtremum    =3;         // Copia os extremos
input             int MinImpulseSize  =0;         // Tamanho do impulso mínimo 
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Período de tempo maior

...

//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit(void)
  {

...

//--- Caminho para o indicador ZZ
   string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5";
   string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5";
//--- Obtemos os manipuladores do indicador
   zz_handle_current   =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false);
   zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false);
   zz_handle           =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe);

...

   return(INIT_SUCCEEDED);
  }

É assim que fica no testador:

 Fig. 5. Indicador MultiPercentageSegmentsZZ

Fig. 5. Indicador MultiPercentageSegmentsZZ

Todos os indicadores descritos acima podem ser usados em várias combinações e ao mesmo tempo em diferentes períodos de tempo. Agora, vamos usar as ferramentas descritas para coletar algumas estatísticas sobre o conjunto de símbolos para entender quais delas são mais adequadas para a negociação no canal de preço.


EA para coleta e exibição de estatísticas

Como complemento, o artigo apresenta uma nova versão da biblioteca EasyAndFast para desenvolvimento de interfaces gráficas do usuário. Aqui nós vamos listar apenas os novos recursos da biblioteca:

 Fig. 6. Combinando elementos em grupos

Fig. 6. Combinando elementos em grupos

A nova versão da biblioteca pode ser baixada na CodeBase

Em seguida, vamos criar um EA de teste para reunir algumas estatísticas usando a nova versão da biblioteca EasyAndFast. Nós vamos começar desenvolvendo a interface gráfica do usuário (GUI) do aplicativo e, em seguida, procederemos aos métodos para coletar e exibir as estatísticas.

Vamos definir quais controles da GUI nós precisamos:

Como mencionado anteriormente, a classe CWndCreate deve ser incluída na classe personalizada como base para desenvolver uma GUI mais rápida e mais conveniente. A conexão completa é a seguinte: CWndContainer -> CWndEvents -> CWndCreate -> CProgram. A presença da classe CWndCreate permite criar elementos da GUI em uma única linha, sem criar métodos separados em uma classe personalizada. A classe contém modelos diferentes para quase todos os elementos da biblioteca. Você pode adicionar novos modelos, se necessário. 

Para criar uma GUI, declare os elementos contidos na lista acima, conforme mostrado no código a seguir. A versão atual da classe CWndCreate não tem nenhum modelo de criação de tabela rápida, portanto vamos desenvolver nossa próprio método

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. 
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndCreate.mqh>
//+------------------------------------------------------------------+
//| Classe para o desenvolvimento da aplicação                       |
//+------------------------------------------------------------------+
class CProgram : public CWndCreate
  {
private:
   //--- Janela
   CWindow           m_window;
   //--- Barra de status
   CStatusBar        m_status_bar;
   //--- Calendários suspensos
   CDropCalendar     m_from_date;
   CDropCalendar     m_to_date;
   //--- Botões
   CButton           m_request;
   //--- Campos de entrada
   CTextEdit         m_filter;
   CTextEdit         m_level;
   //--- Caixas de combinação
   CComboBox         m_data_type;
   //--- Tabelas
   CTable            m_table;
   //--- Barra de progresso
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Cria uma GUI
   bool              CreateGUI(void);
   //---
private:
   //--- Tabelas
   bool              CreateTable(const int x_gap,const int y_gap);
  };

Para criar uma interface gráfica com tal conteúdo, basta chamar os métodos necessários da classe CWndCreate especificando os valores das propriedades como argumentos, conforme mostrado no bloco de código abaixo. Para definir uma propriedade com a qual um parâmetro do método está relacionado, defina o cursor de texto nele e clique em Ctrl + Shift + Espaço:

 Fig. 7. Visualizando os parâmetros do método

Fig. 7. Visualizando os parâmetros do método

Se você precisar definir propriedades adicionais, poderá fazer isso da mesma maneira mostrada no exemplo que envolve o campo de entrada do filtro de moeda. Aqui, é indicado que a caixa de seleção deve ser ativada por padrão logo após a criação do elemento.

//+------------------------------------------------------------------+
//| Cria a GUI                                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Cria os formulários de controle
   if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",1,1,640,480,true,true,true,true))
      return(false);
//--- Barra de status
   string text_items[1];
   text_items[0]="For Help, press F1";
   int width_items[]={0};
   if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items))
      return(false);
//--- Campo de entrada do filtro de moeda
   if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,0,true,7,25,627,535,"USD","Example: EURUSD,GBP,NOK"))
      return(false);
   else
      m_filter.IsPressed(true);
//--- Calendário suspenso
   if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,0,7,50,130,D'2018.01.01'))
      return(false);
   if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,0,150,50,117,::TimeCurrent()))
      return(false);
//--- Campo de entrada para especificar o nível
   if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,0,false,280,50,85,50,100,0,1,0,30))
      return(false);
//--- Botão
   if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70))
      return(false);
//--- Tabela
   if(!CreateTable(2,75))
      return(false);
//--- Barra de progresso
   if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3))
      return(false);
//--- Fim do desenvolvimento da GUI
   CWndEvents::CompletedGUI();
   return(true);
  }

No caso de uma tabela, crie um método personalizado, já que é um elemento complexo com um grande número de propriedades que devem ser especificadas antes de criar um elemento. É para apresentar quatro colunas. A primeira mostrará os pares de moedas. Os restantes mostrarão dados estatísticos em três períodos de tempo: M5, H1 e H8.

//+------------------------------------------------------------------+
//| Cria a tabela                                                    |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 4
#define ROWS1_TOTAL    1
//--- Salva um ponteiro para o elemento principal
   m_table.MainPointer(m_window);
//--- Array de largura da coluna
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,50);
   width[0]=80;
//--- Array de deslocamento de texto em colunas pelo eixo X
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,7);
//--- Array do alinhamento de texto em colunas
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_LEFT;
//--- Propriedades
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ShowHeaders(true);
   m_table.IsSortMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoYResizeMode(true);
   m_table.AutoXResizeRightOffset(2);
   m_table.AutoYResizeBottomOffset(24);
//--- Cria um controle
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Cabeçalhos
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
//--- Adiciona um objeto ao array comum de grupos de objetos
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

Agora vamos considerar os métodos para a obtenção dos dados. Primeiro, nós precisamos obter os símbolos com os quais nós devemos trabalhar. Nesta versão do EA, nós receberemos os dados de símbolos de Forex. Ao mesmo tempo, nós vamos excluir os símbolos, para os quais a negociação está desativada. Aqui, nós também precisaremos do método auxiliar CheckFilterText() para verificar o símbolo pelo filtro. No campo de entrada, os usuários podem inserir os valores de texto separados por vírgulas que devem estar presentes nos nomes dos símbolos. Se a caixa de seleção de campo estiver desativada ou o texto não for digitado, a verificação não é executada. Se as verificações forem passadas e uma correspondência for encontrada, o texto inserido será dividido em substrings e a pesquisa por uma string necessária será executada. 

class CProgram : public CWndCreate
  {
private:
   //--- Verifica um símbolo por filtro
   bool              CheckFilterText(const string symbol_name);
  };
//+------------------------------------------------------------------+
//| Verifica um símbolo por filtro                                   |
//+------------------------------------------------------------------+
bool CProgram::CheckFilterText(const string symbol_name)
  {
   bool check=false;
//--- Se o filtro com o nome do símbolo estiver ativado
   if(!m_filter.IsPressed())
      return(true);
//--- Se um texto for digitado
   string text=m_filter.GetValue();
   if(text=="")
      return(true);
//--- Divide em substrings
   string elements[];
   ushort sep=::StringGetCharacter(",",0);
   ::StringSplit(text,sep,elements);
//--- Verifique se há correspondência
   int elements_total=::ArraySize(elements);
   for(int e=0; e<elements_total; e++)
     {
      //--- Exclui espaços externos
      ::StringTrimLeft(elements[e]);
      ::StringTrimRight(elements[e]);
      //--- Se uma correspondência for detectada
      if(::StringFind(symbol_name,elements[e])>-1)
        {
         check=true;
         break;
        }
     }
//--- Resultado
   return(check);
  }

No método CProgram::GetSymbols(), ele repassa todos os símbolos presentes no servidor em um loop e coleta os que se encaixam nos critérios especificados no array. No loop geral, todos os símbolos são excluídos da janela da Observação do Mercado. Apenas os contidos no array são adicionados à janela depois. 

class CProgram : public CWndCreate
  {
private:
   //--- array de símbolos
   string            m_symbols[];
   //---
private:
   //--- Obtém os símbolos
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
//| Obtém os símbolos                                                |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
//--- Progresso
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(0,1);
//--- Limpa um array de símbolos
   ::ArrayFree(m_symbols);
//--- Coleta o array de símbolos de Forex
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Obtém um nome do símbolo
      string symbol_name=::SymbolName(i,false);
      //--- Oculta na janela de Observação do Mercado
      ::SymbolSelect(symbol_name,false);
      //--- Se este não for um símbolo de Forex, vá para o próximo
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- Se a negociação estiver desativada, vá para a próximo
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED)
         continue;
      //--- Verifica um símbolo por filtro
      if(!CheckFilterText(symbol_name))
         continue;
      //--- Salve um símbolo no array
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1,1000);
      m_symbols[array_size]=symbol_name;
     }
//--- Se um array estiver vazio, define o símbolo atual como padrão
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=_Symbol;
     }
//--- Exibe na janela de Observação do Mercado
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

Para obter os dados sobre os símbolos coletados, nós devemos primeiro obter os manipuladores do indicador neles. Toda vez que nós obtemos o manipulador do indicador, nós precisamos esperar até o final do cálculo antes de copiar os dados para análise posterior. Depois que todos os dados são recebidos, os cálculos necessários são realizados. 

O método CProgram::GetSymbolsData() é usado para isso. Ele aceita dois parâmetros: símbolo e período de tempo. Depois de receber o manipulador do indicador, descubra quantas barras estão presentes no intervalo de tempo especificado. O intervalo de datas pode ser especificado usando os controles da GUI do aplicativo. Em seguida, nós tentamos obter a quantidade de dados do indicador calculado. O cálculo do indicador pode não ser concluído imediatamente após o recebimento do manipulador. Portanto, se a função BarsCalculated() retorna -1, nós fazemos novas tentativas para obter um valor válido até que seja igual ou exceda o número total de barras no intervalo de tempo especificado.

Depois que os dados do indicador são calculados, nós podemos tentar colocá-los no array. Ele pode levar a várias tentativas até que a quantidade seja também maior ou igual ao número total de barras

Se os indicadores forem copiados com sucesso para o array, resta apenas fazer os cálculos necessários. Neste caso, nós calculamos a porcentagem da quantidade total de dados para o valor, em que o valor do indicador está acima do nível especificado. Esse nível também pode ser especificado na GUI do aplicativo. 

No final do método, remova o manipulador do indicador liberando sua parte de cálculo. O método CProgram::GetSymbolsData() é chamado várias vezes para uma lista selecionada de símbolos e vários períodos de tempo. O cálculo para cada um deles deve ser executado apenas uma vez e o valor resultante é exibido na tabela da GUI, portanto, os manipuladores não são mais necessárias e podem ser removidos. 

class CProgram : public CWndCreate
  {
private:
   //--- Obtém os dados do símbolo
   double            GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Obtém os dados do símbolo                                        |
//+------------------------------------------------------------------+
double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period)
  {
   double result       =0.0;
   int    buffer_index =2;
//--- Obtém o manipulador do indicador
   string path   ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5";
   int    handle =::iCustom(symbol,period,path,0,0,5);
   if(handle!=INVALID_HANDLE)
     {
      //--- Copia os dados dentro de um intervalo especificado
      double   data[];
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      //--- Número de barras em um intervalo especificado
      int bars_total=::Bars(symbol,period,start_time,end_time);
      //--- Número de barras em um intervalo especificado
      int bars_calculated=::BarsCalculated(handle);
      if(bars_calculated<bars_total)
        {
         while(true)
           {
            ::Sleep(100);
            bars_calculated=::BarsCalculated(handle);
            if(bars_calculated>=bars_total)
               break;
           }
        }
      //--- Obtém os dados
      int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
      if(copied<1)
        {
         while(true)
           {
            ::Sleep(100);
            copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
            if(copied>=bars_total)
               break;
           }

        }
      //--- Encerra se nenhum dado for recebido
      int total=::ArraySize(data);
      if(total<1)
         return(result);
      //--- Conta o número de repetições
      int counter=0;
      for(int k=0; k<total; k++)
        {
         if(data[k]>(double)m_level.GetValue())
            counter++;
        }
      //--- Razão percentual
      result=((double)counter/(double)total)*100;
     }
//--- Libera o indicador
   ::IndicatorRelease(handle);
//--- Retorna o valor
   return(result);
  }

Toda vez que uma nova lista de símbolos é formada, a tabela precisa ser reconstruída. Para fazer isso, basta excluir todas as linhas e adicionar o valor necessário.

class CProgram : public CWndCreate
  {
private:
   //--- Re-constrói a tabela
   void              RebuildingTables(void);
  };
//+------------------------------------------------------------------+
//| Re-constrói a tabela                                             |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- Remove todas as linhas
   m_table.DeleteAllRows();
//--- Adiciona os dados
   int symbols_total=::ArraySize(m_symbols);
   for(int i=1; i<symbols_total; i++)
      m_table.AddRow(i);
  }

O método CProgram::SetData() é usado para preencher as colunas da tabela com os dados. Dois parâmetros (índice da coluna e período de tempo) são passados para ele. Aqui, nós movemos pelas células de uma coluna especificada e preenchemos elas com os valores calculados em um loop. A barra de progresso exibe um símbolo e um período de tempo, cujos dados acabam de ser recebidos, para que os usuários entendam o que está acontecendo no momento.

class CProgram : public CWndCreate
  {
private:
   //--- Define os valores para uma coluna especificada
   void              SetData(const int column_index,const ENUM_TIMEFRAMES period);
   //--- Período de tempo para uma string
   string            GetPeriodName(const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Define os valores para uma coluna especificada                   |
//+------------------------------------------------------------------+
void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period)
  {
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
     {
      double value=GetSymbolsData(m_symbols[r],period);
      m_table.SetValue(column_index,r,string(value),2,true);
      m_table.Update();
      //--- Progresso
      m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]...");
      m_progress_bar.Update(r,m_table.RowsTotal());
     }
  }
//+------------------------------------------------------------------+ 
//| Retorna o valor da string do período                             |
//+------------------------------------------------------------------+ 
string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period)
  {
   return(::StringSubstr(::EnumToString(period),7));
  }

O principal método para preencher a tabela com os dados é CProgram::SetDataToTable() A tabela é reconstruída aqui primeiro. Em seguida, nós precisamos definir os cabeçalhos e tipo de dados nele (TYPE_DOUBLE). Definir os símbolos coletados para a primeira coluna. Re-desenhar a tabela para ver as alterações imediatamente.

Agora nós podemos começar a receber os dados do indicador em todos os símbolos e períodos de tempo especificados. Para fazer isso, basta chamar o método CProgram::SetData() passando o índice de coluna e o período de tempo como parâmetros. 

class CProgram : public CWndCreate
  {
private:
   //--- Preenche a tabela com os dados
   void              SetDataToTable(void);
  };
//+------------------------------------------------------------------+
//| Preenche a tabela com os dados                                   |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable(void)
  {
//--- Progresso
   m_progress_bar.LabelText("Data preparation...");
   m_progress_bar.Update(0,1);
//--- Re-constrói a tabela
   RebuildingTable();
//--- Cabeçalhos
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
   for(uint i=1; i<m_table.ColumnsTotal(); i++)
      m_table.DataType(i,TYPE_DOUBLE);
//--- Define os valores para a primeira coluna
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
      m_table.SetValue(0,r,m_symbols[r],0,true);
//--- Mostrar a tabela
   m_table.Update(true);
//--- Preenche as colunas restantes com os dados
   SetData(1,PERIOD_M5);
   SetData(2,PERIOD_H1);
   SetData(3,PERIOD_H8);
  }

Antes de receber os novos dados usando o CProgram::GetData(), nós devemos tornar a barra de progresso visível com a ajuda do método CProgram::StartProgress(). Após novos dados serem recebidos, oculte a barra de progresso e remova o foco do botão pressionado. Para fazer isso, chame o método CProgram::EndProgress()

class CProgram : public CWndCreate
  {
private:
   //--- Obtém os dados
   void              GetData(void);

   //--- Progresso (1) início e (2) fim
   void              StartProgress(void);
   void              EndProgress(void);
  };
//+------------------------------------------------------------------+
//| Obtém os dados                                                   |
//+------------------------------------------------------------------+
void CProgram::GetData(void)
  {
//--- Início do progresso
   StartProgress();
//--- Obtém a lista de símbolos
   GetSymbols();
//--- Preenche a tabela com os dados
   SetDataToTable();
//--- Fim do progresso
   EndProgress();
  }
//+------------------------------------------------------------------+
//| Início do progresso                                              |
//+------------------------------------------------------------------+
void CProgram::StartProgress(void)
  {
   m_progress_bar.LabelText("Please wait...");
   m_progress_bar.Update(0,1);
   m_progress_bar.Show();
   m_chart.Redraw();
  }
//+------------------------------------------------------------------+
//| Fim do progresso                                                 |
//+------------------------------------------------------------------+
void CProgram::EndProgress(void)
  {
//--- Oculta a barra de progresso
   m_progress_bar.Hide();
//--- Atualiza o botão
   m_request.MouseFocus(false);
   m_request.Update(true);
   m_chart.Redraw();
  }

Quando um usuário clica em Request, o evento personalizado ON_CLICK_BUTTON é gerado e nós podemos definir um botão pressionado pelo ID do elemento. Se este é o botão Request, é iniciado o processo de obtenção dos dados

No método de criação de tabelas, nós incluímos a capacidade de ordenar a tabela clicando nos cabeçalhos. O evento personalizado ON_SORT_DATA é gerado toda vez que nós fazemos isso. Quando o evento é recebido, a tabela deve ser atualizada para exibir as alterações

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Botão pressionando eventos
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      if(lparam==m_request.Id())
        {
         //--- Obtém os dados
         GetData();
         return;
        }
      //---
      return;
     }
//--- Eventos da tabela ordenada
   if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA)
     {
      if(lparam==m_table.Id())
        {
         m_table.Update(true);
         return;
        }
      //---
      return;
     }
  }

Agora vamos ver os resultados. Se nós compilarmos o programa e o carregarmos no gráfico, o resultado será como na imagem abaixo. Os seguintes parâmetros são definidos por padrão:

 Fig. 8. GUI da aplicação em MQL

Fig. 8. GUI da aplicação em MQL

Pressionando Request inicia a aquisição de dados:

 Fig. 9. Recebendo os dados

Fig. 9. Recebendo os dados

Depois de receber todos os dados, você pode ordená-los:

 Fig. 10. Ordenação dos dados da tabela

Fig. 10. Ordenação dos dados da tabela

Você pode modificar e usar este aplicativo para resolver algumas das suas tarefas. A tabela pode ser preenchida com outros parâmetros.

Abaixo, eu fornecerei outro exemplo demonstrando como melhorar ainda mais a visibilidade dos dados da tabela. Como eu já mencionei no começo desta seção, a versão mais recente da biblioteca EasyAndFast apresenta a capacidade de definir a cor de fundo das células da tabela. Isso permite que você formate a tabela como você vê ajustada da mesma maneira que é feita em vários editores de tabela. A captura de tela abaixo mostra dados de formatação em planilhas do Excel. Cada célula tem sua própria cor de plano de fundo restante no mesmo valor, mesmo ao ordenar os arrays.

 Fig. 11. Escalas de cores em Excel

Fig. 11. Escalas de cores em Excel

Tal formatação possibilita a rápida execução de análise visual dos dados. 

Vamos fazer pequenas alterações e adições ao aplicativo MQL considerado acima. Para definir uma cor exclusiva para cada célula da tabela, desabilite a formatação no estilo zebra. Comente esta linha de código.

// m_table.IsZebraFormatRows(clrWhiteSmoke);

Agora vamos criar o método CProgram::SetColorsToTable() para a formatação da tabela. A classe CColors deve ser usada para trabalhar com cores. Ela já está presente na biblioteca para criar as GUIs, portanto, não há necessidade de incluir o arquivo no projeto. Declare dois arrays para o trabalho: (1) array para obter as cores do gradiente e (2) o array de cores da qual o gradiente deve ser formado. Nós vamos criar o gradiente de três cores. Quanto menor o valor, mais vermelha fica a cor (clrTomato). Quanto maior o valor, mais azul ele se torna (clrCornflowerBlue). Vamos adicionar a cor branca para separar essas duas zonas de cor. 

Defina o tamanho dos intervalos de valores do mínimo ao máximo. Este será o tamanho do array de gradientes. O método CColors::Gradient() é usado para definir o tamanho do array e preenchê-lo. As cores das células da tabela são definidas no loop final. Para não sair do alcance do array, o índice é calculado como o valor da célula menos o valor mínimo do intervalo. No final do método, a tabela é atualizada para exibir as alterações implementadas.

class CProgram : public CWndCreate
  {
private:
   //--- Preenchendo a tabela com cor de fundo para células
   void              SetColorsToTable(void);
  };
//+------------------------------------------------------------------+
//| Formatando a tabela                                              |
//+------------------------------------------------------------------+
void CProgram::SetColorsToTable(void)
  {
//--- Para trabalhar com cores
   CColors clr;
//--- Array para receber o gradiente
   color out_colors[];
//--- Gradiente de três cores
   color colors[3]={clrTomato,clrWhite,clrCornflowerBlue};
//--- Encontra os maiores e menores valores da tabela
   double max =0;
   double min =100;
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         max =::fmax(max,(double)m_table.GetValue(c,r));
         min =::fmin(min,(double)m_table.GetValue(c,r));
        }
     }
//--- Corrige para o inteiro mais próximo abaixo
   max =::floor(max);
   min =::floor(min);
//--- Obtém o intervalo
   int range =int(max-min)+1;
//--- Obtém o array do gradiente de cores
   clr.Gradient(colors,out_colors,range);
//--- Define a cor de fundo das células
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         int index=(int)m_table.GetValue(c,r)-(int)min;
         m_table.BackColor(c,r,out_colors[index],true);
        }
     }
//--- Atualiza a tabela
   m_table.Update();
  }

Abaixo você pode ver como isso fica na GUI. Nesse caso, os resultados mostram que quanto menor o valor, menor o número de tendências na área considerada. Seria sensato definir um intervalo de datas tão amplo quanto possível para obter informações usando o máximo de dados possível. 

 Fig. 12. Escala de cores para visualizar os dados da tabela

Fig. 12. Escala de cores para visualizar os dados da tabela

Quanto maior o intervalo de datas, mais dados são usados e, consequentemente, mais tempo será necessário para gerar os dados e calcular os parâmetros. Se não houver dados suficientes, será feito uma tentativa de baixá-los do servidor. 


Contagem do número de segmentos por tamanho

Agora, vamos desenvolver um programa para contar o número de segmentos pelo tamanho deles. Copia o EA da seção anterior e faz as alterações necessárias e adições a ele. Haverá duas tabelas aqui. O primeiro é usar apenas uma coluna com a lista de símbolos analisados. O segundo usa duas colunas de dados: (1) o aumento de intervalos em pontos e (2) o número de segmentos por intervalos na primeira coluna. Abaixo, você pode ver como a GUI aparece logo após o upload do aplicativo no gráfico.

 Fig. 13. Programa para calcular o número de segmentos por tamanho

Fig. 13. Programa para calcular o número de segmentos por tamanho

O botão Request solicita a lista de símbolos por um filtro especificado. Ao clicar Calculate, os dados do intervalo de tempo especificado são coletados e distribuídos na segunda tabela. 

Basicamente, todos os métodos permaneceram os mesmos que no EA anterior, então vamos considerar apenas as coisas relacionadas à segunda tabela. Primeiro, nós precisamos receber os dados do indicador. Isso é feito no método CProgram::GetIndicatorData(). Inicialmente, nós conectamos ao indicador ZigZag e depois obtemos os seus dados no intervalo de tempo especificado. O símbolo, período de tempo e o número de segmentos de indicadores obtidos são exibidos na barra de status. 

class CProgram : public CWndCreate
  {
private:
   //--- Obtém os dados do indicador
   void              GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Recebe os dados do indicador                                     |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period)
  {
//--- Obtém o manipulador do indicador
   string path   ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5";
   int    handle =::iCustom(symbol,period,path,0,0);
   if(handle!=INVALID_HANDLE)
     {
      //--- Copia os dados no intervalo especificado
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time);
      //--- Exibe os dados na barra de status
      string text="["+symbol+","+(string)GetPeriodName(period)+"] - Segments total: "+(string)m_zz.SegmentsTotal();
      m_status_bar.SetValue(0,text);
      m_status_bar.GetItemPointer(0).Update(true);
     }
//--- Libera o indicador
   ::IndicatorRelease(handle);
  }

As faixas de preço com uma etapa especificada devem ser calculadas para a primeira coluna. O método CProgram::GetLevels() é usado para isso. Para definir o número de intervalos, nós devemos primeiro obter o tamanho máximo do segmento no conjunto de dados obtidos. Em seguida, preencha o array com os níveis usando uma etapa especificada em um loop até que o valor máximo seja atingido. 

class CProgram : public CWndCreate
  {
private:
   //--- array de intervalo
   int               m_levels_array[];
   //---
private:
   //--- Obtém os níveis
   void              GetLevels(void);
  };
//+------------------------------------------------------------------+
//| Obtém os níveis                                                  |
//+------------------------------------------------------------------+
void CProgram::GetLevels(void)
  {
//--- Libera o array
   ::ArrayFree(m_levels_array);
//--- Obtém o tamanho máximo do segmento
   int max_value=int(m_zz.LargestSegment()/m_symbol.Point());
//--- Preenche o array com níveis
   int counter_levels=0;
   while(true)
     {
      int size=::ArraySize(m_levels_array);
      ::ArrayResize(m_levels_array,size+1);
      m_levels_array[size]=counter_levels;
      //---
      if(counter_levels>max_value)
         break;
      //---
      counter_levels+=(int)m_step.GetValue();
     }
  }

O método CProgram::SetDataToTable2() é usado para preencher a segunda tabela com os dados. No início, a verificação é executada se o símbolo é realçado na primeira lista da tabela. Se não estiver, o programa sai do método enviando a mensagem para o log da guia experts Se uma linha na primeira tabela estiver realçada, defina o símbolo e obtenha os dados nele. Depois disso, os métodos descritos acima são chamados para receber os dados do indicador e calcular os níveis. Nós recebemos os dados do indicador com o mesmo período de tempo em que o EA é lançado.

Quando nós sabemos o número de níveis, nós podemos construir uma tabela com o tamanho necessário e preenchê-la com os valores. Primeiro, preencha a primeira coluna com os valores de intervalo. Depois disso, preencha a segunda coluna. Ao mover sequencialmente por todos os intervalos, há o aumento do contador nas células para os segmentos que se enquadram nessa faixa.

class CProgram : public CWndCreate
  {
private:
   //--- Preenche a tabela 2 com dados
   void              SetDataToTable2(void);
  };
//+------------------------------------------------------------------+
//| Preenche a tabela 2 com dados                                    |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable2(void)
  {
//--- Encerra se a linha não estiver destacada
   if(m_table1.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a symbol in the table on the left!");
      return;
     }
//--- Início do progresso
   StartProgress();
//--- Oculta a mesa
   m_table2.Hide();
//--- Obtém o símbolo da primeira tabela
   string symbol=m_table1.GetValue(0,m_table1.SelectedItem());
   m_symbol.Name(symbol);
//--- Obtém os dados do indicador
   GetIndicatorData(symbol,_Period);
//--- Obtém os níveis
   GetLevels();
//--- Re-constrói a tabela
   RebuildingTable2();
//--- Define os intervalos na primeira coluna
   for(uint r=0; r<(uint)m_table2.RowsTotal(); r++)
      m_table2.SetValue(0,r,(string)m_levels_array[r],0);
//--- Obtém os valores para a segunda coluna
   int items_total=::ArraySize(m_levels_array);
   int segments_total=m_zz.SegmentsTotal();
   for(int i=0; i<items_total-1; i++)
     {
      //--- Progresso
      m_progress_bar.LabelText("Get data ["+(string)m_levels_array[i]+"]...");
      m_progress_bar.Update(i,m_table2.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size=int(m_zz.SegmentSize(s)/m_symbol.Point());
         if(size>m_levels_array[i] && size<m_levels_array[i+1])
           {
            int value=(int)m_table2.GetValue(1,i)+1;
            m_table2.SetValue(1,i,(string)value,0);
           }
        }
     }
//--- Mostrar a tabela
   m_table2.Update(true);
//--- Fim do progresso
   EndProgress();
  }

Como exemplo, vamos receber os segmentos para o EURUSD desde 2010 até o presente no gráfico M5. Defina os intervalos com o passo de 100 pontos de cinco dígitos. O resultado é mostrado na imagem abaixo.

O número total de segmentos é 302145. Como nós podemos ver, o número máximo de segmentos está dentro do intervalo de zero a 100. Mais adiante, o número de segmentos é reduzido de nível para nível. Dentro do período de tempo especificado, o tamanho máximo do segmento atingido foi de 2400 pontos de cinco dígitos. 

 Fig. 14. Resultado do cálculo do número de segmentos por tamanho

Fig. 14. Resultado do cálculo do número de segmentos por tamanho


Contagem do número de segmentos por duração

Também seria bom saber a duração dos segmentos nos grupos formados. Para encontrar padrões, nós precisamos ter todas as estatísticas dos dados analisados. Vamos desenvolver outra versão do EA. Basta copiar o programa da seção anterior e adicionar outra tabela para a GUI. A tabela deve apresentar duas colunas: (1) número de barras e (2) número de segmentos com esse número de barras. Abaixo, você pode ver como a GUI aparece logo após o upload do aplicativo no gráfico.

 Fig. 15. Programa para calcular o número de segmentos por duração

Fig. 15. Programa para calcular o número de segmentos por duração

A sequência de ações para receber os dados em todas as tabelas é a seguinte:

A lista abaixo fornece o código do método CProgram::SetDataToTable3() para receber os dados e preencher a terceira tabela. A linha destacada aqui é usada para receber o intervalo, dentro do qual o número de segmentos deve ser calculado por sua duração. O número de linhas na tabela é definido pelo segmento mais longo (em barras) do conjunto de dados obtido. Ao preencher a segunda coluna da tabela, percorra todas as linhas e conte os segmentos que se encaixam o intervalo selecionado e o número de barras por tamanho.

class CProgram : public CWndCreate
  {
private:
   //--- Preenche a tabela 3 com dados
   void              SetDataToTable3(void);
  };
//+------------------------------------------------------------------+
//| Preenche a tabela 3 com dados                                    |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable3(void)
  {
//--- Encerra se a linha não estiver destacada
   if(m_table2.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a range in the table on the left!");
      return;
     }
//--- Início do progresso
   StartProgress();
//--- Oculta a mesa
   m_table3.Hide();
//--- Obtém a linha destacada
   int selected_row_index=m_table2.SelectedItem();
//--- Intervalo
   int selected_range=(int)m_table2.GetValue(0,selected_row_index);
//--- Re-constrói a tabela
   RebuildingTable3();
//--- Define os valores para a primeira coluna
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
      m_table3.SetValue(0,r,(string)(r+1),0);
//--- Obtém os valores da segunda coluna
   int segments_total=m_zz.SegmentsTotal();
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
     {
      //--- Progresso
      m_progress_bar.LabelText("Get data ["+(string)r+"]...");
      m_progress_bar.Update(r,m_table3.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size =int(m_zz.SegmentSize(s)/m_symbol.Point());
         int bars =m_zz.SegmentBars(s);
         //---
         if(size>selected_range && 
            size<selected_range+(int)m_step.GetValue() && 
            bars==r+1)
           {
            int value=(int)m_table3.GetValue(1,r)+1;
            m_table3.SetValue(1,r,(string)value,0);
           }
        }
     }
//--- Exibe a tabela
   m_table3.Update(true);
//--- Fim do progresso
   EndProgress();
  }

Ao destacar as linhas da tabela e da lista, o evento personalizado ON_CLICK_LIST_ITEM é gerado. Neste caso, nós rastreamos a chegada do evento com o ID da segunda tabela.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Eventos de clique nas linhas
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Linha da tabela clicada
      if(lparam==m_table2.Id())
        {
         //--- Obtém os dados para a terceira tabela
         SetDataToTable3();
         return;
        }
      //---
      return;
     }
...
  }

Ao receber uma nova lista de símbolos ou calcular os dados no novo símbolo realçado na primeira tabela, dados irrelevantes de cálculos anteriores devem ser apagados das tabelas para evitar confusão sobre quais dados são exibidos atualmente.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento de clique nos botões
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Botão de solicitação clicado
      if(lparam==m_request.Id())
        {
         //--- Obtém os dados para a primeira tabela
         SetDataToTable1();
         //--- Exclui os dados irrelevantes das tabelas
         m_table2.DeleteAllRows(true);
         m_table3.DeleteAllRows(true);
         return;
        }
      //--- Calcula o botão clicado
      if(lparam==m_calculate.Id())
        {
         //--- Obtém os dados para a segunda tabela
         SetDataToTable2();
         //--- Exclui os dados irrelevantes das tabelas
         m_table3.DeleteAllRows(true);
        }
      //---
      return;
     }
...
  }

Depois de lançar o EA no gráfico, nós obtemos o resultado conforme mostrado abaixo. Neste caso, nós formamos a lista de pares de moedas com USD. Os dados sobre GBPUSD do início de 2018 foram recebidos posteriormente e a lista de intervalos (segunda tabela) foi formada com o passo de 100 e os segmentos calculados para cada um deles. Como exemplo, a linha com o intervalo de 200 e o número de segmentos de 1922 (de 200 a 300) é destacado na segunda tabela. A terceira tabela exibe a duração de todos os segmentos do intervalo realçado na segunda tabela. Por exemplo, nós podemos ver que apenas 11 segmentos com a duração de 10 barras do intervalo especificado estavam presentes no GBPUSD durante este período.

 Fig. 16. Resultado do cálculo do número de segmentos por duração

Fig. 16. Resultado do cálculo do número de segmentos por duração


Alguns detalhes de como trabalhar com a interface gráfica

Como um suplemento, eu gostaria de mostrar como lidar adequadamente com o evento de alterar um símbolo do gráfico e um período de tempo quando uma GUI é usada em um programa MQL. Como as GUIs podem conter vários controles, pode levar algum tempo para carregar e inicializar todo o conjunto. Às vezes, esse tempo pode ser salvo, o que é exatamente o caso de alterar um símbolo de gráfico e um período de tempo. Aqui, não há necessidade de remover e criar constantemente uma GUI repetidamente.

Isso pode ser conseguido da seguinte maneira:

Crie um campo para armazenar o último motivo da desinicialização do programa na classe principal do programa:

class CProgram : public CWndCreate
  {
private:
   //--- Último motivo para a desinicialização
   int               m_last_deinit_reason;
  };
//+------------------------------------------------------------------+
//| Construtor                                                       |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE)
  {
  }

Durante a desinicialização, a GUI é removida em todos os casos, exceto aqueles em que a razão é REASON_CHARTCHANGE.

//+------------------------------------------------------------------+
//| Desinicialização                                                 |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
//--- Lembre-se do último motivo de desinicialização
   m_last_deinit_reason=reason;
//--- Remove a GUI se o motivo não estiver relacionado a alterar um símbolo e um ponto
   if(reason!=REASON_CHARTCHANGE)
     {
      CWndEvents::Destroy();
     }
  }

Como a GUI é criada ao inicializar o programa chamando o método CProgram::CreateGUI(), agora é suficiente para verificar a última causa de desinicialização. Se a razão for que um símbolo ou período de tempo foi alterado, não há necessidade de criar uma GUI. Em vez disso, simplesmente saia do método notificando que tudo está bem.

//+------------------------------------------------------------------+
//| Cria a GUI                                                       |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Encerra se um gráfico ou período de tempo tiver sido alterado
   if(m_last_deinit_reason==REASON_CHARTCHANGE)
      return(true);
...
   return(true);
  }


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.

Na próxima parte, eu vou mostrar que outros dados podem ser obtidos usando as ferramentas desenvolvidas nesses artigos.

Nome do arquivo Comentário
MQL5\Indicators\Custom\ZigZag\FrequencyChangeZZ.mq5 Indicador para calcular a frequência de formação de segmentos do indicador ZigZag de direção oposta
MQL5\Indicators\Custom\ZigZag\SumSegmentsZZ.mq5 Indicador para calcular a soma dos segmentos do conjunto obtido e seu valor médio
MQL5\Indicators\Custom\ZigZag\PercentageSegmentsZZ.mq5 Indicador da relação percentual das somas do segmento e da diferença entre elas
MQL5\Indicators\Custom\ZigZag\MultiPercentageSegmentsZZ.mq5 Indicador para definir a natureza da formação de vários segmentos a partir de um período de tempo maior, usando a diferença entre as taxas percentuais das somas dos segmentos com direção oposta
MQL5\Experts\ZigZag\TestZZ_05.mq5 EA para testar o indicador MultiPercentageSegmentsZZ
MQL5\Experts\ZigZag\ZZ_Scanner_01.mq5 EA para a coleta de estatísticas sobre o indicador PercentageSegmentsZZ
MQL5\Experts\ZigZag\ZZ_Scanner_02.mq5 EA para calcular os segmentos em diferentes faixas de preço
MQL5\Experts\ZigZag\ZZ_Scanner_03.mq5  EA para calcular os segmentos localizados em diferentes faixas de preço e com duração diferente