Descargar MetaTrader 5

Ondas de Wolfe

24 mayo 2017, 09:21
Dmitry Fedoseev
1
1 568

Contenido

Introducción

Las ondas de Wolfe son una figura de análisis gráfico descubierta y descrita detalladamente por Bill Wolfe. De manera externa, la figura es parecida a un triángulo o a una cuña (el propio Wolfe la llamó cuña ascendente), pero tiene una serie de matices. El método gráfico propuesto por Bill Wolfe permite no solo mostrar una figura y definir al mismo tiempo el momento y la dirección de la entrada, sino también sincronizar el objetivo que deberá alcanzar el precio y el tiempo de dicho alcance.

En este artículo describiremos con todo detalle las normas de búsqueda e interpretación de las ondas de Wolfe. Basándonos en el indicador Zigzag del artículo  "Zigzag universal" crearemos un indicador para detectarlas y representarlas de forma automática en el gráfico. De forma adicional, añadiremos un sencillo asesor que funcionará conforme al indicador obtenido. Esto permitirá comprobar el funcionamiento del indicador y obtener las primeras impresiones sobre el método de análisis gráfico propuesto por Bill Wolfe, analizado en este artículo. 

Normas de definición de las ondas de Wolfe

Estudiaremos las ondas de Wolfe utilizando el ejemplo de la compra (fig. 1). El precio forma dos valles que descienden de forma consecutiva (línea azul, puntos 1 y 3) y dos picos que descienden de forma consecutiva (puntos 2 y 4). Después del viraje y la formación del pico en el punto 4, el precio sigue cayendo, al formarse con el precio de la línea 1—3, se realiza la compra (punto 5). 


Fig. 1 Ondas de Wolfe para la compra. La línea azul es el precio, la línea roja, la construcción para definir el precio. La entrada se ejecuta en el punto 5, el objetivo es el punto 7

El punto 6, obtenido con el cruce de las líneas 1—3 y 2— 4 define el tiempo de alcance del objetivo. El valor del objetivo (punto 7) se define como el cruce de las líneas 1— 4 y la línea vertical trazada a través del punto 6. No hay algoritmo de cálculo del stop-loss en la metodología, solo existe un consejo general para usarlo a su propia discreción. Con esto damos por terminado lo referente a las normas de definición de las ondas en el libro de Wolfe. 

A la hora de desarrollar un indicador para este artículo, se han añadido varias normas más.

  1.  El punto 3 deberá encontrarse de forma notoria por debajo del punto 1, esto se logra comprobando la condición:

    v3<v1-d1

    donde:

    • v3 — es el nivel del punto 3;
    • v1 — es el nivel del punto 1;
    • d1 — es la distancia vertical entre los puntos 1 y 2 (segmento 1''-2''), multiplicada por el coeficiente K1 (el coeficiente K1 es un parámetro en la ventana de propiedades, su valor por defecto es 0.1).

  2. La línea 1—4, que define el objetivo, deberá estar dirigida hacia arriba, es decir, el punto 4 deberá estar notoriamente por encima de 1. Para ello, se comprueba la condición:

    v4>v1+d1;

    donde v4 — es el nivel del punto 4. 

  3. El punto 4 deberá estar notoriamente por debajo del punto 2, esto se logra comprobando la condición:

    v4<v2-d2;

    donde v2 — es el nivel del punto 2, d2 — es la distancia vertical entre los puntos 2 y 3 (el segmento 2''-3'')? multiplicado por el coeficiente K2 (el coeficiente K2 — es un parámetro en la ventana de propiedades, su valor por defecto es 0.1).

  4. Las líneas 2-4 y 1-3, que definen el tiempo de alcance del objetivo, deberán cruzarse desde la derecha, para ello la altura 2-2' deberá ser notoriamente mayor que la altura 4-4'. El cumplimiento de esta condición se realiza con la comprobación:

    h2-h4>K3*h2;

    donde h2 — es la magnitud del segmento 2-2', h4 — es la magnitud del segmento 4-4', K3 — es el coeficiente (el coeficiente K3 — es un parámetro en la ventana de propiedades, su valor por defecto es 0.1).

Las normas matizadas que acabamos de exponer no pretenden tener el estatus de verdad absoluta. A continuación, en el artículo se describe con detalle el proceso de creación del indicador. Tras familiarizarse con él, usted podrá corregir por sí mismo el código de acuerdo con sus ideas y teorías. 

Elección de Zigzag para su perfeccionamiento

Antes de proceder al trabajo, descargaremos los anexos al artículo "Zigzag universal", en los que se encuentran muchas variantes del indicador Zigzag. Debemos decidir cuál de ellos vamos a tomar como base. Podemos descartar de inmediato las variantes iUniZigZagPrice y iUniZigZagPriceSW, que han sido pensadas para calcular Zigzag a partir de otro indicador que se encuentre en el gráfico, y por eso solo tendrán interés para el análisis y la observación visual. El resto de los indicadores son muy interesantes, cada uno de ellos se puede usar para crear expertos. También descartaremos las variantes iCloseZigZag y iHighLowZigZag, se trata solo de los ejemplos iniciales para crear el Zigzag. Solo nos quedan dos opciones: iUniZigZag y iUniZigZagSW. Entre ellas, la más preferible es iUniZigZagSW, que funciona en la subventana y nos ofrece posibilidades más amplias. Sin embargo, en los anexos hay otro indicador más: iUniZigZagSWEvents que supone un ejemplo de uso de la función iCustom() para recurrir al indicador iUniZigZagSW. Nos vamos a detener precisamente en esta opción, ya que nos permitirá no solo usar todas las posibilidades del indicador iUniZigZagSW, sino también extraer completamente el código de detección de las ondas de Wolfe del código de Zigzag.

El indicador iUniZigZagSWEvents se representa en el gráfico de precio, para su dibujado se usan cuatro búferes: dos con flechas y dos con puntos, precisamente lo que hace falta para reconocer las ondas de Wolfe. Con flechas se representarán los lugares de detección de las figuras, y con puntos, los niveles de objetivo. El indicador creado dibujará las ondas y todas las construcciones para definir el objetivo con la ayuda de objetos gráficos, en particular, de la línea de tendencia. Si la dibujamos con un segmento, sin prolongar el rayo, será muy cómoda para representar diferentes construcciones.  

Las ondas de Wolfe se usan no solo para definir el momento y la dirección de la entrada, sino también para pronosticar los objetivos. Por eso, al usar el indicador iUniZigZagSW, surgen problemas. El indicador tiene el parámetro SrcSelect para elegir la fuente de los datos analizados, según los cuales se construye Zigazag. Se puede elegir una de las cuatro opciones:

  • Src_HighLow — según los precios high y low;
  • Src_Close — según los precios close;
  • Src_RSI — según el indicador RSI;
  • Src_MA — según el indicador MA.

Sobre la base del indicador creado en el artículo, se construirá el experto comercial. Por eso, si construimos Zigzag a partir del precio, para establecer el take-profit se puede usar el objetivo pronosticado. Tampoco aparecerán problemas con la representación del objetivo en el gráfico. Si construimos Zigzag según RSI (SrcSelect=Src_RSI), el objetivo pronosticado tampoco será para el precio, sino para el indicador RSI.  Eso significa que al alcanzar el valor objetivo con el indicador RSI deberemos ejecutar un cierre de mercado, y lo más importante  no será posible representar en el gráfico el precio objetivo y las construcciones auxiliares.

Así, al usar zigzag según el precio (Src_HighLow или Src_Close) en el gráfico se representarán el precio meta y las construcciones, y en el resto de los casos, solo la flecha que designe la figura encontrada y su dirección. El valor del objetivo seguirá guardándose en el búfer de indicador correspondiente (para que sea posible ejecutar el cierre de mercado en el experto o para otros cometidos), pero no se representará.

Lo más probable es que la idea del cierre cuando el indicador alcance el nivel meta no sea ejecutable en la práctica. El problema es que la mayoría de los indicadores cambian sus valores en un rango limitado, y el resultado de la construcción puede salirse del marco. Sin embargo, en todos los casos el búfer contendrá el valor del objetivo.

Recopilación de datos para Zigzag

Pasemos a la creación del indicador. Abrimos en el editor el archivo iUniZigZagSWEvents y lo guardamos con el nombre iWolfeWaves. Vamos a trabajar precisamente en él.

Sería muy cómodo tener acceso directo a todos los picos del Zigzag, de forma que no tengamos que buscarlos cada vez en la historia. Vamos a crear una matriz para guardarlos. Ahora, al cambiar la dirección delZigzag, a la matriz se le añadirá un nuevo elemento. Si el indicador simplemente va a continuar el último segmento (actualizar el extremo), se actualizará el último elemento de la matriz.

Para cada pico guardaremos su valor, dirección e índice de la barra en la que se encuentra (la numeración de los elementos va de izquierda a derecha). Utilizaremos para ello una estructura de tres campos:

struct SPeackTrough{
   double   Val; // valor
   int      Dir; // dirección
   int      Bar; // índice de la barra
};

Crearemos una matriz de estas estructuras:

SPeackTrough PeackTrough[];

Si se usara solo el Zigzag según high y low (SrcSelect = Src_HighLow), en el cambio de dirección bastaría con aumentar la matriz, establecer los valores y actualizar el último elemento a medida que se alarga el último segmento del indicador. Con el Zigzag según el precio close (SrcSelect = Src_Close) o según los datos de otro indicador, la cosa se complica. A medida que se forma la barra en la que ha tenido lugar el cambio de dirección, Zigzag puede retornar al estado original (que precede a la apertura de la barra en formación). Esto significa que en cada nuevo cálculo de la misma barra, la matriz para los picos se debe retornar al estado original, el que tenía en la barra anterior. Si cambiamos con frecuencia el tamaño de la matriz, esto podría ralentizar el indicador. Por eso, vamos a introducir una variable en la que se guardará el tamaño utilizado de la matriz, y la propia matriz cambiará por bloques a medida que sea necesario, solo en la dirección del aumento del tamaño. Antes de realizar de nuevo el cálculo de la misma barra, retornaremos el valor fuente de esta variable.

Para guardar el tamaño de la matriz usamos dos variables, en una de ellas se guardará el tamaño de la matriz en la barra anterior, y en la otra, en la barra calculada:

int PreCount; // tamaño de la matriz PeackTrough en la barra anterior
int CurCount; // tamaño de la matriz PeackTrough en la barra calculada

Después de que finalice la formación y el cálculo de la barra o tras la ejecución del cálculo de la barra de turno, el valor de la variable CurCount se debe reubicar en la variable PreCount. Después, antes de ejecutar cada cálculo de la nueva barra en formación, desplazaremos el valor de la variable PreCount a la variable CurCount. En todos los cálculos se usará solo la variable CurCount. La variable PreCount es una variable auxiliar. Es posible saber que la formación de una barra ha finalizado en la apertura de la barra siguiente (o al pasar al cálculo de la barra siguiente en la historia). La aparición de una nueva barra se determinará según la hora: si la hora de la barra ha cambiado, significa que ha aparecido una nueva barra (o que ha tenido lugar el paso al cálculo de la siguiente barra de la historia). Para definir una nueva barra, será necesaria una variable auxiliar:

datetime LastTime;

Las variables PreCount, LastCount, LastTime son las variables globales del indicador. Pero estas pueden ser declaradas también como estáticas en la función OnCalculate() del indicador. 

Vamos a pasar al funcionamiento en la función OnCalculate(). Según el valor de la variable prev_calculated se establece si se ejecuta el primer cálculo del indicador o el cálculo solo de las nuevas barras. El valor 0 designa el cálculo completo, además, habrá que inicializar las variables PreCount, CurCount y LastTime. El siguiente código, que define el rango de las barras para el cálculo y que inicializa las variables auxiliares, se ubica en la parte más alta de la función OnCalculte():

int start; // variable para el índice de la primera barra calculada

if(prev_calculated==0){ // cálculo completo de todas las barras
   start=1;
   CurCount=0;   
   PreCount=0;
   LastTime=0;
}
else{ // cálculo de la nueva barra
   start=prev_calculated-1;
}

Ahora vamos a ocuparnos del ciclo de indicador estándar, al comienzo del mismo posibilitaremos el desplazamiento de los valores en las variables PreCount, CurCount:

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

   if(time[i]>LastTime){ // primer cálculo de la barra nueva (siguiente)
      LastTime=time[i];
      PreCount=CurCount;
      PreDir=CurDir;
   }
   else{ // cálculo repetido de la barra
      CurCount=PreCount;
      CurDir=PreDir;
   }

En todas partes en los cálculos se utlilizará la variable CurCount, y la variable PreCount solo servirá para mantener el valor actual en la variable CurCount. Al abrir una nueva barra, la variable CurCount al principio contendrá el valor obtenido como resultado del cálculo de la barra anterior. Por eso, desplazamos su valor a la variable PreCount. Como resultado del cálculo de la nueva barra, en la variable CurCount puede cambiar el valor, pero solo podremos enteneder si es definitivo o no en la apertura de la nueva barra. Por eso, al calcular de nuevo una misma barra, en CurCount se ubicará el valor de la variable PreCount.

A continuación, en el ciclo principal de indicador, deberá ubicarse el siguiente código, que permanece desde el indicador iUniZigZagSWEvents:

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

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

// dirección

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

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

// nuevo máximo

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

// nuevo mínimo

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

La parte del código que dibuja las flechas no la necesitamos, así que la eliminamos.

Mientras funciona, el indicador realizará un seguimiento de cada cambio del Zigzag, no solo de su cambio de dirección, sino también de cada prolongación del último segmento, por eso el último segmento será necesario para definir los puntos 5 (ver la fig. 1). Para ello, se usará la parte del códio que ha quedado del dibujado de los nuevos extremos en el fragmento mostrado más arriba.

Para realizar un seguimiento de la dirección de zigzag y definir los momentos en que cambia, necesitaremos otro par más de variables, semejantes a las variables CurCount y PreCount: se trata de las variables PreDir y CurDir:

int PreDir; // dirección de zigzag en la barra anterior
int CurDir; // dirección de zigzag en la barra calculada

Asimismo, pueden también ser tanto globales como estáticas en la función OnCalculate(). Al comienzo del cálculo del indicador, también tenemos que inicializarlas y reubicar sus valores al calcular cada barra, igual que sucede con las variables PreCount y CurCount. Más abajo se muestra el código definitivo de la función OnCalculate() en esta etapa de creación del indicador:

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

   int start; // variable para la barra inicial del cálculo
   
   if(prev_calculated==0){ // cálculo completo 
      start=1; 
      CurCount=0;
      PreCount=0;
      CurDir=0;
      PreDir=0;      
      LastTime=0;
   }
   else{ // cálculo solo de las nuevas barras
      start=prev_calculated-1;
   }

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

      // despejando los búferes que dibujan flechas y puntos
      
      UpArrowBuffer[i]=EMPTY_VALUE;
      DnArrowBuffer[i]=EMPTY_VALUE;
      
      UpDotBuffer[i]=EMPTY_VALUE;
      DnDotBuffer[i]=EMPTY_VALUE;    
      
      // variables auxiliares
      
      double hval[1];
      double lval[1];
      
      double zz[1];
      
      // nuevo máximo
      
      double lhb[2];
      // obtenemos los dos elementos del búfer con los índices de las barras de los nuevos máximos
      if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){ 
         return(0);
      }
      if(lhb[0]!=lhb[1]){ // hay un nuevo máximo
         // obtenemos el valor del máximo del precio (o los datos conforme a los que se calcula Zigzag)
         if(CopyBuffer(handle,0,rates_total-i-1,1,hval)<=0){
            return(0);
         }      
         if(CurDir==1){ // última dirección ascendente conocida 
            // actualizamos los datos sobre el último punto de zigzag
            RefreshLast(i,hval[0]);
         }
         else{ // El Zigzag ha cambiado la dirección
               // añadimos un nuevo valor
            AddNew(i,hval[0],1);
         }
         // aquí tendrá lugar la comprobación de las condiciones de detección de las ondas de Wolfe descendentes  
      }
      
      // nuevo mínimo
      
      double llb[2];
      // obtenemos los dos elementos del búfer con los índices de las barras de los nuevos mínimos
      if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
         return(0);
      }
      if(llb[0]!=llb[1]){ // hay un nuevo mínimo
         // obtenemos el valor del mínimo del precio (o los datos conforme a los que se calcula Zigzag)
         if(CopyBuffer(handle,1,rates_total-i-1,1,lval)<=0){
            return(0);
         }         
         if(CurDir==-1){ // última dirección descendente conocida
            // actualizamos los datos sobre el último punto de Zigzag
            RefreshLast(i,lval[0]);
         }
         else{ // El Zigzag ha cambiado de dirección
            // añadimos el nuevo valor
            AddNew(i,lval[0],-1);
         }
         // aquí tendrá lugar la comprobación de las condiciones de detección de las ondas de Wolfe ascendentes 
      }      
   }   
   return(rates_total);
}

En este código se encuentran las funciones AddNew() y RefreshLast(). En ambas funciones se transmiten el índice de la barra en el que tuvo lugar el cambio de Zigzag y el valor del nuevo extremo. A la función AddNew() también se le transmite la dirección del Zigzag.

Función de adición del nuevo punto AddNew():

void AddNew(int i,double v,int d){
   if(CurCount>=ArraySize(PeackTrough)){ // en la matriz no hay elementos libres
      ArrayResize(PeackTrough,ArraySize(PeackTrough)+1024); // aumento del tamaño de la matrizа
   }
   PeackTrough[CurCount].Dir=d; // estableciendo la dirección
   PeackTrough[CurCount].Val=v; // estableciendo el valor
   PeackTrough[CurCount].Bar=i; // estebleciendo la barra
   CurCount++; // aumento de la variable con el número de elementos ocupados de la matriz   
   CurDir=d; // recordamos la última dirección del Zigazag
} 

Función de actualización del último punto RefreshLast():

void RefreshLast(int i,double v){
   PeackTrough[CurCount-1].Bar=i; // estableciendo una nueva barra
   PeackTrough[CurCount-1].Val=v; // estableciendo un nuevo valor
} 

En esta etapa es posible guardar el indicador, para usarlo en lo sucesivo como base a la hora de desarrollar indicadores de definición de las diferentes figuras de Zigzag. En los anexos al artículo, el indicador tiene el nombre "iWolfeWaves_Step_1".

Un poco de geometría

A la hora de detectar las ondas de Wolfe y ejecutar las construcciones que definen el objetivo, necesitaremos un poco de geometría. Analizaremos estas tareas por separado y escribiremos funciones para resolverlas.

Tarea 1. La línea recta es definida por el par de puntos x-y, donde х es el índice de la barra, e у es el valor (precio o valor del indicador). Dada la coordenada x del tercer punto, es necesario encontrar el valor de la línea en este punto (fig. 2).


Fig. 2. Se han establecido: X1, Y1, X2, Y2, X3. Debemos hallar Y3.

Solución de la tarea 1. Definimos la magnitud de crecimiento de la línea en el eje Y en una unidad de incremento en el eje X:

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

Donde D — es la magnitud de crecimiento, Y1 — el valor del precio o el indicador en el punto 1, Y2 — el valor del precio o el indicador en el punto 2, X1 — es el índice de la barra en el punto 1, X2 — es el índice de la barra 2.  

Definimos Y3:

Y3=Y1+(X3-X1)*D

Donde X3 es el índice de la barra en el punto 3, e Y3 es el valor buscado de la línea en el punto 3.

Obtenemos la función siguiente:

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

En la función se transmiten los siguientes parámetros:

  • x1 — índice de la barra del punto 1;
  • y1 — valor en el punto 1;
  • x2 — índice de la barra en el punto 2;
  • y2 — valor en el punto 2.

Tarea 2. Con los dos parámetros de los puntos х-у se han definido dos líneas. Es necesario encontrar la coordenada x del punto donde se cruzan (fig. 3). Aquí puede surgir la pregunta: ¿por qué precisamente la coordenada x? Esto no es fundamental, ya que después de obtener la coordenada x en cualquier caso se calculará la coordenada del punto 3 (usando la ecuación de una de las rectas). Por eso, primero podemos obtener la coordenada y del punto 3, y a continuación, utilizando la ecuación de una de las rectas, calcular su abscisa.  


Fig. 3. Se han definido dos líneas, es necesario encontrar su punto de cruce


En primer lugar, usando las coordenadas de los dos puntos, obtendremos la ecuación de las líneas en forma de y=a+b*x.

Ejecutamos los cálculos preliminares. Magnitud de la inclinación de la línea (en unidades en el eje y, en una unidad en el eje x):

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

Donde D1 — es la magnitud buscada de la inclinación de la primera línea (magnitud del cambio de valor de la línea en una barra), X11 — es el índice de la barra en el punto 1 de la primera línea, X12 — es el índice de la barra en el punto 2 de la primera línea, Y11 — es el valor de la primera línea en el punto 1, Y12 — es el valor de la primera línea en el punto 2.     

Magnitud de la inclinación de la línea 2:

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

Donde D2 — es la magnitud buscada de la inclinación de la seguna línea (magnitud del cambio de valor de la línea en una barra), X21 — es el índice de la barra en el punto 1 de la segunda línea, X22 — es el índice de la barra en el punto 2 de la segunda línea, Y21 — es el valor de la segunda línea en el punto 1, Y22 — es el valor de la segunda línea en el punto 2.

Ahora obtenemos la ecuación de las líneas. Ecuación de la línea 1:

Y3=Y11+D1*(X3-X11)

Donde Y3 — es el valor de la línea en el punto de cruce (punto 3), X3 — es el índice de la barra en el punto 3.

Ecuación de la línea 2:

Y3=Y21+D2*(X3-X21)

En el punto de cruce los valores de las líneas son iguales, por eso igualamos la ecuación de la línea 1 con la ecuación de la línea 2:

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

Usando la expresión obtenida, expresamos X3. Como resultado, obtenemos la función TwoLinesCrossX() para definir la coordenada X de cruce de las dos líneas:

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

En la función se transmiten los siguientes parámetros:

  • x11 — índice de la barra del punto 1 de la primera línea;
  • y11 — valor en el punto 1 de la primera línea;
  • x12 — índice de la barra del punto 2 de la primera línea;
  • y12 — valor en el punto 2 de la primera línea;
  • x21 — índice de la barra del punto 1 de la segunda línea;
  • y21 — valor en el punto 1 de la segunda línea;
  • x22 — índice de la barra del punto 2 de la segunda línea;
  • y22 — valor en el punto 2 de la segunda línea.

Después de definir la coordenada x del punto de cruce de las dos líneas, se puede calcular la coordenada Y usando las coordenadas de los dos puntos de una de las líneas y la función y3(), obtenida tras resolver la tarea 1.

Si en primer lugar debemos obtener la coordenada Y, deberemos transforma la ecuación de las líneas de tal forma que expresen la coordenada x a través de Y. Primero para una línea:

X3=X11+(Y3-Y11)/D1

Para la segunda línea:

X3=X21+(Y3-Y21)/D2

Igualamos ambas ecuaciones:

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

A partir de la ecuación obtenida, debemos expresar Y3. Al final, obtenemos la función TwoLinesCrossY():

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

Los parámetros de esta función son los mismos que tiene TwoLinesCrossX(). 

Definiendo las ondas

Ahora que tenemos un acceso sencillo a todos los picos de zigzag y a las funciones geométricas auxiliares, vamos a trabajar directamente con la definición de las ondas de Wolfe. Es necesario "captar" el momento en el que el último segmento de zigzag cruza la línea 1-3 (ver la fig. 1), es decir, el punto 5. En otras palabras, comprobar las condiciones que detectan las ondas de Wolfe con la aparición de cada extremo de Zigzag (tanto con el cambio de dirección, como con la continuación del último segmento). Más arriba, en el código de la función OnCalculate() se han comentado al detalle los lugares en los que se deberá ejecutar la comprobación de las condiciones. Desde ellas se llamarán las funciones CheckDn() y CheckUp(). Veamos con detalle una de ellas, la función CheckUp():

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

   if(CurCount<5 || CurDir!=-1){ 
      // si hay pocos picos o el Zigzag no está dirigido hacia abajo, no realizamos la comprobación
      return;
   }   
   
   // preparamos las variables breves con los datos sobre los picos 

   // variables con los valores de los picos
   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   // variables con las barras de los picos
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
                  
   if(CurLastBuySig!=i4){ // si en esta configuración de Zigzag aún no se han detectado las ondas
      double d1=K1*(v2-v1); // magnitud mínima del desplazamiento del pico 3 con respecto al pico 1
      if(v3<v1-d1){ // el pico 3 se encuentra notoriamente por debajo del pico 1
         if(v4>v1+d1){ // la línea 1-4 está inclinada hacia arriba
            double d2=K2*(v2-v3); // magnitud mínima del desplazamiento del pico 4 con respecto al pico 2
            if(v4<v2-d2){ // el pico 4 se encuentra notoriamente por debajo del pico 2
               double v5l=y3(i1,v1,i3,v3,i); // valor del punto 5
               if(v5<v5l){ // el último segmento del zigzag ha cruzado la línea 1-3
                  double v4x=y3(i1,v1,i3,v3,i4); // valor en el punto 4'
                  double v2x=y3(i1,v1,i3,v3,i2); // valor en el punto 2'
                  double h4=v4-v4x; // altura 4-4'
                  double h2=v2-v2x; // altura 2-2'
                  if(h2-h4>K3*h2){ // las líneas 1-3 y 2-4 coinciden por la derecha
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // barra de cruce de las líneas 1-3 y 2-4
                     double tv=y3(i1,v1,i4,v4,tb); // valor en el punto de cruce de las líneas 1-3 y 2-4
                     UpArrowBuffer[i]=low[i]; // representación de la flecha hacia arriba
                     UpDotBuffer[i]=tv; // representación del punto en el nivel de objetivo
                     CurLastBuySig=i4; // recordamos que en esta configuración de zigzag se ha detectado la figura
                     if(_DrawWaves){ // dibujado de la figura y construcción
                        DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

Para definir las ondas son necesarios como mínimo 5 picos de zigzag. Condiciones adicionales: para detectar las ondas previstas como consecuencia del viraje hacia arriba, el Zigzag debe estar dirigido hacia abajo:

if(CurCount<5 || CurDir!=-1){ 
   // si hay pocos picos o el Zigzag está dirigido hacia abajo, no ejecutamos la comprobación
   return;
}   

Para obtener los datos sobre los picos se puede recurrir directamente a la matriz PeackTrough, pero esto no resulta cómodo. Resulta más fácil usar las variables auxiliares con nombres cortos:

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

Si se ha detectado una onda, se ha puesto la flecha, entonces no es necesario usar esta misma configuración de Zigzag. Una configuración idéntica de zigzag se ejecuta con la comprobación del índice del pico 4 (del último pico formado):

if(CurLastBuySig!=i4){ // si en esta configuración de zigzag aún no se han detectado ondas

Para guardar los valores del identificador de la configuración se usa un par de variables similares a las variables CurCount y PreCount.

Ahora vamos a pasar directamente a la definición de las ondas. Se calcula la magnitud del desplazamiento mínimo del punto 3 con respecto al punto 1 y del punto 2 con respecto al punto 1:

double d1=K1*(v2-v1); // magnitud mínima del desplazamiento del pico 3 con respecto al pico 1

A continuación se comprueba el desplazamiento de los puntos:

if(v3<v1-d1){ // el pico 3 se encuentra notoriamente por debajo del pico 1
   if(v4>v1+d1){ // la línea 1-4 está inclinada hacia arriba

Calculamos la magnitud mínima del desplazamiento del punto 4 con respecto al punto 2:

double d2=K2*(v2-v3); // magnitud mínima del desplazamiento del pico 4 con respecto al pico 2

Comprobamo la posición de los puntos 2 y 4:

if(v4<v2-d2){ // el pico 4 se encuentra notoriamente por debajo del pico 2

Calculamos el valor del punto que se encuentra en la línea 1-3 y el correspondiente a la barra calculada:

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

Comprobamos si ha habido contacto con la línea 1-3:

if(v5<v5l){ // el último segmento del zigzag ha cruzado la línea 1-3

Calculamos los valores en los puntos 4' y 2':

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

Calculamos las alturas 4-4' y 2-2':

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

Usando estas alturas, comprobamos la coincidencia de las líneas  1-3 y 2-4 de la derecha:

if(h2-h4>K3*h2){ // las líneas 1-3 y 2-4 coinciden por la derecha

El cumplimiento de esta condición significa que se ha localizado una onda. 

Definimos el objetivo. En primer lugar, la barra de alcance del objetivo:

double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // barra de cruce de las líneas 1-3 y 2-4

Nótese que para los cálculos sean más precisos, se usa la variable double.     

Valor del objetivo:

double tv=y3(i1,v1,i4,v4,tb); // valor en el punto de cruce de las líneas 1-3 y 2-4

 Representamos los signos y "recordamos" el identificador de la configuración del zigzag:

UpDotBuffer[i]=tv; // representación del punto en el nivel del objetivo
CurLastBuySig=i4; // recordamos que en esta configuración de zigzag la figura se ha detectado

Y al fin, dibujamos también la construcción que define el objetivo:

if(_DrawWaves){ // dibujado de la figura y construcción
   DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
}

Dedicaremos una sección aparte del artículo al dibujado de ondas y construcciones, es decir, al análisis de la función DrawObjects(). 

Las ondas hacia abajo (para la venta) se definen con la función CheckDn, idéntica a CheckUp, con la excepción de pequeñas diferencias relacionadas con la dirección. Más abajo mostramos su código y analizamos sus diferencias con respecto a la función CheckUp():

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

   // hay pocos picos o la dirección no va hacia arriba 
   if(CurCount<5 || CurDir!=1){ 
      return;
   }

   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
   
   if(CurLastSellSig!=i4){               
      double d1=K1*(v1-v2); // el pico v1 está por encima del pico v2
      if(v3>v1+d1){ // el pico v3 está por encima del pico v1
         if(v4<v1-d1){ // el pico v4 está por debajo del pico v1                     
            double d2=K2*(v3-v2); // el pico v3 está por encima del pico v2                     
            if(v4>v2+d2){ // el pico v4 está por encima del pico v2  
               double v5l=y3(i1,v1,i3,v3,i);
               if(v5>v5l){ // zigzag rompe la línea 1-3 hacia arriba
                  double v4x=y3(i1,v1,i3,v3,i4);
                  double v2x=y3(i1,v1,i3,v3,i2);
                  double h4=v4x-v4; // el punto 4' está por encima del punto 4
                  double h2=v2x-v2; // el punto 4' está por encima del punto 4
                  if(h2-h4>K3*h2){   
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4);
                     double tv=y3(i1,v1,i4,v4,tb);                              
                     DnArrowBuffer[i]=high[i];
                     DnDotBuffer[i]=tv;
                     CurLastSellSig=i4;   
                     if(_DrawWaves){
                        // dibujado con otros colores
                        DrawObjects(SellColor,
                                    SellTargetColor,
                                    v1,
                                    v2,
                                    v3,
                                    v4,
                                    v5l,
                                    i1,
                                    i2,
                                    i3,
                                    i4,
                                    i5,
                                    time,
                                    i,
                                    tb,
                                    tv,
                                    rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

Primera diferencia en la comprobación inicial:

// hay pocos picos o la dirección no es hacia arriba 
if(CurCount<5 || CurDir!=1){ 
   return;
}

Si hay pocos picos o el Zigzag está dirigido hacia abajo, finalizamos el funcionamiento de la función.

Para la dirección hacia abajo, los picos y valles se cambian de lugar: los puntos 1, 3, 5, 6 se encuentran arriba, y los puntos  2, 4, 7, abajo, por eso algunas variables en las fórmulas se cambian de lugar. Definición de la distancia mínima entre los picos 1, 3 y 1, 4:

double d1=K1*(v1-v2); // el pico v1 está por encima del pico v2

Comporbación de la posición de los picos 1, 3:

if(v3>v1+d1){ // el pico v3 está por encima del pico v1

Comprobación de la posición de los picos 1, 4:

if(v4<v1-d1){ // el pico v4 está por debajo del pico v1 

Cálculo de la distancia mínima entre los picos 2, 3 y su comprobación:

double d2=K2*(v3-v2); // el pico v3 está por encima del pico v2                     
if(v4>v2+d2){ // el pico v4 está por encima del pico v2  

Comprobando si se ha formado el punto 5 (el zigzag ha roto la línea 1-3 hacia arriba):

if(v5>v5l){ // el zigzag ha roto la línea 1-3 hacia arriba

Cálculo de las alturas 2-2' y 4-4' para comprobar la coincidencia de las líneas 1-3 y 2-4 por la derecha:

double h4=v4x-v4; // el punto 4' está por encima del punto 4
double h2=v2x-v2; // el punto 4' está por encima del punto 4

El dibujado de la onda y las construcciones se ejecuta con otro color:

// dibujado con otros colores
DrawObjects(SellColor,
            SellTargetColor,
            v1,
            v2,
            v3,
            v4,
            v5l,
            i1,
            i2,
            i3,
            i4,
            i5,
            time,
            i,
            tb,
            tv,
            rates_total);

Dibujando las ondas y el objetivo

Todas las ondas y construcciones se dibujan con la ayuda de un único algorítmo, por eso se usa una función DrawObjects(). Los elementos dirigidos hacia arriba y hacia abajo se dibujan con un color diferente. Para ello, a la función se le transmite el parámetro de color BuyColor o SellColor. Asimismo, se dibujan con un color diferente las ondas y construcciones que definen el objetivo, por eso a la función se le transmiten también los parámetros BuyTargetColor o SellTargetColor. Estas variables son los parámetros externos del indicador, con cuya ayuda se pueden establecer los colores que resulten más cómodos. Aparte del color, se necesitarán otros parámetros externos. Más abajo se muestran todos los parámetros adcionales para las funciones de dibujado de ondas y las construcciones:

input bool   DrawWaves       =  true;             // activando el dibujado de ondas y construcciones
input color  BuyColor        =  clrAqua;          // color de las ondas de compra
input color  SellColor       =  clrRed;           // color de las ondas de venta
input int    WavesWidth      =  2;                // grosor de las ondas
input bool   DrawTarget      =  true;             // activación/desactivación adicional de las construcciones
input int    TargetWidth     =  1;                // grosor de las construcciones
input color  BuyTargetColor  =  clrRoyalBlue;     // color de las construcciones de compra
input color  SellTargetColor =  clrPaleVioletRed; // color de las construcciones de venta

Después del color, a la función se le transmiten las variables con los valores e índices de las barras de los picos. El valor del pico 5 es una excepción, pues para él se transmite, no el valor al final del segmento del zigzag, sino el valor calculado en la línea 1-3. Las coordenadas de todos los puntos del zigzag se indican en barras, y para los objetos gráficos es necesaria también la hora, por eso a la función también se le transmite el puntero a la matriz time. A continuación, se transmite el índice de la barra calculada — i, la barra objetivo — tb, el valor del objetivo — tv y el número total de barras en el gráfico — rates_total. 

Como ya se ha destacado al comienzo del artículo, el dibujado de ondas y construcciones deberá ejecutarse solo si se ha elegido el Zigzag según high/low (SrcSelect igual a Src_HighLow) o close (SrcSelect igual a Src_Close). Esto significa que en la función OnInit(), dependiendo del valor de la variable SrcSelect, deberá ejecutarse la desactivación forzosa del dibujado(con la variable DrawWaves).  Para ello, declararemos una variable adicional, que se usará en lugar de la variable DrawWaves:

bool _DrawWaves;

A continuación, en la función OnInit() definiremos su valor con la variable DrawWaves o la desactivaremos usando el valor false. Definiremos de forma adicional el color invisible para búfer de dibujado del objetivo:

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

Vamos a pasar al análisis de la función DrawObjects(). En primer lugar, mostraremos el código completo de la función, y después lo analizaremos con más detalle:

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

   // prefijo del nombre de los objetos gráficos 
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

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

   // dibujado de las construcciones
   if(DrawTarget){   
    
      datetime TargetTime;
      
      // obteniendo el valor entero del índice de la barra del objetivo 
      int tbc=(int)MathCeil(target_bar);
      
      if(tbc<rates_total){ // el objetivo se encuentra en los límites de las barras existentes del gráfico
         TargetTime=time[tbc];
      }
      else{ // objetivo en el futuro
         TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
      }
      
      // calculando los valores de las líneas de la construcción en la barra de objetivo
      double tv13=y3(i1,v1,i3,v3,tbc);   
      double tv24=y3(i2,v2,i4,v4,tbc);  
      double tv14=y3(i1,v1,i4,v4,tbc); 

      // construcciones

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

Todo el dibujado se ejecuta con varias líneas de tendencia, para las que en primer lugar se forma el prefijo general de los nombres:

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

A continuación, se ejecuta el dibujado de las ondas, las coordenadas de todos los picos se transmiten a la función:

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

Dibujamos la construcción que define el objetivo. Comprobamos si está activado el dibujado de construcciones:

// dibujado de construcciones
if(DrawTarget){  

Si está activado, dibujamos las construcciones. Al representar el indicador en la historia de la barra objetivo, lo más probable es que aparezca en alguna de las barras de la historia ya existentes, pero si las ondas se detectan en barras aparecidas recientemente, el objetivo puede aparecer en el futuro, a la derecha de la última barra. Esto significa que serán necesarias dos variantes de cálculo de la hora de la barra objetivo. Para ello, declararemos la variable:

datetime TargetTime;

La variable target_bar tiene un valor fraccionado, lo redondeamos hasta el entero más próximo:

// obteniendo el valor entero del índice de la barra objetivo 
int tbc=(int)MathCeil(target_bar);

A continuación, usaremos la variable obtenida tbc. Aquí se podría utilizar la función MathFloor() para obtener el número entero menor más próximo. Esto no influiría en el resultado final, puesto que las construcciones ejecutan solo una función informativa. Con el uso de MathCeil() los finales de las líneas 1-3 y 2-4 se cruzarán con total certeza junto a la barra objetivo y las construcciones tendrá un aspecto más natural.

Definimos la hora en la que se alcanza el objetivo. Si el objetivo se encuentra en una de las barras existentes, basta con calcular el índice de la barra y obtener la hora desde la matriz time. Si el objetivo ha resultado estar a la derecha de la última barra, entonces habrá que definir cuántas barras se retrasa el objetivo con respecto a la última y calcular la hora:

if(tbc<rates_total){ // el objetivo se encuentra dentro de los límites de las barras del gráfico
   TargetTime=time[tbc];
}
else{ // el objetivo está en el futuro
   TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
}

Calculamos los valores de todas las líneas (1-3, 2-4 y 1-4) en la barra del objetivo:

// calculamos los valores de las líneas de la construcción en la barra objetivo
double tv13=y3(i1,v1,i3,v3,tbc);   
double tv24=y3(i2,v2,i4,v4,tbc);  
double tv14=y3(i1,v1,i4,v4,tbc); 

Aunque a la función se le transmite el valor del objetivo anteriormente calculado (valor target_value), para las construcciones, incluso para la línea 2-4, se calculará de nuevo. Esto está relacionado con el hecho de que, en lugar de un valor preciso de la variable target_bar, se usa el valor de la variable tbc, que será muy superior a target_bar. Debido a estos valores, en el punto de la coordenda target_bar las líneas solo se cruzarán en el nivel target_value.

Según los valores calculados, dibujamos las líneas:

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

Las líneas se dibujan usando la función auxiliar fObjTrend():

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

La función es universal y puede usarse para crear rápidamente una línea de tendencia definiendo todos sus parámetros. La desiganción de todos los parámetros de la función se muestra en el recuadro 1. De ellos, los modificados con mayor frecuencia se encuentran al comienzo (5 parámetros obligatorios), el resto no son obligatorios y podemos no transmitirlos a la función. Esta variabilidad hace muy cómodo el uso de la función.

Recuadro 1. Parámetro de la función fObjTrend()

Parámetro Utilidad
string aObjName nombre del objeto
datetime aTime_1 hora del primer punto de anclaje
double aPrice_1 precio del primer punto de anclaje
datetime aTime_2 hora del segundo punto de anclaje
double aPrice_2 precio del segundo punto de anclaje
color aColor color
color aWidth grosor
bool aRay_1 prolongar la línea por el lado del primer punto de anclaje
bool aRay_2 prolongar la línea por el lado del segundo punto de anclaje
string aText texto de la sugerencia
int aWindow subventana
color aStyle estilo de la línea
int aChartID identificador del gráfico
bool aBack dibujar en segundo plano
bool aSelectable el objeto se puede seleccionar
bool aSelected el objeto ha sido seleccionado
long aTimeFrames en qué marcos temporales se representa la línea

Solo queda dibujar dos líneas adicionales: una vertical en la barra objetivo y una horizontal en el nivel del objetivo:

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

Como resultado, obtenemos una imagen de las ondas y construcciones:


Fig. 4. Ondas de Wolfe y construcciones para definir los objetivos al comprar

Eliminando los objetos gráficos

Al usar Zigzag según Сlose (SrcSelect=Src_Close) o según otro indicador, a medida que se forme la barra, la figura puede aparecer y desaparecer de forma periódica. Para ello, al comienzo del ciclo de indicador principal, se limpian los búferes con flechas y puntos: 

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

Asimismo, al comienzo del ciclo es necesario posibilitar la eliminación de los objetos gráficos. Si está activado el dibujado de ondas y construcciones, entonces al comienzo del ciclo de indicador se llama la función DeleteObjects():

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

Código de la función DeleteObjects():

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

Se transmite la hora de la barra calculada a la función, y en ella se ejecuta la eliminación de todos los objetos gráficos cuyos nombres que se correspondan con la barra calculada.

Al quitar el indicador del gráfico es necesario eliminar todos objetos gráficos creados por él. Desde la función DeInit(), que se ejecuta de forma automática al finalizar el funcionamiento, se llama la función ObjectsDeleteAll(). El segundo parámetro transmitido a la función es el nombre del indicador, que asimismo es el prefijo de todos sus objetos gráficos. Así se hace posible la eliminación solo de los objetos fráficos que pertenecen a un indicador comcreto:

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

Función de alerta

Añadimos al indicador una función de alerta al aparecer una flecha, la misma que en el indicador de tendencia universal del artículo "Tendencia universal con interfaz gráfica". 

La función de alerta puede seguir a la aparición de la flecha en la barra que se está formando (convendrá al usar el zigzag según high-low) o en la que se ha formado (convendrá al usar el zigzag según close u otro indicador). Esto significa que para elegir el tipo de alerta creamos una enumeración:

enum EAlerts{
   Alerts_off=0,  // alerta activada
   Alerts_Bar0=1, // alerta de barra en formación
   Alerts_Bar1=2  // alerta por barra formada
};

Añadimos a la ventana de propiedades la variable:

input EAlerts              Alerts         =  Alerts_off;

El código de alerta se ha sacado a una función aparte CheckAlerts(). En ella se transmite la cantidad de barras en el gráfico y la matriz de la hora:

void CheckAlerts(int rates_total,const datetime & time[]){
   if(Alerts!=Alerts_off){ // alertas activadas
      static datetime tm0=0; // variable para la hora de la barra de la última alerta de compra
      static datetime tm1=0; // variable para la hora de la barra de la última alerta de compra
      if(tm0==0){ // primera ejecución de la función
         // iniciliazación de variables
         tm0=time[rates_total-1];
         tm1=time[rates_total-1];
      }
      string mes=""; // variable para el mensaje

      // hay una flecha hacia arriba, y en la última barra aún no ha habido alerta
      if(UpArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm0!=time[rates_total-1]
      ){
         tm0=time[rates_total-1]; // recordar la hora de la última alerta
         mes=mes+" buy"; // formación del mensaje
      }

      // hay una flecha hacia abajo, y en la última barra aún no ha habido alerta
      if(DnArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm1!=time[rates_total-1]
      ){
         tm1=time[rates_total-1]; // recordar la hora de la última alerta
         mes=mes+" sell"; // formación del mensaje
      } 
      if(mes!=""){ // hay mensaje
         // abrir la ventana con el mensaje
         Alert(MQLInfoString(MQL_PROGRAM_NAME)+"("+Symbol()+","+IntegerToString(PeriodSeconds()/60)+"):"+mes);
      }        
   }   
}

La llamada de la función CheckAlerts() se ejecuta al final de la función OnCalculate() después del ciclo principal. Asimismo, al final de la función OnCalculate() se ejecuta la llamada de la función de actualización del gráfico para acelerar el dibujado de las ondas y construcciones:

if(_DrawWaves){
   ChartRedraw(0);
}
Con esto, podemos considerar la creación del indicador completamente finalizada. Se llama iWolfeWaves y se puede encontrar en los anexos al artículo.

Experto

El indicador ha resultado bastante complicado. Vamos a intentar asegurarnos de que funcione correctamente no solo en la historia estática. También valoraremos la efectividad del método analizado de análisis gráfico. Para ello, crearemos un sencillo experto.

Para valorar la efectividad es necesario que el experto abra las posiciones según todas las señales del indicador. Por eso, funcionará en las cuentas que permitan cobertura, y no tendrá limitaciones en el número de posiciones abiertas.

En el editor creamos un nuevo experto con el nombre eWolfeWaves. Desde el indicador copiamos todos los parámetros externos y los insertamos en el archivo del experto. Más abajo añadiremos los parámetros adicionales, que definen el stop-loss y el take-profit:

input double               StopLoss_K     =  1;      // coeficiente de stop-loss
input bool                 FixedSLTP      =  false;  // stop-loss y take-profit fijos
input int                  StopLoss       =  50;     // magnitud del stop-loss fijo
input int                  TakeProfit     =  50;     // magnitud del take-profit fijo

Estos parámetros permiten elegir una de las dos variantes de colocación de stop-loss y take-profit.

Si FixedSLTP=false, entonces actuará la variable StopLoss_K. En este caso, el take-profit se coloca según el indicador, al nivel del punto que representa el nivel objetivo, y el stop-loss se calcula de forma proporcional a la magnitud del take-profit, usando el coeficiente StopLoss_K. Esta variante de definición del stop-loss y el take-profit convendrá solo para el zigzag según el precio: según high-low o close (SrcSelect igual a Src_HighLow o Src_Close).

Con FixedSLTP=true se usan las variables StopLoss y TakeProfit. Esta variante da la posibilidad de usar el zigzag según los indicadores, pero se puede usar también con los zigzags según el precio.   

En la función OnInit() del experto, comprobamos el tipo de cuenta. Si la cuenta no permite la cobertura, el inicio del experto se finalizará:

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

Si se van a hacer simulaciones con el experto, pero no en el modo visual, desactivaremos forzosamente el dibujado de ondas y construcciones:

bool _DrawWaves;

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

La variable _DrawWaves se usará en lugar de la variable DrawWaves al llamar el indicador iWolfeWaves con la función iCustom(). Llamamos el indicador y comprobamos si se carga con éxito:

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

Si no se ha logrado cargar el indicador, se finaliza el funcionamiento del experto.

Al usar el indicador según high-low, la flecha del indicador después de la aparición no desaparece, y esto significa que puede funcionar en la barra en formación. En el resto de los casos, el experto debe "mirar" la flecha del indicador en la primera barra en formación. Para ello, declararemos la variable global del experto Shift:

int Shift;

Ajustaremos para ella el valor necesario, dependiendo del tipo de Zigzag: 

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

Más abajo se muestra el código de la función OnInit():

int OnInit(){

   // comprobando el tipo de cuenta
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
      Print("Its not hedging account");
      return(INIT_FAILED);
   }

   // desactivando el dibujado de ondas y construcciones

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

   // carga del indicador
   h=iCustom(  Symbol(),
               Period(),
               "iWolfeWaves",
               Alerts,
               SrcSelect,
               DirSelect,
               RSIPeriod,
               RSIPrice,
               MAPeriod,
               MAShift,
               MAMethod,
               MAPrice,
               CCIPeriod,
               CCIPrice,
               ZZPeriod,
               K1,
               K2,
               K3,
               _DrawWaves,
               BuyColor,
               SellColor,
               WavesWidth,
               DrawTarget,
               TargetWidth,
               BuyTargetColor,
               SellTargetColor);
       
   // comprobando si la carga del indicador ha tenido éxito         
   if(h==INVALID_HANDLE){
      Print("Cant load indicator");
      return(INIT_FAILED);
   }
   
   // definiendo la barra en la que el experto "mira" la flecha del indicador
   if(SrcSelect==Src_HighLow){
      Shift=0;
   }
   else{
      Shift=1;
   }

   return(INIT_SUCCEEDED);
}

Vamos a pasar a la función OnTick(). Es necesario posibilitar el funcionamiento del experto tanto con ticks, como con barras. Añadimos las variables para la hora de la barra en formación y para la última barra procesada (las variables han sido anunciadas en la función OnTick()):

datetime tm[1];     // hora de la barra en formación
static datetime lt; // hora de la última barra procesada

Obtenemos la hora de la última (en formación) barra:

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

Ejecutamos la comprobación de la hora de la barra:

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

Si Shift==0, el experto funciona en cada tick. De lo contrario, solo si el valor de la variable lt no es igual a la hora de la barra en formación (una vez por barra). 

Declaramos las variables auxiliares y obtenemos los valores del indicador:

double tp,sl; // variables para el cálculo del stop-loss y el take-profit

double buf_buy[1];         // para la flecha de compra
double buf_sell[1];        // para la flecha de venta

double buf_buy_target[1];  // para el objetivo de compra
double buf_sell_target[1]; // para el objetivo de venta 

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

Cuando hay señales comerciales, se ejecuta el cálculo del stop-loss, del take-profit y se ejecuta la apertura de posición:

// hay flecha, y en esta barra no se ha abierto posición buy
if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
   // stop-loss y take-profit
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_buy_target[0],_Digits);
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
   }
   // apertura
   if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
   // "recordamos" la hora de la última apertura
   LastBuyTime=tm[0];
}

Si se usan stop-loss y take-profit fijos (FixedStopLoss=true), para el cálculo del take-profit, al precio de apertura de posición (precio Ask en el caso de la compra) se le añade el valor de la variable TakeProfit, multiplicado por _Point. Para calcular el stop-loss del precio de apertura se resta el valor de la variable stop-loss, multiplicado por _Point. Después de calcular los valores obtenidos, se normalizan con la función NormalizeDouble() hasta el número de dígitos que corresponda al número de dígitos de las cotizaciones, que se puede obtener de la variable _Digits.

En caso de tener stop-loss y take-profit no fijos, primero definiremos el valor del take-profit y calcularemos el stop-loss de forma proporcional. Si no logramos abrir la posición, se finalizará el trabajo de la función OnTick(), y en el siguiente tick se intentará de nuevo abrir la posición. Los intentos se repetirán mientras exista la señal del indicador, es decir, durante una barra. Si se abre con éxito la variable LastBuyTime, se asignará la hora de la barra actual para que en esta barra no se realice más la apertura (en el caso de funcionamiento por ticks, cuando Shift=0). La variable LastBuyTime es la variable global del experto.

De forma análoga a la compra, pero con algunos cambios, se realiza la venta:

// si hay flecha y en esta barra no se ha abierto posición sell
if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
   // stop-loss y take-profit
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_sell_target[0],_Digits);
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
   }
   // apertura
   if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
   // "recordamos" la hora de la última apertura
   LastSellTime=tm[0];        
}  

Al vender, en lugar de la variable LastBuyTime se usa la variable LastSelTime y los niveles de stop-loss, take-profit se calculan según el precio bid.

Al final de la variable lt se establece la hora de la barra en formación, para que el experto no haga nada más en esta barra (si, por supuesto, funciona barra por barra, es decir Shift=1). Abajo mostramos el código completo de la función OnTick():

void OnTick(){
   
   datetime tm[1]; // hora de la barra en formacion
   static datetime lt; // hora de la última barra procesada
   
   // copiamos la hora
   if(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;
   
   if(Shift==0 || tm[0]!=lt){ // comprobación del funcionamiento una vez por barra

      double tp,sl; // variables para calcular el stop-loss y el take-profit

      double buf_buy[1];         // para la flecha de compra 
      double buf_sell[1];        // para la flecha de venta
      
      double buf_buy_target[1];  // para el objetivo de compra
      double buf_sell_target[1]; // para el objetivo de venta     
      
      if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
      if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
      if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
      if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

      // hay una flecha y en esta barra no se ha abierto posición buy
      if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
         // stop-loss y take-profit
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_buy_target[0],_Digits);
            double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
         }
         // apertura
         if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
         // "recordamos" la hora de la última apertura
         LastBuyTime=tm[0];
      }
      
      // hay una flecha y en esta barra no se ha abierto posición sell
      if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
         // stop-loss y take-profit
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_sell_target[0],_Digits);
            double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
         }
         // apertura
         if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
         // "recordamos" la hora de la última apertura
         LastSellTime=tm[0];        
      }      
      
      lt=tm[0];
   }
}

En los anexos, el experto se encuentra en el archivo eWolfeWaves. 

Pongamos a prueba el experto obtenido. Si tras realizar la simulación colocamos en el gráfico un indicador, se puede ver que la entrada se realiza en cada flecha, independientemente de la existencia de posiciones abiertas (fig. 5).


Fig. 5. El experto realiza la entrada en cada flecha del indicador

Está claro que lo que nos interesa en primer lugar es la efectividad comercial del indicador obtenido. Los resultados de la simulación del experto con los ajustes por defecto en toda la historia EURUSD H1 se muestran el la fig. 6.


Fig. 6. Resultados de la simulación del experto en toda la historia EURUSD H1

Justo al principio del intervalo se puede ver un fracaso significativo, que quizá esté relacionado con la baja calidad de la historia en la etapa inicial. Pero incluso si esto no es así, después de dicho intervalo, aproximadamente a partir los datos de 1991, comienza un periodo de crecimiento estable. En general, los resultados de la simulación son positivos, y eso sin optimizaciones ni comprobaciones adicionales.

Unos cuantos consejos más del libro de Bill Wolfe

Además de las reglas de identificación de las ondas, Bill Wolfe en su libro da algunos consejos que él llama notas psicológicas y técnicas. Una de las notas técnicas más importantes está relacionada con el seguimiento del volumen de ticks: en los puntos de viraje se puede observar su descenso, lo que puede indicar un viraje. El segundo consejo consiste en hacer un seguimiento de las líneas de tendencia: Los movimientos de las ondas detectados por Bill Wolfe con frecuencia surgen tras la ruptura de la tendencia, en otras palabras, después de romper la línea de tendencia. Es decir, las ondas que aparecen tras la ruptura de tendencia pueden ser más fiables. El tercer consejo es prestar atención a la línea 1-4, especialmente el punto 4, y salir si se da cualquier circunstancia imprevista: en el caso de una onda inversa, de una subida alta del volumen o en el caso de tomar rápidamente un buen beneficio.

Conclusión

Los resultados positivos en la simulación del experto (incluso con los ajustes por defecto) muestran que el método de análisis gráfico estudiado en el artículo es ciertamente efectivo y despierta el interés por una posterior investigación.

Es posible que alguien desee perfeccionar el indicador. En este momento, los parámetros externos del indicador tienen tres coeficientes-variables: K1, K2, K3. Ahora el coeficiente K1 se usa para comrpobar la posición del punto 3 con respecto al punto 1 y del punto 4 con respecto al punto 1. Es posible que para estas comprobaciones sea mejor utilizar coeficientes individuales. Por otra parte, el aumento del número de parámetros complica la optimización del sistema y eleva el riesgo de que se ajusten los resultados en lugar de optimizarlos. Quizá sea mejor unir los coeficientes K1 y K2. Esto hará la configuración más comprensible y sencilla. Por otra parte, puede que sea mejor tener solo un coeficiente. El código del indicador está dividido por funciones de una forma más o menos clara, lo que facilita su perfeccionamiento. Cualquiera que lo desee, podrá experimentar con sus modificaciones de por sí mismo.  

Aparte de usar el indicador para la búsqueda de las ondas de Wolfe, es posible también utilizarlo para crear otros indicadores diseñados para buscar cualquier otra figura de Zigzag. Basta con cambiar el código de las funciones CheckUp y CheckDn. Lo más importante es que se ha solucionado la cuestión del acceso a los valores de los picos de Zigzag.

Quiero llamar la atención de los lectores sobre un truco con las variables CurCount, PreCount y LastTime. Este recurso se ha pensado solo para resolver tareas muy concretas que surgen al escribir indicadores para este artículo. Al desarrollar indicadores, se hace necesario con mucha frecuencia el uso de búferes adicionales para los valores auxiliares, obtenidos como resultado de los cálculos intermedios. En cada barra el valor se desplaza desde el elemento anterior del búfer al actual, y cambia muy rara vez. En los cálculos se usa el valor de un elemento, y se utiliza para ello un búfer completo. El uso del método con dos variables permite reducir significativamente el volumen de memoria operativa utilizado por el indicador.

Archivos adjuntos

En los anexos se encuentran los archivos de los indicadores y del asesor creados en este artículo. Los archivos están ubicados en las carpetas de la misma forma en que deberán estar ubicados en las carpetas del terminal. En total, en los anexos se encuentran los siguientes archivos:

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

Para que todo ello funcione, se necesitarán los archivos adicionales del artículo "Zigzag universal":

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

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

Archivos adjuntos |
MQL5.zip (8.67 KB)
mrvg
mrvg | 27 may. 2017 en 23:52

Excellent article, very didactic.

On the other hand, I can not run the expert advisor "eWolfeWave.ex5", it seems to load without errors, but does not open operations.

Recetas MQL5 - Creando el búfer circular para calcular rápidamente los indicadores en la ventana móvil Recetas MQL5 - Creando el búfer circular para calcular rápidamente los indicadores en la ventana móvil

El búfer circular es el modo más simple y al mismo tiempo más eficaz en la organización de datos para los cálculos en una ventana móvil. En este artículo se describe la estructura de este algoritmo, y se muestra cómo se puede hacer a través de él que el cálculo en la ventana móvil sea un proceso simple y eficaz.

Trading usando canales de Donchian Trading usando canales de Donchian

En este artículo se desarrollan y se prueban varias estrategias a base del canal de Donchian con aplicación de diferentes filtros de indicador. Se realiza el estudio y el análisis comparativo de su trabajo.

Cómo crear documentación usando los códigos fuente MQL5 Cómo crear documentación usando los códigos fuente MQL5

En el artículo se analiza la creación de documentación para el código en MQL5, comenzando por la automatización de la colocación de los tags necesarios. A continuación, se describe el trabajo con el programa Doxygen, su correcta configuración y la obtención de resultados en diferentes formatos: en html, en HtmlHelp y en PDF.

Análisis de los gráficos del Balance/Equidad usando los símbolos y ORDER_MAGIC de los Asesores Expertos Análisis de los gráficos del Balance/Equidad usando los símbolos y ORDER_MAGIC de los Asesores Expertos

Cuando la cobertura (hedging) fue introducida en MetaTrader 5, apareció una perfecta oportunidad de negociar simultáneamente con varios Asesores Expertos en la misma cuenta. Además, puede surgir la situación cuando una estrategia es rentable, mientras que la otra trae pérdidas, y al final el gráfico del beneficio baila alrededor de cero. En este caso, es útil construir los gráficos del Balance y la Equidad para cada estrategia comercial por separado.