English Русский 中文 Español Deutsch 日本語
Indicador universal RSI para operação simultânea em dois sentidos

Indicador universal RSI para operação simultânea em dois sentidos

MetaTrader 5Exemplos | 28 setembro 2018, 11:49
7 154 1
Anatoli Kazharski
Anatoli Kazharski

Sumário

Introdução

Ao desenvolver algoritmos de negociação, quase sempre enfrentamos o desafio de como determinar onde começa e termina a tendência/fase de correção. É difícil encontrar uma solução definitiva. Porém, é possível podermos alcançar esse objetivo combinando estratégias de tendência e de fase de correção num só algoritmo. Neste artigo, criaremos um indicador universal no qual serão combinados sinais para diferentes tipos de estratégias. Tentaremos simplificar ao máximo o recebimento de sinais no EA para realizar operações de negociação. Exemplificaremos a combinação de diferentes indicadores num único indicador. Para fazer isso, aplicaremos programação orientada a objetos — implementaremos cada indicador (ou parte dele) como uma classe que se conecta ao arquivo principal de programa. 

Assim, nossa tarefa será programar um EA que combina duas estratégias de negociação, nomeadamente uma de tendência e outra de fase de correção. Assumiremos que operar em dois sentidos simultaneamente seja mais eficaz, providenciando uma estratégia mais estável. Nesse caso, será fácil conectar ao EA apenas um indicador que gera os sinais para os dois tipos de estratégia. Ele permitira implementar um sistema complexo para determinar sinais de compra e de venda. Talvez seja necessário combinar vários indicadores idênticos com configurações diferentes, ou, até mesmo, juntar dois diferentes, isto é, um principal com outro de filtro (auxiliar). Esses esquemas são fáceis de implementar com a ajuda de POO.

O diagrama abaixo mostra como as duas classes dos indicadores CIndicator1 e CIndicator2 são conectadas ao arquivo principal do indicador Indicator.mq5. O CIndicator2 é um indicador auxiliar, os resultados dos seus cálculos são necessários para o CIndicator1. Aqui aplicaremos um método para determinar a barra verdadeira, da qual já falamos no artigo sobre a criação de indicadores multimoedas. Para este artigo, é escrita a classe separada CFirstTrueBar. Ela se conectará a todos os indicadores para excluir o cálculo nas barras que não pertencem ao timeframe atual.

 Fig. 1 – Um dos esquemas possíveis para criar um indicador usando POO.

Fig. 1. Um dos esquemas possíveis para criar um indicador usando POO.


Selecionando o indicador

Para gerar sinais, podemos selecionar qualquer um dos indicadores que vêm com o terminal. Eles possuem o mesmo fundamento e, geralmente, nenhum deles tem mais vantagens do que outros. Porém, algumas combinações de indicadores e filtros serão mais eficazes dependendo do intervalo de tempo.

No entanto, para facilitar a pesquisa, o melhor é focar a seleção em indicadores de oscilador. Eles podem ser usados a fim de definir sinais de tendência e de fase de correção. Inclusive, os dados do oscilador podem ajudar a plotar o canal de preços. Como resultado, obtemos a capacidade de criar um indicador universal, o que é muito conveniente quando se quer desenvolver estratégias de negociação de qualquer complexidade.

Como exemplo, nesta série de artigos, usaremos o indicador RSI (Relative Strength Index). Abaixo está o resultado dos cálculos deste indicador com período 8, no gráfico AUDUSD, H1

 Fig. 2. Indicador Relative Strength Index.

Fig. 2. Indicador Relative Strength Index.

À primeira vista, parece fácil lucrar com os sinais deste indicador, mas essa é apenas uma sensação errônea. Antes de ter um melhor entendimento para avançar, é necessário trabalhar muito, sem garantias de que você atingirá seus objetivos. 

Consideremos o caso mais simples e óbvio, isto é, quando pensamos que há chance de lucrar quando a linha do indicador cruza os níveis padrão: 70/30. O cruzamento do nível de 70 de cima para baixo é um sinal de venda. O cruzamento do nível de 30 de baixo para cima é um sinal de compra. Porém, vemos muitos sinais falsos e, inclusive, que o preço se afasta na direção oposta à posição aberta. 

Trarei mais um exemplo da análise de sinais deste indicador (veja fig. 3). Vemos que o preço desce durante muito tempo. Linhas vermelhas indicam os sinais que se geram quando o indicador cruza o nível de 30 de baixo para cima. Se seu algoritmo vier programado com a compra segundo esses sinais, você obterá um rebaixamento flutuante. Se, a cada abertura de posição, você colocar Stop-Loss, perdas serão fixadas várias vezes. Sendo que, nesse caso, entre o último sinal de compra e o sinal de venda (linha verde), o preço não chegará a um resultado positivo, ou seja, seu resultado será uma perda. Além disso, esse trecho faz pensar na possibilidade de negociar de acordo com a tendência, o que quer dizer que não veremos nada definido.

Como esse tipo de problemas surge ao usar qualquer indicador, não importa qual escolher para trabalhos futuros.

 Fig. 3. Sinais do indicador RSI.

Fig. 3. Sinais do indicador RSI.


Modificando o indicador RSI

No indicador selecionado, você precisa fazer acréscimos, para que mais tarde seja mais conveniente trabalhar a partir do EA. Criaremos cinco versões do RSI, mudando constantemente do simples para o complexo, a fim de simplificar a compreensão.

Primeira versão. Adicionando os buffers de sinal

A versão padrão do RSI está localizada no diretório do terminal \MQL5\Indicators\Examples. Fazemos uma cópia e começamos a fazer alterações nele. Na lista de parâmetros fixos, adicionamos mais dois buffers de indicador. No total, eles agora serão 5, enquanto os exibidos no gráfico — 3. Dois buffers permanecerão reservados para cálculos auxiliares. Exibimos em verde os rótulos de compra (clrMediumSeaGreen), enquanto — em verde (clrRed) os rótulos de venda. 

//--- Propriedades
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_buffers 5
#property indicator_plots   3
#property indicator_color1  clrSteelBlue
#property indicator_color2  clrMediumSeaGreen
#property indicator_color3  clrRed

Definimos os códigos para os rótulos de sinal. Para exibir pontos, definimos o código 159. Se os sinais precisam de serem exibidos como setas, definimos os códigos 233 e 234, respectivamente.

//--- Setas para sinais: 159 - pontos; 233/234 - setas
#define ARROW_BUY  159
#define ARROW_SELL 159

O fato de as linhas cruzarem as bordas do indicador pode ser sinal tanto de compra como de venda. É por isso que, para o parâmetro externo, precisamos de uma enumeração, com a qual podemos definir como interpretar os sinais do indicador.

  • Break in — rompimento das bordas para o interior do intervalo. Rompimento da borda inferior de baixo para cima — sinal de compra, rompimento da borda superior de cima para baixo — sinal de venda.
  • Break in reverse — rompimento das bordas para o interior do intervalo (no sentido aposto ao impulso). Os mesmos sinais do modo Break in, mas as condições de compra e venda são trocadas de lugares.
  • Break out — rompimento das bordas de dentro para fora do intervalo. Rompimento da borda superior de baixo para cima — sinal de compra, rompimento da borda inferior de cima para baixo — sinal de venda. 
  • Break out reverse — rompimento das bordas para fora do intervalo (no sentido aposto ao impulso). Os mesmos sinais do modo Break out, mas as condições de compra e venda são trocadas de lugares.

Abaixo todos esses modos serão mostrados no gráfico.

//--- Enumeração de modos de rompimento de borda do canal
enum ENUM_BREAK_INOUT
  {
   BREAK_IN          =0, // Break in
   BREAK_IN_REVERSE  =1, // Break in reverse
   BREAK_OUT         =2, // Break out
   BREAK_OUT_REVERSE =3  // Break out reverse
  };

No total, haverá três parâmetros externos no indicador:

  • RSI Period — período do indicador;
  • Signal Level — níveis do indicador;
  • Break Mode — modo de rompimento dos níveis.

//--- Parâmetros de entrada
input  int              PeriodRSI   =8;         // RSI Period
input  double           SignalLevel =30;        // Signal Level
input  ENUM_BREAK_INOUT BreakMode   =BREAK_OUT; // Mode Break

As propriedades do indicador são definidas na função SetPropertiesIndicator(). As matrizes auxiliares são a última coisa que devemos definir. Todas as matrizes de indicador são inicializadas com valores zero na função ZeroIndicatorBuffers(). Em seguida, indicamos que os valores zero não precisam ser exibidos no gráfico, em outras palavras, esses valores estarão vazios.

//+------------------------------------------------------------------+
//| Definindo as propriedades do indicador                           |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Nome curto
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS1");
//--- Casa decimais
   ::IndicatorSetInteger(INDICATOR_DIGITS,2);
//--- Matrizes do indicador
   ::SetIndexBuffer(0,rsi_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(1,buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(2,sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(3,pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(4,neg_buffer,INDICATOR_CALCULATIONS);
//--- Inicialização de matrizes
   ZeroIndicatorBuffers();
//--- Definindo rótulos
   string plot_label[]={"RSI","buy","sell"};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetString(i,PLOT_LABEL,plot_label[i]);
//--- Definindo a espessura das matrizes de indicador
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_LINE_WIDTH,1);
//--- Definindo o tipo para as matrizes de indicador
   ENUM_DRAW_TYPE draw_type[]={DRAW_LINE,DRAW_ARROW,DRAW_ARROW};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]);
//--- Códigos de rótulos
   ::PlotIndexSetInteger(1,PLOT_ARROW,ARROW_BUY);
   ::PlotIndexSetInteger(2,PLOT_ARROW,ARROW_SELL);
//--- Índice do elemento do qual começa o cálculo
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_BEGIN,period_rsi);
//--- Número de níveis horizontais do indicador
   ::IndicatorSetInteger(INDICATOR_LEVELS,2);
//--- Valores dos níveis horizontais do indicador
   up_level   =100-SignalLevel;
   down_level =SignalLevel;
   ::IndicatorSetDouble(INDICATOR_LEVELVALUE,0,down_level);
   ::IndicatorSetDouble(INDICATOR_LEVELVALUE,1,up_level);
//--- Estilo de linha
   ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,0,STYLE_DOT);
   ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,1,STYLE_DOT);
//--- Valor vazio para a construção que não tem plotagem
   for(int i=0; i<indicator_buffers; i++)
      ::PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,0);
//--- Deslocamento no eixo Y
   if(BreakMode==BREAK_IN_REVERSE || BreakMode==BREAK_OUT_REVERSE)
     {
      ::PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,arrow_shift);
      ::PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,-arrow_shift);
     }
   else
     {
      ::PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-arrow_shift);
      ::PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,arrow_shift);
     }
  }

Cálculos preliminares e básicos dos valores do indicador RSI são feitos por conveniência em funções separadas PreliminaryCalculations() e CalculateRSI(). Seu conteúdo é o mesmo que no indicador RSI fornecido com o terminal. Consideraremos só a função para definir os sinais do indicador — CalculateSignals(). Aqui primeiro são verificadas as condições, dependendo do modo definido nos parâmetros externos. Em seguida, se as condições forem atendidas, armazenamos o valor do indicador RSI na matriz de indicadores correspondente. Se a condição não for atendida, manteremos os valores zero que não serão exibidos no gráfico.

//+------------------------------------------------------------------+
//| Calculando os sinais do indicador                                |
//+------------------------------------------------------------------+
void CalculateSignals(const int i)
  {
   bool condition1 =false;
   bool condition2 =false;
//--- Rompimento para dentro do canal
   if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
     {
      condition1 =rsi_buffer[i-1]<down_level && rsi_buffer[i]>down_level;
      condition2 =rsi_buffer[i-1]>up_level && rsi_buffer[i]<up_level;
     }
   else
     {
      condition1 =rsi_buffer[i-1]<up_level && rsi_buffer[i]>up_level;
      condition2 =rsi_buffer[i-1]>down_level && rsi_buffer[i]<down_level;
     }
//--- Exibir sinais se as condições forem atendidas
   if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT)
     {
      buy_buffer[i]  =(condition1)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition2)? rsi_buffer[i] : 0;
     }
   else
     {
      buy_buffer[i]  =(condition2)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition1)? rsi_buffer[i] : 0;
     }
  }

Como resultado, o código das principais funções do indicador, como OnInit() e OnCalculate(), ficará organizado e legível:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Verificação do valor do parâmetro externo
   if(PeriodRSI<1)
     {
      period_rsi=2;
      Print("Incorrect value for input variable PeriodRSI =",PeriodRSI,
            "Indicator will use value =",period_rsi,"for calculations.");
     }
   else
      period_rsi=PeriodRSI;
//--- Definindo as propriedades do indicador
   SetPropertiesIndicator();
  }
//+------------------------------------------------------------------+
//| Relative Strength Index                                          |
//+------------------------------------------------------------------+
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[])
  {
//--- Sair se os dados não forem suficientes
   if(rates_total<=period_rsi)
      return(0);
//--- Cálculos preliminares
   PreliminaryCalculations(prev_calculated,close);
//--- Ciclo principal para cálculos
   for(int i=start_pos; i<rates_total && !::IsStopped(); i++)
     {
      //--- Calculando o indicador RSI
      CalculateRSI(i,close);
      //--- Calculando sinais
      CalculateSignals(i);
     }
//--- Retornando o último número calculado de elementos
   return(rates_total);
  }

Carregamos este indicador no gráfico para ver o que aconteceu. Abaixo estão os resultados em todos os quatro modos de trabalho (parâmetro externo Break Mode).

 Fig. 4. Demonstração do trabalho do indicador modificado RSI.

Fig. 4. Demonstração do trabalho do indicador modificado RSI.

Após essa modificação do indicador RSI já é mais fácil ver quantos sinais falsos ele dá. Analisando os gráficos, concluímos que, para este indicador, às vezes é melhor negociar na fase de correção e às vezes — na tendência. 

  • O modo Break in se destina a negociar segundo uma fase de correção no sentido do RSI.
  • O modo Break in reverse se destina a negociar segundo uma tendência no sentido oposto ao RSI.
  • O modo Break out se destina a negociar segundo uma tendência no sentido do RSI.
  • O modo Break out reverse se destina a negociar segundo uma fase de correção no sentido oposto ao indicador RSI.

Será que se pode operar usando os sinais deste indicador apenas numa fase de correção ou apenas numa tendência? Como aumentar a probabilidade de resultados positivos e a precisão das entradas? É bem provável que o fato de a posição não ser aberta com o primeiro sinal numa série continua possa ajudar a melhorar o resultado.

Segunda versão. Adicionando os buffers dos contadores de sinal

Tentemos adicionar buffers de indicador para exibir o número de sinais contínuos no mesmo sentido. Assim que o sinal oposto entra, é reinicializado o contador do buffer anterior e é ativado o contador da série atual de sinais. Para implementação, aprimoramos um pouco o código.

As alterações estão em parâmetros específicos. Especificamos o novo número de buffers e de séries para plotar e definir sua cor.

//--- Propriedades
...
#property indicator_buffers 7
#property indicator_plots   5
...
#property indicator_color4  clrMediumSeaGreen
#property indicator_color5  clrRed

No nível global, adicionamos dois matrizes adicionais para exibir os valores dos contadores e duas variáveis ​​auxiliares correspondentes:

//--- Matrizes de indicador
...
double buy_counter_buffer[];
double sell_counter_buffer[];
//--- Contadores de sequências contínuas de sinais
int buy_counter  =0;
int sell_counter =0;

As alterações restantes dizem respeito apenas à função para determinar os sinais. Neste caso, se as condições forem atendidas (surgimento de outro sinal para comprar ou vender), o respectivo contador será acionado, enquanto o contador para a série de sinais opostos será zerado. Para remover o aumento do contador na barra atual, precisamos ignorar seu desencadeamento na última barra não formada.

//+------------------------------------------------------------------+
//| Calculando os sinais do indicador                                |
//+------------------------------------------------------------------+
void CalculateSignals(const int i,const int rates_total)
  {
   int last_index=rates_total-1;
//---
   bool condition1 =false;
   bool condition2 =false;
//--- Rompimento para dentro do canal
   if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
     {
      condition1 =rsi_buffer[i-1]<down_level && rsi_buffer[i]>down_level;
      condition2 =rsi_buffer[i-1]>up_level && rsi_buffer[i]<up_level;
     }
   else
     {
      condition1 =rsi_buffer[i-1]<up_level && rsi_buffer[i]>up_level;
      condition2 =rsi_buffer[i-1]>down_level && rsi_buffer[i]<down_level;
     }
//--- Exibir sinais se as condições forem atendidas
   if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT)
     {
      buy_buffer[i]  =(condition1)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition2)? rsi_buffer[i] : 0;
      //--- Contador só das barras formadas
      if(i<last_index)
        {
         if(condition1)
           {
            buy_counter++;
            sell_counter=0;
           }
         else if(condition2)
           {
            sell_counter++;

            buy_counter=0;
           }
        }
     }
   else
     {
      buy_buffer[i]  =(condition2)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition1)? rsi_buffer[i] : 0;
      //--- Contador só das barras formadas
      if(i<last_index)
        {
         if(condition2)
           {
            buy_counter++;
            sell_counter=0;
           }
         else if(condition1)
           {
            sell_counter++;
            buy_counter=0;
           }
        }
     }
//--- Correção do último valor (igual ao penúltimo)
   if(i<last_index)
     {
      buy_counter_buffer[i]  =buy_counter;
      sell_counter_buffer[i] =sell_counter;
     }
   else
     {
      buy_counter_buffer[i]  =buy_counter_buffer[i-1];
      sell_counter_buffer[i] =sell_counter_buffer[i-1];
     }
  }

Após as modificações adicionadas, carregamos o indicador no gráfico para ver o que aconteceu. Agora o indicador RSI se torna mais informativo. Os valores desses contadores podem ser obtidos no EA, a fim de formar condições adicionais para compra e venda.

 Fig. 5. Indicador RSI modificado com contadores de sinais unidirecionais contínuos.

Fig. 5. Indicador RSI modificado com contadores de sinais unidirecionais contínuos.

Para garantir que tudo funcione conforme o pretendido, verificaremos o indicador no testador e em tempo real. Desempenho no testador de estratégia:

 Fig. 6. Verificando o trabalho do indicador RSI modificado no testador de estratégias.

Fig. 6. Verificando o trabalho do indicador RSI modificado no testador de estratégias.

Na seguinte captura de tela, vemos o que o indicador mostra em todos os modos do parâmetro Break Mode.

 Fig. 7. Indicador em todos os modos do parâmetro Break Mode.

Fig. 7. Indicador em todos os modos do parâmetro Break Mode.

No gráfico, pode-se ver momentos em que, entre dois sinais unidirecionais, o preço pode percorrer uma distância muito longa (veja fig. 8). Isso acontece com muita frequência e em qualquer timeframe. Sendo que esse problema não se resolve alterando inúmeras vezes o parâmetro externo Signal Level ao configurar as bordas do intervalo. Essa incerteza pode interferir na criação de uma lógica de negociação clara ou dificultá-la com a escrita de condições adicionais. 

 Fig. 8. Sinais omitidos durante o deslocamento significativo do preço.

Fig. 8. Sinais omitidos durante o deslocamento significativo do preço.

Na próxima versão, eliminaremos essas omissões, tornando o indicador ainda mais informativo.  

Terceira versão. Aumentando o número de sinais, excluindo omissões

Nesta versão, o número de buffers de indicador permanece o mesmo, mas é necessário adicionar matrizes de níveis de indicador que gerarão sinais quando o indicador cruzar a linha.  

//--- Valores dos níveis horizontais do indicador e seu número
double up_level          =0;
double down_level        =0;
int    up_levels_total   =0;
int    down_levels_total =0;
//--- Matrizes de níveis horizontais 
double up_levels[];
double down_levels[];

Para o sinais não serem omitidos, como discutido na seção anterior, vamos definir os níveis a cada 5 pontos. Isto é, se no parâmetro externo Signal Level for definido o valor de 30, para os níveis superiores, serão calculados os seguintes valores: 70, 75, 80, 85, 90, 95. 

A função GetLevelsIndicator() é projetada para calcular os níveis do indicador. Em dois ciclos separados, são calculados os níveis colocados nas matrizes. A função retorna o número total de níveis.

//+------------------------------------------------------------------+
//| Retornando os níveis do indicador                                |
//+------------------------------------------------------------------+
int GetLevelsIndicator(void)
  {
   int levels_counter=0;
   double level=down_level;
//--- Níveis inferiores para o limite inferior
   while(level>0 && !::IsStopped())
     {
      int size=::ArraySize(down_levels);
      ::ArrayResize(down_levels,size+1);
      down_levels[size]=level;
      level-=5;
      levels_counter++;
     }
   level=up_level;
//--- Os níveis superiores ao limite superior
   while(level<100 && !::IsStopped())
     {
      int size=::ArraySize(up_levels);
      ::ArrayResize(up_levels,size+1);
      up_levels[size]=level;
      level+=5;
      levels_counter++;
     }
//---
   return(levels_counter);
  }

Os níveis são definidos na função SetPropertiesIndicator(). Abaixo está sua versão resumida. Aqui, primeiro, são calculados os níveis iniciais para os intervalos superior e inferior e as matrizes de níveis são redefinidas para zero. Em seguida, definimos o número total de níveis do indicador chamando a função GetLevelsIndicator(). Depois disso, a partir das matrizes estabelecemos os níveis já calculados dos intervalos inferior e superior.

//+------------------------------------------------------------------+
//| Definindo as propriedades do indicador                           |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Cálculo dos primeiros níveis
   up_level   =100-SignalLevel;
   down_level =SignalLevel;
//--- Redefinição das matrizes de níveis
   ::ArrayFree(up_levels);
   ::ArrayFree(down_levels);
//--- Número de níveis horizontais do indicador
   ::IndicatorSetInteger(INDICATOR_LEVELS,GetLevelsIndicator());
//--- Valores de níveis horizontais de indicador do nível inferior
   down_levels_total=::ArraySize(down_levels);
   for(int i=0; i<down_levels_total; i++)
      ::IndicatorSetDouble(INDICATOR_LEVELVALUE,i,down_levels[i]);
//--- Valores de níveis horizontais de indicador do nível superior
   up_levels_total=::ArraySize(up_levels);
   int total=up_levels_total+down_levels_total;
   for(int i=down_levels_total,k=0; i<total; i++,k++)
      ::IndicatorSetDouble(INDICATOR_LEVELVALUE,i,up_levels[k]);
...
  }

Por conseguinte, é necessário fazer alterações na função CalculateSignals(). Aqui, também, é mostrada apenas a parte alterada da função. Para verificar se as condições nos ciclos são atendidas, vemos se pelo menos um dos níveis se cruza nas matrizes. 

//+------------------------------------------------------------------+
//| Calculando os sinais do indicador                                |
//+------------------------------------------------------------------+
void CalculateSignals(const int i,const int rates_total)
  {
   int last_index=rates_total-1;
//---
   bool condition1 =false;
   bool condition2 =false;
//--- Rompimento para dentro do canal
   if(BreakMode==BREAK_IN || BreakMode==BREAK_IN_REVERSE)
     {
      if(rsi_buffer[i]<50)
        {
         for(int j=0; j<down_levels_total; j++)
           {
            condition1=rsi_buffer[i-1]<down_levels[j] && rsi_buffer[i]>down_levels[j];
            if(condition1)
               break;
           }
        }
      //---
      if(rsi_buffer[i]>50)
        {
         for(int j=0; j<up_levels_total; j++)
           {
            condition2=rsi_buffer[i-1]>up_levels[j] && rsi_buffer[i]<up_levels[j];
            if(condition2)
               break;
           }
        }
     }
   else
     {
      for(int j=0; j<up_levels_total; j++)
        {
         condition1=rsi_buffer[i-1]<up_levels[j] && rsi_buffer[i]>up_levels[j];
         if(condition1)
            break;
        }
      //---
      for(int j=0; j<down_levels_total; j++)
        {
         condition2=rsi_buffer[i-1]>down_levels[j] && rsi_buffer[i]<down_levels[j];
         if(condition2)
            break;
        }
     }
//--- Exibir sinais se as condições forem atendidas
...
//--- Correção do último valor (igual ao penúltimo)
...
  }

Na Fig. 9 é mostrado como fica isso. 

 Fig. 9. Formação de sinais durante o cruzamento de vários níveis.

Fig. 9. Formação de sinais durante o cruzamento de vários níveis.

Resolvemos um problema, mas apareceram mais dois. O primeiro tem a ver com a necessidade de excluir os sinais em que o preço é maior do que a compra anterior ou menor do que a venda anterior. Na Fig. 10, é ilustrada essa situação para uma série de sinais de compra, pois o preço no último sinal da série é superior ao preço do sinal anterior. Isso é relevante para os modos Break in e Break out reverse quando é necessário aderir à ideia de comprar mais barato e vender mais caro.

 Fig. 10. Situação quando o preço do sinal é maior do que o preço do sinal anterior.

Fig. 10. Situação quando o preço do sinal é maior do que o preço do sinal anterior.

O segundo problema está relacionado à frequência excessiva dos sinais, às vezes até várias barras seguidas. Além disso, o preço entre os sinais se move uma distância insignificante (veja a fig. 11).

 Fig. 11. Acumulações de sinais frequentes com um ligeiro movimento de preços.

Fig. 11. Acumulações de sinais frequentes com um ligeiro movimento de preços.

Na próxima versão, implementaremos uma solução para esses problemas.

Quarta versão. Transferindo o indicador para a janela principal do gráfico

Vamos transferir esta versão do indicador para o gráfico principal. Aqui podemos apreciar melhor como trabalha o indicador, pois veremos sinais no preço imediatamente. Para controlar a distância entre os sinais (em pontos), podemos simplesmente definir um valor fixo no parâmetro externo. Mas vamos tentar fazer uma variante dinâmica e vincular esse valor ao indicador de volatilidade (ATR). Por conveniência, elaboraremos cálculos para indicadores em classes individuais, nomeadamente em CATR e em CRsiPlus. Usando esse método, podemos combinar qualquer número de indicadores, juntando seus resultados de cálculos num programa único. 

Definindo barras verdadeiras

Assumiremos que no futuro esta será a versão usada para criar o EA. É por isso que, para excluir a influência de timeframes grandes sobre os dados históricos, quando as barras do período gráfico não forem suficientes, determinaremos a primeira barra verdadeira. Anteriormente, isso foi discutido em detalhes no artigo sobre indicadores multimoedas. Para determinar a primeira barra verdadeira, escreveremos uma classe separada - CFirstTrueBar. Primeiro, consideremos como é construída essa classe.

Abaixo são mostradas as definições de membros e de métodos da classe CFirstTrueBar. Vamos considerá-los brevemente. 

//+------------------------------------------------------------------+
//| Classe para determinar a barra verdadeira                        |
//+------------------------------------------------------------------+
class CFirstTrueBar
  {
private:
   //--- Hora da barra verdadeira
   datetime          m_limit_time;
   //--- Número da barra verdadeira
   int               m_limit_bar;
   //---
public:
                     CFirstTrueBar(void);
                    ~CFirstTrueBar(void);
   //--- Retornando (1) a hora e (2) o número da barra verdadeira
   datetime          LimitTime(void) const { return(m_limit_time); }
   int               LimitBar(void)  const { return(m_limit_bar);  }
   //--- Determinando a primeira barra verdadeira
   bool              DetermineFirstTrueBar(void);
   //---
private:
   //--- Procurando a primeira barra verdadeira do período atual
   void              GetFirstTrueBarTime(const datetime &time[]);
  };

Para encontrar a barra verdadeira, é usado o método privado CFirstTrueBar::GetFirstTrueBarTime(). Precisamos transferir para ele a matriz de hora das barras do histórico, a fim de encontrar a primeira barra verdadeira. Começamos a passar desde o início da matriz e, assim que é encontrada a barra correspondente ao timeframe atual, lembramos a hora e o índice da barra. Quando uma barra verdadeira é definida, podemos obter sua hora e índice usando os métodos CFirstTrueBar::LimitTime() e CFirstTrueBar::LimitBar().

//+------------------------------------------------------------------+
//| Procurando a primeira barra verdadeira do período atual          |
//+------------------------------------------------------------------+
void CFirstTrueBar::GetFirstTrueBarTime(const datetime &time[])
  {
//--- Obtendo o tamanho da matriz
   int array_size=::ArraySize(time);
   ::ArraySetAsSeries(time,false);
//--- Verificando cada barra uma por uma
   for(int i=1; i<array_size; i++)
     {
      //--- Se a barra corresponder ao timeframe atual
      if(time[i]-time[i-1]==::PeriodSeconds())
        {
         //--- Lembrando e parando o ciclo
         m_limit_time =time[i-1];
         m_limit_bar  =i-1;
         break;
        }
     }
  }

O método CFirstTrueBar::GetFirstTrueBarTime() é chamado no método CFirstTrueBar::DetermineFirstTrueBar(). É aqui que obtemos a matriz de hora das barras segundo o qual, mais tarde, procuraremos a primeira barra verdadeira.

//+------------------------------------------------------------------+
//| Determinando a primeira barra verdadeira                         |
//+------------------------------------------------------------------+
bool CFirstTrueBar::DetermineFirstTrueBar(void)
  {
//--- Matriz de hora das barras
   datetime time[];
//--- Obtendo o número total de barras do símbolo
   int available_bars=::Bars(_Symbol,_Period);
//--- Copiando a matriz de tempo das barras. Se não funcionar, tentaremos novamente.
   if(::CopyTime(_Symbol,_Period,0,available_bars,time)<available_bars)
      return(false);
//--- Obtendo a hora da primeira barra verdadeira que corresponde ao timeframe atual
   GetFirstTrueBarTime(time);
   return(true);
  }

Adicionando o indicador ATR

O indicador ATR será calculado da mesma forma que no fornecido com o terminal. Tomamos o código em \MQL5\Indicators\Examples. Abaixo estão as declarações dos membros e dos métodos da classe CATR. A diferença da versão padrão é que aqui só vamos determinar a primeira barra verdadeira, a partir da qual começam os cálculos.

Conectamos o arquivo com a classe CFirstTrueBar e declaramos sua instância no corpo da classe CATR. Note que as matrizes de indicador são declaradas publicamente. Isso é necessário para que seja possível defini-las como buffers de indicador no arquivo principal do indicador.

//+------------------------------------------------------------------+
//|                                                          ATR.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "FirstTrueBar.mqh"
//+------------------------------------------------------------------+
//| Indicador ATR                                                    |
//+------------------------------------------------------------------+
class CATR
  {
private:
   //--- Para determinar a primeira barra verdadeira
   CFirstTrueBar     m_first_true_bar;
   //--- Período do indicador
   int               m_period;
   //--- Limitador no cálculo dos valores do indicador
   int               m_limit;
   //---
public:
   //--- Buffers de indicador
   double            m_tr_buffer[];
   double            m_atr_buffer[];
   //---
public:
                     CATR(const int period);
                    ~CATR(void);
   //--- Período do indicador
   void              PeriodATR(const int period) { m_period=period; }
   //--- Calculando o indicador ATR 
   bool              CalculateIndicatorATR(const int rates_total,const int prev_calculated,const datetime &time[],const double &close[],const double &high[],const double &low[]);
   //--- Redefinição dos buffers de indicador
   void              ZeroIndicatorBuffers(void);
   //---
private:
   //--- Cálculos preliminares
   bool              PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[],const double &high[],const double &low[]);
   //--- Calculando o ATR 
   void              CalculateATR(const int i,const datetime &time[],const double &close[],const double &high[],const double &low[]);
  };

A primeira barra verdadeira, a partir da qual o indicador será calculado, é determinada no método para cálculos preliminares. Se não estiver definido, sairemos do método.

//+------------------------------------------------------------------+
//| Cálculos preliminares                                            |
//+------------------------------------------------------------------+
bool CATR::PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[],const double &high[],const double &low[])
  {
//--- Se calcular pela primeira vez ou houve alterações
   if(prev_calculated==0)
     {
      //--- Definindo o número da barra verdadeira
      m_first_true_bar.DetermineFirstTrueBar();
      //--- Sair se a barra verdadeira não estiver definida
      if(m_first_true_bar.LimitBar()<0)
         return(false);
      //---
      m_tr_buffer[0]  =0.0;
      m_atr_buffer[0] =0.0;
      //--- Barra a partir da qual será o cálculo
      m_limit=(::Period()<PERIOD_D1)? m_first_true_bar.LimitBar()+m_period : m_period;
      //--- Sair se estivermos fora do intervalo (barras insuficientes)
      if(m_limit>=rates_total)
         return(false);
      //--- Calculando os valores do intervalo real
      int start_pos=(m_first_true_bar.LimitBar()<1)? 1 : m_first_true_bar.LimitBar();
      for(int i=start_pos; i<m_limit && !::IsStopped(); i++)
         m_tr_buffer[i]=::fmax(high[i],close[i-1])-::fmin(low[i],close[i-1]);
      //--- Os primeiros valores do ATR não são calculados
      double first_value=0.0;
      for(int i=m_first_true_bar.LimitBar(); i<m_limit; i++)
        {
         m_atr_buffer[i]=0.0;
         first_value+=m_tr_buffer[i];
        }
      //--- Cálculo do primeiro valor
      first_value/=m_period;
      m_atr_buffer[m_limit-1]=first_value;
     }
   else
      m_limit=prev_calculated-1;
//---
   return(true);
  }

Filtrando sinais no indicador RSI

Além dos buffers de indicador descritos acima, nesta versão do RSI haverá mais dois. Eles serão os níveis contínuos construídos separadamente de acordo com os preços dos sinais de compra e de venda tendo em conta o spread. Para poder incluir os dados do indicador ATR nos cálculos, você precisa obter um ponteiro para essa instância da classe ATR, que é criada no arquivo principal do programa. Portanto, aqui declaramos um ponteiro de tipo CATR e os métodos correspondentes para instalar e receber.

Para otimizar o código, alguns de seus blocos agora são implementados como métodos separados. Eles são a verificação de condições, o trabalho com contadores, etc. O único novo método que não foi considerado anteriormente é o CRsiPlus::DirectionControl(). É nele que o sentido do movimento é controlado e os sinais desnecessários são eliminados de acordo com a volatilidade atual. Além disso, existem métodos auxiliares para remover sinais desnecessáriosCRsiPlus::DeleteBuySignal() e CRsiPlus::DeleteSellSignal().

//+------------------------------------------------------------------+
//|                                                          RSI.mqh |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "FirstTrueBar.mqh"
#include "ATR.mqh"
//--- Enumeração de modos de rompimento de canais
enum ENUM_BREAK_INOUT
  {
   BREAK_IN          =0, // Break in
   BREAK_IN_REVERSE  =1, // Break in reverse
   BREAK_OUT         =2, // Break out
   BREAK_OUT_REVERSE =3  // Break out reverse
  };
//+------------------------------------------------------------------+
//| Indicador RSI com filtro de volatilidade                         |
//+------------------------------------------------------------------+
class CRsiPlus
  {
private:
   //--- Para determinar a primeira barra verdadeira
   CFirstTrueBar     m_first_true_bar;
   //--- Ponteiro para o ATR
   CATR             *m_atr;
   
   //--- Período do indicador
   int               m_period;
   //--- Nível do RSI
   double            m_signal_level;
   //--- Modo para gerar sinais
   ENUM_BREAK_INOUT  m_break_mode;
      
   //--- Contadores de sinais unidirecionais
   int               m_buy_counter;
   int               m_sell_counter;
   //--- Níveis de indicador
   double            m_up_level;
   double            m_down_level;
   double            m_up_levels[];
   double            m_down_levels[];
   int               m_up_levels_total;
   int               m_down_levels_total;
   
   //--- Limitador no cálculo dos valores do indicador
   int               m_limit;
   //--- Determinando a última barra
   bool              m_is_last_index;
   //---
public:
   //--- Buffers de indicador
   double            m_rsi_buffer[];
   double            m_pos_buffer[];
   double            m_neg_buffer[];
   //---
   double            m_buy_buffer[];
   double            m_sell_buffer[];
   double            m_buy_level_buffer[];
   double            m_sell_level_buffer[];
   double            m_buy_counter_buffer[];
   double            m_sell_counter_buffer[];
   //---
public:
                     CRsiPlus(const int period,const double signal_level,const ENUM_BREAK_INOUT break_mode);
                    ~CRsiPlus(void) {}
   //--- Ponteiro para o ATR
   void              AtrPointer(CATR &object) { m_atr=::GetPointer(object);  }
   CATR             *AtrPointer(void)         { return(::GetPointer(m_atr)); }
   //--- Calculando o indicador RSI
   bool              CalculateIndicatorRSI(const int rates_total,const int prev_calculated,const double &close[],const int &spread[]);
   //--- Inicialização de todos os buffers de indicador
   void              ZeroIndicatorBuffers(void);
   //---
private:
   //--- Obtendo os níveis do indicador
   int               GetLevelsIndicator(void);
   //--- Cálculos preliminares
   bool              PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[]);
   //--- Calculando a série do RSI
   void              CalculateRSI(const int i,const double &price[]);
   //--- Calculando os sinais do indicador
   void              CalculateSignals(const int i,const int rates_total,const double &close[],const int &spread[]);

   //--- Verificação das condições
   void              CheckConditions(const int i,bool &condition1,bool &condition2);
   //--- Verificação dos contadores
   void              CheckCounters(bool &condition1,bool &condition2);
   //--- Aumentando os contadores de buy e de sell
   void              IncreaseBuyCounter(const bool condition);
   void              IncreaseSellCounter(const bool condition);

   //--- Monitorando o sentido do movimento
   void              DirectionControl(const int i,bool &condition1,bool &condition2);
   //--- Removendo os sinais de buy e de sell desnecessários
   void              DeleteBuySignal(const int i);
   void              DeleteSellSignal(const int i);
   //--- Redefinição do elemento especificado dos buffers de indicador
   void              ZeroIndexBuffers(const int index);
  };

No método CRsiPlus::DirectionControl() são verificadas as seguintes condições sobre se o sinal é desnecessário:

  • Se houver um sinal, mas o tamanho do impulso for menor do que a volatilidade atual.
  • Se o sinal gerado não for no sentido da série atual.

Se as condições forem atendidas, o sinal será apagado.

//+------------------------------------------------------------------+
//| Controle do sentido do movimento                                 |
//+------------------------------------------------------------------+
void CRsiPlus::DirectionControl(const int i,bool &condition1,bool &condition2)
  {
   double atr_coeff     =0.0;
   double impulse_size  =0.0;
   bool   atr_condition =false;
//---
   bool buy_condition  =false;
   bool sell_condition =false;
//--- Se 'reverse' estiver desativado
   if(m_break_mode==BREAK_IN || m_break_mode==BREAK_OUT)
     {
      buy_condition =condition1 && m_buy_counter>1;
      impulse_size  =::fabs(m_buy_buffer[i]-m_buy_level_buffer[i-1]);
      atr_condition =impulse_size<m_atr.m_atr_buffer[i];
      //---
      if((m_buy_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN && buy_condition && m_buy_buffer[i]>m_buy_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT && buy_condition && m_buy_buffer[i]<m_buy_level_buffer[i-1]))
        {
         DeleteBuySignal(i);
        }
      //---
      sell_condition =condition2 && m_sell_counter>1;
      impulse_size   =::fabs(m_sell_buffer[i]-m_sell_level_buffer[i-1]);
      atr_condition  =impulse_size<m_atr.m_atr_buffer[i];
      //---
      if((m_sell_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN && sell_condition && m_sell_buffer[i]<m_sell_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT && sell_condition && m_sell_buffer[i]>m_sell_level_buffer[i-1]))
        {
         DeleteSellSignal(i);
        }
     }
//--- 'Reverse' desativado     
   else
     {
      buy_condition =condition2 && m_buy_counter>1;
      impulse_size  =::fabs(m_buy_buffer[i]-m_buy_level_buffer[i-1]);
      atr_condition =impulse_size<m_atr.m_atr_buffer[i];
      //---
      if((m_buy_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN_REVERSE && buy_condition && m_buy_buffer[i]<m_buy_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT_REVERSE && buy_condition && m_buy_buffer[i]>m_buy_level_buffer[i-1]))
        {
         DeleteBuySignal(i);
        }
      //---
      sell_condition =condition1 && m_sell_counter>1;
      impulse_size   =::fabs(m_sell_buffer[i]-m_sell_level_buffer[i-1]);
      atr_condition  =impulse_size<m_atr.m_atr_buffer[i];
      //---      
      if((m_sell_counter>1 && atr_condition) || 
         (m_break_mode==BREAK_IN_REVERSE && sell_condition && m_sell_buffer[i]>m_sell_level_buffer[i-1]) ||
         (m_break_mode==BREAK_OUT_REVERSE && sell_condition && m_sell_buffer[i]<m_sell_level_buffer[i-1]))
        {
         DeleteSellSignal(i);
        }
     }
  }

Agora vamos ver como está organizado o arquivo principal do indicador Nesta versão do indicador, há 11 buffers, 7 dos quais são principais e 4 — auxiliares.

//--- Propriedades
#property indicator_chart_window
#property indicator_buffers 11
#property indicator_plots   7
#property indicator_color1  clrMediumSeaGreen
#property indicator_color2  clrRed
#property indicator_color5  clrMediumSeaGreen
#property indicator_color6  clrRed

Por conveniência, todos os arquivos usados​​com classes estão localizados no diretório do indicador na pasta Includes:

 Fig. 12. Diretório do indicador.

Fig. 12. Diretório do indicador.

Portanto, a conexão com o arquivo principal ficará assim:

//--- Conectando as classes dos indicadores
#include "Includes\ATR.mqh"
#include "Includes\RsiPlus.mqh"

Aos parâmetros externos adicionamos mais um para o período do indicador ATR.

//--- Parâmetros de entrada
input int              PeriodRSI   =8;         // RSI period
input double           SignalLevel =30;        // Signal level
input ENUM_BREAK_INOUT BreakMode   =BREAK_OUT; // Break mode
input int              PeriodATR   =200;       // ATR period

Os indicadores são declarados com a transferência de parâmetros para o construtor.

//--- Instâncias dos indicadores para o trabalho
CATR     atr(PeriodATR);
CRsiPlus rsi(PeriodRSI,SignalLevel,BreakMode);

Não esqueçamos que na função de inicialização OnInit() devemos transferir para o indicador RSI o ponteiro para o ATR.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Inicialização dos indicadores
   rsi.AtrPointer(atr);
//--- Definindo as propriedades do indicador
   SetPropertiesIndicator();
  }

Como as matrizes alocadas para buffers de indicador são declaradas como públicas em cada classe dos indicadores, no arquivo principal sua adição ao indicador é semelhante à conexão de matrizes dinâmicas habitual.

//+------------------------------------------------------------------+
//| Definindo as propriedades do indicador                           |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Nome curto
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS_CHART");
//--- Casa decimais
   ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits());
//--- Buffers do indicador
   ::SetIndexBuffer(0,rsi.m_buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(1,rsi.m_sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(2,rsi.m_buy_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(3,rsi.m_sell_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(4,rsi.m_buy_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(5,rsi.m_sell_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(6,atr.m_atr_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(7,rsi.m_rsi_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(8,rsi.m_pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(9,rsi.m_neg_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(10,atr.m_tr_buffer,INDICATOR_CALCULATIONS);
//--- Inicialização de matrizes
   atr.ZeroIndicatorBuffers();
   rsi.ZeroIndicatorBuffers();
...
  }

Matrizes auxiliares usadas ​​para cálculos adicionais não são exibidos no gráfico e na janela de dados. Se quisermos que os dados sejam exibidos na janela de dados, mas, não, no gráfico, para essa série precisamos definir a propriedade DRAW_NONE.

//+------------------------------------------------------------------+
//| Definindo as propriedades do indicador                           |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Definindo o tipo para os buffers de indicador
   ENUM_DRAW_TYPE draw_type[]={DRAW_ARROW,DRAW_ARROW,DRAW_NONE,DRAW_NONE,DRAW_LINE,DRAW_LINE,DRAW_NONE};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_TYPE,draw_type[i]);
...
  }

O conteúdo da função OnCalculate() só se resume a chamar dois métodos para calcular os indicadores ATR e RSI modificado

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- Calculando o indicador ATR
   if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low))
      return(0);
//--- Calculando o indicador RSI
   if(!rsi.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
//--- Retornando o último número calculado de elementos
   return(rates_total);
  }

Depois de compilar e carregar o indicador no gráfico, você verá o resultado, como na Fig. 13. Os níveis construídos sobre os sinais do indicador formam uma espécie de canal. Graças a ele, é fácil determinar a distância percorrida pelo preço desde o último sinal oposto da série anterior.

 Fig. 13. Resultado do indicador RSI modificado no gráfico principal.

Fig. 13. Resultado do indicador RSI modificado no gráfico principal.

Ao desenvolver um sistema de negociação, podemos precisar obter visualmente os valores exatos do buffer de indicador. Isso tem a ver particularmente com o momento em que no gráfico não se conseguem ver as séries de certos buffers cujos valores estão fora de vista. Seus valores podem ser vistos na Janela de dados. Por conveniência, podemos ativar os fios com o botão do meio do mouse. Na Fig. 14, ao gráfico é adicionado o indicador ATR que vem com o terminal, a fim de poder comparar com aquilo que é calculado no RSI modificado.

 Fig. 14. Visualização dos valores na janela de dados do indicador.

Fig. 14. Visualização dos valores na janela de dados do indicador.

Quinta versão. Indicador universal RSI para operar em dois sentidos simultaneamente

E se precisarmos de um indicador que mostre sinais simultaneamente em dois sentidos? Afinal, no MetaTrader 5 existe a possibilidade de abrir contas de cobertura, o que significa que podemos desenvolver um sistema com posições em diferentes sentidos. Neste caso, seria útil usar um indicador que fornece sinais de tendência e de fase de correção ao mesmo tempo. 

Vamos considerar brevemente como criar um indicador desse tipo. Já temos tudo o que precisamos. As alterações serão apenas no arquivo principal. Nesta versão haverá apenas 20 buffers, dos quais nós usamos 15 para plotagem.

#property indicator_buffers 20
#property indicator_plots   15

Os sinais de tendência serão exibidos na forma de setas e os de fase de correção — na forma de pontos. Dessa maneira, será mais fácil entender a que tipo de sinal este ou aquele sinal pertence.

//--- Setas para os sinais: 159 - pontos; 233/234 - setas;
#define ARROW_BUY_IN   233
#define ARROW_SELL_IN  234
#define ARROW_BUY_OUT  159
#define ARROW_SELL_OUT 159

Conectamos os mesmos arquivos da versão anterior e também no diretório local do indicador. Posteriormente, você pode colocar uma única cópia desses arquivos onde você quiser. 

//--- Conectando as classes dos indicadores
#include "Includes\ATR.mqh"
#include "Includes\RsiPlus.mqh"

Nos parâmetros externos desta versão do indicador, não precisamos especificar o tipo de sinal. Mas vamos fazer com que seja possível especificar os níveis para sinais de tendência (Signal level In) e de fase de correção (Signal level Out) separadamente. 

//--- Parâmetros de entrada
input int    PeriodRSI      =8;   // RSI period
input double SignalLevelIn  =35;  // Signal level In
input double SignalLevelOut =30;  // Signal level Out
input int    PeriodATR      =100; // ATR period

Em seguida, precisamos declarar duas instâncias da classe CRsiPlus: a primeira — para sinais de tendência, enquanto a segunda — para sinais de fase de correção. Em ambos os casos, são usados os sinais de tipo 'reverse' (BREAK_IN_REVERSE e BREAK_OUT_REVERSE). Ou seja, para a fase de correção, o sinal será um impulso desde o canal, enquanto, para a tendência, a entrada será no sentido da tendência após a reversão.

//--- Instâncias dos indicadores para o trabalho
CATR atr(PeriodATR);
CRsiPlus rsi_in(PeriodRSI,SignalLevelIn,BREAK_IN_REVERSE);
CRsiPlus rsi_out(PeriodRSI,SignalLevelOut,BREAK_OUT_REVERSE);

O ponteiro para o indicador ATR deve ser enviado para ambas as instâncias do RSI:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Transferência do ponteiro para o ATR  
   rsi_in.AtrPointer(atr);
   rsi_out.AtrPointer(atr);
//--- Definindo as propriedades do indicador
   SetPropertiesIndicator();
  }

Os buffers de indicador para cada instância são definidos de maneira que seja mais fácil navegar no código. Naturalmente, a sequência não importa, você só precisa saber qual número de buffer e, em seguida, no EA, obter os valores da série para formar as condições.

//+------------------------------------------------------------------+
//| Definindo as propriedades do indicador                           |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Nome curto
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS2_CHART");
//--- Casa decimais
   ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits());
//--- Buffers do indicador
   ::SetIndexBuffer(0,rsi_in.m_buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(1,rsi_in.m_sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(2,rsi_in.m_buy_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(3,rsi_in.m_sell_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(4,rsi_in.m_buy_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(5,rsi_in.m_sell_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(6,rsi_in.m_rsi_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(7,rsi_in.m_pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(8,rsi_in.m_neg_buffer,INDICATOR_CALCULATIONS);
//---
   ::SetIndexBuffer(9,rsi_out.m_buy_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(10,rsi_out.m_sell_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(11,rsi_out.m_buy_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(12,rsi_out.m_sell_counter_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(13,rsi_out.m_buy_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(14,rsi_out.m_sell_level_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(15,rsi_out.m_rsi_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(16,rsi_out.m_pos_buffer,INDICATOR_CALCULATIONS);
   ::SetIndexBuffer(17,rsi_out.m_neg_buffer,INDICATOR_CALCULATIONS);
//---
   ::SetIndexBuffer(18,atr.m_atr_buffer,INDICATOR_DATA);
   ::SetIndexBuffer(19,atr.m_tr_buffer,INDICATOR_CALCULATIONS);
//--- Inicialização de matrizes
   atr.ZeroIndicatorBuffers();
   rsi_in.ZeroIndicatorBuffers();
   rsi_out.ZeroIndicatorBuffers();
...
  }

Para não ficar confuso, a que tipo de sinais pertence o nível de sinal, para a fase de correção vamos desenhá-los com uma linha tracejada.

//+------------------------------------------------------------------+
//| Definindo as propriedades do indicador                           |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Definindo o estilo para as linhas dos buffers de indicador definidos
   ::PlotIndexSetInteger(13,PLOT_LINE_STYLE,STYLE_DOT);
   ::PlotIndexSetInteger(14,PLOT_LINE_STYLE,STYLE_DOT);
...
  }

O código da função OnCalculate() é limitado apenas pelos métodos para cada instância do indicador.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- Calculando o indicador ATR
   if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low))
      return(0);
//--- Calculando o indicador RSI
   if(!rsi_in.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
   if(!rsi_out.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
//--- Retornando o último número calculado de elementos
   return(rates_total);
  }

Na Fig. 15 é exibido como funciona o indicador no gráfico. Tudo isso é apenas o resultado da inicialização do RSI.

 Fig. 15. Indicador universal RSI.

Fig. 15. Indicador universal RSI.

 Fig. 16. Indicador universal RSI no testador de estratégias.

Fig. 16. Indicador universal RSI no testador de estratégias.

Assim, o indicador mostra os pontos de entrada, bem como os pontos em que o volume da posição pode ser aumentado. Além disso, podemos obter o índice do sinal atual na série contínua à qual ele pertence. Como aqui também há níveis de preço, podemos obter a distância percorrida pelo preço desde o último sinal da série anterior. É possível implementar um algoritmo de negociação mais complexo. Por exemplo, ao abrir posições usando a tendência, podemos fechá-las parcialmente ou completamente usando os sinais para trabalhar na fase de correção.

Considerando os resultados do indicador no gráfico, você pode inventar diferentes algoritmos de negociação que primeiro precisam ser verificados no testador. Esse também é um trabalho muito grande, pois pode haver infinitas opções para implementação. Pode-se fazer com que cada módulo de negociação receba os resultados da negociação de outro. Ou seja, o módulo para negociar usando a tendência pode receber os resultados da negociação do módulo para negociar usando a fase de correção e vice-versa. Com base nesses dados, eles podem corrigir seu comportamento, ajustando-se à situação atual. Cada módulo pode influenciar a tática de negociação, alterando as condições de abertura, de fechamento ou de acompanhamento da posição, mudando o sistema de controle de capital (passando de um modelo conservador para um agressivo e vice-versa). O sistema de negociação pode ser muito complexo e se adaptar ao comportamento atual dos preços.

Fim do artigo

Como pode ser visto, um indicador simples pode ser muito mais informativo. Isso, por sua vez, simplifica a criação de algoritmos de negociação. Caso contrário, tudo isso teria que ser feito no EA, tornando complexo seu código. Agora tudo está oculto dentro de um programa, e podem-se apenas ler os valores já calculados em suas matrizes.

Você pode continuar a desenvolver essa ideia. Por exemplo, você pode inserir buffers adicionais para novas características que especificam o estado da tendência ou da fase de correção. Você pode adicionar um buffer para trailing stop cujo cálculo está vinculado à volatilidade atual (indicador ATR). Como resultado, todos os cálculos necessários serão implementados no indicador, enquanto o EA simplesmente receberá os níveis prontos.

Nome do arquivo Comentário
MQL5\Indicators\RSI\RSI_Plus1.mq5 Primeira versão do indicador modificado RSI
MQL5\Indicators\RSI\RSI_Plus2.mq5 Segunda versão do indicador modificado RSI
MQL5\Indicators\RSI\RSI_Plus3.mq5 Terceira versão do indicador modificado RSI
MQL5\Indicators\RSI\ChartRSI_Plus1\ChartRSI_Plus1.mq5 Quarta versão do indicador modificado RSI
MQL5\Indicators\RSI\ChartRSI_Plus2\ChartRSI_Plus2.mq5 Quinta versão do indicador modificado RSI


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

Arquivos anexados |
MQL5.zip (31.59 KB)
Últimos Comentários | Ir para discussão (1)
luisd72
luisd72 | 16 abr 2021 em 02:17

Great article and amazing work, comes with entry points and where to reinforce our position... congratulations.

I just installed this.

Just one question. I was watching the several files, can you set a alarm?

50 000 encomendas atendidas no Freelance MQL5.com 50 000 encomendas atendidas no Freelance MQL5.com
Mais de 50 000 pedidos foram concluídos até outubro de 2018 pelos membros do serviço oficial Freelance MetaTrader — o maior site freelance do mundo para programadores MQL, contando com mais de mil desenvolvedores, com dezenas encomendas diárias e com localização em 7 idiomas.
Redes Neurais Profundas (Parte VII). Ensemble de redes neurais: stacking Redes Neurais Profundas (Parte VII). Ensemble de redes neurais: stacking
Nós continuamos a construir os ensembles. Desta vez, o bagging de ensemble criado anteriormente será complementado com um combinador treinável — uma rede neural profunda. Uma rede neural combina as 7 melhores saídas ensemble após a poda. A segunda obtém todas as 500 saídas do ensemble como entrada, realizando a poda e combinando elas. As redes neurais serão construídas usando o pacote keras/TensorFlow para Python. Os recursos do pacote serão brevemente considerados. Serão realizados os testes e a comparação da qualidade de classificação do bagging e stacking de ensembles.
Negociação social. Sera que um sinal lucrativo pode ainda ser melhorado? Negociação social. Sera que um sinal lucrativo pode ainda ser melhorado?
A maioria dos assinantes escolhe sinais de negociação pela aparência da curva de saldo e pelo número de assinantes. Daí que muitos provedores hoje se preocupam mais com estatísticas bonitas do que com a qualidade real do sinal, muitas vezes testando diversos volumes de transação e melhorando artificialmente a aparência da curva de saldo. Este artigo aborda critérios de confiabilidade e maneiras pelas quais um provedor pode melhorar a qualidade de seu sinal. São exemplificadas a análise do histórico de um determinado sinal e as maneiras que podem ajudar o provedor a torná-lo mais lucrativo e menos arriscado.
950 sites transmitindo o calendário econômico da MetaQuotes 950 sites transmitindo o calendário econômico da MetaQuotes
A adição do widget fornece os sites com um cronograma detalhado de 500 indicadores das maiores economias do mundo. Assim, além do conteúdo principal do site, os traders recebem rapidamente informações atualizadas sobre todos os eventos importantes com explicações e gráficos.