Trabajando con las series temporales en la biblioteca DoEasy (Parte 58): Series temporales de los datos de búferes de indicadores

Artyom Trishkin | 2 febrero, 2021

Contenido


Concepto

En conclusión del tema de trabajo con series temporales, vamos a organizar el almacenamiento, la búsqueda y la ordenación de los datos que se guardan en los búferes de indicadores. En el futuro, eso nos permitirá realizar el análisis a base de los valores de los indicadores que se crean a base de la biblioteca en nuestros programas.
El concepto de la construcción de las series temporales de los datos de indicadores parece al concepto de la construcción de la colección de series temporales: para cada indicador se crea su propia lista en la que van a almacenarse todos los datos de todos sus búferes. El número de datos en la lista de los búferes de indicador corresponderá al número de datos de las series temporales correspondientes a base de las cuales se calculan los indicadores. El concepto general de todas las clases de colección de la biblioteca permite encontrar fácilmente los datos necesarios en la colección correspondiente, y por tanto, lo mismo también será posible en la clase que vamos a crear hoy.

Posteriormente, crearemos las clases para realizar el análisis usando todos los datos almacenados en la biblioteca. Eso nos proveerá de una herramienta bastante flexible para realizar estudios estadísticos durante una exhaustiva comparación de cualquier dato en la colección de la biblioteca.


Mejorando las clases de la biblioteca

La clase de la colección de datos de los búferes de indicador va a actualizar automáticamente los datos de todos los indicadores creados en la barra actual, y cuando aparece una barra nueva, va a crear datos nuevos de los búferes de indicador y colocarlos en la colección. Ya tenemos la clase «Nueva barra» en el archivo NewBarObj.mqh que hemos creado para la colección de series temporales.
Se ubica en la carpeta de las clases de series temporales en el directorio de la biblioteca \MQL5\Include\DoEasyPart57\Objects\Series\.
Ahora, lo necesitamos también para realizar el seguimiento de una barra nueva en la clase de colección de datos de búferes de indicador.
Por eso, lo trasladamos a la carpeta de las funciones de servicio y clases de la biblioteca \MQL5\Include\DoEasy\Services\.
Eliminamos el archivo de su ubicación anterior.

Dado que la clase «Serie temporal» utiliza la clase «Nueva barra», hay que modificar la ruta antigua hacia esta clase en el archivo de la clase «Serie temporal» \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh en el bloque de archivos de inclusión:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "NewBarObj.mqh"
#include "Bar.mqh"
//+------------------------------------------------------------------+

introduciendo una ruta nueva

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "..\..\Services\NewBarObj.mqh"
#include "Bar.mqh"
//+------------------------------------------------------------------+

Añadimos los índices de nuevos mensajes al archivo \MQL5\Include\DoEasy\Data.mqh:

   MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_ASK,            // Failed to get Ask price. Error
   MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_BID,            // ailed to get Bid price. Error
   MSG_LIB_SYS_ERROR_FAILED_GET_TIME,                 // Failed to get time. Error
   MSG_LIB_SYS_ERROR_FAILED_OPEN_BUY,                 // Failed to open Buy position. Error

...

//--- CDataInd
   MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM,              // Indicator buffer number
   MSG_LIB_TEXT_IND_DATA_BUFFER_VALUE,                // Indicator buffer value
   
//--- CSeriesDataInd
   MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS,            // The method is not intended to handle indicator programs
   MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA,      // Failed to get indicator data timeseries
   MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA,     // Failed to get current data of indicator buffer
   MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ,            // Failed to create indicator data object
   MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST,          // Failed to add indicator data object to list
   
  };
//+------------------------------------------------------------------+

y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:

   {"Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "},
   {"Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "},
   {"Не удалось получить время. Ошибка ","Could not get time. Error "},
   {"Не удалось открыть позицию Buy. Ошибка ","Failed to open Buy position. Error "},

...

   {"Номер буфера индикатора","Indicator buffer number"},
   {"Значение буфера индикатора","Indicator buffer value"},
   
   {"Метод не предназначен для работы с программами-индикаторами","The method is not intended for working with indicator programs"},
   {"Не удалось получить таймсерию индикаторных данных","Failed to get indicator data timeseries"},
   {"Не удалось получить текущие данные буфера индикатора","Failed to get the current data of the indicator buffer"},
   {"Не удалось создать объект индикаторных данных","Failed to create indicator data object"},
   {"Не удалось добавить объект индикаторных данных в список","Failed to add indicator data object to the list"},
   
  };
//+---------------------------------------------------------------------+

Como hoy vamos a crear una colección nueva, hay que definir su propio identificador de colección para ella. Aparte de eso, vamos a crear constantes de los parámetros del temporizador de nueva colección. Es que los datos de los búferes de indicador van a actualizarse automáticamente en el temporizador.

Añadimos datos nuevos al archivo \MQL5\Include\DoEasy\Defines.mqh:

//--- Parameters of the timeseries collection timer
#define COLLECTION_TS_PAUSE            (64)                       // Timeseries collection timer pause in milliseconds
#define COLLECTION_TS_COUNTER_STEP     (16)                       // Account timer counter increment
#define COLLECTION_TS_COUNTER_ID       (6)                        // Timeseries timer counter ID
//--- Parameters of the timer of indicator data timeseries collection
#define COLLECTION_IND_TS_PAUSE        (64)                       // Pause of the timer of indicator data timeseries collection in milliseconds
#define COLLECTION_IND_TS_COUNTER_STEP (16)                       // Increment of indicator data timeseries timer counter
#define COLLECTION_IND_TS_COUNTER_ID   (7)                        // ID of indicator data timeseries timer counter
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID

Para buscar los datos de los búferes de indicador en su colección, tenemos que buscar adicionalmente los datos según el manejador (porque los datos estarán relacionados con este indicador, y sería extraño no usar para la búsqueda de datos un identificador exacto de su pertenencia al indicador).

A las propiedades enteras de los datos de indicador le añadimos una propiedad nueva, y aumentamos el número de estos datos de 5 a 6.

//+------------------------------------------------------------------+
//| Integer properties of indicator data                             |
//+------------------------------------------------------------------+
enum ENUM_IND_DATA_PROP_INTEGER
  {
   IND_DATA_PROP_TIME = 0,                                  // Start time of indicator data bar period
   IND_DATA_PROP_PERIOD,                                    // Indicator data period (timeframe)
   IND_DATA_PROP_INDICATOR_TYPE,                            // Indicator type
   IND_DATA_PROP_IND_BUFFER_NUM,                            // Indicator data buffer number
   IND_DATA_PROP_IND_ID,                                    // Indicator ID
   IND_DATA_PROP_IND_HANDLE,                                // Indicator handle
  }; 
#define IND_DATA_PROP_INTEGER_TOTAL (6)                     // Total number of indicator data integer properties
#define IND_DATA_PROP_INTEGER_SKIP  (0)                     // Number of indicator data properties not used in sorting
//+------------------------------------------------------------------+

Por tanto, como ya hemos añadido una propiedad entera nueva, entonces, vamos a añadirla a la lista de posibles criterios de ordenación para tener la posibilidad de buscar y ordenar según esta propiedad:

//+------------------------------------------------------------------+
//| Possible criteria for indicator data sorting                     |
//+------------------------------------------------------------------+
#define FIRST_IND_DATA_DBL_PROP          (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP)
#define FIRST_IND_DATA_STR_PROP          (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP+IND_DATA_PROP_DOUBLE_TOTAL-IND_DATA_PROP_DOUBLE_SKIP)
enum ENUM_SORT_IND_DATA_MODE
  {
//--- Sort by integer properties
   SORT_BY_IND_DATA_TIME = 0,                               // Sort by bar period start time of indicator data
   SORT_BY_IND_DATA_PERIOD,                                 // Sort by indicator data period (timeframe)
   SORT_BY_IND_DATA_INDICATOR_TYPE,                         // Sort by indicator type
   SORT_BY_IND_DATA_IND_BUFFER_NUM,                         // Sort by indicator data buffer number
   SORT_BY_IND_DATA_IND_ID,                                 // Sort by indicator ID
   SORT_BY_IND_DATA_IND_HANDLE,                             // Sort by indicator handle
//--- Sort by real properties
   SORT_BY_IND_DATA_BUFFER_VALUE = FIRST_IND_DATA_DBL_PROP, // Sort by indicator data value
//--- Sort by string properties
   SORT_BY_IND_DATA_SYMBOL = FIRST_IND_DATA_STR_PROP,       // Sort by indicator data symbol
   SORT_BY_IND_DATA_IND_NAME,                               // Sort by indicator name
   SORT_BY_IND_DATA_IND_SHORTNAME,                          // Sort by indicator short name
  };
//+------------------------------------------------------------------+

Los datos de los búferes de indicador almacenados en la colección están representados por la clase de datos de los búferes de indicador que creamos en el artículo anterior .

Ahora, tenemos que añadirle (en el archivo \MQL5\Include\DoEasy\Objects\Indicators\DataInd.mqh) el método de establecimiento del manejador del indicador cuyos datos del búfer se almacenan en el objeto de esta clase. Además, al constructor de la clase se le añade la transferencia de los parámetros del manejador del indicador y los valores de su búfer, lo que permitirá especificar de inmediato los valores de estos parámetros durante la creación del objeto:

//--- Set (1) symbol, timeframe and time for the object, (2) indicator type, (3) number of buffers, (4) number of data buffer,
//--- (5) ID, (6) handle, (7) data value, (8) name, (9) indicator short name
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetIndicatorType(const ENUM_INDICATOR type)              { this.SetProperty(IND_DATA_PROP_INDICATOR_TYPE,type);               }
   void              SetBufferNum(const int num)                              { this.SetProperty(IND_DATA_PROP_IND_BUFFER_NUM,num);                }
   void              SetIndicatorID(const int id)                             { this.SetProperty(IND_DATA_PROP_IND_ID,id);                         }
   void              SetIndicatorHandle(const int handle)                     { this.SetProperty(IND_DATA_PROP_IND_HANDLE,handle);                 }
   void              SetBufferValue(const double value)                       { this.SetProperty(IND_DATA_PROP_BUFFER_VALUE,value);                }
   void              SetIndicatorName(const string name)                      { this.SetProperty(IND_DATA_PROP_IND_NAME,name);                     }
   void              SetIndicatorShortname(const string shortname)            { this.SetProperty(IND_DATA_PROP_IND_SHORTNAME,shortname);           }
   
//--- Compare CDataInd objects with each other by all possible properties (for sorting the lists by a specified data object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CDataInd objects with each other by all properties (to search equal objects)
   bool              IsEqual(CDataInd* compared_data) const;
//--- Constructors
                     CDataInd(){;}
                     CDataInd(const ENUM_INDICATOR ind_type,
                              const int ind_handle,
                              const int ind_id,
                              const int buffer_num,
                              const string symbol,
                              const ENUM_TIMEFRAMES timeframe,
                              const datetime time,
                              const double value);

Al bloque de métodos para el acceso simplificado a las propiedades del objeto le añadimos el método que devuelve el manejador del indicador, y renombramos el método que devuelve el valor de datos del búfer del indicador (antes el método se llamaba PriceValue()):

//+------------------------------------------------------------------+ 
//| Methods of simplified access to object properties                |
//+------------------------------------------------------------------+
//--- Return (1) bar period start time, (2) timeframe, (3) indicator type,
//--- (4) number of buffers, (5) buffer number, (6) ID, (7) indicator handle
   datetime          Time(void)                                         const { return (datetime)this.GetProperty(IND_DATA_PROP_TIME);                   }
   ENUM_TIMEFRAMES   Timeframe(void)                                    const { return (ENUM_TIMEFRAMES)this.GetProperty(IND_DATA_PROP_PERIOD);          }
   ENUM_INDICATOR    IndicatorType(void)                                const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_INDICATOR_TYPE);   }
   int               BufferNum(void)                                    const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_BUFFER_NUM);   }
   int               IndicatorID(void)                                  const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_ID);           }
   int               IndicatorHandle(void)                              const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_HANDLE);       }

//--- Return the value of indicator buffer data
   double            BufferValue(void)                                  const { return this.GetProperty(IND_DATA_PROP_BUFFER_VALUE);                     }

En el cuerpo del constructor de la clase, establecemos los valores de propiedades nuevas que se transmiten a él durante la creación de un objeto nuevo:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDataInd::CDataInd(const ENUM_INDICATOR ind_type,
                   const int ind_handle,
                   const int ind_id,
                   const int buffer_num,
                   const string symbol,
                   const ENUM_TIMEFRAMES timeframe,
                   const datetime time,
                   const double value)
  {
   this.m_type=COLLECTION_INDICATORS_DATA_ID;
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)+1;
   this.m_period_description=TimeframeDescription(timeframe);
   this.SetSymbolPeriod(symbol,timeframe,time);
   this.SetIndicatorType(ind_type);
   this.SetIndicatorHandle(ind_handle);
   this.SetBufferNum(buffer_num);
   this.SetIndicatorID(ind_id);
   this.SetBufferValue(value);
  }
//+------------------------------------------------------------------+

El bloque del código para visualizar la descripción del manejador del indicador se añade al método que devuelve la descripción de propiedades enteras:

//+------------------------------------------------------------------+
//| Return description of object's integer property                  |
//+------------------------------------------------------------------+
string CDataInd::GetPropertyDescription(ENUM_IND_DATA_PROP_INTEGER property)
  {
   return
     (
      property==IND_DATA_PROP_TIME           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==IND_DATA_PROP_PERIOD         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.m_period_description
         )  :
      property==IND_DATA_PROP_INDICATOR_TYPE ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.IndicatorTypeDescription()
         )  :
      property==IND_DATA_PROP_IND_BUFFER_NUM ?  CMessage::Text(MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==IND_DATA_PROP_IND_ID         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==IND_DATA_PROP_IND_HANDLE     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


Clase de la serie temporal de los datos de los búferes del indicador

Todas las clases de colección en la biblioteca están creadas a base de la clase de la matriz dinámica de punteros a las instancias de la clase CObject y sus herederos de la Biblioteca estándar. La clase de colección de los datos de los búferes de indicador no será una excepción.

Esta clase permitirá almacenar los objetos de datos del búfer de un indicador en la lista de objetos CArrayObj, obtener datos de cualquier búfer del indicador que corresponden a la hora de apertura de la barra de la serie temporal. Naturalmente, la lista tendrá la posibilidad de una actualización automática, búsqueda y ordenación según las propiedades de los objetos almacenados en ella.

En la carpeta \MQL5\Include\DoEasy\Objects\Indicators\ creamos la nueva clase CSeriesDataInd en el archivo SeriesDataInd.mqh.
El objeto de la clase debe derivarse del objeto básico de todos los objetos de la biblioteca CBaseObj.
Vamos a analizar el cuerpo de la clase con todas sus variables y métodos:

//+------------------------------------------------------------------+
//|                                                SeriesDataInd.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "..\..\Services\NewBarObj.mqh"
#include "DataInd.mqh"
//+------------------------------------------------------------------+
//| “Indicator data list” class                                      |
//+------------------------------------------------------------------+
class CSeriesDataInd : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeframe
   ENUM_INDICATOR    m_ind_type;                                        // Indicator type
   string            m_symbol;                                          // Symbol
   string            m_period_description;                              // Timeframe string description
   int               m_ind_handle;                                      // Indicator handle
   int               m_ind_id;                                          // Indicator ID
   int               m_buffers_total;                                   // Number of indicator buffers
   uint              m_amount;                                          // Amount of applied timeseries data
   uint              m_required;                                        // Required amount of applied timeseries data
   uint              m_bars;                                            // Number of bars in history by symbol and timeframe
   bool              m_sync;                                            // Synchronized data flag
   CArrayObj         m_list_data;                                       // Indicator data list
   CNewBarObj        m_new_bar_obj;                                     // "New bar" object
//--- Create a new indicator data object, return pointer to it
   CDataInd         *CreateNewDataInd(const int buffer_num,const datetime time,const double value);
   
public:
//--- Return (1) oneself, (2) the full list of indicator data
   CSeriesDataInd   *GetObject(void)                                    { return &this;               }
   CArrayObj        *GetList(void)                                      { return &this.m_list_data;   }

//--- Return the list of indicator data by selected (1) double, (2) integer and (3) string property fitting a compared condition
   CArrayObj        *GetList(ENUM_IND_DATA_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_IND_DATA_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_IND_DATA_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); }

//--- Return indicator data object by (1) time, (2) bar number and buffer number
   CDataInd         *GetIndDataByTime(const int buffer_num,const datetime time);
   CDataInd         *GetIndDataByBar(const int buffer_num,const uint shift);
   
//--- Set (1) symbol, (2) timeframe, (3) symbol and timeframe, (4) amount of applied timeseries data,
//--- (5) handle, (6) ID, (7) number of indicator buffers
   void              SetSymbol(const string symbol);
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe);
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool              SetRequiredUsedData(const uint required);
   void              SetIndHandle(const int handle)                              { this.m_ind_handle=handle;   }
   void              SetIndID(const int id)                                      { this.m_ind_id=id;           }
   void              SetIndBuffersTotal(const int total)                         { this.m_buffers_total=total; }

//--- Return (1) symbol, (2) timeframe, number of (3) used and (4) requested timeseries data,
//--- (5) number of bars in timeseries, (6) handle, (7) ID, (8) number of indicator buffers
   string            Symbol(void)                                          const { return this.m_symbol;                            }
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe;                         }
   ulong             AvailableUsedData(void)                               const { return this.m_amount;                            }
   ulong             RequiredUsedData(void)                                const { return this.m_required;                          }
   ulong             Bars(void)                                            const { return this.m_bars;                              }
   int               IndHandle(void)                                       const { return this.m_ind_handle;                        }
   int               IndID(void)                                           const { return this.m_ind_id;                            }
   int               IndBuffersTotal(void)                                 const { return this.m_buffers_total;                     }
   bool              IsNewBar(const datetime time)                               { return this.m_new_bar_obj.IsNewBar(time);        }
   bool              IsNewBarManual(const datetime time)                         { return this.m_new_bar_obj.IsNewBarManual(time);  }

//--- Return real list size
   int               DataTotal(void)                                       const { return this.m_list_data.Total();                 }
 
//--- Save the new bar time during the manual time management
   void              SaveNewBarTime(const datetime time)                         { this.m_new_bar_obj.SaveNewBarTime(time);         }
   
//--- (1) Create, (2) update the indicator data list
   int               Create(const uint required=0);
   void              Refresh(void);

//--- Return data of specified indicator buffer by (1) open time, (2) bar index
   double            BufferValue(const int buffer_num,const datetime time);
   double            BufferValue(const int buffer_num,const uint shift);

//--- Constructors
                     CSeriesDataInd(void){;}
                     CSeriesDataInd(const int handle,
                                    const ENUM_INDICATOR ind_type,
                                    const int ind_id,
                                    const int buffers_total,
                                    const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
  };
//+------------------------------------------------------------------+

En el objeto, vemos los métodos ya conocidos que son propios a todos los objetos de la biblioteca, así como, el conjunto de variables-miembros de la clase para almacenar parámetros del objeto.
En la sección pública de la clase, declaramos los métodos para un acceso simplificado a las propiedades del objeto y para su establecimiento desde fuera.

Vamos a analizar la implementación de los métodos de la clase.

Al constructor paramétrico de la clase se le transmiten todos los valores de sus propiedades necesarios para su creación:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CSeriesDataInd::CSeriesDataInd(const int handle,
                               const ENUM_INDICATOR ind_type,
                               const int ind_id,
                               const int buffers_total,
                               const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) :
                               m_bars(0), m_amount(0),m_required(0),m_sync(false)
  {
//--- To the object set indicator data collection list ID
   this.m_type=COLLECTION_INDICATORS_DATA_ID;
//--- Clear the list and set for it the flag of the list sorted by time
   this.m_list_data.Clear();
   this.m_list_data.Sort(SORT_BY_IND_DATA_TIME);
//--- Set values for all object variables
   this.SetSymbolPeriod(symbol,timeframe);
   this.m_sync=this.SetRequiredUsedData(required);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.m_ind_handle=handle;
   this.m_ind_type=ind_type;
   this.m_ind_id=ind_id;
   this.m_buffers_total=buffers_total;
  }
//+------------------------------------------------------------------+

El identificador de la colección de la serie temporal de los datos de búferes de indicador se establecen para el objeto, se limpia la lista en la que van a almacenarse los objetos de la clase CDataInd, y para la lista, se establece la bandera de una lista ordenada. El modo de ordenación más conveniente para estos objetos es la ordenación según la hora: con el fin de asegurar la correspondencia de su localización en la lista a la localización de los datos en el búfer físico del indicador.

Creo que los métodos para establecer el símbolo y el marco temporal no requieren comentarios:

//+------------------------------------------------------------------+
//| Set a symbol                                                     |
//+------------------------------------------------------------------+
void CSeriesDataInd::SetSymbol(const string symbol)
  {
   if(this.m_symbol==symbol)
      return;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
  }
//+------------------------------------------------------------------+
//| Set a timeframe                                                  |
//+------------------------------------------------------------------+
void CSeriesDataInd::SetTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   if(this.m_timeframe==timeframe)
      return;
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe);
  }
//+------------------------------------------------------------------+
//| Set a symbol and timeframe                                       |
//+------------------------------------------------------------------+
void CSeriesDataInd::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
  }
//+------------------------------------------------------------------+

Método para establecer la cantidad necesaria de los datos de búferes de indicador

//+------------------------------------------------------------------+
//| Set the number of required data                                  |
//+------------------------------------------------------------------+
bool CSeriesDataInd::SetRequiredUsedData(const uint required)
  {
//--- If this is indicator program - report and leave
   if(this.m_program==PROGRAM_INDICATOR)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS));
      return false;
     }
//--- Set the necessary amount of data - if less than 1 is passed use by default (1000), or else - the passed value
   this.m_required=(required<1 ? SERIES_DEFAULT_BARS_COUNT : required);
//--- Launch download of historical data (relevant for the “cold” start)
   datetime array[1];
   ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array);
//--- Set the number of available timeseries bars
   this.m_bars=(uint)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_BARS_COUNT);
//--- If managed to set the number of available history, set the amount of data in the list:
   if(this.m_bars>0)
     {
      //--- if zero 'required' value is passed,
      //--- use either the default value (1000 bars) or the number of available history bars - the least one of them
      //--- if non-zero 'required' value is passed,
      //--- use either the 'required' value or the number of available history bars - the least one of them
      this.m_amount=(required==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(required,this.m_bars));
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Para trabajar con indicadores ya tenemos otras clases, por eso, aquí se verifica de inmediato el tipo del programa iniciado. Y si se trata de un indicador, se muestra un mensaje de ello y se devuelve false. El funcionamiento del método parecido ya fue considerado cuando se creaban las clases de la serie temporal en el artículo 35.

El método que crea un nuevo objeto de datos de indicador:

//+------------------------------------------------------------------+
//| Create a new indicator data object, return the pointer           |
//+------------------------------------------------------------------+
CDataInd* CSeriesDataInd::CreateNewDataInd(const int buffer_num,const datetime time,const double value)
  {
   CDataInd* data_ind=new CDataInd(this.m_ind_type,this.m_ind_handle,this.m_ind_id,buffer_num,this.m_symbol,this.m_timeframe,time,value);
   return data_ind;
  }
//+------------------------------------------------------------------+

Crea un nuevo objeto de la clase CDataInd y devuelve el puntero al objeto nuevamente creado, o NULL, si el objeto no ha sido creado.

Método para crear la lista de serie temporal de datos de indicador:

//+------------------------------------------------------------------+
//| Create indicator data timeseries list                            |
//+------------------------------------------------------------------+
int CSeriesDataInd::Create(const uint required=0)
  {
//--- If this is indicator program - report and leave
   if(this.m_program==PROGRAM_INDICATOR)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS));
      return false;
     }
//--- If the required history depth is not set for the list yet,
//--- display the appropriate message and return zero,
   if(this.m_amount==0)
     {
      ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA));
      return 0;
     }
//--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, 
//--- while being lower than the available bar number,
//--- set the new value of the required history depth for the list
   else if(required>0 && this.m_amount!=required && required<this.m_bars)
     {
      //--- If failed to set a new value, return zero
      if(!this.SetRequiredUsedData(required))
         return 0;
     }
//--- For the data[] array we are to receive historical data to,
//--- set the flag of direction like in the timeseries,
//--- clear the list of indicator data objects and set the flag of sorting by timeseries bar time
   double data[];
   ::ArraySetAsSeries(data,true);
   this.m_list_data.Clear();
   this.m_list_data.Sort(SORT_BY_IND_DATA_TIME);
   ::ResetLastError();

   int err=ERR_SUCCESS;
//--- In a loop by the number of indicator data buffers
   for(int i=0;i<this.m_buffers_total;i++)
     {
      //--- Get historical data of i buffer of indicator to data[] array  starting from the current bar in the amount of m_amount,
      //--- if failed to get data, display the appropriate message and return zero
      int copied=::CopyBuffer(this.m_ind_handle,i,0,this.m_amount,data);
      if(copied<1)
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
         return 0;
        }
      //--- In the loop by amount of required data
      for(int j=0;j<(int)this.m_amount;j++)
        {
         //--- Get time of j bar
         ::ResetLastError();
         datetime time=::iTime(this.m_symbol,this.m_timeframe,j);
         //--- If failed to get time
         //--- display the appropriate message with the error description in the journal and move on to the next loop iteration by data (j)
         if(time==0)
           {
            err=::GetLastError();
            ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err));
            continue;
           }
         //--- If failed to create a new indicator data object from i buffer
         //--- display the appropriate message with the error description in the journal and move on to the next loop iteration by data (j)
         CDataInd* data_ind=CreateNewDataInd(i,time,data[j]);
         if(data_ind==NULL)
           {
            err=::GetLastError();
            ::Print
              (
               DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ)," ",::TimeToString(time),". ",
               CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)
              );
            continue;
           }
         //--- If failed to add a new indicator data object to list
         //--- display the appropriate message with the error description in the journal, remove data object
         //--- and move on to the next loop iteration by data
         if(!this.m_list_data.Add(data_ind))
           {
            err=::GetLastError();
            ::Print
              (
               DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST)," ",::TimeToString(time),". ",
               CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)
              );
            delete data_ind;
            continue;
           }
        }
     }
//--- Return the size of the created list of indicator data objects
   return this.m_list_data.Total();
  }
//+------------------------------------------------------------------+

Toda la lógica del método se describe detalladamente en su listado. El método copia los datos desde todos los búferes del indicador a la matriz. A base de estos datos, crea nuevos objetos de datos de indicador CDataInd y los coloca en la lista de colección.

Método para actualizar la lista de colección de datos de indicador:

//+------------------------------------------------------------------+
//| Update indicator data timeseries list and data                   |
//+------------------------------------------------------------------+
void CSeriesDataInd::Refresh(void)
  {
//--- If this is indicator program - report and leave
   if(this.m_program==PROGRAM_INDICATOR)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS));
      return;
     }
//--- If indicator data timeseries is not used, exit
   if(!this.m_available)
      return;
   double data[1];
   
//--- Get the current bar time
   int err=ERR_SUCCESS;
   ::ResetLastError();
   datetime time=::iTime(this.m_symbol,this.m_timeframe,0);
   //--- If failed to get time
   //--- display the appropriate message with the error description in the journal and exit
   if(time==0)
     {
      err=::GetLastError();
      ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err));
      return;
     }
   
//--- Set the flag of sorting the data list by time
   this.m_list_data.Sort(SORT_BY_IND_DATA_TIME);
//--- If a new bar is present on a symbol and period,
   if(this.IsNewBarManual(time))
     {
      //--- create new indicator data objects by the number of buffers and add them to the list end
      for(int i=0;i<this.m_buffers_total;i++)
        {
         //--- Get data of the current bar of indicator’s i buffer to data[] array,
         //--- if failed to get data, display the appropriate message and exit
         int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data);
         if(copied<1)
           {
            err=::GetLastError();
            ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                         CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
            return;
           }
         //--- Create a new data object of indicator’s i buffer
         CDataInd* data_ind=CreateNewDataInd(i,time,data[0]);
         if(data_ind==NULL)
            return;
         //--- If the created object was not added to the list - remove this object and exit from the method
         if(!this.m_list_data.InsertSort(data_ind))
           {
            delete data_ind;
            return;
           }
        }
      //--- if the size of indicator data timeseries has ecxeeded the requested number of bars, remove the earliest data object
      if(this.m_list_data.Total()>(int)this.m_required)
         this.m_list_data.Delete(0);
      //--- save the new bar time as the previous one for the subsequent new bar check
      this.SaveNewBarTime(time);
     }
 
//--- Get the list of indicator data objects by the time of current bar start
   CArrayObj *list=CSelect::ByIndicatorDataProperty(this.GetList(),IND_DATA_PROP_TIME,time,EQUAL);
//--- In the loop, by the number of indicator buffers get indicator data objects from the list by loop index
   for(int i=0;i<this.m_buffers_total;i++)
     {
      //--- If failed to get object from the list - exit
      CDataInd *data_ind=this.GetIndDataByTime(i,time);
      if(data_ind==NULL)
         return;
      //--- Copy data of indicator’s i buffer from current bar
      int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data);
      if(copied<1)
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
         return;
        }
      //--- Add received value of indicator’s i buffer to indicator data object
      data_ind.SetBufferValue(data[0]);
     }
  }
//+------------------------------------------------------------------+

La lógica del funcionamiento del método se describe detalladamente en su listado. El método actualiza los datos de todos los búferes del indicador en la barra actual. Mientras que en la nueva barra, crea nuevos objetos para la barra actual y los añade a la lista de colección. Cuando el tamaño de la colección supera el valor requerido, los objetos más antiguos se eliminan de la lista.

El método que devuelve el objeto de datos de indicador según la hora y el número del búfer:

//+------------------------------------------------------------------+
//| Return indicator data object by time and buffer number           |
//+------------------------------------------------------------------+
CDataInd* CSeriesDataInd::GetIndDataByTime(const int buffer_num,const datetime time)
  {
   CArrayObj *list=GetList(IND_DATA_PROP_TIME,time,EQUAL);
   list=CSelect::ByIndicatorDataProperty(list,IND_DATA_PROP_IND_BUFFER_NUM,buffer_num,EQUAL);
   return list.At(0);
  }
//+------------------------------------------------------------------+

Aquí: obtenemos la lista que contiene objetos de datos de indicador correspondientes a la hora indicada,
luego, filtramos la lista obtenida de tal manera que se quede sólo el objeto del bufer de indicador especificado.
La lista va a contener sólo un objeto. Vamos a devolverlo.
.

El método que devuelve el objeto de datos según la barra y el número del búfer:

//+------------------------------------------------------------------+
//| Return indicator data object by bar and buffer number            |
//+------------------------------------------------------------------+
CDataInd* CSeriesDataInd::GetIndDataByBar(const int buffer_num,const uint shift)
  {
   datetime time=::iTime(this.m_symbol,this.m_timeframe,(int)shift);
   if(time==0)
      return NULL;
   return this.GetIndDataByTime(buffer_num,time);
  }
//+------------------------------------------------------------------+

Aquí: obtenemos la hora de apertura de la barra según el índice indicado, luego, devolvemos el objeto obtenido mediante el método analizado antes.

El método que devuelve los datos del búfer de indicador especificado según la hora:

//+------------------------------------------------------------------+
//| Return data of specified indicator buffer by time                |
//+------------------------------------------------------------------+
double CSeriesDataInd::BufferValue(const int buffer_num,const datetime time)
  {
   CDataInd *data_ind=this.GetIndDataByTime(buffer_num,time);
   return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue());
  }
//+------------------------------------------------------------------+

Aquí: obtenemos el objeto de datos mediante el método GetIndDataByTime() y devolvemos el valor del búfer escrito en el objeto, o bien, un «valor vacío» si el objeto no ha sido obtenido.

El método que devuelve los datos del búfer de indicador especificado según el índice de la barra:

//+------------------------------------------------------------------+
//| Return data of specified indicator buffer by bar index           |
//+------------------------------------------------------------------+
double CSeriesDataInd::BufferValue(const int buffer_num,const uint shift)
  {
   CDataInd *data_ind=this.GetIndDataByBar(buffer_num,shift);
   return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue());
  }
//+------------------------------------------------------------------+

Aquí: obtenemos el objeto de datos mediante el método GetIndDataByBar() y devolvemos el valor del búfer escrito en el objeto, o bien, un «valor vacío» si el objeto no ha sido obtenido.

Todos los indicadores diseñados en el programa se colocan en la colección de objetos de indicador creada en el artículo 54. Cada uno de estos objetos contiene todos los datos de un indicador estándar o de un indicador personalizado. No obstante, tenemos que completar algunos datos. Para que siempre podamos saber qué número de búferes tiene el indicador descrito por el objeto de esta clase, hay que añadirle una variable para almacenar la cantidad de los búferes del indicador. Esto es especialmente relevante para los indicadores personalizados, cuando no podemos saber de antemano la cantidad de sus búferes dibujados. Además, tenemos que añadir la lista de colección de los datos de búferes de indicador cuya clase acabamos de crear. En este caso, el objeto del indicador va a guardar las listas de los datos de todos sus búferes, y de ahí, podremos obtener los datos de cada barra de cada búfer del indicador.

Abrimos el archivo de la clase del objeto de indicador \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh e introducimos en el mismo las mejoras pertinentes:

//+------------------------------------------------------------------+
//|                                                          Ind.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/es/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/es/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "..\..\Objects\BaseObj.mqh"
#include "..\..\Objects\Indicators\SeriesDataInd.mqh"
//+------------------------------------------------------------------+
//| Abstract indicator class                                         |
//+------------------------------------------------------------------+
class CIndicatorDE : public CBaseObj
  {
protected:
   MqlParam          m_mql_param[];                                              // Array of indicator parameters
   CSeriesDataInd    m_series_data;                                              // Timeseries object of indicator buffer data
private:
   long              m_long_prop[INDICATOR_PROP_INTEGER_TOTAL];                  // Integer properties
   double            m_double_prop[INDICATOR_PROP_DOUBLE_TOTAL];                 // Real properties
   string            m_string_prop[INDICATOR_PROP_STRING_TOTAL];                 // String properties
   string            m_ind_type_description;                                     // Indicator type description
   int               m_buffers_total;                                            // Total number of indicator buffers
   
//--- Return the index of the array the buffer's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_INDICATOR_PROP_DOUBLE property)        const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL;                           }
   int               IndexProp(ENUM_INDICATOR_PROP_STRING property)        const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_DOUBLE_TOTAL;}
   
//--- Comare (1) structures MqlParam, (2) array of structures MqlParam between each other
   bool              IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) const;
   bool              IsEqualMqlParamArrays(MqlParam &compared_struct[])    const;

protected:
//--- Protected parametric constructor
                     CIndicatorDE(ENUM_INDICATOR ind_type,
                                  string symbol,
                                  ENUM_TIMEFRAMES timeframe,
                                  ENUM_INDICATOR_STATUS status,
                                  ENUM_INDICATOR_GROUP group,
                                  string name,
                                  string shortname,
                                  MqlParam &mql_params[]);
public:  
//--- Default constructor
                     CIndicatorDE(void){;}
//--- Destructor
                    ~CIndicatorDE(void);
                     
//--- Set buffer's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_INDICATOR_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                                        }
   void              SetProperty(ENUM_INDICATOR_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;                      }
   void              SetProperty(ENUM_INDICATOR_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;                      }
//--- Return (1) integer, (2) real and (3) string buffer properties from the properties array
   long              GetProperty(ENUM_INDICATOR_PROP_INTEGER property)        const { return this.m_long_prop[property];                                       }
   double            GetProperty(ENUM_INDICATOR_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];                     }
   string            GetProperty(ENUM_INDICATOR_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];                     }
//--- Get description of buffer's (1) integer, (2) real and (3) string properties
   string            GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_INDICATOR_PROP_STRING property);
//--- Return the flag of the buffer supporting the property
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)          { return true;       }
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)           { return true;       }
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_STRING property)           { return true;       }

//--- Compare CIndicatorDE objects by all possible properties (for sorting the lists by a specified indicator object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CIndicatorDE objects by all properties (to search for equal indicator objects)
   bool              IsEqual(CIndicatorDE* compared_obj) const;
                     
//--- Set indicator’s (1) group, (2) empty value of buffers, (3) name, (4) short name, (5) indicator ID
   void              SetGroup(const ENUM_INDICATOR_GROUP group)      { this.SetProperty(INDICATOR_PROP_GROUP,group);                         }
   void              SetEmptyValue(const double value)               { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value);                   }
   void              SetName(const string name)                      { this.SetProperty(INDICATOR_PROP_NAME,name);                           }
   void              SetShortName(const string shortname)            { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname);                 }
   void              SetID(const int id)                             { this.SetProperty(INDICATOR_PROP_ID,id);                               }
   void              SetBuffersTotal(const int buffers_total)        { this.m_buffers_total=buffers_total;                                   }
   
//--- Return (1) status, (2) group, (3) timeframe, (4) type, (5) handle, (6) ID,
//--- (7) empty value of buffers, (8) name, (9) short name, (10) symbol, (11) number of buffers
   ENUM_INDICATOR_STATUS Status(void)                          const { return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);}
   ENUM_INDICATOR_GROUP  Group(void)                           const { return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP);  }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME);   }
   ENUM_INDICATOR    TypeIndicator(void)                       const { return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE);         }
   int               Handle(void)                              const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE);                  }
   int               ID(void)                                  const { return (int)this.GetProperty(INDICATOR_PROP_ID);                      }
   double            EmptyValue(void)                          const { return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE);                  }
   string            Name(void)                                const { return this.GetProperty(INDICATOR_PROP_NAME);                         }
   string            ShortName(void)                           const { return this.GetProperty(INDICATOR_PROP_SHORTNAME);                    }
   string            Symbol(void)                              const { return this.GetProperty(INDICATOR_PROP_SYMBOL);                       }
   int               BuffersTotal(void)                        const { return this.m_buffers_total;                                          }
   
//--- Return description of (1) type, (2) status, (3) group, (4) timeframe, (5) empty value of indicator, (6) parameter of m_mql_param array
   string            GetTypeDescription(void)                  const { return m_ind_type_description;                                        }
   string            GetStatusDescription(void)                const;
   string            GetGroupDescription(void)                 const;
   string            GetTimeframeDescription(void)             const;
   string            GetEmptyValueDescription(void)            const;
   string            GetMqlParamDescription(const int index)   const;

//--- Return indicator data timeseries
   CSeriesDataInd   *GetSeriesData(void)                             { return &this.m_series_data;  }
   
//--- Display the description of indicator object properties in the journal (full_prop=true - all properties, false - supported ones only)
   void              Print(const bool full_prop=false);
//--- Display (1) a short description, (2) description of indicator object parameters in the journal (implementation in the descendants)
   virtual void      PrintShort(void) {;}
   virtual void      PrintParameters(void) {;}

//--- Return data of specified buffer from specified bar (1) by index, (2) by bar time
   double            GetDataBuffer(const int buffer_num,const int index);
   double            GetDataBuffer(const int buffer_num,const datetime time);
  };
//+------------------------------------------------------------------+

Para que la clase vea el objeto de la clase de colección de datos de indicador, vamos a incluirla en el archivo SeriesDataInd.mqh, en la sección de archivos incluidos.

En el área protegida de la clase, hemos declarado el objeto de la clase de colección de los datos de indicador m_series_data.

La variable m_buffers_total va a almacenar la cantidad total de los búferes dibujados del indicador. Los objetos de los datos de todos estos búferes van a almacenarse en la colección de datos de los búferes del indicador.

Los métodos SetBuffersTotal() y BuffersTotal() establecen/devuelven la cantidad total de los búferes dibujados del indicador.

El método GetSeriesData() devuelve el puntero a la colección de datos de los búferes de indicador.


Ahora, abrimos el archivo de la clase de colección de indicadores \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh e introducimos en el mismo las mejoras pertinentes.

Durante la creación del indicador, se crea el objeto de la clase del indicador abstracto con precisión de todos los datos en función con el tipo del indicador creado. Luego, este objeto de indicador se añade a la lista de colección. Precisamente en el momento de añadir el objeto de indicador creado a la colección mediante el método AddIndicatorToList(), nosotros establecemos adicionalmente los parámetros necesarios del indicador para su exacta identificación.
Añadimos a este método la indicación de la cantidad de búferes dibujados del indicador y la cantidad requerida de los datos de sus búferes:

//--- (1) Create, (2) add to collection list a new indicator object and set an ID for it
   CIndicatorDE           *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT);
   int                     AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0);

En el método de la creación del indicador personalizado, vamos a transmitir adicionalmente el número total de sus búferes dibujados:

   int                     CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int buffers_total,
                                       ENUM_INDICATOR_GROUP group,
                                       MqlParam &mql_param[]);

Declaramos el método que devuelve el puntero al objeto de datos de la serie temporal del búfer de indicador según la hora indicada y los métodos para crear, actualizar y devolver los datos desde la colección de datos de los búferes de indicador:

//--- Return (1) indicator object by its ID, (2) the data object of indicator buffer timeseries by time
   CIndicatorDE           *GetIndByID(const uint id);
   CDataInd               *GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(void);
   void                    PrintShort(void);

//--- Create (1) timeseries of specified indicator data, (2) all timeseries used of all collection indicators
   bool                    SeriesCreate(CIndicatorDE *indicator,const uint required=0);
   bool                    SeriesCreateAll(const uint required=0);
//--- Update buffer data of all indicators
   void                    SeriesRefreshAll(void);
   void                    SeriesRefresh(const int ind_id);
//--- Return by bar (1) time, (2) number the value of the buffer specified by indicator ID
   double                  GetBufferValue(const uint ind_id,const int buffer_num,const datetime time);
   double                  GetBufferValue(const uint ind_id,const int buffer_num,const uint shift);

//--- Constructor
                           CIndicatorsCollection();

  };
//+------------------------------------------------------------------+

En la implementación del método AddIndicatorToList(), añadimos el establecimiento del número de los búferes del indicador y la creación de su serie temporal:

//+------------------------------------------------------------------+
//| Add a new indicator object to collection list                    |
//+------------------------------------------------------------------+
int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0)
  {
//--- If invalid indicator is passed to the object - return INVALID_HANDLE
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- If such indicator is already in the list
   int index=this.Index(indicator);
   if(index!=WRONG_VALUE)
     {
      //--- Remove the earlier created object, get indicator object from the list and return indicator handle
      delete indicator;
      indicator=this.m_list.At(index);
     }
//--- If indicator object is not in the list yet
   else
     {
      //--- If failed to add indicator object to the list - display a corresponding message,
      //--- remove object and return INVALID_HANDLE
      if(!this.m_list.Add(indicator))
        {
         ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST));
         delete indicator;
         return INVALID_HANDLE;
        }
     }
//--- If indicator is successfully added to the list or is already there...
//--- If indicator with specified ID (not -1) is not in the list - set ID
   if(id>WRONG_VALUE && !this.CheckID(id))
      indicator.SetID(id);
//--- Set the total number of buffers and create data timeseries of all indicator buffers
   indicator.SetBuffersTotal(buffers_total);
   this.SeriesCreate(indicator,required);
//--- Return the handle of a new indicator added to the list
   return indicator.Handle();
  }
//+------------------------------------------------------------------+

Puesto que ahora al método AddIndicatorToList() también se le transmite la cantidad total de los búferes del indicador, es necesario añadir la transmisión del valor de la cantidad de los búferes a todos los métodos de la creación de los objetos de indicador. Sabemos la cantidad exacta para los indicadores estándar, mientras que para un indicador personalizado transmitimos esta cantidad en el método de su creación.

Ya hemos introducido estas modificaciones en todos estos métodos de la clase. Analicemos algunos de ellos:

Método de creación del indicador Accelerator Oscillator:

//+------------------------------------------------------------------+
//| Create a new indicator object Accelerator Oscillator             |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
  {
//--- AC indicator possesses no parameters - resize the array of parameter structures
   ::ArrayResize(this.m_mql_param,0);
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_AC,this.m_mql_param,symbol,timeframe);
//--- Return indicator handle received as a result of adding the object to collection list
   return this.AddIndicatorToList(indicator,id,1);
  }
//+------------------------------------------------------------------+

Cuando llamamos al método para añadir el objeto de indicador a la lista de colección, indicamos adicionalmente que este indicador tiene un búfer dibujado.

Método de la creación del indicador Average Directional Movement Index:

//+------------------------------------------------------------------+
//| Create a new indicator object                                    |
//| Average Directional Movement Index                               |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int adx_period=14)
  {
//--- Add required indicator parameters to the array of parameter structures
   ::ArrayResize(this.m_mql_param,1);
   this.m_mql_param[0].type=TYPE_INT;
   this.m_mql_param[0].integer_value=adx_period;
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_ADX,this.m_mql_param,symbol,timeframe);
//--- Return indicator handle received as a result of adding the object to collection list
   return this.AddIndicatorToList(indicator,id,3);
  }
//+------------------------------------------------------------------+

Cuando llamamos al método para añadir el objeto de indicador a la lista de colección, indicamos adicionalmente que este indicador tiene tres búferes dibujados.

Método de la creación de un indicador personalizado

//+------------------------------------------------------------------+
//| Create a new object - custom indicator                           |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                        const int buffers_total,
                                        ENUM_INDICATOR_GROUP group,
                                        MqlParam &mql_param[])
  {
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_CUSTOM,mql_param,symbol,timeframe);
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- Set a group for indicator object
   indicator.SetGroup(group);
//--- Return indicator handle received as a result of adding the object to collection list
   return this.AddIndicatorToList(indicator,id,buffers_total);
  }
//+------------------------------------------------------------------+

En las variables de entrada del método, transferimos el valor de la cantidad de los búferes dibujados del indicador, y al llamar al método de adición del objeto de indicador a la lista de colección, especificamos la cantidad de búferes dibujados que ha sido transferida al método.

El método que devuelve el puntero al objeto de datos de la serie temporal del búfer de indicador según la hora:

//+------------------------------------------------------------------+
//| Return data object of indicator buffer timeseries by time        |
//+------------------------------------------------------------------+
CDataInd *CIndicatorsCollection::GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time)
  {
   CIndicatorDE *indicator=this.GetIndByID(ind_id);
   if(indicator==NULL) return NULL;
   CSeriesDataInd *buffers_data=indicator.GetSeriesData();
   if(buffers_data==NULL) return NULL;
   return buffers_data.GetIndDataByTime(buffer_num,time);
  }
//+------------------------------------------------------------------+

Aquí: obtenemos el objeto de indicador desde la colección según su identificador,
obtenemos desde el objeto el puntero a la lista de colección de los datos de sus búferes
,
devolvemos los datos del búfer especificado según la hora de la apertura de la barra de la serie temporal
.

El método que crea la serie temporal de los datos del indicador especificado:

//+------------------------------------------------------------------+
//| Create data timeseries of the specified indicator                |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::SeriesCreate(CIndicatorDE *indicator,const uint required=0)
  {
   if(indicator==NULL)
      return false;
   CSeriesDataInd *buffers_data=indicator.GetSeriesData();
   if(buffers_data!=NULL)
     {
      buffers_data.SetSymbolPeriod(indicator.Symbol(),indicator.Timeframe());
      buffers_data.SetIndHandle(indicator.Handle());
      buffers_data.SetIndID(indicator.ID());
      buffers_data.SetIndBuffersTotal(indicator.BuffersTotal());
      buffers_data.SetRequiredUsedData(required);
     }
   return(buffers_data!=NULL ? buffers_data.Create(required)>0 : false);
  }
//+------------------------------------------------------------------+

Aquí: al método se le transmite el puntero al objeto de indicador y la cantidad necesaria de las barras creadas de los datos de búferes del indicador.
Desde el objeto de indicador obtenemos el puntero a su colección de los datos de búferes,
establecemos todos los parámetros necesarios de la colección de datos
y
devolvemos el resultado de la creación de la cantidad solicitada de los datos de los búferes del indicador
.

El método que crea todas las series temporales usadas de todos los indicadores de la colección:

//+------------------------------------------------------------------+
//| Create all timeseries used of all collection indicators          |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::SeriesCreateAll(const uint required=0)
  {
   bool res=true;
   for(int i=0;i<m_list.Total();i++)
     {
      CIndicatorDE *indicator=m_list.At(i);
      if(!this.SeriesCreate(indicator,required))
         res&=false;
     }
   return res;
  }
//+------------------------------------------------------------------+

Aquí: de acuerdo con la cantidad total de objetos de indicador en la colección, obtenemos en el ciclo el puntero al siguiente objeto de indicador y añadimos el resultado de la creación de la serie temporal de los datos de sus búferes a la variable res usando el método analizado antes. Una vez finalizado el ciclo, retornamos el valor de la variable res. Si por lo menos una serie temporal de los datos de los búferes no ha sido creada, esta variable va a tener el valor false.

El método que actualiza los datos de los búferes de todos los indicadores de la colección:

//+------------------------------------------------------------------+
//| Update buffer data of all indicators                             |
//+------------------------------------------------------------------+
void CIndicatorsCollection::SeriesRefreshAll(void)
  {
   for(int i=0;i<m_list.Total();i++)
     {
      CIndicatorDE *indicator=m_list.At(i);
      if(indicator==NULL) continue;
      CSeriesDataInd *buffers_data=indicator.GetSeriesData();
      if(buffers_data==NULL) continue;
      buffers_data.Refresh();
     }
  }
//+------------------------------------------------------------------+

Aquí: de acuerdo con la cantidad total de objetos de indicador en la colección, obtenemos en el ciclo el puntero al siguiente objeto de indicador, obtenemos el puntero a su objeto de los datos de la serie temporal de los búferes y actualizamos la serie temporal a través del método Refresh().
.

El método que actualiza los datos de los búferes del indicador especificado:

//+------------------------------------------------------------------+
//| Update buffer data of the specified indicator                    |
//+------------------------------------------------------------------+
void CIndicatorsCollection::SeriesRefresh(const int ind_id)
  {
   CIndicatorDE *indicator=this.GetIndByID(ind_id);
   if(indicator==NULL) return;
   CSeriesDataInd *buffers_data=indicator.GetSeriesData();
   if(buffers_data==NULL) return;
   buffers_data.Refresh();
  }
//+------------------------------------------------------------------+

El identificador del indicador necesario se transmite al método. Usamos el método GetIndByID() para obtener el puntero al indicador necesario, obtenemos su objeto de la serie temporal de datos de los búferes y actualizamos la serie temporal a través del método Refresh().

Los métodos que devuelven el valor del búfer especificado del indicador especificado según la hora o el índice de la barra:

//+------------------------------------------------------------------+
//| Return buffer value by bar time                                  |
//|  specified by indicator ID                                       |
//+------------------------------------------------------------------+
double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const datetime time)
  {
   CIndicatorDE *indicator=GetIndByID(ind_id);
   if(indicator==NULL) return EMPTY_VALUE;
   CSeriesDataInd *series=indicator.GetSeriesData();
   return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,time) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Return by bar number the buffer value                            |
//|  specified by indicator ID                                       |
//+------------------------------------------------------------------+
double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const uint shift)
  {
   CIndicatorDE *indicator=GetIndByID(ind_id);
   if(indicator==NULL) return EMPTY_VALUE;
   CSeriesDataInd *series=indicator.GetSeriesData();
   return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,shift) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

Los métodos son idénticos, a excepción de que el primero recibe la hora de apertura de la barra, y el segundo, el índice de la barra.

Obtenemos el puntero al indicador necesario en la colección, obtenemos el puntero al objeto de la colección de datos de los búferes y devolvemos el valor del búfer indicado usando el método sobrecargado BufferValue().

Para vincular las clases de la biblioteca con el «mundo externo», se usa la clase principal de la biblioteca CEngine. Se ubica en el archivo \MQL5\Include\DoEasy\Engine.mqh, donde se encuentran los métodos de acceso a todos los métodos de la biblioteca desde los programas.

Añadimos a la sección pública de la clase los métodos para trabajar con las colecciones de los datos de los búferes de indicador:
//--- Return (1) the indicator collection, (2) the indicator list from the collection 
   CIndicatorsCollection *GetIndicatorsCollection(void)                                { return &this.m_indicators;              }
   CArrayObj           *GetListIndicators(void)                                        { return this.m_indicators.GetList();     }
   
//--- Create all timeseries used of all collection indicators
   bool                 IndicatorSeriesCreateAll(void)                                 { return this.m_indicators.SeriesCreateAll();}
//--- Update buffer data of all indicators
   void                 IndicatorSeriesRefreshAll(void)                                { this.m_indicators.SeriesRefreshAll();   }
   void                 IndicatorSeriesRefresh(const int ind_id)                       { this.m_indicators.SeriesRefresh(ind_id);}
   
//--- Return the value of the specified buffer by (1) time, (2) number of the bar specified by indicator ID
   double               IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const datetime time)
                          { return this.m_indicators.GetBufferValue(ind_id,buffer_num,time); }
   double               IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const uint shift)
                          { return this.m_indicators.GetBufferValue(ind_id,buffer_num,shift); }

Todos estos métodos llaman a los métodos correspondientes que han sido añadidos antes a la clase de la colección de indicadores.

Creamos un nuevo temporizador para actualizar las colecciones de los datos de los búferes de indicador en el constructor de la clase:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

En el temporizador de la clase, añadimos los bloques del código para trabajar con series temporales de los datos de los búferes de indicador según el temporizador y el tick (en el Simulador de Estrategias):

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- If this is not a tester, work with collection events by timer
   if(!this.IsTester())
     {
   //--- Timer of the collections of historical orders and deals, as well as of market orders and positions
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- If unpaused, work with the order, deal and position collections events
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Account collection timer
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- If unpaused, work with the account collection events
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Timer 1 of the symbol collection (updating symbol quote data in the collection)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- If the pause is over, update quote data of all symbols in the collection
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- If the pause is over
         if(cnt4.IsTimeDone())
           {
            //--- update data and work with events of all symbols in the collection
            this.SymbolEventsControl();
            //--- When working with the market watch list, check the market watch window events
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Trading class timer
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- If unpaused, work with the list of pending requests
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Timeseries collection timer
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- If the pause is over, work with the timeseries list (update all except the current one)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Timer of timeseries collection of indicator buffer data
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- If the pause is over, work with the timeseries list of indicator data (update all except for the current one)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
     }
//--- If this is a tester, work with collection events by tick
   else
     {
      //--- work with events of collections of orders, deals and positions by tick
      this.TradeEventsControl();
      //--- work with events of collections of accounts by tick
      this.AccountEventsControl();
      //--- update quote data of all collection symbols by tick
      this.m_symbols.RefreshRates();
      //--- work with events of all symbols in the collection by tick
      this.SymbolEventsControl();
      //--- work with the list of pending orders by tick
      this.m_trading.OnTimer();
      //--- work with the timeseries list by tick
      this.SeriesRefresh(data_calculate);
      //--- work with the timeseries list of indicator buffers by tick
      this.IndicatorSeriesRefreshAll();
     }
  }
//+------------------------------------------------------------------+

Estas son todas las mejoras por el momento.


Prueba

Para la simulación, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part58\ con el nombre nuevo TestDoEasyPart58.mq5
.

La prueba va a realizarse de la manera igual que en el asesor anterior: usaremos cuatro indicadores dos de los cuales son del tipo estándar y otros dos son personalizados.

La diferencia consiste en lo siguiente: en el asesor anterior, creábamos los objetos de todas las clases directamente en él, mientras que hoy vamos a usar los objetos de las clases de colección de los datos de búferes de indicadores creados, estos objetos han sido creados antes en la biblioteca.

Eliminamos los punteros a los objetos de los datos de indicadores del área global:

//--- Pointers to indicator data objects
CDataInd      *data_ma1_0=NULL;
CDataInd      *data_ma1_1=NULL;
CDataInd      *data_ma2_0=NULL;
CDataInd      *data_ma2_1=NULL;
CDataInd      *data_ama1_0=NULL;
CDataInd      *data_ama1_1=NULL;
CDataInd      *data_ama2_0=NULL;
CDataInd      *data_ama2_1=NULL;
//+------------------------------------------------------------------+

En el manejador OnInit() del asesor, añadimos la cantidad de los búferes de los indicadores personalizados creados:

//--- Create indicators
   ArrayResize(param_ma1,4);
   //--- Name of indicator 1
   param_ma1[0].type=TYPE_STRING;
   param_ma1[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Calculation period
   param_ma1[1].type=TYPE_INT;
   param_ma1[1].integer_value=13;
   //--- Horizontal shift
   param_ma1[2].type=TYPE_INT;
   param_ma1[2].integer_value=0;
   //--- Smoothing method
   param_ma1[3].type=TYPE_INT;
   param_ma1[3].integer_value=MODE_SMA;
   //--- Create indicator 1
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,1,INDICATOR_GROUP_TREND,param_ma1);
   
   ArrayResize(param_ma2,5);
   //--- Name of indicator 2
   param_ma2[0].type=TYPE_STRING;
   param_ma2[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Calculation period
   param_ma2[1].type=TYPE_INT;
   param_ma2[1].integer_value=13;
   //--- Horizontal shift
   param_ma2[2].type=TYPE_INT;
   param_ma2[2].integer_value=0;
   //--- Smoothing method
   param_ma2[3].type=TYPE_INT;
   param_ma2[3].integer_value=MODE_SMA;
   //--- Calculation price
   param_ma2[4].type=TYPE_INT;
   param_ma2[4].integer_value=PRICE_OPEN;
   //--- Create indicator 2
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,1,INDICATOR_GROUP_TREND,param_ma2);

En el manejador OnDeinit() del asesor, quitamos la eliminación de los objetos de indicador creados:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Remove EA graphical objects by an object name prefix
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Remove created data objects of MA indicators
   if(CheckPointer(data_ma1_0)==POINTER_DYNAMIC)
      delete data_ma1_0;
   if(CheckPointer(data_ma1_1)==POINTER_DYNAMIC)
      delete data_ma1_1;
   if(CheckPointer(data_ma2_0)==POINTER_DYNAMIC)
      delete data_ma2_0;
   if(CheckPointer(data_ma2_1)==POINTER_DYNAMIC)
      delete data_ma2_1;
//--- Remove created data objects of AMA indicators
   if(CheckPointer(data_ama1_0)==POINTER_DYNAMIC)
      delete data_ama1_0;
   if(CheckPointer(data_ama1_1)==POINTER_DYNAMIC)
      delete data_ama1_1;
   if(CheckPointer(data_ama2_0)==POINTER_DYNAMIC)
      delete data_ama2_0;
   if(CheckPointer(data_ama2_1)==POINTER_DYNAMIC)
      delete data_ama2_1;
//--- Deinitialize library
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+

En comparación con el asesor anterior, ahora el manejador OnTick() se ha hecho más corto:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Handle the NewTick event in the library
   engine.OnTick(rates_data);

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      engine.EventsHandling();      // Working with events
     }
      
//--- Get indicator values by their IDs from data collections of their buffers from bars 0 and 1
   double ma1_value0=engine.IndicatorGetBufferValue(MA1,0,0), ma1_value1=engine.IndicatorGetBufferValue(MA1,0,1);
   double ma2_value0=engine.IndicatorGetBufferValue(MA2,0,0), ma2_value1=engine.IndicatorGetBufferValue(MA2,0,1);
   double ama1_value0=engine.IndicatorGetBufferValue(AMA1,0,0), ama1_value1=engine.IndicatorGetBufferValue(AMA1,0,1);
   double ama2_value0=engine.IndicatorGetBufferValue(AMA2,0,0), ama2_value1=engine.IndicatorGetBufferValue(AMA2,0,1);
   
//--- Display the values of indicator buffers to comment on the chart from data objects
   Comment
     (
      "ma1(1)=",DoubleToString(ma1_value1,6),", ma1(0)=",DoubleToString(ma1_value0,6),"  ",
      "ma2(1)=",DoubleToString(ma2_value1,6),", ma2(0)=",DoubleToString(ma2_value0,6),", \n",
      "ama1(1)=",DoubleToString(ama1_value1,6),", ama1(0)=",DoubleToString(ama1_value0,6),"  ",
      "ama1(1)=",DoubleToString(ama2_value1,6),", ama1(0)=",DoubleToString(ama2_value0,6)
     );
   
//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
  }
//+------------------------------------------------------------------+

Compilamos y ejecutamos el asesor en el gráfico, especificando en sus ajustes el uso solamente del símbolo y marco temporal actuales. Los datos de la primera barra y de la barra cero (actual) de todos los indicadores creados se visualizarán en los comentarios en el gráfico:


Para que se vea de forma más clara, en el gráfico se muestran los mismos indicadores con las mismas configuraciones: los datos de indicadores en los comentarios en el gráfico y en la ventana de datos (Ctrl+D) coinciden, y los valores en la barra actual se actualizan.

¿Qué es lo próximo?

En el siguiente artículo, comenzaremos a preparar la creación de las clases para trabajar con ticks y profundidad del mercado.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

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
Trabajando con las series temporales en la biblioteca DoEasy (Parte 46): Búferes de indicador de periodo y símbolos múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 47): Indicadores estándar de periodo y símbolo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 48): Indicadores de periodo y símbolo múltiples en un búfer en una subventana
Trabajando con las series temporales en la biblioteca DoEasy (Parte 49): Indicadores estándar de periodo, símbolo y búfer múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 50): Indicadores estándar de periodo y símbolo múltiples con desplazamiento
Trabajando con las series temporales en la biblioteca DoEasy (Parte 51): Indicadores estándar compuestos de período y símbolo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 52): Concepto multiplataforma de indicadores estándar de periodo y símbolo múltiples de búfer único
Trabajando con las series temporales en la biblioteca DoEasy (Parte 53): Clase del indicador abstracto básico
Trabajando con las series temporales en la biblioteca DoEasy (Parte 54): Clases herederas del indicador abstracto básico
Trabajando con las series temporales en la biblioteca DoEasy (Parte 55): Clase de colección de indicadores
Trabajando con las series temporales en la biblioteca DoEasy (Parte 56): Objeto del indicador personalizado, obtención de datos de parte de los objetos de indicador en la colección
Trabajando con las series temporales en la biblioteca DoEasy (Parte 57): Objeto de datos del búfer de indicador