Trabajando con las series temporales en la biblioteca DoEasy (Parte 46): Búferes de indicador de periodo y símbolo múltiples

27 octubre 2020, 09:13
Artyom Trishkin
0
359

Contenido


Concepto

La biblioteca creada ya contiene la funcionalidad para crear y ofrecer seguimiento a los búferes de indicador multiperiodo. Ahora, solo nos queda añadir la funcionalidad necesaria para trabajar en el modo multisímbolo, y así poder solucionar con libertad las tareas posteriores relacionadas con el desarrollo de los recursos ofrecidos por la biblioteca, para utilizar estos en nuestros programas.

Resulta que el trabajo realizado anteriormente con las clases de los objetos de búfer ya nos ofrece esta funcionalidad, con tan solo realizar unas mínimas mejoras. Por eso, hoy vamos a realizar dicho trabajo y a simplificar la creación de los indicadores estándar de periodo y símbolo múltiples.
Para ello, necesitaremos completar la clase del objeto de búfer de cálculo de tal forma que pueda recibir en su matriz los datos de una matriz según el manejador del indicador estándar. En principio, la biblioteca ya permite hacer esto, pero las pequeñas mejoras introducidas facilitarán sustancialmente dicha tarea, permitiendo mostrar de inmediato los datos en el gráfico actual de los indicadores estándar de cualquier símbolo/periodo.

En artículos posteriores, trasladaremos el concepto que hoy pondremos a prueba a las clases para trabajar con los datos de los indicadores estándar desde cualquier símbolo/periodo, simplificando la creación de los indicadores estándar de periodo y símbolo múltiples.

Mejorando las clases de los objetos de búfer para trabajar con cualquier símbolo

La clase del objeto de búfer abstracto básico contiene los valores de los índices de las matrices para los siguientes búferes de indicador, es decir, aquellos que pueden ser creados después del actual. En este objeto actual, tenemos que acudir a cálculos simples de los índices de los búferes de indicador posteriores, dependiendo del tipo de objeto de búfer actual y su estilo de dibujado; en este caso, además, el cálculo también considera la ausencia del búfer de color para el búfer de indicador con el estilo de dibujo "Rellenado de color entre dos niveles".

Para simplificar el cálculo de los índices de los búferes posteriores y no inducir a confusión en cuanto a la claridad de los cálculos al considerar los diferentes factores, podemos hacerlo todo bastante más simple. Vamos a crear una variable de miembro de clase que contenga el número total de todas las matrices utilizadas para construir el objeto de búfer. Este valor estrictamente establecido se indica al crear cada objeto de búfer (descendiente de la clase de búfer abstracta) transmitiendo el valor necesario al constructor de clase del objeto básico protegido en los parámetros del constructor de la clase descendiente.

Parece complicado, pero es en realidad muy simple: cuando creamos un nuevo objeto indicador de búfer, ya transmitimos algunos valores en el constructor de la clase del objeto búfer a su constructor de la clase padre. Solo añadiremos un valor adicional. Precisamente este valor extra será para cada objeto de búfer el número de matrices necesario para su construcción.

Abrimos el archivo de la clase del objeto básico del búfer de indicador abstracto \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh e introducimos en él los cambios necesarios.

En la sección privada de la clase, declaramos la nueva variable:

//+------------------------------------------------------------------+
//| Abstract indicator buffer class                                  |
//+------------------------------------------------------------------+
class CBuffer : public CBaseObj
  {
private:
   long              m_long_prop[BUFFER_PROP_INTEGER_TOTAL];                     // Integer properties
   double            m_double_prop[BUFFER_PROP_DOUBLE_TOTAL];                    // Real properties
   string            m_string_prop[BUFFER_PROP_STRING_TOTAL];                    // String properties
   bool              m_act_state_trigger;                                        // Auxiliary buffer status switch flag
   uchar             m_total_arrays;                                             // Total number of buffer arrays

En los parámetros de entrada de la declaración del constructor paramétrico protegido de la clase, declaramos una variable adicional, con cuya ayuda transmitiremos a la clase el número total de todas las matrices del objeto de búfer al crear este:

//--- Default constructor
                     CBuffer(void){;}
protected:
//--- Protected parametric constructor
                     CBuffer(ENUM_BUFFER_STATUS status_buffer,
                             ENUM_BUFFER_TYPE buffer_type,
                             const uint index_plot,
                             const uint index_base_array,
                             const int num_datas,
                             const uchar total_arrays,
                             const int width,
                             const string label);
public:  

En el código de implementación del constructor, añadimos también esta variable a la lista de parámetros de entrada, y asignamos el valor transmitido a través de ella a la variable privada declarada anteriormente. A continuación, utilizaremos este valor para calcular el índice de la matriz básica para el posterior objeto de búfer. Al calcular el índice de la matriz de colores, comprobaremos el tipo de búfer, y si se trata de un búfer de dibujado, calcularemos el índice añadiendo al índice de la matriz básica el número total de las matrices de datos, mientras que para el búfer de cálculo, añadiremos cero, dado que el búfer de cálculo no tiene matriz de color:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 const uint index_plot,
                 const uint index_base_array,
                 const int num_datas,
                 const uchar total_arrays,
                 const int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE      : 
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   : 
      ENUM_DRAW_TYPE(this.Status()+8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                     = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                     = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                        = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                    = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                   = 0;
   this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                    = 0;
   this.m_long_prop[BUFFER_PROP_SHOW_DATA]                     = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
   this.m_long_prop[BUFFER_PROP_SHIFT]                         = 0;
   this.m_long_prop[BUFFER_PROP_LINE_STYLE]                    = STYLE_SOLID;
   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                    = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]                 = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0);
   this.m_long_prop[BUFFER_PROP_COLOR]                         = clrRed;
   this.m_long_prop[BUFFER_PROP_NUM_DATAS]                     = num_datas;
   this.m_long_prop[BUFFER_PROP_INDEX_PLOT]                    = index_plot;
   this.m_long_prop[BUFFER_PROP_INDEX_BASE]                    = index_base_array;
   this.m_long_prop[BUFFER_PROP_INDEX_COLOR]                   = this.GetProperty(BUFFER_PROP_INDEX_BASE)+
                                                                   (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = index_base_array+this.m_total_arrays;
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT]               = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot);
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0);
//--- Save string properties
   this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)]      = ::Symbol();
   this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)]       = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL);

//--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string
   if(::ArrayResize(this.DataBuffer,(int)this.GetProperty(BUFFER_PROP_NUM_DATAS))==WRONG_VALUE)
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError());
      
//--- If failed to change the size of the color array (only for a non-calculated buffer), display the appropriate message indicating the string
   if(this.TypeBuffer()>BUFFER_TYPE_CALCULATE)
      if(::ArrayResize(this.ArrayColors,(int)this.ColorsTotal())==WRONG_VALUE)
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError());

//--- For DRAW_FILLING, fill in the color array with two default colors
   if(this.Status()==BUFFER_STATUS_FILLING)
     {
      this.SetColor(clrBlue,0);
      this.SetColor(clrRed,1);
     }

//--- Bind indicator buffers with arrays
//--- In a loop by the number of indicator buffers
   int total=::ArraySize(DataBuffer);
   for(int i=0;i<total;i++)
     {
      //--- calculate the index of the next array and
      //--- bind the indicator buffer by the calculated index with the dynamic array
      //--- located by the i loop index in the DataBuffer array
      int index=(int)this.GetProperty(BUFFER_PROP_INDEX_BASE)+i;
      ::SetIndexBuffer(index,this.DataBuffer[i].Array,(this.TypeBuffer()==BUFFER_TYPE_DATA ? INDICATOR_DATA : INDICATOR_CALCULATIONS));
      //--- Set indexation flag as in the timeseries to all buffer arrays
      ::ArraySetAsSeries(this.DataBuffer[i].Array,true);
     }
//--- Bind the color buffer with the array (only for a non-calculated buffer and not for the filling buffer)
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
     {
      ::SetIndexBuffer((int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX);
      ::ArraySetAsSeries(this.ColorBufferArray,true);
     }
//--- If this is a calculated buffer, all is done
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
//--- Set integer parameters of the graphical series
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH));
   this.SetColor((color)this.GetProperty(BUFFER_PROP_COLOR));
//--- Set real parameters of the graphical series
   ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE));
//--- Set string parameters of the graphical series
   ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
  }
//+------------------------------------------------------------------+


Ahora, en todas las clases de los objetos descendientes del objeto básico del búfer abstracto , añadimos a los constructores de las clases (en su lista de inicialización) la transmisión del valor necesario del número de matrices utilizadas para la construcción del búfer.

Para el búfer de flechas (\MQL5\Include\DoEasy\Objects\Indicators\BufferArrow.mqh):

//+------------------------------------------------------------------+
//| Buffer with the "Drawing with arrows" drawing style              |
//+------------------------------------------------------------------+
class CBufferArrow : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferArrow(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_ARROW,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Arrows") {}

Para el búfer de líneas (\MQL5\Include\DoEasy\Objects\Indicators\BufferLine.mqh):

//+------------------------------------------------------------------+
//| Buffer of the Line drawing style                                 |
//+------------------------------------------------------------------+
class CBufferLine : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferLine(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_LINE,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Line") {}

Para el búfer de segmentos (\MQL5\Include\DoEasy\Objects\Indicators\BufferSection.mqh):

//+------------------------------------------------------------------+
//| Buffer of the Section drawing style                              |
//+------------------------------------------------------------------+
class CBufferSection : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferSection(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_SECTION,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Section") {}

Para el búfer de histograma de la línea cero (\MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram.mqh):

//+------------------------------------------------------------------+
//| Buffer of the "Histogram from the zero line" drawing style       |
//+------------------------------------------------------------------+
class CBufferHistogram : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferHistogram(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_HISTOGRAM,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,2,"Histogram") {}

Para el búfer de histograma de dos búferes de indicador (\MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram2.mqh):

//+--------------------------------------------------------------------+
//|Buffer of the "Histogram on two indicator buffers" drawing style    |
//+--------------------------------------------------------------------+
class CBufferHistogram2 : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferHistogram2(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_HISTOGRAM2,BUFFER_TYPE_DATA,index_plot,index_base_array,2,3,8,"Histogram2 0;Histogram2 1") {}

Para el búfer de zigzag (\MQL5\Include\DoEasy\Objects\Indicators\BufferZigZag.mqh):

//+------------------------------------------------------------------+
//|Buffer of the ZigZag drawing style                                |
//+------------------------------------------------------------------+
class CBufferZigZag : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferZigZag(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_ZIGZAG,BUFFER_TYPE_DATA,index_plot,index_base_array,2,3,1,"ZigZag 0;ZigZag 1") {}

Para el búfer de rellenado (\MQL5\Include\DoEasy\Objects\Indicators\BufferFilling.mqh):

//+------------------------------------------------------------------+
//|Buffer of the "Color filling between two levels" drawing style    |
//+------------------------------------------------------------------+
class CBufferFilling : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferFilling(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_FILLING,BUFFER_TYPE_DATA,index_plot,index_base_array,2,2,1,"Filling 0;Filling 1") {}

Para el búfer de dibujado en forma de barras (\MQL5\Include\DoEasy\Objects\Indicators\BufferBars.mqh):

//+------------------------------------------------------------------+
//|Buffer of the Bars drawing style                                  |
//+------------------------------------------------------------------+
class CBufferBars : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferBars(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_BARS,BUFFER_TYPE_DATA,index_plot,index_base_array,4,5,2,"Bar Open;Bar High;Bar Low;Bar Close") {}

Para el búfer de dibujado en forma de velas (\MQL5\Include\DoEasy\Objects\Indicators\BufferCandles.mqh):

//+------------------------------------------------------------------+
//|Buffer of the Candles drawing style                               |
//+------------------------------------------------------------------+
class CBufferCandles : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferCandles(const uint index_plot,const uint index_base_array) : 
                        CBuffer(BUFFER_STATUS_CANDLES,BUFFER_TYPE_DATA,index_plot,index_base_array,4,5,1,"Candle Open;Candle High;Candle Low;Candle Close") {}

Para el búfer de cálculo (\MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh):

//+------------------------------------------------------------------+
//| Calculated buffer                                                |
//+------------------------------------------------------------------+
class CBufferCalculate : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferCalculate(const uint index_plot,const uint index_array) :
                        CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,1,1,0,"Calculate") {}

Estos cambios nos evitan realizar comprobaciones sobre el tipo de búfer y el estilo de dibujado al calcular los índices de los búferes creados posteriormente, ya que siempre usaremos el mismo número de matrices para cada tipo de búfer: lo transmitiremos mediante valores rigurosamente establecidos al crear el búfer.

En la clase del búfer de indicador, añadimos los nuevos métodos, con cuya ayuda podremos registrar en la matriz del búfer de indicador los datos del manejador del indicador estándar:

//+------------------------------------------------------------------+
//| Calculated buffer                                                |
//+------------------------------------------------------------------+
class CBufferCalculate : public CBuffer
  {
private:

public:
//--- Constructor
                     CBufferCalculate(const uint index_plot,const uint index_array) :
                        CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,1,1,0,"Calculate") {}
//--- Supported integer properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property);
//--- Supported real properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property);
//--- Supported string properties of a buffer
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property);
//--- Display a short buffer description in the journal
   virtual void      PrintShort(void);
   
//--- Set the value to the data buffer array
   void              SetData(const uint series_index,const double value)               { this.SetBufferValue(0,series_index,value);       }
//--- Return the value from the data buffer array
   double            GetData(const uint series_index)                            const { return this.GetDataBufferValue(0,series_index);  }
   
//--- Copy data of the specified indicator to the buffer object array
   int               FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count);
   int               FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const int count);
   int               FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const datetime stop_time);
   
  };
//+------------------------------------------------------------------+

Los implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Copy data of the specified indicator to the buffer object array  |
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,start_pos,count,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const int count)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,start_time,count,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const datetime stop_time)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,start_time,stop_time,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+

Los tres métodos usan tres variantes de la función sobrecargada CopyBuffer(). Como matriz receptora, actuará la matriz designada por el búfer de indicador del objeto de búfer desde el que se llaman estos métodos para registrar en la matriz del objeto los datos necesarios del indicador según su manejador.

Ahora, vamos a implementar el modo multisímbolo de trabajo con los objetos de búfer. Y aquí surgen un par de omisiones cometidas al crear el material del artículo anterior, donde implementamos el modo multiperiodo.
En la clase de colección de los búferes de indicador, creamos un método para obtener los datos de las series temporales y las barras necesarias para trabajar con una barra del búfer. Precisamente en este método existen todos los datos necesarios para los periodos de los gráficos (del objeto de búfer actual y el indicado), y para los símbolos (del actual y el designado al objeto de búfer). Aquí está el método con el aspecto del artículo anterior:

//+------------------------------------------------------------------+
//| Get data of the necessary timeseries and bars                    |
//| for working with a single bar of the buffer                      |
//+------------------------------------------------------------------+
int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period)
  {
//--- Get timeseries of the current chart and the chart of the buffer timeframe
   CSeriesDE *series_current=this.m_timeseries.GetSeries(buffer.Symbol(),PERIOD_CURRENT);
   CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe());
   if(series_current==NULL || series_period==NULL)
      return WRONG_VALUE;
//--- Get the bar object of the current timeseries corresponding to the required timeseries index
   CBar *bar_current=series_current.GetBar(series_index);
   if(bar_current==NULL)
      return WRONG_VALUE;
//--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into
   CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),NULL,series_period.Timeframe());
   if(bar_period==NULL)
      return WRONG_VALUE;
//--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart
   index_bar_period=bar_period.Index(PERIOD_CURRENT);
//--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period
//--- and return this value (1 if the result is 0)
   int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe());
   return(num_bars>0 ? num_bars : 1);
  }
//+------------------------------------------------------------------+

Si nos fijamos con atención, veremos que solo hemos omitido en dos lugares la obtención de datos del símbolo necesario: para la serie temporal del gráfico actual hemos obtenido los datos del gráfico del símbolo asignado al objeto de búfer. En el segundo sitio, sucede lo contrario: hemos obtenido el símbolo del gráfico actual donde deberíamos tomar el símbolo del objeto de búfer.

Como resultado, solo debemos introducir dos correcciones en dos líneas del código.

Listado completo del método corregido:

//+------------------------------------------------------------------+
//| Get data of the necessary timeseries and bars                    |
//| for working with a single bar of the buffer                      |
//+------------------------------------------------------------------+
int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period)
  {
//--- Get timeseries of the current chart and the chart of the buffer timeframe
   CSeriesDE *series_current=this.m_timeseries.GetSeries(Symbol(),PERIOD_CURRENT);
   CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe());
   if(series_current==NULL || series_period==NULL)
      return WRONG_VALUE;
//--- Get the bar object of the current timeseries corresponding to the required timeseries index
   CBar *bar_current=series_current.GetBar(series_index);
   if(bar_current==NULL)
      return WRONG_VALUE;
//--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into
   CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),buffer.Symbol(),series_period.Timeframe());
   if(bar_period==NULL)
      return WRONG_VALUE;
//--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart
   index_bar_period=bar_period.Index(PERIOD_CURRENT);
//--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period
//--- and return this value (1 if the result is 0)
   int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe());
   return(num_bars>0 ? num_bars : 1);
  }
//+------------------------------------------------------------------+

Ya está todo. Ahora, nuestro objetos de búfer también pueden trabajar en el modo multisímbolo.

Aún nos falta un método que retorne el índice de una barra en el gráfico del símbolo/periodo en el que se encuentra el índice de la barra indicada del gráfico actual. El método resulta imprescindible para representar los datos de otro símbolo periodo en el gráfico actual.

El lugar más adecuado para este método es la clase de colección de series temporales \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.
Declaramos en esta el nuevo método:

//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time
//--- bar object of the first timeseries corresponding to the bar open time on the second timeseries (3) by index, (4) by time
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true);
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);

//--- Return the bar index on the specified timeframe chart by the current chart's bar index                                |
   int                     IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe);

//--- Return the flag of opening a new bar of the specified timeseries of the specified symbol
   bool                    IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0);

Lo implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Return the bar index on the specified timeframe chart            |
//| by the current chart's bar index                                 |
//+------------------------------------------------------------------+
int CTimeSeriesCollection::IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CSeriesDE *series=this.GetSeries(::Symbol(),(ENUM_TIMEFRAMES)::Period());
   if(series==NULL)
      return WRONG_VALUE;
   CBar *bar=series.GetBar(series_index);
   if(bar==NULL)
      return WRONG_VALUE;
   return ::iBarShift(symbol,timeframe,bar.Time());
  }
//+------------------------------------------------------------------+

Transmitimos al método el índice de la barra del gráfico actual, y el símbolo y el periodo del gráfico para el que se debe retornar el índice de la barra que se corresponde con la hora del índice del gráfico actual transmitido al método.

A continuación, obtenemos el puntero a la serie temporal del gráfico actual, obtenemos el puntero al objeto de barra según el índice de la serie temporal actual, y
según la hora de la barra, retornamos el índice de la barra correspondiente a la serie temporal correspondiente.

Como en el búfer de cálculo se guardarán los datos del búfer de indicador en plena correspondencia con la serie temporal cuyos datos han servido para crear el indicador, utilizaremos este método para obtener el índice en la matriz del búfer de cálculo que se corresponde con el índice de la barra indicada en el gráfico actual (como ejemplo práctico, el índice del ciclo del indicador). Y esto significa que, como podemos establecer esta correspondencia de datos entre dos series temporales, también podremos representar correctamente estos datos en el gráfico necesario.

Para acceder a este método desde nuestros programas, deberemos ofrecer acceso al mismo desde la clase del objeto básico de la biblioteca CEngine
(\MQL5\Include\DoEasy\Engine.mqh):

//--- Clear data by the timeseries index for the (1) arrow, (2) line, (3) section, (4) zero line histogram,
//--- (5) histogram on two buffers, (6) zigzag, (7) filling, (8) bars and (9) candles
   void                 BufferArrowClear(const int number,const int series_index)         { this.m_buffers.ClearBufferArrow(number,series_index);     }
   void                 BufferLineClear(const int number,const int series_index)          { this.m_buffers.ClearBufferLine(number,series_index);      }
   void                 BufferSectionClear(const int number,const int series_index)       { this.m_buffers.ClearBufferSection(number,series_index);   }
   void                 BufferHistogramClear(const int number,const int series_index)     { this.m_buffers.ClearBufferHistogram(number,series_index); }
   void                 BufferHistogram2Clear(const int number,const int series_index)    { this.m_buffers.ClearBufferHistogram2(number,series_index);}
   void                 BufferZigZagClear(const int number,const int series_index)        { this.m_buffers.ClearBufferZigZag(number,series_index);    }
   void                 BufferFillingClear(const int number,const int series_index)       { this.m_buffers.ClearBufferFilling(number,series_index);   }
   void                 BufferBarsClear(const int number,const int series_index)          { this.m_buffers.ClearBufferBars(number,series_index);      }
   void                 BufferCandlesClear(const int number,const int series_index)       { this.m_buffers.ClearBufferCandles(number,series_index);   }

//--- Return the bar index on the specified timeframe chart by the current chart's bar index
   int                  IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe)
                          { return this.m_time_series.IndexBarPeriodByBarCurrent(series_index,symbol,timeframe);  }

//--- Display short description of all indicator buffers of the buffer collection
   void                 BuffersPrintShort(void);

A día de hoy, estas son todas las mejoras realizadas sobre las clases de la biblioteca para simular la creación y el trabajo con los indicadores de símbolo y periodo múltiples.

Para la prueba, vamos a crear dos indicadores de símbolo y periodo múltiples: una media móvil y un MACD que dibuje sus datos (obtenidos del símbolo/periodo establecido) en el gráfico actual. En los ajustes del indicador, estableceremos los parámetros del indicador y el símbolo con el periodo del gráfico de los que deberemos obtener los datos del indicador estándar.

Prueba: media móvil de periodo y símbolo múltiples

Para la simulación, vamos a tomar el indicador del artículo anterior y guardarlo en la nueva carpeta \MQL5\Indicators\TestDoEasy\Part46\
con el nombre TestDoEasyPart46_1.mq5.

El indicador mostrará en una subventana aparte las velas del símbolo y el periodo especificado en los ajustes. En dicha subventana mostrará también la media móvil con los parámetros especificados, con el mismo símbolo/periodo que las velas.

Establecemos para el indicador la muestra de datos del indicador en la subventana del gráfico, añadimos la introducción de los valores del símbolo y el periodo del gráfico para el indicador, los parámetros de entrada para la media móvil, y establecemos las variables globales para corregir los parámetros introducidos de la МА:

//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 8
#property indicator_plots   2

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
//---
sinput   uint                 InpPeriodMA       =  14;            // MA Period
sinput   int                  InpShiftMA        =  0;             // MA Shift
sinput   ENUM_MA_METHOD       InpMethodMA       =  MODE_SMA;      // MA Method
sinput   ENUM_APPLIED_PRICE   InpPriceMA        =  PRICE_CLOSE;   // MA Applied Price
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds

//--- indicator buffers
CArrayObj     *list_buffers;                                      // Pointer to the buffer object list
//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // List of used timeframes
CEngine              engine;                                      // CEngine library main object
string               prefix;                                      // Prefix of graphical object names
int                  min_bars;                                    // The minimum number of bars for the indicator calculation
int                  used_symbols_mode;                           // Mode of working with symbols
string               array_used_symbols[];                        // The array for passing used symbols to the library
string               array_used_periods[];                        // The array for passing used timeframes to the library
int                  handle_ma;                                   // МА handle
int                  period_ma;                                   // Moving Average calculation period
//+------------------------------------------------------------------+

En el manejador OnInit(), creamos tres objetos de búfer: el primero, para la línea de МА, el segundo, para las velas del símbolo seleccionado, y el tercero, para el búfer de cálculo encargado de guardar los datos de la media móvil obtenidos del símbolo/periodo seleccionado.
A continuación, establecemos para el búfer de velas las descripciones de las cuatro matrices de búferes para la ventana de datos, y hacemos lo mismo para el búfer de líneas de la media móvil. Al finalizar, creamos el manejador del indicador Moving Average:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Initialize DoEasy library
   OnInitDoEasy();
   IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+1);
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- Get the index of the maximum used timeframe in the array,
   //--- calculate the number of bars of the current period fitting in the maximum used period
   //--- Use the obtained value if it exceeds 2, otherwise use 2
   int index=ArrayMaximum(ArrayUsedTimeframes);
   int num_bars=NumberBarsInTimeframe(ArrayUsedTimeframes[index]);
   min_bars=(index>WRONG_VALUE ? (num_bars>2 ? num_bars : 2) : 2);

//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel

//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping
//--- Create all the necessary buffer objects
   engine.BufferCreateLine();          // 2 arrays
   engine.BufferCreateCandles();       // 5 arrays
   engine.BufferCreateCalculate();     // 1 array

//--- Check the number of buffers specified in the 'properties' block
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Create the color array and set non-default colors to all buffers within the collection
   color array_colors[]={clrDodgerBlue,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);
    
//--- Set МА period
   period_ma=int(InpPeriodMA<2 ? 2 : InpPeriodMA);

//--- In a loop by the list of collection buffer objects,
   for(int i=0;i<engine.GetListBuffers().Total();i++)
     { 
      //--- get the next buffer
      CBuffer *buff=engine.GetListBuffers().At(i);
      if(buff==NULL)
         continue;
      //--- and set its display in the data window depending on its specified usage
      //--- and also a chart period and symbol selected in the settings
      buff.SetShowData(true);
      buff.SetTimeframe(InpPeriod);
      buff.SetSymbol(InpUsedSymbols);
      if(buff.Status()==BUFFER_STATUS_CANDLES)
        {
         string pr=InpUsedSymbols+" "+TimeframeDescription(InpPeriod)+" ";
         string label=pr+"Open;"+pr+"High;"+pr+"Low;"+pr+"Close";
         buff.SetLabel(label);
        }
      if(buff.Status()==BUFFER_STATUS_LINE)
        {
         string label="MA("+(string)period_ma+")";
         buff.SetLabel(label);
        }
     }
     
//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();

//--- Create МА handle
   handle_ma=iMA(InpUsedSymbols,InpPeriod,period_ma,InpShiftMA,InpMethodMA,InpPriceMA);
   if(handle_ma==INVALID_HANDLE)
      return INIT_FAILED;
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

En el manejador OnCalculate(), en el bloque de preparación de datos, deberemos copiar los datos de la media móvil en nuestro búfer de cálculo.

Para no copiar toda la matriz de datos МА en cada tick, deberemos partir del hecho de que la variable limit se calcula de forma que en el primer inicio o con el cambio de los datos históricos, tiene un valor superior a 1, en la apertura de una nueva barra tiene un valor de 1, y el resto del tiempo, tiene un valor de cero.
No podemos copiar los datos apoyándonos en el valor de limit: no podemos copiar cero barras. Esto significa que deberemos copiar una barra con un valor limit igual a cero, y en el resto de los casos, copiaremos el número que se indique en limit. De esta forma, organizaremos un copiado bastante ahorrativo de los datos actuales de la media móvil a nuestro búfer de cálculo:

//--- Prepare data
   CBufferCalculate *buff_calc=engine.GetBufferCalculate(0);
   int total_copy=(limit<2 ? 1 : limit);
   int copied=buff_calc.FillAsSeries(handle_ma,0,0,total_copy);
   if(copied<total_copy)
      return 0;
        

Básicamente, primero limpiamos en un ciclo por el indicador la barra actual de todos los búferes de dibujado del indicador (para librarnos de los valores innecesarios), y después calculamos la línea de МА y las velas del símbolo seleccionado recalculando su representación en el gráfico actual:

//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- Clear the current bar of all created buffers
      engine.BufferLineClear(0,0);
      engine.BufferCandlesClear(0,0);
      
      //--- Get the timeseries bar corresponding to the loop index time on the chart period specified in the settings
      bar=engine.SeriesGetBar(InpUsedSymbols,InpPeriod,time[i]);
      if(bar==NULL)
         continue;
      //--- Calculate the color index depending on the candle direction on the timeframe specified in the settings
      color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2);
      //--- Calculate the MA line buffer
      int index=engine.IndexBarPeriodByBarCurrent(i,InpUsedSymbols,InpPeriod);
      if(index<0)
         continue;
      engine.BufferSetDataLine(0,i,buff_calc.GetData(index),color_index);
      //--- Calculate the candle buffer
      engine.BufferSetDataCandles(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index);
     }
//--- return value of prev_calculated for next call

Podrá ver el código completo del indicador en los archivos adjuntos al artículo.

Vamos a iniciar el indicador en EURUSD M15, estableciendo en los ajustes el símbolo GBPUSD M30 y una media móvil simple de los precios Close con un periodo 14 y un desplazamiento 0:


Para comparar, hemos abierto un gráfico de GBPUSD con el indicador Moving Average con los mismos parámetros.


Prueba: MACD de periodo y símbolo múltiples

Ahora, vamos a crear un MACD de símbolo y periodo múltiples. Guardamos el asesor recién creado con el nuevo nombre TestDoEasyPart46_2.mq5.

Establecemos los parámetros de entrada para MACD, así como todos los búferes necesarios para su cálculo y representación. Vamos a necesitar dos búferes de dibujado: un histograma y una línea para representar MACD en el gráfico actual, y dos búferes de cálculo para guardar los datos del histograma y la línea de señal de MACD obtenidos del símbolo/periodo indicado en los ajustes.

Hemos intentado describir con detalle todas las acciones y la lógica en los comentarios al código, así que aquí solo veremos los cambios principales respecto al indicador anterior:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart46_2.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 6
#property indicator_plots   2

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
//---
sinput   uint                 InpPeriodFastEMA  =  12;            // MACD Fast EMA Period
sinput   uint                 InpPeriodSlowEMA  =  26;            // MACD Slow EMA Period
sinput   uint                 InpPeriodSignalMA =  9;             // MACD Signal MA Period
sinput   ENUM_APPLIED_PRICE   InpPriceMACD      =  PRICE_CLOSE;   // MA Applied Price
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers
CArrayObj     *list_buffers;                                      // Pointer to the buffer object list
//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // List of used timeframes
CEngine              engine;                                      // CEngine library main object
string               prefix;                                      // Prefix of graphical object names
int                  min_bars;                                    // The minimum number of bars for the indicator calculation
int                  used_symbols_mode;                           // Mode of working with symbols
string               array_used_symbols[];                        // The array for passing used symbols to the library
string               array_used_periods[];                        // The array for passing used timeframes to the library
int                  handle_macd;                                 // МАCD handle
int                  fast_ema_period;                             // Fast EMA calculation period
int                  slow_ema_period;                             // Slow EMA calculation period
int                  signal_period;                               // MACD signal line calculation period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Initialize DoEasy library
   OnInitDoEasy();
   IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+1);
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- calculate the number of bars of the current period fitting in the maximum used period
   //--- Use the obtained value if it exceeds 2, otherwise use 2
   int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>2 ? num_bars : 2);

//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel

//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing MACD
   engine.BufferCreateHistogram();     // 2 arrays
   engine.BufferCreateLine();          // 2 arrays
   engine.BufferCreateCalculate();     // 1 array for MACD histogram data from the specified symbol/period
   engine.BufferCreateCalculate();     // 1 array for MACD signal line from the specified symbol/period

//--- Check the number of buffers specified in the 'properties' block
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Create the color array and set non-default colors to all buffers within the collection
   color array_colors[]={clrDodgerBlue,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);

//--- Set МАCD calculation periods
   fast_ema_period=int(InpPeriodFastEMA<1 ? 1 : InpPeriodFastEMA);
   slow_ema_period=int(InpPeriodSlowEMA<1 ? 1 : InpPeriodSlowEMA);
   signal_period=int(InpPeriodSignalMA<1  ? 1 : InpPeriodSignalMA);

//--- Get the histogram buffer (the first drawn buffer)
//--- It has the index of 5 considering that the starting point is zero
   CBufferHistogram *buff_hist=engine.GetBufferHistogram(0);
   if(buff_hist!=NULL)
     {
      //--- Set the line width for the histogram
      buff_hist.SetWidth(3);
      //--- Set the graphical series description for the histogram
      string label="MACD ("+(string)fast_ema_period+","+(string)slow_ema_period+","+(string)signal_period+")";
      buff_hist.SetLabel(label);
      //--- and set display in the data window for the buffer
      //--- and also a chart period and symbol selected in the settings
      buff_hist.SetShowData(true);
      buff_hist.SetTimeframe(InpPeriod);
      buff_hist.SetSymbol(InpUsedSymbols);
     }
//--- Get the signal line buffer (the first drawn buffer)
//--- It has the index of 5 considering that the starting point is zero
   CBufferLine *buff_line=engine.GetBufferLine(0);
   if(buff_line!=NULL)
     {
      //--- Set the signal line width
      buff_line.SetWidth(1);
      //--- Set the graphical series description for the signal line
      string label="Signal";
      buff_line.SetLabel(label);
      //--- and set display in the data window for the buffer
      //--- and also a chart period and symbol selected in the settings
      buff_line.SetShowData(true);
      buff_line.SetTimeframe(InpPeriod);
      buff_line.SetSymbol(InpUsedSymbols);
     }
    
//--- Get the first calculated buffer
//--- It has the index of 5 considering that the starting point is zero
   CBufferCalculate *buff_calc=engine.GetBufferCalculate(0);
   if(buff_calc!=NULL)
     {
      //--- Set the description of the first calculated buffer as the "MACD histogram temporary array""
      buff_calc.SetLabel("MACD_HIST_TMP");
      //--- and set a chart period and symbol selected in the settings for it
      buff_calc.SetTimeframe(InpPeriod);
      buff_calc.SetSymbol(InpUsedSymbols);
     }
//--- Get the second calculated buffer
//--- It has the index of 1 considering that the starting point is zero
   buff_calc=engine.GetBufferCalculate(1);
   if(buff_calc!=NULL)
     {
      //--- Set the description of the second calculated buffer as the "MACD signal line temporary array""
      buff_calc.SetLabel("MACD_SIGN_TMP");
      //--- and set a chart period and symbol selected in the settings for it
      buff_calc.SetTimeframe(InpPeriod);
      buff_calc.SetSymbol(InpUsedSymbols);
     }
    
//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();

//--- Create МАCD handle
   handle_macd=iMACD(InpUsedSymbols,InpPeriod,fast_ema_period,slow_ema_period,signal_period,InpPriceMACD);
   if(handle_macd==INVALID_HANDLE)
      return INIT_FAILED;
//---
   IndicatorSetString(INDICATOR_SHORTNAME,InpUsedSymbols+" "+TimeframeDescription(InpPeriod)+" MACD("+(string)fast_ema_period+","+(string)slow_ema_period+","+(string)signal_period+")");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Remove indicator graphical objects by an object name prefix
   ObjectsDeleteAll(0,prefix);
   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[])
  {
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
   
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      EventsHandling();             // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
     }

//--- Prepare data 
   int total_copy=(limit<2 ? 1 : limit);
//--- Get the first calculated buffer by its number
   CBufferCalculate *buff_calc_hist=engine.GetBufferCalculate(0);
//--- Fill in the first calculated buffer with MACD histogram data
   int copied=buff_calc_hist.FillAsSeries(handle_macd,0,0,total_copy);
   if(copied<total_copy)
      return 0;
//--- Get the second calculated buffer by its number
   CBufferCalculate *buff_calc_sig=engine.GetBufferCalculate(1);
//--- Fill in the second calculated buffer with MACD signal line data
   copied=buff_calc_sig.FillAsSeries(handle_macd,1,0,total_copy);
   if(copied<total_copy)
      return 0;
        
//--- Calculate the indicator
   CBar *bar=NULL;         // Bar object for defining the candle direction
   uchar color_index=0;    // Color index to be set for the buffer depending on the candle direction

//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- Clear the current bar of all created buffers
      engine.BufferHistogramClear(0,0);
      engine.BufferLineClear(0,0);
      
      //--- Get the timeseries bar corresponding to the loop index time on the chart period specified in the settings
      bar=engine.SeriesGetBar(InpUsedSymbols,InpPeriod,time[i]);
      if(bar==NULL)
         continue;
      //--- Calculate the color index depending on the candle direction on the timeframe specified in the settings
      color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2);
      //--- Calculate the MACD histogram buffer
      int index=engine.IndexBarPeriodByBarCurrent(i,InpUsedSymbols,InpPeriod);
      if(index<0)
         continue;
      engine.BufferSetDataHistogram(0,i,buff_calc_hist.GetData(index),color_index);
      //--- Calculate MACD signal line buffer
      engine.BufferSetDataLine(0,i,buff_calc_sig.GetData(index),color_index);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

El color de las columnas del histograma y la línea de señal en cada barra se corresponden con la dirección de la vela del símbolo/periodo conforme a los que se ha calculado MACD:


El indicador se ha iniciado en EURUSD M15 con los ajustes por defecto de MACD, calculado en GBPUSD M30. Para una mejor comprensión, hemos abierto el gráfico GBPUSD M30 con el MACD estándar iniciado en él con los mismos parámetros.

¿Qué es lo próximo?

En el próximo artículo, añadiremos a la biblioteca una funcionalidad que facilitará la creación de indicadores estándar de periodo y símbolo múltiples.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
Querríamos recordar al lector que en este artículo hemos creado un indicador de prueba en MQL5 para MetaTrader 5.
Los archivos adjuntos han sido diseñados solo para MetaTrader 5, y en MetaTrader 4, la biblioteca en su versión actual no ha sido puesta a prueba.
Después de crear la funcionalidad necesaria para trabajar con los búferes de indicador y poner estos a prueba, trataremos también de implementar algunas cosas de MQL5 en MetaTrader 4.

Volver al contenido

Artículos de esta serie:

Trabajando con las series temporales en la biblioteca DoEasy (Parte 35): El objeto "Barra" y la lista de serie temporal del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 36): El objeto de series temporales de todos los periodos utilizados del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 37): Colección de series temporales - Base de datos de series temporales según el símbolo y el periodo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 38): Colección de series temporales - Actualización en tiempo real y acceso a los datos desde el programa
Trabajando con las series temporales en la biblioteca DoEasy (Parte 39): Indicadores basados en la biblioteca - Preparación de datos y eventos de la series temporales
Trabajando con las series temporales en la biblioteca DoEasy (Parte 40): Indicadores basados en la biblioteca - actualización de datos en tiempo real
Trabajando con las series temporales en la biblioteca DoEasy (Parte 41): Ejemplo de indicador de símbolo y periodo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 42): La clase del objeto de búfer de indicador abstracto
Trabajando con las series temporales en la biblioteca DoEasy (Parte 43): Las clases de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 44): Las clases de colección de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple

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

Archivos adjuntos |
MQL5.zip (3749.86 KB)
Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple

En el artículo, comenzaremos a mejorar los objetos de búfer de indicador y la clase de colección de búferes para trabajar en los modos de periodo y símbolo múltiples. Asimismo, analizaremos el funcionamiento de los objetos de búfer para obtener y mostrar los datos desde cualquier marco temporal en el gráfico actual del símbolo actual.

Instrumental para el comercio manual rápido: Funcionalidad básica Instrumental para el comercio manual rápido: Funcionalidad básica

En la actualidad, cada vez son más los tráders que dan el salto a los sistemas comerciales automáticos. Muchos de ellos, o bien demandan una configuración inicial, o bien (una parte de los mismos) que los sistemas ya estén totalmente automatizados. No obstante, queda una parte significativa de tráders que comercian manualmente, a la antigua. En este artículo, crearemos un conjunto de herramientas para el comercio automático rápido con la ayuda de atajos de teclado y la ejecución de acciones comerciales rápidas en un solo clic.

Teoría de probabilidad y estadística matemática con ejemplos (Parte I): Fundamentos y teoría elemental Teoría de probabilidad y estadística matemática con ejemplos (Parte I): Fundamentos y teoría elemental

El trading siempre ha estado relacionado con la toma de decisiones en condiciones de incertidumbre. Esto significa que los resultados de las decisiones tomadas no son totalmente obvios en el momento en que se toman. Por este motivo, resultan importantes los enfoques teóricos sobre la construcción de los modelos matemáticos que permiten describir estas situaciones ofreciendo información relevante e ilustrativa.

Instrumental para el comercio manual rápido: Trabajando con órdenes abiertas y órdenes pendientes Instrumental para el comercio manual rápido: Trabajando con órdenes abiertas y órdenes pendientes

En este artículo, ampliaremos las posibilidades del instrumental, añadiremos al mismo las capacidades de abrir posiciones comerciales, y también crearemos un recuadro para registrar las órdenes abiertas y las órdenes pendientes con la posibilidad de editar las mismas.