Работа с таймсериями в библиотеке DoEasy (Часть 55): Класс-коллекция индикаторов

22 октября 2020, 07:59
Artyom Trishkin
0
1 353

Содержание


Концепция

Сегодня доведём до логического завершения классы-наследники базового объекта абстрактного индикатора, которые мы начали делать в прошлой статье.
Следуя общей концепции построения объектов библиотеки, и чтобы организация объектов индикаторов не отличалась от других объектов библиотеки, нам необходимо добавить в объекты-индикаторы их описание. Заодно доработаем хранение этих объектов в их коллекции — исправим недочёты при создании коллекции индикаторов и добавлении объектов в коллекцию, допущенные нами в прошлой статье.

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

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


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

По уже сложившейся традиции при разработке классов объектов библиотеки, мы в первую очередь добавляем нужные текстовые сообщения, использующиеся объектами при выводе их описаний. Для объектов-индикаторов нам необходимы сообщения для отображения всех возможных параметров всех стандартных индикаторов.
В файл\MQL5\Include\DoEasy\Data.mqh  добавим индексы новых сообщений:

   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,            // Период фактора сглаживания
   
//--- CIndicatorsCollection
   MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,                // Ошибка. Не удалось добавить объект-индикатор в список
   
  };
//+------------------------------------------------------------------+

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

   {"Пустое значение для построения, для которого нет отрисовки","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"},
   
  };
//+---------------------------------------------------------------------+

Для вывода некоторых параметров индикаторов, таких как метод усреднения, тип цены и объёма для расчёта, и т. д., напишем отдельные функции в файле сервисных функций библиотеки в \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//| Возвращает описание таймфрейма                                   |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return StringSubstr(EnumToString((timeframe>PERIOD_CURRENT ? timeframe : (ENUM_TIMEFRAMES)Period())),7);
  }
//+------------------------------------------------------------------+
//| Возвращает описание объема для расчета                           |
//+------------------------------------------------------------------+
string AppliedVolumeDescription(const ENUM_APPLIED_VOLUME volume)
  {
   return StringSubstr(EnumToString(volume),7);
  }
//+------------------------------------------------------------------+
//| Возвращает описание типа индикатора                              |
//+------------------------------------------------------------------+
string IndicatorTypeDescription(const ENUM_INDICATOR indicator)
  {
   return StringSubstr(EnumToString(indicator),4);
  }
//+------------------------------------------------------------------+
//| Возвращает описание метода усреднения                            |
//+------------------------------------------------------------------+
string AveragingMethodDescription(const ENUM_MA_METHOD method)
  {
   return StringSubstr(EnumToString(method),5);
  }
//+------------------------------------------------------------------+
//| Возвращает описание используемой цены                            |
//+------------------------------------------------------------------+
string AppliedPriceDescription(const ENUM_APPLIED_PRICE price)
  {
   return StringSubstr(EnumToString(price),6);
  }
//+------------------------------------------------------------------+
//| Возвращает описание цены расчёта стохастика                      |
//+------------------------------------------------------------------+
string StochPriceDescription(const ENUM_STO_PRICE price)
  {
   return StringSubstr(EnumToString(price),4);
  }
//+------------------------------------------------------------------+

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

Каждый индикатор имеет свой определённый набор параметров. Эти параметры могут быть установлены индикатору при помощи массива структур параметров индикатора MqlParam, что мы и делаем при создании каждого объекта-индикатора. Соответственно, мы можем для каждого индикатора распечатать в журнал все значения массива этих структур. Для разных индикаторов в каждой ячейке массива будут находиться данные о значении параметров, присущих только данному типу индикатора. Но для нескольких индикаторов одного и того же типа в каждой ячейке массива будут указаны одинаковые по назначению свойства, отличающиеся лишь своим значением.
Таким образом, для каждого индикатора мы можем написать метод, который будет выводить в журнал набор параметров индикатора с установленными для них значениями. Справедливо это только для стандартных индикаторов, так как для них мы точно знаем набор параметров каждого отдельно взятого индикатора.

Это будет виртуальный метод. Напишем его в классе объекта абстрактного индикатора в файле \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh:

//--- Выводит в журнал описание свойств объекта-индикатора (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал (1) краткое описание, (2) описание параметров объекта-индикатора (реализация в наследниках)
   virtual void      PrintShort(void) {;}
   virtual void      PrintParameters(void) {;}

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

В данном классе этот метод ничего не делает. Реализация же метода вывода в журнал данных индикатора будет выполнена в объектах-наследниках.
В каждом классе-наследнике будет собственный метод, так как каждому индикатору присущ свой набор параметров.

В закрытом параметрическом конструкторе заменим строку получения описания типа индикатора

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

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

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
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_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;
  }
//+------------------------------------------------------------------+

А в методе, выводящем в журнал свойства индикатора, в самом конце листинга — после вывода всех свойств объекта-индикатора, допишем вызов метода, выводящего в журнал набор параметров индикатора с установленными для них значениями:

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

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

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

Класс объекта-индикатора Accelerator Oscillator:

//+------------------------------------------------------------------+
//|                                                        IndAC.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"
//+------------------------------------------------------------------+
//| Стандартный индикатор Accelerator Oscillator                     |
//+------------------------------------------------------------------+
class CIndAC : public CIndicatorDE
  {
private:

public:
   //--- Конструктор
                     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) {}
   //--- Поддерживаемые свойства индикатора (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 CIndAC::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если индикатор поддерживает переданное        |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CIndAC::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта-индикатора             |
//+------------------------------------------------------------------+
void CIndAC::PrintShort(void)
  {
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [",this.Handle(),"]");
  }
//+------------------------------------------------------------------+

Здесь у нас лишь объявлен виртуальный метод для вывода параметров, и он пустой — так как у индикатора AC нет входных параметров. Можно было его вообще здесь не писать, так как при отсутствии виртуального метода в классе-наследнике, будет вызываться виртуальный метод класса-родителя. Но мы его здесь написали лишь для того, чтобы все классы-наследники имели одинаковую структуру своих методов.
В методе вывода короткого описания индикатора добавим вывод хэндла созданного индикатора для этого объекта. Такие изменения в этом методе сделаны для всех объектов-индикаторов, и рассматривать их далее не будем, а ограничимся только методами вывода описания параметров индикатора.

Класс объекта-индикатора Accumulation/Distribution и его метод вывода описания параметров индикатора:

//+------------------------------------------------------------------+
//|                                                        IndAD.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"
//+------------------------------------------------------------------+
//| Стандартный индикатор Accumulation/Distribution                  |
//+------------------------------------------------------------------+
class CIndAD : public CIndicatorDE
  {
private:

public:
   //--- Конструктор
                     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) {}
   //--- Поддерживаемые свойства индикатора (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 CIndAD::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если индикатор поддерживает переданное        |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CIndAD::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта-индикатора             |
//+------------------------------------------------------------------+
void CIndAD::PrintShort(void)
  {
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [",this.Handle(),"]");
  }
//+------------------------------------------------------------------+
//| Выводит в журнал описание параметров объекта-индикатора          |
//+------------------------------------------------------------------+
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));
  }
//+------------------------------------------------------------------+

У индикатора Accumulation/Distribution всего один входной параметр — тип объёма для расчёта. Поэтому массив структур входных параметров имеет всего одну ячейку, в которой хранится это параметр. Соответственно, чтобы его вывести в журнал, мы его получаем из массива по индексу 0 из целочисленных данных структуры и распечатываем в журнал при помощи сервисной функции, написанной нами выше. Вывод значения параметра предваряем его описанием из ранее написанных текстовых сообщений библиотеки.

Далее будем рассматривать только методы вывода описания входных параметров индикаторов.

Метод вывода описания параметров индикатора класса объекта-индикатора Average Directional Index:

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

У индикатора ADX один входной параметр — период расчёта, поэтому точно так же выводим описание всего одной ячейки массива по индексу 0 из целочисленных данных структуры.

Метод вывода описания параметров индикатора класса объекта-индикатора Alligator:

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

Индикатор Alligator имеет восемь входных параметров, поэтому выводим их поочерёдно в соответствии с порядком их следования при создании индикатора:

  • Период расчёта линии "Челюсти" — индекс в массиве 0
  • Смещение по горизонтали линии "Челюсти" — индекс в массиве 1
  • Период расчёта линии "Зубы" — индекс в массиве 2
  • Смещение по горизонтали линии "Зубы" — индекс в массиве 3
  • Период расчёта линии "Губы" — индекс в массиве 4
  • Смещение по горизонтали линии "Губы" — индекс в массиве 5
  • Тип сглаживания — индекс в массиве 6
  • Тип цены или хэндл индикатора — индекс в массиве 7

Все данные имеют целочисленный тип, поэтому выводим описание целочисленных данных структуры из соответствующих ячеек массива.
Последний параметр может хранить либо тип цены, по которой строится индикатор, либо же хэндл индикатора, на данных которого строится Alligator. Поэтому сначала проверяем значение в ячейке массива, и если значение меньше 10, то это означает, что индикатор построен на одном из возможных типов цены, и выводим описание типа цены. Если же значение 10 и выше — значит индикатор построен на данных другого индикатора, хэндл которого и записан в массиве — выводим значение хэндла этого индикатора.

Метод вывода описания параметров индикатора класса объекта-индикатора Envelopes:

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

Индикатор Envelopes имеет пять входных параметров. Последний параметр "Отклонение границ от средней линии" имеет вещественный тип, поэтому для вывода описания этого параметра, берём значение из вещественных данных структуры входных параметров.

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

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

Внесём некоторые правки в класс коллекции объектов-индикаторов в файле \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh, который мы начали делать в прошлой статье. Для поиска и сравнения двух объектов-индикаторов мы использовали метод Search() класса динамического массива указателей на экземпляры класса CObject и его наследников из поставки стандартной библиотеки. Но этот метод не может точно определить равенство двух объектов, в состав которых входят структуры. Данный метод предназначен для сравнения одного указанного свойства у двух однотипных объектов. А мы в объектах-индикаторах активно используем массив структур параметров индикатора MqlParam, в которых необходимо делать поэлементное сравнение каждого свойства структуры в массиве. К счастью, мы во всех объектах библиотеки по умолчанию имеем метод IsEqual() для точного сравнения двух однотипных объектов. Этим методом и воспользуемся для сравнения двух однотипных объектов на равенство.

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

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

//--- Создаёт новый объект-индикатор
   CIndicatorDE           *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT);
//--- Возвращает индекс индикатора в списке
   int                     Index(CIndicatorDE *compared_obj);

public:

В самом конце листинга тела класса объявим два публичных метода — для вывода полных и кратких описаний объектов-индикаторов, находящихся в списке коллекции:

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

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

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

За пределами тела класса напишем реализацию объявленных методов.

Метод, выводящий в журнал полное описание коллекции:

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

В цикле по списку-коллекции получаем очередной объект-индикатор и выводим в журнал его полное описание.

Метод, выводящий в журнал краткое описание коллекции:

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

В цикле по списку-коллекции получаем очередной объект-индикатор и выводим в журнал его краткое описание.

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

//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

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

Во всех методах создания новых объектов-индикаторов и помещения их в список-коллекцию были внесены идентичные для каждого метода правки для исключения возможных утечек памяти при неудачном создании объекта или неудачном размещении его в список.

Рассмотрим на примере метода создания индикатора Accelerator Oscillator:

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

Проверку на наличие объекта-индикатора в списке делаем по его индексу.
Если индекс больше -1
, значит, он есть в списке и вновь созданный объект нужно удалить.
Если такого индикатора нет в списке и его по какой-либо причине не удалось добавить в список-коллекцию
, то опять-таки нужно удалить вновь созданный объект.
Это избавит нас от утечек памяти при неудачном размещении нового объекта в список-коллекцию.

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

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

В прошлой статье нами был написан один такой метод для получения указателя на индикатор Accelerator Oscillator. Он самый простой, так как у индикатора AC нет входных параметров, и всё, что требуется — это найти нужный объект по символу и таймфрейму:

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

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

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

Если такой объект найден в списке, то возвращается его индекс, если нет — возвращается -1.

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

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

Всё остальное — идентично вышерассмотренному методу возврата указателя на объект-индикатора Accumulation/Distribution.
В каждом из методов обязательно удаляется временный объект-индикатор, служащий эталоном для поиска совпадения в списке-коллекции.

Остальные подобные методы здесь рассматривать не будем — они идентичны двум только что рассмотренным.

На этом доработка классов в рамках данной статьи завершена.


Тестовый советник

Для тестирования создания индикаторов в советниках возьмём тестовый советник из статьи 39
и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part55\ под новым именем TestDoEasyPart55.mq5.

В основном доработки будут косметическими. В одной из прошлых статей мы перенесли функцию для работы с событиями в тестере EventsHandling() в библиотеку — в файл Engine.mqh. Поэтому из кода советника удалим эту функцию и в обработчике OnTick() заменим её вызов из файла советника

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      EventsHandling();             // Работа с событиями
     }

на вызов из библиотеки:

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      engine.EventsHandling();      // Работа с событиями
     }

Удалим блок кода из обработчика OnTick(), выводящего на график комментарий с данными по текущему бару:

//--- Получим нулевой бар текущей таймсерии
   CBar *bar=engine.SeriesGetBar(NULL,PERIOD_CURRENT,0);
   if(bar==NULL)
      return;
//--- Создаём строку параметров текущего бара такую же,
//--- какая выводится описанием от объекта-бара:
//--- 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)
     );
//--- Выводим в комментарий на графике первой строкой данные, полученные от объекта-бара,
//--- и второй строкой - при помощи методов получения ценовых данных таймсерии
   Comment(bar.Header(),": ",bar.ParameterDescription(),"\n",parameters);

Таким образом, обработчик события "новый тик" примет такой вид:

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

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      engine.EventsHandling();      // Работа с событиями
     }

//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
  }
//+------------------------------------------------------------------+

В функции инициализации библиотеки OnInitDoEasy() в блоке вывода списка используемых символов заменим количество символов, заданное числом

//--- Вывод списка используемых символов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#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   

на количество символов, указанное в макроподстановке:

//--- Вывод списка используемых символов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#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   

так как в MetaTrader 5, начиная с версии 2430, изменилось общее количество рабочих символов, и это количество проверяется библиотекой и устанавливается автоматически в макроподстановку SYMBOLS_COMMON_TOTAL, объявленную в файле \MQL5\Include\DoEasy\Defines.mqh.

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

//+------------------------------------------------------------------+
//| Инициализация библиотеки DoEasy                                  |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Проверка на выбор работы с полным списком
   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="\nThe number of symbols on server "+(string)total+".\nMaximal 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;
        }
     }
//--- Установим начало отсчёта счётчика для замера примерного времени инициализации библиотеки
   ulong begin=GetTickCount();
   Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---"));
//--- Заполнение массива используемых символов
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols);
//--- Установка типа используемого списка символов в коллекции символов и заполнение списка таймсерий символов
   engine.SetUsedSymbols(array_used_symbols);
//--- Отображение в журнале выбранного режима работы с коллекцией объектов-символов
   string num=
     (
      used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : 
      TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(string)engine.GetSymbolsCollectionTotal()
     );
   Print(engine.ModeSymbolsListDescription(),num);
//--- Вывод списка используемых символов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#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   
//--- Установка используемых таймфреймов
   CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods);
//--- Отображение выбранного режима работы с коллекцией объектов-таймсерий
   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);
//--- Вывод списка используемых таймфреймов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#ifdef __MQL5__
   if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT)
      ArrayPrint(array_used_periods);
#endif 
//--- Создание таймсерий всех используемых символов
   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();

//--- Создание тестовых файлов ресурсов
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Sound fallen coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Sound of coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Click on the button sound 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Click on the button sound 1"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Click on the button sound 1"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","The sound of the 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);

//--- Передача в основной класс библиотеки всех имеющихся коллекций
   engine.CollectionOnInit();

//--- Установка магика по умолчанию для всех используемых инструментов
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Установка синхронной передачи приказов для всех используемых символов
   engine.TradingSetAsyncMode(false);
//--- Установка количества торговых попыток при ошибке
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Установка корректных типов истечения и заливки ордера всем торговым объектам
   engine.TradingSetCorrectTypeExpiration();
   engine.TradingSetCorrectTypeFilling();

//--- Установка стандартных звуков торговым объектам всех используемых символов
   engine.SetSoundsStandart();
//--- Установка общего флага использования звуков
   engine.SetUseSounds(InpUseSounds);
//--- Установка множителя спреда торговым объектам символов в коллекции символов
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Установка контрольных значений для символов
   //--- Получаем список всех символов коллекции
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- В цикле по списку устанавливаем нужные значения для отслеживаемых свойств символов
      //--- По умолчанию всем свойствам установлены значения LONG_MAX, что означает "Не отслеживать данное свойство" 
      //--- Включить или выключить (задать величину меньше LONG_MAX или наоборот - установить значение LONG_MAX) можно в любое время в любом месте программы
      /*
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Установка контроля увеличения цены символа на 100 пунктов
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Установка контроля уменьшения цены символа на 100 пунктов
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Установка контроля увеличения спреда символа на 40 пунктов
         symbol.SetControlSpreadInc(400);
         //--- Установка контроля уменьшения спреда символа на 40 пунктов
         symbol.SetControlSpreadDec(400);
         //--- Установка контроля размера спреда по значению 40 пунктов
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Установка контрольных значений для текущего аккаунта
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Установка контроля увеличения значения прибыли на 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Установка контроля увеличения значения средств на 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Установка контрольного уровня прибыли на 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
//--- Получим конец отсчёта времени инициализации библиотеки и выведем его в журнал
   ulong end=GetTickCount();
   Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS));
  }
//+------------------------------------------------------------------+

Здесь мы создали два индикатора Adaptive Moving Average, рассчитываемых на текущих символе и таймфрейме, но с разными значениями входных параметров.

Скомпилируем советник и запустим его на графике в терминале.
После его инициализации в журнал "Эксперты" будут выведены сообщения библиотеки об инициализации, и в их числе полная и краткая распечатки параметров двух созданных индикаторов:

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

Хоть тип индикатора и один — AMA, но создано два хэндла этого индикатора, так как параметры создаваемых индикаторов были разными, и поэтому это два разных индикатора — каждый со своим хэндлом. Соответственно, были созданы и два объекта-индикатора и размещены в коллекцию индикаторов.

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

Что дальше

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

Ниже прикреплены все файлы текущей версии библиотеки и файл тестового советника для 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): Классы-наследники абстрактного базового индикатора

Прикрепленные файлы |
MQL5.zip (3893.48 KB)
Набор инструментов для ручной разметки графиков и торговли (Часть II). Рисование разметки Набор инструментов для ручной разметки графиков и торговли (Часть II). Рисование разметки

Статья продолжает цикл, в котором я показываю, как создавал удобную для меня библиотеку для ручной разметки графиков с помощью сочетаний клавиш. Разметка происходит прямыми линиями и их комбинациями. В этой части рассказано непосредственно о самом рисовании с помощью функций, описанных в первой части. Библиотеку можно подключить к любому эксперту или индикатору, существенно облегчив себе задачи разметки. Данное решение НЕ ИСПОЛЬЗУЕТ внешних dll, все команды реализованы с помощью встроенных средств языка MQL.

Нейросети — это просто (Часть 6): Эксперименты с коэффициентом обучения нейронной сети Нейросети — это просто (Часть 6): Эксперименты с коэффициентом обучения нейронной сети

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

Оптимальный подход к разработке и анализу торговых систем Оптимальный подход к разработке и анализу торговых систем

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

Практическое применение нейросетей в трейдинге. Python (Часть I) Практическое применение нейросетей в трейдинге. Python (Часть I)

В данной статье мы поэтапно разберем вариант реализации торговой системы на основе программирования глубоких нейронных сетей на Python. Для этого мы используем библиотеку машинного обучения TensorFlow, разработанной компанией Google. А для описания нейронных сетей используем библиотеку Keras.