English Русский 中文 Español Deutsch 日本語
Padrão de bandeira

Padrão de bandeira

MetaTrader 5Exemplos | 4 setembro 2017, 12:27
5 942 1
Dmitry Fedoseev
Dmitry Fedoseev

Conteúdo

O padrão de Bandeira é chamado assim por ter uma característica especial, isto é, possui um considerável movimento vertical do preço ("mastro") e, logo, um amplo movimento horizontal com forma de "pano" retangular (fig. 1).


Fig. 1. Bandeira

Nos livros e sites dedicados a análise técnica, o padrão de Bandeira é visto frequentemente em paralelo com o padrão de Flâmula. Flâmula, ao contrário de Bandeira, tem um pano triangular (fig. 2), de modo que, em alguns guias sobre análise técnica, o padrão de Bandeira é considerado juntamente com o de Triângulo.


Fig. 2. Flâmula

Poderia parecer que a Flâmula e o Triângulo são apenas nomes diferentes para o mesmo padrão. Mas, em alguns guias sobre análise técnica, particularmente no livro de Tomas Bulkovsky "Enciclopédia completa de modelos gráficos de preços" [Полная энциклопедия графических ценовых моделей] ambos figuram separadamente. Neste livro, também é descrito o padrão de Cunha, semelhante ao de Triângulo mas espelhado horizontalmente. À sua esquerda está a parte estreita, enquanto à sua direita o preço se move com impulso crescente (fig. 3).


Fig. 3. Cunha


Além do padrão de Cunha, são conhecidos o de Triângulo expansivo e outras Formações retangulares, semelhantes com a Bandeira. Isto quer dizer que devem existir regras claras para diferenciar a Flâmula do Triângulo, a Cunha do Triângulo expansivo e a Bandeira da Formação horizontal. Esta questão será discutida aqui em primeiro lugar. A seguir, serão criados indicadores para encontrar todos esses padrões. 


Diferença entre o padrão de Flâmula e o de Triângulo

Consideraremos as diferenças entre a Flâmula e o Triângulo, bem como todos os padrões listados na introdução e que têm uma forma semelhante:

  • Formação retangular — Bandeira;
  • Triângulo — Flâmula;
  • Triângulo expansivo — Cunha.

Numa categoria estão os padrões de Formação retangular, Triângulo e Triângulo expansivo, enquanto na outra, os padrões de Bandeira, Flâmula e Cunha.

Os padrões de primeira categoria geram sua forma através dos pontos de reversão do preço (fig. 4), pode-se usar o indicador ZigZag para encontrá-los.


Fig. 4. Padrões: a — Formação horizontal, b — Triângulo, c — Triângulo expansivo.  
Os padrões são mostrados para um potencial movimento para cima (para comprar).

Os padrões da segunda categoria geram sua forma preenchendo com barras a área da figura (fig. 5). Certamente, é pouco provável ver algo tão explícito no gráfico, como mostrado na figura, mas a ideia é que uma barra é sobreposta por uma barra adjacente, sendo assim formada uma determinada figura. 


Fig. 5. Padrões: a — Bandeira, b — Flâmula, c — Cunha.
Os padrões são mostrados para um potencial movimento para cima (para comprar).

Tendo definido as categorias e suas diferenças fundamentais, olharemos para cada um dos padrões separadamente. 


Formação horizontal

Comecemos por considerar os padrões da primeira categoria. Basta usar o indicador ZigZag para defini-los. O primeiro padrão nesta categoria é a Formação horizontal, que na segunda categoria corresponde à Bandeira. No entanto, na maioria dos guias para a análise técnica, a Formação horizontal é chamada de Bandeira.

Para a geração de uma Formação horizontal, o preço deve fazer um movimento vertical significativo, em seguida, formar, pelo menos, dois picos, mais ou menos no mesmo nível, e dois fundos, também no mesmo nível de preço. Pode acontecer que o preço forme três picos e fundos (fig. 6), ou até mais. Por isso, no indicador, será criado um parâmetro que define o número de picos e fundos que geram o padrão.


Fig. 6 Formações horizontais: a — composta de dois picos/fundos, b — composta de três picos/fundos.
Os padrões são mostrados para um potencial movimento para cima (para comprar). 

Não é preciso que a borda superior e a inferior do padrão sejam horizontais. É importante que sejam paralelas, por isso o indicador terá um outro parâmetro, a fim de selecionar a inclinação da formação, isto é: horizontal, ascendente, descendente (fig. 7).


Fig. 7. a — Formação horizontal, b — Formação com inclinação descendente, c — Formação com inclinação ascendente. 
Os padrões são mostrados para um potencial movimento para cima (para comprar).

Naturalmente, a formação com inclinação ascendente (ou descendente) não pode ser chamada de horizontal, no entanto é fundamentalmente próxima da formação horizontal.

O padrão termina de se gerar, quando o preço rompe o patamar formado pelos picos (fig. 8).

 
Fig. 8. Conclusão da formação do padrão e momento de abertura
da posição buy: a — para a formação horizontal,
b — para a formação com inclinação descendente 

Definição do momento de entrada para a formação - com inclinação ascendente - será executada sem levar em conta a inclinação dos picos, isto é, o nível horizontal a partir do último pico (fig. 9).


Fig. 9. Definição do momento de entrada (de compra) para a formação com inclinação ascendente

Para formação com inclinação descendente, pode-se utilizar uma variante simples com nível horizontal, por isso, no indicador, haverá uma variável para selecionar o tipo de nível independentemente do tipo de formação.

O alvo da Formação horizontal é determinado segundo tamanho do movimento vertical até a formação do padrão. O preço deverá se deslocar - após a formação - a mesma distância percorrida por ele antes da geração da formação (fig. 10).


Fig. 10. Definição de alvo. Distância L1, percorrida pelo preço antes de entrar
na formação, é igual à distância L2 após sair da formação.  

Como as bordas superior e inferior da formação são paralelas, é possível usar uma versão mais simples para definir o alvo, isto é, medir a distância percorrida pelo preço até o primeiro pico de formação e movê-lo desde o último fundo (fig. 11).


Fig. 11. Uma maneira simples de determinar o alvo. Distância L1, percorrida pelo preço antes da formação
do primeiro pico,
é igual à distância L2 desde o último fundo antes do alvo


Triângulo contrativo

O padrão de Triângulo contrativo difere ligeiramente da Formação horizontal. A única diferença reside no fato dos segmentos do Zigzag, que forma o padrão, terem de se estreitar gradualmente (fig. 12).


 Fig. 12. Triângulo contrativo. O segmento 3-4 deve ser
mais pequeno do que o segmento 1-2, e o segmento 5-6 mais pequeno do que o segmento 3-4
 

Em todos os outros aspectos é igual que o padrão de Formação horizontal: a localização horizontal do Triângulo ou a inclinação ascendente/descendente, a entrada segundo o rompimento da linha de resistência formada pelos dois últimos picos ou do nível horizontal a partir do último pico, cálculo de alvo semelhante.


Triângulo expansivo

Aparentemente tudo que tem a ver com o Triângulo contrativo se aplica ao Triângulo expansivo, a única diferença é que os segmentos do ZigZag, que constituem o padrão, são aumentados (fig. 13).


 Fig. 13. Triângulo expansivo. O segmento 3-4 deve ser
maior do que o segmento 1-2, e o segmento 5-6 maior do que o segmento 3-4
 

Essa similaridade significativa entre os três padrões é propício para a criação de um indicador universal que nos ajude a encontrá-los.  


Indicador universal de pesquisa de Formação horizontal e Triângulos

Para criar este indicador, é necessário o indicador  iUniZigZagSW do artigo "ZigZag universal". Além disso, é preciso ter vários arquivos adicionais: CSorceData.mqh, CZZDirection.mqh e CZZDraw.mqh. Estes arquivos, bem como o arquivo  iUniZigZagSW.mq5, estão no apêndice do artigo "ZigZag universal". Baixe e descomprima o ficheiro MQL5 na pasta de dados do terminal. Após copiar e colar na pasta MQL5/Indicators, aparecerá a pasta do ZigZags com vários arquivos (incluindo os arquivos com iUniZigZagSW.mq5), enquanto, na pasta MQL5/Includes, surgirá a pasta ZigZag com os arquivos CSorceData.mqh, CZZDirection.mqh e CZZDraw.mqh. Depois da cópia de arquivos, reinicie o terminal, para que os indicadores sejam pré-compilados, ou compile-os separadamente no MetaEditor. Certifique-se sempre que o indicador iUniZigZagSW esteja funcionando, após anexá-lo no gráfico, no terminal.

No artigo "Ondas de Wolfe", num dos estágios de criação do indicador, foi salvo o arquivo iWolfeWaves_Step_1.mq5. Nele, é acessado o indicador iUniZigZagSW.mq5 através da função iCustom() e é formada a matriz com todos os picos e fundos do ZigZag. Baixe o aplicativo do artigo "Ondas de Wolfe", descompacte seu arquivo iWolfeWaves_Step_1.mq5, copie e cole na pasta MQL5/Indicators, altere seu nome para "iHorizontalFormation" e abra no MetaEditor. O futuro trabalho do indicador de pesquisa de padrão de Formação horizontal será levado a cabo neste arquivo. É possível que seja necessário - no arquivo - alterar o caminho para o indicador iUniZigZagSW. Para verificar isto, compile o indicador e tente anexá-lo no gráfico. Se, ao fazer isto, for aberta uma janela com a mensagem "Error load indicator", encontre, na função OnInit(), a chamada da função iCustom() e corrija o nome do indicador a ser chamado de "iUniZigZagSW" para "ZigZags\\iUniZigZagSW". Depois de corrigir, novamente compile o indicador e certifique-se de que está agora anexado ao gráfico sem mensagens de erro. Por enquanto, não se dever plotar o indicador.

Todo o processo de pesquisa dos padrões a serem examinados pode ser claramente dividido em várias tarefas quase independentes:

  1. Definição da dimensão do movimento do preço, antes da formação do padrão.
  2. Definição da forma do padrão.
  3. Definição da inclinação do padrão.
  4. Conclusão da formação do padrão: imediatamente após a formação de um padrão ou expectativa de rompimento de nível.
  5. Cálculo do alvo.

A solução de cada tarefa (excepto a primeira) será apresentada usando variantes de realização. Desse modo, será atingida tanto a universalidade quanto a possibilidade de usar o indicador para identificar os três padrões. Alternância entre variantes será levada a cabo na janela Propriedades do indicador, através das listas drop-down das enumerações. 

Enumeração para o seletor de formulário (tipo de padrão):

enum EPatternType{
   PatternTapered,
   PatternRectangular,
   PatternExpanding
};

Variável correspondente na janela de propriedades:

input EPatternType         Pattern        =  PatternRectangular;

Este parâmetro permite escolher a forma do padrão: PatternTapered — Triângulo contrativo, PatternRectangular — Retângulo, PatternExpanding — Triângulo expansivo.

Enumeração para selecionar a inclinação do padrão:

enum EInclineType{
   InclineAlong,
   InclineHorizontally,
   InclineAgainst
};

Variável correspondente na janela de propriedades:

input EInclineType         Incline        =  InclineHorizontally; 

Este parâmetro permite selecionar a inclinação do padrão: InclineAlong — inclinação ao longo do movimento pretendido (para compras é ascendente, para vendas é descendente), InclineHorizontally — sem inclinação, InclineAgainst — inclinação na direção oposta do movimento de preço pretendido.

Enumeração para selecionar o método de conclusão da formação do padrão:

enum EEndType{
   Immediately,
   OneLastVertex,
   TwoLastVertices
};

Variável correspondente na janela de propriedades:

input EEndType             CompletionType =  Immediately;

Este parâmetro permite selecionar as seguintes variantes: Immediately — imediatamente após a formação do padrão, OneLastVertex — após a rompimento do nível horizontal, formado pelo último pico do padrão, TwoLastVertices — após o rompimento do nível, formado pelos dois últimos picos do padrão.

Enumeração para selecionar a variante de cálculo de alvo:

enum ETargetType{
   FromVertexToVertex,
   OneVertex,
   TwoVertices
};

Variável correspondente na janela de propriedades:

input ETargetType          Target         =  OneVertex;

Este parâmetro permite escolher entre as seguintes variantes: FromVertexToVertex — de pico a pico (fig. 11), OneVertex — num pico (fig. 10), TwoVertices — em dois picos (são usados os dois fundos iniciais do padrão, ver fig. 14).


Fig. 14. Padrão de três picos. Variante de definição de alvo — TwoVertices,
método de conclusão de padrão — OneLastVertex.

Ao usar a variante de conclusão de padrão Immediately, o parâmetro Target não é válido, uma vez que existe apenas uma variante de definição de alvo — FromVertexToVertex. Para as outras duas variantes de conclusão de padrão (OneLastVertex и TwoLastVertices), são possíveis diferentes combinações com as três variantes do parâmetro CompletionType. Preste atenção a uma outra particularidade: nas variantes de definição de alvo OneVertex e TwoVertices - para definição do valor - são usados o fundo inicial ou os dois fundos iniciais (ponto 2, ou pontos 2 e 4 na fig. 14), para definição do nível de rompimento, são usadas o pico finais ou os dois picos finais (ponto 5 ou pontos 3 e 5 na fig. 14). Se for usado o padrão de dois picos, deverá ser usado o ponto 3 ou os pontos 1 e 3.

Para levar a cabo a tarefa 1, será necessário um parâmetro que define a dimensão do movimento do preço do padrão anterior:

input double               K1             =  1.5;

Altura do segmento 1-2 (ver fig. 14) é tomado com base do padrão (tamanho de referência), todas as validações de tamanhos serão executadas dependendo dela. Parâmetro K1 determina quantas vezes o segmento 0-1 deve ser maior do que a altura do segmento 1-2.

Para resolver a tarefa 2 (definição da forma do padrão) é usado o parâmetro K2:

input double               K2             =  0.25;

Quanto menor for o parâmetro, mais constante deve será a altura do padrão ao longo de todo seu comprimento. Para padrões triangulares (expansivos e contrativos) um aumento nos padrões indicará a busca de padrões com a forma triangular bem definida.

Para resolver a tarefa 3 (definição da inclinação do padrão) é usado o parâmetro K3:

input double               K3             =  0.25;

Quanto menor for o valor do parâmetro, mais exato deverá ser o parâmetro localizado. Ao procurar padrões inclinados, o aumento do parâmetro K2 permite encontrar apenas padrões com uma inclinação bem definida.

Finalmente, um dos principais parâmetros:

input int                  N              =  2;

O parâmetro N determina o número de picos do padrão. 

Como resultado, obtemos o seguinte conjunto de parâmetros externos (excepto parâmetros do ZigZag):

input EPatternType         Pattern        =  PatternRectangular;
input EInclineType         Incline        =  InclineHorizontally;     
input double               K1             =  1.5;
input double               K2             =  0.25;
input double               K3             =  0.25;
input int                  N              =  2;
input EEndType             CompletionType =  Immediately;
input ETargetType          Target         =  OneVertex;

Usando o parâmetro N, calculamos o quantos pontos do ZigZag são necessários para determinar o padrão. Primeiro, declaramos uma variável global:

int RequiredCount;

Na função OnInit(), calculamos seu valor:

RequiredCount=N*2+2;

2*N — número de picos formando o padrão (N picos e N fundos). Outro pico determina o movimento do preço anterior, e outro, o último ponto do novo segmento do ZigZag (nos cálculos não são utilizados).

Para o trabalho à frente, será usada a função OnTick(). O novo código será adicionado à extremidade do indicador de ciclo principal. Condições de formação de padrão serão validadas quando houver suficientes pontos de ZigZag e só ao mudarem de direção. Acompanhamento do nível e do preço será realizado a cada mudança do ZigZag:

if(CurCount>=RequiredCount){
   if(CurDir!=PreDir){      
      // validação de condições

   }
   // acompanhamento do preço e do nível

} 

Em primeiro lugar, é determinado o valor de base, isto é, a altura do segmento 2-1 (ver fig. 14). Este valor será usado para a validar as condições de formação do padrão. Em seguida, validam-e as condições da tarefa 1, isto é, o valor do curso anterior:

int li=CurCount-RequiredCount;                                            // índice do ponto inicial do padrão na matriz PeackTrough
double base=MathAbs(PeackTrough[li+1].Val-PeackTrough[li+2].Val); Я       // valor base
double l1=MathAbs(PeackTrough[li+1].Val-PeackTrough[li].Val);             // altura do segmento 1-2
   if(l1>=base*K1){                                                       // validação do valor do curso anterior
        // outras validações

   }

Futuras validações dependerão de e o último segmento do ZigZag for ascendente ou descendente.

if(CurDir==1){              // último segmento - do ZigZag - ascendente
   // validação da condição para a direção ascendente  
             
}
else if(CurDir==-1){        // último segmento - do ZigZag - descendente
   // validação da condição para direção descendente

}

Examinemos a validação da condição para a direção ascendente:

if(CheckForm(li,base) && CheckInclineForBuy(li,base)){      // validação da forma e direção
   if(CompletionType==Immediately){ 
      // imediatamente colocamos a seta do indicador
      UpArrowBuffer[i]=low[i];
      // definimos o ponto-alvo
      UpDotBuffer[i]=PeackTrough[CurCount-1].Val+l1;
   }
   else{
      // configuração de parâmetros do nível a ser rompido
      SetLevelParameters(1);
      // definição dos parâmetros do alvo
      SetTarget(1,li);
   }
} 

Validação das condições de formação de padrão é realizada por dois funções: CheckForm() — validação da forma do padrão (tarefa 2) e CheckInclineForBuy() — validação da inclinação (tarefa 3). Se a validação da forma e da inclinação estiver realizada, em seguida, dependendo do tipo de conclusão do padrão, será realizada quer a definição da seta e do ponto-alvo no gráfico, quer a definição dos parâmetros do nível a ser rompido, após o qual o indicador monitorizará o nível.

Função CheckForm(). O índice do primeiro ponto do padrão (na matriz PeackTrough) e o valor base são encaminhados para a função. Consideremos o código da função:

bool CheckForm(int li,double base){               
   switch(Pattern){
      case PatternTapered: 
         // contrativo
         return(CheckFormTapered(li,base));
      break;               
      case PatternRectangular: 
         // retangular
         return(CheckFormRectangular(li,base));
      break;
      case PatternExpanding: 
         // expansivo
         return(CheckFormExpanding(li,base));
      break;
   }
   return(true);
}

Na função, dependendo do valor do parâmetro Pattern, é executada a chamada das respectivas funções: CheckFormTapered() — triângulo contrativo, CheckFormRectangular() — formação retangular, CheckFormExpanding() — triângulo expansivo,

Função CheckFormTapered():

bool CheckFormTapered(int li,double base){
   // ciclo de 1, o primeiro segmento não é verificado,
   // mas todos os segmentos subsequentes são verificados em relação a ele 
   for(int i=1;i<N;i++){ 
      // cálculo do índice do seguinte ponto superior do padrão 
      int j=li+1+i*2;
      // valor do seguinte segmento 
      double lv=MathAbs(PeackTrough[j].Val-PeackTrough[j+1].Val);
      // valor do segmento anterior
      double lp=MathAbs(PeackTrough[j-2].Val-PeackTrough[j-1].Val);
      // segmento anterior deve ser maior, caso contrário,
      // a função retorna false   
      if(!(lp-lv>K2*base)){
         return(false);
      }
   } 
   return(true);
}

Na função, tem lugar o ciclo nos segmentos do ZigZag que constituem o padrão, cada segmento tem de ser menor que o anterior.

A função CheckFormExpanding() é semelhante, a única diferença é uma condição:

if(!(lv-lp>K2*base)){
   return(false);
}

Para cumprir esta condição, cada segmento subsequente deve ser maior do que o anterior.

Função CheckFormRectangular():

bool CheckFormRectangular(int li,double base){   
   // ciclo de todos picos superiores, com exceção do primeiro      
   for(int i=1;i<N;i++){
      // cálculo do índice do pico subsequente 
      int j=li+1+i*2; 
      // cálculo do tamanho do segmento seguinte
      double lv=MathAbs(PeackTrough[j].Val-PeackTrough[j+1].Val);
      // o segmento não deverá ser muito diferente do valor base 
      if(MathAbs(lv-base)>K2*base){
         return(false); 
      }
   }
   return(true);
}

Nesta função, cada um dos segmentos é comparado com o valor de base. Se houver uma diferença significativa, a função retornará false.

Se a forma for validada com sucesso, será verificada a inclinação. Função CheckInclineForBuy():

bool CheckInclineForBuy(int li,double base){                 
   switch(Incline){
      case InclineAlong:
         // inclinação segundo a direção do movimento
         return(CheckInclineUp(li,base));
      break;
      case InclineHorizontally:
         // sem inclinação
         return(CheckInclineHorizontally(li,base));
      break;                     
      case InclineAgainst:
         // inclinação contra o movimento
         return(CheckInclineDn(li,base));
      break;
   } 
   return(true);
}  

A função de validação de inclinação para venda difere apenas em duas linhas:

bool CheckInclineForSell(int li,double base){                 
   switch(Incline){
      case InclineAlong:
         // inclinação segundo a direção do movimento
         return(CheckInclineDn(li,base));
      break;
      case InclineHorizontally:
         // sem inclinação
         return(CheckInclineHorizontally(li,base));
      break;                     
      case InclineAgainst:
         // inclinação contra o movimento
         return(CheckInclineUp(li,base));
      break;
   } 
   return(true);
} 

Para compra, no caso de Incline igual a InclineAlong (no curso do movimento), é chamada a função CheckInclineUp(), enquanto para para venda, CheckInclineDn(). No caso de Incline igual a InclineAgainst (contra o curso do movimento, o oposto.

Função de validação de inclinação de padrão ascendente — CheckInclineUp():

bool CheckInclineUp(int li,double base){   
   // ciclo de todos picos superiores, com exceção do primeiro      
   for(int v=1;v<N;v++){
      // cálculo do índice do pico superior seguinte
      int vi=li+1+v*2;
      // cálculo do ponto médio do segmento do ZigZag
      double mc=(PeackTrough[vi].Val+PeackTrough[vi+1].Val)/2;
      // cálculo do ponto médio do segmento anterior do ZigZag
      double mp=(PeackTrough[vi-2].Val+PeackTrough[vi-1].Val)/2;
      // o seguinte segmento deve ser maior do que o anterior
      if(!(mc>mp+base*K3)){
         return(false);
      }
   }
   return(true);
} 

Na função, é executada passagem em todos os segmentos do ZigZag, para cada um deles, é calculado o valor do ponto médio e é comparado com a média do segmento anterior. Cada segmento deve ser maior base*K3 vezes do que o anterior.

A função de validação de inclinação descendente de padrão CheckInclineDn() difere apenas numa condição:

if(!(mc<mp-base*K3)){
   return(false);
}

Para cumprir esta condição, cada segmento deve estar localizado abaixo do anterior.

Função CheckInclineHorizontally():

bool CheckInclineHorizontally(int li,double base){ 
   // ponto médio do segmento de base
   double mb=(PeackTrough[li+1].Val+PeackTrough[li+2].Val)/2;        
   for(int v=1;v<N;v++){
      // índice do seguinte ponto superior
      int vi=li+1+v*2;
      // ponto médio do segmento seguinte 
      double mc=(PeackTrough[vi].Val+PeackTrough[vi+1].Val)/2;
      // ponto médio do segmento seguinte não deve se desviar muito 
      // do ponto médio do segmento de base
      if(MathAbs(mc-mb)>base*K3){
         return(false);
      }
   }                  
   return(true);
}

Se as condições de forma e inclinação forem passarem a validação, será executado o seguinte trecho de código:

if(CompletionType==Immediately){                    // entrada imediata
   UpArrowBuffer[i]=low[i];
   UpDotBuffer[i]=PeackTrough[CurCount-1].Val+l1;
}
else{                                               // esperar o rompimento do nível
   SetLevelParameters(1);
   SetTarget(1,li);
}

Ao usar a variante de conclusão de padrão Immediately, o indicador imediatamente plota uma seta e define um ponto com o alvo, em outros casos, a função SetLevelParameters() estabelece o nível de rompimento e a função SetTarget() define a o alvo.

Função SetLevelParameters():

void SetLevelParameters(int dir){
   CurLevel.dir=dir;   
   switch(CompletionType){
      case OneLastVertex:                            // segundo um ponto
          CurLevel.v=PeackTrough[CurCount-3].Val;
      break;
      case TwoLastVertices:                          // segundo dois pontos
         CurLevel.x1=PeackTrough[CurCount-5].Bar;
         CurLevel.y1=PeackTrough[CurCount-5].Val;
         CurLevel.x2=PeackTrough[CurCount-3].Bar;
         CurLevel.y2=PeackTrough[CurCount-3].Val;
      break;
   }
} 

Na função SetLevelParameters(), para manter os parâmetros de nível, é usada a estrutura SLevelParameters:

struct SLevelParameters{
   int x1;
   double y1;
   int x2;
   double y2;       // de x1 a y2 - parâmetros de nível de inclinação
   double v;        // valor do nível horizontal
   int dir;         // direção
   double target;   // alvo
   // método para calcular o valor do nível de inclinação
   double y3(int x3){
      if(CompletionType==TwoLastVertices){
            return(y1+(x3-x1)*(y2-y1)/(x2-x1));
      }
      else{
         return(v);
      }
   }
   // método para inicializar ou redefinir parâmetros
   void Init(){
      x1=0;
      y1=0;
      x2=0;
      y2=0;
      v=0;
      dir=0;   
   }
};

A estrutura contém campos para os parâmetros da linha: x1, y1, x2, y2; campo v para o valor do nível horizontal; d para a direção do padrão; target para o alvo. O alvo pode ser definido tanto diretamente para o os níveis de preço (ao usar a variante FromVertexToVertex), quanto para eles através da magnitude a partir do nível de rompimento (para as variantes OneVertex e TwoVertices). Método y3() é usado para calcular o valor do nível de inclinação. Método Init() é usado para inicializar ou redefinir os valores.

Cumpridas todas as condições quanto à formação do padrão, é chamada a função SetLevelParameter(), nesta função, dependendo do tipo de nível selecionado (horizontal ou vertical), são definidos os parâmetros do nível de inclinação (campos x1, y1, x2, y2) ou um valor de nível horizontal - v. No método y3(), é executado o cálculo do nível usando os campos x1, y1, x2, y2 ou é retornado o valor do campo v.

No indicador, são declaradas duas variáveis do tipo SLevelParameters:

SLevelParameters CurLevel;
SLevelParameters PreLevel;

Este par de variáveis é usado tal como acontece com os pares de variáveis CurCount-PreCount e CurDir-PreDir, quer dizer, antes do cálculo inicial do indicador, são redefinidas as variáveis (parcela de código localizado no extremo inicial da função OnTick()):

int start;

if(prev_calculated==0){           // primeiro cálculo de acordo com todas as barras
   start=1;      
   CurCount=0;
   PreCount=0;
   CurDir=0;
   PreDir=0;  
   CurLevel.Init();    
   CurLevel.Init();
   LastTime=0;
}
else{                           // cálculo do novas barras e da barra que se está formando
   start=prev_calculated-1;
}

Ao realizar o cálculo de cada barra, são deslocados os valores nestas variáveis (o código está localizado no início do ciclo de indicador):

if(time[i]>LastTime){
   // primeiro cálculo da barra nova
   LastTime=time[i];
   PreCount=CurCount;
   PreDir=CurDir;
   PreLevel=CurLevel;
}
else{
   // re-cálculo da barra
   CurCount=PreCount;
   CurDir=PreDir;
   CurLevel=PreLevel;
}

A chamada da função SetTarget() executa a definição de parâmetros do alvo:

void SetTarget(int dir,int li){
   switch(Target){
      case FromVertexToVertex:
         // variante "de pico a pico"
         if(dir==1){
            CurLevel.target=PeackTrough[CurCount-1].Val+(PeackTrough[li+1].Val-PeackTrough[li].Val);
         }
         else if(dir==-1){
            CurLevel.target=PeackTrough[CurCount-1].Val-(PeackTrough[li].Val-PeackTrough[li+1].Val);
         }
      break;
      case OneVertex:
         // segundo um pico
         CurLevel.target=MathAbs(PeackTrough[li].Val-PeackTrough[li+2].Val);
      break;
      case TwoVertices:
         // segundo dois picos
         SetTwoVerticesTarget(dir,li);
      break;
   }
}

Para a variante FromVertexToVertex, é executado o cálculo do valor de preço. Para a variante OneVertex, ao campo target é atribuída a magnitude do curso do preço desde o nível de rompimento até o alvo. Cálculo para a variante SetTwoVerticesTarget é realizado na função  SetTwoVerticesTarget():

void SetTwoVerticesTarget(int dir,int li){
   // coordenadas da linha inicial (longa)
   // padrão - deste o fundo até o pico  
   double x11=PeackTrough[li].Bar;
   double y11=PeackTrough[li].Val;
   double x12=PeackTrough[li+1].Bar;
   double y12=PeackTrough[li+1].Val;
   // coordenadas de linha segundo dois fundos para compra
   // ou de acordo com dois picos para venda
   double x21=PeackTrough[li+2].Bar;
   double y21=PeackTrough[li+2].Val;
   double x22=PeackTrough[li+4].Bar;
   double y22=PeackTrough[li+4].Val;
   // valor no ponto de cruzamento das linhas
   double t=TwoLinesCrossY(x11,y11,x12,y12,x21,y21,x22,y22);
   // definição da valo do alvo em relação à direção
   if(dir==1){
      CurLevel.target=t-PeackTrough[li].Val;
   }
   else if(dir==-1){
      CurLevel.target=PeackTrough[li].Val-t;         
   }
}

Para as variantes SetTwoVerticesTarget, o campo target recebe o valor do curso do preço desde o nível de rompimento até o alvo, como para a variante OneVertex.

Examinemos como é realizado o acompanhamento do preço e do nível (CompletionType не равно Immediately):

// usa-se o nível
if(CompletionType!=Immediately){
   // alteração do ZigZag
   if(PeackTrough[CurCount-1].Bar==i){
      if(CurLevel.dir==1){                // esperados o rompimento para cima
         // na variável cl, obtemos o valor do nível
         double cl=CurLevel.y3(i); 
         // ZigZag rompendo o nível
         if(PeackTrough[CurCount-1].Val>cl){
            // posicionamento da seta para cima
            UpArrowBuffer[i]=low[i];
            // definição do ponto-alvo
            if(Target==FromVertexToVertex){
               // no campo target, preço
               UpDotBuffer[i]=CurLevel.target;                        
            }
            else{
               // no campo target, distância a partir do nível
               UpDotBuffer[i]=cl+CurLevel.target;
            }
            // redefinição do campo dir, para parar o acompanhamento do nível
            CurLevel.dir=0;
         }
      }
      else if(CurLevel.dir==-1){         // esperamos rompimento para baixo
         // na variável cl, obtemos o valor do nível
         double cl=CurLevel.y3(i);
         // ZigZag rompendo o nível
         if(PeackTrough[CurCount-1].Val<cl){
            // posicionamento da seta para baixo
            DnArrowBuffer[i]=low[i];
            // definição do ponto-alvo
            if(Target==FromVertexToVertex){
               // no campo target, preço
               DnDotBuffer[i]=CurLevel.target;
            }
            else{                     
               // no campo target, distância a partir do nível
               DnDotBuffer[i]=cl-CurLevel.target;
            }
            // redefinição do campo dir, para parar o acompanhamento do nível
            CurLevel.dir=0;
         }         
      }         
   }
} 

Esta validação é realizada a cada mudança do ZigZag. Todos os picos do ZigZag são armazenados na matriz PeackTrough, a alteração do ZigZag é determinada pelos índices correspondentes do último ponto do ZigZag para o índice da barra atual:

if(PeackTrough[CurCount-1].Bar==i){

Com ajuda do método y3() é calculado o valor atual do nível:

double cl=CurLevel.y3(i); 

Verifica-se es o último segmento do ZigZag rompeu este nível:

if(PeackTrough[CurCount-1].Val>cl){

Se o nível for rompido, o indicador desenhará uma seta e posicionará um ponto-alvo. O campo target pode conter o valor de preço do alvo, neste caso, o valor é usado diretamente, ou pode conter a magnitude do curso do preço antes do alvo, neste caso, o valor do alvo é calculado levando em conta o valor atual do nível: 

if(Target==FromVertexToVertex){
   UpDotBuffer[i]=CurLevel.target;                        
}
else{
   UpDotBuffer[i]=cl+CurLevel.target;
}

No final do campo, dir é zerado, a fim de não continuar acompanhando o preço até surgir o seguinte padrão:

CurLevel.dir=0;

A criação do indicador está concluída, alguns fragmentos de seu trabalho são mostrados na fig. 15.


Fig. 15. Alguns sinais do indicador iHorizontalFormation

Além disso, a função de alerta foi adicionada ao indicador. Você pode encontrar o indicador pronto para ser usado no apêndice do artigo, o nome do arquivo é iHorizontalFormation.


Indicador universal para pesquisa da Bandeira, Flâmula e Cunha

Agora criamos um indicador para encontrar os padrões da segunda categoria. Eles formam sua forma, preenchendo com barras a área da figura. O padrão começa com um forte movimento do preço, neste caso, a partir de uma barra longa. Para determinar o barras longas, usamos o indicador ATR com um período grande. A barras será considerada longa se o tamanho da sombra exceder o valor do ATR multiplicado pelo coeficiente. Por isso, no indicador, são necessários parâmetros externos para o período do ATR e o coeficiente:

input int                  ATRPeriod            =  50;
input double               K1                   =  3;

Declaramos a variável global do indicador para o identificador do ATR:

int h;

Na função OnInit(), carregamos o indicadorATR e obtemos o valor do identificador:

h=iATR(Symbol(),Period(),ATRPeriod);
if(h==INVALID_HANDLE){
   Alert("Error load indicator");
   return(INIT_FAILED);
}

No valor principal ciclo de indicador, obtemos o valor do ATR:

double atr[1];
if(CopyBuffer(h,0,rates_total-i-1,1,atr)==-1){
   return(0);
}

Usando o valor obtido do ATR, verificamos o valor da barra. Se a sombra da barra exceder o limite definido pelo coeficiente, determinaremos a direção do movimento pretendido do preço. A direção é determinada pela cor da barra (a partir dos valores open e close). Se o preço close estiver acima do preço open, será esperada a continuação do movimento ascendente do preço. Se os preços close estiver abaixo do preço open, será esperado um movimento descendente:

if(high[i]-low[i]>atr[0]*K1){    // barra longa
   if(close[i]>open[i]){         // barra para cima
      Cur.Whait=1;
      Cur.Count=0;
      Cur.Bar=i;
   }
   else if(close[i]<open[i]){    // barra para baixo
      Cur.Whait=-1;   
      Cur.Count=0;
      Cur.Bar=i;
   }
}

Cumpridas as condições do tamanho da barra e sua direção para os campos da estrutura Cur, são estabelecidos os valores correspondentes, isto é: no campo Whait, é definida a direção pretendida (1 — para cima, -1 — para baixo), é executada a redefinição do campo Count. A ele é atribuído o valor de 0, este campo pode ser usado para contagem das barras do padrão. No campo Bar, é armazenado o índice da barra inicial (longa) do padrão.

Examinemos em detalhes a estrutura Cur. A estrutura tem três campos e o método Init() para redefinir rapidamente todos os campos:

struct SCurPre{
   int Whait;
   int Count;
   int Bar;
   void Init(){
      Whait=0;
      Count=0;
      Bar=0;
   }
};

No início da função OnTick(), são declaradas duas variáveis estáticas de tipo de dados e a variável de tipo datetime:

static datetime LastTime=0;   
static SCurPre Cur;          
static SCurPre Pre;

A seguir, é executado o cálculo do índice da primeira barra, a partir da qual começa o cálculo do indicador e é executada a inicialização das variáveis Cur e Pre:

int start=0;

if(prev_calculated==0){           // primeiro cálculo do indicador
   
   start=1;      
   
   Cur.Init();
   Pre.Init();             
     
   LastTime=0;      
}
else{                             // cálculo de novas barras da da barra que está sendo formada
   start=prev_calculated-1;
}

No início do ciclo principal de indicador, são deslocados os valores nas variáveis Cur e Pre:

if(time[i]>LastTime){       // primeiro cálculo de barra
   LastTime=time[i];
   Pre=Cur;              
}
else{                      // re-cálculo de barra
   Cur=Pre;
}  

Esta técnica com variáveis é considerada em detalhes no artigo "Ondas de Wolfe" (variáveis PreCount e СurCount). Neste artigo, ele é usado durante a criação do indicador iHorizontalFormation (variáveis com prefixos Cur e Pre).

Se a variável Cur.Count não for zero, o indicador estará em modo de esclarecimento de condições de definição de padrão. Ao acontecer isto, é realizada a contagem de barras, que compõem o padrão, portanto, aumenta a variável CurCount. A primeira barra depois de uma barra longa é ignorada, a partir da terceira barra são realizadas validações de refinamento:

if(Cur.Whait!=0){
   Cur.Count++;            // contagem de barras
   if(Cur.Count>=3){
      // validações de refinamento

   }
}

A dimensão da sobreposição das barras será o principal indicador de validações de refinamento (fig. 16).

Fig. 16. Sobreposição de duas barras L é definida como a diferença entre
o preço mínimo high o o preço máximo low

A dimensão da sobreposição é calculada utilizando duas barras como a diferença entre o menor máximo e o maior mínimo: 

Overlapping=MathMin(high[i],high[i-1])-MathMax(low[i],low[i-1]);

Sobreposição com a barra inicial não é verificada, por isto a partir da terceira barra começam validações sobre a sobreposição, mas não a partir da segunda.

A dimensão da sobreposição das duas barras deve exceder um determinado limiar. Se você definir esse valor em pontos, o trabalho do indicador dependerá do período, já que o valor do parâmetro será muito diferente para diferentes períodos. Para não depender do período, definimos o valor de base das barras a serem validada, ou seja, usamos a sombra maior entre as duas barras:

double PreSize=MathMax(high[i-1]-low[i-1],high[i]-low[i]);

Validamos a dimensão da sobreposição das barras:

if(!(Overlapping>=PreSize*MinOverlapping))

Se duas barras sucessivas não estão sobrepostas, termina a série de barras sobrepostas contíguas. Neste caso, verificaremos o número de barras na série:

if(Cur.Count-2>=MinCount){
   // validações de refinamento
}
Cur.Whait=0;

Se o número de barras numa série for maior do que o valor da variável MinCount, serão realizadas validações de refinamento adicionais, do contrário, para a espera pela formação do padrão através da zeragem da variável CurCount. No código acima, ao validar a condição, subtrai-se 2 da variável CurCount, esta é a primeira barra longa e a barra final, onde as condições de sobreposição não são satisfeitas.

As variáveis MinOverlapping e MinCount são variáveis externas do indicador:

input double               MinOverlapping       =  0.4;
input int                  MinCount             =  5;

Após cumprida a condição do número de barras sobrepostas, procedemos à validação das condições de refinamento, isto é: forma de padrão e de inclinação. Para fazer isso, primeiro são definidos os parâmetros da série de barras sobrepostas detectada:  

double AverSize,AverBias,AverSizeDif;
PatternParameters(high,low,i-1,Cur.Count-2,AverSize,AverBias,AverSizeDif);

Os parâmetros são definidos na função PatternParameters(), e são retornados segundo os links nas variáveis AverSize, AverBias, AverSizeDif. Na variável AverSize, é retornado o tamanho médio da barra, na variável AverBias, é retornado o deslocamento médio do centro das barras, em AverSizeDif, a diferença média dos tamanhos de duas barras contíguas. Para entender exatamente como calcular estes parâmetros, estudaremos em detalhes a função PatternParameters():

void PatternParameters( const double & high[],
                        const double & low[],
                        int i,
                        int CurCnt,
                        double & AverSize,
                        double & AverBias,
                        double & AverSizeDif
){
            
   // tamanho médio da bara            
   AverSize=high[i-CurCnt]-low[i-CurCnt];
   // deslocamento médio da barra
   AverBias=0;
   // diferença média dos tamanhos de duas barras contíguas
   AverSizeDif=0;
   
   for(int k=i-CurCnt+1;k<i;k++){      // segundo todas as barras da série, com exceção da primeira
      // tamanho médio
      AverSize+=high[k]-low[k];
      // deslocamento médio
      double mc=(high[k]+low[k])/2;
      double mp=(high[k-1]+low[k-1])/2;
      AverBias+=(mc-mp);
      // tamanho médio de tamanhos
      double sc=(high[k]-low[k]);
      double sp=(high[k-1]-low[k-1]);
      AverSizeDif+=(sc-sp);               
      
   }
   
   // divisão de somas por uma quantidade
   AverSize/=CurCnt;
   AverBias/=(CurCnt-1);
   AverSizeDif/=(CurCnt-1); 
} 

Para a função são encaminhadas duas matrizes: high e low, índice da barra, em que a série de barras sobrepostas termina, comprimento da série de barras e três variáveis para os valores retornados. A contagem de indicadores é executada no ciclo for, mas, como os indicadores AverBias e AverDiff são calculados por duas barras contíguas, a primeira barra da série é ignorada:

for(int k=i-CurCnt+1;k<i;k++)

Por isso, antes do ciclo, as variáveis AverBias e AverDiff são zeradas, enquanto à variável AverSize é atribuído o valor calculado segundo a barra ignorada no ciclo.

No ciclo, à variável AverSize são adicionados os tamanhos das barras:

AverSize+=high[k]-low[k];

Para o indicador AverBias (deslocamento), são calculados os valores médios das barras e sua diferença, a diferença obtida é somada:

double mc=(high[k]+low[k])/2;
double mp=(high[k-1]+low[k-1])/2;
AverBias+=(mc-mp);

Para o indicador AverSizeDif, são calculados os tamanhos de duas barras contíguas e sua diferença, a diferença obtida é somada:

double sc=(high[k]-low[k]);
double sp=(high[k-1]-low[k-1]);
AverSizeDif+=(sc-sp);    

Após o ciclo, as somas são divididas pelo número de valores somados:

AverSize/=CurCnt;
AverBias/=(CurCnt-1);
AverSizeDif/=(CurCnt-1); 

Após o cálculo dos parâmetros, é validada a forma do padrão. Esta validação não depende da direção do movimento pretendido do preço. Para exame da forma, são usadas três funções: FormTapered() — forma contrativa (Flâmula), FormHorizontal() — forma retangular (Bandeira), FormExpanding() — forma expansiva (Cunha):

if(   FormTapered(AverSizeDif,AverSize) ||
      FormHorizontal(AverSizeDif,AverSize) ||
      FormExpanding(AverSizeDif,AverSize)
){ 
   // validação da direção
}

Nas configuração do indicador iHorizontalFormation, era possível selecionar uma de três formas, aqui todas as três formas são ativadas de forma independente. Isto é devido a um exclusivo cumprimento de condições e também a uns sinais de negociação menos frequentes. Para ativar/desativar cada uma das formas nos parâmetros do indicador, foram acrescentadas três variáveis. Além disso, para cada uma das formas, na janela Propriedades, foram adicionadas de acordo com coeficiente:

input bool                 FormTapered          =  true;
input double               FormTaperedK         =  0.05;
input bool                 FormRectangular      =  true;
input double               FormRectangularK     =  0.33;
input bool                 FormExpanding        =  true;
input double               FormExpandingK       =  0.05;

 Consideremos a função de validação de forma. Função FormTapered():

bool FormTapered(double AverDif, double AverSize){
   return(FormTapered && AverDif<-FormTaperedK*AverSize);
}

Se o diferença média dos tamanhos das barras for menor do que o liminar negativo, os tamanhos das barras serão reduzidos, o que corresponde à forma aguçada do padrão:

Função FormHorizontal():

bool FormHorizontal(double AverDif, double AverSize){
   return(FormRectangular && MathAbs(AverDif)<FormRectangularK*AverSize);
}

Se o valor absoluto dos tamanhos das barras for menor do que o valor limite, todas as barras serão aproximadamente do mesmo tamanho, o que corresponde à forma retangular:

Função FormExpanding():

bool FormExpanding(double AverDif, double AverSize){
   return(FormExpanding && AverDif>FormExpandingK*AverSize);
}

Nesta função, ao contrário do padrão de forma aguçada, a diferença média dos tamanhos das barras deve exceder o valor limite, o que corresponde às barras que estão aumentando e à forma que está se expandido.

Validada a forma, é realizada validação da inclinação do padrão. Esta validação dependente da direção pretendida do movimento do preço. Para a direção ascendente, é usada a função CheckInclineForBuy(), para a direção descendente, CheckInclineForSell():

if(Cur.Whait==1){
   if(CheckInclineForBuy(AverBias/AverSize)){
      // validações adicionais para a direção ascendente

   }
}
else if(Cur.Whait==-1){
   if(CheckInclineForSell(AverBias/AverSize)){   
      // validações adicionais para a direção descendente

   }
}

Tanto as variantes de validação de inclinação quanto as de validação de forma são habilitadas separadamente. Para fazer isso, existem as variáveis ​​correspondentes na janela de propriedades. Além disso, para cada variante, a inclinação na janela de propriedades tem seu próprio coeficiente:

input bool                 InclineAlong         =  true;
input double               InclineAlongK        =  0.1;
input bool                 InclineHorizontal    =  true;
input double               InclineHorizontalK   =  0.1;
input bool                 InclineAgainst       =  true;
input double               InclineAgainstK      =  0.1;

Função CheckInclineForBuy():

bool CheckInclineForBuy(double Val){
   return(  (InclineAlong && Val>InclineAlongK) || 
            (InclineHorizontal && MathAbs(Val)<InclineHorizontalK) || 
            (InclineAgainst && Val<-InclineAgainstK)
   );
}   

À função é encaminhado o valor do deslocamento relativo das barras AverBias/AverSize. Se ele estiver acima do limiar negativo, o padrão será ascendente, se estiver abaixo do limiar negativo, a inclinação será descendente. Se o valor estiver dentro do valor limite sem levar em conta o sinal, o padrão será horizontal:

bool CheckInclineForBuy(double Val){
   return(  (InclineAlong && Val>InclineAlongK) || 
            (InclineHorizontal && MathAbs(Val)<InclineHorizontalK) || 
            (InclineAgainst && Val<-InclineAgainstK)
   );
}   

Da mesma forma, descendente:

bool CheckInclineForSell(double Val){
   return(  (InclineAlong && Val<-InclineAlongK) || 
            (InclineHorizontal && MathAbs(Val)<InclineHorizontalK) || 
            (InclineAgainst && Val>InclineAgainstK)
   );
}  

Só agora a direção do curso corresponde à inclinação descendente, enquanto a direção contra-movimento corresponde à inclinação ascendente.

Resta executar uma validação final, isto é, a direção da barra final. Pode haver duas variantes da última validação: a barra final é dirigida quer na direção do padrão quer contra ela. Para ativar as variantes de validação final, na janela Propriedades foram adicionados os parâmetros:

input bool                 EnterAlong           =  true;
input bool                 EnterAgainst         =  true;

Para a direção ascendente, a validação é a seguinte:

if((EnterAlong && close[i]>open[i]) || (EnterAgainst && close[i]<open[i])){
   Label1Buffer[i]=low[i];
   Label3Buffer[i]=close[i]+(high[Cur.Bar]-low[Cur.Bar]);
}

Se a variante selecionada é EnterAlong e a barra aponta para cima ou selecionada a variante  EnterAgainst e a barra aponta para baixo, o indicador plota uma seta e um ponto-alvo. O alvo está a uma distância igual à magnitude da barra inicial.

Da mesma forma, descendente:

if((EnterAlong && close[i]<open[i]) || (EnterAgainst && close[i]>open[i])){
   Label2Buffer[i]=high[i];                  
   Label4Buffer[i]=close[i]-(high[Cur.Bar]-low[Cur.Bar]);
}

Neste ponto, pode ser considerado que o indicador está completamente pronto. O indicador com a função de alerta pode ser encontrado no apêndice do artigo, o nome do arquivo é iFlag. 


Indicador-testador

A maneira mais fácil e mais conveniente para testar a eficácia do indicador abrange o uso de um EA e o testador de estratégias, no terminal. No artigo "Ondas de Wolfe", foi criado um Expert Advisor simples, que após pequenas modificações pode ser utilizado para testar indicadores criados neste artigo. A indexação dos buffers dos indicadores iHorizontalFormation e iFlag corresponde à indexação dos buffers do indicador iWolfeWaves, por isso basta mudar os parâmetros externos do EA e chamada da função iCustom().

Há outra maneira muito interessante de testar indicadores, ela permite avaliar a eficácia durante o trabalho, estamos falando do indicador-testador. Num indicador adicional, é modelada a negociação de acordo com as setas do indicador principal, enquanto que no gráfico são exibidas as linhas de saldo da conta e capital liquido.

A abordagem mais simples e óbvia na criação do indicador-testador consiste no uso da função iCustom(), no entanto essa abordagem tem desvantagens significativas: o indicador principal, que desenha as setas, é exibido no gráfico de preço, enquanto o indicador-testador, que deve plotar a linha de capital liquido e saldo, deve ser exibido numa subjanela. Assim, é necessário anexar ao gráfico os dois indicadores, bem como definir os mesmos parâmetros para cada um deles. Adicionalmente, doravante se você quiser alterar os parâmetros, eles devem ser trocados nos dois indicadores, o que nem sempre é conveniente.

Outra opção é fazer com que o indicador-testador plote setas através de objetos gráficos, no gráfico.

A terceira opção é usar a função ChartIndicatorAdd(). Esta função permite anexar programaticamente um outro indicador ao gráfico. Neste caso, a cada mudança de parâmetros do indicador principal, será necessário encontrar no gráfico o indicador-testador adicional, removê-lo e anexar com novos parâmetros. Esta é uma opção aceitável e até mesmo conveniente.

Mas, há uma quarta opção, não menos confortável do que a terceira, mas mais simples em termos de implementação. Além disso, é possível criar um indicador-testador universal, que pode ser usado como o indicador iHorizontalFormation e como o indicador iFlag, após pequenas modificações.

A modificação do iHorizontalFormation e do iFlag consiste na criação de uma variável externa ID: 

input int                  ID             =  1;

Em seguida, na função OnInit, usando o valor desta variável, ao indicador é atribuído um nome curto:

string ShortName=MQLInfoString(MQL_PROGRAM_NAME)+"-"+IntegerToString(ID);
IndicatorSetString(INDICATOR_SHORTNAME,ShortName);

O nome curto é o nome do arquivo do indicador, ao qual é adicionado o sinal "-" e o valor da variável ID. De acordo com este nome, o indicador-testador encontrará o indicador principal (obterá seu identificador).

O indicador iHorizontalFormation funciona na base do ZigZag, que pode ser calculado de acordo com os preços high-low, bem como segundo os preços close e outros indicadores. Ao calcular conforme os preços high-low, a seta já não desaparecerá do gráfico. Que dizer, quando você usa o indicador para negociação, você pode monitorá-lo na barra emergente. Em outros casos, durante o cálculo do Zigzag segundo os preços close e outros indicadores, é necessário monitorar a formação da seta, na barra. Assim, o indicador-testador terá de alguma forma que notificar em qual barra é necessário monitorar a formação da barra.

Os indicadores iHorizontalFormation e iFlag desenham pontos alvo, que se podem usar para posicionamento do Take-Profit. No entanto, para o indicador iHorizontalFormation, isso só é possível quando o ZigZag é calculado de acordo com o preço. Assim, o indicador-testador deve especificar se tem que utilizar pontos-alvo ou parâmetros adicionais de Take-Profit e Stop-Loss. A primeira ideia que surge é usar os dados da variável global para enviar dados. No entanto, o terminal MetaTrader 5 tem uma característica particular: ao mudar os parâmetros do indicador, é carregada uma nova instância do indicador com um novo identificador, mas a instância anterior não é removida da memória imediatamente. Portanto, se forem retornados os parâmetros externos do indicador, não serão executados o novo carregamento e o cálculo do indicador e, portanto, não será executada a função OnInit() e não será zerada a variável prev_calculated. Assim, as variáveis ​​globais não recebem o novo valor. 

Os parâmetros que necessita o indicador-testador são enviados através do buffer de indicador. Para fazer isto, basta apenas um elemento do buffer. Usamos o que está disponível, isto é, o primeiro o elemento que está no extremo direito. Tudo que se precisa é transferir dois valores. Um deles determina se é preciso acompanhar o indicador principal na barra formada, ou se pode ser usada a que se está formando, enquanto que o outro elemento estabelece se é possível utilizar o ponto-alvo para o Take-Profit da posição. Na função OnCalculate() quando prev_calculate=0 é adicionado o código:

int ForTester=0;       // variável para o valor
if(!(SrcSelect==Src_HighLow)){
   // o trabalho da barra formada
   ForTester+=10;
}   
if(!(SrcSelect==Src_HighLow || SrcSelect==Src_Close)){
   // pode usar o ponto-alvo
   ForTester+=1;
}     
UpArrowBuffer[0]=ForTester;  

Depois de um elemento da matriz, é necessário enviar dois números não superiores a 10, por isso um deles é multiplicado por 10, enquanto a ele é adicionado um segundo número. Como os números enviados podem ter valores de 0 ou 1, pode ser utilizado um número com base 2, mas, o volume dos dados transferidos é pequeno, de modo que não é necessário poupar bytes.

É necessário realizar uma modificação com o indicador iFlag. Na função OnInit():

string ShortName=MQLInfoString(MQL_PROGRAM_NAME)+"-"+IntegerToString(ID);
IndicatorSetString(INDICATOR_SHORTNAME,ShortName);

Na função OnCalculate():

Label1Buffer[0]=11;

O indicador iFlag pode ser sempre visualizado na barra formada, e sempre pode usar o o ponto-alvo. Portanto, é atribuído o valor 11 sem cálculo.

Quando você usa a barra gerada, é óbvio que a posição de abertura é realizada na abertura de uma nova barra, mas, na entrada, na barra que se está formando, a entrada é desconhecida. Portanto o indicador iHorizontalFormation foi submetido a mais uma modificação, isto é, foi adicionado outro buffer, que desenha pontos nas barras com setas. Este buffer especifica o nível de abertura da posição.

Agora, vamos nos ocupar exclusivamente do indicador-testador. Criamos um novo indicador chamado iTester, adicionamos os parâmetros externos:

input int                  ID             =  1;
input double               StopLoss_K     =  1;
input bool                 FixedSLTP      =  false;
input int                  StopLoss       =  50;
input int                  TakeProfit     =  50;

Aqui:

  • ID — identificador do indicador principal
  • StopLoss_K — coeficiente do cálculo do Stop-Loss em relação ao Take-Profit ao utilizar os pontos-alvo
  • FixedSLTP — utilizar as variáveis StopLoss eTakeProfit ou o ponto-alvo e a variável StopLoss_K.

Vai ser muito conveniente se o indicador-testador - no canto superior esquerdo - mostrar não só seu nome, mas também o nome do indicador, cujas setas ele usa para trabalhar. Mas, enquanto o indicador de teste ainda não estiver anexado ao gráfico, será exibido o nome do indicador-testador em si. Para fazer isso, declaramos uma variável global:

string IndName;

N função OnInit(), atribuímos a ela o nome do indicador-testador:

IndName=MQLInfoString(MQL_PROGRAM_NAME);

A informação sobre cada posição aberta pelo indicador-testador é armazenada numa matriz de estruturas SPos, a variável PosCnt é utilizada para registrar o número de posições abertas:

struct SPos{
   int dir;
   double price;
   double sl;
   double tp;
   datetime time; 
};
SPos Pos[];
int PosCnt;

A transação deve ser adicionada à matriz uma só vez, para isso, é utilizada a variável:

datetime LastPosTime;

Ao adicionar uma posição à matriz, é executada a validação do tempo - desde a variável LastPosTime - e da hora da barra em que a posição é aberta. Se a posição for adicionada, à variável LastPosTime será atribuído um novo tempo. Se o tempo da barra for igual ao tempo da LastPosTime, a posição já estará aberta.

Para fechar as posições, ou melhor, para contagem de seus lucros, são necessárias dois variáveis adicionais:

int Closed;
datetime CloseTime;

À variável Closed é atribuído o lucro das posições fechadas numa barra, enquanto à variável CloseTime é atribuído o valor dessa barra. Abaixo consideraremos em detalhes como isso funciona.

Todas as variáveis auxiliares e a função OnInit() já foram consideradas, agora passamos para a função OnCalculate(). Primeiro, são declaradas algumas variáveis ​​auxiliares:

string name;
static int last_handle=-1;
static int shift=0;
static int use_target=0;
int handle=-1;     
int start=2;  

Descrevemos as variáveis:

  • name será usada para obter o nome do indicador através da função ChartIndicatorName();
  • variável estática last_handle será utilizada para armazenar o identificador do invariável a ser testado;
  • shift e use_target estáticos serão empregados para parâmetros, transferidos desde o indicador de teste;
  • handle é usado para obter o identificador através da função ChartIndicatorGet();
  • start é utilizado para início do cálculo do indicador.

Analisamos o código para encontrar indicador a ser testado. Primeiro, determinamos o número de indicadores anexados ao gráfico do preço:

int it=ChartIndicatorsTotal(0,0);

Passamos por eles no ciclo:

for(int i=0;i<it;i++){        // todos os indicadores do gráfico
   // obtenção dos nomes do seguinte indicador
   name=ChartIndicatorName(0,0,i);
   // pesquisa da substring "-"
   int p=StringFindRev(name,"-");
   if(p!=-1){
      // substring encontrada, é verificado o valor do identificador
      if(StringSubstr(name,p+1,StringLen(name)-p-1)==IntegerToString(ID)){
         // identificador corresponde, obtemos o identificador
         handle=ChartIndicatorGet(0,0,name);
      }
   }
} 

Consideramos em pormenor a porção de código mencionado acima. À variável name é atribuído o nome do indicador, obtido pela função ChartIndicatorName(), por sua vez, esta função retorna o nome do indicador pelo seu índice. O nome resultante é examinado para saber se corresponde ao identificador. Para este fim, é executada a busca da última ocorrência de uma substring "-". Se o sino "-" for encontrado, será extraída a porção da linha após ele. Se ele corresponder ao identificador, à variável handle será atribuído o valor do identificador, obtido pela função ChartIndicatorGet(), esta função retorna um identificador para o nome do indicador. 

Após receber o identificador, ele deve ser comparado com o identificador anterior a partir da variável last_handle (variável estática, isto é, retém seu valor após concluir o trabalho da função OnCalculate()):

if(handle!=last_handle){
   if(handle==-1){                    // não há identificador
      // definição do nome inicial
      IndicatorSetString(INDICATOR_SHORTNAME,IndName);
      ChartRedraw(0);
      return(0);
   }
   // verifica se foi concluído o cálculo do indicador a ser testado
   int bc=BarsCalculated(handle);
   if(bc<=0)return(0);                // se não estiver concluído, o trabalho da função será interrompido
   // cópia da dados com parâmetros de teste
   double sh[1];
   if(CopyBuffer(handle,0,rates_total-1,1,sh)==-1){
      // se a cópia não for bem-sucedida, o trabalho da função será interrompido até
      // o seguinte tick
      return(0);
   }
   // check-out de parâmetros separados
   shift=((int)sh[0])/10;              // barra formada o que se está formando
   use_target=((int)sh[0])%10;         // é possível usar ou não o ponto-alvo
   last_handle=handle;                 // armazenamento do valor do identificador
   // definição de nome do indicador
   IndicatorSetString(INDICATOR_SHORTNAME,name);
   ChartRedraw(0);
}
else if(prev_calculated!=0){
   // se não houver um novo identificador, será executado o cálculo apenas das barras novas 
   // e da barra que se está formando
   start=prev_calculated-1;
}

O fato do valor da variável handle não ser igual ao valor da variável last_handle indica que aconteceu algum tipo de alterações no indicador testado. Talvez ele acaba de ser anexado ao gráfico ou seus parâmetros foram modificados. Possivelmente foi removido do gráfico. Quando o indicador é removido do gráfico, o valor da variável handle será igual a -1, ao acontecer isso, ao indicador-testador é atribuído o nome padrão e é concluído o trabalho da função OnCalculate(). Quando a variável hadle possui o valor real de identificador, é executada a obtenção dos parâmetros de teste, isto é: a barra formada/que-se-está-formando e a permissão para usar o ponto-alvo. Na seguinte execução da função OnCalculate(), as variáveis handle e last_handle são iguais, é executado o cálculo usual da função start, ou seja, da barra inicial, a partir da qual é executado o cálculo do indicador.

A variável start por padrão possui dois valores 2. Se for necessário executar o re-cálculo do indicador (isso é necessário quando se altera o identificador ou quando há um valor prev_calculated igual a 0), neste caso, será necessário redefinir algumas variáveis ​​adicionais:

if(start==2){
   PosCnt=0;
   BalanceBuffer[1]=0;
   EquityBuffer[1]=0;
   LastPosTime=0;
   Closed=0;
   CloseTime=0;      
}

Ao levar a cabo a redefinição, são zerados: a quantidade de posições abertas — PosCnt, os primeiros elementos dos buffers de indicador para o saldo e o capital liquido — BalanceBuffer[1] e EquityBuffer[1], tempo da última posição — LastPosTime, lucro das barras fechadas numa barra — Closed e tempo da barra de fechamento — ClosedTime.

Agora consideramos o ciclo de indicador principal. Primeiro, é mostrado todo seu código com comentários, em seguida, ele é examinado por linhas:

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

   // transferência dos valores já conhecidos de saldo e capital liquido
   BalanceBuffer[i]=BalanceBuffer[i-1];
   EquityBuffer[i]=EquityBuffer[i-1];

   if(CloseTime!=time[i]){ 
      // início do cálculo da barra nova
      Closed=0; // zeragem da variável para o lucro
      CloseTime=time[i];          
   }

   // obtenção dos dados do indicador testado
   double buy[1],sell[1],buy_target[1],sell_target[1],enter[1];
   int ind=rates_total-i-1+shift;
   if(CopyBuffer(last_handle,0,ind,1,buy)==-1 || 
      CopyBuffer(last_handle,1,ind,1,sell)==-1 ||
      CopyBuffer(last_handle,2,ind,1,buy_target)==-1 || 
      CopyBuffer(last_handle,3,ind,1,sell_target)==-1        
   ){
      return(0);
   }
  
   if(shift==0){
      // se o teste é na barra que se está formando, obtemos o preço 
      // de abertura a partir do buffer adicional do indicador testado       
      if(CopyBuffer(last_handle,4,ind,1,enter)==-1){
         return(0);
      } 
   }
   else{
      // quando se realiza o teste na barra formada, é usado o preço 
      // de abertura da barra
      enter[0]=open[i];
   }

   // seta de compra
   if(buy[0]!=EMPTY_VALUE){
      AddPos(1,enter[0],buy_target[0],spread[i],time[i],use_target);      
   }
   // seta de venda
   if(sell[0]!=EMPTY_VALUE){
      AddPos(-1,enter[0],sell_target[0],spread[i],time[i],use_target);       
   }

   // validação das posições no que diz respeito à necessidade de fechamento
   CheckClose(i,high,low,close,spread);

   // linha do saldo
   BalanceBuffer[i]+=Closed;
   
   // linha do capital liquido
   EquityBuffer[i]=BalanceBuffer[i]+SolveEquity(i,close,spread);

}

O valor do saldo é composto do saldo e do lucro - previamente conhecidos - das posições fechadas, por isso o valor do saldo - anteriormente conhecido - é transferido a partir do elemento anterior do buffer:

// transferência dos valores já conhecidos de saldo e capital liquido
BalanceBuffer[i]=BalanceBuffer[i-1];

No primeiro cálculo de cada barra, é executada a zeragem da variável para o luro das posições a serem fechadas nesta barra:

if(CloseTime!=time[i]){ 
   Closed=0;
   CloseTime=time[i];          
}

São copiados os dados do indicador testado:

// obtenção dos dados do indicador testado
double buy[1],sell[1],buy_target[1],sell_target[1],enter[1];
int ind=rates_total-i-1+shift;
if(CopyBuffer(last_handle,0,ind,1,buy)==-1 || 
   CopyBuffer(last_handle,1,ind,1,sell)==-1 ||
   CopyBuffer(last_handle,2,ind,1,buy_target)==-1 || 
   CopyBuffer(last_handle,3,ind,1,sell_target)==-1        
){
   return(0);
}

Nas matrizes buy e sell, são colados os dados dos buffers com setas, enquanto que, nas matrizes buy_target e sell_target, são colados os dados dos pontos-alvo. Antes da cópia, é calculado o índice da barra ind levando em conta a variável shift.

Dependendo do valor de shift, é executada a cópia do buffer auxiliar ou é usado o preço de abertura da barra:

if(shift==0){
   // se o teste é na barra que se está formando, obtemos o preço 
   // de abertura a partir do buffer adicional do indicador testado       
   if(CopyBuffer(last_handle,4,ind,1,enter)==-1){
      return(0);
   } 
}
else{
   // quando se realiza o teste na barra formada, é usado o preço 
   // de abertura da barra
   enter[0]=open[i];
}

Se, na barra calculada, são mostradas setas, é executada a abertura de posição através da chamada da função AddPos():

// seta de compra
if(buy[0]!=EMPTY_VALUE){
   AddPos(1,enter[0],buy_target[0],spread[i],time[i],use_target);      
}
// seta de venda
if(sell[0]!=EMPTY_VALUE){
   AddPos(-1,enter[0],sell_target[0],spread[i],time[i],use_target);       
}

Na função CheckClose(), é executada a validação para o fechamento da posição. Se for executado o fechamento, o peço obtido será localizado na variável Closed:

// validação das posições no que diz respeito à necessidade de fechamento
CheckClose(i,high,low,close,spread);

O lucro fechado desde a variável Closed é adicionado ao saldo:

// linha do saldo
BalanceBuffer[i]+=Closed;

O capital liquido é formado a partir do saldo e da posição não fechada, calculada pela função SolveEquity():

EquityBuffer[i]=BalanceBuffer[i]+SolveEquity(i,close,spread);

Consideremos as funções  AddPos(), CheckClose(), SolveEquity(), abaixo é apresentado o código de cada função com comentários detalhados.  

Função AddPos():

void AddPos(int dir, double price,double target,int spread,datetime time,bool use_target){

   if(time<=LastPosTime){
      // posição com hora time já adicionada
      return;
   }
   
   // não há espaço live na matriz
   if(PosCnt>=ArraySize(Pos)){
      // o tamanho da matriz é aumentado - pelo bloco - 32 elementos
      ArrayResize(Pos,ArraySize(Pos)+32);
   }
   
   // é armazenada a direção da posição
   Pos[PosCnt].dir=dir;
   // tempo de abertura da posição
   Pos[PosCnt].time=time;
   // preço de abertura da posição
   if(dir==1){
      // para compra, preço ask
      Pos[PosCnt].price=price+Point()*spread;  
   }
   else{
      // para venda, preço Bid
      Pos[PosCnt].price=price;  
   }

   // é calculado o Stop-Loss e o Take-Profit
   if(use_target && !FixedSLTP){ 
      // é usado o ponto-alvo e é calculado o Stop-Loss
      if(dir==1){
         Pos[PosCnt].tp=target;
         Pos[PosCnt].sl=NormalizeDouble(Pos[PosCnt].price-StopLoss_K*(Pos[PosCnt].tp-Pos[PosCnt].price),Digits());
      }
      else{
         Pos[PosCnt].tp=target+Point()*spread;
         Pos[PosCnt].sl=NormalizeDouble(Pos[PosCnt].price+StopLoss_K*(Pos[PosCnt].price-Pos[PosCnt].tp),Digits());
      }   
   }
   else{
      // são usadas as variáveis StopLoss e TakeProfit
      if(dir==1){
         Pos[PosCnt].tp=Pos[PosCnt].price+Point()*TakeProfit;
         Pos[PosCnt].sl=Pos[PosCnt].price-Point()*StopLoss;
      }
      else{
         Pos[PosCnt].tp=Pos[PosCnt].price-Point()*TakeProfit;
         Pos[PosCnt].sl=Pos[PosCnt].price+Point()*StopLoss;
      }     
   }
   
   PosCnt++;
   
}  

Função CheckClose():

void CheckClose(int i,const double & high[],const double & low[],const double & close[],const int & spread[]){
   for(int j=PosCnt-1;j>=0;j--){                                       // para todas as posições 
      bool closed=false;                                               // o valor false indica que as função está aberta  
      if(Pos[j].dir==1){                                               // compra
         if(low[i]<=Pos[j].sl){                                        // preço inferior ou igual ao Stop-Loss
            // lucro em pontos
            Closed+=(int)((Pos[j].sl-Pos[j].price)/Point());
            closed=true;                                               // posição com índice j está fechada
         }
         else if(high[i]>=Pos[j].tp){                                  // Take-Profit atingido
            // lucro em pontos
            Closed+=(int)((Pos[j].tp-Pos[j].price)/Point());    
            closed=true;                                               // posição com índice j está fechada        
         }
      }
      else{ // venda
         if(high[i]+Point()*spread[i]>=Pos[j].sl){                     // Stop-Loss atingido
            // lucro em pontos
            Closed+=(int)((Pos[j].price-Pos[j].sl)/Point());
            closed=true;                                               // posição com índice j está fechada
         }
         else if(low[i]+Point()*spread[i]<=Pos[j].tp){                 // preço inferior ou igual ao Take-Profit
            // lucro em pontos
            Closed+=(int)((Pos[j].price-Pos[j].tp)/Point());              
            closed=true;                                               // posição com índice j está fechada
         }         
      }
      // posição fechada, é necessário removê-la da matriz
      if(closed){ 
         int ccnt=PosCnt-j-1;
         if(ccnt>0){
            ArrayCopy(Pos,Pos,j,j+1,ccnt);
         }
         PosCnt--;
      }
   }
}

Na função CheckClose(), é realizada a passagem por todas as posições armazenadas na matriz Pos, e são comparados os valores de seus Stop-Loss e Take-Profit com os preços atuais high ou low. Se a posição é fechada, seu lucro em pontos é adicionado à variável Closed, após isto, a posição é removida da matriz.

Função SolveEquity():

int SolveEquity(int i,const double & close[],const int & spread[]){
   int rv=0;                                // variável para o resultado
   for(int j=PosCnt-1;j>=0;j--){            // para todas as posições
      if(Pos[j].dir==1){                    // compra
                                            // lucro
         rv+=(int)((close[i]-Pos[j].price)/Point());
      }
      else{                                // venda
         // lucro
         rv+=(int)((Pos[j].price+Point()*spread[i]-close[i])/Point());         
      }
   }
   return(rv);
}  

Na função SolveEquity(), é realizada a passagem por todas as posições abertas armazenadas na matriz Pos, e é calculado o lucro em pontos levando em conta o preço atual close.

Finalizamos o exame do indicador iTester. O indicador pronto para usar pode ser encontrado no apêndice do artigo, sob o nome de iTester. Na fig. 17, é exibido um gráfico com os indicadores iHorizontalFormation (setas) e iTester (na subjanela). Linha verde — capital liquido, vermelha — saldo.


Fig. 17. O indicador iTester (na subjanela) funcionando de acordo com o indicador iHorizontalFormation (setas no gráfico)


Fim do artigo

Os métodos discutidos para determinar os padrões resolvem de maneira adequada sua tarefa, uma vez que o gráfico mostra claramente as diferentes formas exibidas pelos indicadores, ou seja, as bandeiras, flâmulas, triângulos, cunhas. Os métodos acima não devem ser considerados os os únicos possíveis e absolutamente corretos. É possível conceber outras maneiras de detectar os mesmos padrões. Por exemplo, é possível utilizar a regressão linear, quer dizer, executar cálculos separados para os preços high e low e, em seguida, verificar a inclinação e convergência/divergência destas linhas. Ainda podem surgir mais ideias para abordar subtarefas específicas que compõem o problema geral da pesquisa de padrões. Seja como for, os métodos de analise de preços, estudados na criação de indicadores, neste artigo, podem ser uteis para resolver outras tarefas da análise técnica. 


Arquivos do aplicativo

Todos os indicadores, criados no artigo, estão no anexo:

  • iHorizontalFormation
  • iFlag
  • iTester

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

Arquivos anexados |
files.zip (9.87 KB)
Últimos Comentários | Ir para discussão (1)
Parlo
Parlo | 8 jan 2018 em 01:20

O  indicador iHorizontalFormation da erro de leitura  quando tento anexar ao gráfico, Na compilação não deu nenhum erro. Gostaria de testa-lo. pode verificar o que ocorre?

Classificador Bayesiano Ingênuo para sinais de um conjunto de indicadores Classificador Bayesiano Ingênuo para sinais de um conjunto de indicadores
O artigo analisa a aplicação da fórmula de Bayes para melhorar a fiabilidade dos sistemas de negociação através do uso dos sinais de vários indicadores independentes. Os cálculos teóricos são verificados com um EA universal simples, personalizado para trabalhar com indicadores exploratórios ou customizados.
Expert Advisor Universal: Acessando as Propriedades do Símbolo (Parte 8) Expert Advisor Universal: Acessando as Propriedades do Símbolo (Parte 8)
A oitava parte do artigo apresenta a descrição da classe CSymbol, que é um objeto especial que fornece acesso a qualquer instrumento de negociação. Quando usada dentro de um Expert Advisor, a classe fornece um amplo conjunto de propriedades do símbolo, permitindo simplificar a programação do Expert Advisor e expandir a sua funcionalidade.
Otimização Walk Forward em MetaTrader 5 feita com suas próprias mãos Otimização Walk Forward em MetaTrader 5 feita com suas próprias mãos
No artigo, são discutidas abordagens que permitem emular com bastante precisão a Otimização Walk Forward através do testador interno e bibliotecas auxiliares implementadas em MQL.
Teste de padrões que surgem ao negociar cestas de pares de moedas. Parte I Teste de padrões que surgem ao negociar cestas de pares de moedas. Parte I
Nós começamos a testar os padrões e tentamos os métodos descritos nos artigos sobre a negociação de cestas de pares de moedas. Veja como os padrões de rompimento dos níveis de sobrevenda/sobrecompra são aplicados na prática.