Estudo de técnicas de análise de velas (parte IV): Atualizações e adições ao Pattern Analyzer

Alexander Fedosov | 31 maio, 2019

Índice

Introdução

Nos artigos anteriores desta série, nós criamos um aplicativo MetaTrader 5, que testou a relevância dos padrões de velas existentes. Uma versão posterior mostrava a possibilidade de criar padrões personalizados baseados nos tipos simples de velas, como velas curtas e longas, doji, spining top, etc. Na última parte, nós desenvolvemos uma biblioteca para a criação do indicadores e Expert Advisors baseados nos padrões de velas.

Este artigo apresenta uma nova versão do aplicativo Pattern Analyzer. Esta versão fornece correções de bugs e novos recursos, bem como a interface de usuário revisada. Os comentários e sugestões do artigo anterior foram levados em conta no desenvolvimento da nova versão. A aplicação resultante é descrita neste artigo.

Visão geral da atualização

A interface do usuário é uma parte importante de qualquer aplicativo: uma estrutura de interface bem preparada torna o uso do aplicativo mais eficiente. Vamos comparar o novo visual da aplicação com o anterior. Vamos começar com a guia Analysis: por que ela precisou de melhorias.

Fig.1 Interface da guia Analyze na versão anterior

Ponto 1. Tabulação e dimensões.

Na Fig.1, as guias marcadas por 1 estão localizadas na parte superior da janela. A parte superior direita aqui está vazia e não é usada, porém a parte não é suficiente para adicionar mais guias. A fonte do texto é muito pequena. Essas três guias foram movidas para a parte esquerda da janela: elas agora estão organizadas verticalmente e são mais visíveis. Além disso, há espaço adicional para adicionar mais seções.

Ponto 2. Tabelas com os resultados de teste do padrão.

A apresentação dos dados visuais não é muito eficiente. Portanto, a fonte, a altura da linha e o tamanho da tabela foram aumentados para melhorar a legibilidade.

Ponto 3. Seleção do período de tempo atual.

A estrutura de seleção 'Período de tempo -> Resultado' para todos os padrões limita a exibição visual dos resultados do teste. Para melhorar isso, nós desenvolveremos uma opção de seleção de vários períodos de tempo, bem como uma seleção individual dos padrões analisados. Isso permitirá uma personalização mais flexível da operação com os padrões. 

Ponto 4. Faixa de amostragem.

A ideia implementada na versão anterior era testar no intervalo dos dados atuais para um determinado número de velas no histórico. Uma seleção mais específica de uma data para outra não estava disponível. Portanto, o método de seleção do intervalo será revisado. Abaixo, a Fig.2 apresenta a solução de todas as questões acima mencionadas e as possíveis melhorias.

Fig. 2. Interface atualizada da guia Analyze.

Aqui estão as soluções para os pontos acima.

A janela do aplicativo ficou maior para a exibição dos novos elementos. Outra novidade importante é que o parâmetro 'Trend threshold value' (Fig.3) em pontos foi movido da guia Settings para as guias Analyze e AutoSearch. A configuração é individual para cada uma das guias. 


Fig.3 Nova posição do parâmetro Trend Threshold

O último elemento que foi alterado é a estrutura da tabela Resultados. A coluna de ocorrência foi removida e um parâmetro mais relevante, o período de tempo, foi adicionado. 

Fig. 4. A nova estrutura da tabela de resultados

Agora vamos ver as melhorias na segunda guia, AutoSearch, que trabalha com os padrões gerados.

Ponto 1. Configurações em guias diferentes.

As configurações que são diretamente relacionadas à seção AutoSearch estavam localizadas na guia Setting, portanto era necessário alternar constantemente entre as guias AutoSearch e Setting para alterar as configurações. É por isso que quase todas as configurações foram movidas para a guia AutoSearch. Outras melhorias foram implementadas em relação ao valor do limiar da Tendência, seleção dos períodos de tempo e do intervalo das datas. O resultado da atualização da guia AutoSearch é mostrado na Fig.5.

Fig.5 Funcionalidade atualizada na guia AutoSearch 

 Isso permite um trabalho mais conveniente com os padrões. O intervalo de datas nesta guia também é individual.


Implementação de atualizações

Vamos considerar com mais detalhes a implementação das atualizações acima, assim como as alterações nos cálculos.

Estrutura das janelas de aplicativos. Método de criação da janela principal.

O método CPrograma::CreateGUI() responsável pela criação da interface gráfica foi complementado:

//+------------------------------------------------------------------+
//| Cria a interface gráfica do programa                             |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Criando um painel
   if(!CreateWindow("Pattern Analyzer"))
      return(false);
//--- Criando uma janela de diálogo
   if(!CreateWindowSetting1("Settings"))
      return(false);
//--- Criando uma janela de diálogo
   if(!CreateWindowSetting2("Date range settings"))
      return(false);
//--- Criando uma janela de diálogo
   if(!CreateWindowSetting3("Date range settings"))
      return(false);
//--- Criação completa da GUI
   CWndEvents::CompletedGUI();
   return(true);
  }
//+-----------------------------------------------------------------

Os métodos CreateWindowSetting2() e CreateWindowSetting3() são responsáveis pela exibição da nova ferramenta de seleção do intervalo de datas mostrada na Fig.1. O método principal para a criação de janelas do aplicativo CreateWindow() também foi redesenhado. Ele foi dividido em três blocos correspondentes aos elementos da interface do usuário de cada uma das guias: Analyze, AutoSearch e Setting.

//+------------------------------------------------------------------+
//| A guia Analyze                                                   |
//+------------------------------------------------------------------+
//--- Cria os botões do conjunto de padrões
   if(!CreatePatternSet(m_patterns,10,10))
      return(false);
//--- Cabeçalhos do período de tempo
   if(!CreateTFLabel(m_text_labels[1],10,100,0))
      return(false);
//--- Cria os botões do conjunto de períodos de tempo
   if(!CreateTimeframeSet(m_timeframes,10,125,0))
      return(false);
//--- Janela de busca com o filtro do símbolo 
   if(!CreateSymbolsFilter(m_symb_filter1,m_request1,10,180,0))
      return(false);
//--- Cria um botão para a seleção do intervalo da data
   if(!CreateDateRange(m_request3,280,180,0))
      return(false);
//--- Cria um campo de entrada para o valor do limiar do lucro
   if(!CreateThresholdValue(m_threshold1,400,180,100,0))
      return(false);
//--- Cria uma tabela de símbolos
   if(!CreateSymbTable(m_symb_table1,10,225,0))
      return(false);
//--- Cria uma tabela de resultados
   if(!CreateTable1(m_table1,120,225,0))
      return(false);

Os métodos para a exibição de novos elementos da interface foram adicionados na primeira guia. 

//+------------------------------------------------------------------+
//| A guia AutoSearch                                                |
//+------------------------------------------------------------------+
   if(!CreateTFLabel(m_text_labels[4],10,10,1))
      return(false);
//--- Botões
   if(!CreateDualButton(m_buttons[6],m_buttons[7],200,50))
      return(false);
   if(!CreateTripleButton(m_buttons[8],m_buttons[9],m_buttons[10],10,50))
      return(false);
//--- Cabeçalhos do período de tempo
   if(!CreateTFLabel(m_text_labels[5],10,100,1))
      return(false);
//--- Cria os botões do conjunto de períodos de tempo
   if(!CreateTimeframeSet(m_timeframes1,10,125,1))
      return(false);
//--- Campos de edição
   if(!CreateSymbolsFilter(m_symb_filter2,m_request2,10,180,1))
      return(false);
//--- Cria um botão para a seleção do intervalo da data
   if(!CreateDateRange(m_request4,280,180,1))
      return(false);
//--- Cria um campo de entrada para o valor do limiar do lucro
   if(!CreateThresholdValue(m_threshold2,400,180,100,1))
      return(false);
//--- Cria uma tabela de símbolos
   if(!CreateSymbTable(m_symb_table2,10,225,1))
      return(false);
//--- Cria uma tabela de resultados
   if(!CreateTable2(m_table2,120,225,1))
      return(false);

Além disso, os seguintes métodos foram movidos para a segunda guia AutoSearch a partir da guia Setting (Fig.5): os métodos responsáveis pela exibição dos elementos relacionados à seleção de tamanhos de padrões gerados CreateTripleButton() e a opção alternável Repeat/No repete com o método CreateDualButton(). Novos métodos foram adicionados: métodos responsáveis pelo cabeçalho do período de tempo e sua seleção.

//+------------------------------------------------------------------+
//| A guia Settings                                                  |
//+------------------------------------------------------------------+
//--- Criando as configurações de velas
   if(!CreateCandle(m_pictures[0],m_buttons[0],m_candle_names[0],"Long",10,10,"Images\\EasyAndFastGUI\\Candles\\long.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[1],m_buttons[1],m_candle_names[1],"Short",104,10,"Images\\EasyAndFastGUI\\Candles\\short.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[2],m_buttons[2],m_candle_names[2],"Spinning top",198,10,"Images\\EasyAndFastGUI\\Candles\\spin.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[3],m_buttons[3],m_candle_names[3],"Doji",292,10,"Images\\EasyAndFastGUI\\Candles\\doji.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[4],m_buttons[4],m_candle_names[4],"Marubozu",386,10,"Images\\EasyAndFastGUI\\Candles\\maribozu.bmp"))
      return(false);
   if(!CreateCandle(m_pictures[5],m_buttons[5],m_candle_names[5],"Hammer",480,10,"Images\\EasyAndFastGUI\\Candles\\hammer.bmp"))
      return(false);
//--- Rótulos de texto
   if(!CreateTextLabel(m_text_labels[0],10,140))
      return(false);
   if(!CreateTextLabel(m_text_labels[3],300,140))
      return(false);
//--- Campos de edição
   if(!CreateCoef(m_coef1,10,180,"K1",1))
      return(false);
   if(!CreateCoef(m_coef2,100,180,"K2",0.5))
      return(false);
   if(!CreateCoef(m_coef3,200,180,"K3",0.25))
      return(false);
   if(!CreateLanguageSetting(m_lang_setting,10,240,2))
      return(false);
//--- Lista
   if(!CreateListView(300,180))
      return(false);
//---
   if(!CreateCheckBox(m_checkbox1,300+8,160,"All candlesticks"))
      return(false);
//--- Barra de estado
   if(!CreateStatusBar(1,26))
      return(false);

A seção Setting agora contém menos elementos. Ela contém as configurações individuais de vela, configurações de taxas para o cálculo dos coeficientes de probabilidade e eficiência, seleção do idioma de interface e seleção da vela para a geração do padrão na guia AutoSearch. Em seguida, considere os novos métodos com mais detalhes. 

O método para a criação de seleção de padrões CreatePatternSet(). Ele é um conjunto de botões comutáveis para a seleção dos padrões existentes para a análise.

Fig.6 O princípio do seletor de padrões para análise

A implementação é apresentada abaixo:

//+------------------------------------------------------------------+
//| Cria um conjunto de botões de padrão                             |
//+------------------------------------------------------------------+
bool CProgram::CreatePatternSet(CButton &button[],int x_gap,int y_gap)
  {
   ArrayResize(button,15);
   string pattern_names[15]=
     {
      "Hummer",
      "Invert Hummer",
      "Handing Man",
      "Shooting Star",
      "Engulfing Bull",
      "Engulfing Bear",
      "Harami Cross Bull",
      "Harami Cross Bear",
      "Harami Bull",
      "Harami Bear",
      "Doji Star Bull",
      "Doji Star Bear",
      "Piercing Line",
      "Dark Cloud Cover",
      "All Patterns"
     };
   int k1=x_gap,k2=x_gap,k3=x_gap;
   for(int i=0;i<=14;i++)
     {
      if(i<5)
        {
         CreatePatternButton(button[i],pattern_names[i],k1,y_gap);
         k1+=150;
        }
      else if(i>=5 && i<10)
        {
         CreatePatternButton(button[i],pattern_names[i],k2,y_gap+30);
         k2+=150;
        }
      else if(i>=10 && i<14)
        {
         CreatePatternButton(button[i],pattern_names[i],k3,y_gap+60);
         k3+=150;
        }
      else if(i==14)
        {
         CreatePatternButton(button[i],pattern_names[i],k3,y_gap+60);
        }
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| Cria um botão para a seleção de um padrão para análise           |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Candles\\passive.bmp"
#resource "\\Images\\EasyAndFastGUI\\Candles\\pressed.bmp"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreatePatternButton(CButton &button,const string candlename,const int x_gap,const int y_gap)
  {
//--- Salva o ponteiro para o controle principal
   button.MainPointer(m_tabs1);
//--- Anexa à guia
   m_tabs1.AddToElementsArray(0,button);
//--- Propriedades
   button.XSize(120);
   button.YSize(20);
   button.Font("Trebuchet");
   button.FontSize(9);
   button.LabelColor(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.IsCenterText(true);
   button.TwoState(true);
   button.IconFile("Images\\EasyAndFastGUI\\Candles\\passive.bmp");
   button.IconFilePressed("Images\\EasyAndFastGUI\\Candles\\pressed.bmp");
//--- Cria um controle
   if(!button.CreateButton(candlename,x_gap,y_gap))
      return(false);
//--- Adiciona o ponteiro do elemento à base de dados
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

Preste atenção ao último botão 'All Patterns' que seleciona/desmarca todos os padrões. O pressionamento do botão é processado pelo código adicional na seção de manipulamento do evento de pressionamento do botão:

   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Seleciona e desmarca todos os botões do padrão
      if(lparam==m_patterns[14].Id())
        {
         if(m_patterns[14].IsPressed())
           {
            for(int i=0;i<14;i++)
               m_patterns[i].IsPressed(true);
           }
         else if(!m_patterns[14].IsPressed())
           {
            for(int i=0;i<14;i++)
               m_patterns[i].IsPressed(false);
           }
         for(int i=0;i<14;i++)
            m_patterns[i].Update(true);
        }
...
}

O método atual de seleção do período de tempo CreateTimeframeSet() é muito semelhante ao anterior. Ele também possui um conjunto de botões comutáveis que selecionam os períodos de tempo para análise.

Fig.7 O princípio do seletor do período de tempo para análise

A implementação é apresentada no código abaixo:

//+------------------------------------------------------------------+
//| Cria um conjunto de botões do período de tempo                   |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeSet(CButton &button[],int x_gap,int y_gap,const int tab)
  {
   ArrayResize(button,22);
   string timeframe_names[22]=
     {"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN","ALL"};
   int k1=x_gap,k2=x_gap;
   for(int i=0;i<22;i++)
     {
      CreateTimeframeButton(button[i],timeframe_names[i],k1,y_gap,tab);
      k1+=33;
     }
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeButton(CButton &button,const string candlename,const int x_gap,const int y_gap,const int tab)
  {
//--- Salva o ponteiro para o controle principal
   button.MainPointer(m_tabs1);
//--- Anexa à guia
   m_tabs1.AddToElementsArray(tab,button);
//--- Propriedades
   button.XSize(30);
   button.YSize(30);
   button.Font("Trebuchet");
   button.FontSize(10);
   button.LabelColor(clrWhite);
   button.LabelColorHover(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.BackColor(C'200,200,200');
   button.BackColorHover(C'200,200,200');
   button.BackColorPressed(C'50,180,75');
   button.BorderColor(C'200,200,200');
   button.BorderColorHover(C'200,200,200');
   button.BorderColorPressed(C'50,180,75');
   button.IsCenterText(true);
   button.TwoState(true);
//--- Cria um controle
   if(!button.CreateButton(candlename,x_gap,y_gap))
      return(false);
//--- Adiciona o ponteiro do elemento à base de dados
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

It also has a button for selecting/unselecting all timeframes and is processed in the button-press handling section:

//--- Seleciona e desmarca todos os botões do padrão
      if(lparam==m_timeframes[21].Id())
        {
         if(m_timeframes[21].IsPressed())
           {
            for(int i=0;i<21;i++)
               m_timeframes[i].IsPressed(true);
           }
         else if(!m_timeframes[21].IsPressed())
           {
            for(int i=0;i<21;i++)
               m_timeframes[i].IsPressed(false);
           }
         for(int i=0;i<21;i++)
            m_timeframes[i].Update(true);
        }

O próximo item novo é o botão de intervalo de datas (Date Range). Ele faz parte da nova ferramenta de configuração da faixa de amostra composta. Ele é implementado usando o método CreateDateRange().

Fig.8 O princípio do seletor de intervalo de datas para análise

A implementação é apresentada abaixo:

//+----------------------------------------------------------------------+
//| Cria um botão para mostrar a janela de seleção do intervalo de datas |
//+----------------------------------------------------------------------+
bool CProgram::CreateDateRange(CButton &button,const int x_gap,const int y_gap,const int tab)
  {
//--- Salva o ponteiro para o controle principal
   button.MainPointer(m_tabs1);
//--- Anexa à guia
   m_tabs1.AddToElementsArray(tab,button);
//--- Propriedades
   button.XSize(100);
   button.YSize(25);
   button.Font("Trebuchet");
   button.FontSize(10);
   button.IsHighlighted(false);
   button.IsCenterText(true);
   button.BorderColor(C'0,100,255');
   button.BackColor(clrAliceBlue);
//--- Cria um controle
   if(!button.CreateButton("",x_gap,y_gap))
      return(false);
//--- Adiciona o ponteiro do elemento à base de dados
   CWndContainer::AddToElementsArray(0,button);
   return(true);
  }

O manipulador de eventos do pressionamento do botão também inclui o código que é responsável pela exibição da caixa de diálogo com o intervalo de datas:

      //---
      if(lparam==m_request3.Id())
        {
         int x=m_request3.X();
         int y=m_request3.Y()+m_request3.YSize();
         m_window[2].X(x);
         m_window[2].Y(y);
         m_window[2].OpenWindow();
         val=(m_lang_index==0)?"Настройки диапазона дат":"Date Range Settings";
         m_window[2].LabelText(val);
        }

Não há necessidade de descrever os novos elementos que foram adicionados na guia, pois são semelhantes à implementação dos elementos da guia Analyze, com exceção dos parâmetros das coordenadas. Portanto, vamos considerar os métodos responsáveis pela exibição de outras novas janelas. 

Estrutura das janelas de aplicativos. Um método para a criação da caixa de diálogo do aplicativo.

Os métodos que exibem as caixas de diálogo para as guias Analyze e AutoSearch são semelhantes e, portanto, nós iremos considerar um deles.

//+------------------------------------------------------------------+
//| Cria uma caixa de diálogo de seleção do intervalo de datas na    |
//| guia Analyze                                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateWindowSetting2(const string caption_text)
  {
//--- Adiciona o ponteiro ao array da janela
   CWndContainer::AddWindow(m_window[2]);
//--- Coordenadas
   int x=m_request3.X();
   int y=m_request3.Y()+m_request3.YSize();
//--- Propriedades
   m_window[2].XSize(372);
   m_window[2].YSize(300);
   m_window[2].WindowType(W_DIALOG);

//--- Cria o formulário
   if(!m_window[2].CreateWindow(m_chart_id,m_subwin,caption_text,x,y))
      return(false);
//---
   if(!CreateCalendar(m_calendar1,m_window[2],10,25,D'01.01.2018',2))
      return(false);
   if(!CreateCalendar(m_calendar2,m_window[2],201,25,m_calendar2.Today(),2))
      return(false);
//---
   if(!CreateTimeEdit(m_time_edit1,m_window[2],10,200,"Time",2))
      return(false);
   if(!CreateTimeEdit(m_time_edit2,m_window[2],200,200,"Time",2))
      return(false);
//---
   return(true);
  }


Seção de cálculo. Métodos de busca por padrões e velas redesenhadas.

Devido as sérias alterações na estrutura da interface do usuário, bem como devido à adição de novos elementos e à exclusão de alguns antigos, os métodos de cálculo também foram alterados. Dois métodos de cálculo estão disponíveis no aplicativo atual: o primeiro é usado para os padrões existentes e o segundo método é usado para os padrões gerados. 

O cálculo é iniciado após o clique em um dos instrumentos de negociação disponíveis na tabela Symbols. Esta regra se aplica a ambas as guias, Analyze e AutoSearch. Um dos dois métodos é chamado dependendo da guia.

//+------------------------------------------------------------------+
//| Alteração do símbolo na guia Analyze                             |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol1(const long id)
  {
//--- Verifica o ID do elemento
   if(id!=m_symb_table1.Id())
      return(false);
//--- Sai se a linha não estiver selecionada
   if(m_symb_table1.SelectedItem()==WRONG_VALUE)
     {
      //--- Exibe a descrição completa do símbolo na barra de estado
      m_status_bar.SetValue(0,"No symbol selected for analysis");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- Obtém um símbolo
   string symbol=m_symb_table1.GetValue(0,m_symb_table1.SelectedItem());
//--- Exibe a descrição completa do símbolo na barra de estado
   string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: ";
   m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
//---
   GetPatternType(symbol);
   return(true);
  }
//+------------------------------------------------------------------+
//| Alteração do símbolo na guia AutoSearch                          |
//+------------------------------------------------------------------+
bool CProgram::ChangeSymbol2(const long id)
  {
//--- Verifica o ID do elemento
   if(id!=m_symb_table2.Id())
      return(false);
//--- Sai se a linha não estiver selecionada
   if(m_symb_table2.SelectedItem()==WRONG_VALUE)
     {
      //--- Exibe a descrição completa do símbolo na barra de estado
      m_status_bar.SetValue(0,"No symbol selected for analysis");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- Obtém um símbolo
   string symbol=m_symb_table2.GetValue(0,m_symb_table2.SelectedItem());
//--- Exibe a descrição completa do símbolo na barra de estado
   string val=(m_lang_index==0)?"Выбранный символ: ":"Selected symbol: ";
   m_status_bar.SetValue(0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
//---
   if(!GetCandleCombitation())
     {
      if(m_lang_index==0)
         MessageBox("Число выбранных свечей меньше размера исследуемого паттерна!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("The number of selected candles is less than the size of the studied pattern!","Error",MB_OK);
      return(false);
     }
//---
   GetPatternType(symbol,m_total_combination);
   return(true);
  }

O método GetPattertType() com os dois tipos de argumentos diferentes é chamado no final de cada método. Este é o método-chave na busca por padrões e no manuseio dos resultados obtidos. Agora vamos considerar cada um dos métodos de maneira detalhada.

O primeiro tipo do método é usado para procurar por padrões existentes.

   bool              GetPatternType(const string symbol);

O método tem uma implementação bastante longa, portanto, será fornecido abaixo um exemplo para apenas um padrão.

//+------------------------------------------------------------------+
//| Reconhecimento de padrão                                         |
//+------------------------------------------------------------------+
bool CProgram::GetPatternType(const string symbol)
  {
   CANDLE_STRUCTURE cand1,cand2;
//---
   RATING_SET hummer_coef[];
   RATING_SET invert_hummer_coef[];
   RATING_SET handing_man_coef[];
   RATING_SET shooting_star_coef[];
   RATING_SET engulfing_bull_coef[];
   RATING_SET engulfing_bear_coef[];
   RATING_SET harami_cross_bull_coef[];
   RATING_SET harami_cross_bear_coef[];
   RATING_SET harami_bull_coef[];
   RATING_SET harami_bear_coef[];
   RATING_SET doji_star_bull_coef[];
   RATING_SET doji_star_bear_coef[];
   RATING_SET piercing_line_coef[];
   RATING_SET dark_cloud_cover_coef[];
//--- Recebe os dados para os períodos de tempo selecionados
   GetTimeframes(m_timeframes,m_cur_timeframes1);
   int total=ArraySize(m_cur_timeframes1);
//--- Verifica pelo menos um período de tempo selecionado
   if(total<1)
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not selected a working timeframe!","Error",MB_OK);
      return(false);
     }
   int count=0;
   m_total_row=0;
   m_table_number=1;
//--- Exclui todas as linhas
   m_table1.DeleteAllRows();
//--- Obtém o intervalo de datas
   datetime start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   datetime end=StringToTime(TimeToString(m_calendar2.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit2.GetHours()+":"+(string)m_time_edit2.GetMinutes()+":00");
//--- Verifica as datas especificadas
   if(start>end || end>TimeCurrent())
     {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return(false);
     }
//--- Martelo, modelo altista
   if(m_patterns[0].IsPressed())
     {
      ArrayResize(m_hummer_total,total);
      ArrayResize(hummer_coef,total);
      ZeroMemory(m_hummer_total);
      ZeroMemory(hummer_coef);
      ZeroMemory(cand1);
      count++;
      //--- Cálculo pelos períodos de tempo
      for(int j=0;j<total;j++)
        {
         MqlRates rt[];
         ZeroMemory(rt);
         int copied=CopyRates(symbol,m_cur_timeframes1[j],start,end,rt);
         for(int i=0;i<copied;i++)
           {
            GetCandleType(symbol,cand1,m_cur_timeframes1[j],i);             // Vela atual
            if(cand1.trend==DOWN &&                                        // Verifica a direção da tendência
               cand1.type==CAND_HAMMER)                                    // Verifica o "Martelo"
              {
               m_hummer_total[j]++;
               GetCategory(symbol,i+3,hummer_coef[j],m_cur_timeframes1[j],m_threshold_value1);
              }
           }
         AddRow(m_table1,"Hammer",hummer_coef[j],m_hummer_total[j],m_cur_timeframes1[j]);
        }
     }
...
//---
   if(count>0)
     {
      //---
      m_table1.DeleteRow(m_total_row);
      //--- Atualiza a tabela
      m_table1.Update(true);
      m_table1.GetScrollVPointer().Update(true);
     }
   else
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали паттерн!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not chosen a pattern!","Error",MB_OK);
     }
   return(true);
  }

O algoritmo de operação é executado da seguinte maneira:

Em seguida, nós consideramos os métodos que existiam nas versões anteriores e que foram alterados na versão atualizada, bem como um novo método para o cálculo dos dados obtidos e sua exibição na tabela de resultados:

O método GetPatternType() para busca de padrão tem duas implementações diferentes, enquanto esses três métodos são universais. Vamos considerá-los em detalhes:

//+------------------------------------------------------------------+
//| Reconhecimento do tipo da vela                                   |
//+------------------------------------------------------------------+
bool CProgram::GetCandleType(const string symbol,CANDLE_STRUCTURE &res,ENUM_TIMEFRAMES timeframe,const int shift)
  {
   MqlRates rt[];
   int aver_period=5;
   double aver=0.0;
   datetime start=TimeCurrent();
   SymbolSelect(symbol,true);
   //--- Obtém a data de início do intervalo, dependendo do tipo de padrões
   if(m_table_number==1)
      start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   else if(m_table_number==2)
      start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00");
//--- Desloca a data 
   start+=PeriodSeconds(timeframe)*shift;
   int copied=CopyRates(symbol,timeframe,start,aver_period+1,rt);
   if(copied<6)
     {
      Print(start,": Not enough data for calculation — ",GetLastError());
     }
//--- Obtém os detalhes da vela anterior
   if(copied<aver_period)
      return(false);
//---
   res.open=rt[aver_period].open;
   res.high=rt[aver_period].high;
   res.low=rt[aver_period].low;
   res.close=rt[aver_period].close;
//--- Determina a direção da tendência
   for(int i=0;i<aver_period;i++)
      aver+=rt[i].close;

   aver/=aver_period;

   if(aver<res.close)
      res.trend=UPPER;
   if(aver>res.close)
      res.trend=DOWN;
   if(aver==res.close)
      res.trend=FLAT;
//--- Determina se é uma vela de alta ou de baixa
   res.bull=res.open<res.close;
//--- Obtém o tamanho absoluto do corpo da vela
   res.bodysize=MathAbs(res.open-res.close);
//--- Obtém os tamanhos das sombras
   double shade_low=res.close-res.low;
   double shade_high=res.high-res.open;
   if(res.bull)
     {
      shade_low=res.open-res.low;
      shade_high=res.high-res.close;
     }
   double HL=res.high-res.low;
//--- Calcula o tamanho médio do corpo das velas anteriores
   double sum=0;
   for(int i=1; i<=aver_period; i++)
      sum+=MathAbs(rt[i].open-rt[i].close);
   sum/=aver_period;

//--- Determina o tipo de vela   
   res.type=CAND_NONE;
//--- alta 
   if(res.bodysize>sum*m_long_coef && res.bull)
      res.type=CAND_LONG_BULL;
//--- baixa 
   if(res.bodysize<sum*m_short_coef && res.bull)
      res.type=CAND_SHORT_BULL;
//--- comprido e altista
   if(res.bodysize>sum*m_long_coef && !res.bull)
      res.type=CAND_LONG_BEAR;
//--- curto e baixista
   if(res.bodysize<sum*m_short_coef && !res.bull)
      res.type=CAND_SHORT_BEAR;
//--- doji
   if(res.bodysize<HL*m_doji_coef)
      res.type=CAND_DOJI;
//--- marubozu
   if((shade_low<res.bodysize*m_maribozu_coef && shade_high<res.bodysize*m_maribozu_coef) && res.bodysize>0)
      res.type=CAND_MARIBOZU;
//--- martelo
   if(shade_low>res.bodysize*m_hummer_coef2 && shade_high<res.bodysize*m_hummer_coef1)
      res.type=CAND_HAMMER;
//--- martelo invertido
   if(shade_low<res.bodysize*m_hummer_coef1 && shade_high>res.bodysize*m_hummer_coef2)
      res.type=CAND_INVERT_HAMMER;
//--- spinning top
   if((res.type==CAND_SHORT_BULL || res.type==CAND_SHORT_BEAR) && shade_low>res.bodysize*m_spin_coef && shade_high>res.bodysize*m_spin_coef)
      res.type=CAND_SPIN_TOP;
//---
   ArrayFree(rt);
   return(true);
  }

O algoritmo do método é o seguinte: obtenha a data inicial do intervalo de seleção, dependendo dos padrões que deseja analisar, os existentes ou gerados. Como esse método é usado no ciclo de cálculo no período, altere a data inicial deslocando-a do passado para o futuro por uma vela do período determinado. Em seguida, copie os dados necessários para o cálculo dos tipos simples de velas. Se esses dados não forem suficientes, exiba uma mensagem apropriada para o usuário. 

Nota importante! É necessário monitorar a disponibilidade de dados históricos no terminal da MetaTrader 5, caso contrário, o aplicativo pode não funcionar corretamente. 

Se os dados forem suficientes, será realizada uma verificação se o candle atual pertence a um tipo de vela simples.

Como é de conhecimento dos artigos anteriores, o método GetCategory() verifica o comportamento do preço após a ocorrência do padrão usando os dados históricos.

//+------------------------------------------------------------------+
//| Determina as categorias de lucro                                 |
//+------------------------------------------------------------------+
bool CProgram::GetCategory(const string symbol,const int shift,RATING_SET &rate,ENUM_TIMEFRAMES timeframe,int threshold)
  {
   MqlRates rt[];
   datetime start=TimeCurrent();
   if(m_table_number==1)
      start=StringToTime(TimeToString(m_calendar1.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit1.GetHours()+":"+(string)m_time_edit1.GetMinutes()+":00");
   else if(m_table_number==2)
      start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00");
   start+=PeriodSeconds(timeframe)*shift;
   int copied=CopyRates(symbol,timeframe,start,4,rt);
//--- Obtém os detalhes da vela anterior
   if(copied<4)
     {
      return(false);
     }
   double high1,high2,high3,low1,low2,low3,close0,point;
   close0=rt[0].close;
   high1=rt[1].high;
   high2=rt[2].high;
   high3=rt[3].high;
   low1=rt[1].low;
   low2=rt[2].low;
   low3=rt[3].low;
   if(!SymbolInfoDouble(symbol,SYMBOL_POINT,point))
      return(false);

//--- Verifica se é uma tendência de alta
   if((int)((high1-close0)/point)>=threshold)
     {
      rate.a_uptrend++;
     }
   else if((int)((high2-close0)/point)>=threshold)
     {
      rate.b_uptrend++;
     }
   else if((int)((high3-close0)/point)>=threshold)
     {
      rate.c_uptrend++;
     }

//--- Verifica se é uma tendência de baixa
   if((int)((close0-low1)/point)>=threshold)
     {
      rate.a_dntrend++;
     }
   else if((int)((close0-low2)/point)>=threshold)
     {
      rate.b_dntrend++;
     }
   else if((int)((close0-low3)/point)>=threshold)
     {
      rate.c_dntrend++;
     }
   return(true);
  }

Neste algoritmo do método, foi alterado apenas o método para obter os dados sobre as velas analisadas. Isso está diretamente relacionado à nova ferramenta de seleção do intervalo de tempo.

O último método comum para ambos GetPatternType() é obter, calcular e exibir os dados na tabela de resultados. 

//+------------------------------------------------------------------+
//| Obtém, calcula e exibe os dados na tabela de resultados          |
//+------------------------------------------------------------------+
void CProgram::AddRow(CTable &table,string pattern_name,RATING_SET &rate,int found,ENUM_TIMEFRAMES timeframe)
  {
   int row=m_total_row;
   int total_patterns=ArraySize(m_total_combination);
   double p1,p2,k1,k2;
   int sum1=0,sum2=0;
   sum1=rate.a_uptrend+rate.b_uptrend+rate.c_uptrend;
   sum2=rate.a_dntrend+rate.b_dntrend+rate.c_dntrend;
//---
   p1=(found>0)?NormalizeDouble((double)sum1/found*100,2):0;
   p2=(found>0)?NormalizeDouble((double)sum2/found*100,2):0;
   k1=(found>0)?NormalizeDouble((m_k1*rate.a_uptrend+m_k2*rate.b_uptrend+m_k3*rate.c_uptrend)/found,3):0;
   k2=(found>0)?NormalizeDouble((m_k1*rate.a_dntrend+m_k2*rate.b_dntrend+m_k3*rate.c_dntrend)/found,3):0;

//---
   table.AddRow(row);
   if(m_table_number==1)
      table.SetValue(0,row,pattern_name);
   else if(m_table_number==2)
     {
      if(row<total_patterns)
         table.SetValue(0,row,m_total_combination[row]);
      else if(row>=total_patterns)
        {
         int i=row-int(total_patterns*MathFloor(double(row)/total_patterns));
         table.SetValue(0,row,m_total_combination[i]);
        }
     }
   table.SetValue(1,row,(string)found);
   table.SetValue(2,row,TimeframeToString(timeframe));
   table.SetValue(3,row,(string)p1,2);
   table.SetValue(4,row,(string)p2,2);
   table.SetValue(5,row,(string)k1,2);
   table.SetValue(6,row,(string)k2,2);
   ZeroMemory(rate);
   m_total_row++;
  }
//+------------------------------------------------------------------+

O método recebe todos os dados para o cálculo em seus argumentos. O algoritmo de manipulação de dados é bastante simples. Dois momentos devem ser mencionados aqui. Nas versões anteriores do aplicativo, o número exato de linhas era conhecido antecipadamente. Por exemplo, ao processar os dados do padrão existentes, a tabela de resultados sempre teve o mesmo número de linhas igual ao número de padrões predefinidos, ou seja, 14. Agora, o usuário pode escolher qualquer número de padrões ou períodos de tempo para trabalhar e, portanto, o número de linhas não é conhecido. Portanto, foi adicionado um simples contador de linha m_total_row. A chamada do método AddRow() adiciona uma linha à tabela de resultados com base em dois sinais: padrão e período de tempo.

O segundo ponto diz respeito à guia AutoSearch. Nas versões anteriores, o número finito de linhas era igual ao número de combinações dos padrões gerados. O algoritmo anterior já não é mais adequado pelo mesmo motivo: o número de períodos de tempo é desconhecido. Portanto, todo o array de combinações gerados deve ser gravado novamente para cada um dos períodos de tempo selecionados.

Vamos considerar a segunda variante do método GetPatternType().

bool              GetPatternType(const string symbol,string &total_combination[]);

Aqui, além do símbolo atual, o segundo parâmetro é o link para o array de strings de padrões gerados. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::GetPatternType(const string symbol,string &total_combination[])
  {
   CANDLE_STRUCTURE cand1[],cand2[],cand3[],cur_cand,prev_cand,prev_cand2;
   RATING_SET ratings;
   int total_patterns,m_pattern_total[];
   string elements[];
//---
   total_patterns=ArraySize(total_combination);
   ArrayResize(cand1,total_patterns);
   ArrayResize(cand2,total_patterns);
   ArrayResize(cand3,total_patterns);
   ArrayResize(m_pattern_total,total_patterns);
   ArrayResize(elements,m_pattern_size);
//---
   for(int i=0;i<total_patterns;i++)
     {
      StringReplace(total_combination[i],"[","");
      StringReplace(total_combination[i],"]","");
      if(m_pattern_size>1)
        {
         ushort sep=StringGetCharacter(",",0);
         StringSplit(total_combination[i],sep,elements);
        }
      m_pattern_total[i]=0;
      if(m_pattern_size==1)
         IndexToPatternType(cand1[i],(int)total_combination[i]);
      else if(m_pattern_size==2)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
        }
      else if(m_pattern_size==3)
        {
         IndexToPatternType(cand1[i],(int)elements[0]);
         IndexToPatternType(cand2[i],(int)elements[1]);
         IndexToPatternType(cand3[i],(int)elements[2]);
        }
     }
//---
   GetTimeframes(m_timeframes1,m_cur_timeframes2);
   int total=ArraySize(m_cur_timeframes2);
   if(total<1)
     {
      if(m_lang_index==0)
         MessageBox("Вы не выбрали рабочий таймфрейм!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("You have not selected a working timeframe!","Error",MB_OK);
      return(false);
     }
   m_total_row=0;
   m_table_number=2;
//--- Exclui todas as linhas
   m_table2.DeleteAllRows();
//---
   datetime start=StringToTime(TimeToString(m_calendar3.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit3.GetHours()+":"+(string)m_time_edit3.GetMinutes()+":00");
   datetime end=StringToTime(TimeToString(m_calendar4.SelectedDate(),TIME_DATE)+" "+(string)m_time_edit4.GetHours()+":"+(string)m_time_edit4.GetMinutes()+":00");
//---
   if(start>end || end>TimeCurrent())
     {
      if(m_lang_index==0)
         MessageBox("Неправильно выбран диапазон дат!","Ошибка",MB_OK);
      else if(m_lang_index==1)
         MessageBox("Incorrect date range selected!","Error",MB_OK);
      return(false);
     }
//---
   if(m_pattern_size==1)
     {
      ZeroMemory(cur_cand);
      //--- Cálculo pelos períodos de tempo
      for(int i=0;i<total;i++)
        {
         MqlRates rt[];
         ZeroMemory(rt);
         ZeroMemory(ratings);
         int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
         //--- Cálculo por padrões
         for(int j=0;j<total_patterns;j++)
           {
            //--- Cálculo pelo intervalo de datas         
            for(int k=0;k<copied;k++)
              {
               //--- Obtém o tipo de vela atual
               GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);                 // vela atual
               //---
               if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull)
                 {
                  m_pattern_total[j]++;
                  GetCategory(symbol,k+3,ratings,m_cur_timeframes2[i],m_threshold_value2);
                 }
              }
            AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
            m_pattern_total[j]=0;
           }
        }
     }
   else if(m_pattern_size==2)
     {
      ZeroMemory(cur_cand);
      ZeroMemory(prev_cand);
      //--- Cálculo pelos períodos de tempo
      for(int i=0;i<total;i++)
        {
         MqlRates rt[];
         ZeroMemory(rt);
         ZeroMemory(ratings);
         int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
         //--- Cálculo por padrões
         for(int j=0;j<total_patterns;j++)
           {
            //--- Cálculo pelo intervalo de datas         
            for(int k=0;k<copied;k++)
              {
               //--- Obtém o tipo de vela atual
               GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+1);               // vela anterior
               GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);                  // vela atual
               //---
               if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && 
                  prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull)
                 {
                  m_pattern_total[j]++;
                  GetCategory(symbol,k+4,ratings,m_cur_timeframes2[i],m_threshold_value2);
                 }
              }
            AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
            m_pattern_total[j]=0;
           }
        }
     }
   else if(m_pattern_size==3)
     {
      ZeroMemory(cur_cand);
      ZeroMemory(prev_cand);
      ZeroMemory(prev_cand2);
      //--- Cálculo pelos períodos de tempo
      for(int i=0;i<total;i++)
        {
         MqlRates rt[];
         ZeroMemory(ratings);
         int copied=CopyRates(symbol,m_cur_timeframes2[i],start,end,rt);
         //--- Cálculo por padrões
         for(int j=0;j<total_patterns;j++)
           {
            //--- Cálculo pelo intervalo de datas         
            for(int k=0;k<copied;k++)
              {
               //--- Obtém o tipo de vela atual
               GetCandleType(symbol,prev_cand2,m_cur_timeframes2[i],k+2);                                  // vela anterior
               GetCandleType(symbol,prev_cand,m_cur_timeframes2[i],k+1);                                   // vela aneterior
               GetCandleType(symbol,cur_cand,m_cur_timeframes2[i],k);                                      // vela atual
               //---
               if(cur_cand.type==cand1[j].type && cur_cand.bull==cand1[j].bull && 
                  prev_cand.type==cand2[j].type && prev_cand.bull==cand2[j].bull && 
                  prev_cand2.type==cand3[j].type && prev_cand2.bull==cand3[j].bull)
                 {
                  m_pattern_total[j]++;
                  GetCategory(symbol,k+5,ratings,m_cur_timeframes2[i],m_threshold_value2);
                 }
              }

            AddRow(m_table2,"",ratings,m_pattern_total[j],m_cur_timeframes2[i]);
            m_pattern_total[j]=0;
           }
        }
     }
//---
   m_table2.DeleteRow(m_total_row);
//--- Atualiza a tabela
   m_table2.Update(true);
   m_table2.GetScrollVPointer().Update(true);
   return(true);
  }

É importante entender a sequência de cálculos dessa versão do algoritmo, com base nos dados de entrada. Nós não vamos considerar o recebimento de períodos de tempo e intervalos de datas aqui, como foi discutido anteriormente. Em seguida, o algoritmo verifica o tamanho dos padrões que estão sendo testados. Considere um padrão de três velas. Depois de declarar uma estrutura para armazenar os dados de preço e depois de anular a estrutura de 'ratings' usada, o algoritmo faz um loop pela primeira vez através dos períodos de tempo e obtém a quantidade de dados copiados para cada um deles. Isso permite determinar o intervalo, no qual os padrões especificados serão buscados. Após o período de tempo, entramos no ciclo de cálculo para cada padrão no período de tempo especificado. Em seguida, para cada um dos padrões, percorremos as velas definidas no intervalo de datas especificado.

Para um melhor entendimento, veja o exemplo de cálculo e a ordem de exibição de informações na tabela de resultados.

Fig.9. Exemplo do cálculo e exibição da ordem do resultado na tabela

Como pode ser visto na Fig.9, o teste foi realizado usando o par de moedas EURUSD, com o padrão de 1-vela, nos períodos de tempo M15, M30, H1 e H2. Duas velas simples foram selecionadas para os testes: com os índices 1 e 2. A implementação do algoritmo descrito acima pode ser observada na tabela de resultados. Ele é realizado da seguinte maneira: primeiro todos os padrões gerados são analisados um a um no período de 15 minutos, depois no período de 30 minutos e assim por diante.

Conclusão

O arquivo anexado abaixo contém todos os arquivos descritos organizados nas pastas apropriadas. Para o seu bom funcionamento, você só precisa salvar a pasta MQL5 na pasta do terminal. Para abrir a pasta raiz do terminal, na qual a pasta MQL5 está localizada, pressione a combinação de teclas Ctrl+Shift+ no terminal da MetaTrader 5 ou use o menu de contexto como é mostrado na Fig. 10 logo abaixo.

Fig.10 Abrindo a pasta MQL5 no diretório raiz do terminal MetaTrader 5.

Programas utilizados no artigo

#
 Nome
Tipo
Descrição
1
PatternAnalyzer.mq5 Interface gráfica
 Barra de ferramentas para analisar padrões de velas
2 MainWindow.mqh Código Base  Biblioteca GUI
3 Program.mqh Código Base  Biblioteca de métodos para a criação de elementos da interface do usuário e cálculo

Artigos anteriores desta série:

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 II): Busca automática de novos padrões
Estudo de técnicas de análise de velas (Parte III): Biblioteca para trabalhar com os padrões