English Русский 中文 Español Deutsch 日本語
Busca automática de divergências e convergência

Busca automática de divergências e convergência

MetaTrader 5Exemplos | 6 novembro 2017, 10:54
4 397 0
Dmitry Fedoseev
Dmitry Fedoseev

Conteúdo

Introdução

O termo divergência vem da palavra latina divergĕre (dobrar para diversos lados). A divergência é geralmente definida como uma discrepância nas leituras dos indicadores e no movimento dos preços. O termo antônimo é convergência, proveniente da palavra latina convergĕre (convergir). Existem sistemas mais amplos de classificação de divergência/convergência, incluindo definições como divergência oculta, divergência estendida, divergências de classes A, B e C, etc.

Neste artigo, primeiro examinaremos os termos básicos: divergência e convergência. Logo depois, deter-nos-emos noutros métodos de classificação, realizaremos uma análise comparativa, bem como identificaremos suas vantagens e desvantagens. Finalmente, desenvolveremos nossa própria classificação, mais completa e sem quaisquer inconvenientes, bem como um indicador universal para pesquisar e exibir convergências/divergências no gráfico. 

Divergência e convergência (definição)

Bem, a divergência é uma discrepância nas leituras dos indicadores e no movimento dos preços. A convergência, por sua vez, tem o significado oposto. De acordo com essa lógica, se a divergência é uma discrepância nas leituras dos indicadores e no movimento do preço, é possível concluir que a convergência significa conformidade. No entanto, este não é o caso, uma vez que a conformidade não pode ser equiparada à convergência. 

Para que ambos os termos - divergência e convergência - tenham um significado mais preciso, eles precisam de definições mais estreitas. Vejamos os gráficos de preços e indicadores. Se o preço sobe, enquanto o indicador cai, temos uma divergência. Se o preço cai, enquanto o indicador sobe, temos uma convergência (Fig. 1).


Fig. 1. Discrepância nas direções do movimento do preço e dos indicadores. À esquerda, o preço se move para cima, 
enquanto o indicador se move para baixo, ou seja, divergência. À direita, o preço cai, enquanto o indicador sobe, isto é, convergência

Estamos acostumados ao fato de que o indicador geralmente é colocado abaixo do gráfico de preços, portanto esta definição parece aceitável à primeira vista. No entanto, se invertermos as posições do indicador e do gráfico de preços, tudo muda radicalmente, uma vez que a divergência se transforma em convergência e vice-versa (Fig. 2).


Fig. 2. Discrepância nas direções do movimento do preço e dos indicadores. À esquerda, o preço se move para cima, 
enquanto o indicador se move para baixo, convergência. À direita, o preço cai, enquanto o indicador sobe, divergência

Agora demos uma olhada nas Figuras 1 e 2. do ponto de vista da escolha da direção do trade. Suponhamos que o preço suba, enquanto o indicador se move para baixo, e então decidimos vender. Seguindo a analogia, devemos comprar quando o preço se move para baixo, e o indicador, para cima (Fig. 3). 3).


Fig. 3. À esquerda, condições para vender, o preço e o indicador divergem. À direita, condições
para comprar (semelhantes à venda), o preço e o indicador convergem
 

Acontece que durante a venda o preço e a leitura do indicador divergem, e durante a compra, eles convergem, ou seja, num caso, observa-se a divergência, enquanto que num outro, a convergência. Embora as condições de compra e venda sejam idênticas, são opostas de maneira espelhada. Isto significa que podemos chamar uma das condições de baixa, enquanto a segunda, de alta. Quer dizer que, para definir convergência e divergência, não basta saber se o indicador está abaixo do gráfico de preços. 

Pode-se acrescentar que as definições fornecidas têm a ver com a direção de venda, enquanto para comprar é o oposto. Mas existe uma versão mais simples e precisa da definição baseada na própria essência da análise técnica. Se adicionarmos à definição de divergência e convergência o pressuposto do movimento futuro de preço, tudo se tornará mais simples e conciso.

A divergência é um sinal de reversão do preço, que apresenta a falta de correspondência entre as leituras do indicador e a direção do movimento do preço

Uma vez que este é um sinal de reversão, para a venda, o preço deve subir, enquanto para compra, ele deve cair. Para que a discrepância referida apareça, o indicador deve se mover para baixo e para cima, respectivamente. Nesta definição, a venda é usada como uma direção de referência, enquanto o indicador deve ser colocado sob o gráfico de preços. A divergência mostrada na Fig. 3 segue esta definição.  

Como a convergência é o oposto em relação à divergência: agora, o preço deve ir para baixo, enquanto o indicador, para cima. No entanto, a direção do preço prevista não muda. Assim, obtemos a seguinte definição de convergência:

A convergência é um sinal de continuação da tendência, que apresenta a falta de correspondência entre as leituras do indicador e a direção do movimento do preço

Como este é um sinal de continuação, para venda, o preço deve se mover para baixo, enquanto que para compra, para cima. Para que o movimento dos preços não seja consistente com as leituras do indicador, ele deve ir para cima e para baixo, respectivamente (Fig. 4).


Fig. 4. Sinais de convergência

Claro, pode-se argumentar se a divergência é um sinal de reversão e a convergência é um sinal de continuação. Mas isso já é uma questão de aplicação prática das possibilidades de análise técnica.

A Fig. 5 mostra sinais de divergência e convergência simultaneamente para permitir que você domine a terminologia.


Fig. 5. Sinais de divergência e convergência

 

Métodos para determinar a direção do movimento do preço e do indicador

Até agora, em nossos gráficos, as linhas de preços e indicador eram retas. Mas esta é uma abstração que não tem nada a ver com o movimento dos preços reais. Portanto, consideremos os métodos que podem ser usados ​​para definir a direção do preço e do indicador e detectar uma divergência. Então, examinaremos os sistemas de classificação de divergências na prática.

Em geral, primeiro precisamos identificar os preços ou os picos e fundos do indicador no gráfico e, em seguida, comparar seus valores. A divergência de alta (sinais de compra) é detectada pelos fundos: se um fundo for maior do que o anterior, então o indicador estará apontando para cima, e vice-versa. A divergência de baixa (sinais de venda) é detectada pelos picos.    

Existem três maneiras de detectar pontos extremos no gráfico.

  1. Por barras.
  2. Ao superar o valor limite do último mínimo/máximo.
  3. Valor máximo/mínimo numa posição acima/abaixo da linha central do indicador. 

Definição de picos/ fundos usando barras. Utilizamos o número de barras do pico/fundo. Por exemplo, se o parâmetro for 2, o valor do indicador na barra superior - do pico - deve exceder duas barras à esquerda e duas à direita. Por conseguinte, o valor para o fundo deve ser menor do que para as barras vizinhas (Fig. 6).


Fig. 6 Definição de picos/ fundos usando duas barras. Definição de pico à esquerda. Na barra, marcada com a seta, tornou-se
conhecida a formação do pico com a marca de seleção. Definição de fundo à direita

O número de barras necessárias à esquerda e à direita do pico/fundo pode variar: por exemplo, 5 à esquerda e 2 à direita (Fig. 7).


Fig. 7. O topo definido por cinco barras à esquerda e duas à direita 

Definição de picos/ fundos usando valor limite. Quando o indicador se move para cima, o sistema segue seu valor máximo. Nas barras sem um novo valor extremo, o valor atual é comparado com máximo/mínimo previamente fixado. Se a diferença exceder o limite definido pelo parâmetro externo, assume-se que o indicador mudou de direção, enquanto a barra, na qual o valor máximo/mínimo foi atingido, é considerada o pico/fundo (Fig. 8).


Fig. 8. Definição de picos e fundos usando valor limite. O valor limite é exibido no canto superior esquerdo.
Até a barra 2 o indicador se move para cima, na barra 2 é fixado o valor máximo, na barra 5 o valor
diminui para o valor limite, o que indica da mudança na direção do
indicador. Na barra 6 o indicador novamente
ultrapassa o valor limite e altera a direção, e assim por diante.

A variação por barras é o mais conveniente, uma vez que não depende do indicador. Em contraste, o valor limite depende do tipo de indicador. Por exemplo, para o RSI com uma faixa de oscilação de 0-100, o valor limite pode ser de cerca de 5. Para o Momentum, o limite é de 0.1-1, uma vez que o indicador flutua ligeiramente em torno do nível de 100. Além disso, a magnitude dessas flutuações depende do timeframe. Isso complica ainda mais o uso do valor limite. 

Valor máximo/mínimo numa posição acima/abaixo da linha central do indicador. Este método é usado com menos frequência do que outros. Também depende do indicador usado, uma vez que nem todos os indicadores têm um valor médio de 0 (por exemplo, o valor médio do RSI é de 50). Mas sua principal desvantagem é um forte atraso (Fig. 9). 


Fig. 9. Definição de picos/ fundos atravessando a linha central. Nós saberemos sobre o pico 1 somente depois
que a linha central é cruzada na barra identificada como 2. Nós saberemos sobre o pico 3
depois do cruzamento na barra identificada como 4


Sistemas de classificação de divergência

Você pode encontrar muitos artigos sobre divergência na web. Eles descrevem uma variedade de abordagens que diferem na terminologia e princípios de sistematização da divergência e convergência. Podem-se encontrar a divergência simples, clássica, oculta e estendida. Há autores que as dividem em classes A, B e C. Não vamos analisar as fontes primárias neste artigo. Em vez disso, estudaremos alguns dos tipos selecionados.

Divergência clássica. Este tipo já foi descrito acima e mostrado na Fig. 5.

Divergência oculta. A divergência oculta difere da clássica no que diz respeito à direção do movimento dos preços e dos indicadores. Em outras palavras, a divergência oculta é semelhante à convergência.

Divergência estendida. Até agora, discutimos apenas as orientações ascendente e descendente do movimento do preço e do indicador. Se for considerado o movimento horizontal, haverá mais variações. Apesar das múltiplas variações que podemos obter ao combinar as três direções do movimento dos preços e as três direções do movimento do indicador, apenas foi identificada a variação da divergência estendida:

  • Movimento do preço horizontal, o indicador em baixa: divergência estendida de baixa (sinal de venda)
  • Movimento do preço horizontal, o indicador em alta: divergência estendida de alta (sinal de compra)

Classes: A, B, C. Classe А é divergência clássica, enquanto as classes B e C são variações da divergência estendida.

Classe B: 

    • Movimento do preço horizontal, o indicador em baixa: divergência de baixa (sinal de venda)
    • Movimento do preço horizontal, o indicador em alta: divergência de alta (sinal de compra)

Classe С: 

    • Preço em alta, picos do indicador num mesmo nível: divergência de baixa (sinal de venda)
    • Preço em baixa, fundos do indicador num mesmo nível: divergência de alta (sinal de compra)

Como podemos ver, as classes B e С são variações da divergência estendida, sendo que a variação B atende completamente a definição mencionada.

Minha primeira impressão após navegar na rede e estudar materiais sobre divergência é que falta terminologia clara e variações. Por isso, analisaremos as diferentes variações para combinar as direções do preço e dos indicadores, e sistematizá-las-emos.

Sistematização completa dos movimentos dos preços e dos indicadores

Primeiro, definiremos duas possíveis direções de movimento do preço e do indicador.

  1. Duas direções de movimento: para cima e para baixo. 
  2. Três direções de movimento: para cima, para baixo e horizontal.

O primeiro caso fornece apenas quatro possíveis combinações. Daremos uma olhada neles usando sinais de venda como exemplo.

  1. Preço para acima, indicador para acima. 
  2. Preço para cima, indicador para baixo (divergência).
  3. Preço para baixo, indicador para cima (convergência).
  4. Preço para baixo, indicador para baixo.  

Agora que, na seção anterior, lidamos com métodos de definição de direção, vamos visualizar essas variações (Fig. 10). 


Fig. 10. Todas as combinações possíveis dos movimentos do preço e dos indicadores, em caso de duas direções disponíveis 

No caso de três direções, já pode haver nove combinações.

  1. Preço para acima, indicador para acima.
  2. Preço para cima, indicador para cima.
  3. Preço para cima, indicador para baixo (divergência).
  4. Preço horizontal, indicador para cima.
  5. Preço horizontalmente, indicador horizontal.
  6. Preço horizontal, indicador para baixo.
  7. Preço para baixo, indicador para cima (convergência).
  8. Preço para baixo, indicador horizontal.
  9. Preço para baixo, indicador para baixo.

Todas essas combinações são exibidas na Fig. 11.


Fig. 11. Todas as combinações possíveis dos movimentos do preço e dos indicadores, em caso de três direções disponíveis 

Se você criar um indicador que lhe permite escolher qualquer uma das variações consideradas, poderá escolher a divergência, a convergência, a divergência oculta ou estendida que você acha correta. Em outras palavras, obtemos um indicador universal útil mesmo para aqueles que não concordam com a sistematização e as definições dadas neste artigo. 

Divergência tripla

Até agora, a direção do preço e o movimento do indicador foram determinados por dois pontos: para cima, para baixo e horizontal. Podemos adicionar o terceiro ponto para aumentar o número de possíveis variações de movimento do preço e do indicador. No total, existem nove variações: 

  1. Para cima, para cima.
  2. Para cima, horizontal.
  3. Para cima, para baixo.
  4. Horizontal, para cima.
  5. Horizontal, horizontal.
  6. Horizontal, para baixo.
  7. Para baixo, para cima.
  8. Para baixo, horizontal.
  9. Para baixo, para baixo.

Neste caso, seria mais correto falar da forma do movimento e não da direção. Os tipos de movimento definidos por três picos são exibidos na Fig. 12.


Fig. 12. Vários movimentos possíveis com base em três picos 

Os movimentos apropriados com base em fundos são exibidos na Fig. 13.

 
Fig. 13. Vários movimentos possíveis com base em três fundos 

Ao combinar 9 tipos de movimento do preço com 9 tipos de movimento do indicador, podemos obter 81 variações da divergência tripla.

Assim, pode-se determinar o movimento por qualquer número de pontos. Se acrescentarmos um quarto ponto, teremos 81 variações do movimento do preço ou do indicador e 6561 (81*81) possíveis versões de sua combinação. Claro, quanto mais variações possíveis, é menos provável elas serem encontradas. Talvez, não há sentido na aplicação do quarto ponto, mas o indicador no artigo atual não tem limitações no número de pontos usados ​​para definir o tipo de movimento.

Indicador universal para definir a divergência

Agora que lidamos com a teoria, procedamos a desenvolver o indicador. 

Escolha do oscilador. Para não ser limitado por um único oscilador para definir a divergência, usaremos o oscilador universal descrito neste artigo. Entre seus anexos: iUniOsc (oscilador universal) e iUniOscGUI (o mesmo oscilador com uma interface gráfica do usuário). Usaremos a versão básica, iUniOsc.

Criação de um novo indicador. Criemos um novo indicador iDivergence, no MetaEditor. Usaremos a função OnCalculate. A função OnTimer() não será necessária. Marcamos a opção "indicador em uma janela separada". Criamos três buffers: uma linha de exibição do oscilador e dois buffers de seta para desenhar setas no caso de uma divergência. Depois que um novo arquivo é aberto no editor, mudamos os nomes dos buffers: 1 - buf_osc, 2 - buf_buy, 3 - buf_sell. Os nomes devem ser alterados onde são declarados os arrays, bem como na função OnInit(). Também podemos ajustar as propriedades dos buffers: indicator_label1, indicator_label2, indicator_label3 - os valores dessas propriedades são exibidos no balão de informações ao passar o mouse sobre a linha ou o rótulo do indicador, bem como na janela de dados. Vamos chamá-los "osc", "buy", "sell".

Uso do oscilador universal. Inserimos todos os parâmetros externos do indicador iUniOsc no novo indicador. Os parâmetros ColorLine1, ColorLine2 e ColorHisto não são necessários na janela de propriedades. Nós os esconderemos. O parâmetro Type possui o tipo personalizado OscUni_RSI descrito no arquivo UniOsc/UniOscDefines.mqh. Incluiremos este arquivo. Por padrão, o valor do parâmetro Type é definido como OscUni_ATR - indicador ATR. Mas o indicador ATR não depende da direção do movimento do preço, o que significa que não é adequado para determinar a divergência. Portanto, configuramos o OscUni_RSI - indicador RSI como padrão:

#include <UniOsc/UniOscDefines.mqh>

input EOscUniType          Type              =  OscUni_RSI;
input int                  Period1           =  14;
input int                  Period2           =  14;
input int                  Period3           =  14;
input ENUM_MA_METHOD       MaMethod          =  MODE_EMA;
input ENUM_APPLIED_PRICE   Price             =  PRICE_CLOSE;   
input ENUM_APPLIED_VOLUME  Volume            =  VOLUME_TICK;   
input ENUM_STO_PRICE       StPrice           =  STO_LOWHIGH;   
      color                ColorLine1        =  clrLightSeaGreen;
      color                ColorLine2        =  clrRed;
      color                ColorHisto        =  clrGray;

Declaramos o identificador do oscilador universal abaixo das variáveis ​​externas:

int h;

Carregamos o oscilador universal no início da função OnInit():

h=iCustom(Symbol(),Period(),"iUniOsc", Type,
                                       Period1,
                                       Period2,
                                       Period3,
                                       MaMethod,
                                       Price,
                                       Volume,
                                       StPrice,
                                       ColorLine1,
                                       ColorLine2,
                                       ColorHisto);
if(h==INVALID_HANDLE){
   Alert("Can't load indicator");
   return(INIT_FAILED);
}

Na função OnCalculate(), copiamos os dados do oscilador universal para o buffer buf_osc:

int cnt;   

if(prev_calculated==0){
   cnt=rates_total;
}
else{ 
   cnt=rates_total-prev_calculated+1; 
}

if(CopyBuffer(h,0,0,cnt,buf_osc)<=0){
   return(0);
}  

Nesta fase, podemos verificar se as ações executadas são corretas anexando o indicador iDivergence ao gráfico. Se tudo for feito corretamente, poder-se-á ver a linha do oscilador na sub-janela. 

Definição dos valores extremos do oscilador. Já consideramos três maneiras de definir valores extremos. Vamos incluí-las no indicador e fornecer a possibilidade de selecionar qualquer um deles (variável externa com uma lista suspensa). Na pasta Include, criamos a pasta UniDiver, na qual devem estar localizados todos os arquivos adicionais com o código. Criamos o arquivo anexado UniDiver/UniDiverDefines.mqh, nele escrevemos a enumeração EExtrType:

enum EExtrType{
   ExtrBars,
   ExtrThreshold,
   ExtrMiddle
};

Opções de enumeração:

  • ExtrBars — por barras;
  • ExtrThreshold — ao superar o valor limite do último mínimo/máximo.
  • ExtrMiddle — valor máximo ou mínimo quando o indicador se encontra acima ou abaixo do seu ponto médio. 

No indicador, criamos o parâmetro externo ExtremumType e o inserimos acima de todos os outros parâmetros externos. Ao definir valores extremos por barras, serão necessários dois parâmetros, isto é, o número de barras à esquerda e à direita do valor extremo, enquanto ao definir por limite, precisaremos do parâmetro para calcular o valor limite:

input EExtrType            ExtremumType      =  ExtrBars; // Tipo de extremo
input int                  LeftBars          =  2;        // Barras à esquerda para a opção ExtrBars
input int                  RightBars         =  -1;       // Barras à direita para a opção ExtrBars
input double               MinMaxThreshold   =  5;        // Magnitude do limite para a opção ExtrThreshold 

Implementaremos a possibilidade de usar um parâmetro, RightBars ou LeftBars, além de usar os dois de uma só vez. RightBars é igual a -1 por padrão. Isso significa que não é usado e o valor do segundo parâmetro deve ser atribuído a ele.

Classes de definição de valores extremos. Não há necessidade de alterar o método de definição de valor extremo durante o trabalho do indicador, portanto, é mais razoável usar POO em vez de os dois operadores 'if' e 'switch'. Criamos a classe base e três classes filho para os três métodos de definição do valor extremo. Uma dessas classes filho deve ser selecionada ao inicializar o indicador. Essas classes devem ser usadas tanto para definir valores extremos quanto para realizar todos os trabalhos necessários para encontrar a divergência. Elas são diferentes apenas nas formas de definir valores extremos, enquanto a definição de convergência é completamente idêntica em todos os casos. Portanto, a função de definição de divergência deve estar localizada na classe base e ser chamada a partir de classes filho. Mas, primeiro, precisamos fornecer um acesso fácil a todos os valores extremos dos indicadores (como foi feito com os picos do ZigZag no artigo Ondas de Wolfe).

A estrutura SExtremum é usada para armazenar dados sobre o valor extremo. A descrição da estrutura está localizada no arquivo UniDiverDefines:

struct SExtremum{
   int SignalBar;
   int ExtremumBar;
   datetime ExtremumTime;
   double IndicatorValue;
   double PriceValue;
};

Atribuição dos campos da estrutura:

  • SignalBar — barra em que se tornou conhecida a formação de um valor extremo
  • ExtremumBar — barra com um valor extremo
  • ExtremumTime — tempo da barra com um valor extremos
  • IndicatorValue — magnitude do indicador no valor extremo
  • PriceValue — magnitude do preço na barra com um valor extremo

Duas matrizes dessas estruturas devem ser usadas para armazenar dados sobre todos os picos e fundos. Elas serão membros da classe base.

Classes para definir valores extremos estão localizadas no arquivo UniDiver/CUniDiverExtremums.mqh, o nome da classe base é CDiverBase. Por enquanto, consideremos apenas a estrutura da classe com métodos básicos. O resto deve ser adicionado mais tarde, conforme necessário.

class CDiverBase{
   protected: 
      
      SExtremum m_upper[];
      SExtremum m_lower[];

      void AddExtremum( SExtremum & a[],
                        int & cnt,
                        double iv,
                        double pv,
                        int mb,
                        int sb,
                        datetime et);
   
      void CheckDiver(  int i,
                        int ucnt,
                        int lcnt,
                        const datetime & time[],
                        const double &high[],
                        const double &low[],                     
                        double & buy[],
                        double & sell[],
                        double & osc[]
      );

   public:
      virtual void Calculate( const int rates_total,
                              const int prev_calculated,
                              const datetime &time[],
                              const double &high[],
                              const double &low[],
                              double & osc[],
                              double & buy[],     
                              double & sell[]
      );
}; 

O método virtual Calculate() permite selecionar a opção para definir o valor extremo. Os métodos AddExtremum() e CheckDiver() estão localizados na seção protected. Eles são chamados a partir do método Calculate() das classes filho. O método AddExtremum() coleta dados sobre picos e fundos para os arrays m_upper[] e m_lower[]. O método CheckDiver() verifica se a condição de divergência é atendida e define as setas indicadoras. Abaixo, analisaremos todos esses métodos de forma mais detalhada, mas, por enquanto, vamos nos familiarizar com as classes derivadas para outras formas de determinar valores extremos.

Definição de valores extremos por barras. Classe para definir valores extremos por barras:

class CDiverBars:public CDiverBase{
   private:   
   
      SPseudoBuffers1 Cur;
      SPseudoBuffers1 Pre;  
           
      int m_left,m_right,m_start,m_period;   
   
   public:
         
      void CDiverBars(int Left,int Right);
   
      void Calculate( const int rates_total,
                      const int prev_calculated,
                      const datetime &time[],
                      const double &high[],
                      const double &low[],
                      double & osc[],
                      double & buy[],
                      double & sell[]
      );
}; 

Os parâmetros para definir valores extremos (as variáveis ​​externas LeftBars e RightBars) são passados ​​para o construtor da classe, seus valores são verificados e alterados, se necessário, e são calculados os parâmetros adicionais:

void CDiverBars(int Left,int Right){
   m_left=Left;
   m_right=Right;   
   if(m_left<1)m_left=m_right;   // o parâmetro Left não está definido
   if(m_right<1)m_right=m_left;  // o parâmetro Right não está definido
   if(m_left<1 && m_right<1){    // ambos os parâmetros não estão definidos
      m_left=2;
      m_right=2;
   }
   m_start=m_left+m_right;       // deslocamento do ponto de partida do intervalo
   m_period=m_start+1;           // número de barras do intervalo 
}

Primeiro, os valores dos parâmetros são verificados. Se algum deles não é positivo (não está definido), o valor do segundo parâmetro é atribuído a ele. Se não for definido qualquer parâmetro, os valores padrão são atribuídos a eles (número 2). O recuo da barra inicial para procurar um valor extremo (m_start) e o número total de barras do extremo (m_period) são calculados depois disso.

O método Calculate() é idêntico à função OnCalculate() padrão, mas recebe apenas os arrays dos preços necessários em vez de todos: time[], high[], low[] e buffers de indicadores osc[] (dados do oscilador), buy[] e sell[] (setas). Como de costume, o intervalo das barras calculadas é definido na função OnCalculte(). Os valores extremos do indicador (as funções ArrayMaximum() e ArrayMinimum()) são definidos no ciclo do indicador padrão posteriormente. Depois de detectar um ponto extremo, o método AddExtremum() é chamado para adicionar dados à matriz m_upper[] ou m_lower[]. No final, o método CheckDiver() é chamado para analisar dados dos arrays com valores extremos. Se a divergência for encontrada, serão posicionadas setas.

void Calculate( const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &high[],
                const double &low[],
                double & osc[],
                double & buy[],
                double & sell[]
){

   int start; // variável para o índice de barra inicial
   
   if(prev_calculated==0){ // cálculo do indicador completo
      start=m_period; // definir a barra inicial de cálculo
      m_LastTime=0;   // redefinir a variável para definir uma nova barra 
      Cur.Reset();    // redefinir estruturas auxiliares
      Pre.Reset();    // redefinir estruturas auxiliares
   }
   else{ // ao calcular apenas novas barras
      start=prev_calculated-1; // calcular o índice da barra, a partir do qual os cálculos são retomados
   }

   for(int i=start;i<rates_total;i++){ // ciclo de indicador principal
      
      if(time[i]>m_LastTime){ // nova barra
         m_LastTime=time[i];
         Pre=Cur;
      }
      else{ // re-cálculo da mesma barra
         Cur=Pre;
      }
      
      // cálculo de parâmetros de pesquisa de máximo/mínimo 
      int sb=i-m_start; // índice da barra, a partir do qual começa o intervalo
      int mb=i-m_right; // índice da barra com pico/fundo 
      
      if(ArrayMaximum(osc,sb,m_period)==mb){ // há um pico
         // adicionar o pico ao array
         this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
      }
      if(ArrayMinimum(osc,sb,m_period)==mb){ // há um fundo
         // adicionar fundo à matriz 
         this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
      }
      
      // verificar se há divergência
      this.CheckDiver(i,Cur.UpperCnt,Cur.LowerCnt,time,high,low,buy,sell,osc);
   } 
}

Consideremos em mais detalhes este código. No início do ciclo de indicador:

if(time[i]>m_LastTime){ // nova barra
   m_LastTime=time[i];
   Pre=Cur;
}
else{ // re-cálculo da mesma barra
   Cur=Pre;
}

A variável m_LastTime é declarada na classe base. Se o tempo time[i] da barra exceder a variável, a barra é calculada pela primeira vez. O tempo da barra é atribuído à variável m_LastTime, enquanto o valor da variável Cur é atribuído à variável Pre. Inversamente, ao recalcular a mesma barra, a variável Pre é atribuída à Cur. O uso das variáveis ​​Cur e Pre é considerado em detalhes neste artigo. As variáveis ​​Pre e Cur são do tipo SPseudoBuffers1 descrito no arquivo UniDiverDefines:

struct SPseudoBuffers1{
   int UpperCnt;
   int LowerCnt;
   void Reset(){
      UpperCnt=0;
      LowerCnt=0;  
   }   
};

A estrutura inclui dois campos:

  • UpperCount — número de elementos da matriz usados m_upper[];
  • LowerCount — número de elementos da matriz usado m_lower[];

O método Reset() é criado para anulação rápida de todos os campos da estrutura.

Depois de trabalhar com as variáveis ​​Cur e Pre, os índices de barras para procurar valores extremos são calculados:

// cálculo de parâmetros de pesquisa de máximo/mínimo 
int sb=i-m_start; // índice da barra, a partir do qual começa o intervalo
int mb=i-m_right; // índice da barra com pico/fundo  

O índice da barra, a partir do qual a pesquisa do pico/fundo começa, é atribuído à variável sb. O índice da barra, em que o pico/fundo deve ser localizado, é atribuído à variável mb.

Definimos o pico ou fundo usando as funções ArrayMaximum() e ArrayMinimum():

if(ArrayMaximum(osc,sb,m_period)==mb){ // há um pico
   // adicionar o pico ao array
   this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
}
if(ArrayMinimum(osc,sb,m_period)==mb){ // há um fundo
   // adicionar fundo à matriz 
   this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
}

Se a função ArrayMaximum() ou ArrayMinimum() retornar mb, o número especificado de barras estará localizado à esquerda e à direita do pico/fundo. Por sua vez, isso indica que o pico/fundo desejado se formou. O método AddExtremum() é chamado e os dados são adicionados ao array m_upper[] ou m_lower[].

Consideremos o método AddExtremum() simples:

void AddExtremum( SExtremum & a[], // matriz em que são adicionados os dados
                  int & cnt,       // número de elemento da matriz ocupados
                  double iv,       // valor do indicador
                  double pv,       // valor do preço 
                  int mb,          // índice da barra com um valor extremo
                  int sb,          // índice da barra, no qual se tornou conhecido sobre um valor extremo 
                  datetime et      // tempo da barra com um valor extremo
){
   if(cnt>=ArraySize(a)){ // matriz preenchida
      // aumento no tamanho da matriz
      ArrayResize(a,ArraySize(a)+1024);
   }
   // adição de novos dados
   a[cnt].IndicatorValue=iv; // valor do indicador
   a[cnt].PriceValue=pv;     // valor do preço
   a[cnt].ExtremumBar=mb;    // índice da barra com um valor extremo
   a[cnt].SignalBar=sb;      // índice da barra em que se tornou conhecido o valor extremo 
   a[cnt].ExtremumTime=et;   // tempo da barra com um valor extremo
   cnt++;                    // aumento do contador de elementos ocupados
}

A matriz a[], à qual os novos dados devem ser adicionados, é enviada para o método através dos parâmetros. Esta pode ser a matriz m_upper[] ou m_lower[]. O número dos elementos ocupados da matriz a[] é passado através da variável cnt. Este pode ser a variável Cur.UpperCnt ou Cur.LowerCnt. A matriz a[] e a variável cnt são enviadas através de referências, uma vez que são alteradas no método. 

iv - valor do indicador com base no valor extremo, pv - preço na barra com um valor extremo, mb - índice da barra com um valor extremo, sb - barra de sinal (no qual se tornou conhecido o surgimento do valor extremo), et - tempo da barra com um valor extremo.

O tamanho da matriz é verificado no início do método AddExtremum(). Se estiver preenchida, seu tamanho aumentará até 1024 elementos. Os dados são adicionados e a variável cnt é aumentada posteriormente.

Vamos dar uma olhada no método CheckDiver() mais tarde.

Definição de valores extremos pelo valor limite.

A classe para definir por um valor limite é diferente da classe para definir por barras principalmente nos tipos de variáveis ​​Cur e Pre: este é o tipo SPseudoBuffers2 descrito no arquivo UniDiverDefines.mqh:

struct SPseudoBuffers2{
   int UpperCnt;
   int LowerCnt;
   double MinMaxVal;
   int MinMaxBar;   
   int Trend;
   void Reset(){
      UpperCnt=0;
      LowerCnt=0;  
      MinMaxVal=0;
      MinMaxBar=0;
      Trend=1;
   }   
};

A estrutura SPseudoBuffers2 tem os mesmos campos que SPseudoBuffers1, mais alguns adicionais:

  • MinMaxVal — variável para para o valor máximo ou mínimo do indicador
  • MinMaxBar — variável para o índice da barra, na qual o valor do indicador máximo ou mínimo é encontrado
  • Trend — variável para a direção do movimento do indicador. Se o valor for 1, o indicador se moverá para cima e seu valor máximo será monitorado. No caso de -1, o valor mínimo é rastreado.

Para o construtor da classe é enviado o parâmetro externo com o valor limite, isto é, a variável MinMaxThreshold. Seu valor é armazenado na variável m_threshold, declarada na partição private. 

O método Calculate() desta classe difere apenas na maneira de determinar o valor extremo:

switch(Cur.Trend){ // direção atual do movimento do indicador
   case 1: // para cima
      if(osc[i]>Cur.MinMaxVal){ // novo valor máximo
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<Cur.MinMaxVal-m_threshold){ // valor limite ultrapassado
         // adicionar o pico ao array
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-1;          // mudança na direção monitorada
         Cur.MinMaxVal=osc[i];  // valor inicial do mínimo
         Cur.MinMaxBar=i;       // barra com valor inicial do mínimo        
      }
   break;
   case -1: // para baixo
      if(osc[i]<Cur.MinMaxVal){ // novo mínimo
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>Cur.MinMaxVal+m_threshold){ // valor limite ultrapassado
         // adicionar fundo à matriz 
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=1;           // mudança na direção monitorada
         Cur.MinMaxVal=osc[i];  // valor inicial do valor máximo
         Cur.MinMaxBar=i;       // barra com valor inicial do valor máximo
      }         
   break;
}

Se a variável Cur.Trend for 1, o valor do oscilador será comparado com o valor da variável Cur.MinMaxValue. Se o novo valor do oscilador exceder o valor da variável, o valor da variável será atualizado. O índice da barra, no qual o novo máximo é detectado, é atribuído à variável Cur.MinMaxBar. Em seguida, é verificado se o valor do oscilador não diminuiu, desde o último máximo conhecido, no valor m_threshold. Se tiver, o oscilador terá mudado sua direção. O método AddExtremum() é chamado, os dados no novo valor extremo são salvos na matriz, o valor da variável Cur.Trend é substituído pelo contrário, enquanto os parâmetros iniciais do novo mínimo estão definidos nas variáveis Cur.MinMaxVal e Cur. MinMaxBar. Como o valor da variável Cur.Trend mudou, outra seção case - rastreamento dos valores mínimos do oscilador e ultrapassagem ascendente do limite - é executada a partir de agora.

Definição de valores extremos por sua posição em relação ao ponto médio do oscilador. O tipo de oscilador aplicado é carregado no construtor da classe. Dependendo desse tipo, o valor do ponto médio do oscilador é definido:

void CDiverMiddle(EOscUniType type){
   if(type==OscUni_Momentum){
      m_level=100.0;
   }
   else if(type==OscUni_RSI || type==OscUni_Stochastic){
      m_level=50.0;
   }
   else if(type==OscUni_WPR){
      m_level=-50.0;
   }
   else{
      m_level=0.0;
   }
}

Para o Momentum, o valor é 100, para o RSI e o Estocástico, é 50, para o WPR, é -50, para outros osciladores, é 0.

O próprio método para determinar o valor extremo é similar em muitos aspectos ao método de valor limite:

switch(Cur.Trend){
   case 1:
      if(osc[i]>Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<m_level){
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;               
      }
   break;
   case -1:
      if(osc[i]<Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>m_level){
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }         
   break;
}

A única diferença é que a mudança de direção é feita através de uma comparação com o nível médio do oscilador: osc[i]<m_level ou osc[i]>m_level.

Método de configuração do tipo de divergência. Adicionamos a variável para selecionar o tipo de divergência reconhecida aos parâmetros externos:

input int                  Number            =  3;

Por padrão, o valor do parâmetro é 3. De acordo com a Fig. 11, isso indica uma divergência clássica. No total, 9 combinações do movimento do preço e do indicador são exibidas na Fig 11. Vamos adicionar mais uma variação - "não é verificada". Agora, temos 10 combinações. Assim, usando o número decimal usual, podemos descrever qualquer combinação de movimentos diferentes em qualquer número (incluindo uma divergência tripla). Acontece que um dígito corresponde a uma divergência simples (em dois picos/fundos), dois dígitos, a um número triplo, e assim por diante. Por exemplo, o número 13 corresponderá à combinação exibida na Fig. 14.


Fig. 14. Combinação dos movimentos do indicador e
os preços quando Number=13 para venda
 

Classes para verificar condições de divergência. Para verificar as condições de divergência serão criadas uma classe base e outra filho. O valor do parâmetro Number será analisado na inicialização do indicador. O atributo mais importante é seu comprimento, uma vez que afeta o tamanho da matriz de ponteiros para as classes. Logo, será gerado o objeto apropriado para cada elemento de matriz de acordo com o tipo de condição.    

As classes de verificação de condição estão localizadas no arquivo UniDiver/CUniDiverConditions.mqh. A classe base é chamada de CDiverConditionsBase. Demos uma olhada nisso:

class CDiverConditionsBase{
   protected:
      double m_pt;
      double m_it;
   public:
   void SetParameters(double pt,double it){
      m_pt=pt;
      m_it=it;
   }
   virtual bool CheckBuy(double i1,double p1,double i2,double p2){
      return(false);
   }
   virtual bool CheckSell(double i1,double p1,double i2,double p2){
      return(false);
   }
};

A classe possui dois métodos virtuais para comparar dois picos vizinhos. Os parâmetros desses picos são carregados ​​neles: 

  • i1 — valor do indicador no ponto 1
  • p1 — valor do preço no ponto 1
  • i2 — valor do indicador no ponto 2
  • p2 — valor do preço no ponto 2

Os pontos são contados da direita para a esquerda, começando com um.

O método SetParameters() é usado para configurar parâmetros de comparação adicionais: pt - diferença disponível dos valores de preço, no qual os pontos do preço são assumidos como localizados num único nível. it - o mesmo parâmetro para comparação de picos do indicador. Os valores desses parâmetros são definidos através da janela de propriedades:

input double               IndLevel          =  0;
input int                  PriceLevel        =  0;

O código de uma das classes filho é mostrado abaixo:

class CDiverConditions1:public CDiverConditionsBase{
   private:
   public:
   bool CheckBuy(double i1,double p1,double i2,double p2){
      return((p1>p2+m_pt) && (i1>i2+m_it));
   }
   bool CheckSell(double i1,double p1,double i2,double p2){
      return((p1<p2-m_pt) && (i1<i2-m_it));
   }
};

No método para CheckBuy(), é verificado se o preço do ponto 1 excede o ponto 2. O mesmo vale para o indicador: o preço do ponto 1 deve exceder o valor no ponto 2. O método CheckSell() é simétrico ao método CheckBuy(), de maneira espelhada. Todas as outras classes são semelhantes e diferem apenas nas expressões boolianas, exceto CDiverConditions0. Os métodos CheckSell() e CheckBuy() retornam imediatamente true, nessa classe. É usado quando a verificação de condições está desativada (é possível qualquer variação).

Preparação para verificar as condições de divergência. Na seção protected da classe  CDiverBase são declaradas a matriz e a variável para seu tamanho:

CDiverConditionsBase * m_conditions[];
int m_ccnt;  

No método SetConditions(), é executada a alteração do tamanho da matriz m_conditions e a criação dos objetos de verificação da condição de divergência:

void SetConditions(int num,      // número da divergência
                   double pt,    // parâmetro PriceLevel
                   double it){   // parâmetro IndLevel
   if(num<1)num=1; // o número da divergência não pode ser inferior a 1
   ArrayResize(m_conditions,10); // número máximo de possível de condições
   m_ccnt=0; // contador do número real de condições
   while(num>0){
      int cn=num%10; // variação de divergência entre o novo par de valores extremos
      m_conditions[m_ccnt]=CreateConditions(cn); // criação do objeto
      m_conditions[m_ccnt].SetParameters(pt,it); // definição dos parâmetros de verificação de condição
      num=num/10; // passar para a próxima condição 
      m_ccnt++; // contagem do número de condições
   }
   // correção do tamanho da matriz de acordo com o número real de condições 
   ArrayResize(m_conditions,m_ccnt);    
}

No método são carregados os parâmetros:

  • num — parâmetro externo Number;
  • pt — parâmetro externo PriceLevel;
  • it — parâmetro externo IndLevel.

Primeiro, é verificado o parâmetro num:

if(num<1)num=1; // o número da divergência não pode ser inferior a 1

Então, a matriz m_conditions aumenta até o tamanho máximo possível (10 - comprimento do valor máximo da variável int). Depois disso, no ciclo while, dependendo do valor de cada um dos dígitos de num, é criado o objeto da verificação de condição através do método CreateConditions() e os parâmetros são definidos usando o método SetParameters(). Após o ciclo, o tamanho da matriz muda de acordo com o número real de condições aplicadas.

Examinemos o método CreateConditions():

CDiverConditionsBase * CreateConditions(int i){
   switch(i){
      case 0:
         return(new CDiverConditions0());      
      break;
      case 1:
         return(new CDiverConditions1());      
      break;
      case 2:
         return(new CDiverConditions2());      
      break;
      case 3:
         return(new CDiverConditions3());      
      break;
      case 4:
         return(new CDiverConditions4());      
      break;
      case 5:
         return(new CDiverConditions5());      
      break;      
      case 6:
         return(new CDiverConditions6());      
      break;
      case 7:
         return(new CDiverConditions7());      
      break;
      case 8:
         return(new CDiverConditions8());      
      break;
      case 9:
         return(new CDiverConditions9());
      break;
   }
   return(new CDiverConditions0()); 
}

O método é simples: dependendo do parâmetro i, é criado o objeto apropriado e é retornada a referência a ele.

Definição da divergência. Agora, podemos considerar o método CheckDivergence() da classe CDiverBase. Primeiro, consideramos todo o código do método, logo, nós o consideramos linha-a-linha:

void CheckDiver(  int i,                    // índice da barra contada
                  int ucnt,                 // número de picos na matriz m_upper
                  int lcnt,                 // número de fundos na matriz m_lower
                  const datetime & time[],  // matriz com o tempo das barras 
                  const double &high[],     // matriz com os preços high das barras
                  const double &low[],      // matriz com os preços low das barras               
                  double & buy[],           // buffer de indicador com setas para cima 
                  double & sell[],          // buffer de indicado com setas para baixo
                  double & osc[]            // buffer de indicador com valores do oscilador
){

   // limpeza de buffers com setas
   buy[i]=EMPTY_VALUE;
   sell[i]=EMPTY_VALUE;
   
   // limpeza de objetos gráficos
   this.DelObjects(time[i]);
   
   // para picos (sinais de venda)
   if(ucnt>m_ccnt){ // há um número suficiente de picos
      if(m_upper[ucnt-1].SignalBar==i){ // pico detectado na barra calculada

         bool check=true; // assumimos que a divergência ocorreu
         
         for(int j=0;j<m_ccnt;j++){ // através de todos os pares de picos
            // verificação das condições no próximo par de picos
            bool result=m_conditions[j].CheckSell( m_upper[ucnt-1-j].IndicatorValue,
                                                   m_upper[ucnt-1-j].PriceValue,
                                                   m_upper[ucnt-1-j-1].IndicatorValue,
                                                   m_upper[ucnt-1-j-1].PriceValue
                                                );
            if(!result){ // condição não é atendida 
               check=false; // sem divergência
               break; 
            } 
                                
         }
         if(check){ // ocorreu divergência
            // posicionamento da seta do buffer de indicador
            sell[i]=osc[i];
            // plotagem de  linhas auxiliares e/ou uma seta no gráfico de preços
            this.DrawSellObjects(time[i],high[i],ucnt);
         }
      }
   }
   
   // para fundos (sinais de compra)
   if(lcnt>m_ccnt){
      if(m_lower[lcnt-1].SignalBar==i){
         bool check=true;
         for(int j=0;j<m_ccnt;j++){
            bool result=m_conditions[j].CheckBuy(  m_lower[lcnt-1-j].IndicatorValue,
                                                   m_lower[lcnt-1-j].PriceValue,
                                                   m_lower[lcnt-2-j].IndicatorValue,
                                                   m_lower[lcnt-2-j].PriceValue
                                                );
            if(!result){
               check=false;
               break;
            }                                          
         }
         if(check){
            buy[i]=osc[i];
            this.DrawBuyObjects(time[i],low[i],lcnt);
         }
      }
   }    
}

No método são carregados os parâmetros:

  • i — índice da barra atual calculada;
  • ucnt — número de elemento da matriz usados m_upper[];
  • lcnt — número de elemento da matriz usados m_lower[];
  • time[] — matriz com o tempo das barras;
  • high[] — matriz com os preços das barras high;
  • low[] — matriz com os preços das barras low;                  
  • buy[] — buffer de indicador para setas de compra;
  • sell[] — buffer de indicador para setas de venda;
  • osc[] — buffer de indicador com os valores do oscilador.

Primeiro, são limpados todos os buffers com setas.

// limpeza de buffers com setas
buy[i]=EMPTY_VALUE;
sell[i]=EMPTY_VALUE;

Os objetos gráficos correspondentes à barra calculada são removidos:

// limpeza de objetos gráficos
this.DelObjects(time[i]);

Além das setas, o indicador iDivergence aplica objetos gráficos para exibir setas, preços e linhas que conectam valores extremos no gráfico de preços com os picos/fundos no gráfico do oscilador. 

Em seguida, há duas seções idênticas de código para verificar as condições de venda e compra. Consideremos a primeira seção para venda. O número de osciladores disponíveis deve ser 1 mais do que o número de condições verificadas. Por isso, é executada uma revisão:  

// para picos (sinais de venda)
if(ucnt>m_ccnt){ // existe um número suficiente de picos

}

Logo, é verificado se existem picos na barra calculada. Isso é definido de acordo com o índice da barra calculada e do índice da matriz com os dados dos picos/fundos:

if(m_upper[ucnt-1].SignalBar==i){ // existe um pico na barra calculada

}

Precisa-se de uma variável auxiliar para os resultados da verificação das condições:

bool check=true; // assumimos que tenha corrido uma divergência

No ciclo for, percorremos todas as condições, carregando neles os dados dos picos:

for(int j=0;j<m_ccnt;j++){ // através de todos os pares de picos
   // verificação das condições no próximo par de picos
   bool result=m_conditions[j].CheckSell( m_upper[ucnt-1-j].IndicatorValue,
                                          m_upper[ucnt-1-j].PriceValue,
                                          m_upper[ucnt-1-j-1].IndicatorValue,
                                          m_upper[ucnt-1-j-1].PriceValue
                                        );
   if(!result){ // condição não é atendida 
      check=false; // sem divergência
      break; 
   } 
}

Se alguma das condições não for atendida, saímos do ciclo. À variável check é atribuído o valor false. Se todas as condições forem atendidas, check armazenará o valor true, além disso, será definida um seta e criado o objeto gráfico:

if(check){ // ocorreu divergência
   // posicionamento da seta do buffer de indicador
   sell[i]=osc[i];
   // plotagem de  linhas auxiliares e/ou uma seta no gráfico de preços
   this.DrawSellObjects(time[i],high[i],ucnt);
}

A exibição de objetos gráficos pode ser ativada/desativada na janela de propriedades do indicador. As seguintes variáveis ​​são declaradas para isso:

input bool                 ArrowsOnChart     =  true;
input bool                 DrawLines         =  true;
input color                ColBuy            =  clrAqua;
input color                ColSell           =  clrDeepPink;
  • ArrowsOnChart — ativar setas de objetos gráficos no gráfico de preços
  • DrawLines — ativar plotagem de linhas que ligam Highs e Lows do preço e do indicador
  • ColBuy и ColSell — cor dos objetos gráficos para sinais de compra e venda

Na seção protected da classe CDiverBase, são declaradas as variáveis ​​correspondentes:

bool m_arrows;  // corresponde à variável ArrowsOnChart
bool m_lines;   // corresponde à variável DrawLines
color m_cbuy;   // corresponde à variável ColBuy
color m_csell;  // corresponde à variável ColSell 

Os valores destas variáveis são definidos no método SetDrawParmeters():

void SetDrawParmeters(bool arrows,bool lines,color cbuy,color csell){
   m_arrows=arrows;
   m_lines=lines;
   m_cbuy=cbuy;
   m_csell=csell;
}

Consideremos os métodos de trabalho com objetos gráficos. Remoção:

void DelObjects(datetime bartime){ // desenho de linhas habilitado
   if(m_lines){
      // geração de um prefixo comum
      string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_";
      for(int j=0;j<m_ccnt;j++){ // segundo o número de condições de divergência
         ObjectDelete(0,pref+"bp_"+IntegerToString(j)); // linha no gráfico de preços no caso de um sinal de compra
         ObjectDelete(0,pref+"bi_"+IntegerToString(j)); // linha no gráfico indicador em caso de um sinal de compra
         ObjectDelete(0,pref+"sp_"+IntegerToString(j)); // linha no gráfico de preços em caso de sinal de venda 
         ObjectDelete(0,pref+"si_"+IntegerToString(j)); // linha no gráfico do indicador em caso de um sinal de venda 
      }            
   }
   if(m_arrows){ // setas ativadas no gráfico de preços
      // 
      ObjectDelete(0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_ba");
      // 
      ObjectDelete(0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_sa");
   }
}

Linhas de conexão de picos/fundos e setas são removidas separadamente. Os nomes de todos os objetos gráficos começam com o nome do indicador seguido pelo tempo da barra, na qual a divergência foi detectada. Os nomes são formados adicionalmente no ciclo correspondente ao número de condições m_ccnt. No gráfico de preços, "bp" é exibido para sinais de compra, enquanto no gráfico do indicador, "bi" é exibido para sinais de compra. Da mesma forma, "sp" e "ip" são adicionados para sinais de venda. O índice j é adicionado ao final dos nomes. "_ba" (seta de sinal de compra) ou "_sa" (seta de sinal de venda) são adicionados aos nomes de seta.

A criação de objetos gráficos é realizada nos métodos DrawSellObjects() e DrawBuyObjects(). Examinemos um deles.

void DrawSellObjects(datetime bartime,double arprice,int ucnt){
   if(m_lines){ // desenho de linhas habilitado 
      
      // geração de um prefixo comum
      string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_";
      
      for(int j=0;j<m_ccnt;j++){  // para todas as condições de divergência
                  
         // linha no gráfico de preços
         fObjTrend(  pref+"sp_"+IntegerToString(j),
                     m_upper[ucnt-1-j].ExtremumTime,
                     m_upper[ucnt-1-j].PriceValue,
                     m_upper[ucnt-2-j].ExtremumTime,
                     m_upper[ucnt-2-j].PriceValue,
                     m_csell);
                     
         // linha no gráfico do indicador
         fObjTrend(  pref+"si_"+IntegerToString(j),
                     m_upper[ucnt-1-j].ExtremumTime,
                     m_upper[ucnt-1-j].IndicatorValue,
                     m_upper[ucnt-2-j].ExtremumTime,
                     m_upper[ucnt-2-j].IndicatorValue,
                     m_csell,
                     ChartWindowFind(0,MQLInfoString(MQL_PROGRAM_NAME)));  
      }
   }
      
   if(m_arrows){ // setas ativadas no gráfico de preços
      fObjArrow(MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_sa",
               bartime,
               arprice,
               234,
               m_csell,
               ANCHOR_LOWER); 
   }            
} 

Os nomes dos objetos são formados da mesma maneira que durante a remoção. Depois disso, os objetos gráficos são criados usando as funções fObjTrend() e fObjArrow(). Eles estão localizados no arquivo de inclusãoUniDiver/UniDiverGObjects.mqh. As funções são bastante simples, não há sentido em analisá-las.

Conclusão do indicador. Só resta aplicar as classes criadas no indicador. Na função OnInit(), dependendo do tipo de valores extremos selecionado, criamos o objeto correspondente:

switch(ExtremumType){
   case ExtrBars:
      diver=new CDiverBars(LeftBars,RightBars);
   break;
   case ExtrThreshold:
      diver=new CDiverThreshold(MinMaxThreshold);
   break;      
   case ExtrMiddle:
      diver=new CDiverMiddle(Type);
   break;      
}

Um dos parâmetros externos - PriceLevel - é medido em pontos, por isso é conveniente corrigi-lo dependendo do número de casas decimais. Para fazer isso, declaramos outra variável para poder desativar esta correção:

input bool                 Auto5Digits       =  true;

Em seguida, declaramos uma variável auxiliar para o parâmetro corrigido e ajustamo-la na função OnInit():

int pl=PriceLevel;   
if(Auto5Digits && (Digits()==5 || Digits()==3)){
   pl*=10;
}  

Definimos parâmetros de divergência e exibição:

diver.SetConditions(Number,Point()*pl,IndLevel);
diver.SetDrawParmeters(ArrowsOnChart,DrawLines,ColBuy,ColSell);

Restam apenas algumas linhas na função OnCalculate(). Chamada do método básico Calculate():

diver.Calculate(  rates_total,
                  prev_calculated,
                  time,
                  high,
                  low,
                  buf_osc,
                  buf_buy,
                  buf_sell);

Em caso de plotagem de objetos gráficos, devemos acelerar sua renderização:

if(ArrowsOnChart || DrawLines){
   ChartRedraw();
}

Quando o indicador completar seu trabalho, os objetos gráficos devem ser removidos. Isso é feito no destructor da classe CDiverBase. Os objetos de verificação de condições de divergência também são removidos.

void ~CDiverBase(){
   for(int i=0;i<ArraySize(m_conditions);i++){ // por todas as condições
      if(CheckPointer(m_conditions[i])==POINTER_DYNAMIC){
         delete(m_conditions[i]); // remoção do objeto
      }      
   }  
   // limpeza de objetos gráficos
   ObjectsDeleteAll(0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw();          
}   

Neste ponto, a fase principal do desenvolvimento do indicador acabou. Na Figura 15 exibe o gráfico com o indicador anexado a ele (na sub-janela) com a exibição ativada de setas no gráfico de preços e linhas traçadas entre os picos. 


Fig. 15. Indicador de divergência no gráfico de preços com as setas exibidas no gráfico de preços e as linhas que ligam os pontos extremos

Agora, só precisamos adicionar a função de alerta. É muito simples e já foi descrito em outros artigos. Nos anexos abaixo, você pode encontrar um indicador pronto com a função de alerta juntamente com todos os arquivos necessários para o indicador.

Fim do artigo

Apesar da versatilidade do indicador, também podemos notar suas desvantagens. A principal desvantagem é a dependência do parâmetro IndLevel no tipo de oscilador usado, bem como a dependência do parâmetro PriceLevel num timeframe. Para eliminar essa dependência, os valores padrão para esses parâmetros são 0. Mas, ao mesmo tempo, é quase impossível cumprir as condições para certas combinações do movimento de preços e indicadores. Se a verificação de um movimento horizontal for incluída na verificação de divergência, sua implementação será improvável. Neste caso, apenas permanecem as opções de divergência 1, 3, 7 e 9. Isso pode ser um problema apenas no testador ao otimizar um EA que usa indicadores.

Com a abordagem certa para a otimização, isso não será um problema, pois a otimização geralmente é realizada num único símbolo e timeframe. Primeiro, precisamos determinar o símbolo e o timeframe aplicados e definir o valor apropriado para o parâmetro PriceLevel. Em seguida, precisamos selecionar o oscilador utilizado e definir o valor apropriado para o parâmetro IndLevel. Não há nenhum objetivo em lutar pela otimização automática do tipo de oscilador a ser aplicado e os valores dos parâmetros PriceLevel e IndLevel, já que existem muitos outros parâmetros de otimização além deles. Em primeiro lugar, este é o tipo de divergência (variável Number) e o período do oscilador.

Arquivos de aplicativo

Para a operação iDivergence, você precisa do oscilador universal do artigo "Oscilador universal com interface gráfica do usuário". O oscilador e todos os arquivos necessários podem ser encontrados no apêndice.

Todos os arquivos de aplicativo:

  • Include/InDiver/CUniDiverConditions.mqh — arquivos com classes de verificação de condições de divergência;
  • Include/InDiver/CUniDiverExtremums.mqh — arquivo com classes de identificação de valores extremos; 
  • Include/InDiver/UniDiverDefines.mqh — descrição de estruturas e enumerações;
  • Include/InDiver/UniDiverGObjects.mqh — função para trabalhar com objetos gráficos;
  • Indicators/iDivergence.mq5 — indicador;
  • Indicators/iUniOsc.mq5 — oscilador universal;
  • Include/UniOsc/CUniOsc.mqh — arquivo com as classes para o oscilador universal;
  • Include/UniOsc/UniOscDefines.mqh — descrição de estruturas e enumerações para o indicador universal.

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

Arquivos anexados |
MQL5.zip (14.11 KB)
Expert Advisor Multiplataforma: Stops Expert Advisor Multiplataforma: Stops
Este artigo discute uma implementação dos níveis de stop em um expert advisor para torná-lo compatível com as duas plataformas - MetaTrader 4 e MetaTrader 5.
Avaliação de risco numa sequência de operações com um ativo Avaliação de risco numa sequência de operações com um ativo
Este artigo descreve como usar os métodos da teoria da probabilidade e estatística matemática na análise de sistemas de negociação.
Expert Advisor Multiplataforma: Stops personalizados, Breakeven e Stop Móveis Expert Advisor Multiplataforma: Stops personalizados, Breakeven e Stop Móveis
Este artigo discute como os níveis de stop personalizados podem ser configurados em um expert advisor multiplataforma. Ele também discute um método fortemente relacionado ao assunto na qual envolve a possibilidade de definir a evolução do nível de stop ao longo do tempo.
Interfaces gráficas XI: Integração da Biblioteca Gráfica Padrão (build 16) Interfaces gráficas XI: Integração da Biblioteca Gráfica Padrão (build 16)
Uma nova versão da biblioteca gráfica para a criação de gráficos científicos (a classe CGraphic) foi apresentada recentemente. Esta atualização da biblioteca desenvolvida para criar interfaces gráficas irá introduzir uma versão com um novo controle para a criação de gráficos. Agora está ainda mais fácil de visualizar os dados de diferentes tipos.