Indicador universal RSI para operar simultáneamente en dos direcciones

19 octubre 2018, 08:45
Anatoli Kazharski
0
1 177

Contenido

Introducción

Al desarrollar algoritmos comerciales, casi siempre topamos con un problema: ¿cómo determinar dónde comienza y dónde termina la tendencia/flat? Resulta difícil encontrar una solución inequívoca. Parece que el objetivo se puede lograr combinando en un algoritmo las estrategias de ambos tipos: tendencia y flat. En este artículo, crearemos un indicador universal en el que se combinarán las señales de diferentes tipos de estrategias. También intentaremos simplificar al máximo la obtención de señales para realizar transacciones comerciales en un experto. Asimismo, mostraremos un ejemplo de combinación de varios indicadores distintos en uno. Para ello, usaremos la programación orientada a objetos, en la que cada indicador o una parte del mismo se implementa como una clase conectada al archivo principal del programa. 

Por lo tanto, nuestra tarea será escribir un experto en el que se combinarán dos estrategias comerciales: una para trabajar con la tendencia, y la otra para trabajar en flat. Supongamos que el comercio simultáneo en las dos direcciones resulta más efectivo, y que dicha estrategia será más estable. Entonces, resultará cómodo conectar al experto solo un indicador en el que se generan las señales para ambos tipos de estrategias. Dentro de él, podremos implementar un sistema complejo para determinar las señales de compra y venta. También podría resultarnos necesario combinar en uno solo varios indicadores idénticos con diferentes configuraciones. O incluso incluir en un indicador dos indicadores diferentes: uno principal, y otro de filtrado (auxiliar). Estos esquemas se pueden implementar cómodamente con la ayuda de la POO.

En el esquema se muestra cómo se conectan al archivo principal del indicador (Indicator.mq5) dos clases de indicadores (CIndicator1 y CIndicator2). CIndicator2 es un indicador auxiliar, los resultados de sus cálculos son necesarios para CIndicator1. Aquí usaremos el método para determinar la barra verdadera del que ya hablamos en el artículo sobre la creación de indicadores multidivisa. Para este artículo, hemos escrito una clase aparte, CFirstTrueBar. Este se conectará a todos los indicadores para evitar el cálculo en aquellas barras que son irrelevantes para el periodo de tiempo actual.

 Fig. 1 – Uno de los posibles esquemas de creación de un indicador utilizando el método de POO.

Fig. 1. Uno de los posibles esquemas de creación de un indicador utilizando el método de POO.


Eligiendo el indicador

Para generar señales, podemos elegir cualquier indicador del paquete estándar del terminal. La esencia de la mayoría de ellas es la misma, y por lo general, una no tiene ventaja sobre otra. Algunas combinaciones de indicadores y filtros serán efectivas por un cierto periodo de tiempo, otras, durante otro.

Pero para que la investigación resulte más cómoda, será mejor elegir los indicadores de tipo oscilador. Se pueden utilizar para determinar las señales tanto de tendencia, como de flat. Según los datos del oscilador, podemos incluso construir un canal de precios. Como resultado, tendremos la oportunidad de crear un indicador universal, muy cómodo al desarrollar estrategias comerciales de cualquier nivel de complejidad.

Como ejemplo, en esta serie de artículos tomaremos el indicador RSI (Relative Strength Index). Abajo mostramos el resultado de los cálculos de este indicador con un periodo 8 en el gráfico AUDUSD, H1

 Fig. 2. Indicador Relative Strength Index.

Fig. 2. Indicador Relative Strength Index.

A primera vista, parece que las señales de este indicador hacen más fácil lograr beneficios. Pero se trata de una confusión. Necesitaremos mucho trabajo antes de llegar a saber aproximadamente qué dirección seguir. En este caso, además, no hay garantías de que vayamos a lograr nuestros objetivos. 

Veamos un caso más simple y obvio: pensamos que es posible obtener beneficios cuando la línea del indicador cruza los niveles predeterminados: 70/30. Si el nivel 70 es cruzado de arriba hacia abajo, estaremos ante una señal de venta. Si el nivel 30 es cruzado de abajo hacia arriba, estaremos ante una señal de compra. Pero vemos muchas señales falsas, y en este caso, además, el precio se irá en la dirección opuesta a la posición abierta. 

Vamos a ver otro ejemplo de análisis de las señales de este indicador (ver fig. 3). Se ve que el precio desciende durante mucho tiempo. Con líneas rojas se marcan las señales que se forman cuando el indicador cruza el nivel 30 de abajo hacia arriba. Si en su algoritmo se ha implementado la compra basada en estas señales, entonces se encontrará con reducción flotante. Si usted coloca un stop loss cada vez que abre una posición, entonces registrá pérdidas en varias ocasiones. En este caso, además, el precio ni siquiera ha alcanzado un resultado positivo desde la última señal de compra hasta la señal de venta (línea verde). Como resultado, usted obtendrá pérdidas. Asimismo, esta parte del gráfico sugiere la posibilidad de negociar con la tendencia, es decir, no percibimos nada erróneo.

Este tipo de problemas surgen al utilizar cualquier indicador. Por ello, no importa cuál elijamos para trabajar posteriormente.

 Fig. 3. Señales según el indicador RSI.

Fig. 3. Señales según el indicador RSI.


Modificando el indicador RSI

Es necesario añadir ciertos detalles al indicador elegido, para que luego resulte más cómodo a la hora de trabajar con el experto. Vamos a crear 5 versiones de RSI, moviéndonos paulatinamente de lo simple a lo complejo (para que el lector lo comprenda con mayor facilidad).

Primera parte. Añadiendo los búferes de señal

La versión estándar de RSI se encuentra en el directorio del terminal \MQL5\Indicators\Examples. Hacemos una copia suya y comenzamos a introducir cambios en ella. Añadimos dos búferes de indicador más a la lista de parámetros fijos. Su número total ahora será de 5, mientras que en el gráfico se representarán 3. Dos búferes quedarán reservados para los cálculos auxiliares. Las etiquetas de compra las representaremos en color verde (clrMediumSeaGreen), y las etiquetas de venta, en rojo (clrRed). 

//--- Propiedades
#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

Determinamos los códigos de las etiquetas de las señales. Si necesitamos representar puntos, usaremos el código 159. Si necesitamos representar las señales en forma de flechas, usaremos los códigos 233 y 234, respectivamente.

//--- Flechas para las señales: 159 - puntos; 233/234 - flechas
#define ARROW_BUY  159
#define ARROW_SELL 159

El cruce de los límites de las líneas del indicador puede ser tanto una señal de compra, como de venta. Por lo tanto, para el parámetro externo, vamos a necesitar una enumeración con la que sea posible especificar cómo interpretar las señales del indicador.

  • Break in — ruptura de los límites dentro del intervalo. La ruptura del límite inferior de abajo hacia arriba será la señal de compra, y la ruptura del límite superior de arriba hacia abajo será la señal de venta.
  • Break in reverse — ruptura de los límites dentro del intervalo (en contra del impulso). Las mismas señales que en el modo Break in, pero las condiciones de compra y venta se invierten.
  • Break out — ruptura de los límites desde dentro hacia fuera del intervalo. La ruptura del límite superior de abajo hacia arriba será la señal de compra, y la ruptura del límite inferior de arriba hacia abajo será la señal de venta. 
  • Break out reverse — ruptura de los límites desde dentro hacia fuera del intervalo (en contra del impulso). Las mismas señales que en el modo Break out, pero las condiciones de compra y venta se invierten.

Mostraremos todos estos modos en el gráfico de abajo.

//--- Enumerando los modos de ruptura de los límites del 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
  };

En total, en el indicador habrá tres parámetros externos:

  • RSI Period — periodo del indcador;
  • Signal Level — niveles del indicador;
  • Break Mode — modo de ruptura de los niveles.

//--- 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

Las propiedades del indicador las establecemos en la función SetPropertiesIndicator(). Las matrices auxiliares las establecemos en último lugar. Todas las matrices de indicador se inicializan con valores cero en la función ZeroIndicatorBuffers(). A continuación,  indicamos que los valores cero no se deben representar en el gráfico, es decir, estos valores estarán vacíos.

//+------------------------------------------------------------------+
//| Estableciendo las propiedades del indicador                      |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Nombre corto
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS1");
//--- Dígitos decimales
   ::IndicatorSetInteger(INDICATOR_DIGITS,2);
//--- Matrices del 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);
//--- Inicializando matrices
   ZeroIndicatorBuffers();
//--- Estableciendo las etiquetas de texto
   string plot_label[]={"RSI","buy","sell"};
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetString(i,PLOT_LABEL,plot_label[i]);
//--- Estableciendo el grosor para las matrices de indicador
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_LINE_WIDTH,1);
//--- Estebleciendo el tipo para las matrices 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 las etiquetas
   ::PlotIndexSetInteger(1,PLOT_ARROW,ARROW_BUY);
   ::PlotIndexSetInteger(2,PLOT_ARROW,ARROW_SELL);
//--- Índice del elemento a partir del cual comienza el cálculo
   for(int i=0; i<indicator_plots; i++)
      ::PlotIndexSetInteger(i,PLOT_DRAW_BEGIN,period_rsi);
//--- Número de niveles horizontales del indicador
   ::IndicatorSetInteger(INDICATOR_LEVELS,2);
//--- Valores de los niveles horizontales del indicador
   up_level   =100-SignalLevel;
   down_level =SignalLevel;
   ::IndicatorSetDouble(INDICATOR_LEVELVALUE,0,down_level);
   ::IndicatorSetDouble(INDICATOR_LEVELVALUE,1,up_level);
//--- Estilo de la línea
   ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,0,STYLE_DOT);
   ::IndicatorSetInteger(INDICATOR_LEVELSTYLE,1,STYLE_DOT);
//--- Valor vacío para la construcción que no tiene dibujado
   for(int i=0; i<indicator_buffers; i++)
      ::PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,0);
//--- Desplazamiento en el eje 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);
     }
  }

Los cálculos preliminares y básicos de los valores del indicador RSI se muestran para mayor comodidad en las funciones aparte PreliminaryCalculations() y CalculateRSI(). Su contenido es el mismo que en el indicador RSI del paquete estándar. Vamos a analizar solo la función para determinar las señales del indicador CalculateSignals(). Aquí, verificamos en primer lugar las condiciones según el modo establecido en los parámetros externos. Luego, si se cumplen las condiciones, guardamos el valor del indicador RSI en la matriz de indicador correspondiente. De no cumplirse la condición, guardaremos los valores cero, que no se mostrarán en el gráfico.

//+------------------------------------------------------------------+
//| Calculando las señales del indicador                             |
//+------------------------------------------------------------------+
void CalculateSignals(const int i)
  {
   bool condition1 =false;
   bool condition2 =false;
//--- Ruptura dentro del 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;
     }
//--- Representamos las señales, si las condiciones se han cumplido
   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;
     }
  }

En definitiva, el código de las funciones básicas del indicador, tales como OnInit() y OnCalculate(), tendrá un aspecto cuidado y legible:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Comprobando los valores del 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;
//--- Estableciendo las propiedades del 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[])
  {
//--- Salir, si no hay suficientes datos
   if(rates_total<=period_rsi)
      return(0);
//--- Cálculos preliminares
   PreliminaryCalculations(prev_calculated,close);
//--- Ciclo principal para los cálculos
   for(int i=start_pos; i<rates_total && !::IsStopped(); i++)
     {
      //--- Calculando el indicador RSI
      CalculateRSI(i,close);
      //--- Calculando las señales
      CalculateSignals(i);
     }
//--- Retornando el último número de elementos calculado
   return(rates_total);
  }

Cargamos este indicador en el gráfico para ver qué hemos conseguido. Abajo se muestran los resultados de los cuatro modos de trabajo (parámetro externo Break Mode).

 Fig. 4. Demostración del funcionamiento del indicador RSI modificado.

Fig. 4. Demostración del funcionamiento del indicador RSI modificado.

Después de esta modificación del indicador RSI, ya se ve mejor cuántas señales falsas da. Tras analizar los gráficos, llegamos a la conclusión de que según este indicador, a veces es mejor comerciar en flat, y a veces, según la tendencia. 

  • El modo Break in ha sido pensado para comerciar en flat en la dirección de RSI.
  • El modo Break in reverse ha sido pensado para comerciar según la tendencia, en contra de la dirección de RSI.
  • El modo Break out ha sido pensado para comerciar en la dirección de RSI.
  • El modo Break out reverse ha sido pensado para comerciar en flat en dirección contraria a RSI.

¿Es posible comerciar con las señales de este indicador solo en flat o solo según la tendencia? ¿Cómo podemos aumentar la probabilidad de obtener resultados positivos, así como la precisión de las entradas? Es muy posible que, si la posición no se abre según la primera señal en una serie continua, esto ayude a mejorar el resultado.

Segunda parte. Añadiendo los búferes de los contadores de señal

Vamos a intentar añadir indicadores intermedios, en los que mostraremos el número de señales continuas en una dirección. En cuanto llegue la señal opuesta, el contador del búfer anterior se reiniciará y se activará el contador para la serie de señales actual. Para su implementación, tendremos que completar un tanto el código.

Habrá cambios en parámetros específicos. Indicamos el nuevo número de búferes, las series para el dubujado y establecemos el color para las mismas.

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

A nivel global, añadimos dos matrices adicionales para mostrar los valores de los contadores y las dos variables auxiliares correspondientes:

//--- Matrices de indicador
...
double buy_counter_buffer[];
double sell_counter_buffer[];
//--- Contadores de secuencias de señales continuas
int buy_counter  =0;
int sell_counter =0;

El resto de los cambios están relacionados con la función para determinar las señales. Aquí, si se cumplen las condiciones (aparece la siguiente señal de compra o venta), se activará el contador correspondiente, y el contador de la serie opuesta de señales se reseteará. Para evitar que el contador aumente en la barra actual, hay que omitir su activación en la última barra no formada.

//+------------------------------------------------------------------+
//| Calculando las señales del indicador                             |
//+------------------------------------------------------------------+
void CalculateSignals(const int i,const int rates_total)
  {
   int last_index=rates_total-1;
//---
   bool condition1 =false;
   bool condition2 =false;
//--- Ruptura dentro del 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;
     }
//--- Representamos las señales, si las condiciones se han cumplido
   if(BreakMode==BREAK_IN || BreakMode==BREAK_OUT)
     {
      buy_buffer[i]  =(condition1)? rsi_buffer[i] : 0;
      sell_buffer[i] =(condition2)? rsi_buffer[i] : 0;
      //--- Contador solo de las 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 solo de las barras formadas
      if(i<last_index)
        {
         if(condition2)
           {
            buy_counter++;
            sell_counter=0;
           }
         else if(condition1)
           {
            sell_counter++;
            buy_counter=0;
           }
        }
     }
//--- Corriegiendo el último valor (igual al 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];
     }
  }

Después de introducir las adiciones, cargamos el indicador en el gráfico, para ver qué resultados hemos obtenido. Ahora el indicador RSI es más informativo. Los valores de estos contadores se pueden obtener en el experto comercial para formar las condiciones adicionales de compra y venta.

 Fig. 5. Indicador RSI modificado con contadores de señales continuas unidireccionales.

Fig. 5. Indicador RSI modificado con contadores de señales continuas unidireccionales.

Para asegurarnos de que todo funciona como habíamos pensado, vamos a probar el indicador en el simulador y en tiempo real. Resultados del funcionamiento del simulador de estrategias:

 Fig. 6. Comprobación del funcionamiento del indicador RSI modificado en el simulador de estrategias.

Fig. 6. Comprobación del funcionamiento del indicador RSI modificado en el simulador de estrategias.

En la siguiente captura vemos qué muestra el indicador en todos los modos del parámetro Break Mode.

 Fig. 7. Indicador en todos los modos del parámetro Break Mode.

Fig. 7. Indicador en todos los modos del parámetro Break Mode.

En el gráfico podemos ver las situaciones en las que el precio puede moverse una gran distancia entre dos señales unidireccionales (ver fig. 8). Esto sucede con frecuenciia, y con cualquier marco temporal. En este caso, además, se puede cambiar cuanto se desee el parámetro Signal Level, configurando los límites del intervalo. Dicha incertidumbre puede obstaculizar la creación de una lógica comercial clara o complicarla con la necesidad de escribir condiciones adicionales. 

 Fig. 8. Señales omitidas durante un desplazamiento de precio poco significativo

Fig. 8. Señales omitidas durante un desplazamiento de precio poco significativo

En la próxima versión eliminaremos estas omisiones, haciendo el indicador todavía más informativo.  

Tercera parte. Aumentando el número de señales, excluyendo las omisiones

En esta versión, el número de búferes de indicador sigue siendo el mismo, pero deberemos añadir matrices de niveles de indicador, que se utilizarán para generar señales cuando el indicador cruce la línea.  

//--- Valores de los niveles horizontales del indicador y su número
double up_level          =0;
double down_level        =0;
int    up_levels_total   =0;
int    down_levels_total =0;
//--- Matrices de los niveles horizontales 
double up_levels[];
double down_levels[];

Para que no se den las omisiones de señales sobre las que hemos hablado en el apartado anterior, estableceremos los niveles cada 5 puntos. Es decir, si en el parámetro externo Signal Level se ha establecido el nivel 30, para los niveles superiores, se calcularán estos valores: 70, 75, 80, 85, 90, 95. 

Para calcular los niveles del indicador, se ha pensado la función GetLevelsIndicator(). En dos ciclos aparte, se calculan los valores de los niveles que se ubican en las matrices. La función retorna el número total de niveles.

//+------------------------------------------------------------------+
//| Retornando los niveles del indicador                             |
//+------------------------------------------------------------------+
int GetLevelsIndicator(void)
  {
   int levels_counter=0;
   double level=down_level;
//--- Los niveles inferiores hasta el límite 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;
//--- Los niveles superiores hasta el límite 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);
  }

Los niveles se establecen en la función SetPropertiesIndicator(). Abajo mostramos su versión abreviada. Aquí, en primer lugar se calculan los niveles iniciales para los intervalos inferior y superior y se resetean las matrices de los niveles. A continuación, establecemos el número de niveles del indicador llamando la función GetLevelsIndicator(). Después de ello, establecemos a partir de las matrices los niveles ya calculados de los intervalos inferior y superior.

//+------------------------------------------------------------------+
//| Estableciendo las propiedades del indicador                      |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Calculando los primeros niveles
   up_level   =100-SignalLevel;
   down_level =SignalLevel;
//--- Reseteando las matrices de los niveles
   ::ArrayFree(up_levels);
   ::ArrayFree(down_levels);
//--- Número de niveles horizontales del indicador
   ::IndicatorSetInteger(INDICATOR_LEVELS,GetLevelsIndicator());
//--- Valores de los niveles horizontales del indicador del nivel 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 los niveles horizontales del indicador del nivel 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 consiguiente, debemos introducir los cambios en la función CalculateSignals(). Aquí también se muestra solo la parte modificada de la función. Para comprobar la ejecución de las condiciones en los ciclos, miramos si se cruza aunque sea uno de los niveles en las matrices. 

//+------------------------------------------------------------------+
//| Calculando las señales del indicador                             |
//+------------------------------------------------------------------+
void CalculateSignals(const int i,const int rates_total)
  {
   int last_index=rates_total-1;
//---
   bool condition1 =false;
   bool condition2 =false;
//--- Ruptura dentro del 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;
        }
     }
//--- Representamos las señales, si las condiciones se han cumplido
...
//--- Corriegiendo el último valor (igual al penúltimo)
...
  }

En la fig. 9 se muestra el aspecto que tiene. 

 Fig. 9. Formando las señales al cruzarse varios niveles.

Fig. 9. Formando las señales al cruzarse varios niveles.

Hemos resuelto un problema, pero han surgido dos más. El primero reside en la necesidad de excluir aquellas señales en las que el precio resulta superior a la anterior compra o inferior a la anterior venta. En la fig. 10 se muestra una situación así para una serie de señales de compra: el precio en la última señal de la serie es superior al precio de la señal anterior. Esto es relevante para los modos Break in y Break out reverse, cuando debemos ceñirnos a la idea de comprar más barato y vender más caro.

 Fig. 10. Situación cuando el precio de la señal es superior al precio de la señal anterior.

Fig. 10. Situación cuando el precio de la señal es superior al precio de la señal anterior.

El segundo problema lo representan las señales demasiado frecuentes, en ocasiones en varias barras seguidas. En este caso, además, entre las señales pasa una distancia poco significativa (ver fig. 11).

 Fig. 11. Acumulación de señales frecuentes con un movimiento de precio pequeño.

Fig. 11. Acumulación de señales frecuentes con un movimiento de precio pequeño.

Nos ocuparemos de las soluciones a estos problemas en la próxima versión.

Cuarta parte. Transfiriendo el indicador a la ventana principal del gráfico

Esta versión del indicador la trasladaremos al gráfico principal. Aquí se puede ver mejor cómo funciona el indicador: veremos las señales directamente en el precio. Para controlar la distancia entre señales (en puntos), podemos simplemente establecer un valor fijo en el parámetro externo. Pero vamos a intentar hacer una variante dinámica y vincular esta magnitud al indicador de volatilidad (ATR). Para mayor comodidad, implementaremos los cálculos para los indicadores en clases aparte: CATR y CRsiPlus. Usando este método, podremos combinar cualquier número de indicadores, juntando los resultados de sus cálculos en un programa. 

Determinando las barras verdaderas

Se supone que esta es la versión que usaremos en el futuro para crear un experto comercial. Por ello, para eliminar la influencia de los marcos temporales mayores en los datos históricos, cuando las barras del periodo actual no sean suficientes, determinaremos la primera barra verdadera. Ya hemos hablado de ello con detalle en el artículo sobre indicadores multidivisa. Para determinar la primera barra verdadera, escribiremos una clase aparte, CFirstTrueBar. Vamos a ver primero cómo se construye esta clase.

Las definiciones de los miembros y los métodos de la clase CFirstTrueBar se muestran abajo. Vamos a analizarlas brevemente. 

//+------------------------------------------------------------------+
//| Clase para determinar la barra verdadera                         |
//+------------------------------------------------------------------+
class CFirstTrueBar
  {
private:
   //--- Hora de la barra verdadera
   datetime          m_limit_time;
   //--- Número de la barra verdadera
   int               m_limit_bar;
   //---
public:
                     CFirstTrueBar(void);
                    ~CFirstTrueBar(void);
   //--- Retornando (1) la hora y (2) el número de la barra verdadera
   datetime          LimitTime(void) const { return(m_limit_time); }
   int               LimitBar(void)  const { return(m_limit_bar);  }
   //--- Determinando la primera barra verdadera
   bool              DetermineFirstTrueBar(void);
   //---
private:
   //--- Buscando la primera barra verdadera del periodo actual
   void              GetFirstTrueBarTime(const datetime &time[]);
  };

Para buscar la barra verdadera se usa el método privado CFirstTrueBar::GetFirstTrueBarTime(). En él se debe transmitir la matriz de hora de las barras de la historia para buscar la primera barra verdadera. Iteramos desde el inicio de la matriz, y en cuanto hallamos una barra que se corresponda con el marco temporal actual, recordamos la hora y el índice de esta barra. Cuando hemos definido una barra verdadera, obtenemos su hora e índice con la ayuda de los métodos CFirstTrueBar::LimitTime() y CFirstTrueBar::LimitBar().

//+------------------------------------------------------------------+
//| Busca la primera barra verdadera del periodo actual              |
//+------------------------------------------------------------------+
void CFirstTrueBar::GetFirstTrueBarTime(const datetime &time[])
  {
//--- Obtenemos el tamaño de la matriz
   int array_size=::ArraySize(time);
   ::ArraySetAsSeries(time,false);
//--- Comprobando cada barra una a una
   for(int i=1; i<array_size; i++)
     {
      //--- Si la barra se corresponde con el marco temporal actual
      if(time[i]-time[i-1]==::PeriodSeconds())
        {
         //--- Guardando y deteniendo el ciclo
         m_limit_time =time[i-1];
         m_limit_bar  =i-1;
         break;
        }
     }
  }

El método CFirstTrueBar::GetFirstTrueBarTime() se llama en el método CFirstTrueBar::DetermineFirstTrueBar(). Precisamente aquí obtenemos la matriz de hora de las barras, según la cual buscamos la primera barra verdadera a continuación.

//+------------------------------------------------------------------+
//| Determinando la primera barra verdadera                          |
//+------------------------------------------------------------------+
bool CFirstTrueBar::DetermineFirstTrueBar(void)
  {
//--- Matriz de hora de las barras
   datetime time[];
//--- Obteniendo el número total de barras del símbolo
   int available_bars=::Bars(_Symbol,_Period);
//--- Copiando la matriz de hora de las barras. Si no lo hemos logrado, lo intentamos de nuevo.
   if(::CopyTime(_Symbol,_Period,0,available_bars,time)<available_bars)
      return(false);
//--- Obteniendo la hora de la primera barra verdadera que se corresponda con el marco temporal actual
   GetFirstTrueBarTime(time);
   return(true);
  }

Añadiendo el indicador ATR

El indicador ATR se calculará de la misma forma que en el paquete estándar. Tomamos el código de aquí: \MQL5\Indicators\Examples. Abajo mostramos la declaración de lo miembros y métodos de la clase CATR. La diferencia con la versión estándar radica en que aquí determinaremos la primera barra verdadera a partir de la cual comenzarán los cálculos.

Incluimos el archivo con la clase CFirstTrueBar y declaramos su ejemplar en el cuerpo de la clase CATR. Preste atención a que las matrices de indicador aquí se declaran con acceso público. Esto es necesario para que quede la posibilidad de establecerlos como búferes de indicador en el archivo principal del indicador.

//+------------------------------------------------------------------+
//|                                                          ATR.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "FirstTrueBar.mqh"
//+------------------------------------------------------------------+
//| Indicador ATR                                                    |
//+------------------------------------------------------------------+
class CATR
  {
private:
   //--- Para determinar la primera barra verdadera
   CFirstTrueBar     m_first_true_bar;
   //--- Periodo del indicador
   int               m_period;
   //--- Limitador en los cálculos de los valores del indicador
   int               m_limit;
   //---
public:
   //--- Búferes de indicador
   double            m_tr_buffer[];
   double            m_atr_buffer[];
   //---
public:
                     CATR(const int period);
                    ~CATR(void);
   //--- Periodo del indicador
   void              PeriodATR(const int period) { m_period=period; }
   //--- Calculando el indicador ATR 
   bool              CalculateIndicatorATR(const int rates_total,const int prev_calculated,const datetime &time[],const double &close[],const double &high[],const double &low[]);
   //--- Reseteando los búferes 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 ATR 
   void              CalculateATR(const int i,const datetime &time[],const double &close[],const double &high[],const double &low[]);
  };

La primera barra verdadera, a partir de la cual se calculará en lo sucesivo el indicador, se determina en el método para los cálculos preliminares. Si no ha sido determinada, salimos del 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[])
  {
//--- Si el cálculo se da por primera vez o ha habido cambios
   if(prev_calculated==0)
     {
      //--- Definiendo el nombre de la barra verdadera
      m_first_true_bar.DetermineFirstTrueBar();
      //--- Salimos si la barra verdadera no ha sido determinada
      if(m_first_true_bar.LimitBar()<0)
         return(false);
      //---
      m_tr_buffer[0]  =0.0;
      m_atr_buffer[0] =0.0;
      //--- Barra a partir de la cual se dará el cálculo
      m_limit=(::Period()<PERIOD_D1)? m_first_true_bar.LimitBar()+m_period : m_period;
      //--- Salir, si estamos fuera del intervalo (no hay barras suficientes)
      if(m_limit>=rates_total)
         return(false);
      //--- Calculando los valores del intervalo verdadero
      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]);
      //--- Los primeros valores de ATR no se calculan
      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 del primer valor
      first_value/=m_period;
      m_atr_buffer[m_limit-1]=first_value;
     }
   else
      m_limit=prev_calculated-1;
//---
   return(true);
  }

Filtrando las señales en el indicador RSI

Aparte de los búferes de indicador calculados más arriba, en este versión de RSI habrá dos más. Se trata de niveles continuos que se construyen aparte según los precios de las señales de compra y venta, teniendo en cuenta el spread. Para que haya la posibilidad de incluir en los cálculos los datos del indicador ATR, debemos obtener el puntero al ejemplar ATR creado en el archivo principal del programa. Por eso aquí declaramos un puntero del tipo CATR y los métodos correspondientes de instalación y obtención.

Para optimizar el código, algunos de sus bloques ahora han sido implementados como métodos aparte. Se trata de la comprobación de las condiciones, el trabajo con los contadores, etc. El único método nuevo entre ellos, que no hemos visto aún, es CRsiPlus::DirectionControl(). Precisamente en este se controla la dirección del movimiento y se criban las señales según la volatilidad actual. Además, existen métodos auxiliares para eliminar las señales sobrantes CRsiPlus::DeleteBuySignal() y CRsiPlus::DeleteSellSignal().

//+------------------------------------------------------------------+
//|                                                          RSI.mqh |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "FirstTrueBar.mqh"
#include "ATR.mqh"
//--- Enumerando los modos de ruptura de canales
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 con filtro de volatilidad                          |
//+------------------------------------------------------------------+
class CRsiPlus
  {
private:
   //--- Para determinar la primera barra verdadera
   CFirstTrueBar     m_first_true_bar;
   //--- Puntero a ATR
   CATR             *m_atr;
   
   //--- Periodo del indicador
   int               m_period;
   //--- Nivel de RSI
   double            m_signal_level;
   //--- Modo de formación de las señales
   ENUM_BREAK_INOUT  m_break_mode;
      
   //--- Contadores de las señales unidireccionales
   int               m_buy_counter;
   int               m_sell_counter;
   //--- Niveles 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 en los cálculos de los valores del indicador
   int               m_limit;
   //--- Para determinar la última barra
   bool              m_is_last_index;
   //---
public:
   //--- Búferes 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) {}
   //--- Puntero a ATR
   void              AtrPointer(CATR &object) { m_atr=::GetPointer(object);  }
   CATR             *AtrPointer(void)         { return(::GetPointer(m_atr)); }
   //--- Calculando el indicador RSI
   bool              CalculateIndicatorRSI(const int rates_total,const int prev_calculated,const double &close[],const int &spread[]);
   //--- Inicializando todos los búferes de indicador
   void              ZeroIndicatorBuffers(void);
   //---
private:
   //--- Obteniendo los niveles del indicador
   int               GetLevelsIndicator(void);
   //--- Cálculos preliminares
   bool              PreliminaryCalc(const int rates_total,const int prev_calculated,const double &close[]);
   //--- Calculando la serie de RSI
   void              CalculateRSI(const int i,const double &price[]);
   //--- Calculando las señales del indicador
   void              CalculateSignals(const int i,const int rates_total,const double &close[],const int &spread[]);

   //--- Comprobando las condiciones
   void              CheckConditions(const int i,bool &condition1,bool &condition2);
   //--- Comprobando los contadores
   void              CheckCounters(bool &condition1,bool &condition2);
   //--- Aumentando los contadores buy y sell
   void              IncreaseBuyCounter(const bool condition);
   void              IncreaseSellCounter(const bool condition);

   //--- Control de la dirección del moviento
   void              DirectionControl(const int i,bool &condition1,bool &condition2);
   //--- Eliminando las señales buy y sell sobrantes
   void              DeleteBuySignal(const int i);
   void              DeleteSellSignal(const int i);
   //--- Reseteando el elemento indicado de los búferes de indicador
   void              ZeroIndexBuffers(const int index);
  };

En el método CRsiPlus::DirectionControl() se comprueban las siguientes condiciones, según las cuales se determina si la señal es sobrante:

  • Si hay señal, pero el tamaño del impulso es menor que la volatilidad actual
  • Si la señal formada no va en la dirección de la serie actual.

Si las condiciones se cumplen, la señal se elimina.

//+------------------------------------------------------------------+
//| Control de la dirección del movimiento                           |
//+------------------------------------------------------------------+
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;
//--- Si "reverse" está desactivado
   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" activado     
   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);
        }
     }
  }

Ahora vamos a ver cómo está construido el archivo principal del indicador. En esta versión del indicador ya hay 11 búferes, de los cuales 7 son básicos, y 4 son auxiliares.

//--- Propiedades
#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

Para que resulte más cómodo, todos los archivos utilizados con las clases están ubicados en el directorio del indicador, en la carpeta Includes:

 Fig. 12. Directorio del indicador.

Fig. 12. Directorio del indicador.

Por eso, la conexión con el archivo principal tendrá el aspecto que sigue:

//--- Conectando las clases de los indicadores
#include "Includes\ATR.mqh"
#include "Includes\RsiPlus.mqh"

A los parámetros externos, añadimos otro más para el periodo del 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

Los indicadores se actualizan con la transmisión de los parámetros al constructor.

//--- Ejemplares de los indicadores para el trabajo
CATR     atr(PeriodATR);
CRsiPlus rsi(PeriodRSI,SignalLevel,BreakMode);

En la función de inicialización OnInit() no debemos olvidar transmitir al indicador RSI el puntero a ATR.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Inicializando indicadores
   rsi.AtrPointer(atr);
//--- Estableciendo las propiedades del indicador
   SetPropertiesIndicator();
  }

Dado que las matrices asignadas para los búferes de indicador se declaran públicamente en cada clase de los indicadores, su adición al indicador en el archivo principal parece una conexión normal de matrices dinámicas.

//+------------------------------------------------------------------+
//| Estableciendo las propiedades del indicador                      |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Nombre corto
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS_CHART");
//--- Dígitos decimales
   ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits());
//--- Búferes de 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);
//--- Inicializando matrices
   atr.ZeroIndicatorBuffers();
   rsi.ZeroIndicatorBuffers();
...
  }

Las matrices auxiliares usadas para los cálculos adicionales no se representan en el gráfico y en la ventana de datos Si es necesario, para que los datos se representen en la ventana de datos (pero sin verse en el gráfico), debemos establecer para estas series la propiedad DRAW_NONE.

//+------------------------------------------------------------------+
//| Estableciendo las propiedades del indicador                      |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Estableciendo el tipo para los búferes 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]);
...
  }

El contenido de la función OnCalculate() se reduce a la llamada de los dos métodos para el cálculo del indicador ATR y 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 el indicador ATR
   if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low))
      return(0);
//--- Calculando el indicador RSI
   if(!rsi.CalculateIndicatorRSI(rates_total,prev_calculated,close,spread))
      return(0);
//--- Retornando el último número de elementos calculado
   return(rates_total);
  }

Después de compilar y cargar el indicador, en el gráfico se verá un resultado como en la fig. 13. Los niveles que se construyen según las señales del indicador forman un canal peculiar. Según el mismo, es fácil determinar la distancia que ha pasado el precio desde el la última señal opuesta de la serie anterior.

 Fig. 13. Resultado del funcionamiento del indicador RSI modificado en el gráfico principal.

Fig. 13. Resultado del funcionamiento del indicador RSI modificado en el gráfico principal.

Al desarrollar un sistema comercial, podríamos necesitar recibir de forma visual los valores precisos de este o aquel búfer de indicador. Esto se refiere especialmente a los casos en los que en el gráfico no se ven las series de búferes de indicador cuyos valores se salen de la zona visible. Sus valores se pueden ver en la Ventana de datos. Para mayor comodidad, podemos activar el cursor en cruz con la ruleta del ratón. En la fig. 14 se ha añadido al gráfico el indicador ATR del paquete estándar, para que sea posible comparar con lo calculado en el RSI modificado.

 Fig. 14. Visualización de valores en la ventana de datos del indicador.

Fig. 14. Visualización de valores en la ventana de datos del indicador.

Quinta parte. Indicador universal RSI para trabajar simultáneamente en dos direcciones

¿Y qué sucede si necesitamos un indicador que muestre las señales simultáneamente en dos direcciones? Y es que en MetaTrader 5 existe la posibilidad de abrir cuentas con cobertura, y esto significa que podemos desarrollar un sistema con posiciones en direcciones diferentes. Aquí necesitaríamos un indicador que diese a la vez señales según la tendencia y según el flat. 

Vamos a analizar brevemente cómo crear un indicador así. Ya tenemos todo lo necesario, solo tendremos cambios en el archivo principal. En esta versión habrá un total de 20 búferes, de los cuales 15 los usaremos para el dibujado.

#property indicator_buffers 20
#property indicator_plots   15

Las señales según la tendencia las representaremos en forma de flechas, y para la tendencia, como puntos. Así resultará visualmente más comprensible a qué tipo pertenece esta u otra señal.

//--- Flechas para las señales: 159 - puntos; 233/234 - flechas;
#define ARROW_BUY_IN   233
#define ARROW_SELL_IN  234
#define ARROW_BUY_OUT  159
#define ARROW_SELL_OUT 159

Conectaremos los mismos archivos que en la versión anterior, y también en el directorio local del indicador. Como consecuencia, podremos ubicar la única copia de estos archivos donde nos sea más cómodo. 

//--- Conectando las clases de los indicadores
#include "Includes\ATR.mqh"
#include "Includes\RsiPlus.mqh"

En los parámetros externos de esta versión del indicador no necesitamos indicar el tipo de señales. Pero vamos a hacer que resulte posible indicar los niveles para las señales según la tendencia (Signal level In) y flat por separado (Signal level Out)

//--- 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

A continuación, necesitamos dos ejemplares de la clase CRsiPlus: uno para las señales de tendencia, y el segundo para las señales de flat. En ambos casos, se usan las señales de tipo "reverse" (BREAK_IN_REVERSE y BREAK_OUT_REVERSE). Es decir, para el flat, la señal será un impulso desde el canal, mientras que para la tendencia, la entrada se dará en el sentido de la tendencia después del retroceso.

//--- Ejemplares de los indicadores para el trabajo
CATR atr(PeriodATR);
CRsiPlus rsi_in(PeriodRSI,SignalLevelIn,BREAK_IN_REVERSE);
CRsiPlus rsi_out(PeriodRSI,SignalLevelOut,BREAK_OUT_REVERSE);

El puntero al indicador ATR lo debemos transmitir a ambos ejemplares de RSI:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit(void)
  {
//--- Transmitiendo el puntero a ATR  
   rsi_in.AtrPointer(atr);
   rsi_out.AtrPointer(atr);
//--- Estableciendo las propiedades del indicador
   SetPropertiesIndicator();
  }

Los búferes de indicador para cada ejemplar están instalados de tal forma que resulte más sencillo orientarse en el código. El orden, por supuesto, tiene su importancia. Usted debe saber según qué número de búfer podrá obtener después en el experto comercial los valores de esta o aquella serie para formar las condiciones.

//+------------------------------------------------------------------+
//| Estableciendo las propiedades del indicador                      |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
//--- Nombre corto
   ::IndicatorSetString(INDICATOR_SHORTNAME,"RSI_PLUS2_CHART");
//--- Dígitos decimales
   ::IndicatorSetInteger(INDICATOR_DIGITS,::Digits());
//--- Búferes de 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);
//--- Inicializando matrices
   atr.ZeroIndicatorBuffers();
   rsi_in.ZeroIndicatorBuffers();
   rsi_out.ZeroIndicatorBuffers();
...
  }

Para que no haya confusiones sobre qué nivel de señal se corresponde con qué tipo de señales, para el flat los dibujaremos con una línea punteada.

//+------------------------------------------------------------------+
//| Estableciendo las propiedades del indicador                      |
//+------------------------------------------------------------------+
void SetPropertiesIndicator(void)
  {
...
//--- Estableciendo el estilo de las líneas de los búferes de indicador designados
   ::PlotIndexSetInteger(13,PLOT_LINE_STYLE,STYLE_DOT);
   ::PlotIndexSetInteger(14,PLOT_LINE_STYLE,STYLE_DOT);
...
  }

El código de la función OnCalculate() está limitado solo por la llamada de los métodos para cada ejemplar del 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 el indicador ATR
   if(!atr.CalculateIndicatorATR(rates_total,prev_calculated,time,close,high,low))
      return(0);
//--- Calculando el 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 el último número de elementos calculado
   return(rates_total);
  }

En la fig. 15 se muestra qué aspecto tiene el funcionamiento del indicador en el gráfico. Todo esto es solo el resultado del inicio de RSI.

 Fig. 15. Indicador RSI universal.

Fig. 15. Indicador RSI universal.

 Fig. 16. Indicador RSI universal en el simulador de estrategias.

Fig. 16. Indicador RSI universal en el simulador de estrategias.

Así, el indicador muestra los puntos de entrada, además de los puntos en los que se puede incrementar el volumen de la posición. Asimismo, podemos obtener el índice de la señal actual en la serie continua a la que pertenece. Y puesto que aquí tenemos también los niveles de precio, podemos obtener la distancia que ha pasado el precio desde la última señal de la serie anterior. También podemos implementar un algoritmo más complejo. Por ejemplo, al abrir posiciones según la tendencia, podemos cerrarlas parcialmente o por completo según las señales para trabajar con el flat.

Tras analizar los resultados del funcionamiento del indicador en el gráfico, podemos inventar diferentes algoritmos comerciales que primero debemos comprobar en el simulador. Esto también supone un trabajo considerable: las variantes de implementación pueden ser infinitas. Podemos hacer que cada módulo comercial obtenga los resultados del comercio del otro módulo. Es decir, el módulo de comercio según la tendencia puede obtener los resultados comerciales del módulo de comercio en flat, y al revés. Usando como base estos datos, ellos pueden corregir su comportamiento, adecuándose a la situación actual. Cada módulo puede influir en la táctica comercial del otro: cambiar las condiciones de apertura, cierre o acompañamiento de posiciones, o modificar el sistema de gestión de capital (pasándolo de un modelo conservador a uno agresivo, y al contrario). El sistema comercial puede ser muy complejo y adecuarse al comportamiento actual del precio.

Conclusión

Como podemos ver, un indicador sencillo se puede hacer más informativo. Esto, a su vez, simplificará la creación de algoritmos comerciales. De lo contrario, deberíamos hacerlo en el experto comercial, complicando con ello su código. Ahora todo está escondido dentro de un solo programa, y podemos simplemente leer los valores ya calculados en sus matrices.

Usted puede continuar con el desarrollo de esta idea. Por ejemplo, puede introducir búferes adicionales para nuevas características que precisan el estado de la tendencia o el flat. Puede añadir un búfer para el trailig-stop cuyo cálculo se vincule a la volatilidad actual (indicador ATR). Como resultado, todos los cálculos necesarios se implementarán en el indicador, y el experto comercial simplemente recibirá los niveles preparados.

Nombre del archivo Comentarios
MQL5\Indicators\RSI\RSI_Plus1.mq5 Nueva versión del indicador RSI modificado
MQL5\Indicators\RSI\RSI_Plus2.mq5 Segunda versión del indicador RSI modificado
MQL5\Indicators\RSI\RSI_Plus3.mq5 Tercera versión del indicador RSI modificado
MQL5\Indicators\RSI\ChartRSI_Plus1\ChartRSI_Plus1.mq5 Cuarta versión del indicador RSI modificado
MQL5\Indicators\RSI\ChartRSI_Plus2\ChartRSI_Plus2.mq5 Quinta versión del indicador RSI modificado


Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/4828

Archivos adjuntos |
MQL5.zip (31.59 KB)
Neuroredes profundas (Parte VIII). Aumentando la calidad de la clasificación de los conjuntos bagging Neuroredes profundas (Parte VIII). Aumentando la calidad de la clasificación de los conjuntos bagging

En el artículo se analizan tres métodos con cuya ayuda podemos aumentar la calidad de clasificación de los conjuntos bagging y valorar su efectividad. Se ha evaluado cómo influye la optimización de los hiperparámetros de las redes neuronales ELM y los parámetros de post-procesado en la calidad de clasificación del conjunto.

50 000 trabajos ejecutados en la bolsa Freelance de MQL5.com 50 000 trabajos ejecutados en la bolsa Freelance de MQL5.com

Hasta el mes de octubre de 2018, los participantes del servicio Freelance oficial para las plataformas MetaTrader han ejecutado más de 50 000 encargos. Se trata de la bolsa más grande del mundo de trabajo a distancia para programadores de MQL: más de mil desarrolladores, decenas de nuevos encargos diarios por parte de tráders y localización en 7 idiomas.

Integración de un experto en MQL y bases de datos (SQL Server, .NET y C#) Integración de un experto en MQL y bases de datos (SQL Server, .NET y C#)

El artículo describe cómo añadir a los expertos en MQL5 la posibilidad de trabajar con el servidor de bases de datos Microsoft SQL Server. Usaremos la importación de funciones de DLL. Para crear la DLL, se utilizará la plataforma Microsoft .NET y el lenguaje C#. Los métodos utilizados en el artículo, aunque con algunos cambios poco significativos, funcionan también para los expertos escritos en MQL4.

Desarrollo de indicadores bursátiles con control de volumen tomando como ejemplo el indicador delta Desarrollo de indicadores bursátiles con control de volumen tomando como ejemplo el indicador delta

En el artículo se analiza el algoritmo de construcción de indcadores sobre volúmenes reales usando las funciones CopyTicks() y CopyTicksRange(). Asimismo, se muestran las peculiaridades de la construcción de estos indicadores y se describe su funcionamiento en tiempo real y en el simulador de estrategias.