English Русский 中文 Español Deutsch 日本語
preview
Trabalhando com séries temporais na biblioteca DoEasy (Parte 56): objeto de indicador personalizado, obtenção de dados a partir de objetos-indicadores numa coleção

Trabalhando com séries temporais na biblioteca DoEasy (Parte 56): objeto de indicador personalizado, obtenção de dados a partir de objetos-indicadores numa coleção

MetaTrader 5Exemplos | 19 fevereiro 2021, 12:13
539 0
Artyom Trishkin
Artyom Trishkin

Sumário


Ideia

Continuamos a criar funcionalidades para trabalhar com indicadores em Expert Advisors com base nesta biblioteca.
Nós já criamos classes de objetos-indicadores padrão e começamos a colocá-los numa lista-coleção. Para um "sortimento" completo, precisamos apenas de um objeto-indicador personalizado. Hoje vamos criar um objeto desse tipo. O conceito por trás da sua construção será diferente do de criação de objetos de indicadores padrão.
Visto que temos um conjunto finito de indicadores padrão no terminal, podemos criar objetos de indicadores padrão com base em dados conhecidos com antecedência para cada um dos indicadores, e para fazer isso, indicamos um conjunto estritamente especificado de parâmetros que correspondem ao indicador criado. Embora um indicador personalizado possa ter qualquer conjunto de parâmetros, não há como saber isso com antecedência.
Por esse motivo, o conceito de criação de objeto-indicador personalizado será diferente da criação de um objeto-indicador padrão.

Se, para criar um objeto-indicador padrão, basta gerar para cada um deles um método de criação próprio cujos parâmetros de entrada conterão todas as propriedades necessárias, então para criar um indicador personalizado com um número e tipo de parâmetros previamente desconhecidos, teremos que passar para o método de criação deste a matriz de estruturas de parâmetros de entrada do indicador pré-preenchida, matriz essa que permitirá retirar todos os parâmetros e propriedades necessários para criar o indicador. Assim, infelizmente, o usuário da biblioteca terá que preencher por contra própria essa matriz de estruturas de parâmetros de entrada para criar um indicador personalizado em seu programa.

Como estamos tentando criar uma biblioteca para simplificar a criação de nossos programas, dentre várias opções de acesso aos indicadores criados em meu programa, escolhi a seguinte: cada indicador criado será marcado com seu próprio identificador. Por este identificador podemos nos acessar cada um dos indicadores criados. Felizmente, nós mesmos os criamos, atribuímos identificadores e sabemos perfeitamente bem qual dos identificadores atribuídos corresponde a um ou outro indicador criado no programa.

Além do identificador, também haverá métodos para acessar o indicador com base em todos os seus parâmetros. Quer dizer, será possível obter o objeto do indicador, especificando os mesmos parâmetros com os quais este objeto do indicador foi criado, para logo trabalhar com o objeto resultante - receber dados do indicador (iremos criar métodos para receber dados hoje) e copiar em matrizes para pesquisa estatística (isso já estará em artigos posteriores).


Aprimorando as classes da biblioteca

Como de costume, em primeiro lugar, vamos adicionar novas mensagens de biblioteca ao arquivo \MQL5\Include\DoEasy\Data.mqh.
Adicionamos os índices das novas mensagens:

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

e as mensagens de texto correspondentes aos índices adicionados recentemente:

   {"Стрелочный индикатор","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"},
   
  };
//+---------------------------------------------------------------------+


Quando criamos um objeto-indicador padrão, logo em seu construtor definimos para ele o grupo que corresponde ao tipo de indicador: tendência, seta, oscilador ou indicador de volume. Para indicadores padrão, podemos fazer isso, pois sabemos com antecedência o tipo de indicador criado. Para um indicador personalizado, não podemos saber de que tipo é com antecedência (o grupo ao qual o indicador pertence) e defini-lo no construtor da classe do objeto. Por isso, precisamos criar mais um grupo de indicadores "qualquer", que será automaticamente atribuído ao objeto recém-criado, e permitir que o usuário indique independentemente o grupo ao qual o indicador personalizado pertence após criar seu objeto.

No arquivo \MQL5\Include\DoEasy\Defines.mqh na enumeração do grupo do indicador adicionamos uma nova constante:

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

Visto que concordamos em acessar aos indicadores criados por identificadores únicos, precisamos adicionar uma nova propriedade à enumeração de propriedades inteiras do 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
//+------------------------------------------------------------------+

e, consequentemente, aumentamos o número de propriedades inteiras do indicador de 5 para 6.

Para poder pesquisar um indicador na lista por identificador, vamos introduzir um novo critério para classificar indicadores por 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
  };
//+------------------------------------------------------------------+


Como novas propriedades foram adicionadas ao objeto indicador, agora é necessário modificar ligeiramente a classe do objeto indicador abstrato no arquivo \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh.

Na seção pública da classe, vamos escrever dois métodos para definire obter propriedades "identificador do 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);                       }

Nos objetos de indicadores padrão, poderíamos escrever métodos para exibir a descrição de cada parâmetro do indicador, uma vez que sabemos exatamente qual parâmetro de que indicador específico estamos descrevendo. Para exibir uma descrição dos parâmetros de um indicador personalizado, não podemos saber exatamente a finalidade de cada parâmetro de um indicador que não conhecemos de antemão. Por isso, aqui só temos que exibir as descrições de cada parâmetro subsequente da matriz de parâmetros do indicador MqlParam.
Na seção pública da classe do objeto do indicador abstrato declaramos um método para exibir a descrição dos parâmetros da estrutura MqlParam, bem como mais dois métodos para receber dados do objeto-indicador por índice e hora da 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);
  };
//+------------------------------------------------------------------+

Fora do corpo da classe, vamos escrever a implementação desses métodos:

Método que retorna uma descrição de um parâmetro da matriz de estruturas 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]);
  }
//+------------------------------------------------------------------+

Ao método é transferido o índice do parâmetro na matriz e, logo, é criada um string a partir do índice da matriz e da descrição do parâmetro em conformidade com os dados armazenados na estrutura de acordo com o índice da matriz. Vamos escrever a função MqlParameterDescription() para retornar a descrição dos dados da estrutura MqlParam logo abaixo.

Métodos que retornam dados do indicador por índice e tempo de 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());
  }
//+------------------------------------------------------------------+

Aos métodos são passados o índice ou o tempo da barra cujos dados devem ser obtidos a partir do indicador. Usamos a função CopyBuffer() para solicitar uma barra de acordo com o índice ou tempo, e retornamos o resultado registrado na matriz. Se por algum motivo os dados não podem ser recebidos, os métodos retornam um valor vazio definido para o objeto-indicador.

No construtor da classe definimos o identificador do indicador por padrão (-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 indicador tem um identificador único (que nós mesmos iremos atribuir), ao comparar a identidade de dois indicadores por parâmetros, precisamos pular os dois parâmetros mencionados acima, caso contrário, cada indicador será definido como único, e não poderemos compará-los corretamente.
Para evitar isso, ignoramos as propriedades "handle" e "identificador" no método para comparar dois objetos-indicadores:

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

No método que retorna a descrição da propriedade inteira do indicador, incorporamos a saída da descrição da propriedade do indicador "identificador":

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

Agora ao arquivo de funções de serviço \MQL5\Include\DoEasy\Services\DELib.mqh adicionamos a função para exibir a descrição da estrutura MqlParam:

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

Na estrutura MqlParam existem alguns campos. Um deles contém o tipo de dados armazenados na estrutura. Por este tipo de dado, podemos entender de qual campo da estrutura receber dados e qual função representando dados como string usar para enviar dados para o log.

Obtemos o tipo de dados e começamos a gerar uma string consistindo na palavra "Tipo " +" "+ descrição de string do tipo de dados + ": ".
Em seguida, dependendo do tipo de dados, adicionamos à string já criada a descrição do valor que armazena no campo as estruturas, que correspondem ao tipo, com ajuda de funções padrão para exibir como uma string o tipo necessário de dados.

Como resultado, ao usar o método de saída de parâmetros da estrutura MqlParam da classe do objeto indicador abstrato e a função de serviço acima no log do terminal, teremos uma descrição dos parâmetros de um indicador personalizado com quatro parâmetros assim:

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

Como adicionamos a nova propriedade para o objeto-indicador (isto é, seu identificador), em todas as classes de todos os objetos dos indicadores, alteramos ligeiramente os arquivos que estão localizados na pasta \MQL5\Include\DoEasy\Objects\Indicators\Standart\, isto é, simplesmente alteramos o nome curto do valor do identificador:

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

Aqui, criamos a descrição do identificador. Além disso, se o valor do identificador for maior que -1, o identificador será exibido, caso contrário, se não houver identificador (se seu valor for -1), ele não será exibido na descrição (simplesmente aparecerá um colchete de fechamento). Em seguida, adicionamos a linha resultante à descrição breve do indicador.

Essas modificações foram feitas em todas as classes de objetos-indicadores.


Objeto do indicador personalizado

Agora vamos escrever a classe do objeto-indicador personalizado. Vamos colocá-lo na pasta de indicadores padrão da biblioteca \MQL5\Include\DoEasy\Objects\Indicators\Standart\. Simplesmente visto que existe um indicador personalizado na lista de indicadores do terminal, seu local também está nessa lista.

Vamos dar uma olhada em toda a classe:

//+------------------------------------------------------------------+
//|                                                    IndCustom.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| 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));
     }
  }
//+------------------------------------------------------------------+

Todos os métodos já nos são familiares graças às classes de indicadores padrão. Mas ao contrário deles, obtemos no construtor da classe os parâmetros do indicador criado, não nas variáveis de entrada, mas, sim, na matriz de estruturas de parâmetros de entrada MqlParam criada anteriormente. E ao construtor privado da classe do objeto indicador abstrato passamos o status "indicador personalizado", grupo "qualquer indicador", e como nome, passamos o primeiro elemento da matriz de parâmetros que, ao criar um indicador personalizado, deve ter o tipo TYPE_STRING e o valor do campostring_value contém o nome do indicador personalizado. Da mesma forma, nós criamos o nome curto do indicador, mas com a adição de uma descrição do símbolo e de período gráfico. No futuro, o nome e o nome abreviado do indicador podem ser alterados usando os métodos da classe pai SetName() e SetShortName(). Na verdade, o nome do indicador também contém o caminho para ele, portanto (pelo menos neste estágio do desenvolvimento da biblioteca) o nome de um indicador personalizado já criado não pode ser alterado.

No método que registra a descrição dos parâmetros do objeto indicador fazemos literalmente o seguinte: primeiro mostramos o título e, em seguida, num loop percorrendo a matriz de parâmetros do indicador, imprimimos cada parâmetro subsequente usando os métodos considerados anteriormente (em particular, o método da classe pai GetMqlParamDescription()).

Armazenamos todos os objetos-indicadores numa lista-coleção na classe CIndicatorsCollection no arquivo \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh.

Ao adicionar um indicador à lista-coleção, precisamos verificar adicionalmente se seu identificador é único. Além disso, devemos tornar visível a classe do indicador personalizado para que a classe-coleção possa trabalhar com ela.

Anexamos o arquivo da classe do indicador personalizado à classe da coleção de objetos-indicadores:

//+------------------------------------------------------------------+
//|                                         IndicatorsCollection.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 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"
//+------------------------------------------------------------------+

Na seção privada da classe declaramos o método para adicionar o objeto-indicador na coleção e o método que verifica a presença na lista do objeto-indicador com o identificador em questão:

//+------------------------------------------------------------------+
//| 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:

Na seção pública da classe declaramos o método que retorna o ponteiro para o objeto do indicador personalizado de acordo com seu grupo e parâmetro definido na matriz MqlParam (ao contrário dos indicadores padrão, podemos especificar parâmetros apenas passando-os nessa 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);

No bloco de declaração de métodos para a criação de indicadores, primeiro adicionamos aos parâmetros de cada método uma indicação do identificador do indicador e, em segundo lugar, adicionamos uma declaração do método para criar um indicador personalizado. Não forneceremos uma lista completa de declarações desses métodos, em vez dissos, nos limitaremos a três métodos (todos eles já foram finalizados e podem ser visualizados nos arquivos anexados ao artigo):

   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);

Em todos os métodos, após os parâmetros de entrada do símbolo e período gráfico, foi adicionada uma indicação do identificador do indicador ao criá-lo.
No método de criação do indicador personalizado é adicionalmente especificado o grupo do indicador e é transferida a matriz de parâmetros de indicador previamente criada e preenchida, matriz essa pela qual será criado o indicador personalizado.

No final da listagem do corpo da classe, declaramos o método para definir o identificador para o indicador especificado e o método que retorna um objeto indicador segundo o 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();

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

Agora, vamos examinar todos os métodos declarados.

No método privado para criar um novo objeto-indicador adicionamos a criação de um novo objeto-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;
  }
//+------------------------------------------------------------------+

Vamos mudar o método que adiciona um novo objeto-indicador à lista-coleção:

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

Agora o identificador do indicador é passado para o método, e se o indicador com este identificador ainda não estiver na lista-coleção, então para o objeto-indicador é definido o identificador especificado. Caso contrário, o identificador do indicador será definido por padrão como -1.

Agora, todos os métodos de criação de objetos-indicadores são mais curtos.
Vejamos o exemplo de criação de objetos dos indicadores AC e 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);
  }
//+------------------------------------------------------------------+

Agora só precisamos preencher a estrutura dos parâmetros de entrada, criar o indicador e chamar o método para adicioná-lo à lista-coleção. Essas mudanças foram feitas em todos os métodos de criação de objetos-indicadores, e não as analisaremos aqui, com exceção do método para criar um 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);
  }
//+------------------------------------------------------------------+

Aqui tudo muda um pouco. Para o método de criação é passado, além do identificador, o grupo de indicadores. Todos os parâmetros do indicador criado são imediatamente passados na matriz de parâmetros MqlParam, porque não podemos saber com antecedência sobre os parâmetros do indicador personalizado que está sendo criado.

Absolutamente a todos os métodos de criação de indicadores padrão foram adicionados a valores padrão para cada parâmetro de entrada. Assim, para criar um indicador padrão com parâmetros padrão, bastará especificar o símbolo, período e identificador.

Implementação de um método que retorna um ponteiro para um objeto-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);
  }
//+------------------------------------------------------------------+

Aqui: criamos um objeto temporário do indicador para procurar exatamente o mesmo objeto na lista-coleção, definimos um grupo para ele e obter o índice do objeto encontrado na lista-coleção. Em seguida, excluímos o objeto temporário e retornamos um ponteiro para um objeto com base no índice encontrado da lista, ouNULL se nenhum objeto foi encontrado na lista.

Método que verifica se um objeto-indicador existe com o id especificado na 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);
  }
//+------------------------------------------------------------------+

Obtemos uma lista de objetos-indicadores com o identificador especificado e retornamos o sinalizador que verifica se a lista é válida e não está vazia (a lista deve conter um objeto).

Método que define o identificador para o 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);
  }
//+------------------------------------------------------------------+

Ao método é transferido o ponteiro para o objeto-indicador, que precisa definir o identificador passado pelo parâmetro para o método.

Se for passado um ponteiro inválido, nós relatamos isso e saímos do método.
Se o valor do identificador for maior que -1, verificamos se existe um objeto-indicador com este identificador e, se já existir, relatamos isso e saímos.
Se todas as verificações forem passadas, definimos o identificador do objeto. Se o valor do identificador passado para o método for menor que zero, esse identificador será definido para o objeto sem verificação, e tal identificador do objeto-indicador indica sua ausência.

Método que retorna um objeto-indicador de acordo com o 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));
  }
//+------------------------------------------------------------------+

Aqui: obtemos uma lista de objetos-indicadores com o identificador especificado e retornamos NULL (se a lista não puder ser recuperada ou se a lista estiver vazia), ou um ponteiro para um objeto com o identificador desejado. Como pode haver apenas um objeto com o identificador especificado, não importa qual índice na lista resultante deve ser indicado - o primeiro ou o último. Aqui indicamos o último.

Ao testar a criação de indicadores no EA houve um problema, quando o intervalo de tempo é alterado, são criados exatamente os mesmos indicadores adicionais, mas com um intervalo de tempo diferente. E isso é certo, afinal, indicadores com os mesmos parâmetros de entrada, mas calculados com base em timeframes diferentes, já são dois indicadores diferentes. Para simplesmente evitar esse problema, basta limpar a lista de indicadores criados ao desinicializar o programa. Para tanto, no objeto principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh declaramos um novo manipulador 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);

E fora do corpo da classe, escrevemos sua implementação:

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

Este método de classe será chamado a partir do manipulador OnDeinit() do programa. Tudo o que é feito aqui é chamar o método para limpar a lista-coleção do objeto-coleção dos indicadores.

Teste

Para testar a criação de diferentes indicadores e obter dados dos objetos-indicadores criados, tomamos este Expert Advisor do artigo anterior.

Vamos salvá-lo na pasta nova \MQL5\Experts\TestDoEasy\Part56\ usando o novo nome TestDoEasyPart56.mq5.

No EA, criaremos dois indicadores de média móvel personalizados, mas com parâmetros diferentes (pegaremos os indicadores na pasta de exemplos de indicadores que vem com o terminal padrão \MQL5\Indicators\Examples\). E criaremos dois indicadores padrão de Adaptive Moving Average com diferentes parâmetros de entrada.

No escopo global definimos substituições de macro para simplificar o acesso aos indicadores por identificador e declaramos duas matrizes de parâmetros para criar dois indicadores personalizados:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart56.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- 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[];
//+------------------------------------------------------------------+

Em essência, as substituições de macro declaradas são descrições dos valores numéricos dos identificadores. Será mais fácil acessar o identificador do indicador pelo nome do que por seu valor.

No manipulador OnInit() do Expert Advisor criamos todos os quatro indicadores e imediatamente exibimos os dados de todos os indicadores criados no log:

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

Para o segundo indicador MA personalizado, definiremos o preço de cálculo PRICE_OPEN. Se o preço de cálculo não for indicado explicitamente, então por padrão (no primeiro indicador MA), o preço PRICE_CLOSE é usado para calcular o indicador.

Ao criar indicadores AMA, o primeiro é definido para um período de cálculo de 9 (definido por padrão), já o segundo é definido explicitamente para 14.
Assim, todos os quatro indicadores criados têm diferentes valores de entrada a nível de parâmetros.

No manipulador OnDeinit() do Expert Advisor chamamos o manipulador OnDeinit() da 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();
  }
//+------------------------------------------------------------------+

Isso limpará a lista-coleção dos indicadores durante a desinicialização do EA ao alternar os períodos gráficos, o que nos evitará criar objetos-indicadores desnecessários.

No manipulador OnTick() do Expert Advisor obtemos acesso a cada um dos objetos-indicadores criados e imprimimos os dados da barra atual de cada indicador, exibindo-os nos comentários do 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
     }
  }
//+------------------------------------------------------------------+

No último artigo, criamos temporariamente objetos-indicadores na função de inicialização da biblioteca OnInitDoEasy(). Vamos remover essas linhas da função:

//--- 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

Vamos compilar o Expert Advisor e iniciá-lo no gráfico, tendo previamente definido nas configurações para usar apenas o símbolo e período gráfico atuais.
O log conterá descrições dos parâmetros de todos os indicadores criados:

--- 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]

No gráfico do símbolo, serão exibidos os dados dos buffers de todos os indicadores criados:

No gráfico podemos colocar os indicadores necessários que correspondem aos parâmetros criados no EA, e verificar se esses indicadores coincidem nos comentários no gráfico e na janela de dados.

O que vem agora?

No próximo artigo, continuaremos a criar funcionalidade para trabalhar com indicadores em Expert Advisors. Nos próximos artigos, pretende-se criar uma vinculação desses indicadores a barras das classes da série temporal e obter dados de indicadores para vários estudos estatísticos.

Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho.
Quero ressaltar que a classe-coleção de indicadores está atualmente em desenvolvimento, portanto, não é recomendável usá-la em nossos programas.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

Artigos desta série:

Trabalhando com séries temporais na biblioteca DoEasy (Parte 35): Objeto "Barra" e lista-série temporal do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 36): objeto das séries temporais de todos os períodos usados do símbolo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 37): coleção de séries temporais - banco de dados de séries temporais para símbolos e períodos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 38): coleção de séries temporais - atualização em tempo real e acesso aos dados do programa
Trabalhando com séries temporais na biblioteca DoEasy (Parte 39): indicadores com base na biblioteca - preparação de dados e eventos das séries temporais
Trabalhando com séries temporais na biblioteca DoEasy (Parte 40): indicadores com base na biblioteca - atualização de dados em tempo real
Trabalhando com séries temporais na biblioteca DoEasy (Parte 41): exemplo de indicador multissímbolo multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 42): classe de um objeto de buffer abstrato de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 43): classes de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 44): classe-coleção de objetos de buffers de indicador
Trabalhando com séries temporais na biblioteca DoEasy (Parte 45): buffers de indicador multiperíodo
Trabalhando com séries temporais na biblioteca DoEasy (Parte 46): buffers de indicador multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 47): indicadores padrão multiperíodos multissímbolos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 48): indicadores multissímbolos multiperíodos num buffer de uma subjanela
Trabalhando com séries temporais na biblioteca DoEasy (Parte 49): indicadores padrão multiperíodos multissímbolos multibuffer
Trabalhando com séries temporais na biblioteca DoEasy (Parte 50): indicadores padrão multiperíodos multissímbolos com deslocamento
Trabalhando com séries temporais na biblioteca DoEasy (Parte 51): indicadores padrão multiperíodos multissímbolos compostos
Trabalhando com séries temporais na biblioteca DoEasy (Parte 52): natureza multiplataforma de indicadores padrão multiperíodos multissímbolos de buffer único
Trabalhando com séries temporais na biblioteca DoEasy (Parte 53): classe do indicador base abstrato
Trabalhando com séries temporais na biblioteca DoEasy (Parte 54): classes herdeiras do indicador base abstrato
Trabalhando com séries temporais na biblioteca DoEasy (Parte 55): classe-coleção de indicadores


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/8646

Arquivos anexados |
MQL5.zip (3865.91 KB)
Trabalhando com séries temporais na biblioteca DoEasy (Parte 57): objeto de dados do buffer do indicador Trabalhando com séries temporais na biblioteca DoEasy (Parte 57): objeto de dados do buffer do indicador
Neste artigo, veremos um objeto que conterá todos os dados de um buffer de um indicador. Tais objetos serão necessários para armazenar dados seriais de buffers de indicadores, e com a ajuda dos quais será possível classificar e comparar dados de buffers de quaisquer indicadores e outros dados semelhantes entre si.
Exemplos de análise de gráficos usando o TD Sequential e os níveis de Murray-Gann Exemplos de análise de gráficos usando o TD Sequential e os níveis de Murray-Gann
O TD Sequential mostra perfeitamente as mudanças no equilíbrio durante o movimento do preço. Isso é especialmente evidente se usarmos seus sinais juntamente com um indicador de nível, como com os níveis de Murray. Este artigo falará sobre essa combinação. O texto é destinado principalmente a iniciantes e àqueles que ainda não conseguiram encontrar seu "Graal", embora eu mostre alguns recursos de construção de níveis que não vi em outros fóruns. Sendo assim, algumas partes podem ser úteis também para usuários avançados. Por outra parte, quanto aos gurus, eu os convido ao diálogo e à crítica...
Força bruta para encontrar padrões (Parte II): Imersão Força bruta para encontrar padrões (Parte II): Imersão
Neste artigo, continuarei o tópico sobre força bruta. Tentarei apresentar melhor os padrões com ajuda de uma nova versão melhorada do meu programa e me esforçarei para encontrar a diferença a nível de estabilidade usando diferentes períodos gráficos.
Aplicação prática de redes neurais no trading. Python (Parte I) Aplicação prática de redes neurais no trading. Python (Parte I)
Neste artigo, analisaremos passo a passo a implementação de um sistema de negociação baseado na programação de redes neurais profundas em Python. Para isso, usaremos a biblioteca de aprendizado de máquina TensorFlow desenvolvida pelo Google. Para descrever as redes neurais, iremos por em uso a biblioteca Keras.