English Русский Deutsch 日本語 Português
preview
El tipo de dibujado DRAW_ARROW en indicadores de símbolo y periodo múltiple

El tipo de dibujado DRAW_ARROW en indicadores de símbolo y periodo múltiple

MetaTrader 5Ejemplos | 19 julio 2024, 10:01
42 0
Artyom Trishkin
Artyom Trishkin

Contenido


Introducción

Continuamos con el tema de los indicadores de símbolo y periodo múltiple. El último artículo de esta serie trataba de los búferes de color en los indicadores de símbolo y periodo múltiple. Hoy mejoraremos la clase de indicador múltiple para que pueda trabajar con indicadores de flecha.

Los indicadores de flecha no implican la presencia constante de datos en su búfer de dibujado. Cuando aparece la flecha, hay un valor en el búfer, mientras que otras veces hay un valor vacío establecido en el búfer. Normalmente es EMPTY_VALUE, pero es posible establecer cualquier valor para un búfer que esté "vacío" y no aparezca en el gráfico. Esto podrá hacerse utilizando una función

PlotIndexSetDouble(buffer_index,PLOT_EMPTY_VALUE,empty_value);

donde buffer_index será el índice del búfer al que se asigna un valor vacío, mientras que empty_value será la magnitud del "valor vacío" que se asignará a ese búfer.

En los indicadores de periodo múltiple, donde el búfer se rellena de datos con interrupciones, deberemos tener en cuenta la presencia de valores vacíos donde no hay flechas, y no escribir estos valores vacíos en la barra del gráfico donde ya está escrito un valor no vacío. En caso contrario, la flecha colocada anteriormente será borrada por el nuevo valor en vacío. Esto será cierto si los datos del indicador calculados en un marco temporal inferior se copian en un marco temporal superior.

Lo explicaremos con un ejemplo. En la imagen del gráfico M5, las barras del gráfico M15 están marcadas:


Aquí vemos los fractales del gráfico M5 que deben fijarse en las barras del gráfico M15.

  • Para la primera barra de M15 (I) deberemos establecer el fractal superior situado en la barra 3, este fractal será visible en M15 de todos modos, ya que es el último de los tres.
  • Para la segunda barra M15 (II) deberemos establecer el fractal situado en la barra 2, este fractal será visible en M15 solo si no copiamos el valor vacío de la barra 3, ya que sustituirá al valor del fractal inferior. La barra en M15 tendrá dos fractales, uno superior y otro inferior, ya que M5 tiene un fractal inferior en la barra 2 y uno superior en la barra 3.
  • Para la tercera barra M15 (III) deberemos establecer el fractal superior de la barra 1, este fractal será visible en M15 solo si no copiamos los valores vacíos de las barras 2 y 3, ya que reemplazarán el valor del fractal.

Por lo tanto, tendremos que controlar el valor de la barra del gráfico actual y si está vacío, podremos copiar cualquier valor de el búfer del marco temporal inferior. Pero si el valor no está vacío, solo se copiará un valor no vacío. En este caso, la barra del gráfico del marco temporal superior se ajustará al valor del último fractal de aquellas barras del marco temporal inferior que estén incluidas en la barra del marco temporal superior del gráfico. Por qué exactamente la última barra y no la primera, creo que este valor resulta más relevante, ya que se encuentra más cerca de la hora actual.


Al copiar los datos del indicador calculados en el marco temporal superior, al gráfico del marco temporal inferior, deberemos tener en cuenta el hecho de que la flecha puede colocarse no en el cero o en la primera barra del gráfico, sino, por ejemplo, en la segunda, tercera o más antigua. Si copiamos solamente los datos de las barras primera y cero, como se hace ahora en la clase de indicador múltiple, entonces no se pondrán flechas en el gráfico, simplemente se encontrarán en las barras con un índice más alto y, en consecuencia, el algoritmo de cálculo del indicador no podrá verlas:


Aquí vemos que el fractal se forma en la barra con índice 2. Si solo copiamos las barras cero y primera, este fractal no será visible para el programa.

En consecuencia, como no podremos saber exactamente qué indicador coloca sus flechas en qué barra, para la clase de indicadores múltiples deberemos introducir una variable en la que especificaremos explícitamente la barra a partir de la cual deberemos empezar a buscar y copiar valores. En este momento la clase copiará solo la primera barra y la barra cero en el tick actual, respectivamente, no podrá ver fractales: para ellos los valores comenzarán a partir de la barra con índice 2.

Si utilizamos un indicador personalizado como indicador de origen, podremos poner flechas en cualquier otra barra, no solo en la segunda barra, como el indicador Fractals. Por ejemplo, si se trata de un indicador de fractales personalizado en el que se buscan fractales de una dimensionalidad diferente, por ejemplo 3-X-3, entonces la flecha podrá colocarse en la barra con índice 3 (si el fractal está redibujado) o 4 (si el fractal no está redibujado).

Para los indicadores estándar indicaremos explícitamente el índice de la barra, tal y como se conoce. Para los indicadores personalizados haremos posible indicar la barra de inicio de búsqueda en los parámetros del constructor de la clase.


Mejoramos las clases de indicador múltiple

Utilizaremos arrays temporales locales al copiar datos en los métodos de la clase. Las haremos globales para no tener que reasignar memoria para estos arrays cada vez.

En la sección privada de la clase básica de indicadores de símbolo y periodo múltiple CIndMSTF, declararemos los nuevos arrays, y en la sección protegida, una variable para almacenar el número de barras para calcular el indicador en el tick actual:

//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:
   ENUM_PROGRAM_TYPE m_program;           // Program type
   ENUM_INDICATOR    m_type;              // Indicator type
   ENUM_TIMEFRAMES   m_timeframe;         // Chart timeframe
   string            m_symbol;            // Chart symbol
   int               m_handle;            // Indicator handle
   int               m_id;                // Identifier
   bool              m_success;           // Successful calculation flag
   ENUM_ERR_TYPE     m_type_err;          // Calculation error type
   string            m_description;       // Custom description of the indicator
   string            m_name;              // Indicator name
   string            m_parameters;        // Description of indicator parameters
   double            m_array_data[];      // Temporary array for copying data
   datetime          m_array_time[];      // Temporary array for copying time
   
protected:
   ENUM_IND_CATEGORY m_category;          // Indicator category
   MqlParam          m_param[];           // Array of indicator parameters
   string            m_title;             // Title (indicator name + description of parameters)
   SBuffer           m_buffers[];         // Indicator buffers
   int               m_digits;            // Digits in indicator values
   int               m_limit;             // Number of bars required to calculate the indicator on the current tick
   int               m_rates_total;       // Number of available bars for indicator calculation
   int               m_prev_calculated;   // Number of calculated bars on the previous indicator call
   uint              m_bars_to_calculate; // Number of bars required to calculate the indicator on the current tick


En la sección pública de la clase, escribiremos un método que establecerá el número de barras necesarias para calcular el indicador en el tick actual:

//--- Returns amount of calculated data
   int               Calculated(void)                          const { return ::BarsCalculated(this.m_handle); }
//--- Set the number of bars required to calculate the indicator on the current tick
   void              SetBarsToCalculate(const int bars)
                       {
                        this.m_bars_to_calculate=bars;
                        if(this.m_array_data.Size()!=this.m_bars_to_calculate)
                          {
                           ::ArrayResize(this.m_array_data,this.m_bars_to_calculate);
                           ::ArrayResize(this.m_array_time,this.m_bars_to_calculate);
                          }
                       }
   
//--- Virtual method returning the type of object (indicator)

El número requerido de barras se transmitirá al método, este valor se escribirá en una nueva variable y los tamaños del array se establecerán como iguales al valor de esta variable.

De este modo, si se redimensionarán correctamente los arrays, y ya no será necesario restablecerlos cada vez en los métodos de copiado de datos. Si no es posible redimensionar los arrays aquí, las funciones de copiado de datos dentro de los métodos de copiado intentarán redimensionar el array al número requerido. Si tampoco es posible hacerlo ahí, se mostrará un mensaje de error de copiado de los métodos.

Así, estableceremos el tamaño requerido de los arrays una vez, ya sea en el constructor de la clase o (si no tiene éxito) en los métodos de copiado, y luego utilizaremos los arrays con el tamaño requerido establecido, lo cual nos evitará reasignar memoria constantemente para los arrays, en el caso de que fueran locales.

En el constructor de la clase, llamaremos a este método con un mismo valor por defecto para la gran mayoría de los indicadores, igual a dos barras, para copiar los datos (primero y cero):

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Start the timer
   ::ResetLastError();
   if(!::EventSetTimer(1))
      ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError());
//--- Set properties to the values passed to the constructor or to default values
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_type=type;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_handle=INVALID_HANDLE;
   this.m_digits=::Digits();
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- Set the size of the buffer structure array to the number of indicator buffers
   ::ResetLastError();
   if(::ArrayResize(this.m_buffers,buffers)!=buffers)
      ::PrintFormat("%s: Buffers ArrayResize failed. Error  %lu"__FUNCTION__,::GetLastError());
//--- Set the "empty" value for each buffer by default (can be changed it later)
   for(int i=0;i<(int)this.m_buffers.Size();i++)
      this.SetBufferInitValue(i,EMPTY_VALUE);
//--- Set initial values of variables involved in resource-efficient calculation of the indicator
   this.m_prev_calculated=0;
   this.m_limit=0;
   this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe);
   this.SetBarsToCalculate(2);
//--- If the indicator is calculated on non-current chart, request data from the required chart
//--- (the first access to data starts data pumping)
   datetime array[];
   if(symbol!=::Symbol() || timeframe!=::Period())
      ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array);
  }


En el destructor de la clase, liberaremos la memoria asignada a los arrays temporales:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CIndMSTF::~CIndMSTF()
  {
//--- Delete timer
   ::EventKillTimer();
//--- Release handle of the indicator
   ::ResetLastError();
   if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle))
      ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError());
//--- Free up the memory of buffer arrays
   for(int i=0;i<(int)this.BuffersTotal();i++)
     {
      ::ArrayFree(this.m_buffers[i].array0);
      ::ArrayFree(this.m_buffers[i].array1);
      ::ArrayFree(this.m_buffers[i].array2);
      ::ArrayFree(this.m_buffers[i].array3);
      ::ArrayFree(this.m_buffers[i].color_indexes);
     }
//--- Free up the memory of temporary arrays
   ::ArrayFree(this.m_array_data);
   ::ArrayFree(this.m_array_time);
  }


En el método de copiado de datos del array especificado del búfer especificado, ahora comprobaremos la cantidad de datos a copiar según el valor establecido en la nueva variable. Antes había un valor fijo de dos barras:

//+------------------------------------------------------------------+
//| Copy data of the specified array of the specified buffer         |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArray(const uint buff_num,const uint array_num,const int to_copy,double &array[])
  {
   ::ResetLastError();
//--- Copy either the last two bars to 'array' or all available historical data from indicator's calculation part array to buffer array of indicator object
   int copied=0;
   if(to_copy==this.m_bars_to_calculate)
     {
      switch(array_num)
        {
         case 0   :  case 1 : case 2 :
         case 3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,array);   break;
         case 4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,array);   break;
         default  :  break;
        }
     }
   else
     {
      switch(array_num)
        {
         case 0   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array0);          break;
         case 1   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array1);          break;
         case 2   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array2);          break;
         case 3   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(),  -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array3);          break;
         case 4   :  copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].color_indexes);   break;
         default  :  break;
        }
     }
//--- If copied successfully
   if(copied>0)
      return true;
//--- If not all data is copied
//--- If CopyBuffer returned -1, this means the start of historical data downloading
//--- print a message about this to the log
   if(copied==WRONG_VALUE)
      ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
//--- In any other case, not all data has been copied yet
//--- print a message about this to the log
   else
      ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied);
   return false;
  }


En el método de copiado de datos de todos los arrays del búfer anteriormente indicado, se establecía rigurosamente el copiado de dos barras, y se escribía directamente en el código desde que índice del array temporal a qué índice del array de búfer se copiaban los datos:

//+------------------------------------------------------------------+
//| Copy data of all arrays of the specified buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy)
  {
   bool res=true;
   double array[2];
   if(to_copy==2)
     {

      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            res=this.CopyArray(buff_num,0,to_copy,array);
            if(res)
              {
               this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-1]=array[1];
               this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-2]=array[0];
              }
            return res;
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :

Ahora no se conoce de antemano la cantidad de datos que hay que copiar, sino que se escribe en una nueva variable. Por lo tanto, comprobaremos el valor de esta variable, y luego copiaremos los datos en un ciclo por el número de datos copiados.
Los índices de los arrays de origen y destino se calcularán partiendo del índice del ciclo. Al mismo tiempo, ahora tendremos un array temporal global declarado, por lo que el local, tal y como estaba antes, ya no será necesario:

//+------------------------------------------------------------------+
//| Copy data of all arrays of the specified buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy)
  {
   bool res=true;
   if(to_copy==this.m_bars_to_calculate)

     {
      int total=(int)this.m_array_data.Size();
      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Four buffers
         case DRAW_BARS             :
         case DRAW_CANDLES          :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- One buffer + color buffer
         case DRAW_COLOR_LINE       :
         case DRAW_COLOR_HISTOGRAM  :
         case DRAW_COLOR_ARROW      :
         case DRAW_COLOR_SECTION    :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Two buffers + color buffer
         case DRAW_COLOR_HISTOGRAM2 :
         case DRAW_COLOR_ZIGZAG     :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //--- Four buffers + color buffer
         case DRAW_COLOR_BARS       :
         case DRAW_COLOR_CANDLES    :
            res=this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i];
              }
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            if(res)
              {
               for(int i=0;i<total;i++)
                  this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i];
              }
            return res;
         //---DRAW_NONE
         default:
           break;
        }
     }
   else
     {
      switch(this.BufferDrawType(buff_num))
        {
         //--- One buffer
         case DRAW_LINE             :
         case DRAW_HISTOGRAM        :
         case DRAW_ARROW            :
         case DRAW_SECTION          :
            return this.CopyArray(buff_num,0,to_copy,this.m_array_data);
         //--- Two buffers
         case DRAW_HISTOGRAM2       :
         case DRAW_ZIGZAG           :
         case DRAW_FILLING          :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            return res;
         //--- Four buffers
         case DRAW_BARS             :
         case DRAW_CANDLES          :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            return res;
         //--- One buffer + color buffer
         case DRAW_COLOR_LINE       :
         case DRAW_COLOR_HISTOGRAM  :
         case DRAW_COLOR_ARROW      :
         case DRAW_COLOR_SECTION    :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //--- Two buffers + color buffer
         case DRAW_COLOR_HISTOGRAM2 :
         case DRAW_COLOR_ZIGZAG     :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //--- Four buffers + color buffer
         case DRAW_COLOR_BARS       :
         case DRAW_COLOR_CANDLES    :
            res  =this.CopyArray(buff_num,0,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data);
            res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data);
            return res;
         //---DRAW_NONE
         default:
           break;
        }
     }
   return false;
  }


En el método de rellenado de los búferes del objeto con los datos del búfer de la parte de cálculo en el bloque de código responsable del trabajo en el tick actual, ahora copiaremos no dos barras, sino el número de barras establecido en la variable m_bars_to_calculate:

//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0)
//--- In this case, it is necessary to calculate the number of bars specified in the m_bars_to_calculate variable - the current one and the rest
   if(this.m_limit<=1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- If this is the opening of a new bar and resizing the indicator buffer failed,
         if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total))
           {
            //--- add 'false' to the m_success variable and return 'false'
            //--- Here, an error message will be printed to log from the BufferResize method
            this.m_success=false;
            return false;
           }
         //--- If failed to copy bars in the amount of m_bars_to_calculate from the indicator's calculation part buffer,
         ::ResetLastError();
         if(!this.CopyArrays(i,this.m_bars_to_calculate))
           {
            //--- report this via the log, add 'false' to the m_success variable and return 'false'
            ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError());
            this.m_success &=false;
           }
        }
      //--- If the loop is followed by errors, return 'false'
      if(!this.m_success)
        {
         this.m_type_err=ERR_TYPE_NO_DATA;
         return false;
        }
      
      //--- Success
      this.m_type_err=ERR_TYPE_NO_ERROR;
      this.m_success=true;
      return true;
     }
//--- Undefined 'limit' option - return 'false'
   return false;
  }


El método que rellena el array dibujado transmitido con los datos del búfer de la clase ha sido completamente rediseñado. Ahora tiene en cuenta los matices del copiado de datos del marco temporal inferior al superior, del superior al inferior y la cantidad de datos que deben copiarse en el tick actual, que comentamos al principio de este artículo. La lógica de este método ha sido completamente reescrita y comentada:

//+------------------------------------------------------------------+
//| Fills the passed plot array with data from the class buffer      |
//+------------------------------------------------------------------+
bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const uint array_num,const int limit,double &buffer[])
  {
//--- Set the success flag
   this.m_success=true;
//--- Get the indexing direction of the buffer array passed to the method and,
//--- if non-timeseries indexing, set timeseries indexing
   bool as_series=::ArrayGetAsSeries(buffer);
   if(!as_series)
      ::ArraySetAsSeries(buffer,true);
//--- Set the symbol name and timeframe value passed to the method
   string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);

//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>1 && this.m_limit>1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num));
     }
      
//--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart)
   int count=(limit<=1 ? (int)this.m_bars_to_calculate : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
//--- In a loop from the zero bar to the value of the loop counter
   for(int i=0;i<count;i++)
     {
      //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
         buffer[i]=this.GetData(buffer_num,array_num,i);
      //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         //--- If the chart timeframe is lower than the timeframe of the class object,
         if(timeframe<this.m_timeframe)
           {
            //--- If this is historical data (a bar with an index greater than m_bars_to_calculate-1)
            if(i>(int)this.m_bars_to_calculate-1)
              {
               //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
               ::ResetLastError();
               if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1)
                 {
                  //--- If there is no data in the terminal, move on
                  if(::GetLastError()==4401)
                     continue;
                  //--- Error in obtaining existing data - return false
                  this.m_success &=false;
                  return false;
                 }
               //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array
               int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]);
               if(bar==WRONG_VALUE)
                 {
                  this.m_success &=false;
                  continue;
                 }
               //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
               buffer[i]=this.GetData(buffer_num,array_num,bar);
              }
            //--- If this is the current (zero) bar or a bar with an index less than or equal to m_bars_to_calculate-1
            else
              {
               //--- Get the time of bars by symbol/timeframe of the class object in the amount of m_bars_to_calculate
               if(::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_bars_to_calculate,this.m_array_time)!=this.m_bars_to_calculate)
                 {
                  this.m_success=false;
                  return false;
                 }
               
               //--- Get the number of bars of the current chart period contained in the chart period of the class object
               int num_bars=::PeriodSeconds(this.m_timeframe)/::PeriodSeconds(timeframe);
               if(num_bars<1)
                  num_bars=1;
               
               //--- In a loop through the array of received bar times
               for(int j=0;j<(int)this.m_array_time.Size();j++)
                 {
                  //--- Get the corresponding bar on the chart by the time of the bar in the buffer array of the class object
                  int barN=::iBarShift(symbol,timeframe,this.m_array_time[j]);
                  if(barN==WRONG_VALUE)
                    {
                     this.m_success &=false;
                     return false;
                    }
                  //--- Calculate the final bar on the chart to copy data from the buffer of the calculation part of the indicator 
                  int end=barN-num_bars;
                  if(end<0)
                     end=-1;
                  //--- In a loop from barN to end, copy data from the buffer array of the calculation part to the indicator buffer being drawn on the chart 
                  for(int n=barN;n>end;n--)
                     buffer[n]=this.GetData(buffer_num,array_num,this.m_bars_to_calculate-1-j);
                 }
              }
           }
         
         //--- If the chart timeframe is higher than the timeframe of the class object,
         else if(timeframe>this.m_timeframe)
           {
            //--- Get the number of bars of the class object chart period contained in the bar of the current chart period
            int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(this.m_timeframe);
            //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
            ::ResetLastError();
            if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1)
              {
               //--- If there is no data in the terminal, move on
               if(::GetLastError()==4401)
                  continue;
               //--- Error in obtaining existing data - return false
               this.m_success &=false;
               return false;
              }
            //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array
            //--- This bar will be the first of several bars of the class object in the amount of num_bars included in the chart bar
            int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]);
            if(bar==WRONG_VALUE)
              {
               this.m_success &=false;
               continue;
              }
            //--- Calculate the index of the last bar to copy in the loop
            int end=bar-num_bars;
            if(end<0)
               end=-1;
            //--- In a loop from the first to the last bar included in the bar of the older chart period 
            for(int j=bar;j>end;j--)
              {
               //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
               ::ResetLastError();
               if(::CopyTime(this.m_symbol,this.m_timeframe,j,1,this.m_array_time)!=1)
                 {
                  //--- If there is no data in the terminal, move on
                  if(::GetLastError()==4401)
                     continue;
                  //--- Error in obtaining existing data - return false
                  this.m_success &=false;
                  return false;
                 }
               //--- Get the bar number on the chart corresponding to the time of the buffer array of the calculation part by j loop index
               int barN=::iBarShift(symbol,timeframe,this.m_array_time[0]);
               //--- Get the data of the j bar in the buffer array of the calculation part and the data of barN contained in the chart indicator buffer
               double value_data=this.GetData(buffer_num,array_num,j);
               double value_buff=buffer[barN];
               //--- If the bar on the chart has an empty value, then write data from the buffer array of the calculation part into it. Or
               //--- if there is already data on the chart, then we write new data to this bar only if the data contains a non-empty value.
               if(value_buff==this.BufferInitValue(buffer_num) || (value_buff!=this.BufferInitValue(buffer_num) && value_data!=this.BufferInitValue(buffer_num)))
                  buffer[barN]=value_data;
              }
           }
        }
     }
//--- Set initial indexing of the buffer array passed to the method
   ::ArraySetAsSeries(buffer,as_series);
//--- Successful
   return true;
  }

La clase de objeto básico del indicador de símbolo y periodo múltiple está lista.

Ahora vamos a finalizar las clases herederas.

De todos los indicadores estándar, solo el indicador de fractales de Bill Williams tiene un dibujado en forma de flechas con interrupciones (Parabolic SAR también se dibuja con flechas, pero sin interrupciones en el gráfico).

Y el dibujo del indicador Fractals comienza en la barra con el índice 2, es decir, para el cálculo del indicador es necesario actualizar tres barras en cada tick: 2, 1 y 0.

En el constructor de la clase de este indicador, especificaremos un número de barras de cálculo igual a tres:

//+------------------------------------------------------------------+
//| Fractals indicator class                                         |
//+------------------------------------------------------------------+
class CIndFractals : public CIndMSTF
  {
public:
//--- Constructor
   CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Fractals");
      this.SetDescription("Fractals");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Up");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Down");
      
      //--- Set drawing style for buffers 0 and 1 and the numbers of calculation part data buffers
      this.SetBufferDrawType(0,DRAW_ARROW,UPPER_LINE);
      this.SetBufferDrawType(1,DRAW_ARROW,LOWER_LINE);
      
      //--- Set default colors for buffers 0 and 1
      this.SetBufferColorToIndex(UPPER_LINE,0,clrGray);
      this.SetBufferColorToIndex(LOWER_LINE,0,clrGray);
      
      //--- Set the number of bars required to calculate the indicator on the current tick
      this.SetBarsToCalculate(3);
     }
  };

En la clase básica, este número se establecerá por defecto en dos barras. Aquí redefiniremos la cantidad a tres barras y, en consecuencia, aumentaremos también a tres la dimensionalidad de los arrays temporales.


En la clase de indicador personalizado, no se sabe de antemano cuántas barras son necesarias para su cálculo en el tick actual. Esto significa que no podremos especificar de antemano el número de barras necesario, como se hace en el constructor de la clase del indicador de fractales. Aquí tenemos que especificar esta cantidad en los parámetros del constructor de la clase, transmitiéndola y estableciéndola a través de la variable de entrada:

//+------------------------------------------------------------------+
//| Custom indicator class                                           |
//+------------------------------------------------------------------+
class CIndCustom : public CIndMSTF
  {
public:
//--- Constructor
   CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const string path,                      // path to the indicator (for example, "Examples\\MACD.ex5")
              const string name,                      // name of the custom indicator
              const uint   buffers,                   // number of indicator buffers
              const uint   bars_to_calculate,         // number of bars required to calculate the indicator on the current tick
              const MqlParam &param[]                 // array of custom indicator parameters
             ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe)
     {
      //--- If an empty array of parameters is passed, print this to log
      int total=(int)param.Size();
      if(total==0)
         ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__);
      //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name)
      ResetLastError();
      if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1)
        {
         //--- Reset data in the array and enter name (path to file and name of .ex5 file)
         ::ZeroMemory(this.m_param);
         //--- name of the custom indicator
         this.m_param[0].type=TYPE_STRING;
         this.m_param[0].string_value=path;
         //--- fill the array of indicator parameters
         for(int i=0;i<total;i++)
           {
            this.m_param[i+1].type=param[i].type;
            this.m_param[i+1].double_value=param[i].double_value;
            this.m_param[i+1].integer_value=param[i].integer_value;
            this.m_param[i+1].string_value=param[i].string_value;
           }
         //--- Create description of parameters
         //--- If non-current chart symbol or period, their descriptions are added to parameters
         bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
         string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
         string param=(current ? "" : StringFormat("(%s)",symbol_period));
         //--- Write description of parameters, indicator name, its description, title and category
         this.SetParameters(param);
         this.SetName(name);
         this.SetDescription(name);
         this.m_title=this.Name()+this.Parameters();
         this.m_category=IND_CATEGORY_CUSTOM;
         //--- Write a description of the first line buffer
         this.SetBufferDescription(0,this.m_title);
         //--- Set the number of bars required to calculate the indicator on the current tick
         this.SetBarsToCalculate(bars_to_calculate);
        }
     }
  };


En la clase de colección de indicadores, en el método de creación de un nuevo objeto de indicador personalizado, también deberemos especificar el número de barras para calcular el indicador:

   int               AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,             // path to the indicator (for example, "Examples\\MACD.ex5")
                                                                                      const string name,             // name of custom indicator (for example, "Custom MACD")
                                                                                      const uint   buffers,          // number of buffers
                                                                                      const uint   bars_to_calculate,// number of bars required to calculate the indicator on the current tick
                                                                                      const MqlParam &param[]);      // Array of parameters
//--- Timer

mientras que en el código de implementación de este método transmitiremos el valor de esta variable al constructor de la clase de indicador personalizado:

//+------------------------------------------------------------------+
//| Add a custom indicator to the collection                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const uint bars_to_calculate,const MqlParam &param[])
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,bars_to_calculate,param);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }

Ahora las clases de indicadores de símbolo y periodo múltiple deberán trabajar con indicadores de flechas y aquellos indicadores cuyo búfer contenga búferes con interrupciones, es decir, indicadores con estilos de dibujado DRAW_ARROW, DRAW_COLOR_ARROW, DRAW_SECTION, DRAW_ZIGZAG, DRAW_COLOR_SECTION y DRAW_COLOR_ZIGZAG.


Simulación

Para la prueba tomaremos cualquier indicador de los creados anteriormente, por ejemplo Parabolis SAR, ya que también se dibuja con flechas. Tiene un búfer de dibujado, mientras que el indicador fractal tiene dos. Un búfer se utilizará para dibujar los fractales superiores, y el segundo se usará para dibujar los fractales inferiores. Modificaremos el código del indicador para que dibuje flechas en dos búferes y lo guardaremos con el nombre TestMSTFFractals.mq5.

El indicador mostrará los datos de dos indicadores Fractals a la vez en el gráfico: uno dibujará los fractales del periodo actual del gráfico, mientras que el segundo dibujará los fractales del indicador Fractals calculado sobre el símbolo y el periodo del gráfico especificados en los ajustes. Además, para mostrar estos indicadores fractales utilizaremos el panel de información que utilizamos en todos los indicadores de los artículos de esta serie:

//+------------------------------------------------------------------+
//|                                             TestMSTFFractals.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4 
//--- enums

//--- plot FractalsUp1
#property indicator_label1  "FractalsUp1"
#property indicator_type1   DRAW_ARROW
#property indicator_color1  clrGray
#property indicator_width1  1

//--- plot FractalsDown1
#property indicator_label2  "FractalsDown1"
#property indicator_type2   DRAW_ARROW
#property indicator_color2  clrGray
#property indicator_width2  1

//--- plot FractalsUp2
#property indicator_label3  "FractalsUp2"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrDodgerBlue
#property indicator_width3  1

//--- plot FractalsDown2
#property indicator_label4  "FractalsDown2"
#property indicator_type4   DRAW_ARROW
#property indicator_color4  clrDodgerBlue
#property indicator_width4  1

//--- includes
#include <IndMSTF\IndMSTF.mqh>
#include <Dashboard\Dashboard.mqh>
//--- input parameters
input string               InpSymbol      =  NULL;             /* Symbol                     */ // Moving average symbol
input ENUM_TIMEFRAMES      InpTimeframe   =  PERIOD_CURRENT;   /* Timeframe                  */ // Moving average timeframe
input uchar                InpArrowShift1 =  10;               /* Senior period Arrow VShift */ // Shift the arrows vertically for the higher period
input uchar                InpArrowShift2 =  10;               /* Junior period Arrow VShift */ // Shift the arrows vertically for the lower period
input bool                 InpAsSeries    =  true;             /* As Series flag             */ // Timeseries flag of indicator buffer arrays

//--- indicator buffers
double         BufferFractalsUp1[];
double         BufferFractalsDn1[];
double         BufferFractalsUp2[];
double         BufferFractalsDn2[];
//--- global variables
int handle_fractals1;
int handle_fractals2;
CMSTFIndicators indicators;      // An instance of the indicator collection object
//--- variables for the panel
CDashboard *panel=NULL;          // Pointer to the panel object
int         mouse_bar_index;     // Index of the bar the data is taken from
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set a timer with an interval of 1 second
   EventSetTimer(1);
//--- Assign the BufferFractalsUp1 and BufferFractalsDn1 arrays to the plot buffers 0 and 1, respectively
   SetIndexBuffer(0,BufferFractalsUp1,INDICATOR_DATA);
   SetIndexBuffer(1,BufferFractalsDn1,INDICATOR_DATA);
//--- Assign the BufferFractalsUp2 and BufferFractalsDn2 arrays to the plot buffers 2 and 3, respectively
   SetIndexBuffer(2,BufferFractalsUp2,INDICATOR_DATA);
   SetIndexBuffer(3,BufferFractalsDn2,INDICATOR_DATA);
//--- Define the symbol code from the Wingdings font to draw in PLOT_ARROW
   PlotIndexSetInteger(0,PLOT_ARROW,217);
   PlotIndexSetInteger(1,PLOT_ARROW,218);
   PlotIndexSetInteger(2,PLOT_ARROW,217);
   PlotIndexSetInteger(3,PLOT_ARROW,218);
//--- Set the vertical shift of the arrows 
   PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-InpArrowShift1);
   PlotIndexSetInteger(1,PLOT_ARROW_SHIFT, InpArrowShift1);
   PlotIndexSetInteger(2,PLOT_ARROW_SHIFT,-InpArrowShift2);
   PlotIndexSetInteger(3,PLOT_ARROW_SHIFT, InpArrowShift2);
//--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference)
   ArraySetAsSeries(BufferFractalsUp1,InpAsSeries);
   ArraySetAsSeries(BufferFractalsDn1,InpAsSeries);
   ArraySetAsSeries(BufferFractalsUp2,InpAsSeries);
   ArraySetAsSeries(BufferFractalsDn2,InpAsSeries);
   
//--- Create two indicators of the same type
//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   handle_fractals1=indicators.AddNewFractals(NULL,PERIOD_CURRENT);
   handle_fractals2=indicators.AddNewFractals(InpSymbol,InpTimeframe);

//--- If failed to create indicator handles, return initialization error
   if(handle_fractals1==INVALID_HANDLE || handle_fractals2==INVALID_HANDLE)
      return INIT_FAILED;
//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(0,handle_fractals1,0);
   indicators.SetPlotLabelFromBuffer(1,handle_fractals1,1);
   indicators.SetPlotLabelFromBuffer(2,handle_fractals2,0);
   indicators.SetPlotLabelFromBuffer(3,handle_fractals2,1);
      
//--- Dashboard
//--- Create the panel
   int width=311;
   panel=new CDashboard(1,20,20,width,264);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Set font parameters
   panel.SetFontParams("Calibri",9);
//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));

//--- Create a table with ID 0 to display bar data in it
   panel.CreateNewTable(0);
//--- Draw a table with ID 0 on the panel background
   panel.DrawGrid(0,2,20,6,2,18,width/2-2);

//--- Create a table with ID 1 to display the data of indicator 1
   panel.CreateNewTable(1);
//--- Get the Y2 table coordinate with ID 0 and
//--- set the Y1 coordinate for the table with ID 1
   int y1=panel.TableY2(0)+22;
//--- Draw a table with ID 1 on the panel background
   panel.DrawGrid(1,2,y1,2,2,18,width/2-2);

//--- Create a table with ID 2 to display the data of indicator 2
   panel.CreateNewTable(2);
//--- Get the Y2 coordinate of the table with ID 1 and
//--- set the Y1 coordinate for the table with ID 2
   int y2=panel.TableY2(1)+3;
//--- Draw a table with ID 2 on the background of the dashboard
   panel.DrawGrid(2,2,y2,3,2,18,width/2-2);
   
//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=0;
//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());

//--- Successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the timer
   EventKillTimer();
//--- If the panel object exists, delete it
   if(panel!=NULL)
      delete panel;
//--- Delete all comments
   Comment("");
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      /*
      // If the indicator has any buffers that display other calculations (not multi-indicators),
      // initialize them here with the "empty" value set for these buffers
      */
     }
//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      return 0;

//--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);

//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,0,0,limit,BufferFractalsUp1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,1,0,limit,BufferFractalsDn1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,0,0,limit,BufferFractalsUp2))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,1,0,limit,BufferFractalsDn2))
      return 0;

//--- return value of prev_calculated for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   | 
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Call the indicator collection timer
   indicators.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Handling the panel
//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Declare the variables to record time and price coordinates in them
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }

//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }
//+------------------------------------------------------------------+
//| Display data from the specified timeseries index to the panel    |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Declare the variables to receive data in them
   MqlRates rates[1];

//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Set font parameters for bar and indicator data headers
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,9);

//--- Display the data of the specified bar in table 0 on the panel
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Output the data of buffer 0 of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_fractals1)+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value10=indicators.GetData(handle_fractals1,0,0,index);
   string value_str10=(value10!=EMPTY_VALUE ? DoubleToString(value10,indicators.Digits(handle_fractals1)) : " ");
   panel.DrawText(value_str10,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110);
   
//--- Output the data of buffer 1 of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_fractals1)+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   double value11=indicators.GetData(handle_fractals1,1,0,index);
   string value_str11=(value11!=EMPTY_VALUE ? DoubleToString(value11,indicators.Digits(handle_fractals1)) : " ");
   panel.DrawText(value_str11,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110);
   
//--- Output the data of buffer 0 of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_fractals2)+" Up", panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2);
   double value20=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,0,0,index);
   string value_str20=(value20!=EMPTY_VALUE ? DoubleToString(value20,indicators.Digits(handle_fractals2)) : " ");
   panel.DrawText(value_str20,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110);
   
//--- Output the data of buffer 1 of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_fractals2)+" Down", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2);
   double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,1,0,index);
   string value_str21=(value21!=EMPTY_VALUE ? DoubleToString(value21,indicators.Digits(handle_fractals2)) : " ");
   panel.DrawText(value_str21,panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110);
   
//--- Redraw the chart to immediately display all changes on the panel
   ChartRedraw(ChartID());
  }
//+------------------------------------------------------------------+


Luego compilaremos el indicador y lo ejecutaremos en el gráfico M1, cambiando después el periodo del gráfico a M5 y M15:


Podemos ver como los búferes del indicador en el gráfico M1 se rellenan con los datos del indicador calculado para M5.

Al mismo tiempo, al cambiar el gráfico a M15, vemos cómo todos los fractales del indicador calculados en M5 se dibujan en las barras de M15. Al mismo tiempo, podemos ver que en cada barra del gráfico M15 donde hay fractales, estos, a juzgar por el gráfico, no deberían estar ahí. Pero no es así: justo en cada barra del gráfico M15 hay tres barras del periodo del gráfico M5, y estos fractales están ahí, y se muestran en las barras del gráfico M15, ya que el indicador está creado para eso, para ser un indicador de símbolo y periodo múltiple.


Conclusión

En el artículo de hoy, hemos creado y probado el funcionamiento de las clases de los indicadores de símbolo periodo múltiple con estilo de dibujado DRAW_ARROW. En los próximos artículos intentaremos abarcar el resto de estilos de dibujado que implican interrupciones de datos en el gráfico, y terminar con el resto de indicadores estándar que aún no se han analizado, cerrando así el tema de los indicadores de símbolo y periodo múltiple.

Todos los archivos utilizados en el artículo (las clases de indicador múltiple, la clase de panel y el archivo del indicador) se adjuntan al artículo para su prueba y uso independiente.



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

Archivos adjuntos |
IndMSTF.mqh (645.11 KB)
Dashboard.mqh (217.99 KB)
Introducción a MQL5 (Parte 3): Estudiamos los elementos básicos de MQL5 Introducción a MQL5 (Parte 3): Estudiamos los elementos básicos de MQL5
En este artículo, seguiremos estudiando los fundamentos de la programación MQL5. Hoy veremos los arrays, las funciones definidas por el usuario, los preprocesadores y el procesamiento de eventos. Para una mayor claridad, todos los pasos de cada explicación irán acompañado de un código. Esta serie de artículos sienta las bases para el aprendizaje de MQL5, prestando especial atención a la explicación de cada línea de código.
Desarrollo de un sistema de repetición (Parte 50): Esto complica las cosas (II) Desarrollo de un sistema de repetición (Parte 50): Esto complica las cosas (II)
Vamos resolver la cuestión del ID del gráfico, pero al mismo tiempo, vamos empezar a garantizar que el usuario pueda hacer uso de una plantilla personal, enfocada en analizar el activo que desea estudiar y simular. El contenido expuesto aquí tiene como objetivo, pura y simplemente, ser didáctico. En ningún caso debe considerarse como una aplicación cuya finalidad no sea el aprendizaje y el estudio de los conceptos mostrados.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Desarrollo de un sistema de repetición (Parte 49): Esto complica las cosas (I) Desarrollo de un sistema de repetición (Parte 49): Esto complica las cosas (I)
En este artículo complicaremos un poco las cosas. Utilizando lo que vimos en los artículos anteriores, comenzaremos a liberar el archivo de plantilla para que el usuario pueda utilizar una plantilla personalizada. Sin embargo, haré los cambios poco a poco, ya que también modificaré el indicador con el fin de reducir la carga de MetaTrader 5.