Работа с таймсериями в библиотеке DoEasy (Часть 56): Объект пользовательского индикатора, получение данных от объектов-индикаторов в коллекции

Artyom Trishkin | 5 ноября, 2020

Содержание


Концепция

Продолжаем создавать функционал для работы с индикаторами в экспертах, созданных на основе данной библиотеки.
Мы уже создали классы объектов-стандартных индикаторов и начали размещать их в список-коллекцию. Для полного "комплекта" нам не достаёт лишь объекта-пользовательского индикатора. Сегодня создадим такой объект. Концепция его построения будет отличаться от концепции создания объектов стандартных индикаторов.
Так как мы имеем конечный набор стандартных индикаторов в терминале, то мы можем создавать объекты стандартных индикаторов на точно и заранее известных данных для каждого из индикаторов, что мы и делаем — указываем строго заданный набор параметров, соответствующий создаваемому индикатору. В то время как пользовательский индикатор может иметь какой угодно набор параметров, который заранее знать нет никакой возможности.
По этой причине, концепция создания объекта-пользовательского индикатора будет отличаться от создания объекта-стандартного индикатора.

Если нам для создания объекта-стандартного индикатора достаточно создать для каждого из них свой метод создания, в котором будут во входных параметрах прописаны все необходимые его свойства, то чтобы создать пользовательский индикатор с заранее неизвестным количеством и типом параметров, нам придётся в его метод создания передавать заранее заполненный массив структур входных параметров индикатора, из которого уже будут браться все необходимые для создания индикатора его параметры и свойства. Поэтому, к сожалению, пользователю библиотеки придётся самостоятельно заполнить такой массив структур входных параметров для создания пользовательского индикатора в своей программе.

Так как мы пытаемся создать библиотеку для упрощения создания своих программ, то из нескольких вариантов обращения к созданным индикаторам в своей программе я выбрал следующий: каждый создаваемый индикатор будет помечаться своим собственным уникальным идентификатором. По этому идентификатору мы сможем обратиться к каждому из созданных индикаторов. Благо, мы их сами создаём, сами им назначаем идентификаторы, и прекрасно знаем какой из назначенных идентификаторов соответствует тому, или иному индикатору, созданному в программе.

Помимо идентификатора также будут методы обращения к индикатору по всем его параметрам. Т.е., можно будет получить объект индикатора, указав те же самые параметры, с которыми и был создан этот объект-индикатор, и уже работать с полученным объектом — получать данные от индикатора (методы получения данных сегодня создадим) и копировать их в массивы для статистических исследований (это уже будет в последующих статьях).


Доработка классов библиотеки

Как обычно, в первую очередь внесём новые сообщения библиотеки в файл \MQL5\Include\DoEasy\Data.mqh.
Добавим индексы новых сообщений:

   MSG_LIB_TEXT_IND_TEXT_GROUP,                       // Группа индикатора
   MSG_LIB_TEXT_IND_TEXT_GROUP_TREND,                 // Трендовый индикатор
   MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR,            // Осциллятор
   MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES,               // Объёмы
   MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS,                // Стрелочный индикатор
   MSG_LIB_TEXT_IND_TEXT_ID,                          // Идентификатор индикатора
   
   MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE,                 // Пустое значение для построения, для которого нет отрисовки
   MSG_LIB_TEXT_IND_TEXT_SYMBOL,                      // Символ индикатора
   MSG_LIB_TEXT_IND_TEXT_NAME,                        // Имя индикатора
   MSG_LIB_TEXT_IND_TEXT_SHORTNAME,                   // Короткое имя индикатора
   
   MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS,              // Параметры индикатора
   MSG_LIB_TEXT_IND_TEXT_APPLIED_VOLUME,              // Тип объема для расчета
   MSG_LIB_TEXT_IND_TEXT_PERIOD,                      // Период усреднения
   MSG_LIB_TEXT_IND_TEXT_FAST_PERIOD,                 // Период быстрой скользящей
   MSG_LIB_TEXT_IND_TEXT_SLOW_PERIOD,                 // Период медленной скользящей
   MSG_LIB_TEXT_IND_TEXT_SIGNAL,                      // Период усреднения разности
   MSG_LIB_TEXT_IND_TEXT_TENKAN_PERIOD,               // Период Tenkan-sen
   MSG_LIB_TEXT_IND_TEXT_KIJUN_PERIOD,                // Период Kijun-sen
   MSG_LIB_TEXT_IND_TEXT_SPANB_PERIOD,                // Период Senkou Span B
   MSG_LIB_TEXT_IND_TEXT_JAW_PERIOD,                  // Период для расчета челюстей
   MSG_LIB_TEXT_IND_TEXT_TEETH_PERIOD,                // Период для расчета зубов
   MSG_LIB_TEXT_IND_TEXT_LIPS_PERIOD,                 // Период для расчета губ
   MSG_LIB_TEXT_IND_TEXT_JAW_SHIFT,                   // Смещение челюстей по горизонтали
   MSG_LIB_TEXT_IND_TEXT_TEETH_SHIFT,                 // Смещение зубов по горизонтали
   MSG_LIB_TEXT_IND_TEXT_LIPS_SHIFT,                  // Смещение губ по горизонтали
   MSG_LIB_TEXT_IND_TEXT_SHIFT,                       // Смещение индикатора по горизонтали
   MSG_LIB_TEXT_IND_TEXT_MA_METHOD,                   // Тип сглаживания
   MSG_LIB_TEXT_IND_TEXT_APPLIED_PRICE,               // Тип цены или handle
   MSG_LIB_TEXT_IND_TEXT_STD_DEVIATION,               // Количество стандартных отклонений
   MSG_LIB_TEXT_IND_TEXT_DEVIATION,                   // Отклонение границ от средней линии
   MSG_LIB_TEXT_IND_TEXT_STEP,                        // Шаг изменения цены - коэффициент ускорения
   MSG_LIB_TEXT_IND_TEXT_MAXIMUM,                     // Максимальный шаг
   MSG_LIB_TEXT_IND_TEXT_KPERIOD,                     // K-период (количество баров для расчетов)
   MSG_LIB_TEXT_IND_TEXT_DPERIOD,                     // D-период (период первичного сглаживания)
   MSG_LIB_TEXT_IND_TEXT_SLOWING,                     // Окончательное сглаживание
   MSG_LIB_TEXT_IND_TEXT_PRICE_FIELD,                 // Способ расчета стохастика
   MSG_LIB_TEXT_IND_TEXT_CMO_PERIOD,                  // Период Chande Momentum
   MSG_LIB_TEXT_IND_TEXT_SMOOTHING_PERIOD,            // Период фактора сглаживания
   MSG_LIB_TEXT_IND_TEXT_CUSTOM_PARAM,                // Входной параметр
   
//--- CIndicatorsCollection
   MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,                // Ошибка. Не удалось добавить объект-индикатор в список
   MSG_LIB_SYS_INVALID_IND_POINTER,                   // Ошибка. Передан неверный указатель на объект-индикатор
   MSG_LIB_SYS_IND_ID_EXIST,                          // Ошибка. Уже существует объект-индикатор с идентификатором
   
  };
//+------------------------------------------------------------------+

и текстовые сообщения, соответствующие вновь добавленным индексам:

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


При создании объекта-стандартного индикатора мы ему сразу же — в его конструкторе — задаём группу, соответствующую типу индикатора: трендовый, стрелочный, осциллятор, или индикатор объёмов. Для стандартных индикаторов мы это можем делать, так как заранее знаем тип создаваемого индикатора. Для пользовательского индикатора мы не можем знать его тип заранее (группу, к которой принадлежит индикатор) и задавать его в конструкторе класса объекта. Поэтому нам нужно создать ещё одну группу индикаторов "любой", которая будет автоматически назначаться вновь созданному объекту, и дать пользователю самостоятельно указывать группу принадлежности пользовательского индикатора уже после создания его объекта.

В файле \MQL5\Include\DoEasy\Defines.mqh в перечислении группы индикатора добавим новую константу:

//+------------------------------------------------------------------+
//| Группа индикатора                                                |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_GROUP
  {
   INDICATOR_GROUP_TREND,                                   // Трендовый индикатор
   INDICATOR_GROUP_OSCILLATOR,                              // Осциллятор
   INDICATOR_GROUP_VOLUMES,                                 // Объёмы
   INDICATOR_GROUP_ARROWS,                                  // Стрелочный индикатор
   INDICATOR_GROUP_ANY,                                     // Любой индикатор
  };
//+------------------------------------------------------------------+

Так как мы условились обращаться к созданным индикаторам по уникальным идентификаторам, то нам необходимо в перечисление целочисленных свойств индикатора добавить новое свойство:

//+------------------------------------------------------------------+
//| Целочисленные свойства индикатора                                |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_INTEGER
  {
   INDICATOR_PROP_STATUS = 0,                               // Статус индикатора (из перечисления ENUM_INDICATOR_STATUS)
   INDICATOR_PROP_TYPE,                                     // Тип индикатора (из перечисления ENUM_INDICATOR)
   INDICATOR_PROP_TIMEFRAME,                                // Таймфрейм индикатора
   INDICATOR_PROP_HANDLE,                                   // Хэндл индикатора
   INDICATOR_PROP_GROUP,                                    // Группа индикатора
   INDICATOR_PROP_ID,                                       // Идентификатор индикатора
  }; 
#define INDICATOR_PROP_INTEGER_TOTAL (6)                    // Общее количество целочисленных свойств индикатора
#define INDICATOR_PROP_INTEGER_SKIP  (0)                    // Количество неиспользуемых в сортировке свойств индикатора
//+------------------------------------------------------------------+

и соответственно, увеличим количество целочисленных свойств индикатора с 5 до 6.

Для возможности поиска индикатора в списке по его идентификатору, внесём новый критерий сортировки индикаторов по идентификаторам:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки индикаторов                            |
//+------------------------------------------------------------------+
#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_INDICATOR_INDEX_STATUS = 0,                      // Сортировать по статусу индикатора
   SORT_BY_INDICATOR_TYPE,                                  // Сортировать по типу индикатора
   SORT_BY_INDICATOR_TIMEFRAME,                             // Сортировать по таймфрейму индикатора
   SORT_BY_INDICATOR_HANDLE,                                // Сортировать по хэндлу индикатора
   SORT_BY_INDICATOR_GROUP,                                 // Сортировать по группе индикатора
   SORT_BY_INDICATOR_ID,                                    // Сортировать по идентификатору индикатора
//--- Сортировка по вещественным свойствам
   SORT_BY_INDICATOR_EMPTY_VALUE = FIRST_INDICATOR_DBL_PROP,// Сортировать по пустому значению для построения, для которого нет отрисовки
//--- Сортировка по строковым свойствам
   SORT_BY_INDICATOR_SYMBOL = FIRST_INDICATOR_STR_PROP,     // Сортировать по символу индикатора
   SORT_BY_INDICATOR_NAME,                                  // Сортировать по имени индикатора
   SORT_BY_INDICATOR_SHORTNAME,                             // Сортировать по короткому имени индикатора
  };
//+------------------------------------------------------------------+


Так как были внесены новые свойства объекту-индикатору, то теперь необходимо немного доработать класс объекта абстрактного индикатора в файле \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh.

В публичной секции класса напишем два метода для установки и получения свойства "идентификатор индикатора":

//--- Устанавливает (1) группу, (2) пустое значение буферов, (3) имя, (4) короткое имя, (5) идентификатор индикатора
   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);                               }
   
//--- Возвращает (1) статус, (2) группу, (3) таймфрейм, (4) тип, (5) хэндл, (6) идентификатор,
//--- (7) пустое значение буферов, (8) имя, (9) короткое имя, (10) символ индикатора
   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);                       }

В объектах стандартных индикаторов мы могли написать методы для вывода описания каждого параметра индикатора, так как точно знаем какой параметр конкретного индикатора мы описываем. Для вывода описания параметров пользовательского индикатора мы не можем точно знать назначение каждого параметра неизвестного нам заранее индикатора. Поэтому здесь нам придётся просто выводить описания каждого последующего параметра из массива параметров индикатора MqlParam.
В публичной секции класса объекта абстрактного индикатора объявим метод для вывода описания параметров структуры MqlParam, и ещё два метода — для получения данных от объекта-индикатора по индексу и времени бара:

//--- Возвращает описание (1) типа, (2) статуса, (3) группы, (4) таймфрейма, (5) пустого значения индикатора, (6) параметра массива m_mql_param
   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;
   
//--- Выводит в журнал описание свойств объекта-индикатора (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал (1) краткое описание, (2) описание параметров объекта-индикатора (реализация в наследниках)
   virtual void      PrintShort(void) {;}
   virtual void      PrintParameters(void) {;}

//--- Возвращает данные указанного буфера с указанного бара (1) по индексу, (2) по времени бара
   double            GetDataBuffer(const int buffer_num,const int index);
   double            GetDataBuffer(const int buffer_num,const datetime time);
  };
//+------------------------------------------------------------------+

За пределами тела класса напишем реализацию этих методов:

Метод, возвращающий описание параметра массива структур MqlParam:

//+------------------------------------------------------------------+
//| Возвращает описание параметра массива m_mql_param                |
//+------------------------------------------------------------------+
string CIndicatorDE::GetMqlParamDescription(const int index) const
  {
   return "["+(string)index+"] "+MqlParameterDescription(this.m_mql_param[index]);
  }
//+------------------------------------------------------------------+

В метод передаётся индекс параметра в массиве, и далее создаётся строка из индекса массива и описания параметра в соответствии с данными, хранящимися в структуре по указанному индексу массива. Функцию MqlParameterDescription() для возврата описания данных структуры MqlParam мы напишем чуть ниже.

Методы, возвращающие данные индикатора по индексу и времени бара:

//+------------------------------------------------------------------+
//| Возвращает данные указанного буфера с указанного бара по индексу |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+
//| Возвращает данные указанного буфера с указанного бара по времени |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+

В методы передаются индекс или время бара, данные которого необходимо получить от индикатора. При помощи функции CopyBuffer() запрашиваем один бар по индексу или времени и возвращаем полученный результат, записанный в массив. Если по какой-либо причине получить данные не удалось, то методы возвращают пустое значение, установленное объекту-индикатору.

В конструкторе класса установим идентификатор индикатора по умолчанию (-1):

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
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[])
  {
//--- Устанавливаем идентификатор коллекции объекту
   this.m_type=COLLECTION_INDICATORS_ID;
//--- Записываем описание типа индикатора
   this.m_ind_type_description=IndicatorTypeDescription(ind_type);
//--- Если размер переданного в конструктор массива параметров больше нуля, то
//--- заполняем массив параметров объекта данными из переданного в конструктор массива
   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;
     }
//--- Создаём хэндл индикатора
   int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,this.m_mql_param);
   
//--- Сохранение целочисленных свойств
   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;
   
//--- Сохранение вещественных свойств
   this.m_double_prop[this.IndexProp(INDICATOR_PROP_EMPTY_VALUE)]=EMPTY_VALUE; 
//--- Сохранение строковых свойств
   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;
  }
//+------------------------------------------------------------------+

Так как у каждого индикатора его хэндл является уникальным, а так же и его идентификатор, который мы будем назначать самостоятельно, то при сравнении двух индикаторов на идентичность по их параметрам, нам нужно пропустить озвученные выше два параметра — иначе каждый индикатор будет определяться как уникальный, и мы не сможем верно сравнить параметры двух индикаторов на идентичность.
Чтобы этого не происходило, пропустим свойства "хэндл" и "идентификатор" в методе сравнения двух объектов-индикаторов:

//+------------------------------------------------------------------+
//| Сравнивает объекты CIndicatorDE между собой по всем свойствам    |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

В методе, возвращающем описание целочисленного свойства индикатора, впишем вывод описания свойства индикатора "идентификатор":

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства индикатора           |
//+------------------------------------------------------------------+
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)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Теперь в файл сервисных функций \MQL5\Include\DoEasy\Services\DELib.mqh добавим функцию для вывода описания структуры MqlParam:

//+------------------------------------------------------------------+
//| Возвращает описание параметра массива MqlParam                   |
//+------------------------------------------------------------------+
string MqlParameterDescription(const MqlParam &mql_param)
  {
   int type=mql_param.type;
   string res=CMessage::Text(MSG_ORD_TYPE)+" "+typename(type)+": ";
   //--- тип параметра string
   if(type==TYPE_STRING)
      res+=mql_param.string_value;
   //--- тип параметра datetime
   else if(type==TYPE_DATETIME)
      res+=TimeToString(mql_param.integer_value,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   //--- тип параметра color
   else if(type==TYPE_COLOR)
      res+=ColorToString((color)mql_param.integer_value,true);
   //--- тип параметра bool
   else if(type==TYPE_BOOL)
      res+=(string)(bool)mql_param.integer_value;
   //--- целочисленные типы
   else if(type>TYPE_BOOL && type<TYPE_FLOAT)
      res+=(string)mql_param.integer_value;
   //--- вещественные типы
   else
      res+=DoubleToString(mql_param.double_value,8);
   return res;
  }
//+------------------------------------------------------------------+

В структуре MqlParam существует несколько полей. Одно из которых содержит тип данных, хранящихся в структуре. По этому типу данных мы можем понять из какого поля структуры получать данные, и какую функцию строчного представления данных использовать для вывода данных в журнал.

Получаем тип данных и начинаем формировать строку, состоящую из слова "Тип " +" "+ строковое описания типа данных + ": ".
И далее, в зависимости от типа данных, добавляем к уже созданной строке описание значения, хранящегося в поле структуры, соответствующем типу, при помощи стандартных функций для вывода строкового представления нужного типа данных.

В итоге описание параметров пользовательского индикатора с четырьмя параметрами при использовании метода вывода параметров структуры MqlParam класса объекта абстрактного индикатора и вышерассмотренной сервисной функции в журнале терминала будет выглядеть так:

--- Параметры индикатора --- 
- [1] Тип int: 13
- [2] Тип int: 0
- [3] Тип int: 0
- [4] Тип int: 2

Так как мы добавили новое свойство объекту-индикатору — его идентификатор, то во все классы всех объектов индикаторов, файлы которых лежат в папке \MQL5\Include\DoEasy\Objects\Indicators\Standart\ внесём небольшое дополнение в методы вывода короткого наименования индикатора — просто добавим к короткому названию значение идентификатора:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта-индикатора             |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Здесь: создаём описание идентификатора. При этом, если значение идентификатора больше, чем -1, то идентификатор выводиться будет, иначе, если идентификатора нет (его значение -1), то и выводиться в описании он не будет (просто закрывающая квадратная скобка). И далее добавляем полученную строку к короткому описанию индикатора.

Такие доработки уже внесены во все классы объектов-индикаторов.


Объект пользовательского индикатора

Теперь напишем класс объекта-пользовательского индикатора. Разместим его в папку стандартных индикаторов библиотеки \MQL5\Include\DoEasy\Objects\Indicators\Standart\. Просто потому, что в списке индикаторов терминала имеется и пользовательский индикатор, а значит, и его место тоже в списке индикаторов терминала.

Рассмотрим весь класс целиком:

//+------------------------------------------------------------------+
//|                                                    IndCustom.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\\IndicatorDE.mqh"
//+------------------------------------------------------------------+
//| Пользовательский индикатор                                       |
//+------------------------------------------------------------------+
class CIndCustom : public CIndicatorDE 
  {
private:

public:
   //--- Конструктор
                     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) {}
   //--- Поддерживаемые свойства индикатора (1) вещественные, (2) целочисленные
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_INTEGER property);
   
//--- Выводит в журнал (1) краткое описание, (2) описание параметров объекта-индикатора
   virtual void      PrintShort(void);
   virtual void      PrintParameters(void);
  };
//+------------------------------------------------------------------+
//| Возвращает истину, если индикатор поддерживает переданное        |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если индикатор поддерживает переданное        |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта-индикатора             |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+
//| Выводит в журнал описание параметров объекта-индикатора          |
//+------------------------------------------------------------------+
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));
     }
  }
//+------------------------------------------------------------------+

Все методы нам уже знакомы по классам объектов стандартных индикаторов. Но в отличие от них, здесь мы получаем в конструктор класса параметры создаваемого индикатора не во входных переменных, а в заранее созданном массиве структур входных параметров MqlParam. И передаём в закрытый конструктор класса объекта абстрактного индикатора статус "пользовательский индикатор", группу "любой индикатор", и в качестве имени передаём самый первый элемент массива параметров, который при создании пользовательского индикатора обязательно имеет тип TYPE_STRING и значение поля string_value содержит имя пользовательского индикатора. Точно так же создаём и короткое имя индикатора, но с добавлением описания символа и таймфрейма. В дальнейшем имя и короткое имя индикатора можно поменять при помощи методов родительского класса SetName() и SetShortName(). Правда, в имени индикатора содержится и путь к нему, поэтому (по крайней мере на данном этапе развития библиотеки) имя уже созданного пользовательского индикатора менять нельзя.

В методе, выводящем в журнал описание параметров объекта-индикатора делаем буквально следующее: сначала выводим заголовок, а затем в цикле по массиву параметров индикатора распечатываем каждый очередной параметр при помощи ранее рассмотренных методов (в частности — метода родительского класса GetMqlParamDescription()).

Все объекты-индикаторы мы храним в списке-коллекции в классе CIndicatorsCollection в файле \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh.

При добавлении индикатора в список-коллекцию нам необходимо дополнительно проверить уникальность его идентификатора. И добавить видимость класса пользовательского индикатора чтобы класс-коллекция индикаторов мог с ним работать.

Подключим файл класса пользовательского индикатора к классу коллекции объектов-индикаторов:

//+------------------------------------------------------------------+
//|                                         IndicatorsCollection.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+

В приватной секции класса объявим метод для добавления объекта-индикатора в коллекцию и метод, проверяющий наличие в списке объекта-индикатора с заданным идентификатором:

//+------------------------------------------------------------------+
//| Коллекция индикаторов                                            |
//+------------------------------------------------------------------+
class CIndicatorsCollection : public CObject
  {
private:
   CListObj                m_list;                       // Список объектов-индикаторов
   MqlParam                m_mql_param[];                // Массив параметров индикатора

//--- (1) Создаёт, (2) добавляет в список-коллекцию новый объект-индикатор и устанавливает ему идентификатор
   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);
//--- Возвращает индекс индикатора в списке
   int                     Index(CIndicatorDE *compared_obj);
//--- Проверяет наличие объекта-индикатора с указанным id в списке
   bool                    CheckID(const int id);

public:

В публичной секции класса объявим метод, возвращающий указатель на объект пользовательского индикатора по его группе и параметрам, указанным в массиве MqlParam (в отличие от стандартных индикаторов мы можем указать параметры только посредством их передачи в таком массиве):

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

В блоке объявления методов создания индикаторов во-первых добавим в параметры каждого метода указание идентификатора индикатора, и во-вторых — добавим объявление метода для создания пользовательского индикатора. Приводить полный список объявления этих методов не будем, а ограничимся тремя методами (всё методы уже доработаны и их можно посмотреть в прилагаемых к статье файлах):

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

Во всех методах после входных параметров символа и таймфрейма было добавлено обязательное указание идентификатора индикатора при его создании.
В методе создания пользовательского индикатора дополнительно указывается группа индикатора и передаётся заранее созданный и заполненный массив параметров индикатора, по которым и будет создан пользовательский индикатор.

В конце листинга тела класса объявим метод для установки идентификатора указанному индикатору и метод, возвращающий объект-индикатор по указанному идентификатору:

//--- Устанавливает идентификатор указанному индикатору
   void                    SetID(CIndicatorDE *indicator,const int id);
   
//--- Возвращает объект-индикатор по его идентификатору
   CIndicatorDE           *GetIndByID(const uint id);

//--- Выводит в журнал (1) полное, (2) краткое описание коллекции
   void                    Print(void);
   void                    PrintShort(void);

//--- Конструктор
                           CIndicatorsCollection();

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

Теперь рассмотрим все объявленные методы.

В приватном методе создания нового объекта-индикатора добавим создание нового объекта-пользовательского индикатора:

//+------------------------------------------------------------------+
//| Создаёт новый объект-индикатор                                   |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Изменим метод, добавляющий в список-коллекцию новый объект-индикатор:

//+------------------------------------------------------------------+
//| Добавляет в список-коллекцию новый объект-индикатор              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id)
  {
//--- Если передан невалидный указатель на объект - возвращаем INVALID_HANDLE
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- Если такой индикатор уже есть в списке
   int index=this.Index(indicator);
   if(index!=WRONG_VALUE)
     {
      //--- Удаляем созданный ранее объект, получаем объект-индикатор из списка и возвращаем хэндл индикатора
      delete indicator;
      indicator=this.m_list.At(index);
     }
//--- Если объекта-индикатора ещё нет в списке
   else
     {
      //--- Если не удалось добавить объект-индикатор в список - выводим об этом сообщение,
      //--- удаляем объект и возвращаем INVALID_HANDLE
      if(!this.m_list.Add(indicator))
        {
         ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST));
         delete indicator;
         return INVALID_HANDLE;
        }
     }
//--- Если индикатор успешно добавлен в список, или уже есть в нём...
//--- Если в списке нет индикатора с указанным идентификатором (не -1) - устанавливаем идентификатор
   if(id>WRONG_VALUE && !this.CheckID(id))
      indicator.SetID(id);
//--- Возвращаем хэндл добавленного в список нового индикатора
   return indicator.Handle();
  }
//+------------------------------------------------------------------+

Теперь в метод передаётся и идентификатор индикатора, и если индикатора с таким идентификатором ещё нет в списке-коллекции, то объекту-индикатору устанавливается указанный идентификатор. В противном случае идентификатор индикатора будет установленным по умолчанию -1.

Теперь все методы создания объектов-индикаторов стали лаконичнее и короче.
Рассмотрим на примере создания объектов индикаторов AC и Alligator:

//+------------------------------------------------------------------+
//| Создаёт новый объект-индикатор Accelerator Oscillator            |
//| и помещает его в список-коллекцию                                |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
  {
//--- У индикатора AC нет параметров - обнуляем размер массива структур параметров
   ::ArrayResize(this.m_mql_param,0);
//--- Создаём объект-индикатор
   CIndicatorDE *indicator=this.CreateIndicator(IND_AC,this.m_mql_param,symbol,timeframe);
//--- Возвращаем хэндл индикатора, полученный в результате добавления объекта в список-коллекцию
   return this.AddIndicatorToList(indicator,id);
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Создаёт новый объект-индикатор Alligator                         |
//| и помещает его в список-коллекцию                                |
//+------------------------------------------------------------------+
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)
  {
//--- Записываем в массив структур параметров необходимые параметры индикатора
   ::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;
//--- Создаём объект-индикатор
   CIndicatorDE *indicator=this.CreateIndicator(IND_ALLIGATOR,this.m_mql_param,symbol,timeframe);
//--- Возвращаем хэндл индикатора, полученный в результате добавления объекта в список-коллекцию
   return this.AddIndicatorToList(indicator,id);
  }
//+------------------------------------------------------------------+

Теперь нам достаточно лишь заполнить структуру входных параметров, создать индикатор и вызвать метод добавления его в список-коллекцию. Такие изменения были проведены во всех методах создания объектов-индикаторов, и здесь их рассматривать не будем, за исключением метода для создания пользовательского индикатора:

//+------------------------------------------------------------------+
//| Создаёт новый объект-пользовательский индикатор                  |
//| и помещает его в список-коллекцию                                |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                        ENUM_INDICATOR_GROUP group,
                                        MqlParam &mql_param[])
  {
//--- Создаём объект-индикатор
   CIndicatorDE *indicator=this.CreateIndicator(IND_CUSTOM,mql_param,symbol,timeframe);
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- Устанавливаем группу для объекта-индикатора
   indicator.SetGroup(group);
//--- Возвращаем хэндл индикатора, полученный в результате добавления объекта в список-коллекцию
   return this.AddIndicatorToList(indicator,id);
  }
//+------------------------------------------------------------------+

Здесь чуть иначе. Здесь в метод создания помимо идентификатора передаётся и группа индикатора. А все параметры создаваемого индикатора сразу же передаются в массиве параметров MqlParam по причине, что мы не можем заранее знать о параметрах создаваемого пользовательского индикатора.

Абсолютно всем методам создания стандартных индикаторв были добавлены стандартные значения по умолчанию для каждого входного параметра. Таким образом, для создания стандартного индикатора с параметрами по умолчанию, достаточно будет указать символ, таймфрейм и идентификатор.

Реализация метода, возвращающего указатель на объект-пользовательский индикатор:

//+------------------------------------------------------------------+
//| Возвращает указатель на объект-пользовательский индикатор        |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Здесь: создаём временный объект индикатора для поиска точно такого же объекта в списке-коллекции, устанавливаем ему группу и получаем индекс найденного объекта в списке-коллекции. Затем удаляем временный объект и возвращаем либо указатель на объект по найденному индексу списка, либо NULL, если такой объект в списке найден не был.

Метод, проверяющий наличие объекта-индикатора с указанным id в списке:

//+------------------------------------------------------------------+
//| Проверяет наличие объекта-индикатора с указанным id в списке     |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::CheckID(const int id)
  {
   CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL);
   return(list!=NULL && list.Total()!=0);
  }
//+------------------------------------------------------------------+

Получаем список объектов-индикаторов с указанным идентификатором и возвращаем флаг проверки на валидный и непустой список (список должен содержать один объект).

Метод, устанавливающий идентификатор указанному индикатору:

//+------------------------------------------------------------------+
//| Устанавливает идентификатор указанному индикатору                |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

В метод передаётся указатель на объект-индикатор, которому необходимо установить идентификатор, переданный параметром в метод.

Если передан невалидный указатель — сообщаем об этом и выходим из метода.
Если значение идентификатора больше -1, то проверяем наличие объекта-индикатора с таким идентификатором, и если он уже есть — сообщаем об этом и выходим.
Если все проверки пройдены — устанавливаем объекту идентификатор. В случае, если переданное в метод значение идентификатора меньше ноля, то такой идентификатор устанавливается объекту без каких-либо проверок, и такой идентификатор объекта-индикатора обозначает его отсутствие.

Метод, возвращающий объект-индикатор по указанному идентификатору:

//+------------------------------------------------------------------+
//| Возвращает объект-индикатор по его идентификатору                |
//+------------------------------------------------------------------+
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));
  }
//+------------------------------------------------------------------+

Здесь: получаем список объектов-индикаторов с указанным идентификатором и возвращаем либо NULL (в случае, если список получить не удалось, либо список пустой), либо указатель на объект с нужным идентификатором. Так как объект с указанным идентификатором может быть только один, то здесь не важно какой индекс в полученном списке указывать — первый или последний. Здесь указываем последний.

Тестирование создания индикаторов в советнике показало одну проблему — при смене таймфрейма создаются дополнительные точно такие же индикаторы, но с другим таймфреймом. И всё верно — ведь индикаторы с одинаковыми входными параметрами, но рассчитанные на разных таймфреймах — это уже два разных индикатора. Чтобы просто избежать этой проблемы, достаточно очищать список созданных индикаторов при деинициализации программы. Для этой цели в главном объекте библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh объявим новый обработчик OnDeinit():

//--- (1) Таймер, обработчик события (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);

И за пределами тела класса напишем его реализацию:

//+------------------------------------------------------------------+
//| Деинициализация библиотеки                                       |
//+------------------------------------------------------------------+
void CEngine::OnDeinit(void)
  {
   this.m_indicators.GetList().Clear();
  }
//+------------------------------------------------------------------+

Данный метод класса будет вызываться из обработчика OnDeinit() программы. Всё, что здесь делается — это вызывается метод очистки списка-коллекции у объекта-коллекции индикаторов.

Тест

Для тестирования создания разных индикаторов и получения данных от созданных объектов-индикаторов возьмём советник из прошлой статьи.

Сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part56\ под новым именем TestDoEasyPart56.mq5.

В советнике создадим два пользовательских индикатора Moving Average, но с разными параметрами (индикаторы возьмём в папке примеров индикаторов из стандартной поставки терминала \MQL5\Indicators\Examples\). И создадим два стандартных индикатора Adaptive Moving Average тоже с разными входными параметрами.

В глобальной области зададим макроподстановки для упрощения обращения к индикаторам по их идентификатору и объявим два массива параметров для создания двух пользовательских индикаторов:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart56.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/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;
//--- Массивы параметров пользовательских индикаторов
MqlParam       param_ma1[];
MqlParam       param_ma2[];
//+------------------------------------------------------------------+

По сути, объявленные макроподстановки являются описанием числовых значений идентификаторов. Просто по имени будет проще обращаться к идентификатору индикатора, чем по его значению.

В обработчике OnInit() советника создадим все четыре индикатора и сразу же выведем в журнал данные всех созданных индикаторов:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();

//--- Установка глобальных переменных советника
   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);
//--- Инициализация случайных номеров групп
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();
   
//--- Создание индикаторов
   ArrayResize(param_ma1,4);
   //--- Имя индикатора 1
   param_ma1[0].type=TYPE_STRING;
   param_ma1[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Период расчёта
   param_ma1[1].type=TYPE_INT;
   param_ma1[1].integer_value=13;
   //--- Горизонтальное смещение
   param_ma1[2].type=TYPE_INT;
   param_ma1[2].integer_value=0;
   //--- Метод сглаживания
   param_ma1[3].type=TYPE_INT;
   param_ma1[3].integer_value=MODE_SMA;
   //--- Создаём индикатор 1
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,INDICATOR_GROUP_TREND,param_ma1);
   
   ArrayResize(param_ma2,5);
   //--- Имя индикатора 2
   param_ma2[0].type=TYPE_STRING;
   param_ma2[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Период расчёта
   param_ma2[1].type=TYPE_INT;
   param_ma2[1].integer_value=13;
   //--- Горизонтальное смещение
   param_ma2[2].type=TYPE_INT;
   param_ma2[2].integer_value=0;
   //--- Метод сглаживания
   param_ma2[3].type=TYPE_INT;
   param_ma2[3].integer_value=MODE_SMA;
   //--- Цена расчёта
   param_ma2[4].type=TYPE_INT;
   param_ma2[4].integer_value=PRICE_OPEN;
   //--- Создаём индикатор 2
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,INDICATOR_GROUP_TREND,param_ma2);
   
   //--- Создаём индикатор 3
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA1);
   //--- Создаём индикатор 4
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA2,14);
   
   //--- Выводим описания созданных индикаторов
   engine.GetIndicatorsCollection().Print();
   engine.GetIndicatorsCollection().PrintShort();
   
//--- Проверка и удаление неудалённых графических объектов советника
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Создание панели кнопок
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Установка состояния кнопки активизации трейлингов
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- Сброс состояний кнопок активизации работы отложенными запросами
   for(int i=0;i<14;i++)
     {
      ButtonState(butt_data[i].name+"_PRICE",false);
      ButtonState(butt_data[i].name+"_TIME",false);
     }

//--- Проверка воспроизведения стандартного звука по макроподстановке и пользовательского звука по описанию
   engine.PlaySoundByDescription(SND_OK);
//--- Ждём 600 милисекунд
   engine.Pause(600);
   engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"));

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

Второму пользовательскому индикатору МА установим цену расчёта PRICE_OPEN. Если цена расчёта явно не указывается, то по умолчанию (в первом индикаторе МА) для расчёта индикатора используется цена PRICE_CLOSE.

При создании индикаторов АМА, первому из них устанавливается период расчёта 9 (заданный по умолчанию), а второму явно задаём значение 14.
Таким образом, все четыре создаваемых индикатора у нас имеют разные входные значения своих парамеров.

В обработчике OnDeinit() советника вызовем обработчик OnDeinit() библиотеки:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаление графических объектов советника по префиксу имени объектов
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Деинициализация библиотеки
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+

Это очистит список-коллекцию индикаторов во время деинициализации советника при переключении таймфреймов, что избавит нас от создания ненужных дополнительных объектов-индикаторов.

В обработчике OnTick() советника получим доступ к каждому из созданных объектов-индикаторов и выведем данные текущего бара каждого индикатора, отобразив их в комментарии на графике:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Обработка события NewTick в библиотеке
   engine.OnTick(rates_data);

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      engine.EventsHandling();      // Работа с событиями
     }
//--- Получаем объекты-пользовательские индикаторы
   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(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
  }
//+------------------------------------------------------------------+

В прошлой статье мы временно создавали объекты-индикаторы в функции инициализации библиотеки OnInitDoEasy(). Удалим эти строки из функции:

//--- Создание таймсерий всех используемых символов
   engine.SeriesCreateAll(array_used_periods);
//--- Проверка созданных таймсерий - выводим в журнал описания всех созданных таймсерий
//--- (true - только созданные, false - созданные и объявленные)
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания
   //engine.GetTimeSeriesCollection().Print(true);      // Полные описания

//--- Создание индикаторов
   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();

//--- Создание тестовых файлов ресурсов

Скомпилируем советник и запустим его на графике, предварительно задав в настройках использовать только текущие символ и таймфрейм.
В журнал будут выведены описания параметров всех созданных индикаторов:

--- Инициализация библиотеки "DoEasy" ---
Работа только с текущим символом: "EURUSD"
Работа только с текущим таймфреймом: H1
Таймсерия символа EURUSD: 
- Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6284
Время инициализации библиотеки: 00:00:00.141
============= Начало списка параметров: "Пользовательский индикатор" =============
Статус индикатора: Пользовательский индикатор
Тип индикатора: CUSTOM
Таймфрейм индикатора: H1
Хэндл индикатора: 10
Группа индикатора: Трендовый индикатор
Идентификатор индикатора: 1
------
Пустое значение для построения, для которого нет отрисовки: EMPTY_VALUE
------
Символ индикатора: EURUSD
Имя индикатора: "Examples\Custom Moving Average.ex5"
Короткое имя индикатора: "Examples\Custom Moving Average.ex5(EURUSD,H1)"
 --- Параметры индикатора --- 
 - [1] Тип int: 13
 - [2] Тип int: 0
 - [3] Тип int: 0
================== Конец списка параметров: "Пользовательский индикатор" ==================
 
============= Начало списка параметров: "Пользовательский индикатор" =============
Статус индикатора: Пользовательский индикатор
Тип индикатора: CUSTOM
Таймфрейм индикатора: H1
Хэндл индикатора: 11
Группа индикатора: Трендовый индикатор
Идентификатор индикатора: 2
------
Пустое значение для построения, для которого нет отрисовки: EMPTY_VALUE
------
Символ индикатора: EURUSD
Имя индикатора: "Examples\Custom Moving Average.ex5"
Короткое имя индикатора: "Examples\Custom Moving Average.ex5(EURUSD,H1)"
 --- Параметры индикатора --- 
 - [1] Тип int: 13
 - [2] Тип int: 0
 - [3] Тип int: 0
 - [4] Тип int: 2
================== Конец списка параметров: "Пользовательский индикатор" ==================
 
============= Начало списка параметров: "Стандартный индикатор" =============
Статус индикатора: Стандартный индикатор
Тип индикатора: AMA
Таймфрейм индикатора: H1
Хэндл индикатора: 12
Группа индикатора: Трендовый индикатор
Идентификатор индикатора: 3
------
Пустое значение для построения, для которого нет отрисовки: EMPTY_VALUE
------
Символ индикатора: EURUSD
Имя индикатора: "Adaptive Moving Average"
Короткое имя индикатора: "AMA(EURUSD,H1)"
 --- Параметры индикатора --- 
 - Период усреднения: 9
 - Период быстрой скользящей: 2
 - Период медленной скользящей: 30
 - Смещение индикатора по горизонтали: 0
 - Тип цены или handle: CLOSE
================== Конец списка параметров: "Стандартный индикатор" ==================
 
============= Начало списка параметров: "Стандартный индикатор" =============
Статус индикатора: Стандартный индикатор
Тип индикатора: AMA
Таймфрейм индикатора: H1
Хэндл индикатора: 13
Группа индикатора: Трендовый индикатор
Идентификатор индикатора: 4
------
Пустое значение для построения, для которого нет отрисовки: EMPTY_VALUE
------
Символ индикатора: EURUSD
Имя индикатора: "Adaptive Moving Average"
Короткое имя индикатора: "AMA(EURUSD,H1)"
 --- Параметры индикатора --- 
 - Период усреднения: 14
 - Период быстрой скользящей: 2
 - Период медленной скользящей: 30
 - Смещение индикатора по горизонтали: 0
 - Тип цены или handle: CLOSE
================== Конец списка параметров: "Стандартный индикатор" ==================

Пользовательский индикатор Examples\Custom Moving Average.ex5 EURUSD H1 [handle 10, id #1]
Пользовательский индикатор Examples\Custom Moving Average.ex5 EURUSD H1 [handle 11, id #2]
Стандартный индикатор Adaptive Moving Average EURUSD H1 [handle 12, id #3]
Стандартный индикатор Adaptive Moving Average EURUSD H1 [handle 13, id #4]

На графике же символа будут выведены данные из буферов всех созданных индикаторов:

На график можно накинуть нужные индикаторы, соответствующие по параметрам созданным в советнике, и проверить совпадение данных индикаторов в комментарии на графике и в окне данных — они будут соответствовать.

Что дальше

В следующей статье продолжим создавать функционал для работы с индикаторами в советниках. В ближайших статьях планируется создание привязки данных индикаторов к барам классов таймсерий и получение данных от индикаторов для различных статистических исследований.

Ниже прикреплены все файлы текущей версии библиотеки и файл тестового советника для MQL5. Их можно скачать и протестировать всё самостоятельно.
Хочу отметить, что класс-коллекция индикаторов на данный момент находится в процессе разработки, поэтому  крайне не рекомендуется использовать его для использования в своих программах.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.

К содержанию

Статьи этой серии:

Работа с таймсериями в библиотеке DoEasy (Часть 35): Объект "Бар" и список-таймсерия символа
Работа с таймсериями в библиотеке DoEasy (Часть 36): Объект таймсерий всех используемых периодов символа
Работа с таймсериями в библиотеке DoEasy (Часть 37): Коллекция таймсерий - база данных таймсерий по символам и периодам
Работа с таймсериями в библиотеке DoEasy (Часть 38): Коллекция таймсерий - реалтайм обновление и доступ к данным из программы
Работа с таймсериями в библиотеке DoEasy (Часть 39): Индикаторы на основе библиотеки - подготовка данных и события таймсерий
Работа с таймсериями в библиотеке DoEasy (Часть 40): Индикаторы на основе библиотеки - реалтайм обновление данных
Работа с таймсериями в библиотеке DoEasy (Часть 41): Пример мультисимвольного мультипериодного индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 42): Класс объекта абстрактного индикаторного буфера
Работа с таймсериями в библиотеке DoEasy (Часть 43): Классы объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 44): Класс-коллекция объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 45): Мультипериодные индикаторные буферы
Работа с таймсериями в библиотеке DoEasy (Часть 46): Мультипериодные, мультисимвольные индикаторные буферы
Работа с таймсериями в библиотеке DoEasy (Часть 47): Мультипериодные мультисимвольные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 48): Мультипериодные мультисимвольные индикаторы на одном буфере в подокне
Работа с таймсериями в библиотеке DoEasy (Часть 49): Мультипериодные мультисимвольные многобуферные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 50): Мультипериодные мультисимвольные стандартные индикаторы со смещением
Работа с таймсериями в библиотеке DoEasy (Часть 51): Составные мультипериодные мультисимвольные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 52): Кроссплатформенность мультипериодных мультисимвольных однобуферных стандартных индикаторов
Работа с таймсериями в библиотеке DoEasy (Часть 53): Класс абстрактного базового индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 54): Классы-наследники абстрактного базового индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 55): Класс-коллекция индикаторов