English Русский 中文 Deutsch 日本語 Português
preview
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 56): Objeto del indicador personalizado, obtención de datos de parte de los objetos de indicador en la colección

MetaTrader 5Ejemplos | 25 enero 2021, 16:27
367 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Continuamos desarrollando la funcionalidad para trabajar con indicadores en los asesores expertos (EAs) creados a base de esta biblioteca.
Ya creamos las clases de los objetos de indicadores estándar y empezamos a colocarlos en la lista de colección. Para un «conjunto» completo nos falta sólo el objeto de indicador personalizado. Hoy, vamos a crear este objeto. El concepto de su construcción será diferente del concepto de la creación de los objetos de indicadores estándar.
Puesto que tenemos un conjunto final de indicadores estándar en el terminal, podemos crear objetos de indicadores estándar a base de los datos definidos y conocidos de antemano para cada uno de los indicadores. Pues, eso es lo que estamos haciendo, es decir, especificamos un conjunto de parámetros establecido estrictamente que corresponde al indicador a crear. Mientras que un indicador personalizado puede tener cualquier conjunto de parámetros, el que es imposible saber de antemano.
Por esta razón, el concepto de creación del objeto de indicador personalizado va a diferenciarse de la creación del objeto de indicador estándar.

Si para crear un objeto de indicador estándar basta con crear para cada uno de ellos su propio método de creación que va a contener todas sus propiedades necesarias en los parámetros de entrada, pues, para crear un indicador personalizado cuyo número y tipo de parámetros se desconocen de antemano, su método de creación tendrá que recibir una matriz de estructuras de los parámetros de entrada del indicador que ha sido rellenada de antemano. Entonces, todos los parámetros y propiedades necesarios para crear el indicador van a tomarse de esta matriz. Por eso, por desgracia, el usuario de la biblioteca tendrá que rellenar esta matriz de estructuras de los parámetros de entrada por si mismo en su programa para crear un indicador personalizado.

Puesto que tratamos de crear una biblioteca para facilitar la creación de nuestros programas, entonces, entre varias opciones de acceder a los indicadores creados en mi programa he elegido la siguiente: cada indicador creado será marcado con su propio identificador único. Usando este identificador, podremos acceder a cada uno de los indicadores creados. Gracias a que nosotros mismos los creamos, nosotros mismos les asignamos los identificadores, y sabemos perfectamente cuál de los identificadores asignados corresponde a uno u otro indicador creado en el programa.

Aparte del identificador, también habrá métodos de acceso al identificador según todos sus parámetros. Es decir, se podrá obtener el objeto de indicador indicando los mismos parámetros con los cuales este objeto de indicador ha sido creado, y luego trabajar con el objeto obtenido: obtener los datos de parte del indicador (los métodos de obtención de datos serán creados hoy), y copiarlos a las matrices para los análisis estadísticos (eso será implementado en los artículos posteriores).


Mejorando las clases de la biblioteca

Como siempre, en primer lugar, vamos a introducir nuevos mensajes de la biblioteca en el archivo \MQL5\Include\DoEasy\Data.mqh.
Añadimos los índices de mensajes nuevos:

   MSG_LIB_TEXT_IND_TEXT_GROUP,                       // Indicator group
   MSG_LIB_TEXT_IND_TEXT_GROUP_TREND,                 // Trend indicator
   MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR,            // Oscillator
   MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES,               // Volumes
   MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS,                // Arrow indicator
   MSG_LIB_TEXT_IND_TEXT_ID,                          // Indicator ID
   
   MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE,                 // Empty value for plotting where nothing will be drawn
   MSG_LIB_TEXT_IND_TEXT_SYMBOL,                      // Indicator symbol
   MSG_LIB_TEXT_IND_TEXT_NAME,                        // Indicator name
   MSG_LIB_TEXT_IND_TEXT_SHORTNAME,                   // Indicator short name
   
   MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS,              // Indicator parameters
   MSG_LIB_TEXT_IND_TEXT_APPLIED_VOLUME,              // Volume type for calculation
   MSG_LIB_TEXT_IND_TEXT_PERIOD,                      // Averaging period
   MSG_LIB_TEXT_IND_TEXT_FAST_PERIOD,                 // Fast MA period
   MSG_LIB_TEXT_IND_TEXT_SLOW_PERIOD,                 // Slow MA period
   MSG_LIB_TEXT_IND_TEXT_SIGNAL,                      // Difference averaging period
   MSG_LIB_TEXT_IND_TEXT_TENKAN_PERIOD,               // Tenkan-sen period
   MSG_LIB_TEXT_IND_TEXT_KIJUN_PERIOD,                // Kijun-sen period
   MSG_LIB_TEXT_IND_TEXT_SPANB_PERIOD,                // Senkou Span B period
   MSG_LIB_TEXT_IND_TEXT_JAW_PERIOD,                  // Period for jaw line calculation
   MSG_LIB_TEXT_IND_TEXT_TEETH_PERIOD,                // Period for teeth line calculation
   MSG_LIB_TEXT_IND_TEXT_LIPS_PERIOD,                 // Period for lips line calculation
   MSG_LIB_TEXT_IND_TEXT_JAW_SHIFT,                   // Horizontal shift of jaws line
   MSG_LIB_TEXT_IND_TEXT_TEETH_SHIFT,                 // Horizontal shift of teeth line
   MSG_LIB_TEXT_IND_TEXT_LIPS_SHIFT,                  // Horizontal shift of lips line
   MSG_LIB_TEXT_IND_TEXT_SHIFT,                       // Horizontal shift of the indicator
   MSG_LIB_TEXT_IND_TEXT_MA_METHOD,                   // Smoothing type
   MSG_LIB_TEXT_IND_TEXT_APPLIED_PRICE,               // Price type or handle
   MSG_LIB_TEXT_IND_TEXT_STD_DEVIATION,               // Number of standard deviations
   MSG_LIB_TEXT_IND_TEXT_DEVIATION,                   // Deviation of channel borders from the central line
   MSG_LIB_TEXT_IND_TEXT_STEP,                        // Price change step — acceleration factor
   MSG_LIB_TEXT_IND_TEXT_MAXIMUM,                     // Maximum step
   MSG_LIB_TEXT_IND_TEXT_KPERIOD,                     // K period (number of bars for calculation)
   MSG_LIB_TEXT_IND_TEXT_DPERIOD,                     // D period (primary smoothing period)
   MSG_LIB_TEXT_IND_TEXT_SLOWING,                     // Final smoothing
   MSG_LIB_TEXT_IND_TEXT_PRICE_FIELD,                 // Stochastic calculation method
   MSG_LIB_TEXT_IND_TEXT_CMO_PERIOD,                  // Chande Momentum period
   MSG_LIB_TEXT_IND_TEXT_SMOOTHING_PERIOD,            // Smoothing factor period
   MSG_LIB_TEXT_IND_TEXT_CUSTOM_PARAM,                // Input parameter
   
//--- CIndicatorsCollection
   MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,                // Error. Failed to add indicator object to list
   MSG_LIB_SYS_INVALID_IND_POINTER,                   // Error. Invalid pointer to indicator object is passed
   MSG_LIB_SYS_IND_ID_EXIST,                          // Error. The indicator object with the ID already exists
   
  };
//+------------------------------------------------------------------+

Añadimos los mensajes de texto correspondientes a los índices nuevamente añadidos:

   {"Стрелочный индикатор","Arrow indicator"},
   {"Идентификатор индикатора","Indicator ID"},
   {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"},
   {"Символ индикатора","Indicator symbol"},
   {"Имя индикатора","Indicator name"},
   {"Короткое имя индикатора","Indicator shortname"},
   
   {"Параметры индикатора","Indicator parameters"},
   {"Тип объема для расчета","Volume type for calculation"},
   {"Период усреднения","Averaging period"},
   {"Период быстрой скользящей","Fast MA period"},
   {"Период медленной скользящей","Slow MA period"},
   {"Период усреднения разности","Averaging period for their difference"},
   {"Период Tenkan-sen","Tenkan-sen period"},
   {"Период Kijun-sen","Kijun-sen period"},
   {"Период Senkou Span B","Senkou Span B period"},
   {"Период для расчета челюстей","Period for the calculation of jaws"},
   {"Период для расчета зубов","Period for the calculation of teeth"},
   {"Период для расчета губ","Period for the calculation of lips"},
   {"Смещение челюстей по горизонтали","Horizontal shift of jaws"},
   {"Смещение зубов по горизонтали","Horizontal shift of teeth"},
   {"Смещение губ по горизонтали","Horizontal shift of lips"},
   {"Смещение индикатора по горизонтали","Horizontal shift of the indicator"},
   {"Тип сглаживания","Smoothing type"},
   {"Тип цены или handle","Price type or handle"},
   {"Количество стандартных отклонений","Number of standard deviations"},
   {"Отклонение границ от средней линии","Deviation of boundaries from the midline"},
   {"Шаг изменения цены - коэффициент ускорения","Price increment step - acceleration factor"},
   {"Максимальный шаг","Maximum value of step"},
   {"K-период (количество баров для расчетов)","K-period (number of bars for calculations)"},
   {"D-период (период первичного сглаживания)","D-period (period of first smoothing)"},
   {"Окончательное сглаживание","Final smoothing"},
   {"Способ расчета стохастика","Stochastic calculation method"},
   {"Период Chande Momentum","Chande Momentum period"},
   {"Период фактора сглаживания","Smoothing factor period"},
   {"Входной параметр","Input parameter"},
   
   {"Ошибка. Не удалось добавить объект-индикатор в список","Error. Failed to add indicator object to list"},
   {"Ошибка. Передан неверный указатель на объект-индикатор","Error. Invalid pointer to indicator object passed"},
   {"Ошибка. Уже существует объект-индикатор с идентификатором","Error. There is already exist an indicator object with ID"},
   
  };
//+---------------------------------------------------------------------+


Al crear un objeto de indicador estándar, establecemos de inmediato un grupo en su constructor que va a corresponder al tipo del indicador: de tendencia, de flechas, oscilador o indicador de volúmenes. Lo podemos hacer para los indicadores estándar porque sabemos de antemano el tipo del indicador a crear. Para un indicador personalizado, no podemos saber su tipo de antemano (el grupo al que pertenece) y establecerlo en el constructor de la clase del objeto. Por eso, tenemos que crear el nuevo grupo de indicadores, «cualquiera», que va a asignarse automáticamente al objeto recién creado, permitiendo al usuario especificar por sí mismo el grupo de pertenencia del indicador personalizado ya después de la creación de su objeto.

En el archivo \MQL5\Include\DoEasy\Defines.mqh, añadimos la nueva constante a la enumeración del grupo de indicadores:

//+------------------------------------------------------------------+
//| Indicator group                                                  |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_GROUP
  {
   INDICATOR_GROUP_TREND,                                   // Trend indicator
   INDICATOR_GROUP_OSCILLATOR,                              // Oscillator
   INDICATOR_GROUP_VOLUMES,                                 // Volumes
   INDICATOR_GROUP_ARROWS,                                  // Arrow indicator
   INDICATOR_GROUP_ANY,                                     // Any indicator
  };
//+------------------------------------------------------------------+

Ya que hemos decidido acceder a los indicadores creados usando los identificadores únicos, necesitamos añadir una nueva propiedad a la enumeración de propiedades enteras del indicador:

//+------------------------------------------------------------------+
//| Indicator integer properties                                     |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_INTEGER
  {
   INDICATOR_PROP_STATUS = 0,                               // Indicator status (from enumeration ENUM_INDICATOR_STATUS)
   INDICATOR_PROP_TYPE,                                     // Indicator type (from enumeration ENUM_INDICATOR)
   INDICATOR_PROP_TIMEFRAME,                                // Indicator timeframe
   INDICATOR_PROP_HANDLE,                                   // Indicator handle
   INDICATOR_PROP_GROUP,                                    // Indicator group
   INDICATOR_PROP_ID,                                       // Indicator ID
  }; 
#define INDICATOR_PROP_INTEGER_TOTAL (6)                    // Total number of indicator integer properties
#define INDICATOR_PROP_INTEGER_SKIP  (0)                    // Number of indicator properties not used in sorting
//+------------------------------------------------------------------+

y por tanto, vamos a aumentar el número de propiedades enteras del indicador de 5 a 6.

Para poder buscar un indicador en la lista según su identificador, introducimos un nuevo criterio de ordenación de los indicadores de acuerdo con identificadores:

//+------------------------------------------------------------------+
//| Possible indicator sorting criteria                              |
//+------------------------------------------------------------------+
#define FIRST_INDICATOR_DBL_PROP          (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP)
#define FIRST_INDICATOR_STR_PROP          (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP+INDICATOR_PROP_DOUBLE_TOTAL-INDICATOR_PROP_DOUBLE_SKIP)
enum ENUM_SORT_INDICATOR_MODE
  {
//--- Sort by integer properties
   SORT_BY_INDICATOR_INDEX_STATUS = 0,                      // Sort by indicator status
   SORT_BY_INDICATOR_TYPE,                                  // Sort by indicator type
   SORT_BY_INDICATOR_TIMEFRAME,                             // Sort by indicator timeframe
   SORT_BY_INDICATOR_HANDLE,                                // Sort by indicator handle
   SORT_BY_INDICATOR_GROUP,                                 // Sort by indicator group
   SORT_BY_INDICATOR_ID,                                    // Sort by indicator ID
//--- Sort by real properties
   SORT_BY_INDICATOR_EMPTY_VALUE = FIRST_INDICATOR_DBL_PROP,// Sort by the empty value for plotting where nothing will be drawn
//--- Sort by string properties
   SORT_BY_INDICATOR_SYMBOL = FIRST_INDICATOR_STR_PROP,     // Sort by indicator symbol
   SORT_BY_INDICATOR_NAME,                                  // Sort by indicator name
   SORT_BY_INDICATOR_SHORTNAME,                             // Sort by indicator short name
  };
//+------------------------------------------------------------------+


Ya que han sido añadidas nuevas propiedades para el objeto de indicador, ahora hay que mejorar un poco la clase del objeto del indicador abstracto en el archivo \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh.

En la sección pública de la clase, escribiremos dos métodos para establecer y obtener la propiedad «identificador del indicador»:

//--- 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);                               }
   
//--- Return (1) status, (2) group, (3) timeframe, (4) type, (5) handle, (6) ID,
//--- (7) empty value of buffers, (8) name, (9) short name, (10) indicator symbol
   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);                       }

En los objetos de indicadores estándar, podíamos escribir los métodos para visualizar la descripción de cada parámetro del indicador porque sabíamos con certeza qué parámetro del indicador determinado estábamos describiendo. Para visualizar la descripción de los parámetros de un indicador personalizado, no podemos saber exactamente el propósito de cada parámetro del indicador desconocido desde el principio. Por eso, aquí tendremos que visualizar simplemente la descripción de cada siguiente parámetro desde la matriz de parámetros del indicador MqlParam.
En la sección pública de la clase del objeto de indicador abstracto, declaramos el método para visualizar la descripción de los parámetros de la estructura MqlParam, y dos métodos más: para obtener datos de parte del objeto de indicador según el índice y la hora de la barra:

//--- 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;
   
//--- 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);
  };
//+------------------------------------------------------------------+

Escribimos la implementación de estos métodos fuera del cuerpo de la clase:

El método que devuelve la descripción del parámetro de la matriz de estructuras MqlParam:

//+------------------------------------------------------------------+
//| Return the description of parameter of m_mql_param array         |
//+------------------------------------------------------------------+
string CIndicatorDE::GetMqlParamDescription(const int index) const
  {
   return "["+(string)index+"] "+MqlParameterDescription(this.m_mql_param[index]);
  }
//+------------------------------------------------------------------+

El índice del parámetro en la matriz se transfiere al método. Luego, se crea una cadena a base del índice de la matriz y la descripción del parámetro de acuerdo con los datos almacenados en la estructura según el índice especificado de la matriz. Más abajo escribiremos la función MqlParameterDescription() para devolver la descripción de datos de la estructura MqlParam.

Métodos para devolver datos del indicador según el índice y la hora de la barra:

//+------------------------------------------------------------------+
//| Return data of specified buffer from specified bar by index      |
//+------------------------------------------------------------------+
double CIndicatorDE::GetDataBuffer(const int buffer_num,const int index)
  {
   double array[1]={EMPTY_VALUE};
   int copied=::CopyBuffer(this.Handle(),buffer_num,index,1,array);
   return(copied==1 ? array[0] : this.EmptyValue());
  }
//+------------------------------------------------------------------+
//| Return data of specified buffer from specified bar by time       |
//+------------------------------------------------------------------+
double CIndicatorDE::GetDataBuffer(const int buffer_num,const datetime time)
  {
   double array[1]={EMPTY_VALUE};
   int copied=::CopyBuffer(this.Handle(),buffer_num,time,1,array);
   return(copied==1 ? array[0] : this.EmptyValue());
  }
//+------------------------------------------------------------------+

Los métodos reciben el índice o la hora de la barra cuyos datos hay que obtener del indicador. Usamos la función CopyBuffer() para solicitar una barra según el índice o la hora y devolvemos el resultado obtenido escrito en la matriz. Si no se ha podido obtener los datos por alguna razón, los métodos devuelven un valor vacío establecido para el objeto de indicador.

En el constructor de la clase, establecemos el identificador del indicador por defecto (-1):

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CIndicatorDE::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[])
  {
//--- Set collection ID to the object
   this.m_type=COLLECTION_INDICATORS_ID;
//--- Write description of indicator type
   this.m_ind_type_description=IndicatorTypeDescription(ind_type);
//--- If parameter array value passed to constructor is more than zero
//--- fill in the array of object parameters with data from the array passed to constructor
   int count=::ArrayResize(this.m_mql_param,::ArraySize(mql_params));
   for(int i=0;i<count;i++)
     {
      this.m_mql_param[i].type         = mql_params[i].type;
      this.m_mql_param[i].double_value = mql_params[i].double_value;
      this.m_mql_param[i].integer_value= mql_params[i].integer_value;
      this.m_mql_param[i].string_value = mql_params[i].string_value;
     }
//--- Create indicator handle
   int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,this.m_mql_param);
   
//--- Save integer properties
   this.m_long_prop[INDICATOR_PROP_STATUS]                     = status;
   this.m_long_prop[INDICATOR_PROP_TYPE]                       = ind_type;
   this.m_long_prop[INDICATOR_PROP_GROUP]                      = group;
   this.m_long_prop[INDICATOR_PROP_TIMEFRAME]                  = timeframe;
   this.m_long_prop[INDICATOR_PROP_HANDLE]                     = handle;
   this.m_long_prop[INDICATOR_PROP_ID]                         = WRONG_VALUE;
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(INDICATOR_PROP_EMPTY_VALUE)]=EMPTY_VALUE; 
//--- Save string properties
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_SYMBOL)]   = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_NAME)]     = name;
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_SHORTNAME)]= shortname;
  }
//+------------------------------------------------------------------+

Como cada manejador del indicador es único, así como, su identificador que vamos a establecer personalmente, pues, habrá que omitir estos dos parámetros a la hora de comparar la coincidencia de los parámetros de dos indicadores. De lo contrario, cada indicador será identificado como único, y no podremos comparar de forma correcta si los parámetros de estos indicadores son idénticos.
Para que eso no ocurra, omitimos la propiedad «manejador» e «identificador» en el método de comparación de dos objetos de indicador:

//+------------------------------------------------------------------+
//| Compare CIndicatorDE objects with each other by all properties   |
//+------------------------------------------------------------------+
bool CIndicatorDE::IsEqual(CIndicatorDE *compared_obj) const
  {
   if(!IsEqualMqlParamArrays(compared_obj.m_mql_param))
      return false;
   int beg=0, end=INDICATOR_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_INDICATOR_PROP_INTEGER prop=(ENUM_INDICATOR_PROP_INTEGER)i;
      if(prop==INDICATOR_PROP_HANDLE || prop==INDICATOR_PROP_ID) continue;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=INDICATOR_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_INDICATOR_PROP_DOUBLE prop=(ENUM_INDICATOR_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=INDICATOR_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_INDICATOR_PROP_STRING prop=(ENUM_INDICATOR_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

Añadimos la visualización de la descripción de la propiedad del indicador «identificador» al método que devuelve la descripción de una propiedad entera del indicador:

//+------------------------------------------------------------------+
//| Return description of indicator's integer property               |
//+------------------------------------------------------------------+
string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return
     (
      property==INDICATOR_PROP_STATUS     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==INDICATOR_PROP_TYPE       ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeDescription()
         )  :
      property==INDICATOR_PROP_GROUP      ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetGroupDescription()
         )  :
      property==INDICATOR_PROP_ID         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==INDICATOR_PROP_TIMEFRAME  ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTimeframeDescription()
         )  :
      property==INDICATOR_PROP_HANDLE     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Ahora, añadimos la función para mostrar la descripción de la estructura MqlParam al archivo de funciones de servicio \MQL5\Include\DoEasy\Services\DELib.mqh :

//+------------------------------------------------------------------+
//| Return the description of parameter MqlParam array               |
//+------------------------------------------------------------------+
string MqlParameterDescription(const MqlParam &mql_param)
  {
   int type=mql_param.type;
   string res=CMessage::Text(MSG_ORD_TYPE)+" "+typename(type)+": ";
   //--- string parameter type
   if(type==TYPE_STRING)
      res+=mql_param.string_value;
   //--- datetime parameter type
   else if(type==TYPE_DATETIME)
      res+=TimeToString(mql_param.integer_value,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   //--- color parameter type
   else if(type==TYPE_COLOR)
      res+=ColorToString((color)mql_param.integer_value,true);
   //--- bool parameter type
   else if(type==TYPE_BOOL)
      res+=(string)(bool)mql_param.integer_value;
   //--- integer types
   else if(type>TYPE_BOOL && type<TYPE_FLOAT)
      res+=(string)mql_param.integer_value;
   //--- real types
   else
      res+=DoubleToString(mql_param.double_value,8);
   return res;
  }
//+------------------------------------------------------------------+

En la estructura MqlParam hay varios campos. Uno de ellos contiene el tipo de datos almacenados en la estructura. De acuerdo con este tipo de datos, podemos comprender de qué campo de la estructura hay que obtener los datos, y qué función de representación string de datos hay que usar para mostrar los datos en el registro.

Obtenemos el tipo de datos y empezamos a generar la cadena compuesta de la palabra "Tipo " +" "+ descripción string del tipo de datos + ": ".
A continuación, dependiendo del tipo de datos, a la cadena ya creada se le añade la descripción del valor que se almacena en el campo de la estructura correspondiente al tipo. Para eso usamos las funciones estándar para visualizar las representaciones string del tipo de datos necesario.

Como resultado, al usar el método de la visualización de los parámetros de la estructura MqlParam de la clase del objeto del indicador abstracto y la función de servicio considerada antes en registro del terminal, la descripción de los parámetros del indicador personalizado con cuatro parámetros tendrá el siguiente aspecto:

--- Indicator parameters --- 
- [1] int type: 13
- [2] int type: 0
- [3] int type: 0
- [4] int type: 2

Ya que hemos añadido una nueva propiedad al objeto de indicador (su identificador), entonces, en todas las clases de todos los objetos de indicadores cuyos archivos se encuentran en la carpeta \MQL5\Include\DoEasy\Objects\Indicators\Standart\, vamos a hacer una pequeña adición a los métodos de visualizar el nombre breve del indicador —simplemente, añadimos el valor del identificador al nombre breve:

//+------------------------------------------------------------------+
//| Display a short description of indicator object in the journal   |
//+------------------------------------------------------------------+
void CIndAC::PrintShort(void)
  {
   string id=(this.ID()>WRONG_VALUE ? ", id #"+(string)this.ID()+"]" : "]");
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [handle ",this.Handle(),id);
  }
//+------------------------------------------------------------------+

Aquí: creamos la descripción del identificador. Además, si el valor del identificador es más de -1, el identificador va a mostrarse; de lo contrario, si no hay identificador (su valor es -1), no va a mostrarse en la descripción (sólo corchete de cierre). A continuación, añadimos la cadena obtenida a la descripción breve del indicador.

Estas mejoras ya han sido introducidas en todas las clases de los objetos de indicadores.


Objeto del indicador personalizado

Ahora, vamos a escribir la clase del objeto de indicador personalizado. Vamos a ubicarla en la carpeta de los indicadores estándar de la biblioteca \MQL5\Include\DoEasy\Objects\Indicators\Standart\. Eso se debe a que la lista de los indicadores del terminal también contiene el indicador personalizado, y eso quiere decir que el mismo también tiene que encontrarse en la lista de los indicadores del terminal.

Vamos a analizar la clase en conjunto:

//+------------------------------------------------------------------+
//|                                                    IndCustom.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"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\\IndicatorDE.mqh"
//+------------------------------------------------------------------+
//| Custom indicator                                                 |
//+------------------------------------------------------------------+
class CIndCustom : public CIndicatorDE 
  {
private:

public:
   //--- Constructor
                     CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,MqlParam &mql_param[]) :
                        CIndicatorDE(IND_CUSTOM,symbol,timeframe,
                                     INDICATOR_STATUS_CUSTOM,
                                     INDICATOR_GROUP_ANY,
                                     mql_param[0].string_value,
                                     mql_param[0].string_value+"("+(symbol==NULL || symbol=="" ? ::Symbol() : symbol)+
                                                               ","+TimeframeDescription(timeframe)+")",mql_param) {}
   //--- Supported indicator properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_INTEGER property);
   
//--- Display (1) a short description, (2) description of indicator object parameters in the journal
   virtual void      PrintShort(void);
   virtual void      PrintParameters(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if the indicator supports the passed               |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the indicator supports the passed               |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Display a short description of indicator object in the journal   |
//+------------------------------------------------------------------+
void CIndCustom::PrintShort(void)
  {
   string id=(this.ID()>WRONG_VALUE ? ", id #"+(string)this.ID()+"]" : "]");
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [handle ",this.Handle(),id);
  }
//+------------------------------------------------------------------+
//| Display parameter description of indicator object in the journal |
//+------------------------------------------------------------------+
void CIndCustom::PrintParameters(void)
  {
   ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- ");
   
   int total=::ArraySize(this.m_mql_param);
   for(int i=1;i<total;i++)
     {
      ::Print(" - ",this.GetMqlParamDescription(i));
     }
  }
//+------------------------------------------------------------------+

Ya estamos familiarizados con todos estos métodos en las clases de los objetos de indicadores estándar. Pero a diferencia de ellos: aquí, el constructor de la clase no recibe los parámetros del indicador creado usando las variables de entrada, sino usando una matriz de estructuras de los parámetros de entrada MqlParam creada de antemano. Al constructor cerrado de la clase del objeto del indicador abstracto se le pasa el estatus «indicador personalizado» y el grupo «cualquier indicador». Como el nombre, le transmitimos el primer elemento de la matriz de parámetros, que durante la creación de un indicador personalizado, tiene por defecto el tipo TYPE_STRING y el valor del campo string_value contiene el nombre del indicador personalizado. De la misma manera, se crea el nombre breve del indicador, pero con adición de la descripción del símbolo y marco temporal. A continuación, podremos usar el nombre y el nombre breve a través de los métodos de la clase padre SetName() y SetShortName(). Lo único es que el nombre del indicador ya contiene la ruta hacia el indicador. Por eso, -por lo menos en esta fase del desarrollo de la biblioteca- no se puede alterar el nombre del indicador personalizado que ya ha sido creado.

En el método que visualiza la descripción de los parámetros del objeto de indicador en el registro, hacemos lo siguiente: primero, mostramos el encabezado, luego, imprimimos en el ciclo cada siguiente parámetro de la matriz usando los métodos considerados antes (en particular, el método de la clase padre GetMqlParamDescription()).

Todos los objetos de indicador se almacenan en la lista de colección de la clase CIndicatorsCollection en el archivo \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh.

Al añadir el indicador a la lista de colección, necesitamos verificar adicionalmente si su identificador es único. Además, hay que añadir la visibilidad de la clase del indicador personalizado para que la clase de colección de los indicadores pueda trabajar con ella.

Incluimos el archivo de la clase del indicador personalizado a la clase de colección de los objetos de indicador:

//+------------------------------------------------------------------+
//|                                         IndicatorsCollection.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"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Indicators\Standart\IndAC.mqh"
#include "..\Objects\Indicators\Standart\IndAD.mqh"
#include "..\Objects\Indicators\Standart\IndADX.mqh"
#include "..\Objects\Indicators\Standart\IndADXW.mqh"
#include "..\Objects\Indicators\Standart\IndAlligator.mqh"
#include "..\Objects\Indicators\Standart\IndAMA.mqh"
#include "..\Objects\Indicators\Standart\IndAO.mqh"
#include "..\Objects\Indicators\Standart\IndATR.mqh"
#include "..\Objects\Indicators\Standart\IndBands.mqh"
#include "..\Objects\Indicators\Standart\IndBears.mqh"
#include "..\Objects\Indicators\Standart\IndBulls.mqh"
#include "..\Objects\Indicators\Standart\IndBWMFI.mqh"
#include "..\Objects\Indicators\Standart\IndCCI.mqh"
#include "..\Objects\Indicators\Standart\IndChaikin.mqh"
#include "..\Objects\Indicators\Standart\IndCustom.mqh"
#include "..\Objects\Indicators\Standart\IndDEMA.mqh"
#include "..\Objects\Indicators\Standart\IndDeMarker.mqh"
#include "..\Objects\Indicators\Standart\IndEnvelopes.mqh"
#include "..\Objects\Indicators\Standart\IndForce.mqh"
#include "..\Objects\Indicators\Standart\IndFractals.mqh"
#include "..\Objects\Indicators\Standart\IndFRAMA.mqh"
#include "..\Objects\Indicators\Standart\IndGator.mqh"
#include "..\Objects\Indicators\Standart\IndIchimoku.mqh"
#include "..\Objects\Indicators\Standart\IndMA.mqh"
#include "..\Objects\Indicators\Standart\IndMACD.mqh"
#include "..\Objects\Indicators\Standart\IndMFI.mqh"
#include "..\Objects\Indicators\Standart\IndMomentum.mqh"
#include "..\Objects\Indicators\Standart\IndOBV.mqh"
#include "..\Objects\Indicators\Standart\IndOsMA.mqh"
#include "..\Objects\Indicators\Standart\IndRSI.mqh"
#include "..\Objects\Indicators\Standart\IndRVI.mqh"
#include "..\Objects\Indicators\Standart\IndSAR.mqh"
#include "..\Objects\Indicators\Standart\IndStDev.mqh"
#include "..\Objects\Indicators\Standart\IndStoch.mqh"
#include "..\Objects\Indicators\Standart\IndTEMA.mqh"
#include "..\Objects\Indicators\Standart\IndTRIX.mqh"
#include "..\Objects\Indicators\Standart\IndVIDYA.mqh"
#include "..\Objects\Indicators\Standart\IndVolumes.mqh"
#include "..\Objects\Indicators\Standart\IndWPR.mqh"
//+------------------------------------------------------------------+

En la sección privada de la clase, declaramos el método de adición del objeto de indicador a la colección y el método que verifica si el objeto de indicador con identificador especificado se encuentre en la lista:

//+------------------------------------------------------------------+
//| Indicator collection                                             |
//+------------------------------------------------------------------+
class CIndicatorsCollection : public CObject
  {
private:
   CListObj                m_list;                       // List of indicator objects
   MqlParam                m_mql_param[];                // Array of indicator parameters

//--- (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);
//--- Return the indicator index in the list
   int                     Index(CIndicatorDE *compared_obj);
//--- Check presence of indicator object with specified id in the list
   bool                    CheckID(const int id);

public:

En la sección pública de la clase, declaramos el método que devuelve el puntero al objeto del indicador personalizado según su grupo y parámetros especificados en la matriz MqlParam (a diferencia de los indicadores estándar, podemos especificar los parámetros sólo a través de su transferencia en esta matriz):

   CIndicatorDE           *GetIndCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price);
   CIndicatorDE           *GetIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,ENUM_INDICATOR_GROUP group,MqlParam &param[]);
   CIndicatorDE           *GetIndDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price);

En el bloque de declaración de los métodos de creación de indicadores, primero, añadimos la indicación del identificador de cada indicador a los parámetros de cada método; segundo, añadimos la declaración del método para crear el indicador personalizado. No vamos a mostrar la lista completa de la declaración de estos métodos. Mostraremos sólo tres de ellos (todos los métodos ya han sido mejorados y están disponibles en los archivos adjuntos al artículo):

   int                     CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                       const int ma_period=14,
                                       const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL);
   int                     CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                       ENUM_INDICATOR_GROUP group,
                                       MqlParam &mql_param[]);
   int                     CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                       const int ma_period=14,
                                       const int ma_shift=0,
                                       const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);

En todos los métodos, después de los parámetros de entrada del símbolo y marco temporal, ha sido añadida la especificación obligatoria del identificador del indicador durante su creación.
En el método de creación del indicador personalizado, también se especifica el grupo del indicador y se transmite la matriz de parámetros del indicador creada y rellenada de antemano, a base de la cual será creado el indicador personalizado.

Al final del listado del cuerpo de la clase, declaramos el método para asignar el identificador al indicador especificado y el método que devuelve el objeto de indicador según el identificador especificado:

//--- Set ID for the specified indicator
   void                    SetID(CIndicatorDE *indicator,const int id);
   
//--- Return indicator object by its ID
   CIndicatorDE           *GetIndByID(const uint id);

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

//--- Constructor
                           CIndicatorsCollection();

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

Ahora, vamos a considerar todos los métodos declarados.

En el método privado de la creación de un nuevo objeto de indicador, añadimos la creación del nuevo objeto de indicador personalizado:

//+------------------------------------------------------------------+
//| Create a new indicator object                                    |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   string symbol=(symbol_name==NULL || symbol_name=="" ? ::Symbol() : symbol_name);
   ENUM_TIMEFRAMES timeframe=(period==PERIOD_CURRENT ? ::Period() : period);
   CIndicatorDE *indicator=NULL;
   switch(ind_type)
     {
      case IND_AC          : indicator=new CIndAC(symbol,timeframe,mql_param);         break;
      case IND_AD          : indicator=new CIndAD(symbol,timeframe,mql_param);         break;
      case IND_ADX         : indicator=new CIndADX(symbol,timeframe,mql_param);        break;
      case IND_ADXW        : indicator=new CIndADXW(symbol,timeframe,mql_param);       break;
      case IND_ALLIGATOR   : indicator=new CIndAlligator(symbol,timeframe,mql_param);  break;
      case IND_AMA         : indicator=new CIndAMA(symbol,timeframe,mql_param);        break;
      case IND_AO          : indicator=new CIndAO(symbol,timeframe,mql_param);         break;
      case IND_ATR         : indicator=new CIndATR(symbol,timeframe,mql_param);        break;
      case IND_BANDS       : indicator=new CIndBands(symbol,timeframe,mql_param);      break;
      case IND_BEARS       : indicator=new CIndBears(symbol,timeframe,mql_param);      break;
      case IND_BULLS       : indicator=new CIndBulls(symbol,timeframe,mql_param);      break;
      case IND_BWMFI       : indicator=new CIndBWMFI(symbol,timeframe,mql_param);      break;
      case IND_CCI         : indicator=new CIndCCI(symbol,timeframe,mql_param);        break;
      case IND_CHAIKIN     : indicator=new CIndCHO(symbol,timeframe,mql_param);        break;
      case IND_DEMA        : indicator=new CIndDEMA(symbol,timeframe,mql_param);       break;
      case IND_DEMARKER    : indicator=new CIndDeMarker(symbol,timeframe,mql_param);   break;
      case IND_ENVELOPES   : indicator=new CIndEnvelopes(symbol,timeframe,mql_param);  break;
      case IND_FORCE       : indicator=new CIndForce(symbol,timeframe,mql_param);      break;
      case IND_FRACTALS    : indicator=new CIndFractals(symbol,timeframe,mql_param);   break;
      case IND_FRAMA       : indicator=new CIndFRAMA(symbol,timeframe,mql_param);      break;
      case IND_GATOR       : indicator=new CIndGator(symbol,timeframe,mql_param);      break;
      case IND_ICHIMOKU    : indicator=new CIndIchimoku(symbol,timeframe,mql_param);   break;
      case IND_MA          : indicator=new CIndMA(symbol,timeframe,mql_param);         break;
      case IND_MACD        : indicator=new CIndMACD(symbol,timeframe,mql_param);       break;
      case IND_MFI         : indicator=new CIndMFI(symbol,timeframe,mql_param);        break;
      case IND_MOMENTUM    : indicator=new CIndMomentum(symbol,timeframe,mql_param);   break;
      case IND_OBV         : indicator=new CIndOBV(symbol,timeframe,mql_param);        break;
      case IND_OSMA        : indicator=new CIndOsMA(symbol,timeframe,mql_param);       break;
      case IND_RSI         : indicator=new CIndRSI(symbol,timeframe,mql_param);        break;
      case IND_RVI         : indicator=new CIndRVI(symbol,timeframe,mql_param);        break;
      case IND_SAR         : indicator=new CIndSAR(symbol,timeframe,mql_param);        break;
      case IND_STDDEV      : indicator=new CIndStDev(symbol,timeframe,mql_param);      break;
      case IND_STOCHASTIC  : indicator=new CIndStoch(symbol,timeframe,mql_param);      break;
      case IND_TEMA        : indicator=new CIndTEMA(symbol,timeframe,mql_param);       break;
      case IND_TRIX        : indicator=new CIndTRIX(symbol,timeframe,mql_param);       break;
      case IND_VIDYA       : indicator=new CIndVIDYA(symbol,timeframe,mql_param);      break;
      case IND_VOLUMES     : indicator=new CIndVolumes(symbol,timeframe,mql_param);    break;
      case IND_WPR         : indicator=new CIndWPR(symbol,timeframe,mql_param);        break;
      case IND_CUSTOM      : indicator=new CIndCustom(symbol,timeframe,mql_param);     break;
      default: break;
     }
   return indicator;
  }
//+------------------------------------------------------------------+

Modificamos el método que añade el nuevo objeto de indicador a la lista de colección:

//+------------------------------------------------------------------+
//| Add a new indicator object to collection list                    |
//+------------------------------------------------------------------+
int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id)
  {
//--- 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);
//--- Return the handle of a new indicator added to the list
   return indicator.Handle();
  }
//+------------------------------------------------------------------+

Ahora, el identificador del indicador se pasa en el método también. Si todavía no hay indicador con este identificador en la lista de colección, al objeto de indicador se le asigna el identificador especificado. De lo contrario, el identificador del indicador va a ser -1 por defecto.

Ahora, todos los métodos de la creación de los objetos de indicador se han hecho más cortos.
Vamos a ver el ejemplo de la creación de los objetos de indicadores AC y Alligator:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Create new indicator object Alligator                            |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                           const int jaw_period=13,
                                           const int jaw_shift=8,
                                           const int teeth_period=8,
                                           const int teeth_shift=5,
                                           const int lips_period=5,
                                           const int lips_shift=3,
                                           const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                           const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN)
  {
//--- Add required indicator parameters to the array of parameter structures
   ::ArrayResize(this.m_mql_param,8);
   this.m_mql_param[0].type=TYPE_INT;
   this.m_mql_param[0].integer_value=jaw_period;
   this.m_mql_param[1].type=TYPE_INT;
   this.m_mql_param[1].integer_value=jaw_shift;
   this.m_mql_param[2].type=TYPE_INT;
   this.m_mql_param[2].integer_value=teeth_period;
   this.m_mql_param[3].type=TYPE_INT;
   this.m_mql_param[3].integer_value=teeth_shift;
   this.m_mql_param[4].type=TYPE_INT;
   this.m_mql_param[4].integer_value=lips_period;
   this.m_mql_param[5].type=TYPE_INT;
   this.m_mql_param[5].integer_value=lips_shift;
   this.m_mql_param[6].type=TYPE_INT;
   this.m_mql_param[6].integer_value=ma_method;
   this.m_mql_param[7].type=TYPE_INT;
   this.m_mql_param[7].integer_value=applied_price;
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_ALLIGATOR,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);
  }
//+------------------------------------------------------------------+

Ahora será suficiente sólo rellenar la estructura de los parámetros de entrada, crear el indicador y llamar al método de su adición a la lista de colección. Estas modificaciones han sido hechas en todos los métodos de creación de los objetos de indicador. Por eso, no vamos a analizarlos aquí, a excepción del método para crear 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,
                                        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);
  }
//+------------------------------------------------------------------+

Aquí, es un poco diferente. Aquí, además del identificador, el método de la creación también recibe el grupo del indicador. Y todos los parámetros del indicador creado se transmiten inmediatamente en la matriz de parámetros MqlParam debido a que no podemos saber de antemano de los parámetros del indicador personalizado que se crea.

Absolutamente todos los métodos de la creación de los indicadores estándar han recibido los valores estándar predefinidos para cada parámetro de entrada. De esta manera, para crear un indicador estándar con parámetros predefinidos bastará con especificar un símbolo, marco temporal y un identificador.

Implementación del método que retorna el puntero al objeto de indicador personalizado:

//+------------------------------------------------------------------+
//| Return pointer to object- custom indicator                       |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,ENUM_INDICATOR_GROUP group,MqlParam &param[])
  {
   CIndicatorDE *tmp=new CIndCustom(symbol,timeframe,param);
   if(tmp==NULL)
      return NULL;
   tmp.SetGroup(group);
   int index=this.Index(tmp);
   delete tmp;
   return(index>WRONG_VALUE ? this.m_list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

Aquí: creamos un objeto de indicador temporal para buscar el mismo objeto en la lista de colección, le asignamos un grupo y obtenemos el índice del objeto encontrado en la lista de colección. Luego, eliminamos el objeto temporal y devolvemos el puntero al objeto según el índice encontrado, o NULL si este objeto no ha sido encontrado en la lista.

El método que devuelve la presencia del objeto de indicador con id especificado en la lista:

//+------------------------------------------------------------------+
//| Check presence of indicator object with specified id in the list |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::CheckID(const int id)
  {
   CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL);
   return(list!=NULL && list.Total()!=0);
  }
//+------------------------------------------------------------------+

Obtenemos la lista de objetos de indicador con identificador especificado y devolvemos la bandera de verificación de que la lista es válida y no está vacía (la lista debe contener un objeto).

El método que establece un identificador para el indicador especificado:

//+------------------------------------------------------------------+
//| Set ID for the specified indicator                               |
//+------------------------------------------------------------------+
void CIndicatorsCollection::SetID(CIndicatorDE *indicator,const int id)
  {
   if(indicator==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_IND_POINTER));
      return;
     }
   if(id>WRONG_VALUE)
     {
      if(CheckID(id))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_IND_ID_EXIST)," #",(string)id);
         return;
        }
     }
   indicator.SetID(id);
  }
//+------------------------------------------------------------------+

El método recibe el puntero al objeto de indicador para el que es necesario establecer el identificador transmitido por el parámetro al método.

Si ha sido transmitido un puntero inválido, avisamos de ello y salimos del método.
Si el valor del identificador es más de -1, verificamos la presencia del objeto de indicador con este identificador, y si ya existe, avisamos de ello y salimos.
Si todas las verificaciones han sido hechas, establecemos el identificador para el objeto. En caso si el valor del identificador transmitido al método es menos de 0, este identificador se le asigna al objeto sin verificación alguna, y este identificador del objeto de indicador significa su ausencia.

El método que devuelve el objeto de indicador según el identificador especificado:

//+------------------------------------------------------------------+
//| Return indicator object by its ID                                |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndByID(const uint id)
  {
   CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL);
   return(list==NULL || list.Total()==0 ? NULL : list.At(list.Total()-1));
  }
//+------------------------------------------------------------------+

Aquí: obtenemos la lista de objetos de indicador con identificador especificado y devolvemos NULL (si no hemos podido obtener la lista, o ella está vacía) o el puntero al objeto con identificador necesario. Puesto que el objeto con identificador especificado puede ser sólo uno, aquí no importa el índice a especificar en la lista obtenida: el primero o el último. Aquí, especificamos el último.

La prueba de la creación de indicadores en los asesores expertos ha demostrado un problema. Cuando cambiamos del marco temporal, se crean adicionalmente los indicadores exactamente iguales pero con otro marco temporal. Y es verdad: es que los indicadores con los mismos parámetros de entrada pero calculados en marcos temporales diferentes son dos indicadores diferentes. Para evitar este problema, será suficiente limpiar la lista de los indicadores creados al deinicializar el programa. Para eso, en el objeto principal de la biblioteca CEngine en el archivo \MQL5\Include\DoEasy\Engine.mqh declaramos el nuevo manejador OnDeinit():

//--- (1) Timer, event handler (2) NewTick, (3) Calculate, Deinit
   void                 OnTimer(SDataCalculate &data_calculate);
   void                 OnTick(SDataCalculate &data_calculate,const uint required=0);
   int                  OnCalculate(SDataCalculate &data_calculate,const uint required=0);
   void                 OnDeinit(void);

Y escribimos su implementación fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Deinitialize library                                             |
//+------------------------------------------------------------------+
void CEngine::OnDeinit(void)
  {
   this.m_indicators.GetList().Clear();
  }
//+------------------------------------------------------------------+

Este método de la clase va a invocarse desde el manejador OnDeinit() del programa. Lo que tenemos aquí es la llamada al método de la limpieza de la lista de colección para el objeto de colección de indicadores.

Prueba

Para simular la creación de diferentes indicadores y obtener los datos de parte de los objetos de indicador, usamos el asesor experto del artículo anterior.

Vamos a guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part56\ con el nombre nuevo TestDoEasyPart56.mq5.

En este EA, creamos dos indicadores personalizados Moving Average, pero con parámetros diferentes (usamos los indicadores de la carpeta de los ejemplos de indicadores de la entrega estándar del terminal \MQL5\Indicators\Examples\). Además, creamos dos indicadores estándar Adaptive Moving Average también con parámetros de entrada diferentes

En el área global, establecemos las macrosustituciones para facilitar el acceso a los indicadores por sus identificadores y declaramos dos matrices de parámetros para crear dos indicadores personalizados:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart56.mq5 |
//|                        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"
//--- includes
#include <DoEasy\Engine.mqh>
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_SET_STOP_LOSS,
   BUTT_SET_TAKE_PROFIT,
   BUTT_PROFIT_WITHDRAWAL,
   BUTT_TRAILING_ALL
  };
#define TOTAL_BUTT   (20)
#define MA1          (1)
#define MA2          (2)
#define AMA1         (3)
#define AMA2         (4)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds
//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           distance_pending_request;
uint           bars_delay_pending_request;
uint           slippage;
bool           trailing_on;
bool           pressed_pending_buy;
bool           pressed_pending_buy_limit;
bool           pressed_pending_buy_stop;
bool           pressed_pending_buy_stoplimit;
bool           pressed_pending_close_buy;
bool           pressed_pending_close_buy2;
bool           pressed_pending_close_buy_by_sell;
bool           pressed_pending_sell;
bool           pressed_pending_sell_limit;
bool           pressed_pending_sell_stop;
bool           pressed_pending_sell_stoplimit;
bool           pressed_pending_close_sell;
bool           pressed_pending_close_sell2;
bool           pressed_pending_close_sell_by_buy;
bool           pressed_pending_delete_all;
bool           pressed_pending_close_all;
bool           pressed_pending_sl;
bool           pressed_pending_tp;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         array_used_symbols[];
string         array_used_periods[];
bool           testing;
uchar          group1;
uchar          group2;
double         g_point;
int            g_digits;
//--- Arrays of custom indicator parameters
MqlParam       param_ma1[];
MqlParam       param_ma2[];
//+------------------------------------------------------------------+

En esencia, las macrosustituciones declaradas representan una descripción de valores numéricos de los identificadores. Es que resulta más fácil llamar al identificador del indicador por su nombre que por su valor.

En el manejador OnInit() del asesor, creamos los cuatro indicadores y visualizamos los datos de todos los indicadores creados en el registro:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq);
   bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq);
   g_point=SymbolInfoDouble(NULL,SYMBOL_POINT);
   g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
//--- Initialize random group numbers
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Initialize DoEasy library
   OnInitDoEasy();
   
//--- 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,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,INDICATOR_GROUP_TREND,param_ma2);
   
   //--- Create indicator 3
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA1);
   //--- Create indicator 4
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA2,14);
   
   //--- Display descriptions of created indicators
   engine.GetIndicatorsCollection().Print();
   engine.GetIndicatorsCollection().PrintShort();
   
//--- Check and remove remaining EA graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Set trailing activation button status
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- Reset states of the buttons for working using pending requests
   for(int i=0;i<14;i++)
     {
      ButtonState(butt_data[i].name+"_PRICE",false);
      ButtonState(butt_data[i].name+"_TIME",false);
     }

//--- Check playing a standard sound by macro substitution and a custom sound by description
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2"));

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Establecemos el precio de cálculo PRICE_OPEN para el segundo indicador personalizado MA. Si el precio de cálculo no se especifica explícitamente, por defecto, (en el primer indicador MA) se usa el precio de cálculo PRICE_CLOSE.

Cuando se crean los indicadores АМА, al primero se le establece el período de cálculo 9 (establecido por defecto), y para el segundo establecemos explícitamente el valor 14.
De esta manera, los cuatro indicadores creados tienen diferentes valores de entrada de sus parámetros.

En el manejador OnDeinit() del EA, llamamos al manejador OnDeinit() de la biblioteca:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Remove EA graphical objects by an object name prefix
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Deinitialize library
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+

Eso limpiará la lista de colección de indicadores durante la deinicialización del EA cuando cambiamos de marcos temporales, lo cual evitará la creación de los objetos de indicador adicionales innecesarios.

En el manejador OnTick() del EA, obtenemos el acceso a cada uno de los objetos de indicador creados y visualizamos los datos de la barra actual de cada indicador mostrándolos en los comentarios en el gráfico:

//+------------------------------------------------------------------+
//| 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 custom indicator objects
   CIndicatorDE *ma1=engine.GetIndicatorsCollection().GetIndByID(MA1);
   CIndicatorDE *ma2=engine.GetIndicatorsCollection().GetIndByID(MA2);
   CIndicatorDE *ama1=engine.GetIndicatorsCollection().GetIndByID(AMA1);
   CIndicatorDE *ama2=engine.GetIndicatorsCollection().GetIndByID(AMA2);
   Comment
     (
      "ma1=",DoubleToString(ma1.GetDataBuffer(0,0),6),
      ", ma2=",DoubleToString(ma2.GetDataBuffer(0,0),6),
      "\nama1=",DoubleToString(ama1.GetDataBuffer(0,0),6),
      ", ama2=",DoubleToString(ama2.GetDataBuffer(0,0),6)
     );
   
   
//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
  }
//+------------------------------------------------------------------+

En el artículo anterior, creábamos temporalmente los objetos de indicador en la función de inicialización de la biblioteca OnInitDoEasy(). Eliminamos estas cadenas de la función:

//--- Create timeseries of all used symbols
   engine.SeriesCreateAll(array_used_periods);
//--- Check created timeseries - display descriptions of all created timeseries in the journal
//--- (true - only created ones, false - created and declared ones)
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
   //engine.GetTimeSeriesCollection().Print(true);      // Full descriptions

//--- Create indicators
   engine.GetIndicatorsCollection().CreateAMA(Symbol(),Period(),9,2,30,0,PRICE_CLOSE);
   engine.GetIndicatorsCollection().CreateAMA(Symbol(),Period(),10,3,32,5,PRICE_CLOSE);
   engine.GetIndicatorsCollection().Print();
   engine.GetIndicatorsCollection().PrintShort();

//--- Create resource text files

Compilamos el EA y lo iniciamos en el gráfico, estableciendo previamente en los ajustes el uso del símbolo y marco temporal actuales.
En el registro, se mostrarán las descripciones de los parámetros de todos los indicadores creados:

--- Initializing "DoEasy" library ---
Working with the current symbol only: "EURUSD"
Working with the current timeframe only: H1
EURUSD symbol timeseries: 
- Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6284
Library initialization time: 00:00:00.141
============= Parameter list start: "Custom indicator" =============
Indicator status: Custom indicator
Type of indicator: CUSTOM
Indicator timeframe: H1
Indicator handle: 10
Indicator group: Trend indicator
Indicator ID: 1
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Examples\Custom Moving Average.ex5"
Indicator short name: "Examples\Custom Moving Average.ex5(EURUSD,H1)"
 --- Indicator parameters --- 
 - [1] int type: 13
 - [2] int type: 0
 - [3] int type: 0
================== Parameter list end: "Custom indicator" ==================
 
============= Parameter list start: "Custom indicator" =============
Indicator status: Custom indicator
Type of indicator: CUSTOM
Indicator timeframe: H1
Indicator handle: 11
Indicator group: Trend indicator
Indicator ID: 2
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Examples\Custom Moving Average.ex5"
Indicator short name: "Examples\Custom Moving Average.ex5(EURUSD,H1)"
 --- Indicator parameters --- 
 - [1] int type: 13
 - [2] int type: 0
 - [3] int type: 0
 - [4] int type: 2
================== Parameter list end: "Custom indicator" ==================
 
============= Parameter list start: "Standard indicator" =============
Indicator status: Standard indicator
Type of indicator: AMA
Indicator timeframe: H1
Indicator handle: 12
Indicator group: Trend indicator
Indicator ID: 3
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Adaptive Moving Average"
Indicator short name: "AMA(EURUSD,H1)"
 --- Indicator parameters --- 
 - Averaging period: 9
 - Fast MA period: 2
 - Slow MA period: 30
 - Horizontal shift of the indicator: 0
 - Price type or handle: CLOSE
================== Parameter list end: "Standard indicator" ==================
 
============= Parameter list start: "Standard indicator" =============
Indicator status: Standard indicator
Type of indicator: AMA
Indicator timeframe: H1
Indicator handle: 13
Indicator group: Trend indicator
Indicator ID: 4
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Adaptive Moving Average"
Indicator short name: "AMA(EURUSD,H1)"
 --- Indicator parameters --- 
 - Averaging period: 14
 - Fast MA period: 2
 - Slow MA period: 30
 - Horizontal shift of the indicator: 0
 - Price type or handle: CLOSE
================== Parameter list end: "Standard indicator" ==================

Custom indicator Examples\Custom Moving Average.ex5 EURUSD H1 [handle 10, id #1]
Custom indicator Examples\Custom Moving Average.ex5 EURUSD H1 [handle 11, id #2]
Standard indicator Adaptive Moving Average EURUSD H1 [handle 12, id #3]
Standard indicator Adaptive Moving Average EURUSD H1 [handle 13, id #4]

En el gráfico del símbolo, se mostrarán los datos de los búferes de todos los indicadores creados:

Se puede añadir al gráfico los indicadores necesarios que corresponden según sus parámetros a los creados en el EA. Así, se puede verificar la coincidencia de los datos de indicadores en el comentario en el gráfico y en la ventana de datos: van a coincidir

¿Qué es lo próximo?

En el siguiente artículo, seguiremos desarrollando la funcionalidad para trabajar con indicadores en los asesores expertos. En los próximos artículos, se planea crear una vinculación de los datos con las barras de las clases de la serie temporal, y obtener datos de parte de los indicadores para diferentes estudios estadísticos.

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.
Nótese que, por el momento, la clase de colección de los indicadores se encuentra en el proceso del desarrollo. Por tanto, no se recomienda estrictamente usarla en sus programas.
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


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

Archivos adjuntos |
MQL5.zip (3865.91 KB)
El comercio en fórex y sus matemáticas básicas El comercio en fórex y sus matemáticas básicas
El artículo pretende describir las principales características del comercio de divisas de la forma más rápida y simple posible, para compartir verdades sencillas con los lectores principiantes. También intentaremos responder a las preguntas más interesantes en el entorno comercial, así como escribir un indicador simple.
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 55): Clase de colección de indicadores
En este artículo, seguiremos desarrollando las clases de los objetos de indicador y sus colecciones. Crearemos la descripción para cada objeto de indicador y ajustaremos la clase de colección para un almacenamiento y obtención correctos de los objetos de indicador desde la lista de colección.
Trabajando con las series temporales en la biblioteca DoEasy (Parte 57): Objeto de datos del búfer de indicador Trabajando con las series temporales en la biblioteca DoEasy (Parte 57): Objeto de datos del búfer de indicador
En este artículo, vamos a desarrollar el objeto que incluirá todos los datos de un búfer de un indicador. Estos objetos serán necesarios para almacenar los datos de serie de los búferes de indicadores, a través de los cuales será posible ordenar y comparar los datos de los búferes de cualquier indicador, así como otros datos parecidos.
Redes neuronales: así de sencillo (Parte 5): Cálculos multihilo en OpenCL Redes neuronales: así de sencillo (Parte 5): Cálculos multihilo en OpenCL
Ya hemos analizado algunos tipos de implementación de redes neuronales. Podemos ver con facilidad que se repiten las mismas operaciones para cada neurona de la red. Y aquí sentimos el legítimo deseo de aprovechar las posibilidades que ofrece la computación multihilo de la tecnología moderna para acelerar el proceso de aprendizaje de una red neuronal. En el presente artículo, analizaremos una de las opciones para tal implementación.