Trabalhando com séries temporais na biblioteca DoEasy (Parte 55): classe-coleção de indicadores

14 janeiro 2021, 09:20
Artyom Trishkin
0
561

Sumário


Ideia

Hoje vamos acabar de refinar as classes herdeiras do objeto base do indicador abstrato, que começamos a fazer no último artigo.
Seguindo o conceito geral de construção de objetos de biblioteca, e para que a geração dos objetos dos indicadores não difira da dos outros da biblioteca, precisamos adicionar uma descrição aos objetos-indicadores. Ao mesmo tempo, vamos melhorar o armazenamento desses objetos numa coleção própria, e para isso, vamos corrigir as deficiências ao criar uma coleção de indicadores e adicionar os objetos à coleção, objetos esses que fizemos no último artigo.

Quero ressaltar que o objeto base do indicador abstrato e os objetos-indicadores que são herdeiros desse objeto são entidades separadas que não se cruzam com os indicadores multissímbolos e multiperíodos que criamos anteriormente para usar ao criar nossos indicadores personalizados com ajuda da biblioteca.

O objeto do indicador abstrato e seus herdeiros são objetos-indicadores que precisarão ser usados para EAs de indicador e para pesquisar combinações de dados e estados de valores de diferentes indicadores.


Aprimorando as classes da biblioteca

Por tradição, ao desenvolver classes de objetos de biblioteca, primeiro adicionamos as mensagens de texto necessárias que são usadas pelos objetos ao exibir suas descrições. Para objetos-indicadores, precisamos de mensagens para exibir todos os parâmetros possíveis de todos os indicadores padrão.
No arquivo \MQL5\Include\DoEasy\Data.mqh adicionamos os índices das novas mensagens:

   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
   
//--- CIndicatorsCollection
   MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,                // Error. Failed to add indicator object to list
   
  };
//+------------------------------------------------------------------+

e mensagens de texto correspondentes aos índices recém-adicionados:

   {"Пустое значение для построения, для которого нет отрисовки","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"},
   
   {"Ошибка. Не удалось добавить объект-индикатор в список","Error. Failed to add indicator object to list"},
   
  };
//+---------------------------------------------------------------------+

Para exibir alguns parâmetros do indicador, como o método de média, o tipo de preço e volume para cálculo, etc., escrevemos funções separadas no arquivo de funções do serviço de biblioteca em \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return StringSubstr(EnumToString((timeframe>PERIOD_CURRENT ? timeframe : (ENUM_TIMEFRAMES)Period())),7);
  }
//+------------------------------------------------------------------+
//| Return volume description for calculation                        |
//+------------------------------------------------------------------+
string AppliedVolumeDescription(const ENUM_APPLIED_VOLUME volume)
  {
   return StringSubstr(EnumToString(volume),7);
  }
//+------------------------------------------------------------------+
//| Return indicator type description                                |
//+------------------------------------------------------------------+
string IndicatorTypeDescription(const ENUM_INDICATOR indicator)
  {
   return StringSubstr(EnumToString(indicator),4);
  }
//+------------------------------------------------------------------+
//| Return averaging method description                              |
//+------------------------------------------------------------------+
string AveragingMethodDescription(const ENUM_MA_METHOD method)
  {
   return StringSubstr(EnumToString(method),5);
  }
//+------------------------------------------------------------------+
//| Return applied price description                                 |
//+------------------------------------------------------------------+
string AppliedPriceDescription(const ENUM_APPLIED_PRICE price)
  {
   return StringSubstr(EnumToString(price),6);
  }
//+------------------------------------------------------------------+
//| Return stochastic price calculation description                  |
//+------------------------------------------------------------------+
string StochPriceDescription(const ENUM_STO_PRICE price)
  {
   return StringSubstr(EnumToString(price),4);
  }
//+------------------------------------------------------------------+

Como você pode ver, por aqui tudo é simples: extraímos a substring da posição desejada desde a representação textual do valor de enumeração e, como resultado, obtemos o nome do indicador, o método de cálculo ou o tipo de volume e preço.

Cada indicador tem seu próprio conjunto específico de parâmetros. Estes parâmetros podem ser definidos para o indicador usando a matriz de estruturas de parâmetros do indicador MqlParam, e fazemos isso ao criar cada objeto-indicador. Assim, para cada indicador, podemos imprimir no log todos os valores da matriz dessas estruturas. Para diferentes indicadores, cada célula da matriz conterá dados sobre os valores dos parâmetros inerentes apenas a este tipo de indicador. Mas, para alguns indicadores do mesmo tipo, cada célula da matriz conterá propriedades com o mesmo propósito que diferiram apenas em seu valor.
Assim, para cada indicador, podemos escrever um método que imprimirá no log um conjunto de parâmetros do indicador com os valores definidos para eles. Isso é verdade apenas para indicadores padrão, uma vez que para eles sabemos exatamente o conjunto de parâmetros de cada indicador.

Este será um método virtual. Vamos escrever isso na classe do objeto de indicador abstrato no arquivo \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh:

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

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

Este método não faz nada nesta classe. A implementação do método para enviar dados do indicador para o log será realizada nos objetos herdeiros.
Cada classe herdada terá seu próprio método, pois cada indicador possui seu próprio conjunto de parâmetros.

Num construtor paramétrico privado substituímos a string para obter a descrição do tipo de indicador

   this.m_ind_type=::StringSubstr(::EnumToString(ind_type),4);

para obter uma descrição por meio de uma nova função de serviço que escrevemos acima:

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

Já no método que imprime as propriedades do indicador no log, no final da listagem - depois que todas as propriedades do objeto indicador são exibidas, adicionamos uma chamada ao método que imprime um conjunto de parâmetros do indicador no log com os valores definidos para eles:

//+------------------------------------------------------------------+
//| Display indicator properties in the journal                      |
//+------------------------------------------------------------------+
void CIndicatorDE::Print(const bool full_prop=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG),": \"",this.GetStatusDescription(),"\" =============");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   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(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   this.PrintParameters();
   ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",this.GetStatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

Agora, se o método PrintPatameters() existir na classe herdada do objeto base do indicador abstrato, quando o método Print() for chamado, também será chamado o método virtual PrintPatameters() da classe herdada, em que os parâmetros do indicador serão enviados para o log.

Em cada classe herdada, precisamos implementar nosso próprio método PrintPatameters(), uma vez que cada tipo de indicador tem seu próprio conjunto de parâmetros.
Esses métodos já foram escritos para cada objeto-indicador; eles são todos do mesmo tipo em lógica, mas diferentes em conteúdo. Os métodos são escritos para todos os indicadores, exceto um, o personalizado, uma vez que a implementação do método para ele será diferente porque não podemos saber com antecedência o conjunto de parâmetros do indicador, como podemos saber para todos os indicadores padrão.
Vamos considerar esses métodos para cada um dos objetos descendentes.

Classe do objeto-indicador Accelerator Oscillator:

//+------------------------------------------------------------------+
//|                                                        IndAC.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"
//+------------------------------------------------------------------+
//| Accelerator Oscillator standard indicator                        |
//+------------------------------------------------------------------+
class CIndAC : public CIndicatorDE
  {
private:

public:
   //--- Constructor
                     CIndAC(const string symbol,const ENUM_TIMEFRAMES timeframe,MqlParam &mql_param[]) : 
                        CIndicatorDE(IND_AC,symbol,timeframe,
                                     INDICATOR_STATUS_STANDART,
                                     INDICATOR_GROUP_OSCILLATOR,
                                     "Accelerator Oscillator",
                                     "AC("+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 CIndAC::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the indicator supports the passed               |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CIndAC::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Display a short description of indicator object in the journal   |
//+------------------------------------------------------------------+
void CIndAC::PrintShort(void)
  {
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [",this.Handle(),"]");
  }
//+------------------------------------------------------------------+

Aqui temos apenas declarado um método virtual para exibir parâmetros, e este está vazio, uma vez que o indicador AC não tem parâmetros de entrada. Era possível não escrevê-lo, pois se não houver um método virtual na classe herdada, será chamado o método virtual da classe pai. Mas vamos escrevê-lo aqui apenas para que os métodos de todas as classes herdadas tenham a mesma estrutura.
No método de exibição de uma breve descrição do indicador adicionamos a exibição do identificador do indicador criado para este objeto. Essas mudanças neste método são feitas para todos os objetos indicadores e não as consideraremos mais adiante, em vez disso, nós nos restringiremos apenas aos métodos de exibição da descrição dos parâmetros do indicador.

Classe de objeto-indicador de Accumulation/Distribution e seus método para exibir a descrição dos parâmetros do indicador:

//+------------------------------------------------------------------+
//|                                                        IndAD.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"
//+------------------------------------------------------------------+
//| Accumulation/Distribution standard indicator                     |
//+------------------------------------------------------------------+
class CIndAD : public CIndicatorDE
  {
private:

public:
   //--- Constructor
                     CIndAD(const string symbol,const ENUM_TIMEFRAMES timeframe,MqlParam &mql_param[]) :
                        CIndicatorDE(IND_AD,symbol,timeframe,
                                     INDICATOR_STATUS_STANDART,
                                     INDICATOR_GROUP_VOLUMES,
                                     "Accumulation/Distribution",
                                     "AD("+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 CIndAD::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if the indicator supports the passed               |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CIndAD::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Display a short description of indicator object in the journal   |
//+------------------------------------------------------------------+
void CIndAD::PrintShort(void)
  {
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [",this.Handle(),"]");
  }
//+------------------------------------------------------------------+
//| Display parameter description of indicator object in the journal |
//+------------------------------------------------------------------+
void CIndAD::PrintParameters(void)
  {
   ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- ");
   //--- applied_volume
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_APPLIED_VOLUME),": ",AppliedVolumeDescription((ENUM_APPLIED_VOLUME)m_mql_param[0].integer_value));
  }
//+------------------------------------------------------------------+

O indicador Accumulation/Distribution é apenas um parâmetro de entrada — tipo de volume para cálculo. Por isso, a matriz de estruturas de parâmetros de entrada possui apenas uma célula na qual esse parâmetro é armazenado. Assim, para exibi-lo no diário, nós o obtemos desde a matriz no índice 0 a partir dos dados inteiros da estrutura e imprimimos no log usando a função de serviço escrita por nós acima. A exibição do valor do parâmetro é precedida por sua descrição a partir das mensagens de texto previamente escritas da biblioteca.

Além disso, consideraremos apenas os métodos de exibição da descrição dos parâmetros de entrada do indicador.

Método para exibir da descrição de parâmetros do indicador da classe do objeto-indicador Average Directional Index:

//+------------------------------------------------------------------+
//| Display parameter description of indicator object in the journal |
//+------------------------------------------------------------------+
void CIndADX::PrintParameters(void)
  {
   ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- ");
   //--- adx_period
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_PERIOD),": ",(string)m_mql_param[0].integer_value);
  }
//+------------------------------------------------------------------+

O indicador ADX possui um parâmetro de entrada, o período de cálculo, por isso, da mesma forma, exibimos a descrição de apenas uma célula da matriz pelo índice 0 a partir da estrutura de dados inteira.

Método para exibir a descrição dos parâmetros do indicador da classe do objeto-indicador Alligator:

//+------------------------------------------------------------------+
//| Display parameter description of indicator object in the journal |
//+------------------------------------------------------------------+
void CIndAlligator::PrintParameters(void)
  {
   ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- ");
   //--- jaw_period
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_JAW_PERIOD),": ",(string)m_mql_param[0].integer_value);
   //--- jaw_shift
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_JAW_SHIFT),": ",(string)m_mql_param[1].integer_value);
   //--- teeth_period
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TEETH_PERIOD),": ",(string)m_mql_param[2].integer_value);
   //--- teeth_shift
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TEETH_SHIFT),": ",(string)m_mql_param[3].integer_value);
   //--- lips_period
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_LIPS_PERIOD),": ",(string)m_mql_param[4].integer_value);
   //--- lips_shift
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_LIPS_SHIFT),": ",(string)m_mql_param[5].integer_value);
   //--- ma_method
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_MA_METHOD),": ",AveragingMethodDescription((ENUM_MA_METHOD)m_mql_param[6].integer_value));
   //--- applied_price
   ::Print(
           " - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_APPLIED_PRICE),": ",
           (m_mql_param[7].integer_value<10 ? AppliedPriceDescription((ENUM_APPLIED_PRICE)m_mql_param[7].integer_value) : (string)m_mql_param[7].integer_value)
          );
  }
//+------------------------------------------------------------------+

O indicador Alligator tem oito parâmetros de entrada, por isso, nós os exibimos um por um de acordo com sua ordem durante a criação do indicador:

  • Período de cálculo da linha "Mandíbulas" — índice na matriz 0
  • Deslocamento horizontal da linha "Mandíbulas" — índice na matriz 1
  • Período de cálculo da linha "Dentes" — índice na matriz 2
  • Deslocamento horizontal da linha "Dentes" — índice na matriz 3
  • Período de cálculo para a linha "Lábios" — índice na matriz 4
  • Deslocamento horizontal da linha "Lips" — índice de matriz 5
  • Tipo de suavização — índice na matriz 6
  • Tipo de preços ou identificador do indicador — índice na matriz 7

Todos os dados têm um tipo inteiro, portanto, exibimos a descrição dos dados inteiros da estrutura a partir das células correspondentes da matriz.
O último parâmetro pode armazenar quer o tipo de preço, com base no qual o indicador é construído, quer o identificador do indicador, cujos dados são usados para gerar o Alligator. Primeiro, verificamos o valor na célula da matriz e se o valor for menor que 10, significa que o indicador é baseado num dos tipos de preços possíveis, e exibimos descrição do tipo de preço. Se o valor for 10 ou superior, o indicador será construído com base nos dados de outro indicador cujo identificador está escrito na matriz - exibimos o valor do identificador deste indicador.

Método para exibir a descrição dos parâmetros do indicador da classe do objeto-indicador Envelopes:

//+------------------------------------------------------------------+
//| Display parameter description of indicator object in the journal |
//+------------------------------------------------------------------+
void CIndEnvelopes::PrintParameters(void)
  {
   ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- ");
   //--- ma_period
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_PERIOD),": ",(string)m_mql_param[0].integer_value);
   //--- ma_shift
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_SHIFT),": ",(string)m_mql_param[1].integer_value);
   //--- ma_method
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_MA_METHOD),": ",AveragingMethodDescription((ENUM_MA_METHOD)m_mql_param[2].integer_value));
   //--- applied_price
   ::Print(
           " - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_APPLIED_PRICE),": ",
           (m_mql_param[3].integer_value<10 ? AppliedPriceDescription((ENUM_APPLIED_PRICE)m_mql_param[3].integer_value) : (string)m_mql_param[3].integer_value)
          );
   //--- deviation
   ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_DEVIATION),": ",::DoubleToString(m_mql_param[4].double_value,3));
   
  }
//+------------------------------------------------------------------+

O indicador Envelopes possui cinco parâmetros de entrada. O último parâmetro "Desvio dos limites da linha central" é do tipo real, portanto para exibir uma descrição deste parâmetro, pegamos valor a partir dos dados reais da estrutura de parâmetros de entrada.

Todos os outros métodos de classes de objetos-indicadores de uma forma ou de outra repetem a lógica acima, e não consideraremos seus métodos - eles podem ser estudados independentemente nos arquivos anexados ao artigo.

Colocamos todos os objetos-indicadores criados na lista-coleção de indicadores. No terminal, ao criarmos qualquer número de indicadores com parâmetros idênticos, de fato, estamos gerando um indicador e todas as chamadas vão para ele. Portanto, para criar objetos-indicadores e colocá-los na lista-coleção, precisamos controlar a presença na lista-coleção desse mesmo indicador com o mesmo tipo, símbolo/período gráfico e parâmetros que queremos colocar na lista. Simplesmente como os objetos-indicadores absolutamente idênticos têm o mesmo identificador para o indicador criado, isto que significa que se trata do mesmo objeto-indicador.

Vamos fazer algumas mudanças na classe da coleção de objetos-indicadores no arquivo \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh que começamos a fazer no último artigo. Para buscar e comparar dois objetos-indicadores, usamos o método Search() da classe da matriz dinâmica de ponteiros para instâncias da classe CObject e seus herdeiros, que vem com a biblioteca padrão. Mas este método não pode determinar com precisão se dos dois objetos que contêm estruturas são idênticos. Este método se destina a comparar uma propriedade especificada de dois objetos do mesmo tipo. Nós, nos objetos indicadores, usamos ativamente a matriz de estruturas de parâmetros do indicador MqlParam nas quais é necessário fazer a comparação elemento a elemento de cada propriedade da estrutura na matriz. Felizmente, em todos os objetos da biblioteca, por padrão, temos o método IsEqual() para comparar dois objetos do mesmo tipo com precisão. Usaremos esse método para comparar se dois objetos do mesmo tipo são idênticos.

Na seção privada da classe declaramos um método que retorna o índice do objeto-indicador na lista-coleção:

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

//--- Create a new indicator object
   CIndicatorDE           *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT);
//--- Return the indicator index in the list
   int                     Index(CIndicatorDE *compared_obj);

public:

No final da lista do corpo da classe declaramos dois métodos públicos, para exibir descrições completas e curtas de objetos-indicadores, que estão na lista de coleção:

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

//--- Constructor
                           CIndicatorsCollection();

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

Fora do corpo da classe, escrevemos a implementação dos métodos declarados.

O método que registra no log uma descrição completa da coleção:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CIndicatorsCollection::Print(void)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CIndicatorDE *ind=m_list.At(i);
      if(ind==NULL)
         continue;
      ind.Print();
     }
  }
//+------------------------------------------------------------------+

Num ciclo percorrendo a lista-coleção obtemos o próximo objeto-indicador e imprimimos sua descrição completa no log.

O método que registra no log uma breve descrição da coleção:

//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CIndicatorsCollection::PrintShort(void)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CIndicatorDE *ind=m_list.At(i);
      if(ind==NULL)
         continue;
      ind.PrintShort();
     }
  }
//+------------------------------------------------------------------+

Num ciclo percorrendo a lista-coleção obtemos o próximo objeto-indicador e imprimimos sua breve descrição no log.

Um método que retorna o índice do objeto-indicador na lista-coleção:

//+------------------------------------------------------------------+
//| Return the indicator index in the list                           |
//+------------------------------------------------------------------+
int CIndicatorsCollection::Index(CIndicatorDE *compared_obj)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CIndicatorDE *indicator=m_list.At(i);
      if(indicator==NULL)
         continue;
      if(indicator.IsEqual(compared_obj))
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Num ciclo percorrendo a lista-coleção obtemos o próximo objeto-indicador, o comparamos com o objeto-indicador cujo ponteiro é passado para o método e, se os objetos forem iguais, retornaremos o índice do ciclo. No final do ciclo (se todos os objetos não forem iguais) retornamos -1.

Em todos os métodos que permitem criar novos objetos indicadores e colocá-los na lista-coleção, foram feitas correções idênticas para cada método para eliminar possíveis vazamentos de memória quando um objeto for criado sem sucesso ou não for colocado na lista.

Vamos dar um exemplo do método para criar o indicador Accelerator Oscillator:

//+------------------------------------------------------------------+
//| Create a new indicator object Accelerator Oscillator             |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- 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);
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- If such indicator is already in the list
   int index=this.Index(indicator);
   if(index!=WRONG_VALUE)
     {
      //--- Remove created object, get indicator object from the list and return indicator handle
      delete indicator;
      indicator=this.m_list.At(index);
      return indicator.Handle();
     }
//--- If such indicator is not in the list
   else
     {
      //--- If failed to add indicator object to the list
      //--- display the appropriate 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;
        }
      //--- Return the handle of a new indicator added to the list
      return indicator.Handle();
     }
//--- Return INVALID_HANDLE
   return INVALID_HANDLE;
  }
//+------------------------------------------------------------------+

Verificamos a presença de um objeto-indicador na lista segundo seu índice.
Se o índice for maior que -1
, então está na lista e o objeto recém-criado precisa ser excluído.
Se não houver tal indicador na lista e por algum motivo não puder ser adicionado à lista de coleção
, então novamente precisamos deletar o objeto recém-criado.
Isso nos salvará de vazamentos de memória quando um novo objeto for colocado sem sucesso numa lista-coleção.

Essas alterações foram feitas em todos os métodos de criação de objetos-indicadores e não as consideraremos - elas podem ser estudadas independentemente nos arquivos anexados ao artigo.

Para pesquisar um objeto-indicador numa lista-coleção e obter um ponteiro para ele a partir desta lista, precisamos de métodos que retornem ponteiros para o objeto requerido. No método, passamos o tipo de indicador requerido, seu símbolo, período gráfico e parâmetros (para cada indicador, seus parâmetros correspondem ao tipo de indicador), e na saída devemos obter um ponteiro para o objeto-indicador encontrado na lista.

No último artigo, escrevemos um desses métodos para obter um ponteiro para o indicador Accelerator Oscillator. Ele é o mais simples, uma vez que não tem parâmetros de entrada, e tudo o que é necessário é encontrar o objeto desejado por símbolo e período gráfico:

//+------------------------------------------------------------------+
//| Return the pointer to Accelerator Oscillator indicator object    |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndAC(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CArrayObj *list=GetListAC(symbol,timeframe);
   return(list==NULL || list.Total()==0 ? NULL : list.At(0));
  }
//+------------------------------------------------------------------+

Para pesquisar indicadores com parâmetros de entrada, temos que criar um objeto-indicador temporário com os parâmetros definidos e procurar por uma correspondência numa lista-coleção:

//+------------------------------------------------------------------+
//| Return the pointer to                                            |
//| Accumulation/Distribution indicator object                       |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume)
  {
   MqlParam param[1];
   param[0].type=TYPE_INT;
   param[0].integer_value=applied_volume;
   CIndicatorDE *tmp=this.CreateIndicator(IND_AD,param,symbol,timeframe);
   if(tmp==NULL)
      return NULL;
   int index=this.Index(tmp);
   delete tmp;
   return(index>WRONG_VALUE ? this.m_list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

Se tal objeto for encontrado na lista, seu índice será retornado; caso contrário, será devolvido -1.

Os métodos restantes para retornar ponteiros para objetos-indicadores na lista são idênticos aos anteriores, mas têm parâmetros diferentes para criar um objeto-indicador. Por exemplo, para retornar um ponteiro para o objeto-indicador Alligator, precisamos criar uma matriz de oito parâmetros:

//+------------------------------------------------------------------+
//| Return the pointer to Alligator indicator object                 |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                     const int jaw_period,
                                                     const int jaw_shift,
                                                     const int teeth_period,
                                                     const int teeth_shift,
                                                     const int lips_period,
                                                     const int lips_shift,
                                                     const ENUM_MA_METHOD ma_method,
                                                     const ENUM_APPLIED_PRICE applied_price)
  {
   MqlParam param[8];
   param[0].type=TYPE_INT;
   param[0].integer_value=jaw_period;
   param[1].type=TYPE_INT;
   param[1].integer_value=jaw_shift;
   param[2].type=TYPE_INT;
   param[2].integer_value=teeth_period;
   param[3].type=TYPE_INT;
   param[3].integer_value=teeth_shift;
   param[4].type=TYPE_INT;
   param[4].integer_value=lips_period;
   param[5].type=TYPE_INT;
   param[5].integer_value=lips_shift;
   param[6].type=TYPE_INT;
   param[6].integer_value=ma_method;
   param[7].type=TYPE_INT;
   param[7].integer_value=applied_price;
   CIndicatorDE *tmp=this.CreateIndicator(IND_ALLIGATOR,param,symbol,timeframe);
   if(tmp==NULL)
      return NULL;
   int index=this.Index(tmp);
   delete tmp;
   return(index>WRONG_VALUE ? this.m_list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

Todo o resto é idêntico ao método acima de retornar um ponteiro para o objeto-indicador Accumulation/Distribution.
Em cada um dos método é necessário excluir o objeto-indicador temporário que serve como uma referência para procurar por uma correspondência na lista-coleção.

Não consideraremos outros métodos semelhantes - eles são idênticos aos dois que acabamos de considerar.

Assim concluímos o aprimoramento das classes neste artigo.


Expert Advisor de teste

Para testar a criação de indicadores em Expert Advisors, vamos pegar no Expert Advisor de teste do artigo 39
e o armazenamos na nova pasta \MQL5\Experts\TestDoEasy\Part55\ com o novo nome TestDoEasyPart55.mq5.

A maioria das melhorias será "cosmética". Num dos artigos anteriores, transferimos a função para trabalhar com eventos no testador EventsHandling() para a biblioteca - para o arquivo Engine.mqh. Por isso, removemos esta função do código do EA, e substituímos no manipulador OnTick() a chamada desde o arquivo do EA

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      EventsHandling();             // Working with events
     }

pela chamada desde a biblioteca:

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

Excluímos o bloco do código do manipulador OnTick(), código esse que exibe um comentário com dados na barra atual do gráfico:

//--- Bet the zero bar of the current timeseries
   CBar *bar=engine.SeriesGetBar(NULL,PERIOD_CURRENT,0);
   if(bar==NULL)
      return;
//--- Create a string of parameters of the current bar similar to the one
//--- displayed by the bar object description:
//--- bar.Header()+": "+bar.ParameterDescription()
   string parameters=
     (TextByLanguage("Бар \"","Bar \"")+Symbol()+"\" "+TimeframeDescription((ENUM_TIMEFRAMES)Period())+"[0]: "+TimeToString(bar.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+
      ", O: "+DoubleToString(engine.SeriesOpen(NULL,PERIOD_CURRENT,0),Digits())+
      ", H: "+DoubleToString(engine.SeriesHigh(NULL,PERIOD_CURRENT,0),Digits())+
      ", L: "+DoubleToString(engine.SeriesLow(NULL,PERIOD_CURRENT,0),Digits())+
      ", C: "+DoubleToString(engine.SeriesClose(NULL,PERIOD_CURRENT,0),Digits())+
      ", V: "+(string)engine.SeriesTickVolume(NULL,PERIOD_CURRENT,0)+
      ", Real: "+(string)engine.SeriesRealVolume(NULL,PERIOD_CURRENT,0)+
      ", Spread: "+(string)engine.SeriesSpread(NULL,PERIOD_CURRENT,0)
     );
//--- Display the data received from the bar object in the first line of the chart comment,
//--- while the second line contains the methods of receiving timeseries price data
   Comment(bar.Header(),": ",bar.ParameterDescription(),"\n",parameters);

Assim, o manipulador de eventos "novo tick" ficará assim:

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

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
  }
//+------------------------------------------------------------------+

Na função de inicialização da biblioteca OnInitDoEasy() no bloco que exibe a lista de símbolos usados substituímos o número de caracteres, dado pelo número,

//--- Implement displaying the list of used symbols only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT)
     {
      string array_symbols[];
      CArrayObj* list_symbols=engine.GetListAllUsedSymbols();
      for(int i=0;i<list_symbols.Total();i++)
        {
         CSymbol *symbol=list_symbols.At(i);
         if(symbol==NULL)
            continue;
         ArrayResize(array_symbols,ArraySize(array_symbols)+1,1000);
         array_symbols[ArraySize(array_symbols)-1]=symbol.Name();
        }
      ArrayPrint(array_symbols);
     }
#endif   

pelo número de símbolos, especificado na substituição de macro:

//--- Implement displaying the list of used symbols only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT)
     {
      string array_symbols[];
      CArrayObj* list_symbols=engine.GetListAllUsedSymbols();
      for(int i=0;i<list_symbols.Total();i++)
        {
         CSymbol *symbol=list_symbols.At(i);
         if(symbol==NULL)
            continue;
         ArrayResize(array_symbols,ArraySize(array_symbols)+1,SYMBOLS_COMMON_TOTAL);
         array_symbols[ArraySize(array_symbols)-1]=symbol.Name();
        }
      ArrayPrint(array_symbols);
     }
#endif   

já no MetaTrader 5, a partir da versão 2430, o número total de símbolos de trabalho mudou, e este número é verificado pela biblioteca e instalado automaticamente na substituição da macro SYMBOLS_COMMON_TOTAL, declarada no arquivo \MQL5\Include\DoEasy\Defines.mqh..

Temporariamente e apenas para verificar a criação de objetos-indicadores, iremos criar dois indicadores do mesmo tipo, mas com parâmetros diferentes. Até que seja feita a criação normal de indicadores em nossos programas, basta criá-los na função de inicialização da biblioteca:

//+------------------------------------------------------------------+
//| Initializing DoEasy library                                      |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Check if working with the full list is selected
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списков коллекций символов и таймсерий может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of lists of symbol collections and timeseries can take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Set the counter start point to measure the approximate library initialization time
   ulong begin=GetTickCount();
   Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---"));
//--- Fill in the array of used symbols
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols);
//--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries
   engine.SetUsedSymbols(array_used_symbols);
//--- Displaying the selected mode of working with the symbol object collection in the journal
   string num=
     (
      used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : 
      TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(string)engine.GetSymbolsCollectionTotal()
     );
   Print(engine.ModeSymbolsListDescription(),num);
//--- Implement displaying the list of used symbols only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT)
     {
      string array_symbols[];
      CArrayObj* list_symbols=engine.GetListAllUsedSymbols();
      for(int i=0;i<list_symbols.Total();i++)
        {
         CSymbol *symbol=list_symbols.At(i);
         if(symbol==NULL)
            continue;
         ArrayResize(array_symbols,ArraySize(array_symbols)+1,SYMBOLS_COMMON_TOTAL);
         array_symbols[ArraySize(array_symbols)-1]=symbol.Name();
        }
      ArrayPrint(array_symbols);
     }
#endif   
//--- Set used timeframes
   CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods);
//--- Display the selected mode of working with the timeseries object collection
   string mode=
     (
      InpModeUsedTFs==TIMEFRAMES_MODE_CURRENT   ? 
         TextByLanguage("Работа только с текущим таймфреймом: ","Work only with the current Period: ")+TimeframeDescription((ENUM_TIMEFRAMES)Period())   :
      InpModeUsedTFs==TIMEFRAMES_MODE_LIST      ? 
         TextByLanguage("Работа с заданным списком таймфреймов:","Work with a predefined list of Periods:")                                              :
      TextByLanguage("Работа с полным списком таймфреймов:","Work with the full list of all Periods:")
     );
   Print(mode);
//--- Implement displaying the list of used timeframes only for MQL5 - MQL4 has no ArrayPrint() function
#ifdef __MQL5__
   if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT)
      ArrayPrint(array_used_periods);
#endif 
//--- 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
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Pass all existing collections to the main library class
   engine.CollectionOnInit();

//--- Set the default magic number for all used symbols
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Set synchronous passing of orders for all used symbols
   engine.TradingSetAsyncMode(false);
//--- Set the number of trading attempts in case of an error
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Set correct order expiration and filling types to all trading objects
   engine.TradingSetCorrectTypeExpiration();
   engine.TradingSetCorrectTypeFilling();

//--- Set standard sounds for trading objects of all used symbols
   engine.SetSoundsStandart();
//--- Set the general flag of using sounds
   engine.SetUseSounds(InpUseSounds);
//--- Set the spread multiplier for symbol trading objects in the symbol collection
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Set controlled values for symbols
   //--- Get the list of all collection symbols
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- In a loop by the list, set the necessary values for tracked symbol properties
      //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" 
      //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program
      /*
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Set control of the symbol price increase by 100 points
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Set control of the symbol price decrease by 100 points
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Set control of the symbol spread increase by 40 points
         symbol.SetControlSpreadInc(400);
         //--- Set control of the symbol spread decrease by 40 points
         symbol.SetControlSpreadDec(400);
         //--- Set control of the current spread by the value of 40 points
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Set controlled values for the current account
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Set control of the profit increase to 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Set control of the funds increase to 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Set profit control level to 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
//--- Get the end of the library initialization time counting and display it in the journal
   ulong end=GetTickCount();
   Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS));
  }
//+------------------------------------------------------------------+

Aqui nós criamos dois indicadores Adaptive Moving Average calculados com base no símbolo e período gráfico atuais, mas com diferentes valores dos parâmetros de entrada.

Vamos compilar o Expert Advisor e executá-lo no gráfico do terminal.
Após sua inicialização, as mensagens de inicialização da biblioteca serão exibidas no log "Experts", incluindo impressões completas ebreves dos parâmetros dos dois indicadores criados:

Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, Demo account MetaTrader 5
--- Initializing the "DoEasy" library ---
Work only with the current symbol: "EURUSD"
Work with a predefined list of Periods:
"H1" "H4"
Symbol time series EURUSD: 
- Timeseries "EURUSD" H1: Required: 1000, Actual: 1000, Created: 1000, On server: 6350
- Timeseries "EURUSD" H4: Required: 1000, Actual: 1000, Created: 1000, On server: 6255
============= The beginning of the event parameter list: "Standard indicator" =============
Indicator status: Standard indicator
Indicator type: AMA
Indicator timeframe: H1
Indicator handle: 10
Indicator group: Trend indicator
------
Empty value for plotting, for which there is no drawing: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Adaptive Moving Average"
Indicator shortname: "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
================== End of the parameter list: "Standard indicator" ==================
 
============= The beginning of the event parameter list: "Standard indicator" =============
Indicator status: Standard indicator
Indicator type: AMA
Indicator timeframe: H1
Indicator handle: 11
Indicator group: Trend indicator
------
Empty value for plotting, for which there is no drawing: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Adaptive Moving Average"
Indicator shortname: "AMA(EURUSD,H1)"
--- Indicator parameters --- 
- Averaging period: 10
- Fast MA period: 3
- Slow MA period: 32
- Horizontal shift of the indicator: 5
- Price type or handle: CLOSE
================== End of the parameter list: "Standard indicator" ==================
 
Standard indicator Adaptive Moving Average EURUSD H1 [10]
Standard indicator Adaptive Moving Average EURUSD H1 [11]
Library initialization time: 00:00:00.000

Embora exista apenas um tipo de indicador, o AMA, foram criados dois identificadores deste indicador, uma vez que os parâmetros dos indicadores criados eram diferentes e, portanto, eram dois indicadores distintos - cada uma com um identificador próprio. Conseqüentemente, na coleção de indicadores foram criados e colocados dois objetos indicadores.

Até agora, só podemos criar diferentes indicadores com diferentes parâmetros, mas para seu uso em Expert Advisors, precisamos preparar um local para armazenar seus dados, local esse de onde podemos receber dados em quaisquer combinações de parâmetros e usá-los em programas de tomada de decisões ou obtenção de dados estatísticos. Trataremos de tudo isso no próximo artigo.

O que vem agora?

No próximo artigo, começaremos a implementar o armazenamento e o recebimento de dados de objetos indicadores em EAs.

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

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/8576

Arquivos anexados |
MQL5.zip (3856.21 KB)
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 54): classes herdeiras do indicador base abstrato

Neste artigo, analisaremos a criação de classes de objetos herdeiros do indicador base abstrato. Esses objetos nos darão acesso à capacidade de criar EAs de indicador, coletar e receber estatísticas sobre valores de dados de diferentes indicadores e preços. Também criaremos uma coleção de objetos-indicadores a partir da qual será possível acessar as propriedades e dados de cada indicador criado no programa.

Trabalhando com séries temporais na biblioteca DoEasy (Parte 53): classe do indicador base abstrato Trabalhando com séries temporais na biblioteca DoEasy (Parte 53): classe do indicador base abstrato

Neste artigo, veremos a criação de uma classe de indicador abstrato que será posteriormente usada como uma classe base para a criação de objetos de indicadores padrão e personalizados da biblioteca.

Redes Neurais de Maneira Fácil (Parte 3): Redes Convolucionais Redes Neurais de Maneira Fácil (Parte 3): Redes Convolucionais

Como uma continuação do tópico das redes neurais, eu proponho ao leitor a análise das redes neurais convolucionais. Esse tipo de rede neural geralmente é aplicado para analisar imagens visuais. Neste artigo, nós consideraremos a aplicação dessas redes no mercado financeiro.

Grade e martingale: o que são e como usá-los? Grade e martingale: o que são e como usá-los?

Neste artigo, tentarei explicar em detalhes o que são grade e martingale, bem como o que eles têm em comum. Além disso, procurarei analisar o quão viáveis essas estratégias são na realidade. Teremos uma parte matemática e outra prática.