Construimos el indicador Zigzag usando osciladores. Ejemplo de ejecución de la tarea técnica

24 mayo 2018, 12:51
Dmitry Fedoseev
0
3 195

Contenido

Introducción

En el artículo «Cómo crear una Tarea Técnica al encargar un indicador», se muestra el ejemplo de la Tarea Técnica para crear el indicador ZigZag usando diferentes oscilador. En este artículo, demostraré paso a paso cómo ejecutarla.

Indicador ZigZag a base de osciladores

No voy a exponer aquí el texto completo de la tarea, por eso, antes de continuar leyendo, estudie la tarea siguiendo el enlace arriba indicado.  

Análisis general de la tarea

Los requerimientos principales para el indicador en desarrollo se revelan con la primera lectura.

  1. El desarrollo se realiza paso a paso.
  2. Es necesario asegurar la velocidad de operación máxima posible del indicador.
  3. El indicador tiene interfaz gráfica.

Algoritmo del Zigzag. El algoritmo de la construcción del ZigZag se diferencia del clásico.

  1. El ZigZag no cambia su dirección cunado se forma el extremo local, sino cuando el valor del oscilador sale fuera del nivel de sobrecompra o sobreventa. 
  2. El ZigZag se construye en el gráfico del precio. Por tanto, el nuevo extremo se define a base de los datos de precios.

Entonces, habrá que prestar atención en algunas particularidades que se desprenden de ello. 

  1. El valor máximo/mínimo del indicador puede no corresponder al valor máximo/mínimo del precio. Por eso, cuando el ZigZag cambia de dirección, habrá que comprobar si se ha formado un nuevo máximo/mínimo del precio un poco antes de que el oscilador haya entrado en la zona de sobrecompra/sobreventa (Fig. 1).



    Fig. 1 La salida de WPR en la zona de sobrecompra ha sido en la barra marcada con la flecha 1,
    sin embargo, el nuevo segmento del ZigZag debe dibujarse hasta la barra marcada con la flecha 2.

  2. Puesto que el cambio de la dirección del ZigZag se determina según del oscilador, su valor va a cambiar a la medida de la formación de la barra. Es decir, el ZigZag puede cambiar la dirección, pero luego, a la medida de la formación de la barra, se puede cancelar el cambio de la dirección. Es necesario asegurar el trabajo correcto del indicador en estas situaciones.
  3. Puesto que el nuevo extremo se determina por el gráfico del precio (según los precios high/low), la formación de un nuevo máximo/mínimo no puede ser cancelada a la medida de la formación de la barra. No obstante, el ZigZag puede dar la vuelta en la barra con un nuevo extremo. En este caso, el nuevo máximo /mínimo se cancela (Fig. 2).


     
    Fig. 2. 1 — el pico del ZigZag se encuentra en el máximo de precios que se crea por la barra en formación.
    2 — el ZigZag da la vuelta y el máximo registrado antes se cancela

    Desde luego, se puede discutir sobre esta situación, es que en MetaTrader 5 hay un estilo de dibujado Color ZigZag. Nos permitiría trazar un segmento del ZigZag verticalmente sin tener que mover su pico más a la izquierda, al máximo determinado anteriormente. Sin embargo, usando este dibujado, no podremos colorear dos segmentos del ZigZag (el vertical y el inclinado que está al lado) independientemente uno de otro. Además, esta manera de dibujar el ZigZag no es muy común, y si fuera necesario, habría que indicar eso en la tarea. O sea, por defecto, se elige la variante más habitual.

Visualización. Visualización del ZigZag también tiene sus particularidades.

  1. Aparte de visualizar el propio indicador, en el gráfico de precios se encuentran los puntos de color que deben marcar las barras en las que el indicador ha entrado en la zona de sobrecompra (puntos amarillos en el nivel high de la barra) y en la zona de sobreventa (puntos verdes en el nivel low de la barra). 
  2. El patrón se detecta basándose en la posición mutua de los picos y los valles del ZigZag. La sección del ZigZag que compone el patrón debe ser coloreado en otro color. Tal vez, este coloreado supone la mayor complejidad. En primer lugar, no sólo hay que marcar la barra en la que ha sido detectado el patrón, sino también recolorear varios segmentos del ZigZag en el historial. En segundo lugar, la reversa del ZigZag y el nuevo extremo pueden ser cancelados a la medida de la formación de la barra. Por eso, antes de hacer cálculos con la barra, hay que limpiar la sección del ZigZag en la que hemos podido marcar el patrón (devolverle el color neutral).  Y en tercer lugar, los patrones (incluso, de direcciones opuestas) pueden solaparse. Por eso, al limpiar y colorear una sección del ZigZag, no se puede alterar el coloreado del patrón encontrado antes (Fig. 3).



    Fig. 3. Patrón solapados

    Vamos a analizar la sección del ZigZag de la imagen 3. Los segmentos de 1 a 4 componen un patrón de la tendencia alcista. Entonces, el segmento 1 tiene que ser azul. Pero antes, ha formado parte del patrón bajista, por eso tiene el colo rojo. Cuando aparece el segmento 6, se forma otro patrón alcista (segmentos de 3 a 6). Puesto que antes de cada cálculo de la barra, es necesario devolver el color original a la sección del ZigZag, en este caso, hay que limpiar sólo los segmentos 5 y 6, ya que los segmentos 3 y 4 ya pertenecen a otro patrón.

    Es posible otra opción de coloreo: cambiar el color de todos los segmentos de cada nuevo patrón. Pero en este caso, el indicador será menos informativo en el historial. Por eso, ha sido elegida la primera opción, aunque es mucho más complicada.

Interfaz gráfica. Los requerimientos para la interfaz gráfica son relativamente simples. El conjunto de los controles del formulario es constante, no hace falta cambiarlo en función del oscilador seleccionado. Se utilizan sólo dos parámetros numéricos (niveles de sobrecompra/sobreventa), y su conjunto es idéntico para todos los osciladores.

Una vez aclaradas todas las particularidades de la tarea, podemos comenzar a diseñar nuestro indicador. Al final de cada paso de la creación del indicador, se indica el nombre correspondiente del archivo adjunto. Si a partir de este momento le surgen dificultades con la comprensión del orden de adición del código, se recomienda abrir el archivo de la etapa correspondiente en el editor y consultar con él.

Paso 1 — construir el ZigZag

Creamos un nuevo indicador de usuario con el nombre OscZigZagStep1 en el editor MetaEditor. Con el fin de marcar un lugar para las variables en el código, añadimos una variable externa. En la ventana de los manejadores de eventos, seleccionamos la primera opción, OnCalculate(...,open,high,low,close), otros manejadores no son necesarios. Creamos dos búferes en la ventana de la visualización de los parámetros. Al primer búfer le asignamos el nombre "HighLow", tipo Color Arrow y dos colores: Golg y LimeGreen. El segundo búfer recibe el nombre "ZigZag", tipo Color Section y tres colores: Gray, CornflowerBlue y Red (Fig. 4).


Fig. 4. Selección de parámetros de visualización en la ventana del Asistente de creación del indicador

Dado que los puntos de color están relacionados con las barras, sería más lógico primero dibujar los puntos (más cerca de las barras), y luego dibujar el ZigZag. Por eso, los búferes se encuentran en este orden.

Al pulsar el botón «Finalizar» en el editor, se abrirá el archivo del indicador. Primero, ajustaremos en él los valores de las propiedades indicator_color1, es decir, eliminaremos las instancias de color sobrantes. La cadena con la propiedad indicator_color1 tiene que ser así:

#property indicator_color1  clrGold,clrLimeGreen

De la misma manera, hay que ajustar un poco la propiedad indicator_color2 (dejar tres colores).

Buscamos la cadena con el parámetro externo creado automáticamente:

input int      Input1;

La eliminamos, y declaramos la variable para los parámetros del indicador WPR en vez de ella:

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

La variable para el manejador (handle) se declara un poco más abajo:

int h;

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

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

Liberamos el handle en la función OnDeinit():

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

Al usar el Asistente para la creación del indicador, hemos creado los búferes visualizados, pero necesitaremos también los búferes auxiliares. Por ejemplo, en este momento necesitamos el búfer para los valores del oscilador. Aumentamos la propiedad indicator_buffers en uno (ponemos el valor 5 en vez de 4):

#property indicator_buffers 5

Añadimos otro array (para el búfer con los valores del oscilador) al lugar donde ya tenemos declarados los arrays para los búferes:

double         wpr[];

Indicamos en la función OnInit() que este array se usa como búfer de indicador para los cálculos intermedios. El código se añade al final de la función OnInit():

SetIndexBuffer(4,wpr,INDICATOR_CALCULATIONS); 

Pasamos a la función OnCalculate(), escribimos el código estándar para calcular el rango del cálculo de las barras y el copiado de los valores del oscilador wpr al búfer:

int start;

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

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

Ahora podemos escribir el ciclo de indicadores estándar y dibujar los puntos ahí donde el oscilador entra en las zonas de sobrecompra/sobreventa:

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

En esta fase, se puede adjuntar el indicador al gráfico. También adjuntamos el WPR estándar y nos cercioramos de la corrección del trabajo ejecutado (Fig. 5).


Fig. 5. Visualización de las zonas de sobrecompra/sobreventa en el gráfico del precio

Continuamos la creación del ZigZag. Necesitaremos varios búferes auxiliares: uno, para la dirección actual del indicador, y otros dos, para los índices de las barras que indican en el último pico y valle del ZigZag:

double         dir[]; // para la dirección
double         lhb[]; // índice de la barra del último pico
double         llb[]; // índice de la barra del último valle

Como añadimos tres búferes, es necesario aumentar la propiedad que determina el número de los búferes del indicador:

#property indicator_buffers 8

En la función OnInit(), aplicamos la función SetIndexBuffer() a los arrays recién declarados:

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

Este código se encuentra justamente después de la última llamada a la función SetIndexBuffer(), que ya se encuentra en la función OnInit().

¡Atención, una cosa importante! Para que el búfer Color Section funcione de forma correcta, hay que indicar el valor vacío 0 para él. Si no, veremos estas extrañas líneas en el gráfico, en vez del ZigZag:


Fig. 6. Si no indicamos el valor vacío para el búfer tipo Color Section, la visualización del ZigZag será incorrecta

Para indicar el valor vacío, insertamos la siguiente cadena al final de la función OnInit(), antes de la cadena return:

PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); 

Fíjese en que el valor del primer parámetro es 1. En este caso, es el índice en el grupo de los búferes visualizados, es decir, 1 corresponde al búfer ZigZagBuffer[].

Tenemos todo listo en la función OnInit().  Ahora, volvemos a pasar a la función OnCalculate() y continuamos escribiendo el código en el ciclo de indicadores estándar.

Al principio del ciclo de indicadores, tras la primera cadena con el vaciado del búfer HighLowBudder, movemos los datos por los búferes auxiliares:

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

En nuestro código donde se determina la entrada del oscilador en las zonas de sobrecompra/sobreventa, añadimos el ajuste de la dirección al búfer dir[]:

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

Ahora, lo más interesante en el primer paso es la construcción del ZigZag. La dirección del ZigZag está determinada, en el búfer dir[] se encuentra el valor 1 para la dirección de arriba y -1 para la dirección de abajo. Además, es necesario determinar en qué barras se cambia la dirección exactamente. El siguiente código —dividido en 4 ramas— servirá de base para la construcción del ZigZag:

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // cambio de la dirección de abajo arriba
   }
   else{
      // continuación del movimiento hacia arriba
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){ 
      // cambio de la dirección de arriba abajo
   }
   else{
      // continuación del movimiento hacia abajo
   }      
}

Analizaremos detalladamente el cambio de la dirección del ZigZag de abajo arriba y la continuación hacia arriba. Otras dos ramas serán simétricas.

Cambio de la dirección hacia arriba

1. Buscamos el valor máximo del precio en el rango de las barras desde el último valle y hasta la barra en el cálculo (la barra de los valles no entra en el rango):

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // cambio de la dirección de abajo arriba
      // búsqueda del máximo
      int hb=i;
      for(int j=i;j>llb[i];j--){
         if(high[j]>high[hb]){
            hb=j;
         }
      }
      //...
   }
   else{
      // continuación del movimiento hacia arriba
   }      
}

2. Colocamos el punto del ZigZag en la barra encontrada, indicamos el índice de esta barra en el búfer lhb[], y establecemos el color neutral a través del búfer ZigZagColor: 

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

3. Durante el recálculo de esta barra, puede resultar que el valor del oscilador ha cambiado y el punto no debe estar ahí. Entonces, hay que eliminarlo. Habitualmente, eso se hace con el vaciado del búfer, al principio del ciclo de indicadores:

ZigZagBuffer[i]=0;

Pero en este caso, el pico del ZigZag en formación se dista de la barra calculada a un número desconocido de las barras (Fig. 1). Por eso, es necesario guardar el índice de la barra en la que se encuentra el pico, y la hora de la barra en cálculo:

NewDotTime=time[i];
NewDotBar=hb;

Las variables NewDotTime y NewDotBar están declaradas a nivel global del indicador.

4. Al principio del ciclo de indicador, comprobamos el valor de la variable NewDotTime respecto a la correspondencia a la barra en cálculo. Si hay correspondencia, quitamos el nuevo punto del ZigZag:

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

Movimiento hacia arriba

Vamos a analizar la sección del código que determina la continuación del movimiento hacia arriba. Si el precio high de la barra consecutiva supera el valor del ZigZag registrado anteriormente, eliminamos el punto de antes y colocamos el nuevo:

if(high[i]>ZigZagBuffer[(int)lhb[i]]){ 
   // quitar el punto de antes
   ZigZagBuffer[(int)lhb[i]]=0;
   // colocar el punto nuevo
   ZigZagBuffer[i]=high[i];
   ZigZagColors[i]=0;
   lhb[i]=i;
}

Al principio del ciclo de indicador, antes de recalcular la barra, hay que devolver el indicador en el estado original, es decir devolver el punto eliminado en su sitio:

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

Para que no haya errores de la salida fuera del array al principio del trabajo del indicador, es necesario inicializar los elementos iniciales de los búferes lhb[] y llb[] con valores nulos. Además, hay que poner a cero las variables NewDotTime y NewDotBar, eso se hace durante el cómputo del rango del cálculo:

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

Pues aquí, terminamos la primera etapa del diseño del indicador. En esta fase, el archivo adjunto del indicador tiene el nombre OscZigZagStep1.mq5.

Paso 2 — detección del patrón y coloreado

Para detectar un patrón, hay que confrontar 5 picos del ZigZag. Para que el indicador trabaje rápido (es la exigencia principal de la Tarea Técnica), no es deseable buscar cada vez estos picos repasando en el ciclo el indicador entero. Va a ser mejor, a medida que aparecen nuevos picos, guardarlos en un array separado, lo que asegurará el acceso directo y rápido a ellos. 

Vamos a guardar los datos sobre los picos del ZigZag en el array de las estructuras. Esta estructura debe contener los campos para el índice de la barra, valor, dirección y un campo más tipo bool. Va a guardar el valor true si el pico es el último dentro del patrón (para limitar el coloreado del ZigZag hasta el patrón detectado anteriormente). Describimos la estructura y declaramos el array:

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

SZZDot ZZDot[];

Luego, añadimos la llamada a la función AddZZDot() al final de cada una de las cuatro secciones del código de construcción del ZigZag. Ella va a añadir nuevos picos del ZigZag al array ZZDot[]:

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

La función AddZdot() recibe cuatro parámetros: dirección, valor, índice de la barra con el pico, índice de la barra en cálculo (analizaremos esta función más tarde). Para el número de los picos encontrados (elementos ocupados en el array AADot[]) usamos el búfer de indicador cnt[]. Declaramos el array cnt[]:

double         cnt[];

Llamamos a la función SetIndexBuffer() dentro de la función OnInit():

SetIndexBuffer(8,cnt,INDICATOR_CALCULATIONS);  

Cambiamos el valor de la propiedad que determina el número de los búferes:  

#property indicator_buffers 9

Al principio del ciclo de indicador, desplazamos el último valor por el búfer:

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

Más arriba ya hemos dicho que el giro del ZigZag detectado al calcular la barra, puede desaparecer al volver a calcular la misma barra. Por eso, es necesario eliminar el pico guardado en el array. Pero esta eliminación no se hace con la reducción del array, sino con la disminución del contador que calcula el número de elementos ocupados dentro del array (búfer cnt[]). Eso aumenta considerablemente la velocidad de operación del indicador.

Vamos a analizar la función AddZdot():

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

   if(c==0){ 
      // al iniciar el indicador o durante su recálculo completo
      ArrayResize(ZZDot,1024);
      ZZDot[c].dir=d;
      ZZDot[c].val=v;
      ZZDot[c].bar=b;
      ZZDot[c].pat=false;
      cnt[i]=1;
   }
   else{
      if(ZZDot[c-1].dir==d){
         // actualización del pico de la misma dirección
         ZZDot[c-1].val=v;
         ZZDot[c-1].bar=b;         
      }
      else{
         // añadir pico nuevo
         // aumento del array con los bloques de 1024 elementos, en caso de la necesidad
         if(c>=ArraySize(ZZDot)){ 
            ArrayResize(ZZDot,c+1024);
         }
         // añadir pico nuevo
         ZZDot[c].dir=d;
         ZZDot[c].val=v;
         ZZDot[c].bar=b;
         ZZDot[c].pat=false;
         cnt[i]=c+1;
      }
   }
}

Al iniciar el indicador o al recálcularlo totalmente, al array se le establece el tamaño 1024, a su elemento inicial se le asignan los parámetros del pico, y el contador del número de los picos se aumenta en 1. Durante las llamadas posteriores a la función, se comprueba la dirección del último pico en el array. Si corresponde a los parámetros con los que ha sido invocada la función, los datos sobre el último pico se actualizan. Si la dirección es opuesta, se añade un pico nuevo. 

Antes, durante el análisis de la tarea, ya he explicado que cuando el ZigZag da la vuelta, el último pico de la dirección opuesta puede ser pasado a la barra más temprana (Fig. 2). Por eso, antes de ejecutar el código principal del ZigZag, es necesario establecer el valor del pico conocido de antemano para el último elemento ocupado del array ZZDot. Eso se hace al principio del ciclo de indicador:

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

Ahora, si se detecta un nuevo pico en la barra calculada, su valor será actualizado en el array ZZDot; y si se forma un giro, se queda el valor del pico conocido de antes.

Antes del primer cálculo del indicador y durante la ejecución de su recálculo total, hace falta inicializar el elemento inicial del array cnt[]:

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

Disponiendo de los datos sobre todos los picos del ZigZag y teniendo un acceso fácil a ellos, procederemos al reconocimiento del patrón y su coloreado. Es posible, si hay por lo menos 5 picos del ZigZag:

if(cnt[i]>=5)

Calculamos el índice del último elemento en el array de los picos:

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

Indicaremos que el patrón no se detecta en este pico . Eso es necesario para devolver el color neutral para el ZigZag:

ZZDot[li].pat=false;

Devolvemos el color original para una parte del ZigZag:

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

¡Nótese! En cuanto se encuentre un pico con patrón, el ciclo será terminado.

Comprobamos las condiciones del patrón:

if(ZZDot[li].dir==1){ // вверх
   if(
      ZZDot[li].val>ZZDot[li-2].val && 
      ZZDot[li-2].val>ZZDot[li-4].val && 
      ZZDot[li-1].val>ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 
      // coloreado 
   }
}
else{ // abajo
   if( 
      ZZDot[li].val<ZZDot[li-2].val && 
      ZZDot[li-2].val<ZZDot[li-4].val && 
      ZZDot[li-1].val<ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 		
      // coloreado                 
   }            
}

Nos queda escribir el código del coloreado. Parece al código de la limpieza. Para la dirección hacia arriba:  

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

Una pequeña diferencia del código de la limpieza consiste en que la salida del ciclo no se realiza cuando j=0.

Pues, la segunda etapa del diseño del indicador está finalizada. El indicador tendrá la siguiente apariencia: 


Fig. 7. Apariencia del indicador al final del paso №2. 

En esta fase, el archivo adjunto del indicador tiene el nombre OscZigZagStep2.mq5. 

Paso 3 — añadir osciladores

Describimos la enumeración:

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

Declaramos una variable externa para seleccionar el oscilador:

input EIType               Type        =  WPR;

Añadimos los parámetros del resto de osciladores:

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

Declaramos las variables para los niveles:

double max,min;

Seleccionamos el oscilador al principio de la función OnStart:

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

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

En la función OnCalculate(), reemplazamos las variables WPRmax y WPmin por las variables max y min. 

Hemos terminado la tercera fase del diseño del indicador, ahora podemos seleccionar el oscilador en la ventana de propiedades del indicador. En esta fase, el archivo adjunto del indicador tiene el nombre OscZigZagStep3.mq5.

Paso 4 — crear la interfaz gráfica

Para crear la interfaz gráfica, se utiliza la librería IncGUI. Hay una serie de artículos dedicada a esta librería, «Controles gráficos personalizados» (Parte 1, Parte 2, Parte 3). La última versión corregida de la librería (IncGUI_v4.mqh) se encuentra en el archivo adjunto al artículo «Oscilador universal con interfaz gráfica». También se adjunta al presente artículo. Antes de empezar a trabajar con la interfaz gráfica, copie el archivo IncGUI_v4.mqh a la carpeta MQL5/Includes de la carpeta de los datos del terminal.

Vamos a estudiar el proceso de la creación de la interfaz gráfica paso a paso.

Incluir la librería. Haga una copia del indicador OscZigZagStep3 con el nombre OscZigZagStep3 e incluya la librería en él:

#include <IncGUI_v4.mqh>

Clase del formulario. En el archivo IncGUI_v4.mqh se puede encontrar la clase CFormTemplate. Es una especie de la plantilla para crear el formulario. Lo copiamos, insertamos en el archivo del indicador inmediatamente después de incluir la librería, renombramos de CFormTemplate en CForm.

Propiedades del formulario Establecemos las propiedades principales del formulario en el método MainProperties():

m_Name         =  "Form";
m_Width        =  200;
m_Height       =  150;
m_Type         =  2;
m_Caption      =  "ZigZag on Oscillator";
m_Movable      =  true;
m_Resizable    =  true;
m_CloseButton  =  true;
  • Variable m_Name — nombre del formulario (prefijo de todos los objetos gráficos que componen el formulario).
  • Variables m_Width y m_Height — ancho y alto del formulario en píxeles.
  • Variable m_Type — tipo del formulario. Si el valor es 2, en la parte inferior del formulario va a encontrarse el botón de cierre.
  • Variable m_Caption — encabezado del formulario.
  • Variable m_Movable — formulario móvil, en la esquina superior izquierda va a encontrarse el botón para el desplazamiento.
  • Variable m_Resizable — se puede minimizar/maximizar el formulario, el botón correspondiente va a encontrarse en la esquina superior derecha.
  • Variable m_CloseButton — se puede cerrar el formulario, el botón correspondiente va a encontrarse en la esquina superior derecha.

Controles. Creación de controles. El formulario va a tener dos frames. Uno de ellos va a contener un grupo de botones de opción; otro, dos campos de edición. Colocamos el código en la sección public de la clase del formulario:

CFrame m_frm1; // frame 1
CFrame m_frm2; // frame 2 
CRadioGroup m_rg; // grupo de botones de opción      
CInputBox m_txt_max; // campo de edición del nivel superior    
CInputBox m_txt_min; // campo de edición del nivel inferior

Inicialización de los controles Inicializamos los controles en el método OnInitEvent().

Inicialización del primer frame con el ancho/alto 85/97 píxeles, encabezado "Osc Type" y espacio para el encabezado de 44 píxeles:

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

Este frame va a contener un grupo de botones de opción.

El segundo frame con los mismos tamaños, encabezado "Levels" y espacio para el encabezado de 32 píxeles:

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

En este frame, van a ubicarse los campos de edición de los niveles.

Inicialización de los botones de opción:

m_rg.Init();

Añadir los botones de opción al grupo:

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

Inicialización de los campos de edición para introducir el nivel superior e inferior:

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

Ambos campos tienen el ancho de 45 píxeles, permiten la introducción del texto (tercer parámetro -1), uno de ellos tiene la entrada "Max", otro, "Min".

Visualización de los controles. En el método OnShowEvent() llamamos a los métodos Show() de todos los controles e indicamos sus coordenadas en el formulario:

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

Ocultar los controles. Podemos ocultar todos los controles en el método OnHideEvent():

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

Encabezado del formulario. Al seleccionar diferentes oscilador, estaría bueno visualizar sus nombres en el encabezado del formulario, por eso vamos a añadir el método para el cambio del texto del encabezado a la sección public de la clase del formulario.

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

Creación del objeto del formulario. Creamos el objeto de la clase CForm:

CForm form;

Eventos del formulario Para que el formulario y los controles reaccionen a las acciones del usuario, hay que llamar al método Event() desde la función OnChartEvent() del indicador. Dependiendo del tipo del evento, el método devuelve valores diferentes. El valor 1 corresponde al cierre del formulario, en este caso, hay que quitar el indicador del gráfico:

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

Eventos de los controles. El evento del grupo de botones de opción cambia el indicador, los eventos del cambio de los valores en los campos de edición, cambia los niveles de sobrecompra/sobreventa. En ambos casos, el indicador se recalcula totalmente. 

La parte del código en la que se ejecuta la selección del indicador en la función OnInit() la sacaremos a una función separada:

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

Va a invocarse desde la función OnInit() del indicador (al principio) y según un evento de los botones de opción. Justamente después de seleccionar el indicador en la función OnInit() inicializamos el formulario, establecemos los valores para los controles y visualizamos el formulario:

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

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

Procesamos los eventos de los controles en la función OnChartEvent(). Evento de los botones de opción para cambiar el indicador:

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

   EventSetMillisecondTimer(100);
}

Primero, se ejecuta la liberación del handle del indicador a través de la fucnión IndicatorRelease(), luego se selecciona un indicador nuevo, se establecen nuevos valores para los campos de edición y se inicia el temporizador. Necesitamos el temporizador porque durante el recálculo del indicador pueden surgir errores de la actualización de los datos. En estos casos, será necesario repetir el intento del recálculo hasta que se haga con éxito.

Modificación de niveles:

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

Según el evento de los campos de edición, a las variables min y max se les asignan nuevos valores y se inicia el temporizador.  

El indicador se recalcula en la función OnTimer(). Si ha salido con éxito, el temporizador se desconecta y luego el indicador continua trabajando como siempre, con los ticks. Todas las acciones necesarias para el recálculo del indicador se analizan detalladamente en el artículo arriba mencionado «Oscilador universal con interfaz gráfica». Por eso, vamos a considerar aquí sólo las diferencias principales. El oscilador universal se calcula en el método de la clase que no requiere los datos de precios. Pero aquí, tenemos que llamar a la función OnCalculate(), y pasarle los arrays con los precios. Declaramos los arrays:

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

Obtenemos el número de las barras:

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

Para construir el ZigZag, no necesitamos todos los datos de precios, sino tres arrays: time, high, low. Vamos a copiar sólo estos arrays:

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

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

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

Durante la prueba del indicador, se ha revelado un problema: el número de los datos copiados a veces es menor que el número de las barras obtenido por la función Bars(). El tamaño de los búferes de indicador corresponde al valor de la función Bars(). Por eso, para la visualización correcta del indicador, es necesario aumentar los arrays con los datos copiados, pasando los datos al final:

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

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

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

Nos queda llamar a la función OnCalculate():

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

Si la función OnCalculte() ha terminado su trabajo sin errores, desactivamos el temporizador: 

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

El código completo de la función OnTimer(), así como el indicador terminado, se puede encontrar en el archivo adjunto OscZigZagStep4.mq5.

Al colocar el indicador en el gráfico, el formulario con los controles debe aparecer en la esquina superior izquierda (Fig. 8).


Fig. 8. Interfaz gráfica en el paso 4

Conclusión

He demostrado el desarrollo del indicador siguiendo la Tarea Técnica propuesta al pie de la letra. No obstante, la exactitud de la ejecución es sólo una parte del trabajo. En nuestro caso, la Tarea ha sido preparada por un especialista experimentado, que evidentemente está muy bien familiarizado con el terminal, con sus posibilidades y particularidades del trabajo de los indicadores. Pero aún así, sería muy deseable discutir algunos puntos de la tarea con su autor, en particularidad, el coloreado del ZigZag.

Cuando se detecta un patrón, algunas de las secciones anteriores del ZigZag se recolorean, lo que puede inducirnos a error durante la investigación del indicador en el gráfico. El hecho de que el patrón puede seguir dibujándose, complica el análisis visual aún más. En la práctica, el patrón es necesario para tomar una decisión comercial. Puede ser tomada en el momento de la aparición del patrón, o bien, después, pero nunca antes. Por eso, en vez de colorear el ZigZag, se podría proponer el dibujado de las flechas en la barra en la que ha sido detectado el patrón. Otra solución es dibujar las líneas horizontales en una serie de las barras mientras que el patrón existe, pero sólo a partir del momento de su detección hasta el futuro.  

Aparte de eso, en el proceso del desarrollo del indicador, ha sido revelada su particularidad muy inesperada e inevidente, en la que es imposible pensar incluso durante la primera lectura de la tarea, y más aún, durante su composición. Me refiero a algunos casos cuando es necesario eliminar el último máximo/mínimo del ZigZag cuando da la vuelta. Como ya ha sido mencionado en el artículo, se podría usar el búfer Color Zigzag, pero en este caso surgirían las complicaciones durante su coloreado, porque en el conjunto de los búferes Color Zigzag hay dos búferes para los datos y sólo uno, para el color. Si en una barra ambos búferes de los datos tienen valores (caso cuando una línea vertical pasa a lo largo de la barra), el color especificado en el búfer de color se asigna a ambos segmentos del ZigZag. En vez del búfer Color Zigzag, podríamos usar simplemente Zigzag, y colorear los segmentos del ZigZag usando objetos gráficos, o simplemente colocar las flechas o puntos. En general, es necesario leer muy atentamente cualquier tarea, con la discusión previa.

Archivos adjuntos

Todos los archivos están ubicados en las carpetas tal como deben estar colocados en el terminal. Los archivos correspondientes a los pasos de la creación del indicador están en la carpeta MQL5/Indicators: OscZigZagStep1.mq5, OscZigZagStep2.mq5, ОscZigZagStep3.mq5,  OscZigZagStep4.mq5.

En la carpeta MQL5/Includes se encuentra el archivo IncGUI_v4.mqh necesario para el diseño de la interfaz gráfica en el indicador OscZigZagStep4.


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

Archivos adjuntos |
MQL5.zip (87.98 KB)
Aplicando el método de Monte Carlo para optimizar estrategias comerciales Aplicando el método de Monte Carlo para optimizar estrategias comerciales

Antes de iniciar un robot en la cuenta comercial, habitualmente lo probamos y optimizamos usando el historial de las cotizaciones. Pues, aquí surge una pregunta razonable, ¿cómo nos pueden ayudar los resultados anteriores en el historial en el futuro? En este artículo, se muestra la aplicación del método de Monte Carlo para construir sus propios criterios de optimización de las estrategias comerciales. Aparte de eso, se consideran los criterios de la estabilidad del Asesor Experto.

Visualización de los resultados de la optimización según el criterio seleccionado Visualización de los resultados de la optimización según el criterio seleccionado

En este artículo, vamos a continuar el desarrollo de la aplicación MQL para el trabajo con los resultados de la optimización empezado en los artículos anteriores. Esta vez, mostraremos cómo se puede formar la tabla de los mejores resultados después de optimizar los parámetros indicando otro criterio a través de la interfaz gráfica.

Cómo crear un panel gráfico de cualquier nivel de complejidad Cómo crear un panel gráfico de cualquier nivel de complejidad

En el artículo se analiza con detalle cómo crear un panel basado en la clase CAppDialog y cómo añadir al mismo los elementos de control. Asimismo, se describe la estructura del panel y el esquema de herencia de los objetos en este. Se muestra qué es necesario para procesar eventos y cómo estos se distribuyen a los elementos de control subordinados. Se dan ejemplos de cambio de los siguientes parámetros del panel: el tamaño y el color del fondo.

Simulación de patrones de parejas de divisas: Uso y perspectivas para el trading real. Parte IV Simulación de patrones de parejas de divisas: Uso y perspectivas para el trading real. Parte IV

Con este artículo terminamos la serie sobre el trading con las cestas de parejas de divisas. En este artículo, vamos a simular el último patrón y discutir sobre el uso de la metodología completa en el trading real. Han sido consideradas la entrada y la salida del mercado, la búsqueda y el análisis de los patrones, y la aplicación de los indicadores combinados.