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

Artyom Trishkin | 25 сентября, 2020

Содержание


Концепция

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

Каждый из создаваемых объектов-индикаторов, помимо принадлежности по статусу (стандартный и пользовательский), будет иметь принадлежность по типу (группы) индикатора:

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

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

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

//--- CBuffer
//--- удалено для экономии места
//--- ...
//--- ...
//--- ...
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_SOLID,              // Сплошная линия
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASH,               // Прерывистая линия
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DOT,                // Пунктирная линия
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOT,            // Штрих-пунктирная линия
   MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOTDOT,         // Штрих - две точки
   
//--- CIndicatorDE
   MSG_LIB_TEXT_IND_TEXT_STATUS,                      // Статус индикатора
   MSG_LIB_TEXT_IND_TEXT_STATUS_STANDART,             // Стандартный индикатор
   MSG_LIB_TEXT_IND_TEXT_STATUS_CUSTOM,               // Пользовательский индикатор
   
   MSG_LIB_TEXT_IND_TEXT_TIMEFRAME,                   // Таймфрейм индикатора
   MSG_LIB_TEXT_IND_TEXT_HANDLE,                      // Хэндл индикатора

   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_EMPTY_VALUE,                 // Пустое значение для построения, для которого нет отрисовки
   MSG_LIB_TEXT_IND_TEXT_SYMBOL,                      // Символ индикатора
   MSG_LIB_TEXT_IND_TEXT_NAME,                        // Имя индикатора
   MSG_LIB_TEXT_IND_TEXT_SHORTNAME,                   // Короткое имя индикатора
   
  };
//+------------------------------------------------------------------+

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

   {"Сплошная линия","Solid line"},
   {"Прерывистая линия","Broken line"},
   {"Пунктирная линия","Dotted line"},
   {"Штрих-пунктирная линия","Dash-dot line"},
   {"Штрих - две точки","Dash - two points"},
   
   {"Статус индикатора","Indicator status"},
   {"Стандартный индикатор","Standard indicator"},
   {"Пользовательский индикатор","Custom indicator"},
   {"Таймфрейм индикатора","Indicator timeframe"},
   {"Хэндл индикатора","Indicator handle"},
   {"Группа индикатора","Indicator group"},
   {"Трендовый индикатор","Trend indicator"},
   {"Осциллятор","Solid lineOscillator"},
   {"Объёмы","Volumes"},
   {"Стрелочный индикатор","Arrow indicator"},
   {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"},
   {"Символ индикатора","Indicator symbol"},
   {"Имя индикатора","Indicator name"},
   {"Короткое имя индикатора","Indicator shortname"},
   
  };
//+---------------------------------------------------------------------+

В файле E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Defines.mqh пропишем уже ставшими стандартными для библиотечных объектов параметры объекта-индикатора.

Так как все эти объекты в итоге будут храниться в списке-коллекции объектов-индикаторов, то введём для них свой собственный идентификатор:

//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID          (0x777A)                   // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID           (0x777B)                   // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID           (0x777C)                   // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Идентификатор списка коллекции аккаунтов
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Идентификатор списка коллекции символов
#define COLLECTION_SERIES_ID           (0x777F)                   // Идентификатор списка коллекции таймсерий
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Идентификатор списка коллекции индикаторных буферов
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Идентификатор списка коллекции индикаторов

//--- Параметры данных для файловых операций

И в самом конце листинга файла пропишем все необходимые перечисления свойств и критериев сортировки объектов-индикаторов в списке-коллекции:

//+------------------------------------------------------------------+
//| Данные для работы с индикаторами                                 |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Статус абстрактного индикатора                                   |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_STATUS
  {
   INDICATOR_STATUS_STANDART,                               // Стандартный индикатор
   INDICATOR_STATUS_CUSTOM,                                 // Пользовательский индикатор
  };
//+------------------------------------------------------------------+
//| Группа индикатора                                                |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_GROUP
  {
   INDICATOR_GROUP_TREND,                                   // Трендовый индикатор
   INDICATOR_GROUP_OSCILLATOR,                              // Осциллятор
   INDICATOR_GROUP_VOLUMES,                                 // Объёмы
   INDICATOR_GROUP_ARROWS,                                  // Стрелочный индикатор
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства индикатора                                |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_INTEGER
  {
   INDICATOR_PROP_STATUS = 0,                               // Статус индикатора (из перечисления ENUM_INDICATOR_STATUS)
   INDICATOR_PROP_TIMEFRAME,                                // Таймфрейм индикатора
   INDICATOR_PROP_HANDLE,                                   // Хэндл индикатора
   INDICATOR_PROP_GROUP,                                    // Группа индикатора
  }; 
#define INDICATOR_PROP_INTEGER_TOTAL (4)                    // Общее количество целочисленных свойств индикатора
#define INDICATOR_PROP_INTEGER_SKIP  (0)                    // Количество неиспользуемых в сортировке свойств индикатора
//+------------------------------------------------------------------+
//| Вещественные свойства индикатора                                 |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_DOUBLE
  {
   INDICATOR_PROP_EMPTY_VALUE = INDICATOR_PROP_INTEGER_TOTAL,// Пустое значение для построения, для которого нет отрисовки
  }; 
#define INDICATOR_PROP_DOUBLE_TOTAL  (1)                    // Общее количество вещественных свойств индикатора
#define INDICATOR_PROP_DOUBLE_SKIP   (0)                    // Количество неиспользуемых в сортировке свойств индикатора
//+------------------------------------------------------------------+
//| Строковые свойства индикатора                                    |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_STRING
  {
   INDICATOR_PROP_SYMBOL = (INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL), // Символ индикатора
   INDICATOR_PROP_NAME,                                     // Имя индикатора
   INDICATOR_PROP_SHORTNAME,                                // Короткое имя индикатора
  };
#define INDICATOR_PROP_STRING_TOTAL  (3)                    // Общее количество строковых свойств индикатора
//+------------------------------------------------------------------+
//| Возможные критерии сортировки индикаторов                            |
//+------------------------------------------------------------------+
#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_TIMEFRAME,                             // Сортировать по таймфрейму индикатора
   SORT_BY_INDICATOR_HANDLE,                                // Сортировать по хэндлу индикатора
   SORT_BY_INDICATOR_GROUP,                                 // Сортировать по группе индикатора
//--- Сортировка по вещественным свойствам
   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\ создадим новый класс CIndicatorDE в файле с именем IndicatorDE.mqh. Так как в стандартной библиотеке уже имеется класс CIndicator, то мы к названию класса и файла добавили аббревиатуру DE (DoEasy).

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

//+------------------------------------------------------------------+
//|                                                          Ind.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"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "..\..\Objects\BaseObj.mqh"
//+------------------------------------------------------------------+
//| Класс абстрактного индикатора                                    |
//+------------------------------------------------------------------+
class CIndicatorDE : public CBaseObj
  {
private:
   long              m_long_prop[INDICATOR_PROP_INTEGER_TOTAL];                  // Целочисленные свойства
   double            m_double_prop[INDICATOR_PROP_DOUBLE_TOTAL];                 // Вещественные свойства
   string            m_string_prop[INDICATOR_PROP_STRING_TOTAL];                 // Строковые свойства
   MqlParam          m_mql_params[];                                             // Массив параметров индикатора
   
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство буфера
   int               IndexProp(ENUM_INDICATOR_PROP_DOUBLE property)        const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL;                           }
   int               IndexProp(ENUM_INDICATOR_PROP_STRING property)        const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_DOUBLE_TOTAL;}
   
//--- Сравнивает (1) структуры MqlParam, (2) массив структур MqlParam между собой
   bool              IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) const;
   bool              IsEqualMqlParamArrays(MqlParam &compared_struct[])    const;

protected:
public:
//--- Защищённый параметрический конструктор
                     CIndicatorDE(ENUM_INDICATOR ind_type,
                                  string symbol,
                                  ENUM_TIMEFRAMES timeframe,
                                  ENUM_INDICATOR_STATUS status,
                                  ENUM_INDICATOR_GROUP group,
                                  string name,
                                  string shortname,
                                  MqlParam &indicator_params[]);
public:  
//--- Конструктор по умолчанию
                     CIndicatorDE(void){;}
//--- Деструктор
                    ~CIndicatorDE(void);
                     
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство буфера
   void              SetProperty(ENUM_INDICATOR_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                                        }
   void              SetProperty(ENUM_INDICATOR_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;                      }
   void              SetProperty(ENUM_INDICATOR_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;                      }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство буфера
   long              GetProperty(ENUM_INDICATOR_PROP_INTEGER property)        const { return this.m_long_prop[property];                                       }
   double            GetProperty(ENUM_INDICATOR_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];                     }
   string            GetProperty(ENUM_INDICATOR_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];                     }
//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства буфера
   string            GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_INDICATOR_PROP_STRING property);
//--- Возвращает флаг поддержания буфером данного свойства
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)          { return true;       }
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)           { return true;       }
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_STRING property)           { return true;       }

//--- Сравнивает объекты CIndicatorDE между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-индикатора)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CIndicatorDE между собой по всем свойствам (для поиска равных объектов-индикаторов)
   bool              IsEqual(CIndicatorDE* compared_obj) const;
                     
//--- Устанавливает (1) группу, (2) пустое значение буферов, (3) имя, (4) короткое имя индикатора
   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);                 }
   
//--- Возвращает (1) статус, (2) группу, (3) таймфрейм, (4) хэндл, (5) пустое значение буферов, (6) имя, (7) короткое имя, (8) символ индикатора
   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);   }
   int               Handle(void)                              const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE);                  }
   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);                       }
   
//--- Возвращает описание (1) статуса, (2) группы, (3) таймфрейма, (4) пустого значения индикатора
   string            GetStatusDescription(void)                const;
   string            GetGroupDescription(void)                 const;
   string            GetTimeframeDescription(void)             const;
   string            GetEmptyValueDescription(void)            const;

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

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

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

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

В деструкторе класса уничтожаем созданный объект индикатора:

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CIndicatorDE::~CIndicatorDE(void)
  {
   ::IndicatorRelease(this.Handle());
  }
//+------------------------------------------------------------------+

Закрытый параметрический конструктор класса:

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
CIndicatorDE::CIndicatorDE(ENUM_INDICATOR ind_type,
                           string symbol,
                           ENUM_TIMEFRAMES timeframe,
                           ENUM_INDICATOR_STATUS status,
                           ENUM_INDICATOR_GROUP group,
                           string name,
                           string shortname,
                           MqlParam &indicator_params[])
  {
//--- Устанавливаем идентификатор коллекции объекту
   this.m_type=COLLECTION_INDICATORS_ID;
//--- Если размер переданного в конструктор массива параметров больше нуля, то
//--- заполняем массив параметров объекта данными из переданного в конструктор массива
   int count=::ArrayResize(m_mql_params,::ArraySize(indicator_params));
   for(int i=0;i<count;i++)
     {
      this.m_mql_params[i].type=indicator_params[i].type;
      this.m_mql_params[i].double_value=indicator_params[i].double_value;
      this.m_mql_params[i].integer_value=indicator_params[i].integer_value;
      this.m_mql_params[i].string_value=indicator_params[i].string_value;
     }
//--- Создаём хэндл индикатора
   int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,indicator_params);
   
//--- Сохранение целочисленных свойств
   this.m_long_prop[INDICATOR_PROP_STATUS]                     = status;
   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;
  }
//+------------------------------------------------------------------+

В конструктор передаются все необходимые параметры для создания объекта-индикатора.
Если массив параметров MqlParam &indicator_params[] имеет нулевой размер, то функция IndicatorCreate() при создании индикатора не будет использовать параметры, которые должны содержаться в массиве.
Далее просто сохраняем все переданные в метод значения в полях свойств объекта.

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

Метод сравнения двух структур MqlParam между собой:

//+------------------------------------------------------------------+
//| Сравнивает структуры MqlParam между собой                        |
//+------------------------------------------------------------------+
bool CIndicatorDE::IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) const
  {
   bool res=
     (struct1.type!=struct2.type ? false :
      (struct1.type==TYPE_STRING && struct1.string_value==struct2.string_value) ||
      (struct1.type<TYPE_STRING && struct1.type>TYPE_ULONG && struct1.double_value==struct2.double_value) ||
      (struct1.type<TYPE_FLOAT && struct1.integer_value==struct2.integer_value) ? true : false
     );
   return res;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Сравнивает массив структур MqlParam между собой                  |
//+------------------------------------------------------------------+
bool CIndicatorDE::IsEqualMqlParamArrays(MqlParam &compared_struct[]) const
  {
   int total=::ArraySize(this.m_mql_params);
   int size=::ArraySize(compared_struct);
   if(total!=size || total==0 || size==0)
      return false;
   for(int i=0;i<total;i++)
     {
      if(!this.IsEqualMqlParams(this.m_mql_params[i],compared_struct[i]))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Если размеры двух массивов не равны или любой из них имеет нулевой размер — возвращаем false.
Далее в цикле по количеству структур в массиве сравниваем каджые последующие структуры двух массивов, и если они не равны — возвращаем false.
После успешной проверки всех структур, содержащихся в двух массивах, возвращаем true.

Метод сравнения объектов CIndicatorDE между собой по всем возможным свойствам:

//+------------------------------------------------------------------+
//| Сравнивает объекты CIndicatorDE между собой                      |
//| по всем возможным свойствам                                      |
//+------------------------------------------------------------------+
int CIndicatorDE::Compare(const CObject *node,const int mode=0) const
  {
   const CIndicatorDE *compared_obj=node;
//--- сравнение целочисленных свойств двух индикаторов
   if(mode<INDICATOR_PROP_INTEGER_TOTAL)
     {
      long value_compared=compared_obj.GetProperty((ENUM_INDICATOR_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_INDICATOR_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение вещественных свойств двух индикаторов
   else if(mode<INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL)
     {
      double value_compared=compared_obj.GetProperty((ENUM_INDICATOR_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_INDICATOR_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение строковых свойств двух индикаторов
   else if(mode<INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL+INDICATOR_PROP_STRING_TOTAL)
     {
      string value_compared=compared_obj.GetProperty((ENUM_INDICATOR_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_INDICATOR_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+

Метод сравнения объектов CIndicatorDE между собой по всем свойствам:

//+------------------------------------------------------------------+
//| Сравнивает объекты CIndicatorDE между собой по всем свойствам    |
//+------------------------------------------------------------------+
bool CIndicatorDE::IsEqual(CIndicatorDE *compared_obj) const
  {
   if(!IsEqualMqlParamArrays(compared_obj.m_mql_params))
      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(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;
  }
//+------------------------------------------------------------------+

Два метода сравнения объектов CIndicatorDE между собой по своей логике идентичны одноимённым методам других объектов библиотеки и неоднократно нами рассматривались. Единственное отличие во втором методе (IsEqual) — это то, что сначала вызывается метод сравнения двух массивов структур MqlParam сравниваемых объектов, и если они не равны, то и объекты не равны — возвращаем false. А далее уже сравниваются объекты по всем их полям.

Методы, возвращающие описание целочисленного, вещественного и строкового свойства индикатора:

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства индикатора           |
//+------------------------------------------------------------------+
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_GROUP          ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetGroupDescription()
         )  :
      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)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание вещественного свойства индикатора            |
//+------------------------------------------------------------------+
string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return
     (
      property==INDICATOR_PROP_EMPTY_VALUE    ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetEmptyValueDescription()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства индикатора               |
//+------------------------------------------------------------------+
string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_STRING property)
  {
   return
     (
      property==INDICATOR_PROP_SYMBOL     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      property==INDICATOR_PROP_NAME       ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_NAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.Name()==NULL || this.Name()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Name()+"\"")
         )  :
      property==INDICATOR_PROP_SHORTNAME  ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_SHORTNAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.ShortName()==NULL || this.ShortName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.ShortName()+"\"")
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Подобные методы есть в каждом объекте библиотеки и точно так же нами были рассмотрены ранее.

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

//+------------------------------------------------------------------+
//| Возвращает описание статуса индикатора                           |
//+------------------------------------------------------------------+
string CIndicatorDE::GetStatusDescription(void) const
  {
   return
     (
      this.Status()==INDICATOR_STATUS_CUSTOM    ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS_CUSTOM)    :
      this.Status()==INDICATOR_STATUS_STANDART  ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS_STANDART)  :
      "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание группы индикатора                            |
//+------------------------------------------------------------------+
string CIndicatorDE::GetGroupDescription(void) const
  {
   return
     (
      this.Group()==INDICATOR_GROUP_TREND       ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_TREND)      :
      this.Group()==INDICATOR_GROUP_OSCILLATOR  ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR) :
      this.Group()==INDICATOR_GROUP_VOLUMES     ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES)    :
      this.Group()==INDICATOR_GROUP_ARROWS      ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS)     :
      "Any"
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание используемого таймфрейма                     |
//+------------------------------------------------------------------+
string CIndicatorDE::GetTimeframeDescription(void) const
  {
   string timeframe=TimeframeDescription(this.Timeframe());
   return(this.Timeframe()==PERIOD_CURRENT ? CMessage::Text(MSG_LIB_TEXT_PERIOD_CURRENT)+" ("+timeframe+")" : timeframe);
  } 
//+------------------------------------------------------------------+
//| Возвращает описание установленного пустого значения              |
//+------------------------------------------------------------------+
string CIndicatorDE::GetEmptyValueDescription(void) const
  {
   double value=fabs(this.EmptyValue());
   return(value<EMPTY_VALUE ? ::DoubleToString(this.EmptyValue(),(this.EmptyValue()==0 ? 1 : 8)) : (this.EmptyValue()>0 ? "EMPTY_VALUE" : "-EMPTY_VALUE"));
  }
//+------------------------------------------------------------------+
//| Выводит в журнал свойства индикатора                             |
//+------------------------------------------------------------------+
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));
     }
   ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",this.GetStatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

Это весь состав объекта-индикатора. Вполне вероятно, что далее мы его доработаем, но для проверки создания объекта на сегодня этого достаточно.

Теперь нужно дать возможность в библиотеке полноценно работать со списком новых объектов-индикаторов — сортировать их и выбирать нужные по заданным критериям. Для этого в файле \MQL5\Include\DoEasy\Services\Select.mqh пропишем подключение файла класса базового абстрактного индикатора

//+------------------------------------------------------------------+
//|                                                       Select.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 <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
#include "..\Objects\Indicators\IndicatorDE.mqh"
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Методы работы с индикаторами                                     |
//+------------------------------------------------------------------+
   //--- Возвращает список индикаторов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс индикатора в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства индикатора
   static int        FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property);
   static int        FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property);
   static int        FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property);
   //--- Возвращает индекс индикатора в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства индикатора
   static int        FindIndicatorMin(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property);
   static int        FindIndicatorMin(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property);
   static int        FindIndicatorMin(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Методы работы со списками индикаторов                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список индикаторов, у которых одно из целочисленных   |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список индикаторов, у которых одно из вещественных    |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список индикаторов, у которых одно из строковых       |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
int CSelect::FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CIndicatorDE *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      long obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//| с максимальным значением вещественного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CIndicatorDE *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      double obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//| с максимальным значением строкового свойства                     |
//+------------------------------------------------------------------+
int CSelect::FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CIndicatorDE *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      string obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//| с минимальным значением целочисленного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindIndicatorMin(CArrayObj* list_source,ENUM_INDICATOR_PROP_INTEGER property)
  {
   int index=0;
   CIndicatorDE *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      long obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//| с минимальным значением вещественного свойства                   |
//+------------------------------------------------------------------+
int CSelect::FindIndicatorMin(CArrayObj* list_source,ENUM_INDICATOR_PROP_DOUBLE property)
  {
   int index=0;
   CIndicatorDE *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      double obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс индикатора в списке                            |
//| с минимальным значением строкового свойства                      |
//+------------------------------------------------------------------+
int CSelect::FindIndicatorMin(CArrayObj* list_source,ENUM_INDICATOR_PROP_STRING property)
  {
   int index=0;
   CIndicatorDE *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CIndicatorDE *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      string obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

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

Наша цель на сегодня — создать и проверить факт создания объекта-индикатора. Поэтому доработки коснутся лишь одного метода класса-коллекции буферов — метода создания индикатора Accelerator Oscillator.
Внесём изменения в методе для кроссплатформенности и создадим объект-индикатор, распечатаем его данные в журнал и тут же удалим этот объект. Это всё, что нужно будет сделать для проверки создания объекта абстрактного индикатора.
Откроем файл класса-коллекции индикаторных буферов \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh и внесём необходимые доработки.

Для создания объекта индикатора мы используем функцию IndicatorCreate(), в которую для многих индикаторов необходимо передать нужные параметры. Такие параметры передаются посредством массива специально для этого разработанной структуры MqlParam.
Объявим такой массив в приватной секции класса:

//+------------------------------------------------------------------+
//| Коллекция индикаторных буферов                                   |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Список объектов-буферов
   CTimeSeriesCollection  *m_timeseries;                 // Указатель на объект-коллекцию таймсерий
   MqlParam                m_mql_param[];                // Массив параметров индикатора
//--- Возвращает индекс (1) последнего, (2) следующего рисуемого, (3) базового буфера
   int                     GetIndexLastPlot(void);
   int                     GetIndexNextPlot(void);
   int                     GetIndexNextBase(void);
//--- Создаёт новый объект-буфер и помещает его в список-коллекцию
   bool                    CreateBuffer(ENUM_BUFFER_STATUS status);
//--- Получает данные нужных таймсерий и баров для работы с одним баром буфера, возвращает количество баров
   int                     GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period);

public:

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

Разделим метод на два блока — для MQL5 и для MQL4 для кроссплатформенности, но для MQL4 пока сделаем лишь создание второго буфера для отображения второго цвета (Accelerator Oscillator двухцветный). Но делать изменение цвета гистограмм, а по сути чередование отображения двух разных буферов для визуального отображения разных цветов индикатора в MQL4 мы не будем — сегодня мы делаем совсем другое.
В самом начале метода пропишем строки создания объекта-индикатора, вывод данных созданного объекта в журнал и удаления объекта:

//+------------------------------------------------------------------+
//| Создаёт мультисимвольный мультипериодный AC                      |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE)
  {
//--- Для проверки создаём объёкт-индикатор, выводим его данные и сразу удаляем
   ::ArrayResize(this.m_mql_param,0);
   CIndicatorDE *indicator=new CIndicatorDE(IND_AC,symbol,timeframe,INDICATOR_STATUS_STANDART,INDICATOR_GROUP_OSCILLATOR,"Accelerator Oscillator","AC("+symbol+","+TimeframeDescription(timeframe)+")",this.m_mql_param);
   indicator.Print();
   delete indicator;

//--- Создаём хэндл индикатора и устанавливаем идентификатор по умолчанию
   int handle= #ifdef __MQL5__ ::iAC(symbol,timeframe) #else 0 #endif ;
   int identifier=(id==WRONG_VALUE ? IND_AC : id);
   color array_colors[3]={clrGreen,clrRed,clrGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Создаём буфер-гистограмму от нулевой линии
      this.CreateHistogram();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetShowData(true);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
      buff.SetIndicatorShortName("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      #ifdef __MQL5__ 
         buff.SetColors(array_colors); 
      #else 
         buff.SetColor(array_colors[0]); 
         buff.SetIndicatorLineAdditionalNumber(0);
      #endif 

      //--- MQL5
      #ifdef __MQL5__
         //--- Создаём расчётный буфер, в котором будут храниться данные стандартного индикатора
         this.CreateCalculate();
         //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры
         buff=this.GetLastCreateBuffer();
         if(buff==NULL)
            return INVALID_HANDLE;
         buff.SetSymbol(symbol);
         buff.SetTimeframe(timeframe);
         buff.SetID(identifier);
         buff.SetIndicatorHandle(handle);
         buff.SetIndicatorType(IND_AC);
         buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
         buff.SetEmptyValue(EMPTY_VALUE);
         buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
         buff.SetIndicatorName("Accelerator Oscillator");
         buff.SetIndicatorShortName("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      
      //--- MQL4
      #else 
         //--- Создаём буфер-гистограмму от нулевой линии для буфера второго цвета
         this.CreateHistogram();
         //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры
         buff=this.GetLastCreateBuffer();
         if(buff==NULL)
            return INVALID_HANDLE;
         buff.SetSymbol(symbol);
         buff.SetTimeframe(timeframe);
         buff.SetID(identifier);
         buff.SetIndicatorHandle(handle);
         buff.SetIndicatorType(IND_AC);
         buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
         buff.SetShowData(true);
         buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
         buff.SetIndicatorName("Accelerator Oscillator");
         buff.SetIndicatorShortName("AC("+symbol+","+TimeframeDescription(timeframe)+")");
         #ifdef __MQL5__ 
            buff.SetColors(array_colors); 
         #else 
            buff.SetColor(array_colors[1]); 
            buff.SetIndicatorLineAdditionalNumber(1);
         #endif 
      #endif 
     }
   return handle;
  }
//+------------------------------------------------------------------+

Так как стандартный индикатор Accelerator Oscillator не имеет параметров, то массив параметров индикатора при создании хэндла индикатора функцией IndicatorCreate() использоваться не будет.
Обнулим размер массива параметров
.
Создадим новый объект-индикатор, передав в его конструктор все необходимые данные для создания объекта
,
сразу же распечатаем данные вновь созданного объекта (проверять успешность создания объекта не будем, так как это всего лишь тест),
и удалим этот объект — чтобы не было утечки памяти.

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

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

//+------------------------------------------------------------------+
//| Устанавливает значения для текущего графика в буферы указанного  |
//| стандартного индикатора по индексу таймсерии в соответствии      |
//| с символом/периодом объекта-буфера                               |
//+------------------------------------------------------------------+
bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE)
  {
//--- Получаем список объектов-буферов по типу и идентификатору
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
     
//--- Получаем список рисуемых буферов с идентификатором
   CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Получаем список расчётных буферов с идентификатором
   CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
 
//--- Если любой из списков пустой - уходим
   if(list_data.Total()==0 #ifdef __MQL5__ || list_calc.Total()==0 #endif )
      return false;
  
//--- Объявляем необходимые объекты и переменные
   CBuffer *buffer_data0=NULL,*buffer_data1=NULL,*buffer_data2=NULL,*buffer_data3=NULL,*buffer_data4=NULL,*buffer_tmp0=NULL,*buffer_tmp1=NULL;
   CBuffer *buffer_calc0=NULL,*buffer_calc1=NULL,*buffer_calc2=NULL,*buffer_calc3=NULL,*buffer_calc4=NULL;
   #ifdef __MQL4__ CBuffer *buff_add=NULL; #endif 

   double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
   double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
   double value20=EMPTY_VALUE, value21=EMPTY_VALUE;
   double value30=EMPTY_VALUE, value31=EMPTY_VALUE;
   double value40=EMPTY_VALUE, value41=EMPTY_VALUE;
   double value_tmp0=EMPTY_VALUE,value_tmp1=EMPTY_VALUE;
   long vol0=0,vol1=0;
   int series_index_start=series_index,index_period=0, index=0,num_bars=1;
   uchar clr=0;
//--- В зависимости от типа стандартного индикатора

   switch((int)ind_type)
     {
   //--- Однобуферные стандартные индикаторы
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
      #ifdef __MQL5__
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
      #endif 
        
        if(buffer_data0==NULL #ifdef __MQL5__ || buffer_calc0==NULL || buffer_calc0.GetDataTotal(0)==0 #endif )
           return false;

        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4,
                                                  buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4,
                                                  ind_type,series_index,series_time,index_period,num_bars,
                                                  value00,value01,value10,value11,value20,value21,value30,value31,value40,value41);

        if(series_index_start==WRONG_VALUE)
           return false;
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           if(ind_type!=IND_BWMFI)
              clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           else
             {
              vol0=::iVolume(buffer_data0.Symbol(),buffer_data0.Timeframe(),index_period);
              vol1=::iVolume(buffer_data0.Symbol(),buffer_data0.Timeframe(),index_period+1);
              clr=
                (
                 value00>value01 && vol0>vol1 ? 0 :
                 value00<value01 && vol0<vol1 ? 1 :
                 value00>value01 && vol0<vol1 ? 2 :
                 value00<value01 && vol0>vol1 ? 3 : 4
                );
             }
           #ifdef __MQL5__
              buffer_data0.SetBufferColorIndex(index,clr);
           #else 
              
           #endif 
          }
        return true;
      
   //--- Многобуферные стандартные индикаторы
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
           
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
           
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4,
                                                  buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4,
                                                  ind_type,series_index,series_time,index_period,num_bars,
                                                  value00,value01,value10,value11,value20,value21,value30,value31,value40,value41);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
      case IND_ALLIGATOR   :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4,
                                                  buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4,
                                                  ind_type,series_index,series_time,index_period,num_bars,
                                                  value00,value01,value10,value11,value20,value21,value30,value31,value40,value41);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(0,index,value10);
           buffer_data2.SetBufferValue(0,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ICHIMOKU :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TENKAN_SEN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_KIJUN_SEN,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANA,EQUAL);
        buffer_data2=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANB,EQUAL);
        buffer_data3=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_CHIKOU_SPAN,EQUAL);
        buffer_data4=list.At(0);
        
        //--- Получаем список объектов-буферов, у которых есть идентификатор вспомогательной линии, а из него - объект-буфер с номером линии 0
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_ADDITIONAL,EQUAL);
        list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_ADDITIONAL_NUM,0,EQUAL);
        buffer_tmp0=list.At(0);
        //--- Получаем список объектов-буферов, у которых есть идентификатор вспомогательной линии, а из него - объект-буфер с номером линии 1
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_ADDITIONAL,EQUAL);
        list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_ADDITIONAL_NUM,1,EQUAL);
        buffer_tmp1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TENKAN_SEN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_KIJUN_SEN,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANA,EQUAL);
        buffer_calc2=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANB,EQUAL);
        buffer_calc3=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_CHIKOU_SPAN,EQUAL);
        buffer_calc4=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        if(buffer_calc3==NULL || buffer_data3==NULL || buffer_calc3.GetDataTotal(0)==0)
           return false;
        if(buffer_calc4==NULL || buffer_data4==NULL || buffer_calc4.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4,
                                                  buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4,
                                                  ind_type,series_index,series_time,index_period,num_bars,
                                                  value00,value01,value10,value11,value20,value21,value30,value31,value40,value41);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(0,index,value10);
           buffer_data2.SetBufferValue(0,index,value20);
           buffer_data3.SetBufferValue(0,index,value30);
           buffer_data4.SetBufferValue(0,index,value40);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
           buffer_data3.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value30>value31 ? 0 : value30<value31 ? 1 : 2) : color_index);
           buffer_data4.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value40>value41 ? 0 : value40<value41 ? 1 : 2) : color_index);
           
           //--- Установим значения для вспомогательных линий индикатора в зависимости от взаимного расположения линий Senkou Span A и Senkou Span B
           value_tmp0=buffer_data2.GetDataBufferValue(0,index);
           value_tmp1=buffer_data3.GetDataBufferValue(0,index);
           if(value_tmp0<value_tmp1)
             {
              buffer_tmp0.SetBufferValue(0,index,buffer_tmp0.EmptyValue());
              buffer_tmp0.SetBufferValue(1,index,buffer_tmp0.EmptyValue());
              
              buffer_tmp1.SetBufferValue(0,index,value_tmp0);
              buffer_tmp1.SetBufferValue(1,index,value_tmp1);
             }
           else
             {
              buffer_tmp0.SetBufferValue(0,index,value_tmp0);
              buffer_tmp0.SetBufferValue(1,index,value_tmp1);
              
              buffer_tmp1.SetBufferValue(0,index,buffer_tmp1.EmptyValue());
              buffer_tmp1.SetBufferValue(1,index,buffer_tmp1.EmptyValue());
             }
           
          }
        return true;
      
      case IND_GATOR    :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
           
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
           
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4,
                                                  buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4,
                                                  ind_type,series_index,series_time,index_period,num_bars,
                                                  value00,value01,value10,value11,value20,value21,value30,value31,value40,value41);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10<value11 ? 0 : value10>value11 ? 1 : 2) : color_index);
          }
        return true;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Это все изменения, которые нам необходимо было сделать сегодня.

Тест

Для проверки создания объекта-индикатора возьмём тестовый индикатор из прошлой статьи ,
сохраним его в новой папке \MQL5\Indicators\TestDoEasy\Part53\ под новым именем TestDoEasyPart53.mq5 и заменим строки, указывающие на работу с индикатором AD на работу с индикатором AC:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Запишем в переменную InpUsedTFs наименование выбранного в настройках рабочего таймфрейма
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();
   
//--- Установка глобальных переменных индикатора
   prefix=engine.Name()+"_";
   //--- Рассчитываем количество баров текущего периода, умещающихся в максимальном используемом периоде
   //--- Используем полученное значение если оно больше 2, иначе используем 2
   int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>2 ? num_bars : 2);

//--- Проверка и удаление неудалённых графических объектов индикатора
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Создание панели кнопок

//--- Проверка воспроизведения стандартного звука по макроподстановкам
   engine.PlaySoundByDescription(SND_OK);
//--- Ждём 600 милисекунд
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы для построения выбранного стандартного индикатора
   if(!engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1))
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Проверяем количество буферов, указанных в блоке properties
   engine.CheckIndicatorsBuffers(indicator_buffers,indicator_plots);
      
//--- Создаём массив цветов и зададём всем буферам в коллекции значения цвета не по умолчанию
//--- (закомментировано так как в методах создания стандартных индикаторов по умолчанию уже заданы цвета)
//--- (всегда можно задать нужные цвета либо для всех индикаторов как здесь, либо для каждого индивидуально)
   //color array_colors[]={clrGreen,clrRed,clrGray};
   //engine.BuffersSetColors(array_colors);

//--- Выведем краткие описания созданных индикаторных буферов
   engine.BuffersPrintShort();

//--- Установим короткое имя индикатора, разрядность данных и уровни
   string label=engine.BufferGetIndicatorShortNameByTypeID(IND_AC,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   SetIndicatorLevels(InpUsedSymbols,IND_AC);

//--- Успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаление графических объектов индикатора по префиксу имени объектов
   ObjectsDeleteAll(0,prefix);
   Comment("");
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//+------------------------------------------------------------------+
//| OnCalculate Блок кода для работы с библиотекой:                  |
//+------------------------------------------------------------------+
//--- Передача в структуру цен текущих данных массивов из OnCalculate() и установка массивам флага "как таймсерия"
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Проверка на минимальное количество баров для расчёта
   if(rates_total<min_bars || Point()==0) return 0;
//--- Обработка события Calculate в библиотеке
//--- Если метод OnCalculate() библиотеки вернул ноль - значит не все таймсерии готовы - уходим до следующего тика
   if(engine.OnCalculate(rates_data)==0)
      return 0;
   
//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере библиотеки
      engine.EventsHandling();      // Работа с событиями библиотеки
     }
//+------------------------------------------------------------------+
//| OnCalculate Блок кода для работы с индикатором:                  |
//+------------------------------------------------------------------+
//--- Проверка и расчёт количества просчитываемых баров
//--- Если limit = 0, значит новых баров нет - рассчитываем текущий
//--- Если limit = 1, значит появился новый бар - рассчитываем первый и текущий
//--- Если limit > 1, значит первый запуск, или есть изменения в истории - полный перерасчёт всех данных
   int limit=rates_total-prev_calculated;
   
//--- Перерасчёт всей истории
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
     }
//--- Подготовка данных 
//--- Заполняем данными расчётные буферы всех созданных стандартных индикаторов
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   if(!engine.BufferPreparingDataAllBuffersStdInd())
      return 0;

//--- Расчёт индикатора
//--- Основной цикл расчёта индикатора
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(IND_AC,1,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

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

Счёт 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, Демонстрационный счёт MetaTrader 5
--- Инициализация библиотеки "DoEasy" ---
Работа только с текущим символом. Количество используемых символов: 1
"EURUSD"
Работа с заданным списком таймфреймов:
"H4" "H1"
Таймсерия символа EURUSD: 
- Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 0, Создано: 0, На сервере: 0
- Таймсерия "EURUSD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6237
Время инициализации библиотеки: 00:00:00.156
 
============= Начало списка параметров: "Стандартный индикатор" =============
Статус индикатора: Стандартный индикатор
Таймфрейм индикатора: H4
Хэндл индикатора: 10
Группа индикатора: Осциллятор
------
Пустое значение для построения, для которого нет отрисовки: EMPTY_VALUE
------
Символ индикатора: EURUSD
Имя индикатора: "Accelerator Oscillator"
Короткое имя индикатора: "AC(EURUSD,H4)"
================== Конец списка параметров: "Стандартный индикатор" ==================
 
Буфер(P0/B0/C1): Гистограмма от нулевой линии EURUSD H4
Буфер[P0/B2/C2]: Расчётный буфер
Таймсерия "EURUSD" H1 создана успешно:
- Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6256


Что дальше

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

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