Visualizar lagunas de datos (elementos vacíos)

En muchos casos, las lecturas de los indicadores deben mostrarse sólo en algunas barras, dejando el resto de las barras intactas (visualmente, sin líneas ni etiquetas adicionales). Por ejemplo, muchos indicadores de señales muestran flechas hacia arriba o hacia abajo en aquellas barras en las que aparece una recomendación de compra o de venta. Pero las señales son raras.

Un valor vacío que no se muestra ni en el gráfico ni en Data Window se establece mediante la función PlotIndexSetDouble.

bool PlotIndexSetDouble(int index, ENUM_PLOT_PROPERTY_DOUBLE property, double value)

La función establece las propiedades de double para el trazado en el index especificada. El conjunto de estas propiedades se resume en la enumeración ENUM_PLOT_PROPERTY_DOUBLE, pero de momento sólo tiene un elemento: PLOT_EMPTY_VALUE. También establece el valor vacío. El valor en sí se pasa en el último parámetro value.

Como ejemplo de indicador con valores poco frecuentes, consideraremos un detector fractal. Marca en el gráfico los precios altos (High) que son superiores a N barras vecinas y los precios bajos (Low) que son inferiores a N barras vecinas, en ambas direcciones. El archivo del indicador se llama IndFractals.mq5.

El indicador tendrá dos búferes y dos trazados del tipo DRAW_ARROW.

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
   
// rendering settings
#property indicator_type1   DRAW_ARROW
#property indicator_type2   DRAW_ARROW
#property indicator_color1  clrBlue
#property indicator_color2  clrRed
#property indicator_label1  "Fractal Up"
#property indicator_label2  "Fractal Down"
   
// indicator buffers
double UpBuffer[];
double DownBuffer[];

La variable de entrada FractalOrder le permitirá establecer el número de barras vecinas, mediante las cuales se determina el extremo superior o inferior.

input int FractalOrder = 3;

Para los símbolos de flecha, proporcionaremos una sangría de 10 píxeles desde los extremos para una mejor visibilidad.

const int ArrowShift = 10;

En la función OnInit, declare arrays como búferes y enlácelos a trazados gráficos.

int OnInit()
{
   // binding buffers
   SetIndexBuffer(0UpBufferINDICATOR_DATA);
   SetIndexBuffer(1DownBufferINDICATOR_DATA);
   
   // up and down arrow character codes
   PlotIndexSetInteger(0PLOT_ARROW217);
   PlotIndexSetInteger(1PLOT_ARROW218);
   
   // padding for arrows
   PlotIndexSetInteger(0PLOT_ARROW_SHIFT, -ArrowShift);
   PlotIndexSetInteger(1PLOT_ARROW_SHIFT, +ArrowShift);
   
   // setting an empty value (can be omitted, since EMPTY_VALUE is the default)
   PlotIndexSetDouble(0PLOT_EMPTY_VALUEEMPTY_VALUE);
   PlotIndexSetDouble(1PLOT_EMPTY_VALUEEMPTY_VALUE);
   
   return FractalOrder > 0 ? INIT_SUCCEEDED : INIT_PARAMETERS_INCORRECT;
}

Tenga en cuenta que el valor vacío por defecto es la constante especial EMPTY_VALUE, por lo que las llamadas a PlotIndexSetDouble anteriores son opcionales.

En el manejador OnCalculate, en el momento de la primera llamada, inicializamos ambos arrays con EMPTY_VALUE, y luego lo asignamos a nuevos elementos a medida que se forman las barras. El relleno es necesario porque la memoria asignada al búfer puede contener datos arbitrarios (basura).

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[])
{
   if(prev_calculated == 0)
   {
      // at the start, fill the arrays entirely
      ArrayInitialize(UpBufferEMPTY_VALUE);
      ArrayInitialize(DownBufferEMPTY_VALUE);
   }
   else
   {
      // on new bars we also clean the elements
      for(int i = prev_calculatedi < rates_total; ++i)
      {
         UpBuffer[i] = EMPTY_VALUE;
         DownBuffer[i] = EMPTY_VALUE;
      }
   }
   ...

En el bucle principal se comparan los precios high y low barra por barra con los mismos tipos de precios de las barras vecinas y se establecen marcas donde se encuentre un extremo entre las barras FractalOrder de cada lado.

   // view all or new bars that have bars in the FractalOrder environment
   for(int i = fmax(prev_calculated - FractalOrder - 1FractalOrder);
       i < rates_total - FractalOrder; ++i)
   {
      // check if the upper price is higher than neighboring bars
      UpBuffer[i] = high[i];
      for(int j = 1j <= FractalOrder; ++j)
      {
         if(high[i] <= high[i + j] || high[i] <= high[i - j])
         {
            UpBuffer[i] = EMPTY_VALUE;
            break;
         }
      }
      
      // check if the lower price is lower than neighboring bars
      DownBuffer[i] = low[i];
      for(int j = 1j <= FractalOrder; ++j)
      {
         if(low[i] >= low[i + j] || low[i] >= low[i - j])
         {
            DownBuffer[i] = EMPTY_VALUE;
            break;
         }
      }
   }
   
   return rates_total;
}

Veamos cómo se ve este indicador en el gráfico.

Indicador fractal

Indicador fractal

Ahora cambiemos el tipo de dibujo de DRAW_ARROW a DRAW_ZIGZAG y comparemos el efecto de los valores vacíos para ambas opciones. El resultado debería ser un zigzag en fractales. La versión modificada del indicador se adjunta en el archivo IndFractalsZigZag.mq5.

Uno de los principales cambios se refiere al número de diagramas: ahora es uno, ya que DRAW_ZIGZAG «consume» ambos búferes.

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   1
   
// rendering settings
#property indicator_type1   DRAW_ZIGZAG
#property indicator_color1  clrMediumOrchid
#property indicator_width1  2
#property indicator_label1  "ZigZag Up;ZigZag Down"
...

Todas las llamadas a funciones relacionadas con el ajuste de las flechas se eliminan de OnInit.

int OnInit()
{
   SetIndexBuffer(0UpBufferINDICATOR_DATA);
   SetIndexBuffer(1DownBufferINDICATOR_DATA);
   
   PlotIndexSetDouble(0PLOT_EMPTY_VALUEEMPTY_VALUE);
   
   return FractalOrder > 0 ? INIT_SUCCEEDED : INIT_PARAMETERS_INCORRECT;
}

El resto del código fuente no se modifica.

En la siguiente imagen se muestra un gráfico en el que se aplica un zigzag además de los fractales: de este modo, se pueden comparar visualmente sus resultados. Ambos indicadores funcionan de forma totalmente independiente, pero debido al mismo algoritmo, los extremos encontrados son los mismos.

Indicador zigzag por fractales

Indicador zigzag por fractales

Es importante tener en cuenta que, si se producen extremos del mismo tipo en una fila, el zigzag utiliza el primero de ellos. Esto es consecuencia del hecho de que los fractales se utilizan como extremos. Por supuesto, esto no puede ocurrir en un zigzag estándar. Si es necesario, quienes lo deseen pueden mejorar el algoritmo adelgazando primero las secuencias de fractales.

También debe tenerse en cuenta que para la representación de DRAW_ZIGZAG (así como de DRAW_SECTION), los segmentos visibles conectan elementos no vacíos y, por lo tanto, estrictamente hablando, se dibuja algún fragmento del segmento en cada barra, incluidos aquellos que tienen el valor EMPTY_VALUE (u otro designado en su lugar). Sin embargo, puede ver en Data Window que los elementos vacíos están realmente vacíos: no se muestra ningún valor para ellos.