English Русский 中文 Español Deutsch 日本語
Ondas de Wolfe

Ondas de Wolfe

MetaTrader 5Exemplos | 24 maio 2017, 09:24
3 540 1
Dmitry Fedoseev
Dmitry Fedoseev

Conteúdo

Introdução

As ondas de Wolfe são uma figura da análise gráfica descoberta e descrita em detalhe por Bill Wolfe. Externamente, a figura é semelhante a um triângulo ou cunha (Woolf chamava-lhe cunha crescente), e tem uma série de nuances. Este método gráfico, proposto por Bill Wolfe, torna possível não só identificar a forma e, assim, determinar o tempo e a direção de entrada, mas também prever o alvo, que deve atingir o preço, e o tempo para alcançá-lo.

Neste artigo, vamos examinar em pormenor as regras para encontrar e interpretar as ondas de Wolfe. Com base no indicador ZigZag do artigo "ZigZag universal" criaremos um indicador para sua detecção automática e exibição no gráfico. Adicionalmente será criado um Expert Advisor simples que opera de acordo com o indicador obtido. Isto permite verificar o trabalho do indicador e obter uma primeira impressão sobre o método de análise gráfica proposto por Bill Wolfe e discutido neste artigo. 

Regras para determinar ondas de Wolfe

Consideremos as ondas de Wolfe como mostra o exemplo de compra (fig. 1). O preço forma dois fundos (linha azul, pontos 1 e 3) e dois picos decrescentes (pontos 2 e 4). Após a reversão e formação do pico no ponto 4, o preço continua a cair, quando o preço forma a linha 1—3, é executada a compra (ponto 5). 


Fig. 1. Ondas de Wolfe para compra. A linha azul é o preço, enquanto a linha vermelha consiste numa construção para definir o alvo. A entrada é realizada no ponto 5, o alvo, no ponto 7

O ponto 6, obtido pela intersecção das linhas 1—3 e 2—4, determina o tempo para atingir o alvo. O valor do alvo (ponto 7) é definido como a intersecção entre as linhas 1—4 e a linha vertical que passa pelo ponto 6. Não existe um algoritmo de cálculo do nível Stop-Loss no método, só é recomendado usá-lo a seu critério. Bem, com isso acabavam as regras de definição destas ondas no livro de Wolfe. 

Ao desenvolver o indicador para este artigo, foram adicionadas algumas regras.

  1.  O ponto 3 deve ser significativamente inferior ao ponto 1, isto é conseguido através da verificação da condição:

    v3<v1-d1

    Onde:

    • v3 — nível do ponto 3;
    • v1 — nível do ponto 1;
    • d1 — distância vertical entre os pontos 1 e 2 (segmento 1''-2''), multiplicada pelo coeficiente K1 (o coeficiente K1 é um parâmetro na janela de propriedades, por padrão 0.1).

  2. A linha 1—4, que determina o alvo, deve ser dirigida para cima, isto é, o ponto 4 deve ser significativamente superior ao ponto 1. Para fazer isso, é verificada a condição:

    v4>v1+d1;

    onde v4 é o nível do ponto 4. 

  3. O ponto 4 deve ser significativamente inferior ao ponto 2, isto é conseguido através da verificação da condição:

    v4<v2-d2;

    onde v2 é o nível do ponto 2; d2 é a distância vertical entre os pontos 2 e 3 (segmento 2''-3'')? multiplicada pelo coeficiente K2 (o coeficiente K2 é um parâmetro na janela de propriedades, por padrão 0.1).

  4. As linhas 2-4 e 1-3, que determinam o tempo para atingir o alvo, devem ser cruzadas da direita, para esta altura 2-2' deve ser significativamente superior à altura 4-4'. Esta condição é verificada usando:

    h2-h4>K3*h2;

    onde h2 é o valor do segmento 2-2'; h4 é o valor do segmento 4-4'; K3 é o coeficiente (o coeficiente K3 é um parâmetro na janela de propriedades, por padrão 0.1).

As regras delineadas aqui não pretendem ter precisão absoluta. Mais tarde, no artigo, será descrito em detalhe o processo de criação do indicador. Após lê-lo, você poderá ajustar o código de acordo com suas próprias opiniões e ideias. 

Seleção do ZigZag para modificações

Antes de começar, baixamos o aplicativo anexo ao artigo "ZigZag universal", nele existem muitas variantes do indicador ZigZag. Determinam qual tomaremos como base. Imediatamente eliminamos as variantes iUniZigZagPrice e iUniZigZagPriceSW, projetadas para calcular o ZigZag a partir de qualquer outro indicador, localizado no gráfico, e, portanto, de interesse apenas para observação visual e análise. Os outros indicadores são mais interessantes, uma vez que podem ser utilizados para a elaboração de Expert Advisors. Também excluímos as variantes iCloseZigZag e iHighLowZigZag, porque se trata de apenas exemplos iniciais de criação do ZigZag. Ficam duas variantes: iUniZigZag e iUniZigZagSW. O iUniZigZagSW é preferível, já que funciona numa subjanela, tendo melhores oportunidades. No entanto, também foi anexado mais um indicador, isto é, o iUniZigZagSWEvents, ele é um exemplo de uso da função iCustom() para acessar o indicador iUniZigZagSW. Nesta variante, paramos, uma vez que ela não só permite usar todos os recursos do indicador iUniZigZagSW, mas também separar completamente o código de identificação - das ondas de Wolfe - do código do ziguezague.

O indicador iUniZigZagSWEvents é exibido no gráfico de preço, usando quatro buffers: dois com setas e dois com pontos, exatamente o que se precisa para reconhecer as ondas de Wolfe. Os lugares de figuras serão exibidos por setas, enquanto os níveis-alvo, por pontos. Graças aos objetos gráficos, nomeadamente, a linha de tendência, o indicador a ser criado desenha as ondas e todas as construções para definir o alvo. Ao desdenhar um segmento de linha, sem prolongação, a linha de tendência torna-se muito conveniente para mostrar as diferentes construções.  

As ondas de Wolfe são usadas ​​não só para determinar o momento e direção de entrada, mas também para fins de previsão. Por isto, ao utilizar o indicador iUniZigZagSW surgem dificuldades. O indicador tem o parâmetro SrcSelect para selecionar a fonte de dados a serem analisados, segundo os quais é construído o ZigZag. É possível selecionar uma das quatro opções:

  • Src_HighLow — segundo preços high e low;
  • Src_Close — segundo preços close;
  • Src_RSI — segundo o indicador RSI;
  • Src_MA — segundo o indicador MA.

Com base no indicador criado no artigo será construído o Expert Advisor. Portanto, se for construído um ZigZag a partir do preço, para instalar o Take-Profit será possível usar o alvo previsto. Também não surgirão problemas com a exibição do alvo no gráfico. Se o ZigZag for construído segundo o RSI (SrcSelect=Src_RSI), o alvo previsto não será para o preço, mas sim para o indicador RSI. Quer dizer, se o indicador RSI alcançar o valor-alvo, será necessário executar o fechamento de mercado, e ainda mais importante é que não poderão ser exibidos o preço-alvo e as construções auxiliares no gráfico.

Ao utilizar o ZigZag segundo o preço (Src_HighLow ou Src_Close), no gráfico serão exibidos o preço-alvo e as construções, e noutros casos será mostrada somente a seta indicando a figura encontrada e sua direção. O valor-alvo será contido no buffer de indicador adequado (para poder executar o fechamento de mercado no Expert Advisor ou para quaisquer outros fins), mas não será exibido.

Muito provavelmente, a ideia de fechamento de mercado quando o indicador atinge o nível-alvo será inviável na prática. Na verdade, a maioria dos indicadores mudam seus valores dentro de um intervalo limitado, e o resultado da construção do alvo pode sair desses limites. No entanto, em todos os casos, o buffer conterá o valor-alvo.

Recolha de dados de picos de ZigZag

Passamos a lidar com a criação do indicador. No editor, abrimos o arquivo iUniZigZagSWEvents e salvamo-lo com o nome iWolfeWaves. É nele que trabalharemos.

Seria bastante cômodo ter acesso a todos os picos de ZigZag, para que não seja necessário procurá-los no histórico. Criaremos uma matriz para guardá-los. Agora ao alterar a direção do ZigZag, será adicionado à matriz um novo elemento. Se o indicador simplesmente estender o último segmento (atualizar o extremo), será atualizado o último elemento da matriz.

Para cada pico, salvaremos seu valor, direção e índice de barra, no qual ela se encontra (numeração dos índices - da esquerda para a direita). Para isso usaremos uma estrutura de três campos:

struct SPeackTrough{
   double   Val; // valor
   int      Dir; // direção
   int      Bar; // índice de barra
};

Criaremos a matriz destas estruturas:

SPeackTrough PeackTrough[];

Se apenas fosse usado o ZigZag segundo high e low (SrcSelect = Src_HighLow), ao alterar a direção, bastaria aumentar a matriz, definir os valores e atulaizar o último elemento à medida que seja estendido o último segmento do indicador. No que diz respeito ao ZigZag, a situação se dificulta segundo o preço close (SrcSelect = Src_Close) ou segundo os dados de qualquer outro indicador. À medida que a barra, na qual houve uma alteração de diração, se formar, o ZigZag pode voltar ao estado original (anterior ao fechamento da formação da barra). Isto significa que, ao contar novamente a mesma barra, a matriz para os picos será devolvida ao estado que tinha na barra anterior. A frequente alteração do tamanho da matriz pode desacelerar o indicador. Por isso, introduziremos uma variável adicional, na qual será armazenado o tamanho da matriz, enquanto a própria matriz será alterada por blocos, se necessário, de modo crescente. Antes do re-cálculo, retornaremos a mesma barra ao valor original desta variável.

Para guardar o tamanho da matriz usaremos duas variáveis, uma para salvar o tamanho da matriz na barra anterior e outra para a barra calculada:

int PreCount; // tamanho da matriz PeackTrough na barra anterior
int CurCount; // tamanho da matriz PeackTrough na barra calculada

Após a geração e contagem da barra ou após a execução da contagem da barra seguinte no histórico, é necessário colocar o valor a partir da variável CurCount na variável PreCount. Então, antes da contagem de cada barra que se forma, deslocaremos o valor a partir de PreCount para CurCount. Em todos os cálculos se usará apenas a variável CurCount. A variável PreCount é auxiliar. É somente possível conhecer a formação da barra na abertura da seguinte (ou na transição para a contagem da barra seguinte no histórico). O surgimento de uma nova barra será determinado pelo tempo: se o tempo da barra mudar, surgirá uma nova barra (ou haverá uma transição para a contagem da barra seguinte no histórico). Para definir uma nova barra é necessário uma variável auxiliar:

datetime LastTime;

As variáveis PreCount, LastCount, LastTime são variáveis globais do indicadores. Mas elas podem ser declaradas como estáticas na função OnCalculate() do indicador. 

Passaremos a trabalhar na função OnCalculate(). De acordo com o valor da variável é determinado o prev_calculated e o primeiro cálculo do indicador será executado ou serão calculadas apenas as novas barras. O valor 0 indica um cálculo completo, além disso, será necessário inicializar as variáveis PreCount, CurCount e LastTime. O código seguinte - que determina o intervalo das barras para cálculo e inicializa as variáveis auxiliares - está localizado no topo da função OnCalculte():

int start; // variável para o índice da primeira barra a ser contada

if(prev_calculated==0){ // contagem completa de todas as barras
   start=1;
   CurCount=0;   
   PreCount=0;
   LastTime=0;
}
else{ // contagem de uma nova barra
   start=prev_calculated-1;
}

Agora trataremos do ciclo de indicador padrão e, no início, moveremos os valores para as variáveis PreCount, CurCount:

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

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

A variável CurCount será utilizada apenas nos cálculos, enquanto a variável PreCount se destina apenas à manutenção do valor atual na variável CurCount. Inicialmente, a variável CurCount mantém o valor obtido pelo cálculo da barra anterior ao abrir uma nova barra. Por isso moveremos o seu valor para a variável PreCount. É possível alterar o valor, como resultado da contagem de uma nova barra na variável CurCount, mas apenas será claro se é definitivo na abertura da barra seguinte. Por isso, ao ser feito o re-cálculo da mesma barra na variável CurCount, será alocado o valor da variável PreCount .

Em seguida, no ciclo do indicador principal deve ser colocado o seguinte código, que resta do indicador iUniZigZagSWEvents:

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;

UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;      

// direção

double dir[2];
if(CopyBuffer(handle,3,rates_total-i-1,2,dir)<=0){
   return(0);
}
if(dir[0]==1 && dir[1]==-1){
   DnArrowBuffer[i]=high[i];
   c++;

}
else if(dir[0]==-1 && dir[1]==1){
   UpArrowBuffer[i]=low[i];
   c++;
}

// novo máximo

double lhb[2];
if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){
   return(0);
}
if(lhb[0]!=lhb[1]){
   UpDotBuffer[i]=high[i];
}

// novo mínimo

double llb[2];
if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
   return(0);
}
if(llb[0]!=llb[1]){
   DnDotBuffer[i]=low[i];
}  

Apagaremos a parte do código encarregado de desenhar as setas de que não necessitamos.

No processo de trabalho, o indicador acompanhará cada mudança do ZigZag, isto é, não apenas a mudança de sua direção, mas também cada extenção do último segmento, porque o último segmento é exigido para definir o ponto 5 (ver fig. 1). Para isso será usada a parte do código que resta do desenho dos novos extremos no fragmento acima.

Para monitorar a direção do ZigZag e definir os momentos da sua mudança ainda se exige o semelhantes às variáveis PreDir e CurDir:

int PreDir; // direção do ZigZag na barra anterior
int CurDir; // direção do ZigZag na barra calculada

Elas podem ser tanto globais quanto estáticas na função OnCalculate(). No início do cálculo do indicador é necessário inicializá-las e alterar seu valor a cada cálculo de barra, do memso modo que com as variáveis PreCount e CurCount. Abaixo está o código final da função OnCalculate() que reflete essa etapa de contrução do indicador:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {

   int start; // variável para a primeira barra de cálculo
   
   if(prev_calculated==0){ // cálculo total 
      start=1; 
      CurCount=0;
      PreCount=0;
      CurDir=0;
      PreDir=0;      
      LastTime=0;
   }
   else{ // contagem de novas barras
      start=prev_calculated-1;
   }

   // principal ciclo de indicador
   for(int i=start;i<rates_total;i++){
   
      if(time[i]>LastTime){ // nova barra
         LastTime=time[i];
         PreCount=CurCount;
         PreDir=CurDir;
      }
      else{ // re-cálculo da barra
         CurCount=PreCount;
         CurDir=PreDir;
      }

      // depuração de buffers, que desenham setas e pontos
      
      UpArrowBuffer[i]=EMPTY_VALUE;
      DnArrowBuffer[i]=EMPTY_VALUE;
      
      UpDotBuffer[i]=EMPTY_VALUE;
      DnDotBuffer[i]=EMPTY_VALUE;    
      
      // variáveis auxiliares
      
      double hval[1];
      double lval[1];
      
      double zz[1];
      
      // novo máximo
      
      double lhb[2];
      // obtemos dois elementos do buffer com índices de barras de novos máximos
      if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){ 
         return(0);
      }
      if(lhb[0]!=lhb[1]){ // existir um novo máximo
         // obtemos o valor do máximo de preço (ou dados segundo os quais é calculado o ZigZag)
         if(CopyBuffer(handle,0,rates_total-i-1,1,hval)<=0){
            return(0);
         }      
         if(CurDir==1){ // a última direção conhecida for de subida 
            // atualizaremos os dados sobre o último ponto do ZigZag
            RefreshLast(i,hval[0]);
         }
         else{ // O ZigZag mudou de direção
               // adicionaremos um novo valor
            AddNew(i,hval[0],1);
         }
         // aqui serão verificadas as condições para a detecção de ondas de Wolfe para baixo  
      }
      
      // novo mínimo
      
      double llb[2];
      // obtemos dois elementos do buffer com índices de barras de novos mínimos
      if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
         return(0);
      }
      if(llb[0]!=llb[1]){ // existir um novo mínimo
         // obtemos o valor do mínimo de preço (ou dados segundo os quais é calculado o ZigZag)
         if(CopyBuffer(handle,1,rates_total-i-1,1,lval)<=0){
            return(0);
         }         
         if(CurDir==-1){ // a última direção conhecida for de descida
            // atualizaremos os dados sobre o último ponto do ZigZag
            RefreshLast(i,lval[0]);
         }
         else{ // O ZigZag mudou de direção
            // adicionaremos um novo valor
            AddNew(i,lval[0],-1);
         }
         // aqui serão verificadas as condições para a detecção de ondas de Wolfe para cima 
      }      
   }   
   return(rates_total);
}

Neste código são encontradas as funções AddNew() e RefreshLast(). O índice da barra, na qual ocorreu a mudança, e o valor do novo extremo são transferidos para ambas as funções, enquanto que para a função AddNew() é enviada a direção do ZigZag.

Função de adição de novos pontos AddNew():

void AddNew(int i,double v,int d){
   if(CurCount>=ArraySize(PeackTrough)){ // não há elementos na matriz
      ArrayResize(PeackTrough,ArraySize(PeackTrough)+1024); // aumento do tamanho da matriza
   }
   PeackTrough[CurCount].Dir=d; // definição da direção
   PeackTrough[CurCount].Val=v; // definição do valor
   PeackTrough[CurCount].Bar=i; // definição da barra
   CurCount++; // aumento da variável com o número de elementos ocupados da matriz   
   CurDir=d; // lembramos a última direção do ZigZag
} 

Função de atualização do último ponto RefreshLast():

void RefreshLast(int i,double v){
   PeackTrough[CurCount-1].Bar=i; // definição da nova barra
   PeackTrough[CurCount-1].Val=v; // definição de novo valor
} 

Por agora o indicador pode ser salvo, para depois ser usado como base ao desenvolver indicadores que definem diversas figuras do ZigZag. No anexo do artigo o indicador tem o nome "iWolfeWaves_Step_1".

Um pouco de geometria

É preciso de um pouco de geometria para implementar as ondas de Wolfe e executar as construções que definem o alvo. Consideremos estas tarefas separadamente e escrevamos as funções para resolvê-las.

Tarefa 1. Uma linha reta é definida pelos pontos x-y; onde x é o índice da barra; y é o valor (preço ou valor do indicador). Dada a coordenada x do terceiro ponto, é necessário encontrar o valor da linha neste ponto (Fig. 2).


Fig. 2. Dado: X1, Y1, X2, Y2, X3. É necessário encontrar Y3.

Solução da tarefa 1. Determinamos o incremento de linha no eixo Y por unidade de incremento no eixo X:

D=(Y2-Y1)/(X2-X1)

Onde D é o valor de incremento; Y1 é o valor do indicador no ponto 1; Y2 é o valor do preço ou indicador no ponto 2; X1 é o índice da barra1; X2 é o índice da barra no ponto 2.  

Definimos Y3:

Y3=Y1+(X3-X1)*D

Onde X3 é o índice da barra no ponto 3; Y3 é o valor desejado da linha no ponto 3.

Obtemos a função:

double y3(double x1,double y1,double x2,double y2,double x3){
   return(y1+(x3-x1)*(y2-y1)/(x2-x1));
}

Para a função são enviados os seguintes parâmetros:

  • x1 — índice da barra no ponto 1;
  • y1 — valor no ponto 1;
  • x2 — índice da barra no ponto 2;
  • y2 — valor no ponto 2.

Tarefa 2. Duas linhas definidas pelo par de pontos x-y. É necessário encontrar a coordenada x do ponto de intersecção (Fig. 3). Aqui pode surgir a pergunta: por que a coordenada x? Isto não importa, porque depois de receber a coordenada x, em qualquer caso, será calculada a coordenada y do ponto 3 (utilizando a equação de uma das linhas). Portanto, primeiro, é possível obter a coordenada y do ponto 3, e, em seguida, a partir da equação de uma das linhas, calcular sua abcissa.  


Fig. 3. Dadas duas linhas, é necessário encontrar seu ponto de intersecção


Em primeiro lugar, utilizando as coordenadas de dois pontos, obtemos a equação das linhas na forma y=a+b*x.

Executamos os cálculos prévios. Inclinação de linha (em unidades no eixo y, por unidade no eixo x):

D1=(Y12-Y11)/(X12-X11)

Onde D1 é o valor de inclinação desejado da primeira linha (valor da variação no valore de linha para uma barra), X11 é o índice da barra no ponto 1 da primeira linha, X12 é o índice da barra no ponto 2 da primeira linha, Y11 é o valor da primeira linha no ponto 1, Y12 é o valor primeira linha no ponto 2.     

Inclinação da linha 2:

D2=(Y22-Y21)/(X22-X21)

Onde D2 é o valor de inclinação desejado da segunda linha (valor da variação no valore de linha para uma barra), X21 é o índice da barra no ponto 1 da segunda linha, X22 é o índice da barra no ponto 2 da segunda linha, Y21 é o valor da segunda linha no ponto 1, Y22 é o valor da segunda linha no ponto 2.

Agora obtemos as equações de linhas. Equação de linha 1:

Y3=Y11+D1*(X3-X11)

Onde Y3 é o valor de linha no ponto de intersecção (ponto 3), X3 é o índice da barra no ponto 3.

Equação da linha 2:

Y3=Y21+D2*(X3-X21)

Nos pontos de intersecção, os valores das linhas são iguais, portanto, igualamos a equação da linha 1 com a equação da linha 2:

Y11+D1*(X3-X11)=Y21+D2*(X3-X21);

Usando a expressão obtida, expressamos X3. Como resultado obtemos a função TwoLinesCrossX() para definir as coordenadas X de intersecção de duas linhas:

double TwoLinesCrossX(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(y22-y21)/(x22-x21);
   double k1=(y12-y11)/(x12-x11);
   return((y11-y21-k1*x11+k2*x21)/(k2-k1));
}

Para a função são enviados os seguintes parâmetros:

  • x11 — índice da barra no ponto 1 da primeira linha;
  • y11 — valor no ponto 1 na primeira linha;
  • x12 — índice da barra no ponto 2 da primeira linha;
  • y12 — valor no ponto 2 na primeira linha;
  • x21 — índice da barra no ponto 1 na primeira linha;
  • y21 — valor no ponto 1 na segunda linha;
  • x22 — índice da barra no ponto 2 na segunda linha;
  • y22 — valor no ponto 2 no segunda linha.

Após definida a coordenada x do ponto de intersecção de duas linhas, a coordenada y pode ser calculada, usando as coordenadas de dois pontos de uma das linhas e de função y3() obtida ao resolver a tarefa 1.

Se primeiro for preciso obter as coordenadas y, a equação das linhas deve ser transformada para que elas expressem a coordenada x através de y. Primeiro para uma linha:

X3=X11+(Y3-Y11)/D1

Para a segunda linha:

X3=X21+(Y3-Y21)/D2

Igualamos as duas equações:

X11+(Y3-Y11)/D1=X21+(Y3-Y21)/D2

A partir desta equação é necessário expressar Y3. Como resultado, obtemos a função TwoLinesCrossY():

double TwoLinesCrossY(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(x22-x21)/(y22-y21);
   double k1=(x12-x11)/(y12-y11);
   return((x11-x21-k1*y11+k2*y21)/(k2-k1));
}

Os parâmetros desta função são os mesmos que em TwoLinesCrossX(). 

Definição das ondas

Agora, tido fácil acesso a todos os picos do ZigZag e funções geométricas auxiliares, passamos a lidar diretamente com a definição das ondas de Wolf. É precisa "pegar" o momento em que o último segmento do ZigZag cruza a linha 1-3 (ver. fig. 1), isto é, ponto 5. Portanto, verificaremos as condições que identificam as ondas Wolf em cada novo extremo do ZigZag (tanto ao mudar de direção, quanto na extensão do último segmento). Acima, no código da função OnCalculate(), comentados em detalhe os lugares onde deve ser executada a verificação de condições. A partir delas serão chamadas as funções CheckDn() e CheckUp(). Consideremos em detalhes a função CheckUp():

void CheckUp(int rates_total,const double & low[],const datetime & time[],int i){

   if(CurCount<5 || CurDir!=-1){ 
      // se houver poucos picos ou o Zigzag não for dirigido para baixo, não realizaremos a verificação
      return;
   }   
   
   // preparamos as variáveis curtas com dados sobre os picos 

   // variáveis com valores de picos
   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   // variáveis com barras de picos
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
                  
   if(CurLastBuySig!=i4){ // se, nesta configuração do ZigZag, ainda não forem identificadas ondas
      double d1=K1*(v2-v1); // valor mínimo de deslocamento do pico 3 em relação ao pico 1
      if(v3<v1-d1){ // pico 3 significativamente inferior ao pico 1
         if(v4>v1+d1){ // linha 1-4 inclinada par cima
            double d2=K2*(v2-v3); // valor mínimo de deslocamento do pico 4 em relação ao pico 2
            if(v4<v2-d2){ // pico 4 significativamente inferior ao pico 2
               double v5l=y3(i1,v1,i3,v3,i); // valor do ponto 5
               if(v5<v5l){ // o último segmento do ZigZag cruzou a linha 1-3
                  double v4x=y3(i1,v1,i3,v3,i4); // valor no ponto 4'
                  double v2x=y3(i1,v1,i3,v3,i2); // valor no ponto 2'
                  double h4=v4-v4x; // altura 4-4'
                  double h2=v2-v2x; // altura 2-2'
                  if(h2-h4>K3*h2){ // linhas 1-3 e 2-4 convergem da direita
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // barra de cruzamento das linhas 1-3 e 2-4
                     double tv=y3(i1,v1,i4,v4,tb); // valor no ponto de intersecção das linhas 1-3 e 2-4
                     UpArrowBuffer[i]=low[i]; // exibição das setas para cima
                     UpDotBuffer[i]=tv; // exibição do ponto no nível do alvo
                     CurLastBuySig=i4; // lembramos que, nessa configuração do ZigZag, a forma é revelada
                     if(_DrawWaves){ // desenho de formas e construções
                        DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

Determinar ondas requer um mínimo de 5 picos do ZigZag. Uma condição adicional consiste em que para identificar ondas que envolvem uma subsequentemente reversão, p ZigZag deve estar dirigido para baixo:

if(CurCount<5 || CurDir!=-1){ 
   // se houver poucos picos ou o Zigzag não for dirigido para baixo, não realizaremos a verificação
   return;
}   

Para obter os dados sobre os picos, é possível acessar diretamente a matriz PeackTrough, mas é inconveniente. Basta usar variáveis​auxiliares com nomes curtos:

// variáveis com valores de picos
double v1=PeackTrough[CurCount-5].Val;
double v2=PeackTrough[CurCount-4].Val;
double v3=PeackTrough[CurCount-3].Val;
double v4=PeackTrough[CurCount-2].Val;
double v5=PeackTrough[CurCount-1].Val;
   
// variáveis com barras de picos
int i1=PeackTrough[CurCount-5].Bar;
int i2=PeackTrough[CurCount-4].Bar;               
int i3=PeackTrough[CurCount-3].Bar;
int i4=PeackTrough[CurCount-2].Bar;
int i5=PeackTrough[CurCount-1].Bar;

Se a onda é identificada, a seta colocada, não é mais necessário usar a mesma configuração do ZigZag. A identificação da configuração do ZigZag é realizada usando a verificação do índice do pico 4 (do último pico formado):

if(CurLastBuySig!=i4){ // se, nesta configuração do ZigZag, ainda não forem identificadas ondas

Para armazenar os valores do identificador da configuração, é usado um par de variáveis semelhantes às variáveis CurCount e PreCount.

Agora chegamos diretamente à definição das ondas. É calculado o valor mínimo do deslocamento do ponto 3 em relação ao ponto 1 e do ponto 2 em relação ao ponto 1:

double d1=K1*(v2-v1); // valor mínimo de deslocamento do pico 3 em relação ao pico 1

Após isso, é verificado o deslocamento de pontos:

if(v3<v1-d1){ // pico 3 significativamente inferior ao pico 1
   if(v4>v1+d1){ // linha 1-4 inclinada par cima

Calculamos o valor mínimo de deslocamento do ponto 4 em relação ao ponto 2:

double d2=K2*(v2-v3); // valor mínimo de deslocamento do pico 4 em relação ao pico 2

Verificamos a posição dos pontos 2 e 4:

if(v4<v2-d2){ // pico 4 significativamente inferior ao pico 2

Calculamos o valor do ponto localizado na linha 1-3 e correspondente à barra contada:

double v5l=y3(i1,v1,i3,v3,i); // valor do ponto 5

Verificamos se existiu contato da linha 1-3:

if(v5<v5l){ // o último segmento do ZigZag cruzou a linha 1-3

Calculamos os valores nos pontos 4 'e 2':

double v4x=y3(i1,v1,i3,v3,i4); // valor no ponto 4'
double v2x=y3(i1,v1,i3,v3,i2); // valor no ponto 2'

Calculamos a altura 4-4' e 2-2':

double h4=v4-v4x; // altura 4-4'
double h2=v2-v2x; // altura 2-2'

Usando estas alturas, verificamos a convergência das linhas 1-3 e 2-4 na direita:

if(h2-h4>K3*h2){ // linhas 1-3 e 2-4 convergem da direita

Esta condição indica que a onda foi encontrada. 

Definimos o alvo. Primeiro barra para alcançar o alvo:

double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // barra de cruzamento das linhas 1-3 e 2-4

Note que para a precisão dos cálculos, é utilizada a variável double.     

Valor do alvo:

double tv=y3(i1,v1,i4,v4,tb); // valor no ponto de intersecção das linhas 1-3 e 2-4

 Exibimos ícones e "lembramos" o identificador de configuração do ZigZag:

UpDotBuffer[i]=tv; // exibição do ponto no nível do alvo
CurLastBuySig=i4; // lembramos que, nessa configuração do ZigZag, a forma é revelada

Finalmente, pintamos as ondas e a construção que determina o alvo:

if(_DrawWaves){ // desenho de formas e construções
   DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
}

Numa seção separada do artigo será discutido em detalhe o desenho de ondas e construções, ou seja, será considerada a função DrawObjects(). 

Ondas para baixo (para venda) são determinados pela função CheckDn, idêntica a CheckUp, com excepção de pequenas diferenças associadas com a direção. Abaixo está o código e discutidas as diferenças com a função CheckUp():

void CheckDn(int rates_total,const double & high[],const datetime & time[],int i){

   // poucos picos ou a direção não é para cima 
   if(CurCount<5 || CurDir!=1){ 
      return;
   }

   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
   
   if(CurLastSellSig!=i4){               
      double d1=K1*(v1-v2); // pico v1 acima do pico v2
      if(v3>v1+d1){ // pico v3 acima do pico v1
         if(v4<v1-d1){ // pico v4 abaixo do pico v1                     
            double d2=K2*(v3-v2); // pico v3 acima do pico v2                     
            if(v4>v2+d2){ // pico v4 acima do pico v2  
               double v5l=y3(i1,v1,i3,v3,i);
               if(v5>v5l){ // ZigZag quebra a linha 1-3 para cima
                  double v4x=y3(i1,v1,i3,v3,i4);
                  double v2x=y3(i1,v1,i3,v3,i2);
                  double h4=v4x-v4; // ponto 4' acima do ponto 4
                  double h2=v2x-v2; // ponto 4' acima do ponto 4
                  if(h2-h4>K3*h2){   
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4);
                     double tv=y3(i1,v1,i4,v4,tb);                              
                     DnArrowBuffer[i]=high[i];
                     DnDotBuffer[i]=tv;
                     CurLastSellSig=i4;   
                     if(_DrawWaves){
                        // desenho com outras cores
                        DrawObjects(SellColor,
                                    SellTargetColor,
                                    v1,
                                    v2,
                                    v3,
                                    v4,
                                    v5l,
                                    i1,
                                    i2,
                                    i3,
                                    i4,
                                    i5,
                                    time,
                                    i,
                                    tb,
                                    tv,
                                    rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

Primeira diferença na verificação inicial:

// poucos picos ou a direção não é para cima 
if(CurCount<5 || CurDir!=1){ 
   return;
}

Se houver poucos picos ou o Zigzag for dirigido para baixo, terminamos o trabalho da função.

Para sentido descendente, os picos e fundos são invertidos de lugar: os pontos 1, 3, 5, 6 são colocados no topo, e os pontos 2, 4, 7 na parte inferior, de modo que algumas das variáveis são permutadas nas fórmulas. Definição da distância mínima entre os picos 1, 3, e 1, 4:

double d1=K1*(v1-v2); // pico v1 acima do pico v2

Verificação da posição dos picos 1, 3:

if(v3>v1+d1){ // pico v3 acima do pico v1

Verificação da posição dos picos 1, 4:

if(v4<v1-d1){ // picos v4 abaixo do pico v1 

Cálculo da distância mínima entre os picos 2, 3 e a sua verificação:

double d2=K2*(v3-v2); // pico v3 acima do pico v2                     
if(v4>v2+d2){ // pico v4 acima do pico v2  

Verificar se foi formado o ponto 5 (o ZigZag quebra da linha 1-3 para cima):

if(v5>v5l){ // ZigZag quebra a linha 1-3 para cima

Cálculo das alturas 2-2' e 4-4' para verificação da convergência das linhas 1-3 e 2-4 na direita:

double h4=v4x-v4; // ponto 4' acima do ponto 4
double h2=v2x-v2; // ponto 4' acima do ponto 4

Desenho de onda e construções realizadas com uma cor diferente:

// desenho com outras cores
DrawObjects(SellColor,
            SellTargetColor,
            v1,
            v2,
            v3,
            v4,
            v5l,
            i1,
            i2,
            i3,
            i4,
            i5,
            time,
            i,
            tb,
            tv,
            rates_total);

Desenho de ondas e alvos

Todas as ondas e construções são plotadas usando um único algoritmo, portanto, é usada uma função DrawObjects(). Elementos para cima e para baixo são plotados em cores diferentes. Para este fim, para a função é enviado o parâmetro de cor BuyColor ou SellColor. Também as ondas e construções que determinam o alvo são plotadas em cores diferentes, portanto, os parâmetros BuyTargetColor ou SellTargetColor são enviados para a função. Estas variáveis são variáveis externas do indicador com as quais podem ser definidas cores. Além da cor, são precisas mais alguns parâmetros externos. Abaixo estão todos os parâmetros adicionais para a função de desenho de ondas e construções:

input bool   DrawWaves       =  true;             // inclusão de desenho de ondas e construções
input color  BuyColor        =  clrAqua;          // cor de ondas de compra
input color  SellColor       =  clrRed;           // cor de ondas de venda
input int    WavesWidth      =  2;                // espessura de ondas
input bool   DrawTarget      =  true;             // ativação/desativação adicional de construções
input int    TargetWidth     =  1;                // espessura de construções
input color  BuyTargetColor  =  clrRoyalBlue;     // cor de construções para compra
input color  SellTargetColor =  clrPaleVioletRed; // cor de construções para venda

Após a cor, a variáveis com valores e índices de barras e picos são enviadas para a função. Uma excepção é o valor do pico 5, para o qual é enviado o valor calculado na linha 1-3. As coordenadas de todos os pontos do ZigZag são mostradas na barras, enquanto para objetos gráficos leva tempo, porque o ponteiro para a matriz time é enviado para a função. Em seguida, é enviado o índice da barra calculada — i, barra de alvo — tb, valor-alvo — tv e número total de barras no gráfico — rates_total. 

Como mencionado no início do artigo, o desenho de ondas e construções deve ser realizado apenas se selecionado o ZigZag segundo high/low (SrcSelect igual a Src_HighLow) ou close (SrcSelect igual a Src_Close). Assim, na função OnInit(), dependendo do valor da variável SrcSelect, é executado o desligamento forçado do desenho (pela variável DrawWaves). Para fazer isso, declaramos uma variável adicional que será usada em vez da variável DrawWaves:

bool _DrawWaves;

Em seguida, na função OnInit(), definimos para ela o valor da variável DrawWaves ou desativamos, isto é, definimos o valor false. Além disso, definimos para o buffer uma cor invisível a fim de desenhar o alvo:

if(SrcSelect==Src_HighLow || SrcSelect==Src_Close){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
   PlotIndexSetInteger(2,PLOT_LINE_COLOR,clrNONE);
   PlotIndexSetInteger(3,PLOT_LINE_COLOR,clrNONE);      
}   

Passamos a considerar a função DrawObjects(). Primeira, introduziremos todo o código da função, logo, considerá-lo-emos:

void DrawObjects( color col,
                  color tcol,
                  double v1,
                  double v2,
                  double v3,
                  double v4,
                  double v5,
                  int i1,
                  int i2,
                  int i3,
                  int i4,
                  int i5,
                  const datetime & time[],
                  int i,
                  double target_bar,
                  double target_value,
                  int rates_total){

   // prefixo de nomes de objetos gráficos 
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

   // desenho de ondas                   
   fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
   fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
   fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
   fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

   // desenho de construções
   if(DrawTarget){   
    
      datetime TargetTime;
      
      // obtenção de todo o valor do índice da barra de alvo 
      int tbc=(int)MathCeil(target_bar);
      
      if(tbc<rates_total){ // alvo nos limites das barras no gráfico
         TargetTime=time[tbc];
      }
      else{ // alvo no futuro
         TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
      }
      
      // cálculo de valores de linhas da construção na barra de alvo
      double tv13=y3(i1,v1,i3,v3,tbc);   
      double tv24=y3(i2,v2,i4,v4,tbc);  
      double tv14=y3(i1,v1,i4,v4,tbc); 

      // construções

      fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);   
      fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
      fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);
      
      // linha horizontal de alvo
      fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
      // linha vertical de alvo 
      fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);      
   }
}

Todo o desenho é realizado pelas linhas de tendência, para isso, inicialmente formado o prefixo comum dos nomes:

// prefixo de nomes de objetos gráficos 
string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

Então, são as ondas, coordenadas de todos os picos enviados para a função:

// desenho de ondas                   
fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

Desenhamos a construção que define o alvo. Verificamos se está ativado o desenho de construções:

// desenho de construções
if(DrawTarget){  

Se ativado, desenhamos as construções. Ao exibir o indicador no histórico, é provável que a barra-alvo esteja numa das barras existentes no histórico, mas se as ondas são identificadas nas barras mais recentes, o alvo pode surgir no futuro, depois da última barra. Ou seja, são necessárias duas maneiras de calcular o tempo da barra-alvo. Para fazer isso, declaramos a variável:

datetime TargetTime;

A variável target_bar tem um valor fracionário, aumentamo-lo para o número inteiro mais próximo:

// obtenção de todo o valor do índice da barra de alvo 
int tbc=(int)MathCeil(target_bar);

Em seguida, usaremos a variável obtida tbc. Neste ponto, poderia se utilizada a função MathFloor(), para obter o menor número inteiro mais próximo. Isto afetaria o resultado final, uma vez que as construções realizam apenas uma função informativa. Necessariamente é perto da barra que as extremidades das linhas 1-3 e 2-4 se cruzam com o uso de MathCeil(), sendo assim como os alvos e as construções terão uma aparência mais natural.

Definimos o tempo para atingir o alvo. Se o preço numa das barras existentes, basta calcular o índice da barra-alvo e obter sua hora a partir da matriz time. Se o alvo estiver mais à direita em relação à última barra, será necessário definir quantas barras é separado o alvo em relação à última barra e calcular a hora:

if(tbc<rates_total){ // alvo nos limites das barras no gráfico
   TargetTime=time[tbc];
}
else{ // alvo no futuro
   TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
}

Calculamos os valores de todas as linhas (1-3, 2-4 e 1-4) na barra-alvo:

// cálculo de valores de linhas da construção na barra de alvo
double tv13=y3(i1,v1,i3,v3,tbc);   
double tv24=y3(i2,v2,i4,v4,tbc);  
double tv14=y3(i1,v1,i4,v4,tbc); 

Apesar do fato de o valor-alvo calculado previamente (variável target_value) ser enviado para a função, ele é recalculado para as construções, mesmo para a linha 2-4. Isto é devido ao fato de que, em vez do valor exato a partir da variável target_bar, é usado o valor a partir da variável tbc, que é ligeiramente maior do que target_bar. Graças a esses cálculos na coordenada target_bar, as linhas se cruzarão exatamente no nível target_value.

Desenhar uma linha de acordo com os valores calculados:

fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);   
fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);

As linhas são plotadas usando a função auxiliar fObjTrend():

void fObjTrend(   string  aObjName,
                  datetime aTime_1,
                  double   aPrice_1,
                  datetime aTime_2,
                  double   aPrice_2,
                  color    aColor      =  clrRed,  
                  color    aWidth      =  1,                
                  bool     aRay_1      =  false,
                  bool     aRay_2      =  false,
                  string   aText       =  "",
                  int      aWindow     =  0,                  
                  color    aStyle      =  0,
                  int      aChartID    =  0,
                  bool     aBack       =  false,
                  bool     aSelectable =  false,
                  bool     aSelected   =  false,
                  long     aTimeFrames =  OBJ_ALL_PERIODS
               ){
   ObjectCreate(aChartID,aObjName,OBJ_TREND,aWindow,aTime_1,aPrice_1,aTime_2,aPrice_2);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_BACK,aBack);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_COLOR,aColor);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTABLE,aSelectable);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTED,aSelected);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_TIMEFRAMES,aTimeFrames);
   ObjectSetString(aChartID,aObjName,OBJPROP_TEXT,aText);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_WIDTH,aWidth);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_STYLE,aStyle);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_LEFT,aRay_1);   
   ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_RIGHT,aRay_2);
   ObjectMove(aChartID,aObjName,0,aTime_1,aPrice_1);
   ObjectMove(aChartID,aObjName,1,aTime_2,aPrice_2);   
}

A função é universal e pode ser usada para criar rapidamente uma linha de tendência com instalação de todos seus parâmetros. A atribuição de parâmetros de função é dada na Tabela 1. Os mais mutáveis deles estão no início (5 parâmetros obrigatórios), e o resto, além de serem opcionais, podem não ser enviados para a função. Essa variação faz com que o uso da função seja muito conveniente.

Tabela 1. Parâmetros da função fObjTrend()

Parâmetros Finalidade
string aObjName nome do objeto
datetime aTime_1 tempo do primeiro ponto de ancoragem
double aPrice_1 preço do primeiro ponto de ancoragem
datetime aTime_2 tempo do segundo ponto de ancoragem
double aPrice_2 o preço do segundo ponto de ancoragem
color aColor cor
color aWidth espessura
bool aRay_1 estender a linha a partir do primeiro ponto de ancoragem
bool aRay_2 estender a linha desde o segundo ponto de ancoragem
string aText dicas
int aWindow subjanela
color aStyle estilo de linha
int aChartID identificador de gráfico
bool aBack desenhar em segundo plano
bool aSelectable pode ser selecionado o objeto
bool aSelected objeto selecionado
long aTimeFrames em quais timeframes exibir a linha

Agora resta desenhar dois linhas adicionais, isto é, uma vertical na barra-alvo e uma vertical no nível-alvo:

fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);  

Como resultado, temos uma imagem de ondas e construções:


Fig. 4. Ondas Wolfe e construções com a finalidade de determinar um alvo durante a compra

Remoção de objetos gráficos

Ao utilizar o ZigZag segundo Close (SrcSelect=Src_Close) ou segundo outro indicador, à medida que seja gerada a barra, a forma pode intermitentemente aparecer e desaparecer. Para fazer isso, no início do ciclo base de indicador, é executada a depuração dos buffers com setas e pontos: 

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;
      
UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;  

Também, no início do ciclo, é necessário para assegurar a remoção de objetos gráficos. Se ativado o desenho de ondas e construções, no início do indicador do ciclo, será chamada a função DeleteObjects():

if(_DrawWaves){
   DeleteObjects(time[i]);
}  

Código da função DeleteObjects():

void DeleteObjects(datetime time){
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time)+"_";
   ObjectDelete(0,prefix+"12");
   ObjectDelete(0,prefix+"23");
   ObjectDelete(0,prefix+"34");
   ObjectDelete(0,prefix+"45");
   ObjectDelete(0,prefix+"13");
   ObjectDelete(0,prefix+"24"); 
   ObjectDelete(0,prefix+"14");    
   ObjectDelete(0,prefix+"67"); 
   ObjectDelete(0,prefix+"7h");    
}

O tempo da barra calculada é enviado para a função, e nela é que é executada a remoção de todos os objetos gráficos com nomes correspondentes à barra a ser calculada.

Ao remover o indicador no gráfico, é necessário eliminar os objetos gráficos criados por ele. A partir da função DeInit(), a ser executada automaticamente ao finalizar o trabalho do indicador, é chamada a função ObjectsDeleteAll(). O segundo parâmetro envia o nome do indicador - que é também o prefixo de todos seus objetos gráficos - para a função. Isto assegura a remoção de somente os objetos gráficos pertencentes a um determinado indicador:

void OnDeinit(const int reason){
   ObjectsDeleteAll(0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw(0);
}  

Função de alerta

Ao surgirem setas, adicionaremos ao indicador uma função de alerta, tal como no indicador de tendência universal do artigo "Tendência universal com GUI". 

A função de alerta pode acompanhar o surgimento de setas na barra emergente (adequada ao usar o ZigZag segundo high-low) ou na barra formada (adequada ao usar o ZigZag segundo close ou outros indicadores). Ou seja, para selecionar o tipo de alerta, criamos uma enumeração:

enum EAlerts{
   Alerts_off=0,  // alerta desativado
   Alerts_Bar0=1, // alerta segundo a barra que se forma
   Alerts_Bar1=2  // alerta segundo a barra formada
};

Na janela de propriedades adicionamos a variável:

input EAlerts              Alerts         =  Alerts_off;

O código da função de alerta foi introduzido à função separada CheckAlerts(). Para ela serão transferidos o número de barras no gráfico e a matriz de tempo:

void CheckAlerts(int rates_total,const datetime & time[]){
   if(Alerts!=Alerts_off){ // alerta desativado
      static datetime tm0=0; // variável para o tempo da barra do último alerta de compra
      static datetime tm1=0; // variável para o tempo da barra do último alerta de compra
      if(tm0==0){ // a primeira execução da função
         // inicialização de variáveis
         tm0=time[rates_total-1];
         tm1=time[rates_total-1];
      }
      string mes=""; // variável para mensagens

      // existe uma seta para cima, e na última barra ainda não foi dado alerta
      if(UpArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm0!=time[rates_total-1]
      ){
         tm0=time[rates_total-1]; // lembrar o tempo do último alerta
         mes=mes+" buy"; // formação de mensagem
      }

      // existe uma seta para baixo, e na última barra ainda não foi dado alerta
      if(DnArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm1!=time[rates_total-1]
      ){
         tm1=time[rates_total-1]; // lembrar o tempo do último alerta
         mes=mes+" sell"; // formação de mensagens
      } 
      if(mes!=""){ // há mensagem
         // abertura de uma janela com mensagens
         Alert(MQLInfoString(MQL_PROGRAM_NAME)+"("+Symbol()+","+IntegerToString(PeriodSeconds()/60)+"):"+mes);
      }        
   }   
}

A chamada da função CheckAlerts() é executada no final da função OnCalculate() após o ciclo principal. Além disso, no final da função OnCalculate() é executada a chamada da função de atualização do gráfico para acelerar a plotagem de ondas e construções:

if(_DrawWaves){
   ChartRedraw(0);
}
Assim, a criação do indicador foi completada. Ele se chama iWolfeWaves e pode ser encontrado no anexo do artigo.

Expert Advisor

O indicador acabou sendo bastante complexo. Tentaremos verificar que funciona bem não só no histórico estático e avaliaremos a eficácia do método considerado de análise gráfica. Para fazer isso, criaremos um Expert Advisor simples.

Para avaliar a eficácia, é preciso que o Expert Advisor abra as posições segundo todos os sinais do indicador. Por isso, ele trabalhará nas contas que permitem cobertura e não terá restrições sobre o número de posições abertas.

No editor, criamos um novo Expert Advisor com o nome eWolfeWaves. Copiamos todos os parâmetros externos do indicador e colamo-los no arquivo do Expert Advisor. Abaixo adicionamos os parâmetros adicionais que definem o Stop-Loss e Take-Profit:

input double               StopLoss_K     =  1;      // coeficiente de Stop-Loss
input bool                 FixedSLTP      =  false;  // Take-Profit e Stop-Loss fixos
input int                  StopLoss       =  50;     // valo do Stop-Loss fixo
input int                  TakeProfit     =  50;     // valor do Take-Profit fixo

Estes parâmetros permitem escolher entre duas opções para instalar o Stop-Loss e Take-Profit.

Se FixedSLTP=false, age a variável StopLoss_K. Neste caso, o Take-Profit é definido de acordo como o indicador, ou seja, no nível do ponto que exibe o alvo, e o Stop-Loss é calculado proporcionalmente ao Take-Profit através do coeficiente StopLoss_K. Esta maneira de definir o Stop-Loss e Take-Profit é adequada apenas para o ZigZag de acordo com o preço, isto é, segundo high-low ou segundo close (SrcSelect igual a Src_HighLow ou Src_Close).

A ter FixedSLTP=true, são usadas as variáveis StopLoss e TakeProfit. Esta opção faz com que seja possível usar o ZigZag segundo os indicadores, mas também segundo o preço.   

Na função OnInit() do Expert Advisor, verificamos o tipo de conta. Se a conta não permitir a cobertura, a execução do Expert Advisor será finalizado:

if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
   Print("Its not hedging account");
   return(INIT_FAILED);
}

Se o Expert Advisor não for testado num modo visual, desativaremos de maneira forçada o desenho de ondas e construções:

bool _DrawWaves;

if(MQLInfoInteger(MQL_VISUAL_MODE)){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
}

A variável _DrawWaves será utilizada em vez da variável DrawWaves ao chamar o indicador iWolfeWaves através da função iCustom(). Chamamos o indicador e verificamos se foi carregado com sucesso:

h=iCustom(  Symbol(),
            Period(),
            "iWolfeWaves",
            Alerts,
            SrcSelect,
            DirSelect,
            RSIPeriod,
            RSIPrice,
            MAPeriod,
            MAShift,
            MAMethod,
            MAPrice,
            CCIPeriod,
            CCIPrice,
            ZZPeriod,
            K1,
            K2,
            K3,
            _DrawWaves,
            BuyColor,
            SellColor,
            WavesWidth,
            DrawTarget,
            TargetWidth,
            BuyTargetColor,
            SellTargetColor);
            
if(h==INVALID_HANDLE){
   Print("Cant load indicator");
   return(INIT_FAILED);
}

Se falhar a carga do indicador, o trabalho do Expert Advisor será finalizado.

Ao usar o indicador segundo high-low, a seta do indicador após o aparecimento não desaparece, indicando que o Expert Advisor pode trabalhar na barra emergente. Em outros casos, a seta do indicador deve "olhar" para a primeira barra formada. Para fazer isso, declaramos a variável globa Shift:

int Shift;

Definimos para ela o valor desejado dependendo do tipo de Zigzag: 

if(SrcSelect==Src_HighLow){
   Shift=0;
}
else{
   Shift=1;
}

Abaixo está todo o código de função OnInit():

int OnInit(){

   // verificação do tipo de conta
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
      Print("Its not hedging account");
      return(INIT_FAILED);
   }

   // desativar o desenho de ondas e construções

   bool _DrawWaves;
   
   if(MQLInfoInteger(MQL_VISUAL_MODE)){
      _DrawWaves=DrawWaves;
   }
   else{
      _DrawWaves=false;
   }

   // carregamento do indicador
   h=iCustom(  Symbol(),
               Period(),
               "iWolfeWaves",
               Alerts,
               SrcSelect,
               DirSelect,
               RSIPeriod,
               RSIPrice,
               MAPeriod,
               MAShift,
               MAMethod,
               MAPrice,
               CCIPeriod,
               CCIPrice,
               ZZPeriod,
               K1,
               K2,
               K3,
               _DrawWaves,
               BuyColor,
               SellColor,
               WavesWidth,
               DrawTarget,
               TargetWidth,
               BuyTargetColor,
               SellTargetColor);
       
   // verificação do carregamento bem-sucedido do indicador         
   if(h==INVALID_HANDLE){
      Print("Cant load indicator");
      return(INIT_FAILED);
   }
   
   // definição da barra em que o Expert Advisor está "olhando" as setas do indicador
   if(SrcSelect==Src_HighLow){
      Shift=0;
   }
   else{
      Shift=1;
   }

   return(INIT_SUCCEEDED);
}

Passamos para função OnTick(). É necessário garantir o trabalho do perito tanto em ticks como em barras. Adicionamos variáveis ​​para o tempo da barra emergente e das últimas barras processadas ​​(variáveis ​​declaradas no função OnTick()):

datetime tm[1];     // tempo da barra emergente
static datetime lt; // tempo da última barra processada

Recebemos o tempo da última barra (emergente):

if(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;

Executamos a verificação do tempo da barra:

if(Shift==0 || tm[0]!=lt){

Se Shift==0, o Expert Advisor trabalha em cada tick. Caso contrário, somente se o valor da variável lt não for igual ao tempo das barras emergentes (uma vez por barra). 

Declaramos as variáveis ​​auxiliares e obtemos os valores do indicador:

double tp,sl; // variáveis para o cálculo de Stop-Loss e Take-Profit

double buf_buy[1];         // para setas de compra
double buf_sell[1];        // para setas de venda

double buf_buy_target[1];  // para alvo de compra
double buf_sell_target[1]; // para alvo de venda

if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

Se houver sinais de negociação, será realizado o cálculo do Stop-Loss, Take-Profit e, além disso, será executada a abertura de posição:

// se houver uma seta, e, nessa barra, a posição buy não estiver aberta
if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
   // Stop-Loss e Take-Profit
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_buy_target[0],_Digits);
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
   }
   // abertura
   if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
   // "lembramos" o tempo da última abertura
   LastBuyTime=tm[0];
}

Se forem utilizados Stop-Loss e Take-Profit fixos (FixedStopLoss=true), para calcular o Take-Profit para o preço da abertura de posição(preço Ask ao comprar), é adicionado o valor da variável TakeProfit multiplicado por _Point. Para calcular o Stop-Loss a partir do preço de abertura, é subtraído o valor da variável do Stop-Loss multiplicado por _Point. Após os cálculos, a função NormalizeDouble() normaliza os valores obtidos deixando apenas o número de dígitos que corresponde à quantidade de dígitos nas cotações, quantidade essa que se pode obter a partir da variável _Digits.

Se não houverem Stop-Loss e Take-Profit fixados, primeiro definimos o valor do Take-Profit e proporcionalmente calculamos o valor do Stop-Loss. Se não for possível abrir a posição, será finalizado o trabalho da função OnTick(), e, no seguinte tick, será executada uma nova tentativa de abrir a posição. Tentativas serão feitas enquanto houver sinal de indicador, isto é, durante uma barra. Se a abertura da variável LastBuyTime for bem-sucedida, será adotado o tempo da barra atual para que nela não seja mais executada a abertura (caso estar trabalhando com ticks, quando Shift=0). A variável LastBuyTime é a variável global do Expert Advisor.

A venda é realizada de maneira semelhante à compra, mas com ligeiras modificações:

// se estiver a seta e, nessa barra, a posição sell não for aberta
if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
   // Stop-Loss e Take-Profit
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_sell_target[0],_Digits);
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
   }
   // abertura
   if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
   // "lembramos" o tempo da última abertura
   LastSellTime=tm[0];        
}  

Durante a compra, em vez da variável LastBuyTime, é utilizada a variável LastSelTime, enquanto os níveis Stop-Loss, Take-Profit são calculados a partir do preço bid.

No final da variável lt é definido o tempo da barra emergente para que o Expert Advisor não faça mais ação nenhuma nesta barra (claro, se funcionar em barras, ou seja, Shift=1). Abaixo está todo o código de função OnTick():

void OnTick(){
   
   datetime tm[1]; // tempo da barra emergente
   static datetime lt; // tempo da última barra processada
   
   // copiamos o tempo
   if(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;
   
   if(Shift==0 || tm[0]!=lt){ // verificação para trabalho na barra

      double tp,sl; // variáveis para o cálculo de Stop-Loss e Take-Profit

      double buf_buy[1];         // para setas de compra
      double buf_sell[1];        // para setas de venda
      
      double buf_buy_target[1];  // para alvo de compra
      double buf_sell_target[1]; // pra alvo de venda     
      
      if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
      if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
      if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
      if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

      // se a seta e, nessa barra, a posição buy não estiverem abertas
      if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
         // Stop-Loss e Take-Profit
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_buy_target[0],_Digits);
            double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
         }
         // abertura
         if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
         // "lembramos" o tempo da última abertura
         LastBuyTime=tm[0];
      }
      
      // se estiver a seta e, nessa barra, a posição sell não for aberta
      if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
         // Stop-Loss e Take-Profit
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_sell_target[0],_Digits);
            double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
         }
         // abertura
         if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
         // "lembramos" o tempo da última abertura
         LastSellTime=tm[0];        
      }      
      
      lt=tm[0];
   }
}

No anexo, o Expert Advisor está no arquivo eWolfeWaves. 

Testamos o Expert Advisor resultante. Se, após o teste, colocarmos o indicador no gráfico, será evidente que a entrada é realizada em cada seta independentemente da existência de posições abertas (fig. 5).


Fig. 5. O Expert Advisor realiza a entrada em cada seta do indicador

Claro, em primeiro lugar, estamos interessados em quão eficaz o indicador resultante na negociação. Os resultados do teste do Expert Advisor com as configurações padrão em todo o histórico, H1, EURUSD, são mostrados na Fig. 6


Fig. 6 Resultados de teste do Expert Advisor em todo o histórico EURUSD H1

No início do intervalo de teste, pode ser vista uma falha significativa, possivelmente, devida à baixa qualidade do histórico na fase inicial. No entanto, mesmo se não for assim, após essa falha, mais o menos a partir dos dados de 1991, começa o período de crescimento robusto. Em geral, os resultados do teste são positivos, mesmo sem optimização e verificações adicionais.

Mais algumas dicas do livro de Bill Wolfe

Além das regras de identificação de ondas, Bill Wolfe em seu livro dá algumas dicas que chama de notas psicológicos e técnicas. Uma das dicas técnicas mais importantes é a de monitorar o volume de ticks, uma vez que nos pontos de reversão pode ser visto um declínio que pode indicar uma reversão. A segunda dica consiste em acompanhar as linhas de tendência. Os movimentos de onda descritos por Bill Wolfe muitas vezes surgem depois da quebra de tendência, em outras palavras, após a ruptura da linha de tendência. Ou seja, as ondas geradas, após o rompimento da tendência, podem ser mais confiáveis. A terceira dica tem a ver com acompanhar a linha 1-4, particularmente, após o ponto 4, e sair no caso de circunstâncias imprevistas, isto é, no caso de um retrocesso de onda, um forte aumento de volume ou uma rápida tomada de bom lucro.

Conclusão

Os resultados positivos do teste do Expert Advisor (mesmo com as configurações padrão) mostram que o método de análise gráfica aqui discutido certamente é eficaz e é de interesse para um estudo mais aprofundado.

Talvez alguém queira melhorar o indicador. No momento, os parâmetros externos do indicador têm três variáveis-coeficientes, isto é: K1, K2, K3. Agora o coeficiente K1 é utilizado para verificar a posição de ponto 3 em relação ao ponto 1; e o ponto 4 em relação ao ponto 1. Talvez, para estas verificações, seja melhor usar coeficientes separados. Por outro lado, o aumento do número de parâmetros complica a optimização do sistema e aumenta, em vez da qualidade da optimização, o risco de ajuste de resultados. Talvez seja melhor combinar os coeficientes K1 e K2. Isso fará com que a configuração do indicador seja mais simples e clara. Por outro lado, pode ser melhor fazer apenas um coeficiente. O código do indicador é claramente dividido por funções, o que facilita quaisquer modificações. Para todos aqueles que desejem, podem experimentar com suas modificações.  

Além de usar o indicador para encontrar ondas de Wolf, ele pode ser usado como um modelo para criar outros indicadores projetados para encontrar outras formas do ZigZag. Basta mudar o código das funções CheckUp e CheckDn. O mais importante é que foi resolvida a questão do acesso aos valores de picos do ZigZag.

Em particular, gostaria de chamar a atenção dos leitores para um truque com a implementação de CurCount, PreCount e LastTime. Este método não é concebido somente para resolver quaisquer objetivos poucos ambiciosos surgidos ao escrever o indicador para este artigo. Ao desenvolver indicadores, há frequentemente uma necessidade nos buffers adicionais para valores auxiliares resultantes de cálculos intermédios. Em cada barra, o valor é movido a partir do elemento de buffer anterior para o atual, e muda apenas ocasionalmente. Nos cálculos, é utilizado o valor de um elemento, e para este fim é utilizado um buffer inteiro. O uso do método com duas variáveis ​​reduzirá significativamente a quantidade de memória usada pelo indicador.

Arquivos do aplicativo

No aplicativo estão anexados os arquivos dos indicadores e Expert Advisors criados neste artigo. Os arquivos são colocados da maneira que eles possam ser distribuídos nas pastas do terminal. Todos os arquivos que se encontram no anexo são os seguintes:

  • Indicators/iWolfeWaves_Step_1.mq5
  • Indicators/iWolfeWaves.mq5
  • Experts/eWolfeWaves.mq5 

Para que tudo funcione corretamente, são necessários os arquivos adicionais a partir do artigo "ZigZag universal":

  • Indicators/iUniZigZagSW.mq5
  • Include/CSorceData.mqh
  • Include/CZZDirection.mqh>
  • Include/CZZDraw.mqh

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

Arquivos anexados |
MQL5.zip (8.67 KB)
Últimos Comentários | Ir para discussão (1)
samuelsps
samuelsps | 24 mai 2017 em 16:49
Como faço para implementar essa técnica no meu MT5?
Análise de gráficos de Balanço/Capital líquido ("equity") de acordo com os símbolos e Expert Advisors ORDER_MAGIC Análise de gráficos de Balanço/Capital líquido ("equity") de acordo com os símbolos e Expert Advisors ORDER_MAGIC
Introduzida a cobertura no MetaTrader 5, surgiu a grande possibilidade de negociar simultaneamente usando Expert Advisors numa só conta de negociação. Ao fazer isto, pode acontecer que exista uma primeira estratégia rentável, uma segunda não-rentável, e, como resultado, o gráfico de lucro flutue perto do zero. Nesse caso, é útil construir gráficos de Balanço e Capital líquido ("equity") para cada estratégia de negociação separadamente.
Receitas MQL5 - Criamos um buffer circular para calcular rapidamente indicadores numa janela deslizante Receitas MQL5 - Criamos um buffer circular para calcular rapidamente indicadores numa janela deslizante
O buffer circular é a maneira mais simples e eficaz de organizar os dados para os cálculos numa janela deslizante. Este artigo descreve como está construído este algoritmo e mostra como usá-lo para fazer o cálculo numa janela deslizante usando um processo simples e eficiente.
Criação de documentos com base em códigos-fonte MQL5 Criação de documentos com base em códigos-fonte MQL5
No artigo é estudada a criação de documentação para um código em linguagem MQL5, partindo da automação de tags (marcação). Além disso, quanto ao programa Doxygen, é descrito seu funcionamento, adequada configuração e obtenção de resultados em vários formatos (HTML, HtmlHelp e PDF).
Interfaces gráficas X: Algoritmo de quebra de linha na caixa de texto multilinha (build 12) Interfaces gráficas X: Algoritmo de quebra de linha na caixa de texto multilinha (build 12)
Nós continuamos com o desenvolvimento do controle da caixa de texto Multilinha. Desta vez, nossa tarefa é implementar um quebra automático de linha no caso da largura da caixa de texto ser excedida ou uma quebra automática de linha inversa do texto para a linha anterior se a oportunidade surgir.