Construímos um ZigZag de osciladores. Exemplo de execução do trabalho segundo os termos de referência

Dmitry Fedoseev | 25 junho, 2018


Sumário

Introdução

No artigo "Como criar uma especificação de requisitos para solicitar um indicador", é apresentado um exemplo de termos de referência para criar um indicador Zigzag de diferentes osciladores. Neste artigo, passo a passo demonstrarei como realizá-lo de acordo com esses termos.

Indicador ZigZag baseado em osciladores

Aqui não vou dar o texto completo dos termos de referência do exercício, por isso, antes de continuar lendo, estude a tarefa no link acima.  

Análise geral dos termos de referência do exercício

Após a primeira leitura, são identificados os requisitos básicos para o desenvolvimento do indicador.

  1. O desenvolvimento é realizado em etapas.
  2. É necessário garantir a maior performance possível do indicador.
  3. O indicador deve ter uma interface gráfica.

Algoritmo do ZigZag. O algoritmo para construir o Zigzag difere do algoritmo clássico.

  1. Em vez de mudar de direção quando se forma um extremo local, o ZigZag faz isso quando o valor do oscilador sai do nível de sobrecompra ou de sobrevenda. 
  2. O ZigZag é construído no gráfico de preços. Assim, cada novo extremo é determinado de acordo com os dados de preço.

Portanto, será necessário prestar atenção a certas características que são consequência disso. 

  1. O valor máximo/mínimo do indicador pode não corresponder ao valor máximo/mínimo do preço. Logo, quando o ZigZag muda de direção, é necessário verificar se, um pouco antes do oscilador entrar na zona de sobrecompra/sobrevenda, é formado um novo valor máximo/mínimo do preço (Fig. 1).



    Fig. 1. A saída do WPR para a zona de sobrecompra ocorre na barra marcada com a seta 1,
    no entanto, um novo segmento do ZigZag deve ser desenhado antes da barra marcada com a seta 2.

  2. Como a mudança de direção do ZigZag é determinada pelo oscilador, seu valor muda conforme é formada a barra. Ou seja, o ZigZag pode mudar de direção, mas, à medida que é formada a barra, a mudança de direção pode ser cancelada. É necessário garantir o correto funcionamento do indicador em tais casos.
  3. Como o novo extremo é determinado pelos preços high/low, a formação de um novo máximo/mínimo não pode ser cancelada à medida que é formada a barra. No entanto, numa barra com um novo extremo, o ZigZag pode virar. Neste caso, o novo máximo/mínimo é cancelado (Fig. 2).


     
    Fig. 2. 1 — o pico do ZigZag está no preço máximo formado pela barra emergente.
    2 — o ZigZag vira, enquanto é cancelado o máximo previamente confirmado

    Claro que esta situação pode ser discutida, uma vez que o MetaTrader 5 possui o estilo de desenho Color ZigZag. Ele teria permitido que o segmento do ZigZag se mantivesse verticalmente, sem transferir seu pico para a esquerda, isto é, para um máximo previamente definido. No entanto, ao usar essa maneira de representar a imagem, não poderemos pintar dois segmentos do ZigZag (o vertical e o oblíquo adjacente a ele) independentemente um do outro. Além disso, essa maneira de desenhar o Zigzag não é muito comum e, se fosse necessário, os termos de referência do exercício deveriam indicar isso. Ou seja, por padrão, é selecionada a opção mais comum.

Exibição. A exibição do ZigZag também tem suas próprias características.

  1. Além da exibição do indicador, no gráfico de preços, pontos coloridos devem marcar as barras nas quais o indicador entra na zona de sobrecompra (pontos amarelos no nível high da barra) e na zona de sobrevenda (pontos verdes no nível low da barra). 
  2. O padrão é detectado com base na disposição mútua de picos e fundos do ZigZag. O segmento do ZigZag que forma o padrão deve ser colorido usando uma cor diferente. Talvez essa coloração seja o mais complexo. Primeiro, não basta marcar a barra em que é localizado o padrão, pois você precisa repintar alguns segmentos do ZigZag no histórico. Em segundo lugar, a viragem do ZigZag e o novo extremo podem ser cancelados à medida que é formada a barra. Por isso, antes de calcular a barra, é necessário limpar o segmento do ZigZag em que pode ser marcado o padrão (devolver-lhe uma cor neutra). E, em terceiro lugar, os padrões (incluindo os multidirecionais) podem se sobrepor. Portanto, ao limpar e pintar o segmento do ZigZag, é impossível interromper a coloração do padrão encontrado anteriormente (Fig. 3).



    Fig. 3. Padrões sobrepostos

    Examinemos o trecho do Zigzag mostrado na Fig. 3. Os segmentos de 1 a 4 constituem um padrão de tendência de alta. Logo, o segmento 1 deve ser pintado de azul. Mas, como antes tinha entrado no padrão de baixa, ele já está pintado de vermelho. Quando surge o segmento 6, é formado outro padrão ascendente (segmentos de 3 a 6). Como antes de cada cálculo de barra é necessário retornar a cor original ao segmento do ZigZag, neste caso apenas é necessário limpar os segmentos 5 e 6, já que os segmentos 3 e 4 já têm a ver com outro padrão.

    Você também pode usar outra variante de coloração, nomeadamente alterar a cor de todos os segmentos incluídos nele, para cada novo padrão. Porém, neste caso, no histórico, o indicador será menos informativo. Por isso, embora seja muito mais complicada, é escolhida a primeira variante.

Interface gráfica do usuário. Os requisitos para a interface gráfica são relativamente simples. O conjunto de controles no formulário é constante e, portanto, não precisa ser alterado dependendo do oscilador selecionado. Dos parâmetros numéricos, são usados dois (níveis de sobrecompra/sobrevenda), e seu conjunto é idêntico para todos os osciladores.

Após entender todas as especificações do exercício, você pode começar a criar o indicador. No final de cada etapa de criação do indicador, é indicado o nome do arquivo correspondente do aplicativo. Se, ao continuar lendo o artigo mais tarde, tiver dificuldade em compreender como adicionar código, você deverá abrir e verificar o arquivo da etapa correspondente no editor.

Etapa 1 — construção do ZigZag

No MetaEditor, crie um novo indicador personalizado com o nome OscZigZagStep1. A fim de marcar o local no código para variáveis, adicione uma variável externa. Na janela para selecionar manipuladores de eventos, selecione a primeira variante, OnCalculate(...,open,high,low,close), não são necessários outros manipuladores. Na janela de opções de exibição, crie dois buffers. Dê ao primeiro buffer o nome de "HighLow", tipo — Color Arrow e dois cores: Golg e LimeGreen. Dê ao segundo buffer o nome de "ZigZag", tipo — Color Section e três cores: Gray, CornflowerBlue e Red (Fig. 4).


Fig. 4. Seleção de opções de exibição na janela do assistente de criação de indicador

Como os pontos de cor estão relacionados às barras, é mais lógico primeiro desenhá-los (mais perto das barras) e depois desenhar no ZigZag. Essa é a razão pela qual os buffers são organizados nessa ordem.

Ao clicar no botão "Concluir", o arquivo do indicador será aberto no editor. Primeiro, corrija os valores do indicator_color1nele, isto é, remova os modelos de cores extras. A cadeia de caracteres com a propriedade indicator_color1 deve ter a seguinte aparência:

#property indicator_color1  clrGold,clrLimeGreen

Da mesma maneira, você precisa corrigir a propriedade indicator_color2 (deixe três cores).

Encontre a cadeia de caracteres com o parâmetro externo criado automaticamente:

input int      Input1;

Remova-o, e, em vez dele, declare as variáveis para os parâmetros do indicador WPR:

input int         WPRperiod   =  14;
input double      WPRmax      =  -20;
input double      WPRmin      =  -80;

Abaixo, declare uma variável para o identificador:

int h;

No início da função OnInit(), carregue o indicador:

h=iWPR(Symbol(),Period(),WPRperiod);
if(h==INVALID_HANDLE){
   Print("Can't load indicator");
   return(INIT_FAILED);
}  

Na função OnDeinit(), libere o identificador:

void OnDeinit(const int reason){
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
   }
}  

Ao usar o Assistente para criar o indicador, crie os buffers exibidos, mas também você precisa dos buffers auxiliares. Por exemplo, agora você agora precisa de um buffer para os valores do oscilador. Aumente a propriedade indicator_buffers numa unidade (coloque o valor 5 em vez de 4):

#property indicator_buffers 5

Para o buffer com os valores do oscilador, adicione mais uma matriz ao lugar onde já estão declaradas as matrizes para buffers:

double         wpr[];

Na função OnInit(), indique que essa matriz é usada como um buffer de indicador para cálculos intermediários. O código é adicionado ao final da função OnInit():

SetIndexBuffer(4,wpr,INDICATOR_CALCULATIONS); 

Vá para a função OnCalculate(), escreva o código padrão para calcular o intervalo de cálculo de barras e copie os valores do oscilador wpr para o buffer:

int start;

if(prev_calculated==0){
   start=0;
}
else{
   start=prev_calculated-1;
}

if(CopyBuffer(h,0,0,rates_total-start,wpr)==-1){
   return(0);
}

Agora você pode gravar um ciclo padrão de indicador e desenhar pontos onde o oscilador entra em zonas de sobrecompra/sobrevenda:

for(int i=start;i<rates_total;i++){
   HighLowBuffer[i]=EMPTY_VALUE;
   if(wpr[i]>WPRmax){
      HighLowBuffer[i]=high[i];
      HighLowColors[i]=0;
   }
   else if(wpr[i]<WPRmin){
      HighLowBuffer[i]=low[i];
      HighLowColors[i]=1;      
   }      
}

Nesta etapa, o indicador pode ser anexado ao gráfico. Também vamos anexar o WPR padrão e verificar o trabalho realizado (Fig. 5).


Fig. 5. Exibição das zonas de sobrecompra/sobrevenda no gráfico de preços

Continuamos a criação do ZigZag. Serão necessários vários buffers auxiliares: um para a direção atual do indicador e dois para os índices de barras que apontam para o último pico e fundo do ZigZag:

double         dir[]; // para a direção
double         lhb[]; // índice da barra do último pico
double         llb[]; // índice da barra do último fundo

Como você adicionou três buffers, precisa aumentar a propriedade que determina o número de buffers do indicador:

#property indicator_buffers 8

Na função OnInit(), aplicamos a função SetIndexBuffer() às matrizes recém declaradas:

SetIndexBuffer(5,dir,INDICATOR_CALCULATIONS);  
SetIndexBuffer(6,lhb,INDICATOR_CALCULATIONS);   
SetIndexBuffer(7,llb,INDICATOR_CALCULATIONS);    

Este código está localizado imediatamente após a última chamada da função SetIndexBuffer() já disponível na função OnInit().

Agora um momento muito importante. A fim de que o buffer Color Section funcione corretamente, você deve especificar o valor vazio 0 para ele. Caso contrário, em vez do ZigZag, no gráfico serão exibidas essas linhas estranhas:


Fig. 6 Se você não especificar o valor vazio para o buffer do tipo Color Section, a exibição do ZigZag não será realizada corretamente

A fim de especificar um valor vazio, ao final da função OnInit(), antes da linha com return, adicionamos a cadeia de caracteres:

PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); 

Observe que o valor do primeiro parâmetro é 1. Nesse caso, trata-se do índice no grupo de buffers exibidos, ou seja, 1 corresponde ao buffer ZigZagBuffer[].

Com a função OnInit(), tudo está pronto. Agora volte novamente para a função OnCalculate() e continue a escrever o código no ciclo de indicador padrão.

No início do ciclo de indicador, após a primeira cadeia de caracteres, com a limpeza do buffer HighLowBudder, você move os dados ao longo dos buffers auxiliares:

lhb[i]=lhb[i-1];      
llb[i]=llb[i-1];
dir[i]=dir[i-1];

Em vosso código, onde definida a saída do oscilador para as zonas de sobrecompra/sobrevenda, adicione a configuração de direção ao buffer dir[]:

if(wpr[i]>WPRmax){
   HighLowBuffer[i]=high[i];
   HighLowColors[i]=0;
   dir[i]=1;
}
else if(wpr[i]<WPRmin){
   HighLowBuffer[i]=low[i];
   HighLowColors[i]=1;      
   dir[i]=-1;
} 

Agora o mais interessante no primeira etapa é a construção do ZigZag. A direção do ZigZag é definida, no buffer dir[] é posicionado o valor de 1 quando a direção é para cima e -1 quando a direção é para baixo. Ainda é necessário determinar em quais barras a direção muda de modo imediato. A base da construção do ZigZag será o seguinte código dividido em 4 ramos:

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // mudança na direção de baixo para cima
   }
   else{
      // continuação do movimento para cima
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){ 
      // mudança na direção de cima para abaixo
   }
   else{
      // continuação do movimento para baixo
   }      
}

Vamos considerar em detalhes a mudança na direção do ZigZag de baixo para cima e a continuação para cima. Os outros dois ramos serão simétricos.

Mudança da direção para cima

1. No intervalo de barras desde o último fundo até à barra calculada (a barra do fundo não está incluída no intervalo), procure o valor máximo do preço:

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // mudança na direção de baixo para cima
      // busca do máximo
      int hb=i;
      for(int j=i;j>llb[i];j--){
         if(high[j]>high[hb]){
            hb=j;
         }
      }
      //...
   }
   else{
      // continuação do movimento para cima
   }      
}

2. Coloque o ponto do ZigZag na barra encontrada, especifique o índice dessa barra no buffer lhb[] e defina a cor neutra usando o buffer ZigZagColor: 

ZigZagBuffer[hb]=high[hb];
lhb[i]=hb;            
ZigZagColors[hb]=0;

3. Quando você recalcula esta barra, pode acontecer que o valor do oscilador tenha mudado e não deva haver um ponto nele. Logo, você precisa apagá-lo. Isso geralmente é feito limpando o buffer no início do ciclo de indicador:

ZigZagBuffer[i]=0;

Mas, neste caso, o pico - do ZigZag - que está sendo formado dista da barra calculada um número desconhecido de barras (Fig. 1). Por isso, é necessário manter tanto o índice da barra em que está localizado o novo pico quanto a hora da barra que está sendo calculada:

NewDotTime=time[i];
NewDotBar=hb;

As variáveis ​​NewDotTime e NewDotBar são declaradas no nível global do indicador.

4. No início do ciclo de indicador, verifique que o valor da variável NewDotTime corresponda à barra que está sendo calculada. Se houver uma correspondência, remova o novo ponto do ZigZag:

if(NewDotTime==time[i]){
   ZigZagBuffer[NewDotBar]=0;  
}

Movimento para cima

Considere o segmento de código que define a continuação do movimento ascendente. Se o preço high da seguinte barra exceder o valor - do ZigZag - previamente confirmado, exclua o ponto antigo e coloque um novo:

if(high[i]>ZigZagBuffer[(int)lhb[i]]){ 
   // remover o ponto antigo
   ZigZagBuffer[(int)lhb[i]]=0;
   // colocar um novo
   ZigZagBuffer[i]=high[i];
   ZigZagColors[i]=0;
   lhb[i]=i;
}

No início do ciclo do indicador, antes de recalcular a barra, é necessário retornar o indicador ao seu estado original, isto é, retornar o ponto remoto ao seu lugar:

ZigZagBuffer[(int)lhb[i]]=high[(int)lhb[i]];
ZigZagBuffer[(int)llb[i]]=low[(int)llb[i]];  

Para que, quando o indicador comece a trabalhar, não aconteça o erro de saída dos limites da matriz, os elementos iniciais dos buffers lhb[] e llb[]devem ser inicializados com valores zero. Além disso, é necessário zerar as variáveis NewDotTime e NewDotBar durante a avaliação do intervalo de cálculo:

if(prev_calculated==0){
   start=1;
   lhb[0]=0;
   llb[0]=0;   
   NewDotTime=0; 
}
else{
   start=prev_calculated-1;
}

Neste ponto, acaba a primeira etapa de criação do indicador. No anexo ao artigo, o indicador nesta etapa tem o nome de OscZigZagStep1.mq5.

Etapa 2 — detecção do padrão e coloração

Para identificar o padrão, você precisa comparar 5 picos do ZigZag. Para que o indicador funcione rapidamente (e esse é o requisito principal dos termos de referência), é indesejável procurar cada vez esses picos no ciclo em todo o indicador. O melhor será, à medida que aparecem novos picos, ir armazenando-os numa matriz separada, fornecendo assim acesso direto e rápido a eles.   

Os dados sobre os picos do Zigzag serão armazenados numa matriz de estruturas. A estrutura deve conter campos para o índice da barra, do valor, da direção; e mais um campo do tipo bool. Nele será armazenado o valor true, se o pico for o último no padrão (para limitar a coloração do ZigZag ao padrão reconhecido anteriormente). Descreva a estrutura e adicione a matriz:

struct SZZDot{
   int bar;
   double val;
   int dir;
   bool pat;
};

SZZDot ZZDot[];

Em seguida, adicione a chamada da função AddZZDot() ao final de cada uma das quatro partes do código de plotagem do ZigZag. Ela adicionará novos picos do ZigZag à matriz ZZDot[]:

if(dir[i]==1){ 
   if(dir[i-1]==-1){          
      //...
      AddZZDot(1,high[hb],hb,i);
   }
   else{ 
      if(high[i]>ZigZagBuffer[(int)lhb[i]]){
         //...
         AddZZDot(1,high[i],i,i);
      }
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){
      //...
      AddZZDot(-1,low[lb],lb,i);
   }
   else{
      if(low[i]<ZigZagBuffer[(int)llb[i]]){
         //...
         AddZZDot(-1,low[i],i,i);
      }
   }      
}

À função AddZdot() transfira os quatro parâmetros: direção, valor, índice de barra com pico, índice de barra calculada (a função em si mesma será examinada um pouco mais tarde). Para o número de picos encontrados (elementos ocupados na matriz AADot[]), use o buffer de indicador cnt[]. Declare a matriz cnt[]:

double         cnt[];

Na função OnInit(), para ela chame a função SetIndexBuffer():

SetIndexBuffer(8,cnt,INDICATOR_CALCULATIONS);  

Altere o valor da propriedade que determina o número de buffers:  

#property indicator_buffers 9

No início do ciclo de indicador, mova dentro do buffer o último valor:

cnt[i]=cnt[i-1];

Acima já dissemos que a reversão do ZigZag revelada durante o cálculo da barra pode desaparecer no próximo cálculo da mesma barra. E assim, em consequência, deve ser excluído o pico armazenado na matriz. Mas essa remoção não é feita diminuindo a matriz, mas, sim, diminuindo o contador do número de elementos ocupados da matriz (buffer cnt[]). Isso aumenta significativamente a velocidade do indicador.

Examinemos a função  AddZdot():

void AddZZDot(int d,double v,int b,int i){
   
   int c=(int)cnt[i];

   if(c==0){ 
      // quando o indicador é iniciado ou quando é totalmente recalculado
      ArrayResize(ZZDot,1024);
      ZZDot[c].dir=d;
      ZZDot[c].val=v;
      ZZDot[c].bar=b;
      ZZDot[c].pat=false;
      cnt[i]=1;
   }
   else{
      if(ZZDot[c-1].dir==d){
         // atualização do pico da mesma direção
         ZZDot[c-1].val=v;
         ZZDot[c-1].bar=b;         
      }
      else{
         // adição de um novo pico
         // incremento da matriz, conforme necessário, com blocos de 1024 elementos
         if(c>=ArraySize(ZZDot)){ 
            ArrayResize(ZZDot,c+1024);
         }
         // adição de um novo pico
         ZZDot[c].dir=d;
         ZZDot[c].val=v;
         ZZDot[c].bar=b;
         ZZDot[c].pat=false;
         cnt[i]=c+1;
      }
   }
}

Ao iniciar o indicador ou ao recalcular totalmente a matriz, é definido o tamanho 1024, ao seu elemento inicial são atribuídos os picos, enquanto o contador do número de picos aumenta em 1. Nas chamadas subsequentes da função, é verificada a direção do último pico na matriz. Se corresponder aos parâmetros com os quais é chamada a função, serão atualizados os dados sobre o último pico. Se a direção é oposta, é adicionado um novo pico. 

Acima, na análise dos termos de referência do exercício, eu já expliquei que com a reversão do ZigZag, o último pico da direção oposta pode ser transferido para uma barra anterior (Fig. 2). É por isso que, antes da execução do código principal do ZigZag, para o último elemento ocupado da matriz ZZDot você deve definir o valor conhecido do pico. Isso é feito no início do ciclo do indicador:

if(cnt[i]>0){
   int ub=(int)cnt[i]-1;
   if(ZZDot[ub].dir==1){
      ZZDot[ub].bar=(int)lhb[i];
      ZZDot[ub].val=high[(int)lhb[i]];
   }
   else{
      ZZDot[ub].bar=(int)llb[i];
      ZZDot[ub].val=low[(int)llb[i]];         
   }
}

Agora, se, na barra calculada, surgir um novo pico, seu valor será atualizado na matriz ZZDot, enquanto se for formada uma reversão, permanecerá o valor conhecido anteriormente do pico.

Antes do primeiro cálculo do indicador e do seu completo recálculo, é necessário inicializar o elemento inicial da matriz cnt[]:

if(prev_calculated==0){
   //...
   cnt[0]=0;
}
else{
   start=prev_calculated-1;
}

Tendo dados em todos os picos do ZigZag e tendo fácil acesso a eles, considere reconhecimento do padrão e da sua coloração. Isto é possível se houver pelo menos 5 picos do ZigZag:

if(cnt[i]>=5)

Calcule o índice do último elemento na matriz de picos:

int li=(int)cnt[i]-1;

Indique que, neste pico, não é detectado o padrão. Isso é necessário para devolver a cor neutra ao ZigZag:

ZZDot[li].pat=false;

Devolva à parte do ZigZag a cor original:

for(int j=0;j<4;j++){
   if(ZZDot[li-j].pat){
      break;
   }
   ZigZagColors[ZZDot[li-j].bar]=0;
}

Atenção: assim que for encontrado o vértice com o padrão, o ciclo terminará.

Verifique as condições do padrão:

if(ZZDot[li].dir==1){ // para cima
   if(
      ZZDot[li].val>ZZDot[li-2].val && 
      ZZDot[li-2].val>ZZDot[li-4].val && 
      ZZDot[li-1].val>ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 
      // coloração 
   }
}
else{ // para baixo
   if( 
      ZZDot[li].val<ZZDot[li-2].val && 
      ZZDot[li-2].val<ZZDot[li-4].val && 
      ZZDot[li-1].val<ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 		
      // coloração                 
   }            
}

Resta escrever o código de coloração. Ele é semelhante ao código de limpeza. Para a direção ascendente:   

for(int j=0;j<4;j++){
   if(j!=0 && ZZDot[li-j].pat){
      break;
   }
   ZigZagColors[ZZDot[li-j].bar]=1;
} 

Diferentemente do código de limpeza, a saída do ciclo não é executada quando j=0.

Isso completa a segunda etapa de criação do indicador. O indicador fica assim: 


Fig. 7. Aparência do indicador na final da etapa №2. 

No anexo ao artigo, o indicador nesta etapa tem o nome OscZigZagStep2.mq5. 

Etapa 3 — adição de osciladores

Descreva a enumeração:

enum EIType{
   WPR,
   CCI,
   Chaikin, 
   RSI,
   Stochastic
};

Declare a variável externa para selecionar o oscilador:

input EIType               Type        =  WPR;

Adicione os parâmetros dos outros osciladores:

// CCI
input int                  CCIperiod   =  14;
input ENUM_APPLIED_PRICE   CCIprice    =  PRICE_TYPICAL;
input double               CCImax      =  100;
input double               CCImin      =  -100;
// Chaikin
input int                  CHfperiod   =  3;
input int                  CHsperiod   =  10;
input ENUM_MA_METHOD       CHmethod    =  MODE_EMA;
input ENUM_APPLIED_VOLUME  CHvolume    =  VOLUME_TICK;
input double               CHmax       =  1000;
input double               CHmin       =  -1000;
// RSI
input int                  RSIperiod   =  14;
input ENUM_APPLIED_PRICE   RSIprice    =  PRICE_CLOSE;
input double               RSImax      =  70;
input double               RSImin      =  30;
// Stochastic
input int                  STperiodK   =  5;  
input int                  STperiodD   =  3;
input int                  STperiodS   =  3;
input ENUM_MA_METHOD       STmethod    =  MODE_EMA;
input ENUM_STO_PRICE       STprice     =  STO_LOWHIGH;
input double               STmax       =  80;
input double               STmin       =  20; 

Declare a variável para os níveis:

double max,min;

No início da função OnStart, faça a escolha do oscilador:

switch(Type){
   case WPR:
      max=WPRmax;
      min=WPRmin;  
      h=iWPR(Symbol(),Period(),WPRperiod);      
   break;
   case CCI:
      max=CCImax;
      min=CCImin;  
      h=iCCI(Symbol(),Period(),CCIperiod,CCIprice);  
   break;      
   case Chaikin:
      max=CHmax;
      min=CHmin;  
      h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume);  
   break;          
   case RSI:
      max=RSImax;
      min=RSImin;  
      h=iRSI(Symbol(),Period(),RSIperiod,RSIprice);  
   break;   
   case Stochastic:
      max=STmax;
      min=STmin;  
      h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice);  
   break; 
}

if(h==INVALID_HANDLE){
   Print("Can't load indicator");
   return(INIT_FAILED);
}

Na função OnCalculate(), mude as variáveis WPRmax e WPmin pelas variáveis max e min. 

Neste ponto, acaba a terceira etapa de criação do indicador, agora você pode selecionar o oscilador na janela de propriedades do indicador. No anexo ao artigo, o indicador nesta etapa tem o nome OscZigZagStep3.mq5.

Etapa 4 — criação da interface gráfica do usuário

Para criar a interface gráfica, use a biblioteca IncGUI. A esta biblioteca é dedicada a série de artigos "Controles gráficos personalizados" que consiste em três partes (parte 1, parte 2, parte 3). A última versão revisada da biblioteca (IncGUI_v4.mqh) está anexada ao artigo "Oscilador universal com interface gráfica". Ela será anexado a este artigo. Antes de começar a trabalhar com a interface gráfica do usuário, copie o arquivo IncGUI_v4.mqh para a pasta MQL5/Includes da pasta de dados do terminal.

Considere o processo de criação da interface gráfica em etapas.

Conexão da biblioteca. Faça uma cópia do indicador OscZigZagStep3 com o nome OscZigZagStep3 e conecte-o à biblioteca:

#include <IncGUI_v4.mqh>

Classe de formulário. No arquivo IncGUI_v4.mqh, você pode encontrar a classe CFormTemplate. Ela é um tipo de modelo para criar formulários. Copie-o, cole-o no arquivo do indicador imediatamente após conectar a biblioteca, renomeie-o de CFormTemplate para CForm.

Propriedades do formulário. No método MainProperties(), defina as propriedades básicas do formulário:

m_Name         =  "Form";
m_Width        =  200;
m_Height       =  150;
m_Type         =  2;
m_Caption      =  "ZigZag on Oscillator";
m_Movable      =  true;
m_Resizable    =  true;
m_CloseButton  =  true;
  • Variável m_Name — nome do formulário (prefixo de todos os objetos gráficos que compõem o formulário).
  • Variável m_Width e m_Height — largura e a altura do formulário em pixels.
  • Variável m_Type — tipo de formulário. Se o valor for 2, haverá um botão fechar na parte inferior do formulário.
  • Variável m_Caption — cabeçalho do formulário.
  • Variável m_Movable — formulário flutuante, no canto superior esquerdo do formulário haverá um botão para deslocamento.
  • Variável m_Resizable — o formulário pode ser minimizado/expandido, o botão para isso estará no canto superior direito.
  • Variável m_CloseButton — o formulário pode se fechar, o botão para isso estará no canto superior direito.

Controles. Criação de controles de formulário. O formulário terá dois quadros. Num quadro haverá um grupo de botões de rádio, no outro, dois campos de entrada. Na seção public da classe formulário, coloque o formulário:

CFrame m_frm1; // quadro 1
CFrame m_frm2; // quadro 2 
CRadioGroup m_rg; // grupo de botões de rádio      
CInputBox m_txt_max; // campo de texto para o nível superior    
CInputBox m_txt_min; // campo de texto para o nível inferior

Inicialização de controles. No método OnInitEvent(), inicialize os controles.

Inicialização do primeiro quadro com uma largura/altura de 85/97 pixels, com um cabeçalho "Osc Type" e com o local para um cabeçalho de largura de 44 pixels:

m_frm1.Init("frame1",85,97,"Osc Type",44);

Neste quadro, será localizado um grupo de botões de rádio.

O segundo quadro com as mesmas dimensões, com o cabeçalho "Levels" e com o local para um cabeçalho de largura de 32 pixels:

m_frm2.Init("frame2",85,97,"Levels",32);

Neste quadro, haverá campos para inserir níveis.

Inicialização do grupo de botões de rádio:

m_rg.Init();

Adição de botões de rádio ao grupo:

m_rg.AddButton(" WPR",0,0);
m_rg.AddButton(" CCI",0,16);
m_rg.AddButton(" Chaikin",0,32);
m_rg.AddButton(" RSI",0,48);            
m_rg.AddButton(" Stochastik",0,64); 

Inicialização de campos de texto para inserir os níveis superior e inferior:

m_txt_max.Init("max",45,-1," Max");
m_txt_min.Init("min",45,-1," Min");

Ambos os campos têm uma largura de 45 pixeis, permitem a entrada de texto (terceiro parâmetro -1), num deles o rótulo "Max", no segundo - "Min".

Exibição de controles. No método OnShowEvent(), chame os métodos Show() de todos os controles e indique suas coordenadas no formulário:

m_frm1.Show(aLeft+10,aTop+10);
m_frm2.Show(aLeft+105,aTop+10);
m_rg.Show(aLeft+17,aTop+20);
m_txt_max.Show(aLeft+115,aTop+30);
m_txt_min.Show(aLeft+115,aTop+50);     

Ocultamento de controles. No método OnHideEvent(), oculte todos os controles:

m_frm1.Hide();
m_frm2.Hide();            
m_rg.Hide();
m_txt_max.Hide();
m_txt_min.Hide(); 

Cabeçalho de formulário. Como, ao escolher diferentes osciladores, é bom exibir seus nomes no cabeçalho do formulário, adicione o método para alterar o texto no cabeçalho à seção public da classe do formulário:

void SetCaption(string str){
   m_Caption="ZigZag on "+str;
   ObjectSetString(0,m_Name+"_Caption",OBJPROP_TEXT,m_Caption);
}

Criação do objeto de formulário. Crie o objeto da classe CForm:

CForm form;

Eventos de formulário. A fim de que o formulário e os controles respondam às ações do usuário, o método Event() deve ser chamado da função OnChartEvent() do indicador. Dependendo do tipo de evento, o método retorna diferentes valores. O fechamento do formulário corresponde ao valor de 1, neste caso, é necessário remover o indicador do gráfico:

if(form.Event(id,lparam,dparam,sparam)==1){
   ChartIndicatorDelete(0,0,MQLInfoString(MQL_PROGRAM_NAME)); 
   ChartRedraw();
}

Eventos de controle. O indicador deve ser alterado segundo o evento do grupo de botões de rádio, enquanto os níveis de sobrecompra/sobrevenda devem mudar de acordo com os eventos de alteração dos valores nos campos de entrada. Em ambos os casos, o indicador é totalmente recalculado. 

Leve a parte do código em que é realizada a escolha do indicador na função OnInit() para uma função separada:

bool LoadIndicator(int aType){
   switch(aType){
      case WPR:
         max=WPRmax;
         min=WPRmin;  
         h=iWPR(Symbol(),Period(),WPRperiod);      
      break;
      case CCI:
         max=CCImax;
         min=CCImin;  
         h=iCCI(Symbol(),Period(),CCIperiod,CCIprice);  
      break;      
      case Chaikin:
         max=CHmax;
         min=CHmin;  
         h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume);  
      break;          
      case RSI:
         max=RSImax;
         min=RSImin;  
         h=iRSI(Symbol(),Period(),RSIperiod,RSIprice);  
      break;   
      case Stochastic:
         max=STmax;
         min=STmin;  
         h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice);  
      break; 
   }
   
   if(h==INVALID_HANDLE){
      Print("Can't load indicator");
      return(false);
   }   
   
   return(true);
   
}   

Ela será chamada a partir da função OnInit() do indicador (no início) e pelo evento de botão de rádio. Imediatamente após selecionar o indicador na função OnInit(), inicialize o formulário, defina os valores para os controles e exiba o formulário:

if(!LoadIndicator(Type)){
   return(INIT_FAILED);
}

form.Init(1);
form.m_rg.SetValue(Type);
form.m_txt_max.SetValue(max);   
form.m_txt_min.SetValue(min);  
form.SetCaption(EnumToString(Type));
form.Show(5,20);

Na função OnChartEvent(), processe os eventos dos controles. Evento de botão de rádio para alterar o indicador:

if(form.m_rg.Event(id,lparam,dparam,sparam)==1){
   
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
      h=INVALID_HANDLE;
   }      
   
   if(!LoadIndicator(form.m_rg.Value())){
      Alert("Can't load indicator");
   }
   
   form.m_txt_max.SetValue(max);   
   form.m_txt_min.SetValue(min);    

   EventSetMillisecondTimer(100);
}

Nesse caso, primeiro o identificador do indicador é liberado pela função IndicatorRelease(), é selecionado um novo indicador, são definidos novos valores para os campos de entrada e é iniciado o temporizador. O uso de um temporizador é necessário, porque, quando o indicador é recalculado, podem ocorrer erros de atualização de dados. Nesses casos, você precisará repetir a tentativa de recálculo até conseguir.

Alteração de níveis:

if(form.m_txt_max.Event(id,lparam,dparam,sparam)==1 ||
   form.m_txt_min.Event(id,lparam,dparam,sparam)==1
){
   max=form.m_txt_max.ValueDouble();
   min=form.m_txt_min.ValueDouble();      
   EventSetMillisecondTimer(100);
}

Pelo evento dos campos de entrada, são atribuídos os novos valores às variáveis ​​min e max e é iniciado o temporizador.  

Na função OnTimer(), é recalculado o indicador. Se isto acontecer com sucesso, o temporizador é desligado e o indicador continua a funcionar normalmente - de acordo com os ticks. Todas as ações necessárias para o recálculo do indicador são consideradas em detalhe no artigo mencionado acima "Oscilador universal com interface gráfica". Portanto, considere apenas diferenças fundamentais. O oscilador universal era calculado num método de classe que não requeria dados de preço, aqui você precisa chamar a função OnCalculate() e transferir para ela matrizes com preços. Declare as matrizes:

datetime time[];
double open[];
double high[];
double low[];
double close[];
long tick_volume[];
long volume[];
int spread[];

Obtenha o número de barras:

int bars=Bars(Symbol(),Period());
      
if(bars<=0){
   return;
}

Para construir o ZigZag, não são necessários todos os dados de preço, mas, sim, apenas três matrizes: time, high, low. Apenas copie-os:

if(CopyTime(Symbol(),Period(),0,bars,time)==-1){
   return;
}

if(CopyHigh(Symbol(),Period(),0,bars,high)==-1){
   return;
}      

if(CopyLow(Symbol(),Period(),0,bars,low)==-1){
   return;
} 

Ao testar o indicador, foi detectado um problema, isto é, o número de dados copiados é às vezes menor do que o número de barras obtido pela função Bars(). O tamanho dos buffers de indicador corresponde ao valor da função Bars(). Por essa razão, para a exibição correta do indicador, é necessário aumentar as matrizes com os dados copiados e mover os dados para o final:

if(ArraySize(time)<bars){
   int sz=ArraySize(time);
   ArrayResize(time,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      time[j]=time[i];
   }   
}

if(ArraySize(high)<bars){
   int sz=ArraySize(high);
   ArrayResize(high,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      high[j]=high[i];
   }
}      

if(ArraySize(low)<bars){
   int sz=ArraySize(low);
   ArrayResize(low,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      low[j]=low[i];
   }
} 

Resta chamar a função OnCalculate():

int rv=OnCalculate(
            bars,
            0,
            time,
            open,
            high,
            low,
            close,
            tick_volume,
            volume,
            spread
);

Se a função OnCalculte() funciona sem erros, desligue o temporizador: 

if(rv!=0){
   ChartRedraw();     
   EventKillTimer();
   form.SetCaption(EnumToString((EIType)form.m_rg.Value()));
}

O código completo da função OnTimer(), assim como o indicador totalmente concluído podem ser vistos no anexo ao arquivo OscZigZagStep4.mq5.

Quando o indicador é anexado ao gráfico, o formulário com os controles deve aparecer no canto superior esquerdo (Fig. 8).


Fig. 8. A interface gráfica na etapa 4

Fim do artigo

Demonstrei a criação de um indicador exatamente de acordo com os termos de referência propostos para o exercício. No entanto, a precisão do desempenho é apenas parte do trabalho. No nosso caso, quanto à parte técnica, o exercício foi preparado por um especialista experiente, obviamente conhecedor do terminal, suas capacidades e suas características de trabalho com indicadores. Mas, mesmo assim, seria muito bem-vinda uma discussão com o autor no que diz respeito a certos pontos no exercício, em particular ao colorir o Zigzag.

Quando um padrão é identificado, são repintados vários segmentos do ZigZag anteriores, o que pode levar a um erro ao examinar o indicador no histórico. O fato de que o padrão pode ser adicionado torna a análise visual ainda mais difícil. Na prática, o padrão é necessário para tomar uma decisão de negociação. Ela pode ser tomada no momento em que o padrão aparece, ou mais tarde, mas não antes. Sendo assim, em vez de colorir o ZigZag, pode-se sugerir desenhar setas na barra em que é revelado o padrão. Outra solução é desenhar, enquanto existir um padrão, linhas horizontais numa série de barras, mas somente a partir do momento em que esse padrão for detectado no futuro.  

Além disso, no processo de criação do indicador, foi revelada uma característica inesperada e não tão óbvia, impensável nem na primeira leitura do exercício nem durante a compilação. Eu quero dizer que há casos em que é necessário excluir o último máximo/mínimo do ZigZag quando ele vira. Como mencionado no artigo, você pode usar o buffer do Color Zigzag, mas, nesse caso, haveria dificuldades em colori-lo, porque o conjunto de buffers Color ZigZag tem dois buffers de dados e apenas um para a cor. Se na mesma barra ambos os buffers de dados tiverem valores (o caso quando através da barra passa uma linha vertical), a cor indicada no buffer de cores será atribuída aos dois segmentos ZigZag de uma só vez. Em vez do Color ZigZag, era possível usar simplesmente o ZigZag, repintar os segmentos do ZigZag com objetos gráficos, ou simplesmente colocar setas ou pontos. Em geral, sejam quais forem os termos de referência do exercício, eles requerem uma leitura muito cuidadosa e uma discussão preliminar.

Arquivos do aplicativo

Todos os arquivos são organizados em pastas, como devem estar localizados no terminal. Na pasta MQL5/Indicators estão os arquivos correspondentes às etapas de criação do indicador: OscZigZagStep1.mq5, OscZigZagStep2.mq5, ОscZigZagStep3.mq5,  OscZigZagStep4.mq5.

Na pasta MQL5/Includes está o arquivo IncGUI_v4.mqh que é necessário para criar uma interface gráfica no indicador OscZigZagStep4.