English Русский 中文 Deutsch 日本語 Português
Búsqueda automática de divergencia y convergencia

Búsqueda automática de divergencia y convergencia

MetaTrader 5Ejemplos | 26 octubre 2017, 07:22
4 624 0
Dmitry Fedoseev
Dmitry Fedoseev

Contenido

Introducción

El término «divergencia» proviene de la palabra latina «divergere» («detectar una discrepancia»). Habitualmente, bajo la divergencia se entiende un desajuste entre las indicaciones del indicador y la dirección del movimiento del precio. A menudo, junto con este término, se utiliza su antónimo «convergencia» que proviene de la palabra latina «convergo» («aproximar»). Existen los sistemas más amplios de la clasificación de la divergencia/convergencia que incluyen las definiciones como «divergencia oculta», «divergencia ampliada», divergencia de clases A, B, C, etc.

En este artículo, en primer lugar, aclararemos los términos principales: la divergencia y la convergencia. Luego, estudiaremos otros sistemas de su clasificación, realizaremos su análisis comparativo, revelaremos sus ventajas y desventajas. En conclusión, crearemos nuestro propio sistema de clasificación, más completo y sin desventajas evidentes, diseñaremos un indicador universal para buscar y visualizar la divergencia/convergencia en el gráfico. 

Divergencia y convergencia (definición del concepto)

Pues bien, la divergencia es un desajuste entre las indicaciones del indicador y la dirección del movimiento del precio. Si en esta definición llamamos el desajuste como la discrepancia, el significado de la definición no se cambiará. Pero tenemos otro término, la convergencia, que tiene el significado opuesto al primero. Según la lógica arriba expuesta, si la divergencia es un desajuste o discrepancia entre las indicaciones del indicador y el movimiento del precio, se puede llegar a la conclusión que la convergencia es la correspondencia o aproximación. Pero no es así, es que no se puede equiparar la correspondencia con la aproximación. 

Para que ambos términos —divergencia y convergencia— tengan el significado equivalente, necesitamos sus definiciones más exactas y estrictas. Vamos a fijarnos en el gráfico del precio y del indicador. Si el precio se mueve hacia arriba y el indicador hacia abajo, se trata de la discrepancia o la divergencia. Si el precio se mueve hacia abajo y el indicador hacia arriba, es la aproximación o la convergencia (fig. 1).


Fig. 1. Desajuste entre la dirección del movimiento del precio e indicador. A la izquierda, el precio se mueve hacia arriba, 
el indicador hacia abajo— es la discrepancia. A la derecha, el precio se mueve hacia abajo, el indicador hacia arriba— es la aproximación.

Todos están acostumbrados a que el gráfico del indicador se encuentra por debajo del gráfico del precio, por eso a primera vista esta definición parece admisible. Sin embargo, si el gráfico del indicador estuviera arriba, la situación se cambiaría de espejo: la divergencia se convertiría en la convergencia, y viceversa (Fig. 2).


Fig. 2. Desajuste entre la dirección del movimiento del precio e indicador. A la izquierda, el precio se mueve hacia arriba, 
el indicador hacia abajo— es la aproximación. A la derecha, el precio se mueve hacia abajo, el indicador hacia arriba— es la discrepancia.

Ahora, vamos a fijarnos en la fig. 1 y 2 desde el punto de vista de la dirección del trading. Supongamos que el precio se mueve hacia arriba, el indicador va hacia abajo, y hemos decidido vender. Siguiendo la analogía, la compra debe realizarse cuando el precio se mueve hacia abajo y el indicador va hacia arriba (fig. 3).


Fig. 3. A lor la izquierda: condiciones para la venta, el precio y el indicador se apartan. A la derecha: condiciones para la compra (condiciones idénticas para la venta), el precio y el indicador se aproximan.

Resulta que en el caso de la venta, el precio y el indicador se apartan, y en el caso de la compra, se aproximan. Es decir, en uno de los casos, se observa la divergencia, y en otro, la convergencia. Pero las condiciones de la venta y la compra son idénticas, siendo sólo diametralmente opuestos. Entonces, una de las condiciones se la podemos llamar bajista, y la otra, alcista. Por tanto, no es suficiente especificar que el gráfico del indicador tiene que ubicarse debajo del gráfico del precio en la definición de la divergencia y convergencia. 

Se puede añadir a la definición que todas las definiciones expuestas se refieren a la dirección de la venta, y para las compras todo es completamente al revés. Pero existe una variante de la definición más simple y más exacta, que se basa en la esencia del análisis técnico. Si añadimos la suposición sobre el siguiente movimiento del precio a la definición de la divergencia y convergencia, todo se pondrá en su sitio y la definición se hará simple y concisa.

La divergencia es una señal de reversión del precio que representa un desajuste entre las indicaciones del indicador y la dirección del movimiento del precio

Puesto que se trata de una señal de reversión, entonces, para la venta el precio debe moverse primero hacia arriba, y para la compra, hacia abajo. Y para que aparezca un desajuste entre el movimiento del precio y el indicador, éste debe moverse hacia abajo y hacia arriba, respectivamente. Se puede notar que la venta es la dirección patrón en esta definición, y el gráfico del indicador tiene que ubicarse debajo del gráfico del precio. En la imagen 3 se muestra la divergencia a base de esta definición.

Puesto que la convergencia es opuesta a la divergencia, entonces, todo lo contrario: ahora el precio tiene que apuntar hacia abajo y el indicador, hacia arriba, pero la predicción de la dirección del movimiento del precio no se cambia. Por tanto, la definición de la convergencia será la siguiente:

La convergencia es una señal de continuación de tendencia que representa un desajuste entre las indicaciones del indicador y la dirección del movimiento del precio

Puesto que se trata de una señal de continuación, entonces, para la venta el precio debe moverse hacia abajo, y para la compra, hacia arriba. Y para que el movimiento del precio no corresponda a las indicaciones del indicador, éste debe moverse hacia arriba y hacia abajo, respectivamente (Fig. 4).


Fig. 4. Señales convergencia

Claro que se puede discutir de que si la divergencia realmente representa una señal de reversión, y la convergencia, una señal de continuación. Pero eso ya es una aplicación práctica de las posibilidades del análisis técnico.

Para consolidar el análisis de la terminología realizado en este apartado, y como una «chuleta», en la imagen 5 se muestran a la vez las señales de divergencia y convergencia.


Fig. 5. Señales de divergencia y convergencia

 

Métodos para definir la dirección del movimiento del precio e indicador

Hasta ahora, las líneas del precio y del indicador eran rectan en nuestros gráficos. Pero es una abstracción que no tiene nada que ver con el movimiento real del precio. Por eso, hablaremos de los métodos con la ayuda de los cuales se puede determinar la dirección del precio e indicador, y encontrar la divergencia. Luego, consideraremos los sistemas de clasificación de la divergencia en la práctica.

Primero, tenemos que revelar los picos y los valles en el gráfico del precio o indicador, y luego, comparar sus valores.  La divergencia alcista (señales de compra) se busca por los valles: sin un valle es más alto que el anterior, entonces, el indicador apunta hacia arriba, y viceversa. La divergencia bajista (señales de venta) se busca por los picos.  

Se puede destacar tres modos de búsqueda de los extremos en el gráfico.

  1. Por barras.
  2. Por la superación del valor de umbral desde el último mínimo/máximo
  3. Valor máximo/mínimo al encontrarse por encima/por debajo de la línea central del indicador. 

Definición de los picos/valles por barras. Se utiliza el número de las barras del pico/valle. Por ejemplo, si el valor de este parámetro es igual a 2, el valor del indicador en la barra del pico debe ser superior al valor de dos barras a la izquierda y dos barras la derecha del pico. Por consiguiente, el valor para el valle debe ser inferior al valor de las barras vecinas (Fig. 6).


Fig. 6. Definición de los picos y valles por dos barras. A la izquierda se determinan los picos La barra marcada con flecha indica en la formación del
pico que se marca con tic. A la derecha se determinan los valles.

El número necesario de las barras a la izquierda y a la derecha del pico/valle puede ser diferente: por ejemplo, 5 a la izquierda y 2 a la derecha (Fig. 7).


Fig. 7. Pico por cinco barras a la izquierda y dos barras a la derecha. 

Definición del pico/valle por valor de umbral. Cuando el indicador va hacia arriba, se fija su valor máximo. En las barras donde el indicador no forma un nuevo extremo, el valor actual se compara con el máximo/mínimo registrado anteriormente. Si la diferencia supera el valor de umbral establecido por el parámetro externo, se considera que el indicador ha cambiado de su dirección, y la barra en la que ha sido alcanzado el valor máximo/mínimo se considera el pico/valle (Fig. 8).


Fig. 8. Definición de los picos y valles por el valor de umbral. En la esquina superior izquierda, se muestra el tamaño del umbral.
El indicador se movía hacia arriba hasta la barra 2. En la barra 2 fue registrado el valor máximo. En la barra 5 el valor bajó hasta el valor de umbral,
lo que significó el cambio de la dirección 
del indicador. En la barra 6, el indicador
superó de nuevo el valor de umbral y cambió de dirección, etc.

La variante por barras es más conveniente de todas, ya que no depende en absoluto del carácter del indicador. El tamaño del valor de umbral, al contrario, depende del tipo del indicador. Por ejemplo, para RSI con el rango de oscilaciones 0 - 100, el valor de umbral puede se alrededor de 5. Para Momentum, este valor será de 0,1 a 1, ya que el indicador oscila levemente alrededor del nivel de 100, además, el valor de estas oscilaciones depende del timeframe. Eso complica aún más el uso del valor de umbral. 

Valor máximo/mínimo al encontrarse por encima/por debajo de la línea central del indicador. Este método se usa menos que los demás. También depende del indicador utilizado, por que no todos los indicadores tienen el valor medio en el nivel 0 (por ejemplo, para el indicador RSI es el nivel 50). Pero la mayor desventaja es un retardo excesivo (Fig. 9). 


Fig. 9. Definición de los picos y valles por intersección de la línea central. Sobre el pico marcado con 1 sabremos sólo después de la intersección de la línea central
en la barra marcada con 2. Sobre el pico marcado con 3 sabremos después de
la intersección de la línea central en la barra marcada con 4.


Sistemas de clasificación de divergencias

En la Red hay muchos artículos dedicados a la divergencia. Contienen descripciones de diferentes enfoque que se diferencian tanto en la terminología, como en los principios de sistematización de la divergencia y convergencia. Se puede encontrar tanto la divergencia regular, como la divergencia clásica, oculta y ampliada. Algúnos la divide en las clases A, B y C. Nuestras tareas no suponen un análisis profundo de las fuentes originarias, simplemente vamos a analizar algunos de los tipos especificados.

Divergencia clásica. Este fenómeno ya ha sido descrito más arriba y mostrado en la imagen 5.

Divergencia oculta. La divergencia oculta se diferencia de la clásica en la dirección del movimiento del precio e indicador. Es decir, la divergencia oculta es lo mismo que está definido arriba como la convergencia.

Divergencia ampliada. Hasta ahora, hemos hablado sobre la dirección del movimiento del precio e indicador sólo hacia arriba o hacia abajo. Si añadimos el movimiento horizontal, habrá más variantes. A pesar de muchas opciones que teóricamente se puede obtener combinando tres direcciones del movimiento del precio y tres direcciones del movimiento del indicador, ha sido seleccionada sólo una variante de la divergencia ampliada:

  • Movimiento horizontal del precio, el indicador cae— divergencia ampliada bajista (señal de venta)
  • Movimiento horizontal del precio, el indicador sube— divergencia ampliada alcista (señal de compra)

Clases: A, B, C. La clase А es una divergencia clásica, y las clases B y C son las versiones de la divergencia ampliada.

Clase B: 

    • Movimiento horizontal del precio, el indicador cae— divergencia bajista (señal de venta).
    • Movimiento horizontal del precio, el indicador sube— divergencia alcista (señal de compra).

Clase C: 

    • El precio sube, los picos del indicador están en el mismo nivel— divergencia bajista (señal de venta).
    • El precio baja, los valles del indicador están en el mismo nivel— divergencia alcista (señal de compra).

Como podemos ver, las clases B y C son las variantes de la divergencia ampliada, siendo de notar que la variante B repite por completo la definición arriba expuesta.

La impresión general y la conclusión principal de todos los materiales disponibles sobre la divergencia consiste en la falta de una terminología clara y un abarcamiento incompleto de las variantes. Por eso, vamos a analizar diferentes variantes de la combinación de la dirección del movimiento del precio e indicador, y las sistematizaremos.

Sistematización completa de variantes del movimiento del precio e indicador

Primero, vamos a destacar dos variantes según el número posible de las direcciones del movimiento del precio e indicador.

  1. Dos direcciones del movimiento: arriba y abajo. 
  2. Tres direcciones del movimiento: arriba, abajo y horizontalmente.

En primer caso, son posibles sólo 4 variantes de las combinaciones. Vamos a considerarlas en el ejemplo de las señales de venta.

  1. Precio arriba, indicador arriba.
  2. Precio arriba, indicador abajo (divergencia).
  3. Precio abajo, indicador arriba (convergencia).
  4. Precio abajo, indicador abajo.

Como ya hemos aclarado los métodos de la determinación de las direcciones en el artículo anterior, visualizaremos estas variantes (Fig. 10). 


Fig. 10. Todas las variantes de combinaciones de diferentes direcciones del movimiento del precio e indicador con dos variantes del movimiento 

En el caso con tres variantes del movimiento del precio e indicador, ya son posibles nueve combinaciones.

  1. Precio arriba, indicador arriba.
  2. Precio arriba, indicador horizontalmente.
  3. Precio arriba, indicador abajo (divergencia).
  4. Precio horizontalmente, indicador arriba.
  5. Precio horizontalmente, indicador horizontalmente.
  6. Precio horizontalmente, indicador abajo.
  7. Precio abajo, indicador arriba (convergencia).
  8. Precio abajo, indicador horizontalmente.
  9. Precio abajo, indicador abajo.

Estas variantes se muestran en la imagen 11.


Fig. 11. Todas las variantes de combinaciones de diferentes direcciones del movimiento del precio e indicador con tres variantes del movimiento 

Si creamos un indicador que permite elegir cualquiera de las variantes consideradas, cada uno podrá elegir por sí mismo lo que considere una divergencia o convergencia correcta, una divergencia oculta o ampliada. Es decir, será un indicador universal que va a ser útil incluso para los que no están de acuerdo con la sistematización y definiciones expuestas en este artículo. 

Divergencia triple

Hasta ahora, la dirección del movimiento del precio e indicador se determinaba por dos puntos: arriba, abajo, horizontalmente. Se puede añadir el tercer punto, y habrá más posibles variantes del movimiento del precio e indicador. En total, habrá nueve variantes:

  1. Arriba, arriba.
  2. Arriba, horizontalmente.
  3. Arriba, abajo.
  4. Horizontalmente, arriba.
  5. Horizontalmente, horizontalmente.
  6. Horizontalmente, abajo.
  7. Abajo, arriba.
  8. Abajo, horizontalmente.
  9. Abajo, abajo.

En este caso, será más correcto hablar de la forma del movimiento, en vez de la dirección. Las variantes de diferentes formas del movimiento determinadas por tres picos se muestran en la imagen 12.


Fig. 12. Diferentes formas del movimiento formadas por tres picos 

Las formas del movimiento correspondientes que se determinan por los valles se muestran en la imagen 13.

 
Fig. 13 Diferentes formas del movimiento formadas por tres valles 

Combinando 9 variantes de la forma del movimiento del precio y 9 variantes de la forma del movimiento del indicador, se puede obtener 81 variantes de la divergencia triple.

De esta manera, para determinar la forma del movimiento, se puede usar cualquier número de puntos. Si añadimos el cuarto punto, obtendremos 81 variantes de la forma del movimiento del precio o indicador, y por consiguiente, 6561 (81*81) variantes de su combinación. Claro que cuanto más posibles variantes haya, con menos frecuencia van a encontrarse. Puede que el uso del cuarto punto no tenga sentido para determinar la forma del movimiento, pero el indicador en este artículo será creado sin limitaciones del número de puntos que se usan para ese propósito.

Indicador universal para determinar la divergencia

Una vez aclarada la teoría, vamos a empezar con la creación del indicador.

Selección del oscilador. Para no limitarnos con un oscilador para la determinación de la divergencia, usamos el oscilador universal de este artículo. Se adjunta iUniOsc (oscilador universal) y iUniOscGUI (el mismo pero con la interfaz gráfica). Vamos a usar la variante base (iUniOsc.).

Creación de un indicador nuevo. Creamos el nuevo indicador iDivergence en MetaEditor. Durante su creación, seleccionamos la función OnCalculate. La función OnTimer() no será necesaria. Marcamos «Indicador en ventana separada». Creamos tres búferes: línea de visuaización del oscilador y dos búferes para el dibujado de las flechas cuando surge la divergencia. Cuando el nuevo archivo se abra en el editor, cambiamos los nombres de los búferes: 1 — buf_osc, 2 — buf_buy, 3 — buf_sell.  Los nombres hay que cambiarlos donde se realiza la declaración de los arrays y en la función OnInit(). Además, se puede corregir las propiedades de los búferes: indicator_label1, indicator_label2, indicator_label3. Los valores de estas propiedades se visualizan en la descripción emergente al situar el cursor sobre la línea o el icono del indicador, y se muestran en la ventana de datos. Los llamaremos "osc", "buy", "sell".

Aplicación del oscilador universal. Insertamos todos los parámetros externos del indicador iUniOsc en el nuevo indicador. No necesitamos los parámetros ColorLine1, ColorLine2, ColorHisto en la ventana de propiedades, vamos a ocultarlos. El parámetro Type tiene el tipo personalizado OscUni_RSI descrito en el archivo UniOsc/UniOscDefines.mqh. Vamos a incluir este archivo. Por defecto, el valor del parámetro Type está establecido en OscUni_ATR —selección del indicador ATR. Pero el indicador ATR no depende de la dirección del movimiento del precio y por eso no conviene para la determinación de la divergencia. Por eso, estableceremos por defecto OscUni_RSI —indicador RSI:

#include <UniOsc/UniOscDefines.mqh>

input EOscUniType          Type              =  OscUni_RSI;
input int                  Period1           =  14;
input int                  Period2           =  14;
input int                  Period3           =  14;
input ENUM_MA_METHOD       MaMethod          =  MODE_EMA;
input ENUM_APPLIED_PRICE   Price             =  PRICE_CLOSE;   
input ENUM_APPLIED_VOLUME  Volume            =  VOLUME_TICK;   
input ENUM_STO_PRICE       StPrice           =  STO_LOWHIGH;   
      color                ColorLine1        =  clrLightSeaGreen;
      color                ColorLine2        =  clrRed;
      color                ColorHisto        =  clrGray;

Declaramos la variable para el handle del oscilador universal un poco por debajo de las variables externas:

int h;

Al principio de la función OnInit(), cargamos el oscilador universal:

h=iCustom(Symbol(),Period(),"iUniOsc", Type,
                                       Period1,
                                       Period2,
                                       Period3,
                                       MaMethod,
                                       Price,
                                       Volume,
                                       StPrice,
                                       ColorLine1,
                                       ColorLine2,
                                       ColorHisto);
if(h==INVALID_HANDLE){
   Alert("Can't load indicator");
   return(INIT_FAILED);
}

En la función OnCalculate(), copiamos los datos del oscilador universal al béfer buf_osc:

int cnt;   

if(prev_calculated==0){
   cnt=rates_total;
}
else{ 
   cnt=rates_total-prev_calculated+1; 
}

if(CopyBuffer(h,0,0,cnt,buf_osc)<=0){
   return(0);
}  

En esta fase, se puede comprobar la exactitud de las acciones ejecutadas, adjuntando el indicador iDivergence al gráfico. Si todo ha sido hecho de forma correcta, se puede observar la línea del oscilador en la ventana. 

Determinación de los extremos del oscilador. Antes hemos considerado tres variantes para determinar los extremos. Escribimos todas estas variantes en nuestro indicador y prevemos la posibilidad de seleccionar cualquiera de ellas (variable externa con lista desplegable). En la carpeta Include, creamos la carpeta UniDiver, donde vamos a crear todos los archivos adicionales con el código. Crearemos el archivo incluido UniDiver/UniDiverDefines.mqh donde escribiremos la enumeración EExtrType:

enum EExtrType{
   ExtrBars,
   ExtrThreshold,
   ExtrMiddle
};

Variantes de la enumeración:

  • ExtrBars — por barras;
  • ExtrThreshold — por la superación del valor de umbral desde el último mínimo/máximo;
  • ExtrMiddle — valor máximo o mínimo cuando la posición del indicador está por encima o por debajo de su centro. 

Creamos el parámetro externo ExtremumType en el indicador, y lo insertamos encima de todos los demás parámetros externos. Al determinar los extremos por barras, necesitaremos dos parámetros: el número de barras a la izquierda y a la derecha del extremo. Y para la variante por umbral, hará falta el parámetro para determinar el valor de umbral:

input EExtrType            ExtremumType      =  ExtrBars; // Tipo del extremo
input int                  LeftBars          =  2;        // Barras a la izquierda para la variante ExtrBars
input int                  RightBars         =  -1;       // Barras a la derecha para la variante ExtrBars
input double               MinMaxThreshold   =  5;        // Valor de umbral para la variante ExtrThreshold 

Hagamos que se pueda usar no sólo dos parámetros a la vez, sino también uno de ellos: RightBars o LeftBars. RightBars tiene establecido el valor predefinido -1. Eso significa que no se usa y le será asignado el valor del segundo parámetro.

Clases para determinar los extremos. No será necesario cambiar el método de determinación del extremo en el proceso del trabajo, por eso será más racional usar la POO en vez del operador if o switch. Vamos a crear la clase base y tres clases derivadas para tres variantes de determinación del extremos. Una de estas clases derivadas va a seleccionarse durante el inicio del indicador. Prácticamente, en estas clases, van a determinarse no sólo los extremos, sino también va a realizarse todo el trabajo de búsqueda de la divergencia. Se diferencian sólo los modos para determinar los extremos, mientras que la propia determinación de la divergencia es absolutamente idéntica en todas las ocasiones. Por eso, la función de la determinación estará en la clase base y va a llamarse desde las clases derivadas. Pero primero, habrá que asegurar el acceso fácil a todos los extremos del indicador (como ha sido hecho en el artículo «Ondas de Wolfe» con los picos del ZigZag).

Para almacenar los datos sobre un pico, va a utilizarse la estructura SExtremum, la descripción de la estructura se encuentra en el archivo UniDiverDefines:

struct SExtremum{
   int SignalBar;
   int ExtremumBar;
   datetime ExtremumTime;
   double IndicatorValue;
   double PriceValue;
};

Finalidad de los campos de la estructura:

  • SignalBar — la barra en la cual se ha sabido sobre la formación del extremos
  • ExtremumBar — barra con el extremo
  • ExtremumTime — hora de la barra con el extremo
  • IndicatorValue — valor del indicador en el extremo
  • PriceValue — valor del precio en la barra con el extremo del indicador

Para almacenar los datos sobre todos los picos y valles, van a usarse dos arrays de estas estructuras. Serán miembros de la clase base.

Las clases de la determinación de los extremos se encuentran en el archivo UniDiver/CUniDiverExtremums.mqh, nombre de la clase base es CDiverBase. Vamos a estudiar las estructura de la clase, pero por ahora sólo con los métodos principales. Los demás serán añadidos más tarde, a medida que sea necesario.

class CDiverBase{
   protected: 
      
      SExtremum m_upper[];
      SExtremum m_lower[];

      void AddExtremum( SExtremum & a[],
                        int & cnt,
                        double iv,
                        double pv,
                        int mb,
                        int sb,
                        datetime et);
   
      void CheckDiver(  int i,
                        int ucnt,
                        int lcnt,
                        const datetime & time[],
                        const double &high[],
                        const double &low[],                     
                        double & buy[],
                        double & sell[],
                        double & osc[]
      );

   public:
      virtual void Calculate( const int rates_total,
                              const int prev_calculated,
                              const datetime &time[],
                              const double &high[],
                              const double &low[],
                              double & osc[],
                              double & buy[],     
                              double & sell[]
      );
}; 

El método virtual Calculate() asegura la selección de la variante de la determinación de los extremos. Los métodos AddExtremum() y CheckDiver() se ubican en la sección protected, van a llamarse desde el método Calculate() de las clases derivadas. El métod AddExtremum() recopila los datos sobre los picos y valles en los arrays m_upper[] y m_lower[]. El método CheckDiver() comprueba si está cumplida la condición de la divergencia, y coloca las flechas de indicador. Luego, analizaremos todos estos métodos más detalladamente, pero por ahora conoceremos las clases derivadas para otros modos de determinación de los extremos.

Determinación de los extremos por barras. La clase para determinar los extremos por barras es la siguiente:

class CDiverBars:public CDiverBase{
   private:   
   
      SPseudoBuffers1 Cur;
      SPseudoBuffers1 Pre;  
           
      int m_left,m_right,m_start,m_period;   
   
   public:
         
      void CDiverBars(int Left,int Right);
   
      void Calculate( const int rates_total,
                      const int prev_calculated,
                      const datetime &time[],
                      const double &high[],
                      const double &low[],
                      double & osc[],
                      double & buy[],
                      double & sell[]
      );
}; 

Los parámetros para determinar los extremos (variables externas LeftBars, RightBars) se pasan al constructor de la clase, se realiza la comprobación de la exactitud de sus valores, la corrección si hace falta, y se calculan los parámetros adicionales:

void CDiverBars(int Left,int Right){
   m_left=Left;
   m_right=Right;   
   if(m_left<1)m_left=m_right;   // el parámetro Left no está establecido
   if(m_right<1)m_right=m_left;  // el parámetro Right no está establecido
   if(m_left<1 && m_right<1){    // ambos parámetros no están establecidos
      m_left=2;
      m_right=2;
   }
   m_start=m_left+m_right;       // desplazamiento del punto del inicio del intervalo
   m_period=m_start+1;           // número de barras del intervalo
}

Primero, se comprueban los valores de los parámetros. Si alguno de ellos no es positivo (es decir, no está establecido), se le asigna el valor del segundo parámetro. Si ninguno de los parámetros está establecido, se les asigna el valor predefinido (cifras 2). Luego, se calcula el tamaño de la margen para la barra inicial para la búsqueda del extremo (m_start) y el número total de barras del extremo (m_period).

El método Calculate() es idéntico a la función estándar OnCalculate(), pero a ella no se le pasan todos los arrays de precios, sino los necesarios: time[], high[], low[] y los búferes de indicadores osc[] (datos del oscilador), buy[] y sell[] (flechas). Como siempre, en la función OnCalculte() se calcula el diapasón de las barras calculadas. Luego, se definen los extremos del indicador (funciones ArrayMaximum() y ArrayMinimum()) en el ciclo de indicador estándar. Cuando se detecta un extremo, se invoca el método AddExtremum() para añadir los datos al array m_upper[] o m_lower[]. Al final, se invoca el método CheckDiver() que analiza los datos desde los arrays con los extremos. Si se encuentra una divergencia, se colocan las flechas.

void Calculate( const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &high[],
                const double &low[],
                double & osc[],
                double & buy[],
                double & sell[]
){

   int start; // variable para el índice de la barra inicial
   
   if(prev_calculated==0){ // cálculo completo del indicador
      start=m_period; // determinación de la barra inicial del cálculo
      m_LastTime=0;   // reseteo de la variable para determinar una barra nueva 
      Cur.Reset();    // reseteo de las estructuras auxiliares
      Pre.Reset();    // reseteo de las estructuras auxiliares
   }
   else{ // durante el cálculo sólo de las barras nuevas
      start=prev_calculated-1; // cálculo del índice de la barra a partir de la cual se debe continuar los cálculos
   }

   for(int i=start;i<rates_total;i++){ // cilco principal de indicador
      
      if(time[i]>m_LastTime){ // nueva barra
         m_LastTime=time[i];
         Pre=Cur;
      }
      else{ // cálculo repetido de la misma barra
         Cur=Pre;
      }
      
      // cálculo de los parámetros de la búsqueda del máximo/mínimo 
      int sb=i-m_start; // índice de la barra a partir de la cual se empieza el intervalo
      int mb=i-m_right; // índice de la barra con el pico/valle  
      
      if(ArrayMaximum(osc,sb,m_period)==mb){ // hay un pico
         // adición del pico al array
         this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
      }
      if(ArrayMinimum(osc,sb,m_period)==mb){ // hay un valle
         // adición del valle al array
         this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
      }
      
      // comprobación de la devergencia
      this.CheckDiver(i,Cur.UpperCnt,Cur.LowerCnt,time,high,low,buy,sell,osc);
   } 
}

Vamos a examinar este código con más detalles. Al principio del ciclo de indicador:

if(time[i]>m_LastTime){ // nueva barra
   m_LastTime=time[i];
   Pre=Cur;
}
else{ // cálculo repetido de la misma barra
   Cur=Pre;
}

la variable m_LastTime está declarada en la clase base. Si la hora de la barra time[i] supera el valor de esta variable, entonces, la barra se calcula por primera vez. En este caso, a la variable m_LastTime se le asigna la hora de la barra, y a la variable Pre, el valor de la variable Cur. Cuando la misma barra se calcula de nuevo, al revés, a la variable Cur se le asigna el valor de la variable Pre. El procedimiento con las variables Cur y Pre fue analizado detalladamente en este artículo. Las variables Pre y Cur tienen el tipo SPseudoBuffers1, descrito en el archivo UniDiverDefines:

struct SPseudoBuffers1{
   int UpperCnt;
   int LowerCnt;
   void Reset(){
      UpperCnt=0;
      LowerCnt=0;  
   }   
};

La estructura incluye dos campos:

  • UpperCount — número de elementos utilizados del array m_upper[];
  • LowerCount — número de elementos utilizados del array m_lower[];

El método Reset() ha sido creado para resetear rápidamente todos los campos de la estructura.

Tras la manipulación con las variables Cur y Pre, se calculan los índices de la barras para la búsqueda de los extremos:

// cálculo de los parámetros de la búsqueda del máximo/mínimo
int sb=i-m_start; // índice de la barra a partir de la cual se empieza el intervalo
int mb=i-m_right; // índice de la barra con el pico/valle  

A la variable sd se le asigna el índice de la barra a partir de la cual se realiza la búsqueda del máximo o el mínimo. A la variable mb se le asigna el índice de la barra en la que debe encontrarse el máximo o el mínimo.

Determinamos el pico o el valle usando las funciones ArrayMaximum() y ArrayMinimum():

if(ArrayMaximum(osc,sb,m_period)==mb){ // hay un pico
   // adición del pico al array
   this.AddExtremum(m_upper,Cur.UpperCnt,osc[mb],high[mb],mb,i,time[mb]);
}
if(ArrayMinimum(osc,sb,m_period)==mb){ // hay un valle
   // adición del valle al array
   this.AddExtremum(m_lower,Cur.LowerCnt,osc[mb],low[mb],mb,i,time[mb]);
}

Si la función ArrayMaximum() o ArrayMinimum() devuelve el valor mb, entonces, a la izquierda o a la derecha del pico/valle se encuentra el número establecido de las barras. En su lugar, eso significa que se ha formado el pico/valle buscado. En este caso, se invoca el método AddExtremum() y los datos se añaden al array m_upper[] o m_lower[].

Vamos a analizar el método simple AddExtremum():

void AddExtremum( SExtremum & a[], // el array al que se le añaden los datos
                  int & cnt,       // número de elementos ocupados en el array
                  double iv,       // valor del indicador
                  double pv,       // valor del precio 
                  int mb,          // índice de la barra con el extremo
                  int sb,          // índice de la barra en la que se ha sabido sobre el extremo 
                  datetime et      // hora de la barra con el extremo
){
   if(cnt>=ArraySize(a)){ //el array está lleno
      // aumento del tamaño del arrayo
      ArrayResize(a,ArraySize(a)+1024);
   }
   // adición de nuevos datos
   a[cnt].IndicatorValue=iv; // valor del indicador
   a[cnt].PriceValue=pv;     // valor del precio
   a[cnt].ExtremumBar=mb;    // índice de la barra con el extremo
   a[cnt].SignalBar=sb;      // índice de la barra en la que se ha sabido sobre el extremo 
   a[cnt].ExtremumTime=et;   // hora de la barra con el extremo
   cnt++;                    // aumento del contador de elementos ocupados
}

Usando los parámetros, al método se le pasa el array a[] al que hace falta añadir datos nuevos. Puede ser el array m_upper[] o m_lower[]. A través de la variable cnt, se pasa el número de los elementos activos del array a[]. Puede ser la variable Cur.UpperCnt o Cur.LowerCnt. El array a[] y la variable cnt se pasan por referencias porque se alteran en el método. 

La variable iv es el valor del indicador en la barra del extremo, pv es el valor del precio en la barra del extremo, mb es el índice de la barra con los extremos, sb es la barra de señal (en la que se ha sabido sobre la aparición del extremo), et es la hora de la barra con el extremo.

Al principio del método AddExtremum(), se comprueba el tamaño del array. Si está completamente lleno, su tamaño se aumenta en 1024 elementos, luego se realiza la adición de datos y el aumento de la variable cnt.

El método CheckDiver() lo vamos a analizar más tarde.

Determinación del extremo por el valor de umbral.

La clase de la determinación por el valor umbral se distingue de la clase de la determinación por barras, en primer lugar, por el tipo de las variables Cur y Pre: es el tipo SPseudoBuffers2 descrito en el archivo  UniDiverDefines.mqh:

struct SPseudoBuffers2{
   int UpperCnt;
   int LowerCnt;
   double MinMaxVal;
   int MinMaxBar;   
   int Trend;
   void Reset(){
      UpperCnt=0;
      LowerCnt=0;  
      MinMaxVal=0;
      MinMaxBar=0;
      Trend=1;
   }   
};

La estructura SPseudoBuffers2 tiene los mismos campos que la estructura SPseudoBuffers1, y algunos adicionales más:

  • MinMaxVal — variable para el valor máximo o mínimo del indicador
  • MinMaxVal — variable para el índice de la barra en la que ha sido encontrado el valor máximo o mínimo del indicador
  • Trend — variable para la dirección del movimiento del indicador. Si el valor es 1, el indicador se mueve hacia arriba y se monitorea su valor máximo. Si es -1 se monitorea su valor mínimo.

El parámetro externo con el valor de umbral (variable MinMaxThreshold) se pasa al constructor de la clase. Su valor se guarda en la variable m_threshold declarada en la sección private. 

El método Calculate() de esta clase se diferencia sólo por el modo de la determinación del extremo:

switch(Cur.Trend){ // dirección actual del movimiento del indicador
   case 1: // arriba
      if(osc[i]>Cur.MinMaxVal){ // nuevo máximo
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<Cur.MinMaxVal-m_threshold){ // valor umbral superado
         // adición del pico al array
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-1;          // cambio de la dirección monitoreada
         Cur.MinMaxVal=osc[i];  // valor inicial del máximo
         Cur.MinMaxBar=i;       // barra con el valor inicial del mínimo        
      }
   break;
   case -1: // abajo
      if(osc[i]<Cur.MinMaxVal){ // nuevo mínimo
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>Cur.MinMaxVal+m_threshold){ // valor umbral superado
         // adición del valle al array
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=1;           // cambio de la dirección monitoreada
         Cur.MinMaxVal=osc[i];  // valor inicial del máximo
         Cur.MinMaxBar=i;       // barra con el valor inicial del máximo 
      }         
   break;
}

Si el valor de la variable es Cur.Trend 1, se compara el valor del oscilador con el valor de la variable Cur.MinMaxValue. Si el nuevo valor del oscilador supera el valor de la variable, el valor de la variable se actualiza. En este caso, a la variable Cur.MinMaxBar se le asigna el índice de la barra en la que ha sido detectado el máximo nuevo. En seguida se comprueba si el valor del oscilador ha bajado del último máximo conocido en el valor m_threshold. Si es así, entonces, el oscilador ha cambiado su dirección. Se invoca el método AddExtremum(), los datos sobre el nuevo extremos se guardan en el array, el valor de la variable Cur.Trend se cambia por el valor opuesto, y en las variables Cur.MinMaxVal y Cur.MinMaxBar se registran los parámetros iniciales del nuevo mínimo. Puesto que el valor de la variable Cur.Trend ha cambiado, a partir de este momento se ejecuta otra sección case (se monitorean los valores mínimos del oscilador y la superación del umbral hacia arriba).

Determinación de los extremos por la posición respecto al centro del oscilador. El tipo del oscilador utilizado se pasa al constructor de la clase, y dependiendo de él, se determina el valor del centro del oscilador:

void CDiverMiddle(EOscUniType type){
   if(type==OscUni_Momentum){
      m_level=100.0;
   }
   else if(type==OscUni_RSI || type==OscUni_Stochastic){
      m_level=50.0;
   }
   else if(type==OscUni_WPR){
      m_level=-50.0;
   }
   else{
      m_level=0.0;
   }
}

Para el Momentum es 100, para RSI Stochastic es 50, para WPR es -50, para los demás osciladores, 0.

El modo de determinación del extremo, en muchos aspectos, parece al modo con el valor de umbral:

switch(Cur.Trend){
   case 1:
      if(osc[i]>Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]<m_level){
         this.AddExtremum(m_upper,Cur.UpperCnt,Cur.MinMaxVal,high[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=-1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;               
      }
   break;
   case -1:
      if(osc[i]<Cur.MinMaxVal){
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }
      if(osc[i]>m_level){
         this.AddExtremum(m_lower,Cur.LowerCnt,Cur.MinMaxVal,low[Cur.MinMaxBar],Cur.MinMaxBar,i,time[Cur.MinMaxBar]);
         Cur.Trend=1;
         Cur.MinMaxVal=osc[i];
         Cur.MinMaxBar=i;
      }         
   break;
}

La única diferencia es que el cambio de la dirección se realiza a través de la comparación con el nivel central del oscilador: osc[i]<m_level o osc[i]>m_level.

Modo para establecer el tipo de divergencia. Vamos a añadir una variable para seleccionar el tipo de la divergencia reconocida a los parámetros externos.

input int                  Number            =  3;

Por defecto, el valor de la variable es 3, lo que significa la divergencia clásica según la imagen 11. En total, en la imagen 11 hay 9 versiones de las combinaciones del movimiento del precio e indicador. Vamos a añadir una versión más, «no se comprueba». Entonces, tendremos 10 versiones. Por tanto, usando un número decimal habitual, se puede describir cualquier combinación de diferentes movimientos de cualquier cantidad (inclusive la divergencia triple). Resulta que un número dígito corresponde a la divergencia simple (por dos picos/valles), un número de dos cifras, a la divergencia triple, etc. Por ejemplo, el número 13 va a corresponder a la combinación mostrada en la imagen 14.


Fig. 14. La combinación del movimiento del indicador y
del precio con Number=13 para la venta
 

Clases para comprobar las condiciones de divergencia. Para comprobar las condiciones de la divergencia, será creada la clase base y las clases derivadas. En el inicio del indicador, va a analizarse el valor del parámetro Number. En primer lugar, es su longitud, en función de la cual va a cambiarse el tamaño del array de los punteros a las clases. Luego, de acuerdo con el tipo de la condición, será creado el objeto correspondiente para cada elemento del array.

Las clases para verificar la condición se ubican en el archivo UniDiver/CUniDiverConditions.mqh. La clase base tiene el nombre CDiverConditionsBase, vamos a analizarla:

class CDiverConditionsBase{
   protected:
      double m_pt;
      double m_it;
   public:
   void SetParameters(double pt,double it){
      m_pt=pt;
      m_it=it;
   }
   virtual bool CheckBuy(double i1,double p1,double i2,double p2){
      return(false);
   }
   virtual bool CheckSell(double i1,double p1,double i2,double p2){
      return(false);
   }
};

La clase tiene dos métodos virtuales para comparar dos picos cercanos, y a ellos se les pasan los parámetros de estos picos: 

  • i1 — valor del indicador en el punto 1
  • p1 — valor del precio en el punto 1
  • i2 — valor del indicador en el punto 2
  • p2 — valor del precio en el punto 2

Los puntos se cuentan de derecha a izquierda, partiendo de 1.

El método SetParameters() se usa para establecer los parámetros adicionales de comparación: pt es la diferencia permitida de los valores del precio con la que se considera que los puntos del precio se encuentran en el mismo nivel. it es el parámetro similar para comparar los picos del indicador. Los valores de estos parámetros se establecen a través de la ventana de propiedades:

input double               IndLevel          =  0;
input int                  PriceLevel        =  0;

Abajo se muestra el código de una de las clases derivadas:

class CDiverConditions1:public CDiverConditionsBase{
   private:
   public:
   bool CheckBuy(double i1,double p1,double i2,double p2){
      return((p1>p2+m_pt) && (i1>i2+m_it));
   }
   bool CheckSell(double i1,double p1,double i2,double p2){
      return((p1<p2-m_pt) && (i1<i2-m_it));
   }
};

En el método CheckBuy() se comprueba si el precio del punto 1 supera el precio del punto. Lo mismo pasa con el indicador: el valor en el punto 1 tiene que ser mayor que el valor en el punto 2. El método CheckSell() es simétrico de espejo al método CheckBuy(). Todas las demás clases son parecidas y se diferencian sólo en las expresiones lógicas, salvo CDiverConditions0. En esta clase, los métodos CheckSell() y CheckBuy() devuelven true en seguida. Se utiliza cuando la comprobación de las condiciones está desactivada (se permite cualquier variante).

Preparación para comprobar las condiciones de divergencia. El array y la variable para su tamaño han sido declarados en la sección protected de la clase CDiverBase:

CDiverConditionsBase * m_conditions[];
int m_ccnt;  

El cambio del tamaño del array m_conditions y la creación de los objetos para comprobar las condiciones de la divergencia se realizan en el método SetConditions() :

void SetConditions(int num,      // número de la divergencia
                   double pt,    // parámetro PriceLevel
                   double it){   // parámetro IndLevel
   if(num<1)num=1; // el número de la divergencia no puede ser menos 1
   ArrayResize(m_conditions,10); // número máximo posible de las condiciones
   m_ccnt=0; // contador de la cantidad real de las condiciones
   while(num>0){
      int cn=num%10; // variante de la divergencia entre el siguiente par de extremos
      m_conditions[m_ccnt]=CreateConditions(cn); // creación del objeto
      m_conditions[m_ccnt].SetParameters(pt,it); // establecimiento de los parámetros de la comprobación de la condición
      num=num/10; // paso a la siguiente condición
      m_ccnt++; // cálculo de la cantidad de las condiciones
   }
   // corrección del tamaño del array de acuerdo con la cantidad real de condiciones 
   ArrayResize(m_conditions,m_ccnt);    
}

Los siguientes parámetros se pasan al método:

  • num — parámetro externo Number;
  • pt — parámetro externo PriceLevel;
  • it — parámetro externo IndLevel.

Primero, se comprueba el parámetro num:

if(num<1)num=1; // el número de la divergencia no puede ser menos 1

Luego el array m_conditions se aumenta hasta el tamaño máximo posible (10 es la longitud del valor máximo de la variable int). Luego, en el ciclo while, dependiendo del valor de cada dígito del número num, se crea el objeto de la comprobación de la condición usando el método CreateConditions(), y se establecen los parámetros usando el método SetParameters(). Después del ciclo, el tamaño del array se altera se acuerdo con el número real de las condiciones utilizadas.

Vamos a analizar el método CreateConditions():

CDiverConditionsBase * CreateConditions(int i){
   switch(i){
      case 0:
         return(new CDiverConditions0());      
      break;
      case 1:
         return(new CDiverConditions1());      
      break;
      case 2:
         return(new CDiverConditions2());      
      break;
      case 3:
         return(new CDiverConditions3());      
      break;
      case 4:
         return(new CDiverConditions4());      
      break;
      case 5:
         return(new CDiverConditions5());      
      break;      
      case 6:
         return(new CDiverConditions6());      
      break;
      case 7:
         return(new CDiverConditions7());      
      break;
      case 8:
         return(new CDiverConditions8());      
      break;
      case 9:
         return(new CDiverConditions9());
      break;
   }
   return(new CDiverConditions0()); 
}

El método es simple: dependiendo del valor i, se crea el objeto correspondiente y se devuelve la referencia a él.

Determinación de la divergencia. Ahora posemos analizar el método CheckDivergence() de la clase CDiverBase. Primero, vamos a mostrar el código completo del método, luego lo analizaremos línea por línea:

void CheckDiver(  int i,                    // índice de la barra calculada
                  int ucnt,                 // número de los picos en el array m_upper
                  int lcnt,                 // número de los valles en el array m_lower
                  const datetime & time[],  // array con la hora de las barras 
                  const double &high[],     // array con los precio high de las barras
                  const double &low[],      // array con los precios low de las barras               
                  double & buy[],           // búfer de indicadores con flechas arriba 
                  double & sell[],          // búfer de indicadores con flechas abajo
                  double & osc[]            // búfer de indicadores con valores del oscilador
){

   // limpiar los búferes con flechas
   buy[i]=EMPTY_VALUE;
   sell[i]=EMPTY_VALUE;
   
   // eliminación de objetos gráficos
   this.DelObjects(time[i]);
   
   // para los picos (señales de venta)
   if(ucnt>m_ccnt){ // hay una cantidad suficiente de los picos
      if(m_upper[ucnt-1].SignalBar==i){ // en la barra calculada se detecta un pico

         bool check=true; // supongamos que se ha formado una diveregencia
         
         for(int j=0;j<m_ccnt;j++){ // por todos los pares de los picos
            // comprobación de la ejecución de la condición en el par de los picos
            bool result=m_conditions[j].CheckSell( m_upper[ucnt-1-j].IndicatorValue,
                                                   m_upper[ucnt-1-j].PriceValue,
                                                   m_upper[ucnt-1-j-1].IndicatorValue,
                                                   m_upper[ucnt-1-j-1].PriceValue
                                                );
            if(!result){ // la condición no se cumple 
               check=false; // la divergencia no se ha formado
               break; 
            } 
                                
         }
         if(check){ // la divergencia se ha formado
            // colocación de la flecha del búfer de indicadores
            sell[i]=osc[i];
            // dibujado de las líneas auxiliares y/o la flecha en el gráfico de precios
            this.DrawSellObjects(time[i],high[i],ucnt);
         }
      }
   }
   
   // para los valles (señales de compra)
   if(lcnt>m_ccnt){
      if(m_lower[lcnt-1].SignalBar==i){
         bool check=true;
         for(int j=0;j<m_ccnt;j++){
            bool result=m_conditions[j].CheckBuy(  m_lower[lcnt-1-j].IndicatorValue,
                                                   m_lower[lcnt-1-j].PriceValue,
                                                   m_lower[lcnt-2-j].IndicatorValue,
                                                   m_lower[lcnt-2-j].PriceValue
                                                );
            if(!result){
               check=false;
               break;
            }                                          
         }
         if(check){
            buy[i]=osc[i];
            this.DrawBuyObjects(time[i],low[i],lcnt);
         }
      }
   }    
}

Los siguientes parámetros se pasan al método:

  • i — índice de la barra actual calculada;
  • ucnt — número de elementos utilizados del array m_upper[];
  • lcnt — número de elementos utilizados del array m_lower[];
  • time[] — array con la hora de las barras;
  • high[] — array con los precios high de las barras;
  • low[] — array con los precios low de las barras;                  
  • buy[] — búfer de indicador para las flechas de compra;
  • sell[] — búfer de indicador para las flechas de venta;
  • osc[] — búfer de indicador con valores del oscilador.

En primer lugar, se vacían los búferes con flechas:

// vaciar los búferes con flechas
buy[i]=EMPTY_VALUE;
sell[i]=EMPTY_VALUE;

Se eliminan los objetos gráficos correspondientes a la barra calculada:

// eliminación de objetos gráficos
this.DelObjects(time[i]);

Aparte de las flechas, el indicador iDivergence va a dibujar las flechas en el gráfico de precios como objetos gráficos, así como las líneas que unen los extremos en el gráfico de precios con los picos/valles en el gráfico del oscilador. 

Luego, tenemos dos bloques idénticos del código para comprobar las condiciones para la venta y para la compra. Vamos a ver el primer bloque para la compra. El número de los picos disponibles del oscilador tiene que superar en 1 al número de las condiciones comprobadas. Por eso, se realiza la comprobación:

// para los picos (señales de venta)
if(ucnt>m_ccnt){ // hay una cantidad suficiente de los picos

}

Luego, se comprueba la presencia del pico en la barra calculada. Eso se determina a través de la correspondencia del índice de la barra calculada al índice desde el array con los datos de picos/valles:

if(m_upper[ucnt-1].SignalBar==i){ // en la barra calculada se detecta un pico

}

Necesitaremos una variable auxiliar para los resultados de la comprobación de las condiciones:

bool check=true; // supongamos que la divergencia se ha formado

Recorremos todas las condiciones en el ciclo for, pasándoles los datos sobre los picos:

for(int j=0;j<m_ccnt;j++){ // por todos los pares de los picos
   // comprobación de la ejecución de la condición en el par de los picos
   bool result=m_conditions[j].CheckSell( m_upper[ucnt-1-j].IndicatorValue,
                                          m_upper[ucnt-1-j].PriceValue,
                                          m_upper[ucnt-1-j-1].IndicatorValue,
                                          m_upper[ucnt-1-j-1].PriceValue
                                        );
   if(!result){ // la condición no se cumple 
      check=false; // la divergencia no se ha formado
      break; 
   } 
}

Si alguna de las condiciones no se cumple, salimos del ciclo. A la variable check se le asigna el valor false. Si todas las condiciones están cumplidas, check guarda el valor true, se establece la flecha y se crea el objeto gráfico:

if(check){ // la divergencia se ha formado
  // establecimiento de la flecha del búfer de indicadores
   sell[i]=osc[i];
  // dibujado de las líneas auxiliares y/o la flecha en el gráfico de precios
   this.DrawSellObjects(time[i],high[i],ucnt);
}

La visualización de objetos gráficos puede activarse/desactivarse en la ventana de propiedades del indicador, para eso se declaran las variables:

input bool                 ArrowsOnChart     =  true;
input bool                 DrawLines         =  true;
input color                ColBuy            =  clrAqua;
input color                ColSell           =  clrDeepPink;
  • ArrowsOnChart — activación de las flechas desde los objetos gráficos en el gráfico de precios
  • DrawLines — activación del dibujado de las líneas que unen los picos y los valles del precio e indicador
  • ColBuy и ColSell — colores de los objetos gráficos para las señales de compra y de venta..

Las variables correspondientes han sido declaradas en la sección protected de la clase CDiverBase:

bool m_arrows;  // corresponde a la variable ArrowsOnChart
bool m_lines;   // corresponde a la variable DrawLines
color m_cbuy;   // corresponde a la variable ColBuy
color m_csell;  // corresponde a la variable ColSell 

Los valores de estas variables se establecen en el método SetDrawParmeters():

void SetDrawParmeters(bool arrows,bool lines,color cbuy,color csell){
   m_arrows=arrows;
   m_lines=lines;
   m_cbuy=cbuy;
   m_csell=csell;
}

Vamos a analizar los métodos que trabajan con los objetos gráficos. Eliminación:

void DelObjects(datetime bartime){ // el dibujado de las líneas está activado
   if(m_lines){
      // formación del prefijo común
      string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_";
      for(int j=0;j<m_ccnt;j++){ // por el número de las condiciones de la divergencia
         ObjectDelete(0,pref+"bp_"+IntegerToString(j)); // línea en el gráfico del precio en caso de la señal de compra
         ObjectDelete(0,pref+"bi_"+IntegerToString(j)); // línea en el gráfico del indicador en caso de la señal de compra 
         ObjectDelete(0,pref+"sp_"+IntegerToString(j)); // línea en el gráfico del precio en caso de la señal de venta 
         ObjectDelete(0,pref+"si_"+IntegerToString(j)); // línea en el gráfico del indicador en caso de la señal de venta 
      }            
   }
   if(m_arrows){ // las flechas en el gráfico del precio están activadas
      // 
      ObjectDelete(0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_ba");
      // 
      ObjectDelete(0,MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_sa");
   }
}

Las líneas que unen los picos/valles, y las flechas se eliminan por separado. Los nombres de todos los objetos gráficos se empiezan con el nombre del indicador, luego se añade la hora de la barra en la que ha sido detectada la divergencia. La siguiente formación de los nombres se realiza en el ciclo que corresponde al número de condiciones m_ccnt. "bp" se añade para las señales de compra en el gráfico de precios, "bi" se añade para las señales de compra en el gráfico del indicador. De la misma manera se añaden "sp" y "ip" para las señales de venta. Al final del nombre se añade j. A los nombres de las flechas se les añade "_ba" (flecha de la señal de compra) o "_sa" (flecha de del señal de venta).

La creación de los objetos gráficos se realiza en los métodos DrawSellObjects() y DrawBuyObjects(). Vamos a analizar uno de ellos:

void DrawSellObjects(datetime bartime,double arprice,int ucnt){
   if(m_lines){ // el dibujado de líneas está activado
      
      // formación del prefijo común
      string pref=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_";
      
      for(int j=0;j<m_ccnt;j++){  // por todas las condiciones de la divergencia
                  
         // línea en el gráfico del precio
         fObjTrend(  pref+"sp_"+IntegerToString(j),
                     m_upper[ucnt-1-j].ExtremumTime,
                     m_upper[ucnt-1-j].PriceValue,
                     m_upper[ucnt-2-j].ExtremumTime,
                     m_upper[ucnt-2-j].PriceValue,
                     m_csell);
                     
         // línea en el gráfico del indicador
         fObjTrend(  pref+"si_"+IntegerToString(j),
                     m_upper[ucnt-1-j].ExtremumTime,
                     m_upper[ucnt-1-j].IndicatorValue,
                     m_upper[ucnt-2-j].ExtremumTime,
                     m_upper[ucnt-2-j].IndicatorValue,
                     m_csell,
                     ChartWindowFind(0,MQLInfoString(MQL_PROGRAM_NAME)));  
      }
   }
      
   if(m_arrows){ // las flechas en el gráfico del precio están activadas
      fObjArrow(MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString((long)bartime)+"_sa",
               bartime,
               arprice,
               234,
               m_csell,
               ANCHOR_LOWER); 
   }            
} 

Los nombres de los objetos se forman de la misma manera como durante la eliminación, luego, se crean los objetos gráficos a través de las funciones fObjTrend() y fObjArrow().  Se ubican en el archivo incluido UniDiver/UniDiverGObjects.mqh. Las funciones son bastante simples, por eso hay sentido analizarlas.

Terminación del indicador Nos queda aplicar las clases creadas en el indicador. Creamos el objeto correspondiente en la función OnInit(), dependiendo del tipo de la determinación de los extremos:

switch(ExtremumType){
   case ExtrBars:
      diver=new CDiverBars(LeftBars,RightBars);
   break;
   case ExtrThreshold:
      diver=new CDiverThreshold(MinMaxThreshold);
   break;      
   case ExtrMiddle:
      diver=new CDiverMiddle(Type);
   break;      
}

Uno de los parámetros —PriceLevel— se mide en puntos, por eso es conveniente realizar su corrección en función del número de los dígitos tras la coma y las cotizaciones. Para eso, vamos a declarar otra variable para poder desactivar esta corrección:

input bool                 Auto5Digits       =  true;

Luego, declaramos la variable auxiliar en la función OnInit() para el parámetro corregido y lo corregimos:

int pl=PriceLevel;   
if(Auto5Digits && (Digits()==5 || Digits()==3)){
   pl*=10;
}  

Establecemos los parámetros de la divergencia y el dibujado:

diver.SetConditions(Number,Point()*pl,IndLevel);
diver.SetDrawParmeters(ArrowsOnChart,DrawLines,ColBuy,ColSell);

Nos queda sólo unas líneas en la función OnCalculate(). La llamada al método principal Calculate():

diver.Calculate(  rates_total,
                  prev_calculated,
                  time,
                  high,
                  low,
                  buf_osc,
                  buf_buy,
                  buf_sell);

En caso de dibujar con objetos gráficos, hay que acelerar su dibujado:

if(ArrowsOnChart || DrawLines){
   ChartRedraw();
}

Al terminar el trabajo del indicador, hay que eliminar los objetos gráficos. Eso se hace en el destructor de la clase CDiverBase, ahí mismos se eliminan los objetos para comprobar las condiciones de la divergencia:

void OnStart()
   for(int i=0;i<ArraySize(m_conditions);i++){ // por todas las condiciones
      if(CheckPointer(m_conditions[i])==POINTER_DYNAMIC){
         delete(m_conditions[i]); // eliminación del objeto
      }      
   }  
   // eliminación de objetos gráficos
   ObjectsDeleteAll(0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw();          
}   

Pues, hemos terminado la etapa principal de la creación del indicador. En la imagen 15 se muestra el gráfico con el indicador adjunto (en la subventana), con la visualización de las flechas activada en el gráfico del precio y con el dibujado de las líneas entre los picos. 


Fig. 15 Indicador de la divergencia en el gráfico del precio con la visualización de las flechas en el gráfico del precio y las líneas entre los extremos

Nos queda añadir la función de las alertas, pero es muy simple y ya ha sido considerada en otros artículos. Al artículo se le adjunta el indicador completamente listo con la función de la alerta y con todos los archivos necesarios para el indicador.

Conclusión

A pesar de una altísima universalidad del indicador, se puede destacar sus desventajas. La principal desventaja es la dependencia del parámetro IndLevel del tipo del oscilador utilizado, y la dependencia del parámetro PriceLevel del timeframe. Para excluir esta dependencia, estos parámetros tienen establecido el valor 0 por defecto. Pero en este caso, es prácticamente irreal cumplir las condiciones con algunas combinaciones del movimiento del precio e indicador. Si la condición de la comprobación de la divergencia incluye la comprobación del movimiento horizontal, su cumplimiento es poco probable. Pero quedan las variantes de la divergencia 1, 3, 7 y 9. Eso puede suponer un problema sólo en el Probador de estrategias durante la optimización del Asesor Experto que utiliza el indicador.

Si el enfoque a la optimización es correcto, eso no va a suponer ningún problema, ya que la optimización suele ejecutarse con un símbolo y en un timeframe. Primero, tenemos que elegir el símbolo y el timeframe que vamos a usar, y establecer un valor apropiado del parámetro PriceLevel para ellos. Luego, tenemos que elegir el oscilador que vamos a usar, y establecer un valor apropiado del parámetro IndLevel. No hace falta tratar de optimizar automáticamente el tipo del oscilador usado y los valores de los parámetros PriceLevel y IndLevel: aparte de ellos hay muchos otros parámetros a optimizar. En primer lugar, es el tipo de la divergencia (variable Number) y el período del oscilador.

Archivos adjuntos

Para el trabajo del indicador iDivergence necesitamos el oscilador universal del artículo «Oscilador universal con interfaz gráfica». Él y todos los archivos necesarios se adjuntan al artículo.

Todos los archivos adjuntos:

  • Include/InDiver/CUniDiverConditions.mqh — archivo con las clases de la comprobación de las condiciones de la divergencia;
  • Include/InDiver/CUniDiverExtremums.mqh — archivo con las clases de la determinación de extremos; 
  • Include/InDiver/UniDiverDefines.mqh — descripción de las estructuras y enumeraciones;
  • Include/InDiver/UniDiverGObjects.mqh — funciones para trabajar con los objetos gráficos;
  • Indicators/iDivergence.mq5 — indicador;
  • Indicators/iUniOsc.mq5 — oscilador universal;
  • Include/UniOsc/CUniOsc.mqh — archivo con las clases para el oscilador universal;
  • Include/UniOsc/UniOscDefines.mqh — descripción de las estructuras y enumeraciones para el oscilador universal.

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

Archivos adjuntos |
MQL5.zip (14.11 KB)
Neuroredes profundas (Parte III). Selección de ejemplos y reducción de dimensiones Neuroredes profundas (Parte III). Selección de ejemplos y reducción de dimensiones
Este artículo continúa la serie de publicaciones sobre las neuroredes profundas. Vamos a analizar la selección de ejemplos (eliminación de ruidos), la reducción de los datos de entrada y la división del conjunto en train/val/test durante la preparación de los datos.
Evaluación del riesgo en la secuencia de transacciones con un activo Evaluación del riesgo en la secuencia de transacciones con un activo
En este artículo se describe el uso de los métodos del cálculo de probabilidades y la estadística matemática en el análisis de los sistemas comerciales.
Optimizamos la estrategia usando el gráfico del balance y comparamos los resultados con el criterio "Balance + max Sharpe Ratio" Optimizamos la estrategia usando el gráfico del balance y comparamos los resultados con el criterio "Balance + max Sharpe Ratio"
Ha sido considerado otro criterio de usuario para la optimización de las estrategias comerciales basado en el análisis del gráfico del balance. Para eso, ha sido utilizado el cálculo de la regresión lineal con la ayuda de la librería ALGLIB.
Asesor Experto multiplataforma: Niveles stop Asesor Experto multiplataforma: Niveles stop
En este artículo se analiza la implementación de niveles stop en el asesor comercial, la implementación es compatible con las plataformas MetaTrader 4 y MetaTrader 5.