Indicador para construir el gráfico de "ejes" (husos)

Dmitriy Zabudskiy | 15 enero, 2016

Introducción

El gráfico de "ejes" se relaciona con los llamados gráficos de volumen de precio. Volumen de precio — es un gráfico que para su construcción usa datos sobre la actividad de uno u otro instrumento en forma de volumen (normalmente, de ticks) y de uno o varios precios. Resulta algo parecido a un perfil de mercado. Vagando por los espacios abiertos de internet, no he logrado reunir mucha información, básicamente, sobre los aspectos de composición del gráfico. De esta forma, se puede decir que este gráfico ha aparecido hace relativamente poco, y que por lo tanto merece nuestra atención.

La información sobre este gráfico la recibí de uno de los lectores del artículo, con la petición de crear un tipo de indicador así. Debido al poco tiempo libre y la gran complejidad de implementación, el desarrollo del indicador se ha alargado más de lo previsto.

El gráfico de "ejes" se parece al de velas japonesas, en él también están presentes los precios de apertura y cierre, los mínimos y los máximos. Sin embargo, como complemento a ello, se usa el precio promedio ponderado según el volumen (Volume Weighted Moving Average — VWMA) y el coeficiente de volumen (Volume Ratio — VR), formando por lo tanto una figura parecida a un huso (ver 1).

Fig. 1. Comparación de la "vela japonesa" con el "eje"

Fig. 1. Comparación de la "vela japonesa" con el "eje"

Como vemos en el dibujo 1, los dos parámetros añadidos (VWMA — el precio promedio ponderado según el volumen y VR — el coeficiente de volumen) solo complementan la vela, formando al mismo tiempo una nueva figura, parecida a la peonza que todos conocemos también desde la infancia. Esto es preciamente el así llamado "eje".

Veamos cómo se forman VR y VWMA. El precio promedio ponderado según el volumen (VWMA) no supone otra cosa que una peculiar media móvil, y se calcula mediante la fórmula (1):

Cálculo de VWMA

donde P es el precio, y V es el volumen. Usando palabras, sonaría más o menos así: "el precio promedio ponderado según el volumen es igual a la suma de todos los productos del precio por el volumen de un periodo indicado, dividido entre la suma de volúmenes del mismo periodo".

El coeficiente de volumen (VR) es algo semejante a la media móvil, pero en el gráfico se representa de otra forma, dado que, en primer lugar, no tiene un valor de precio del diapasón, y en segundo lugar, es el responsable de la actividad del mercado con respecto a los periodos anteriores, por eso es mejor presentarlo, o bien en un gráfico aparte, como volúmenes de ticks, o bien como la anchura de cada "eje". Se calcula con la fórmula (2):

Cálculo de VR

donde V es el volumen. Resulta que: "el coeficiente de volumen es igual al volumen actual, dividido entre la media aritmética de los volúmenes del periodo elegido".

Así, después de todas estas manipulaciones, tenemos el gráfico de "ejes" (fig. 2).

Fig. 2. Gráfico de "ejes"

Fig. 2. Gráfico de "ejes"

Resulta lógico hacerse la siguiente pregunta: "¿por qué en el dibujo 2 los "ejes" no están coloreados en su parte interior, como en el dibujo 1?". Esta cuestión las desvelaremos en el siguiente capítulo — Fundamentos de construcción.


Fundamentos de construcción

Para un indicador de usuario en MQL5 existen siete estilos de construcción (línea, sección, histograma, flecha , área coloreada, barras y velas japonesas), pero ni uno solo de ellos satisface completamente las exigencias de dibujado del gráfico de "ejes". Por lo tanto, es necesario crear un estilo propio. Por desgracia, no existe un constructor interno de estilo, pero sí está disponible una gran cantidad de otras funciones, con la ayuda de las cuales se puede formar un estilo propio, que se distinga de los incorporados, básicamente, por su velocidad, funcionalidad y su calidad de dibujado.

Se ha tomado la decisión de organizar la formación del estilo con la ayuda del objeto "Dibujo". Entre los defectos de esta solución se encuentra, en primer lugar, el uso de un gran volumen de memoria y la relativa complejidad de la construcción, y del primero surgen otros defectos: la velocidad y la estabilidad del funcionamiento. La ventaja del objeto "Dibujo" con respecto a otros objetos, es la posibilidad de limitar la construcción en un espacio determinado, así como la posibilidad de usar la transparencia, una parte del objeto. Precisamente estas ventajas son necesarias a la hora de organizar la construcción del gráfico de "ejes".

La representación visual de un "eje" se muestra en el dibujo 1, además, el cuerpo del "eje" se rellena por completo. Esta construcción es bastante compleja y requiere bastantes recursos para ser implementada a través del objeto "Dibujo". Veamos cómo sucede esto, en el dibujo 3:

Fig. 3. Representación técnica de la construcción "eje" con la ayuda del objeto "Dibujo"

Fig. 3. Representación técnica de la construcción "eje" con la ayuda del objeto "Dibujo"

En el dibujo 3 se muestran las tres posibles variantes de la representación técnica del "eje rellenado", donde:

Así, para construir el primer "eje" (fig. 3, a), lo llamaremos "rombo rollizo", son necesarios cuatro objetos del tipo "Dibujo" (fig. 3, a; partes: I, II, III, IV). Al mismo tiempo, dependiendo del tipo de rombo, de su anchura y altura (es decir, Open, Close, VWMA y VR) son necesarias imágenes a elegir, con diferente ángulo (fig. 3, a; ángulos: x1, x2, x3, x4). La imagen supone un dibujo cuadrado de punto en formato BMP, en el que desde un ángulo sale un haz con un ángulo determinado con respecto a uno de los lados cercanos, que divide el cuadrado en dos zonas: la coloreada y la transparente.

Más abajo discutiremos cómo tiene lugar precisamente el cálculo de un dibujo determinado. Sin embargo, ya se nota que para formar este modelo cuando tenemos diferentes valores de su anchura y altura (es decir, Open, Close, VWMA y VR) se necesitarán 360 dibujos punteados (partiendo de la precisión de construcción en un grado) del cálculo de un color, y si cogemos dos colores, serán ya 720 colores.

Con el segundo "eje" (fig. 3, b) el asunto se pone bastante más complicado, a pesar de que la formación de la figura (la llamaremos "flecha afilada") consta de dos partes. Aquí hay bastantes más combinaciones de ángulos, dado que necesitamos tener en cuenta la distancia entre los precios de apertura y cierre (es decir, Open, Close). No es necesario estudiar la construcción posterior, ya que hay una alternativa (fig.3, c).

En el tercer caso (fig.3, c) la construcción se realiza con cuatro partes, las dos primeras (I y II) son como en el "rombo rollizo", las dos segundas (III y IV) realizan el cierre de las partes sobrantes de las primeras. Con semejante realización, existe la probabilidad de cubrir los "ejes" vecinos, y también se da el anclaje al fondo. En total hay 180 partes, como en el "rombo rollizo", y 180 partes para el cierre.

En general, para implementar un gráfico de "ejes rellenados" harán falta 900 dibujos de punto, teniendo en cuenta que haya un solo fondo de gráfico, lo que, a su vez, consume muchos recursos.

Ahora veremos una variante menos complicada y más rápida de construcción de "ejes" sin rellenar (fig. 2). Adelantándonos un poco, la cantidad de dibujos de punto ha sido de 360 (180 de un color y 180 de otro), independientemente del gráfico.

Fig. 4. Representación técnica de la construcción "eje sin rellanar" con la ayuda del objeto "Dibujo"

Fig. 4. Representación técnica de la construcción "eje sin rellanar" con la ayuda del objeto "Dibujo"

De la misma forma que en la anterior variante, el "eje sin rellanar" se construye a partir de cuatro dibujos, que representan franjas de colores con diferente ángulo (de 0 a 180). Aquí no hay necesidad de construir 360 dibujos de punto, dado que el ángulo cambia dependiendo del punto de anclaje del objeto. Solo hay dos puntos de anclaje (fig. 4, p1 y p2): dos objetos para un punto, dos para el otro.

Aclararé de nuevo por qué se usan aquí menos dibujos de punto. Imagine que en el dibujo 4 (а) hay un rombo simétrico, entonces la parte I se podría sustituir por la parte IV, para ello sería necesario cambiar el punto de anclaje del ángulo superior derecho por el ángulo inferior izquierdo del objeto. En definitiva, hay que preparar en total 180 objetos de un color y cambiar el punto de anclaje, dependiendo del lado de uso.

Ahora, vamos a ver un poco de matemáticas, para ser más exactos, de geometría. Veamos el proceso de cálculo y elección del dibujo para construir un "eje sin rellenar" (fig. 5 y 6).

Fig. 5. Cálculo matemático del "rombo rollizo"

Fig. 5. Cálculo matemático del "rombo rollizo"

En el dibujo 5 se muestra nuestro ya conocido "rombo rollizo" (а) y su parte derecha (b). Todas las distancias marcadas (a, b, c, d) son fáciles de calcular, conociendo Open, Close, VWMA y VR, es decir:

Conociendo los lados a, b, d,, es posible calcular la hipotenusa en el triángulo rectángulo e y f, según las fórmulas 3.1 y 3.3. Por consiguiente, conociendo que en el triángulo rectángulo el cateto, dividido por la hipotenusa, es igual al seno del ángulo opuesto, obtenemos el seno del ángulo x1 y x2 según las fórmulas 3.2 y 3.4. Continuando por el recuadro, o con la ayuda de la calculadora, encontramos los ángulos x1 y x2, y ya a través de x2 calculamos x3. El mismo sistema de construcción tiene la figura "flecha afilada", en el dibujo 6:

Fig. 6. Cálculo matemático de la "flecha afilada"

Fig. 6. Cálculo matemático de la "flecha afilada"

Como ya hemos superado los fundamentos de construcción, analizaremos el código del indicador.


Código del indicador

Antes de escribir el código, ha sido necesario preparar los recursos gráficos del indicador, y para ser más exactos, los dibujos de punto de formato BMP con unas dimensiones de 540 х 540 píxeles con fondo transparente. Los dibujos contienen un haz que proviene del ángulo. En los primeros 89 dibujos, el haz va desde la esquina superior izquierda, cambiando el ángulo desde 1 hasta 89 grados, en los segundos 89 dibujos, el haz va desde la esquina inferior izquierda desde 91 hasta 179 grados (con respecto a la horizontal, desde 1 hasta 89 grados). Los dibujos con los ángulos 0, 90, 180 tienen unas dimensiones de 1 х 540 píxeles y 540 х 1 respectivamente, y no necesitan un fondo transparente.

En total, tenemos 181 dibujos de un color y 181 dibujos de otro (los dibujos 1 y 181 son iguales), lo que nos da 362 dibujos. Los nombres de los archivos han sido elegidos teniendo en cuenta el color de la línea, rojo (primera letra latina "r", del inglés "red") y azul (primera letra latina "b", del inglés "blue"), y teniendo también en cuenta el ángulo en el que está colocada (0 - 180 grados).


Primera parte

La primera parte del código abarca hasta la función OnInit. Vamos a verlo por orden:

//+------------------------------------------------------------------+
//|                                                          SPC.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://login.mql5.com/es/users/aktiniy |
//+------------------------------------------------------------------+
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://login.mql5.com/es/users/aktiniy"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 11
#property indicator_plots 4
//---
#property indicator_label1  "Shadow"
#property indicator_type1   DRAW_COLOR_HISTOGRAM2
#property indicator_color1  clrRed,clrBlue,clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//---
#property indicator_label2  "Open"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//---
#property indicator_label3  "Close"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrBlue
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//---
#property indicator_label4  "VWMA"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrMagenta
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- cargamos los archivos de recursos
#resource "\\Images\\for_SPC\\b0.bmp";
#resource "\\Images\\for_SPC\\b1.bmp";
#resource "\\Images\\for_SPC\\b2.bmp";
#resource "\\Images\\for_SPC\\b3.bmp";
//...
//...
//...
#resource "\\Images\\for_SPC\\b176.bmp";
#resource "\\Images\\for_SPC\\b177.bmp";
#resource "\\Images\\for_SPC\\b178.bmp";
#resource "\\Images\\for_SPC\\b179.bmp";
#resource "\\Images\\for_SPC\\b180.bmp";
#resource "\\Images\\for_SPC\\r0.bmp";
#resource "\\Images\\for_SPC\\r1.bmp";
#resource "\\Images\\for_SPC\\r2.bmp";
#resource "\\Images\\for_SPC\\r3.bmp";
//...
//...
//...
#resource "\\Images\\for_SPC\\r176.bmp";
#resource "\\Images\\for_SPC\\r177.bmp";
#resource "\\Images\\for_SPC\\r178.bmp";
#resource "\\Images\\for_SPC\\r179.bmp";
#resource "\\Images\\for_SPC\\r180.bmp";
//+------------------------------------------------------------------+
//| Type Drawing                                                     |
//+------------------------------------------------------------------+
enum type_drawing
  {
   spindles=0,       // Ejes
   line_histogram=1, // Línea e histograma
  };
//+------------------------------------------------------------------+
//| Type Price                                                       |
//+------------------------------------------------------------------+
enum type_price
  {
   open=0,   // Open
   high=1,   // High
   low=2,    // Low
   close=3,  // Close
   middle=4, // Middle
  };
//--- parámetros de entrada
input long         magic_numb=65758473787389; // Número mágico
input type_drawing type_draw=0;               // Tipo de dibujado del indicador
input int          period_VR=10;              // Periodo de formación del coeficiente de volumen
input int          correct_VR=4;              // Número para la corrección del coeficiente de volumen
input int          period_VWMA=10;            // Periodo de formación del precio promedio ponderado según el volumen
input int          spindles_num=1000;         // Cantidad de ejes
input type_price   type_price_VWMA=0;         // Tipo de precio para la construcción del precio promedio ponderado según el volumen
                                              // open=0; high=1; low=2; close=3; middle=4
//--- variables de salida
int ext_period_VR=0;
int ext_correct_VR;
int ext_period_VWMA=0;
int ext_spin_num=0;
int long_period=0;
//--- parámetros variables del gráfico
double win_price_max_ext=0; // valor máximo del gráfico
double win_price_min_ext=0; // valor mínimo del gráfico
double win_height_pixels_ext=0; // altura en píxeles
double win_width_pixels_ext=0;  // anchura en píxeles
double win_bars_ext=0; // anchura en barras
//--- variables auxiliares
int end_bar;
//--- búfers de indicador
double         Buff_up[];   // búfer de los puntos superiores del histograma
double         Buff_down[]; // búfer de los puntos inferiores del histograma
double         Buff_color_up_down[]; // búfer de color del histograma
double         Buff_open_ext[];  // búfer de salida de los precios de apertura
double         Buff_close_ext[]; // búfer de salida de los precios de cierre
double         Buff_VWMA_ext[];  // búfer de salida del precio promedio ponderado según el volumen
double         Buff_open[];  // búfer del precio de apertura
double         Buff_close[]; // búfer del precio de cierre
double         Buff_VWMA[];  // búfer del precio promedio ponderado según el volumen
double         Buff_VR[];   // búfer del coeficiente de volumen
double         Buff_time[]; // búfer de la hora de apertura de la barra

Aquí se puede ver que hay pocos parámetros a introducir:

Las variables de salida son necesarias para comprobar que los parámetros introducidos y su ajuste sean correctos. Las variables del parámetro del gráfico realizan un seguimiento de los cambios en la ventana del indicador, posteriormente veremos para qué se necesita esto.

La primera parte del código termina con la declaración de los búfers de indicador, aquí son 11.


Función OnInit

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- comprobar las variables de entrada
   if(period_VR<=0)
     {
      ext_period_VR=10; // cambio del valor de la variable
      Alert("El periodo de formación del coeficiente de volumen se ha introducido de forma incorrecta y ha sido modificado.");
     }
   else ext_period_VR=period_VR;
   if(correct_VR<=0)
     {
      ext_correct_VR=10; // cambio del valor de la variable
      Alert("El número para la corrección del coeficiente de volumen se ha introducido de forma incorrecta y ha sido modificado.");
     }
   else ext_correct_VR=correct_VR;
   if(period_VWMA<=0)
     {
      ext_period_VWMA=10; // cambio del valor de la variable
      Alert("El periodo de formación del precio promedio ponderado según el volumen se ha introducido de forma incorrecta y ha sido modificado.");
     }
   else ext_period_VWMA=period_VWMA;
   if(spindles_num<=0)
     {
      ext_spin_num=10; // cambio del valor de la variable
      Alert("La cantidad de ejes se ha introducido de forma incorrecta y ha sido modificada.");
     }
   else ext_spin_num=spindles_num;
//--- búsqueda del periodo más largo para la construcción del gráfico
   if(ext_period_VR>ext_period_VWMA)long_period=ext_period_VR;
   else long_period=ext_period_VWMA;
//--- indicator buffers mapping
   SetIndexBuffer(0,Buff_up,INDICATOR_DATA);
   SetIndexBuffer(1,Buff_down,INDICATOR_DATA);
   SetIndexBuffer(2,Buff_color_up_down,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Buff_open_ext,INDICATOR_DATA);
   SetIndexBuffer(4,Buff_close_ext,INDICATOR_DATA);
   SetIndexBuffer(5,Buff_VWMA_ext,INDICATOR_DATA);
   SetIndexBuffer(6,Buff_open,INDICATOR_CALCULATIONS);
   SetIndexBuffer(7,Buff_close,INDICATOR_CALCULATIONS);
   SetIndexBuffer(8,Buff_VWMA,INDICATOR_CALCULATIONS);
   SetIndexBuffer(9,Buff_VR,INDICATOR_CALCULATIONS);
   SetIndexBuffer(10,Buff_time,INDICATOR_CALCULATIONS);
//--- establecer el nombre del indicador
   IndicatorSetString(INDICATOR_SHORTNAME,"SPC "+IntegerToString(magic_numb));
   PlotIndexSetString(0,PLOT_LABEL,"SPC");
//--- establecer la precisión
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//--- establecer la primera barra desde la que se comenzará a dibujar el indicador
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,long_period+1);
//--- prohibimos la muestra de los resultados de los valores actuales para el indicador
   PlotIndexSetInteger(0,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(2,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(3,PLOT_SHOW_DATA,false);
//--- establecemos los valores que no se mostrarán
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0);
//--- creación de los objetos a usar
   if(type_draw==0)
     {
      for(int x=0; x<=ext_spin_num; x++)
        {
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
         ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4",OBJ_BITMAP,ChartWindowFind(),__DATE__,0);
        }
     }
//---
   return(INIT_SUCCEEDED);
  }

Aquí comprobamos la corrección de los parámetros introducidos, y en caso necesario, corregimos usando la variable anunciada anteriormente (variables de salida). Buscamos cuál de los periodos usados es mayor, inicializamos los búfers y ajustamos el aspecto exterior del indicador. Con una matriz pequeña (con el parámetro limitado "cantidad de ejes") creamos los objetos gráficos para trabajar posteriormente con ellos.


Función OnChartEvent

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- evento de pulsación de tecla
   if(id==CHARTEVENT_KEYDOWN)
     {
      if(lparam==82)
        {
         if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0)// comprobamos la presencia de datos en el gráfico
           {
            if(func_check_chart()==true)
              {
               if(type_draw==0)func_drawing(true,ext_spin_num,end_bar);
              }
           }
        }
     }
  }

Esta función liga la actualización del gráfico al botón "R" (código 82), para ser más exactos, su redibujado. Sirve para corregir el gráfico (redibujar), si el tamaño de la ventana del indicador ha cambiado. Esto se debe a que las imágenes se estiran en el caso de que cambie el tamaño de la ventana. Como es natural, el gráfico se redibuja también en el caso de que surja un evento de cambio de precio, pero a veces es necesario actualizar rápidamente la construcción, para lo que necesitamos precisamente esta función.

La propia función consta por completo de operadores if-else e incluye una función de comprobación del cambio de las dimensiones de la ventana del indicador (func_check_chart), así como de la función de formación (dibujado) del gráfico (func_drawing).


Función de comprobación de la ventana del indicador

//+------------------------------------------------------------------+
//| Func Check Chart                                                 |
//+------------------------------------------------------------------+
bool func_check_chart()
  {
//--- variable de respuesta
   bool x=false;
//--- averiguamos el tamaño del gráfico
   int win=ChartWindowFind(); // definimos la subventana, puesto que el indicador funciona en una ventana parte
   double win_price_max=ChartGetDouble(0,CHART_PRICE_MAX,win); // valor máximo del gráfico
   double win_price_min=ChartGetDouble(0,CHART_PRICE_MIN,win); // valor mínimo del gráfico
   double win_height_pixels=(double)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,win); // altura en píxeles
   double win_width_pixels=(double)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,win); // anchura en píxeles
   double win_bars=(double)ChartGetInteger(0,CHART_WIDTH_IN_BARS,win); // anchura en barras

//--- comprobamos si han cambiado los valores
   int factor=(int)MathPow(10,_Digits);// establecemos el factor de conversión para el paso del tipo double a int
   if(int(win_price_max*factor)!=int(win_price_max_ext*factor))
     {
      win_price_max_ext=win_price_max;
      x=true;
     }
   if(int(win_price_min*factor)!=int(win_price_min_ext*factor))
     {
      win_price_min_ext=win_price_min;
      x=true;
     }
   if(int(win_height_pixels*factor)!=int(win_height_pixels_ext*factor))
     {
      win_height_pixels_ext=win_height_pixels;
      x=true;
     }
   if(int(win_width_pixels*factor)!=int(win_width_pixels_ext*factor))
     {
      win_width_pixels_ext=win_width_pixels;
      x=true;
     }
   if(int(win_bars*factor)!=int(win_bars_ext*factor))
     {
      win_bars_ext=win_bars;
      x=true;
     }
   if(func_new_bar(PERIOD_CURRENT)==true)
     {
      x=true;
     }
   return(x);
  }

La función sirve como señal para cambiar la ventana del indicador. Primero averiguamos los parámetros actuales de la ventana (altura y anchura en los valores de precio y píxeles) con la ayuda de las funciones para trabajar con los gráficos (ChartGetInteger y ChartGetDouble), a continuación, los comparamos con los valores de las variables globales anunciadas con anterioridad (parámetros variables del gráfico).


Función de control de la construcción gráfica

//+------------------------------------------------------------------+
//| Func Drawing                                                     |
//+------------------------------------------------------------------+
void func_drawing(bool type_action,// tipo de acción de la modificación: 0-las dos últimas, 1-todas
                  int num,         // cantidad de ejes dibujados
                  int end_bar_now) // última barra actual
  {
   int begin;
   if(end_bar_now>num)begin=end_bar_now-num;
   else begin=long_period+1;
//--- búsqueda del valor VR máximo   
   double VR_max=0;
   for(int x=begin; x<end_bar_now-1; x++)
     {
      if(Buff_VR[x]<Buff_VR[x+1])VR_max=Buff_VR[x+1];
      else VR_max=Buff_VR[x];
     }
//--- cálculo de la escala
   double scale_height=win_height_pixels_ext/(win_price_max_ext-win_price_min_ext);
   double scale_width=win_width_pixels_ext/win_bars_ext;
//--- construcción (x-parte del nombre del objeto, y-índice de la matriz de los datos para la construcción)
   if(type_action==false)// falso - actualizar los dos últimos ejes
     {
      for(int x=num-2,y=end_bar_now-2; y<end_bar_now; y++,x++)
        {
         func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width);
        }
     }
//---
   if(type_action==true)// verdadero - actualizar todos los ejes
     {
      for(int x=0,y=begin; y<end_bar_now; y++,x++)
        {
         func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width);
        }
     }
   ChartRedraw();
  }

La función es una estructura de control de la construcción del gráfico. Variables de entrada: parámetro de modificación (a elegir, los dos últimos, o todos de golpe), cantidad total de "ejes", último "eje". El parámetro de modificación sirve aquí para modificar solo del último "eje" en el caso de que cambie el precio solo en él y así no tocar todos los "ejes", para aumentar la productividad del indicador.

A continuación, se calcula la barra desde la que comenzar. Si la información sobre las barras es menor que la cantidad de "ejes", entonces el comienzo de la construcción se toma según el periodo mayor de formación del gráfico.

A continuación, encontramos el mayor coeficiente de volumen (es necesario como parámetro a transmitir de la función func_picture, vista más arriba) y calculamos la escala para la construcción del gráfico. Dependiendo del parámetro de modificación, llamamos el ciclo de modificación de los "ejes" (de los objetos gráficos creados con anterioridad, con la ayuda de la función func_picture).


Función de construcción gráfica

//+------------------------------------------------------------------+
//| Func Picture                                                     |
//+------------------------------------------------------------------+
void func_picture(string name,        // nombre del objeto
                  double open,        // precio de apertura de la barra
                  double close,       // precio de cierre de la barra
                  datetime time,      // hora de la barra
                  double VR,          // valor del coeficiente de volumen
                  double VR_maximum,  // valor máximo del coeficiente de volumen
                  double VWMA,        // valor del precio promedio ponderado según el volumen
                  int correct,        // parámetro de corrección del coeficiente de volumen al representarse
                  double scale_height,// escala de altura (píxeles/precio)
                  double scale_width) // escala de anchura (píxeles/barras)
  {
   string first_name;// primera letra del nombre del archivo usado en la construcción
   string second_name_right;// resto del nombre del archivo usado en la construcción del lado derecho
   string second_name_left; // resto del nombre del archivo usado en la construcción del del lado izquierdo
   double cathetus_a;// cateto a
   double cathetus_b;// cateto b
   double hypotenuse;// hipotenusa
   int corner;// ángulo
//--- encontramos los "ángulos" de apertura y cierre de la barra
   cathetus_b=int(VR/VR_maximum/correct*scale_width);// anchura en píxeles
                                                     //picture 540
   if(open<=close) first_name="r";// barra ascendente o Doji
   if(open>close) first_name="b"; // barra descendente
//---
   if(open<VWMA)// VWMA se encuentra por encima del precio de apertura
     {
      cathetus_a=int((VWMA-open)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,open);
     }
   if(open>VWMA)// VWMA se encuentra por debajo del precio de apertura
     {
      cathetus_a=int((open-VWMA)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,open);
     }
   if(open==VWMA)// VWMA se encuentra al nivel del precio de apertura
     {
      func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,open);
      func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,open);
     }
   if(close<VWMA)// VWMA se encuentra por encima del precio de cierre
     {
      cathetus_a=int((VWMA-close)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,close);
     }
   if(close>VWMA)// VWMA se encuentra por debajo del precio de cierre
     {
      cathetus_a=int((close-VWMA)*scale_height);
      hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2)));
      if(hypotenuse<=0) hypotenuse=1;
      corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2)));
      second_name_right=IntegerToString(corner);
      second_name_left=IntegerToString(180-corner);
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,close);
     }
   if(close==VWMA)// VWMA se encuentra al nivel del precio de cierre
     {
      func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,close);
      func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,close);
     }
  }

El "corazón" de la construcción gráfica es la función de cálculo y sustitución de las imágenes de los objetos gráficos. Precisamente en esta función tiene lugar el cálculo de la imagen usada en una barra (más concretamente, de las cuatro imágenes) para construir el "eje". A continuación, con la ayuda de la función func_obj_mod se cambia la imagen del objeto gráfico (todos los objetos gráficos fueron creados al comienzo del código, al final de la función OnInit).

Los parámetros de la barra modificada actual son transferidos a la función, entre ellos está presente el coeficiente de volumen anteriormente mencionado, que sirve como un cierto parámetro relativo para el cálculo del llamado cateto b (fig. 5, b; marcado como la magnitud d).

Después se introducen las variables auxiliares para el cálculo (la primera letra es el color, el resto del lado izquierdo y derecho del nombre del archivo, es el ángulo en el nombre del fichero, el cateto a y b, la hipotenusa y el ángulo), el color del "eje" se define con el operador condicional if. A continuación, dependiendo del nivel del precio de apertura y cierre con respecto al precio promedio ponderado según el volumen (WVMA), tiene lugar el cálculo (de cuatro dibujos) conforme a las fórmulas conocidas de los dibujos 5 y 6, y también la modificación del objeto gráfico con la ayuda de la función func_obj_mod.


Función de modificación del objeto

//+------------------------------------------------------------------+
//| Func Obj Mod                                                     |
//+------------------------------------------------------------------+
void func_obj_mod(string name,             // nombre del objeto
                  string file,             // ruta al recurso del archivo
                  int pix_x_b,             // visibilidad en X
                  int pix_y_a,             // visibilidad en Y
                  int shift_y,             // desplazamiento en Y
                  ENUM_ANCHOR_POINT anchor,// punto de anclaje
                  datetime time,           // coordenada de hora
                  double price)            // coordenada de precio
  {
   ObjectSetString(0,name,OBJPROP_BMPFILE,file);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,pix_x_b);// visibilidad en X
   ObjectSetInteger(0,name,OBJPROP_YSIZE,pix_y_a);// visibilidad en Y
   ObjectSetInteger(0,name,OBJPROP_XOFFSET,0);// sin desplazamiento por el eje X
   ObjectSetInteger(0,name,OBJPROP_YOFFSET,shift_y);// establecemos el desplazamiento por el eje Y
   ObjectSetInteger(0,name,OBJPROP_BACK,false);// lo representamos en el primer plano
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);// desactivamos el modo de arrastre
   ObjectSetInteger(0,name,OBJPROP_SELECTED,false);
   ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);// ocultamos el nombre del objeto gráfico
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,anchor);// establecemos el punto de anclaje
   ObjectSetInteger(0,name,OBJPROP_TIME,time);// establecemos la coordenada de hora
   ObjectSetDouble(0,name,OBJPROP_PRICE,price);// establecemos la coordenada de precio
  }

La función es muy sencilla, se encarga de sustituir los valores trasmitidos a ella por la función de cambio de la propiedad del objeto. Las principales propiedades cambiadas son la imagen del objeto, su visibilidad y el punto de anclaje.


Función OnCalculate

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- comprobar la disponibilidad de la historia por periodo
   if(rates_total<long_period)
     {
      Alert("El periodo VR o VWMA es mayor que los datos históricos, o los datos históricos no están cargados.");
      return(0);
     }
//--- búsqueda de posición
   int position=prev_calculated-1;
   if(position<long_period)position=long_period; // cambio de posición
//--- ciclo principal de cálculo de búfers
   for(int i=position; i<rates_total; i++)
     {
      //--- llenamos los búfers del histograma
      Buff_up[i]=high[i];
      Buff_down[i]=low[i];
      if(open[i]<close[i])Buff_color_up_down[i]=0;// barra ascendente
      if(open[i]>close[i])Buff_color_up_down[i]=1;// barra descendente
      if(open[i]==close[i])Buff_color_up_down[i]=2;// barra Doji
      //--- llenamos los búfers auxiliares
      Buff_open[i]=open[i];
      Buff_close[i]=close[i];
      Buff_time[i]=double(time[i]);
      //--- calculamos el coeficiente de volumen
      double mid_vol=0;
      int x=0;
      for(x=i-ext_period_VR; x<=i; x++)
        {
         mid_vol+=double(tick_volume[x]);
        }
      mid_vol/=x;
      Buff_VR[i]=tick_volume[i]/mid_vol; // подсчет VR
      //--- calculamos el precio promedio ponderado según el volumen
      long vol=0;
      double price_vol=0;
      x=0;
      switch(type_price_VWMA)
        {
         case 0:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(open[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 1:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(high[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 2:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(low[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 3:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               price_vol+=double(close[x]*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
         //---
         case 4:
           {
            for(x=i-ext_period_VWMA; x<=i; x++)
              {
               double price=(open[x]+high[x]+low[x]+close[x])/4;
               price_vol+=double(price*tick_volume[x]);
               vol+=tick_volume[x];
              }
           }
         break;
        }
      Buff_VWMA[i]=price_vol/vol; // calcular VWMA
      //---
      if(type_draw==1)
        {
         Buff_open_ext[i]=Buff_open[i];
         Buff_close_ext[i]=Buff_close[i];
         Buff_VWMA_ext[i]=Buff_VWMA[i];
        }
      else
        {
         //--- reducimos el tamaño de las matrices no usadas
         ArrayResize(Buff_open_ext,1);
         ArrayResize(Buff_close_ext,1);
         ArrayResize(Buff_VWMA_ext,1);
         //--- convertimos en cero las matrices no usadas
         ZeroMemory(Buff_open_ext);
         ZeroMemory(Buff_close_ext);
         ZeroMemory(Buff_VWMA_ext);
        }
     }
   end_bar=rates_total;// definimos el número de la última barra
//---
   if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0 && type_draw==0)// comprobamos la presencia de datos en la ventana del indicador para comenzar a construir
     {
      func_drawing(func_check_chart(),ext_spin_num,end_bar);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

La función estándar del indicador calcula y llena de datos los búfers. En primer lugar se comprueba el periodo VR y WVMA y los datos según las barras, en caso de que no coincidan, se muestra un mensaje. A continuación, se encuentra la posición desde la que hay que comenzar el llenado de los búfers. Se llena el búfer del "histograma", que designa el precio mayor y el menor. Después se calculan y llenan los búfers del "coeficiente de volumen" (VR) y del "precio promedio ponderado según el volumen" (WVMA) conforme a las fórmulas 1 y 2 (señaladas en el capítulo Introducción).


Otras funciones

Para que el indicador funcione de forma más adecuada, también existen la función de definición de una nueva barra func_new_bar y la función de desinicialización OnDeinit.

La función func_new_bar determina la aparición de una nueva barra en el gráfico y sirve de auxiliar en la función func_check_chart.

//+------------------------------------------------------------------+
//| Func New Bar                                                     |
//+------------------------------------------------------------------+
bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
   static datetime old_times; // variable de guardado de valores antiguos
   bool res=false;            // variable del resultado del análisis  
   datetime new_time[1];      // hora de la nueva barra
   int copied=CopyTime(_Symbol,period_time,0,1,new_time); // copiamos la hora de la última barra en la celda new_time  
   if(copied>0) // datos copiados
     {
      if(old_times!=new_time[0]) // si la hora antigua de la barra no es igual a la nueva
        {
         if(old_times!=0) res=true; // si no ese trata del primer inicio, entonces, verdadero = nueva barra
         old_times=new_time[0];     // guardamos la hora de la barra
        }
     }
   return(res);
  }
//+------------------------------------------------------------------+

Esta función se presentó también en artículos anteriores, por lo que pienso que no es necesario comentarla.

La función OnDeinit sirve para borrar los objetos gráficos creados con anterioridad en la función OnInit. La función es estándar para el indicador, y se llama al eliminar el indicador del gráfico.

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- eliminar objetos usados
   if(type_draw==0)
     {
      for(int x=0; x<=ext_spin_num; x++)
        {
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3");
         ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4");
        }
     }
  }

Con esto podemos considerar finalizado el código del indicador. Para cualquier observación o pregunta, les ruego que me escriban en los comentarios del artículo o en los mensajes particulares.


Experto y estrategia comercial.

Antes de estudiar la estrategia comercial, comprobaremos cómo va a funcionar el asesor en este indicador. Realizaremos la comprobación con un asesor que funciona usando para el análisis de sus acciones solo un "eje". Además, no se usará VR (coeficiente de volumen). Resulta, entonces, que el análisis tendrá lugar según un cierto tipo de patrones, que constan de un "eje". Hay un total de 30 variantes así, veámoslo con más detalle en el dibujo 7:

Fig. 7. Posible formación de "ejes"

Fig. 7. Posible formación de "ejes"

De forma convencional, los "ejes" pueden ser divididos en tres grupos y un subgrupo (fig. 7). Esto resulta así si consideramos las diferencias de los "ejes" según la dirección del movimiento del precio, el nivel de apertura y cierre con respecto a todo el eje y el nivel del precio promedio ponderado según el volumen.

Supongamos que la primera diferencia de los "ejes" es su color, y para ser más exactos, el mercado ascendente o descendente en el periodo estudiado (fig. 7, columna 1). En el dibujo 7, en la primera columna, (0) — ascendente (rojo) y (1) — descendente (azul). La siguiente columna muestra la diferencia en el cuerpo B (precio de apertura o cierre) con respecto a la sombra S (el precio mayor y menor en un periodo). Esta diferencia en el ejemplo dado se divide solo en tres partes (fig. 7, columna 2). La tercera columna tiene en cuenta en una compración al nivel VWMA (precio promedio ponderado según el volumen) con respecto al nivel del precio mayor y menor (High y Low). Puede encontrarse por encima (1), por debajo (2) o entre (3) el precio más alto y el más bajo. En la tercera columna, el "eje" (3) puede diferenciarse también por su precio de apertura (Open) y de cierre (Close) del periodo con respecto a VWMA, por lo tanto, en el dibujo 7 se forma otra columna 3-3 (derivada de la columna 3 (3)).

Teniendo en cuenta todas las combinaciones de las diferencias descritas mas arriba, tenemos 30 tipos de "eje".

Todas las cifras en el dibujo 7 están asignadas teniendo en cuenta las respuestas de las funciones en el experto comercial cuyo código veremos más abajo.


Parámetros del experto

Todo el código está dividido en funciones, y para reducir la cantidad de código, las funciones se llaman desde las subfunciones, formando con ello un árbol jerarquizado de funciones. Al principio del código se anuncian los parámetros de entrada idénticos a los parámetros del indicador y complementados solo con el tamaño del lote, el stop-loss y los treinta patrones de "eje". Al final se anuncian las variables para el manejador del indicador y los búfers para guardar los datos utilizados para determinar el patrón. 

//+------------------------------------------------------------------+
//|                                                        EASPC.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://login.mql5.com/es/users/aktiniy |
//+------------------------------------------------------------------+
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://login.mql5.com/es/users/aktiniy"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Type Drawing                                                     |
//+------------------------------------------------------------------+
enum type_drawing
  {
   spindles=0,       // Ejes
   line_histogram=1, // Línea e histograma
  };
//+------------------------------------------------------------------+
//| Type Price                                                       |
//+------------------------------------------------------------------+
enum type_price
  {
   open=0,   // Open
   high=1,   // High
   low=2,    // Low
   close=3,  // Close
   middle=4, // Middle
  };
//--- input parameters
input long         magic_numb=65758473787389; // Número mágico
input type_drawing type_draw=1;               // Tipo de dibujado del indicador
input int          period_VR=10;              // Periodo de formación del coeficiente de volumen
input int          correct_VR=4;              // Número para la corrección del coeficiente de volumen
input int          period_VWMA=10;            // Periodo de formación del precio promedio ponderado según el volumen
input int          spindles_num=10;           // Cantidad de ejes
input type_price   type_price_VWMA=0;         // Tipo de precio para la construcción del precio promedio ponderado según el volumen
                                              // open=0; high=1; low=2; close=3; middle=4
input double lot=0.01;                        // Tamaño del lote
input int    stop=1000;                       // Stop-loss
//---
input char   p1=1;                            // Acciones en los patrones 1-comprar, 2-vender, 3-cerrar posición, 4-no hacer nada
input char   p2=1;
input char   p3=1;
input char   p4=1;
input char   p5=1;
input char   p6=1;
input char   p7=1;
input char   p8=1;
input char   p9=1;
input char   p10=1;
input char   p11=1;
input char   p12=1;
input char   p13=1;
input char   p14=1;
input char   p15=1;
input char   p16=1;
input char   p17=1;
input char   p18=1;
input char   p19=1;
input char   p20=1;
input char   p21=1;
input char   p22=1;
input char   p23=1;
input char   p24=1;
input char   p25=1;
input char   p26=1;
input char   p27=1;
input char   p28=1;
input char   p29=1;
input char   p30=1;
//---
int handle_SPC; // manejador del indicador
long position_type; // tipo de posición
//--- búfers de los valores copiados del indicador
double         Buff_up[3]; // búfer de los puntos superiores del histograma
double         Buff_down[3]; // búfer de los puntos inferiores del histograma
double         Buff_color_up_down[3]; // búfer de color del histograma
double         Buff_open_ext[3]; // búfer de los precios de apertura
double         Buff_close_ext[3]; // búfer de los precios de cierre
double         Buff_VWMA_ext[3]; // búfer del precio promedio ponderado según el volumen
La inicialización del manejador del indicador tiene lugar en la funciónOnInit.
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   handle_SPC=iCustom(_Symbol,PERIOD_CURRENT,"SPC.ex5",magic_numb,type_draw,period_VR,correct_VR,period_VWMA,spindles_num,type_price_VWMA);
//---
   return(INIT_SUCCEEDED);
  }


Función de envío de órdenes al servidor

Hay dos funciones así: una para abrir las órdenes, otra para cerrar las posiciones. Ambas funciones han sido creadas basándose en ejemplos de la documentación sobre MQL5, e incluyen el ensamblaje de la estructura de la solicitud comercial y la llamada de la función OrderSend con el posterior análisis de la estructura de la respuesta.

//+------------------------------------------------------------------+
//| Func Send Order                                                  |
//+------------------------------------------------------------------+
bool func_send_order(ENUM_ORDER_TYPE type_order,// tipo de la orden establecida
                     double volume)             // volumen de la transacción del lote
  {
   bool x=false; // variable para la respuesta
//--- introducimos las variables para el envío de la orden
   +MqlTradeRequest order_request={0};
   MqlTradeResult order_result={0};
//--- llenamos la variable para el envío de la orden
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=3;
   order_request.magic=555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=volume;
   if(type_order==ORDER_TYPE_BUY)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      order_request.sl=order_request.price-(_Point*stop);
     }
   if(type_order==ORDER_TYPE_SELL)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      order_request.sl=order_request.price+(_Point*stop);
     }
//--- enviamos la orden
   bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Error en el envío de la orden.");
//--- comprobamos el resultado
   if(order_result.retcode==10008 || order_result.retcode==10009) x=true;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Delete Position                                             |
//+------------------------------------------------------------------+
bool func_delete_position()
  {
   bool x=false;
//--- marcamos la posición desde la que vamos a trabajar
   PositionSelect(_Symbol);
   double vol=PositionGetDouble(POSITION_VOLUME);
   long type=PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type_order;
   if(type==POSITION_TYPE_BUY)type_order=ORDER_TYPE_SELL;
   else type_order=ORDER_TYPE_BUY;
//--- introducimos las variables para el envío de la orden
   +MqlTradeRequest order_request={0};
   MqlTradeResult order_result={0};
//--- llenamos la variable para el envío de la orden
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=3;
   order_request.magic=555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=vol;
   if(type_order==ORDER_TYPE_BUY)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   if(type_order==ORDER_TYPE_SELL)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
//--- enviamos la orden
   bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Error en el envío de la orden.");
//--- comprobamos el resultado
   if(order_result.retcode==10008 || order_result.retcode==10009) x=true;
   return(x);
  }

En el código también tenemos la función auxiliar func_new_bar, que determina la aparición de una nueva barra en el gráfico. Está descrita más arriba y no es necesario publicar su código.

Tras describir todas las funciones estándar, vamos a echar un vistazo al "corazón" de los cálculos.

En la función OnTick tiene lugar, propiamente, la consolidación de todas las acciones. En primer lugar, los búfers usados para los cálculos se llenan con la ayuda de la función CopyBuffer. Después se comprueba si hay alguna posición en el instrumento actual, esto es necesario para otra función encargada del control del establecimiento y la eliminación de órdenes (posiciones). Después se preparan los tamaños para determinar el patrón, son el cuerpo: la distancia entre el precio de apertura y el de cierre, la sombra: la distancia entre el precio mayor y el menor en el periodo actual. También se encuentra su relación, que más tarde se transmitirá a la función func_one (fig. 7, columna 2).

A continuación, se usan las funciones func_two y func_three, en el dibujo 7, se trata de las columnas 3 y 3-3 respectivamente. Después comprobamos con la ayuda del operador switch el color del "eje", de acuerdo con el dibujo 7, columna 1. De esta forma, obtenemos el árbol de decisiones de las funciones cuando el siguiente operador switch, dependiendo del valor de la variable afun_1_1 , cambia a la función func_pre_work (la veremos más tarde) de acuerdo con la columna 2 del dibujo 7, en base al tamaño del cuerpo y la sombra.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(func_new_bar(PERIOD_CURRENT)==true)
     {
      //--- copiamos los búfers del indicador
      CopyBuffer(handle_SPC,0,1,3,Buff_up);
      CopyBuffer(handle_SPC,1,1,3,Buff_down);
      CopyBuffer(handle_SPC,2,1,3,Buff_color_up_down);
      CopyBuffer(handle_SPC,3,1,3,Buff_open_ext);
      CopyBuffer(handle_SPC,4,1,3,Buff_close_ext);
      CopyBuffer(handle_SPC,5,1,3,Buff_VWMA_ext);
      //--- analizamos la situación
      //--- comprobamos si hay una orden establecida
      if(PositionSelect(_Symbol)==true)
        {
         position_type=PositionGetInteger(POSITION_TYPE); // BUY=0, SELL=1
        }
      else
        {
         position_type=-1; // no hay posición para el instrumento
        }
      //--- preparando magnitudes para la comparación
      double body=Buff_open_ext[2]-Buff_close_ext[2];
      body=MathAbs(body);
      double shadow=Buff_up[2]-Buff_down[2];
      shadow=MathAbs(shadow);
      if(shadow==0)shadow=1;// prevenimos la división por cero
      double body_shadow=body/shadow;
      //--- variables de respuesta de las funciones
      char afun_1_1=func_one(body_shadow);
      char afun_2_1=func_two(Buff_up[2],Buff_down[2],Buff_VWMA_ext[2]);
      char afun_3_1=func_three(Buff_open_ext[2],Buff_close_ext[2],Buff_VWMA_ext[2]);
      //---
      switch(int(Buff_color_up_down[2]))
        {
         case 0:
           {
            switch(afun_1_1)
              {
               case 1:
                  func_pre_work(afun_2_1,afun_3_1,p1,p2,p3,p4,p5);
                  break;
               case 2:
                  func_pre_work(afun_2_1,afun_3_1,p6,p7,p8,p9,p10);
                  break;
               case 3:
                  func_pre_work(afun_2_1,afun_3_1,p11,p12,p13,p14,p15);
                  break;
              }
           }
         break;
         case 1:
           {
            switch(afun_1_1)
              {
               case 1:
                  func_pre_work(afun_2_1,afun_3_1,p16,p17,p18,p19,p20);
                  break;
               case 2:
                  func_pre_work(afun_2_1,afun_3_1,p21,p22,p23,p24,p25);
                  break;
               case 3:
                  func_pre_work(afun_2_1,afun_3_1,p26,p27,p28,p29,p30);
                  break;
              }
           }
         break;
        }
     }
  }

La función func_pre_work continúa la ramificación del árbol de funciones ya formado, con lo que resulta la implementación programática del código basado en el dibujo 7, columnas 3 (variable f_2) y 3-3 (variable f_3), se lleva a cabo el cambio a la última función del árbol, func_work.

//+------------------------------------------------------------------+
//| Func Pre Work                                                    |
//+------------------------------------------------------------------+
void func_pre_work(char f_2,     // resultado de la función Func Two
                   char f_3,     // resultado de la función Func Three
                   char pat_1,   // patrón 1
                   char pat_2,   // patrón 2
                   char pat_3_1, // patrón 3_1
                   char pat_3_2, // patrón 3_2
                   char pat_3_3) // patrón 3_3
  {
   switch(f_2)
     {
      case 1: //1
         func_work(pat_1);
         break;
      case 2: //2
         func_work(pat_2);
         break;
      case 3:
        {
         switch(f_3)
           {
            case 1: //3_1
               func_work(pat_3_1);
               break;
            case 2: //3_2
               func_work(pat_3_2);
               break;
            case 3: //3_3
               func_work(pat_3_3);
               break;
           }
        }
      break;
     }
  }

La función func_work decide qué hacer con la posición, eligiendo una de estas cuatro variantes: comprar, vender, cerrar la posición y no hacer nada. Aquí el control final se le transmite a las funciones ya conocidas func_send_order y func_delete_position, vistas con anterioridad.

//+------------------------------------------------------------------+
//| Func Work                                                        |
//+------------------------------------------------------------------+
void func_work(char pattern)
  {
   switch(pattern)
     {
      case 1: // comprar
         if(position_type!=-1)func_delete_position();
         func_send_order(ORDER_TYPE_BUY,lot);
         break;
      case 2: // vender
         if(position_type!=-1)func_delete_position();
         func_send_order(ORDER_TYPE_SELL,lot);
         break;
      case 3: // cerrar posición
         if(position_type!=-1)func_delete_position();
         break;
      case 4: // no hacer nada
         break;
     }
  }

Solo quedan las últimas funciones recordadas con anterioridad: func_one, func_two y func_three. Sirven para transformar los datos transmitidos en forma de valores de precio y convertirlos en datos de número entero para el operador switch. Si volvemos al dibujo 7, entonces func_one es la implementación de la columna 2, func_two, de la columna 3 y func_three, de la columna 3-3. Los valores retornados de estas funciones son los números enteros 1, 2 y 3, que también se corresponden con las cifras del dibujo 7.

//+------------------------------------------------------------------+
//| Func One                                                         |
//+------------------------------------------------------------------+
char func_one(double body_shadow_in)
  {
   char x=0; // variable para la respuesta
   if(body_shadow_in<=(double(1)/double(3))) x=1;
   if(body_shadow_in>(double(1)/double(3)) && body_shadow_in<=(double(2)/double(3))) x=2;
   if(body_shadow_in>(double(2)/double(3)) && body_shadow_in<=1) x=3;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Two                                                         |
//+------------------------------------------------------------------+
char func_two(double up,// high [Buff_up]
              double down,// low [Buff_down]
              double VWMA) // VWMA [Buff_VWMA_ext]
  {
   char x=0; // variable para la respuesta
   if(VWMA>=up) x=1;
   if(VWMA<=down) x=2;
   else x=3;
   return(x);
  }
//+------------------------------------------------------------------+
//| Func Three                                                       |
//+------------------------------------------------------------------+
char func_three(double open,// open [Buff_open_ext]
                double close,// close [Buff_close_ext]
                double VWMA) // VWMA [Buff_VWMA_ext]
  {
   char x=0; // variable para la respuesta
   if(open>=VWMA && close>=VWMA) x=1;
   if(open<=VWMA && close<=VWMA) x=2;
   else x=3;
   return(x);
  }
//+------------------------------------------------------------------+

Ahora que el asesor ya está listo, vamos a ponerlo a prueba. Primero vamos a definir los parámetros:

La optimización se llevará a cabo solo por acciones, más concretamente: comprar, vender, cerrar posición y no hacer nada, y también según el periodo VWMA. Asimismo, averiguamos qué acciones son más rentables para cada patrón, y qué periodo VWMA es más apropiado para trabajar con el marco temporal H1.

Los ajustes del Simulador se muestran en el dibujo 8:

Fig. 8. Ajustes del simulador de estrategias

Fig. 8. Ajustes del simulador de estrategias

Optimizaremos, como se ha dicho con anterioridad, según las acciones, dependiendo del patrón y según el periodo VWMA en un diapasón de 10 a 500 barras, dibujo 9:

Fig. 9. Parámetros de optimización

Fig. 9. Parámetros de optimización

Durante la optimización, obtemos un gráfico, dibujo 10:

Fig. 10. Gráfico de optimización

Fig. 10. Gráfico de optimización

Como resultado de la optimización, obtenemos un benefico de 138.71, teniendo un tamaño de lote de 0.01 y un depósito inicial de 10000, con unas pérdidas del 2.74% (aproximadamente 28 unidades), dibujo 11:

Fig. 11. Resultados de la optimización

Fig. 11. Resultados de la optimización

Aumentamos el tamaño del lote hasta 0.1, reducimos el depósito hasta 1000 y realizamos una nueva simulación, usando los parámetros obtenidos como resultado de la optimización. Al mismo tiempo, cambiamos el modo de comercio a OHLC en M1 para aumentar la precisión de la simulación, obteniendo el dibujo 12:

Fig. 12. Resultado de la simulación (backtest)

Fig. 12. Resultado de la simulación (backtest)

Como resultado, en dos años se han realizado 742 operaciones (aproximadamente 3 operaciones al día), al mismo tiempo, las pérdidas máximas han sido de 252, y el beneficio neto, de 1407, aproximadamente 60 (el 6% de la suma de inversión) al mes. En teoría todo sale bastante bien, pero no es un hecho que todo vaya a resultar tan bonito en la práctica.

Como es natural, este experto debe ser modernizado y mejorado en lo sucesivo, posiblemente se introduzca un "eje" adicional en el patrón y se añada VR. Este es un tema sobre el que reflexionar más adelante, pero incluso con tan pocos parámetros, el experto en este indicador ha mostrado unos resultados bastante interesantes.

La estrategia comercial al trabajar con el indicador es bastante sencilla, hay que comprar cuando la flecha señala hacia arriba, y vender cuando señala hacia abajo. Rombo — se trata de algo semejante a "Doji", indica un viraje. Esto se ve bien en el dibujo 13:

Fig. 13. Indicador en acción

Fig. 13. Indicador en acción

Como se ve hasta la cifra 1 (fig. 13), el indicador dibuja flechas dirigidas hacia arriba, luego aparece un rombo de color azul, que informa de un posible cambio en el movimiento de la tendencia. La tendencia cambia, y hasta la cifra 2 los precios van hacia abajo, a continuación aparece un rombo rojo, que también anuncia un cambio de tendencia, como así sucede.


Conclusión

Tenía poca información sobre este indicador, pero aun así su originalidad despertó mi interés. Es posible que también me influyese su compleja implementación en el código de programa, que me hizo pensar bastante. Considero que no he invertido el tiempo en vano, y espero que este indicador sea útil a mucha gente. Puedo suponer que no he desentrañado el tema por completo, puesto que es bastante amplio, pero creo que esto se irá corrigiendo en el foro. Es conveniente prestar especial atención a la discusión y modernización del asesor, incluso en su etapa incial, muestra resultados bastante decentes. Me alegraré mucho de poder debatir y responder a cualquier mensaje, ya sea bajo el artículo, o a través de mensajes privados.

Este ha sido otro artículo sobre el tema de los indicadores, si alguien tiene una idea sobre nuevos indicadores interesantes, que me escriba un mensaje privado. No puedo prometer que la idea se vaya implementar, pero es posible que pueda recomendarles algo. Mi próximo artículo seguramente cambie de tema de manera cardinal, pero no voy a adelantarme y hablar de él, dado que por ahora es simplemente una idea, y el código está en su fase de planificación.