Работа с таймсериями в библиотеке DoEasy (Часть 58): Таймсерии данных буферов индикаторов

Artyom Trishkin | 10 декабря, 2020

Содержание


Концепция

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

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


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

Класс коллекции данных индикаторных буферов будет автоматически обновлять данные всех созданных индикаторов на текущем баре, а при появлении нового бара будет создавать новые данные индикаторных буферов и размещать их в коллекцию.
У нас уже есть класс "Новый бар" в файле NewBarObj.mqh, который мы создавали для коллекции таймсерий.
Хранится он в папке классов таймсерий в каталоге библиотеки \MQL5\Include\DoEasyPart57\Objects\Series\.
Сейчас же он нам нужен и для отслеживания нового бара в классе коллекции данных индикаторных буферов.
Поэтому перенесём его в папку сервисных функций и классов библиотеки \MQL5\Include\DoEasy\Services\.
Файл в его старом расположении удалим.

Так как класс "Таймсерия" использует класс "Новый бар", то нужно изменить старый путь к этому классу в файле класса "Таймсерия" \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh в блоке включаемых файлов:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "NewBarObj.mqh"
#include "Bar.mqh"
//+------------------------------------------------------------------+

на новый путь:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "..\..\Services\NewBarObj.mqh"
#include "Bar.mqh"
//+------------------------------------------------------------------+

В файл \MQL5\Include\DoEasy\Data.mqh допишем индексы новых сообщений:

   MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_ASK,            // Не удалось получить цену Ask. Ошибка
   MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_BID,            // Не удалось получить цену Bid. Ошибка
   MSG_LIB_SYS_ERROR_FAILED_GET_TIME,                 // Не удалось получить время. Ошибка
   MSG_LIB_SYS_ERROR_FAILED_OPEN_BUY,                 // Не удалось открыть позицию Buy. Ошибка

...

//--- CDataInd
   MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM,              // Номер буфера индикатора
   MSG_LIB_TEXT_IND_DATA_BUFFER_VALUE,                // Значение буфера индикатора
   
//--- CSeriesDataInd
   MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS,            // Метод не предназначен для работы с программами-индикаторами
   MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA,      // Не удалось получить таймсерию индикаторных данных
   MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA,     // Не удалось получить текущие данные буфера индикатора
   MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ,            // Не удалось создать объект индикаторных данных
   MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST,          // Не удалось добавить объект индикаторных данных в список
   
  };
//+------------------------------------------------------------------+

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

   {"Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "},
   {"Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "},
   {"Не удалось получить время. Ошибка ","Could not get time. Error "},
   {"Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "},

...

   {"Номер буфера индикатора","Indicator buffer number"},
   {"Значение буфера индикатора","Indicator buffer value"},
   
   {"Метод не предназначен для работы с программами-индикаторами","The method is not intended for working with indicator programs"},
   {"Не удалось получить таймсерию индикаторных данных","Failed to get indicator data timeseries"},
   {"Не удалось получить текущие данные буфера индикатора","Failed to get the current data of the indicator buffer"},
   {"Не удалось создать объект индикаторных данных","Failed to create indicator data object"},
   {"Не удалось добавить объект индикаторных данных в список","Failed to add indicator data object to the list"},
   
  };
//+---------------------------------------------------------------------+

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

Внесём новые данные в файл \MQL5\Include\DoEasy\Defines.mqh:

//--- Параметры таймера коллекции таймсерий
#define COLLECTION_TS_PAUSE            (64)                       // Пауза таймера коллекции таймсерий в милисекундах
#define COLLECTION_TS_COUNTER_STEP     (16)                       // Шаг приращения счётчика таймера таймсерий
#define COLLECTION_TS_COUNTER_ID       (6)                        // Идентификатор счётчика таймера таймсерий
//--- Параметры таймера коллекции таймсерий индикаторных данных
#define COLLECTION_IND_TS_PAUSE        (64)                       // Пауза таймера коллекции таймсерий индикаторных данных в милисекундах
#define COLLECTION_IND_TS_COUNTER_STEP (16)                       // Шаг приращения счётчика таймера таймсерий индикаторных данных
#define COLLECTION_IND_TS_COUNTER_ID   (7)                        // Идентификатор счётчика таймера таймсерий индикаторных данных
//--- Идентификаторы списков коллекций
#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)                   // Идентификатор списка коллекции индикаторов
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Идентификатор списка коллекции индикаторных данных

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

В целочисленные свойства индикаторных данных добавим новое свойство и увеличим количество этих данных с 5 до 6:

//+------------------------------------------------------------------+
//| Целочисленные свойства данных индикатора                         |
//+------------------------------------------------------------------+
enum ENUM_IND_DATA_PROP_INTEGER
  {
   IND_DATA_PROP_TIME = 0,                                  // Время начала периода бара данных индикатора
   IND_DATA_PROP_PERIOD,                                    // Период данных индикатора (таймфрейм)
   IND_DATA_PROP_INDICATOR_TYPE,                            // Тип индикатора
   IND_DATA_PROP_IND_BUFFER_NUM,                            // Номер буфера данных индикатора
   IND_DATA_PROP_IND_ID,                                    // Идентификатор индикатора
   IND_DATA_PROP_IND_HANDLE,                                // Хэндл индикатора
  }; 
#define IND_DATA_PROP_INTEGER_TOTAL (6)                     // Общее количество целочисленных свойств данных индикатора
#define IND_DATA_PROP_INTEGER_SKIP  (0)                     // Количество неиспользуемых в сортировке свойств данных индикатора
//+------------------------------------------------------------------+

Соответственно, раз мы добавили новое целочисленное свойство, то для возможности поиска и сортировки по этому свойству внесём его в список возможных критериев сортировки:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки данных индикаторов                 |
//+------------------------------------------------------------------+
#define FIRST_IND_DATA_DBL_PROP          (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP)
#define FIRST_IND_DATA_STR_PROP          (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP+IND_DATA_PROP_DOUBLE_TOTAL-IND_DATA_PROP_DOUBLE_SKIP)
enum ENUM_SORT_IND_DATA_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_IND_DATA_TIME = 0,                               // Сортировать по времени начала периода бара данных индикатора
   SORT_BY_IND_DATA_PERIOD,                                 // Сортировать по периоду данных индикатора (таймфрейм)
   SORT_BY_IND_DATA_INDICATOR_TYPE,                         // Сортировать по типу индикатора
   SORT_BY_IND_DATA_IND_BUFFER_NUM,                         // Сортировать по номеру буфера данных индикатора
   SORT_BY_IND_DATA_IND_ID,                                 // Сортировать по идентификатору индикатора
   SORT_BY_IND_DATA_IND_HANDLE,                             // Сортировать по хэндлу индикатора
//--- Сортировка по вещественным свойствам
   SORT_BY_IND_DATA_BUFFER_VALUE = FIRST_IND_DATA_DBL_PROP, // Сортировать по значению величины данных индикатора
//--- Сортировка по строковым свойствам
   SORT_BY_IND_DATA_SYMBOL = FIRST_IND_DATA_STR_PROP,       // Сортировать по символу данных индикатора
   SORT_BY_IND_DATA_IND_NAME,                               // Сортировать по наименованию индикатора
   SORT_BY_IND_DATA_IND_SHORTNAME,                          // Сортировать по короткому наименованию индикатора
  };
//+------------------------------------------------------------------+

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

Нам необходимо сейчас добавить к нему (в файле \MQL5\Include\DoEasy\Objects\Indicators\DataInd.mqh) метод установки хэндла индикатора, данные буфера которого хранятся в объекте этого класса. Также добавим в конструктор класса передачу параметров хэндла индикатора и значения его буфера, что позволит сразу при создании объекта указать значения этих параметров:

//--- Устанавливает (1) символ, таймфрейм и время объекта, (2) тип индикатора, (3) количество буферов, (4) номер буфера данных,
//--- (5) идентификатор, (6) хэндл, (7) значение величины данных, (8) наименование, (9) короткое наименование индикатора
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetIndicatorType(const ENUM_INDICATOR type)              { this.SetProperty(IND_DATA_PROP_INDICATOR_TYPE,type);               }
   void              SetBufferNum(const int num)                              { this.SetProperty(IND_DATA_PROP_IND_BUFFER_NUM,num);                }
   void              SetIndicatorID(const int id)                             { this.SetProperty(IND_DATA_PROP_IND_ID,id);                         }
   void              SetIndicatorHandle(const int handle)                     { this.SetProperty(IND_DATA_PROP_IND_HANDLE,handle);                 }
   void              SetBufferValue(const double value)                       { this.SetProperty(IND_DATA_PROP_BUFFER_VALUE,value);                }
   void              SetIndicatorName(const string name)                      { this.SetProperty(IND_DATA_PROP_IND_NAME,name);                     }
   void              SetIndicatorShortname(const string shortname)            { this.SetProperty(IND_DATA_PROP_IND_SHORTNAME,shortname);           }
   
//--- Сравнивает объекты CDataInd между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-данных)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CDataInd между собой по всем свойствам (для поиска равных объектов)
   bool              IsEqual(CDataInd* compared_data) const;
//--- Конструкторы
                     CDataInd(){;}
                     CDataInd(const ENUM_INDICATOR ind_type,
                              const int ind_handle,
                              const int ind_id,
                              const int buffer_num,
                              const string symbol,
                              const ENUM_TIMEFRAMES timeframe,
                              const datetime time,
                              const double value);

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

//+------------------------------------------------------------------+ 
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- Возвращает (1) время начала периода бара, (2) таймфрейм, (3) тип индикатора,
//--- (4) количество буферов, (5) номер буфера, (6) идентификатор, (7) хэндл индикатора
   datetime          Time(void)                                         const { return (datetime)this.GetProperty(IND_DATA_PROP_TIME);                   }
   ENUM_TIMEFRAMES   Timeframe(void)                                    const { return (ENUM_TIMEFRAMES)this.GetProperty(IND_DATA_PROP_PERIOD);          }
   ENUM_INDICATOR    IndicatorType(void)                                const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_INDICATOR_TYPE);   }
   int               BufferNum(void)                                    const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_BUFFER_NUM);   }
   int               IndicatorID(void)                                  const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_ID);           }
   int               IndicatorHandle(void)                              const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_HANDLE);       }

//--- Возвращает значение данных буфера индикатора
   double            BufferValue(void)                                  const { return this.GetProperty(IND_DATA_PROP_BUFFER_VALUE);                     }

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CDataInd::CDataInd(const ENUM_INDICATOR ind_type,
                   const int ind_handle,
                   const int ind_id,
                   const int buffer_num,
                   const string symbol,
                   const ENUM_TIMEFRAMES timeframe,
                   const datetime time,
                   const double value)
  {
   this.m_type=COLLECTION_INDICATORS_DATA_ID;
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)+1;
   this.m_period_description=TimeframeDescription(timeframe);
   this.SetSymbolPeriod(symbol,timeframe,time);
   this.SetIndicatorType(ind_type);
   this.SetIndicatorHandle(ind_handle);
   this.SetBufferNum(buffer_num);
   this.SetIndicatorID(ind_id);
   this.SetBufferValue(value);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства объекта              |
//+------------------------------------------------------------------+
string CDataInd::GetPropertyDescription(ENUM_IND_DATA_PROP_INTEGER property)
  {
   return
     (
      property==IND_DATA_PROP_TIME           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==IND_DATA_PROP_PERIOD         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.m_period_description
         )  :
      property==IND_DATA_PROP_INDICATOR_TYPE ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.IndicatorTypeDescription()
         )  :
      property==IND_DATA_PROP_IND_BUFFER_NUM ?  CMessage::Text(MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==IND_DATA_PROP_IND_ID         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==IND_DATA_PROP_IND_HANDLE     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Класс таймсерии данных буферов индикатора

Все классы-коллекции в библиотеке созданы на основе класса динамического массива указателей на экземпляры класса CObject и его наследников Стандартной библиотеки. Класс-коллекция данных индикаторных буферов не будет исключением.

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

В папке \MQL5\Include\DoEasy\Objects\Indicators\ создадим новый  класс CSeriesDataInd в файле SeriesDataInd.mqh.
Объект класса должен быть унаследован от базового объекта всех объектов библиотеки CBaseObj.
Рассмотрим тело класса со всеми его переменными и методами:

//+------------------------------------------------------------------+
//|                                                SeriesDataInd.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 "..\..\Services\NewBarObj.mqh"
#include "DataInd.mqh"
//+------------------------------------------------------------------+
//| Класс "Список данных индикатора"                                 |
//+------------------------------------------------------------------+
class CSeriesDataInd : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Таймфрейм
   ENUM_INDICATOR    m_ind_type;                                        // Тип индикатора
   string            m_symbol;                                          // Символ
   string            m_period_description;                              // Строковое описание таймфрейма
   int               m_ind_handle;                                      // Хэндл индикатора
   int               m_ind_id;                                          // Идентификатор индикатора
   int               m_buffers_total;                                   // Количество буферов индикатора
   uint              m_amount;                                          // Количество используемых данных таймсерии
   uint              m_required;                                        // Требуемое количество используемых данных таймсерии
   uint              m_bars;                                            // Количество баров в истории по символу и таймфрейму
   bool              m_sync;                                            // Флаг синхронизированности данных
   CArrayObj         m_list_data;                                       // Список индикаторных данных
   CNewBarObj        m_new_bar_obj;                                     // Объект "Новый бар"
//--- Создаёт новый объект индикаторных данных, возвращает указатель на него
   CDataInd         *CreateNewDataInd(const int buffer_num,const datetime time,const double value);
   
public:
//--- Возвращает (1) себя, (2) полный список индикаторных данных
   CSeriesDataInd   *GetObject(void)                                    { return &this;               }
   CArrayObj        *GetList(void)                                      { return &this.m_list_data;   }

//--- Возвращает список индикаторных данных по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию
   CArrayObj        *GetList(ENUM_IND_DATA_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_IND_DATA_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_IND_DATA_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); }

//--- Возвращает объект индикаторных данных по (1) времени, (2) номеру бара и номеру буфера
   CDataInd         *GetIndDataByTime(const int buffer_num,const datetime time);
   CDataInd         *GetIndDataByBar(const int buffer_num,const uint shift);
   
//--- Устанавливает (1) символ, (2) таймфрейм, (3) символ и таймфрейм, (4) количество используемых данных таймсерии,
//--- (5) хэндл, (6) идентификатор, (7) количество буферов индикатора
   void              SetSymbol(const string symbol);
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe);
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool              SetRequiredUsedData(const uint required);
   void              SetIndHandle(const int handle)                              { this.m_ind_handle=handle;   }
   void              SetIndID(const int id)                                      { this.m_ind_id=id;           }
   void              SetIndBuffersTotal(const int total)                         { this.m_buffers_total=total; }

//--- Возвращает (1) символ, (2) таймфрейм, количество (3) используемых, (4) запрошенных данных таймсерии,
//--- (5) количество баров в таймсерии, (6) хэндл, (7) идентификатор, (8) количество буферов индикатора
   string            Symbol(void)                                          const { return this.m_symbol;                            }
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe;                         }
   ulong             AvailableUsedData(void)                               const { return this.m_amount;                            }
   ulong             RequiredUsedData(void)                                const { return this.m_required;                          }
   ulong             Bars(void)                                            const { return this.m_bars;                              }
   int               IndHandle(void)                                       const { return this.m_ind_handle;                        }
   int               IndID(void)                                           const { return this.m_ind_id;                            }
   int               IndBuffersTotal(void)                                 const { return this.m_buffers_total;                     }
   bool              IsNewBar(const datetime time)                               { return this.m_new_bar_obj.IsNewBar(time);        }
   bool              IsNewBarManual(const datetime time)                         { return this.m_new_bar_obj.IsNewBarManual(time);  }

//--- Возвращает реальный размер списка
   int               DataTotal(void)                                       const { return this.m_list_data.Total();                 }
 
//--- Сохраняет время нового бара при ручном управлении временем
   void              SaveNewBarTime(const datetime time)                         { this.m_new_bar_obj.SaveNewBarTime(time);         }
   
//--- (1) Создаёт, (2) обновляет список индикаторных данных
   int               Create(const uint required=0);
   void              Refresh(void);

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

//--- Конструкторы
                     CSeriesDataInd(void){;}
                     CSeriesDataInd(const int handle,
                                    const ENUM_INDICATOR ind_type,
                                    const int ind_id,
                                    const int buffers_total,
                                    const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
  };
//+------------------------------------------------------------------+

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

Рассмотрим реализацию методов класса.

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CSeriesDataInd::CSeriesDataInd(const int handle,
                               const ENUM_INDICATOR ind_type,
                               const int ind_id,
                               const int buffers_total,
                               const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) :
                               m_bars(0), m_amount(0),m_required(0),m_sync(false)
  {
//--- Устанавливаем объекту идентификатор списка коллекции индикаторных данных
   this.m_type=COLLECTION_INDICATORS_DATA_ID;
//--- Очищаем список и устанавливаем ему флаг сортированного по времени списка
   this.m_list_data.Clear();
   this.m_list_data.Sort(SORT_BY_IND_DATA_TIME);
//--- Устанавливаем значения всем переменным объекта
   this.SetSymbolPeriod(symbol,timeframe);
   this.m_sync=this.SetRequiredUsedData(required);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.m_ind_handle=handle;
   this.m_ind_type=ind_type;
   this.m_ind_id=ind_id;
   this.m_buffers_total=buffers_total;
  }
//+------------------------------------------------------------------+

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

Методы для установки символа и таймфрейма, думаю, в пояснениях не нуждаются:

//+------------------------------------------------------------------+
//| Устанавливает символ                                             |
//+------------------------------------------------------------------+
void CSeriesDataInd::SetSymbol(const string symbol)
  {
   if(this.m_symbol==symbol)
      return;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
  }
//+------------------------------------------------------------------+
//| Устанавливает таймфрейм                                          |
//+------------------------------------------------------------------+
void CSeriesDataInd::SetTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   if(this.m_timeframe==timeframe)
      return;
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe);
  }
//+------------------------------------------------------------------+
//| Устанавливает символ и таймфрейм                                 |
//+------------------------------------------------------------------+
void CSeriesDataInd::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
  }
//+------------------------------------------------------------------+

Метод для установки требуемого количества данных индикаторных буферов:

//+------------------------------------------------------------------+
//| Устанавливает количество требуемых данных                        |
//+------------------------------------------------------------------+
bool CSeriesDataInd::SetRequiredUsedData(const uint required)
  {
//--- Если это программа-индикатор - сообщаем и уходим
   if(this.m_program==PROGRAM_INDICATOR)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS));
      return false;
     }
//--- Устанавливаем требуемое количество данных - если передано меньше 1, то используем по умолчанию (1000), иначе - переданное значение
   this.m_required=(required<1 ? SERIES_DEFAULT_BARS_COUNT : required);
//--- Запустим закачку исторических данных (актуально для "холодного" запуска)
   datetime array[1];
   ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array);
//--- Установим количество доступных баров таймсерии
   this.m_bars=(uint)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_BARS_COUNT);
//--- Если удалось записать количество доступных баров истории - установим количество данных в списке:
   if(this.m_bars>0)
     {
      //--- если передано нулевое значение required,
      //--- то используем либо значение по умолчанию (1000 баров), либо количество доступных баров истории - меньшее из них
      //--- если передано не нулевое значение required,
      //--- то используем либо значение required, либо количество доступных баров истории - меньшее из них
      this.m_amount=(required==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(required,this.m_bars));
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Для работы в составе индикаторов у нас уже имеются иные классы, поэтому здесь сразу проверяется тип запущенной программы, и если это индикатор, то выводится об этом сообщение и возвращается false. Работу похожего метода мы рассматривали при создании классов таймсерий в статье 35.

Метод, создающий новый объект индикаторных данных:

//+------------------------------------------------------------------+
//| Создаёт новый объект индикаторных данных, возвращает указатель   |
//+------------------------------------------------------------------+
CDataInd* CSeriesDataInd::CreateNewDataInd(const int buffer_num,const datetime time,const double value)
  {
   CDataInd* data_ind=new CDataInd(this.m_ind_type,this.m_ind_handle,this.m_ind_id,buffer_num,this.m_symbol,this.m_timeframe,time,value);
   return data_ind;
  }
//+------------------------------------------------------------------+

Создаёт новый объект класса CDataInd и возвращает указатель на вновь созданный объект, или NULL в случае, если объект создан не был.

Метод для создания списка-таймсерии индикаторных данных:

//+------------------------------------------------------------------+
//| Создаёт список-таймсерию индикаторных данных                     |
//+------------------------------------------------------------------+
int CSeriesDataInd::Create(const uint required=0)
  {
//--- Если это программа-индикатор - сообщаем и уходим
   if(this.m_program==PROGRAM_INDICATOR)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS));
      return false;
     }
//--- Если ещё не установлена требуемая глубина истории для списка
//--- выводим об этом сообщение и возвращаем ноль,
   if(this.m_amount==0)
     {
      ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA));
      return 0;
     }
//--- иначе, если переданное значение required больше нуля, не равно уже установленному, 
//--- и при этом переданное значение required меньше доступного количества баров,
//--- устанавливаем новое значение требуемой глубины истории для списка
   else if(required>0 && this.m_amount!=required && required<this.m_bars)
     {
      //--- Если не удалось установить новое значение - возвращаем ноль
      if(!this.SetRequiredUsedData(required))
         return 0;
     }
//--- Для массива data[], в который будем получать исторические данные,
//--- установим признак направленности как в таймсерии,
//--- очистим список объектов индикаторных данных и установим ему флаг сортировки по времени бара таймсерии
   double data[];
   ::ArraySetAsSeries(data,true);
   this.m_list_data.Clear();
   this.m_list_data.Sort(SORT_BY_IND_DATA_TIME);
   ::ResetLastError();

   int err=ERR_SUCCESS;
//--- В цикле по количеству буферов данных индикатора
   for(int i=0;i<this.m_buffers_total;i++)
     {
      //--- Получим в массив data[] исторические данные буфера i индикатора, начиная от текущего бара в количестве m_amount,
      //--- и если получить данные не удалось - выводим об этом сообщение и возвращаем ноль
      int copied=::CopyBuffer(this.m_ind_handle,i,0,this.m_amount,data);
      if(copied<1)
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
         return 0;
        }
      //--- В цикле по количеству требуемых данных
      for(int j=0;j<(int)this.m_amount;j++)
        {
         //--- Получаем время бара j
         ::ResetLastError();
         datetime time=::iTime(this.m_symbol,this.m_timeframe,j);
         //--- Если время получить не удалось
         //--- выводим об этом сообщение в журнал с описанием ошибки и идём на следующую итерацию цикла по данным (j)
         if(time==0)
           {
            err=::GetLastError();
            ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err));
            continue;
           }
         //--- Если не удалось создать новый объект данных индикатора из буфера i
         //--- выводим об этом сообщение в журнал с описанием ошибки и идём на следующую итерацию цикла по данным (j)
         CDataInd* data_ind=CreateNewDataInd(i,time,data[j]);
         if(data_ind==NULL)
           {
            err=::GetLastError();
            ::Print
              (
               DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ)," ",::TimeToString(time),". ",
               CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)
              );
            continue;
           }
         //--- Если не удалось добавить новый объект индикаторных данных в список
         //--- выводим об этом сообщение в журнал с описанием ошибки, удаляем объект данных
         //--- и идём на следующую итерацию цикла по данным
         if(!this.m_list_data.Add(data_ind))
           {
            err=::GetLastError();
            ::Print
              (
               DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST)," ",::TimeToString(time),". ",
               CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)
              );
            delete data_ind;
            continue;
           }
        }
     }
//--- Возвращаем размер созданного списка объектов индикаторных данных
   return this.m_list_data.Total();
  }
//+------------------------------------------------------------------+

Вся логика метода подробно расписана в его листинге. Метод копирует данные из всех буферов индикатора в массив, на этих данных создаёт новые объекты индикаторных данных CDataInd и размещает их в список-коллекцию.

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

//+------------------------------------------------------------------+
//| Обновляет список и данные тайм-серии индикаторных данных         |
//+------------------------------------------------------------------+
void CSeriesDataInd::Refresh(void)
  {
//--- Если это программа-индикатор - сообщаем и уходим
   if(this.m_program==PROGRAM_INDICATOR)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS));
      return;
     }
//--- Если таймсерия данных индикатора не используется - выходим
   if(!this.m_available)
      return;
   double data[1];
   
//--- Получаем время текущего бара
   int err=ERR_SUCCESS;
   ::ResetLastError();
   datetime time=::iTime(this.m_symbol,this.m_timeframe,0);
   //--- Если время получить не удалось
   //--- выводим об этом сообщение в журнал с описанием ошибки и выходим
   if(time==0)
     {
      err=::GetLastError();
      ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err));
      return;
     }
   
//--- Устанавливаем флаг сортировки списка данных по времени
   this.m_list_data.Sort(SORT_BY_IND_DATA_TIME);
//--- Если есть новый бар на символе и периоде
   if(this.IsNewBarManual(time))
     {
      //--- создаём новые объекты индикаторных данных по количеству буферов и добавляем их в конец списка
      for(int i=0;i<this.m_buffers_total;i++)
        {
         //--- Получим в массив data[] данные текущего бара буфера i индикатора,
         //--- и если получить данные не удалось - выводим об этом сообщение и уходим
         int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data);
         if(copied<1)
           {
            err=::GetLastError();
            ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                         CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
            return;
           }
         //--- Создаём новый объект данных буфера i индикатора
         CDataInd* data_ind=CreateNewDataInd(i,time,data[0]);
         if(data_ind==NULL)
            return;
         //--- Если созданный объект не добавлен в список - удаляем этот объект и уходим из метода
         if(!this.m_list_data.InsertSort(data_ind))
           {
            delete data_ind;
            return;
           }
        }
      //--- если размер таймсерии данных индикатора стал больше запрашиваемого количества баров - удаляем самый ранний объект данных
      if(this.m_list_data.Total()>(int)this.m_required)
         this.m_list_data.Delete(0);
      //--- сохраняем новое время бара как прошлое для последующей проверки на новый бар
      this.SaveNewBarTime(time);
     }
 
//--- Получаем список объектов индикаторных данных по времени начала текущего бара
   CArrayObj *list=CSelect::ByIndicatorDataProperty(this.GetList(),IND_DATA_PROP_TIME,time,EQUAL);
//--- В цикле по количеству буферов индикатора получаем объекты индикаторных данных из списка по индексу цикла
   for(int i=0;i<this.m_buffers_total;i++)
     {
      //--- Если объект не удалось получить из списка - уходим
      CDataInd *data_ind=this.GetIndDataByTime(i,time);
      if(data_ind==NULL)
         return;
      //--- Копируем данные буфера i индикатора с текущего бара
      int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data);
      if(copied<1)
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
         return;
        }
      //--- Записываем полученное значение буфера i индикатора в объект индикаторных данных
      data_ind.SetBufferValue(data[0]);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает объект индикаторных данных по времени и номеру буфера |
//+------------------------------------------------------------------+
CDataInd* CSeriesDataInd::GetIndDataByTime(const int buffer_num,const datetime time)
  {
   CArrayObj *list=GetList(IND_DATA_PROP_TIME,time,EQUAL);
   list=CSelect::ByIndicatorDataProperty(list,IND_DATA_PROP_IND_BUFFER_NUM,buffer_num,EQUAL);
   return list.At(0);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает объект индикаторных данных по бару и номеру буфера    |
//+------------------------------------------------------------------+
CDataInd* CSeriesDataInd::GetIndDataByBar(const int buffer_num,const uint shift)
  {
   datetime time=::iTime(this.m_symbol,this.m_timeframe,(int)shift);
   if(time==0)
      return NULL;
   return this.GetIndDataByTime(buffer_num,time);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает данные указанного буфера индикатора по времени        |
//+------------------------------------------------------------------+
double CSeriesDataInd::BufferValue(const int buffer_num,const datetime time)
  {
   CDataInd *data_ind=this.GetIndDataByTime(buffer_num,time);
   return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue());
  }
//+------------------------------------------------------------------+

Здесь: получаем объект данных при помощи метода GetIndDataByTime() и возвращаем значение буфера, записанное в объекте, либо "пустое значение" если объект получен не был.

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

//+------------------------------------------------------------------+
//| Возвращает данные указанного буфера индикатора по индексу бара   |
//+------------------------------------------------------------------+
double CSeriesDataInd::BufferValue(const int buffer_num,const uint shift)
  {
   CDataInd *data_ind=this.GetIndDataByBar(buffer_num,shift);
   return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue());
  }
//+------------------------------------------------------------------+

Здесь: получаем объект данных при помощи метода GetIndDataByBar() и возвращаем значение буфера, записанное в объекте, либо "пустое значение" если объект получен не был.

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

Откроем файл класса объекта-индикатора \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh и внесём в него все необходимые доработки:

//+------------------------------------------------------------------+
//|                                                          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"
#include "..\..\Objects\Indicators\SeriesDataInd.mqh"
//+------------------------------------------------------------------+
//| Класс абстрактного индикатора                                    |
//+------------------------------------------------------------------+
class CIndicatorDE : public CBaseObj
  {
protected:
   MqlParam          m_mql_param[];                                              // Массив параметров индикатора
   CSeriesDataInd    m_series_data;                                              // Объект-таймсерия данных буферов индикатора
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];                 // Строковые свойства
   string            m_ind_type_description;                                     // Описание типа индикатора
   int               m_buffers_total;                                            // Общее количество буферов индикатора
   
//--- Возвращает индекс массива, по которому фактически расположено (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:
//--- Защищённый параметрический конструктор
                     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[]);
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) короткое имя, (5) идентификатор индикатора
   void              SetGroup(const ENUM_INDICATOR_GROUP group)      { this.SetProperty(INDICATOR_PROP_GROUP,group);                         }
   void              SetEmptyValue(const double value)               { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value);                   }
   void              SetName(const string name)                      { this.SetProperty(INDICATOR_PROP_NAME,name);                           }
   void              SetShortName(const string shortname)            { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname);                 }
   void              SetID(const int id)                             { this.SetProperty(INDICATOR_PROP_ID,id);                               }
   void              SetBuffersTotal(const int buffers_total)        { this.m_buffers_total=buffers_total;                                   }
   
//--- Возвращает (1) статус, (2) группу, (3) таймфрейм, (4) тип, (5) хэндл, (6) идентификатор,
//--- (7) пустое значение буферов, (8) имя, (9) короткое имя, (10) символ, (11) количество буферов индикатора
   ENUM_INDICATOR_STATUS Status(void)                          const { return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);}
   ENUM_INDICATOR_GROUP  Group(void)                           const { return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP);  }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME);   }
   ENUM_INDICATOR    TypeIndicator(void)                       const { return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE);         }
   int               Handle(void)                              const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE);                  }
   int               ID(void)                                  const { return (int)this.GetProperty(INDICATOR_PROP_ID);                      }
   double            EmptyValue(void)                          const { return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE);                  }
   string            Name(void)                                const { return this.GetProperty(INDICATOR_PROP_NAME);                         }
   string            ShortName(void)                           const { return this.GetProperty(INDICATOR_PROP_SHORTNAME);                    }
   string            Symbol(void)                              const { return this.GetProperty(INDICATOR_PROP_SYMBOL);                       }
   int               BuffersTotal(void)                        const { return this.m_buffers_total;                                          }
   
//--- Возвращает описание (1) типа, (2) статуса, (3) группы, (4) таймфрейма, (5) пустого значения индикатора, (6) параметра массива m_mql_param
   string            GetTypeDescription(void)                  const { return m_ind_type_description;                                        }
   string            GetStatusDescription(void)                const;
   string            GetGroupDescription(void)                 const;
   string            GetTimeframeDescription(void)             const;
   string            GetEmptyValueDescription(void)            const;
   string            GetMqlParamDescription(const int index)   const;

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

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

Чтобы класс видел объект класса-коллекции индикаторных данных, мы подключили его файл SeriesDataInd.mqh в разделе подключаемых файлов.

В защищённой области класса объявили объект класса-коллекции индикаторных данных m_series_data.

Переменная m_buffers_total будет хранить общее количество рисуемых буферов индикатора. Объекты данных всех этих буферов будут храниться в коллекции данных буферов индикатора.

Методы  SetBuffersTotal() и BuffersTotal() устанавливают/возвращают общее количество рисуемых буферов индикатора.

Метод GetSeriesData() возвращает указатель на коллекцию данных индикаторных буферов.


Теперь откроем файл класса коллекции индикаторов \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh и внесём в него необходимые доработки.

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

//--- (1) Создаёт, (2) добавляет в список-коллекцию новый объект-индикатор и устанавливает ему идентификатор
   CIndicatorDE           *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT);
   int                     AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0);

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

   int                     CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int buffers_total,
                                       ENUM_INDICATOR_GROUP group,
                                       MqlParam &mql_param[]);

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

//--- Возвращает (1) объект-индикатор по его идентификатору, (2) объект данных таймсерии буфера индикатора по времени
   CIndicatorDE           *GetIndByID(const uint id);
   CDataInd               *GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time);

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

//--- Создаёт (1) таймсерию данных указанного индикатора, (2) все используемые таймсерии всех индикаторов коллекции
   bool                    SeriesCreate(CIndicatorDE *indicator,const uint required=0);
   bool                    SeriesCreateAll(const uint required=0);
//--- Обновляет данные буферов всех индикаторов
   void                    SeriesRefreshAll(void);
   void                    SeriesRefresh(const int ind_id);
//--- Возвращает по (1) времени, (2) номеру бара значение буфера указанного по идентификатору индикатора
   double                  GetBufferValue(const uint ind_id,const int buffer_num,const datetime time);
   double                  GetBufferValue(const uint ind_id,const int buffer_num,const uint shift);

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

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

В реализации метода AddIndicatorToList() допишем установку количества буферов индикатора и создание его таймсерии:

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

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

Во всех таких методах класса уже были внесены эти изменения. Рассмотрим только некоторые.

Метод создания индикатора Accelerator Oscillator:

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

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

Метод создания индикатора Average Directional Movement Index:

//+------------------------------------------------------------------+
//| Создаёт новый объект-индикатор                                   |
//| Average Directional Movement Index                               |
//| и помещает его в список-коллекцию                                |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int adx_period=14)
  {
//--- Записываем в массив структур параметров необходимые параметры индикатора
   ::ArrayResize(this.m_mql_param,1);
   this.m_mql_param[0].type=TYPE_INT;
   this.m_mql_param[0].integer_value=adx_period;
//--- Создаём объект-индикатор
   CIndicatorDE *indicator=this.CreateIndicator(IND_ADX,this.m_mql_param,symbol,timeframe);
//--- Возвращаем хэндл индикатора, полученный в результате добавления объекта в список-коллекцию
   return this.AddIndicatorToList(indicator,id,3);
  }
//+------------------------------------------------------------------+

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

Метод создания пользовательского индикатора:

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

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

Метод, возвращающий указатель на объект данных таймсерии буфера индикатора по времени:

//+------------------------------------------------------------------+
//| Возвращает объект данных таймсерии буфера индикатора по времени  |
//+------------------------------------------------------------------+
CDataInd *CIndicatorsCollection::GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time)
  {
   CIndicatorDE *indicator=this.GetIndByID(ind_id);
   if(indicator==NULL) return NULL;
   CSeriesDataInd *buffers_data=indicator.GetSeriesData();
   if(buffers_data==NULL) return NULL;
   return buffers_data.GetIndDataByTime(buffer_num,time);
  }
//+------------------------------------------------------------------+

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

Метод, создающий таймсерию данных указанного индикатора:

//+------------------------------------------------------------------+
//| Создаёт таймсерию данных указанного индикатора                   |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::SeriesCreate(CIndicatorDE *indicator,const uint required=0)
  {
   if(indicator==NULL)
      return false;
   CSeriesDataInd *buffers_data=indicator.GetSeriesData();
   if(buffers_data!=NULL)
     {
      buffers_data.SetSymbolPeriod(indicator.Symbol(),indicator.Timeframe());
      buffers_data.SetIndHandle(indicator.Handle());
      buffers_data.SetIndID(indicator.ID());
      buffers_data.SetIndBuffersTotal(indicator.BuffersTotal());
      buffers_data.SetRequiredUsedData(required);
     }
   return(buffers_data!=NULL ? buffers_data.Create(required)>0 : false);
  }
//+------------------------------------------------------------------+

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

Метод, создающий все используемые таймсерии всех индикаторов коллекции:

//+------------------------------------------------------------------+
//| Создаёт все используемые таймсерии всех индикаторов коллекции    |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::SeriesCreateAll(const uint required=0)
  {
   bool res=true;
   for(int i=0;i<m_list.Total();i++)
     {
      CIndicatorDE *indicator=m_list.At(i);
      if(!this.SeriesCreate(indicator,required))
         res&=false;
     }
   return res;
  }
//+------------------------------------------------------------------+

Здесь: в цикле по общему количеству объектов-индикаторов в коллекции получаем указатель на очередной объект-индикатор и к переменной res добавляем результат создания таймсерии данных его буферов при помощи вышерассмотренного метода. По окончании цикла возвращаем значение переменной res. Если хоть одна таймсерия данных буферов создана не была, то эта переменная будет иметь значение false.

Метод обновления данные буферов всех индикаторов коллекции:

//+------------------------------------------------------------------+
//| Обновляет данные буферов всех индикаторов                        |
//+------------------------------------------------------------------+
void CIndicatorsCollection::SeriesRefreshAll(void)
  {
   for(int i=0;i<m_list.Total();i++)
     {
      CIndicatorDE *indicator=m_list.At(i);
      if(indicator==NULL) continue;
      CSeriesDataInd *buffers_data=indicator.GetSeriesData();
      if(buffers_data==NULL) continue;
      buffers_data.Refresh();
     }
  }
//+------------------------------------------------------------------+

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

Метод обновления данных буферов указанного индикатора:

//+------------------------------------------------------------------+
//| Обновляет данные буферов указанного индикатора                   |
//+------------------------------------------------------------------+
void CIndicatorsCollection::SeriesRefresh(const int ind_id)
  {
   CIndicatorDE *indicator=this.GetIndByID(ind_id);
   if(indicator==NULL) return;
   CSeriesDataInd *buffers_data=indicator.GetSeriesData();
   if(buffers_data==NULL) return;
   buffers_data.Refresh();
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает по времени бара значение буфера                       |
//|  указанного по идентификатору индикатора                         |
//+------------------------------------------------------------------+
double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const datetime time)
  {
   CIndicatorDE *indicator=GetIndByID(ind_id);
   if(indicator==NULL) return EMPTY_VALUE;
   CSeriesDataInd *series=indicator.GetSeriesData();
   return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,time) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает по номеру бара значение буфера                        |
//|  указанного по идентификатору индикатора                         |
//+------------------------------------------------------------------+
double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const uint shift)
  {
   CIndicatorDE *indicator=GetIndByID(ind_id);
   if(indicator==NULL) return EMPTY_VALUE;
   CSeriesDataInd *series=indicator.GetSeriesData();
   return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,shift) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

Методы идентичны друг другу за исключением того, что в первый передаётся время открытия бара, а во второй — индекс бара.

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

Для связи классов библиотеки с "внешним миром" служит основной класс библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh — в нём располагаются методы доступа ко всем методам библиотеки из программ.

В публичной секции класса добавим методы для работы с коллекциями данных индикаторных буферов:
//--- Возвращает (1) коллекцию индикаторов, (2) список индикаторов из коллекции 
   CIndicatorsCollection *GetIndicatorsCollection(void)                                { return &this.m_indicators;              }
   CArrayObj           *GetListIndicators(void)                                        { return this.m_indicators.GetList();     }
   
//--- Создаёт все используемые таймсерии всех индикаторов коллекции
   bool                 IndicatorSeriesCreateAll(void)                                 { return this.m_indicators.SeriesCreateAll();}
//--- Обновляет данные буферов всех индикаторов
   void                 IndicatorSeriesRefreshAll(void)                                { this.m_indicators.SeriesRefreshAll();   }
   void                 IndicatorSeriesRefresh(const int ind_id)                       { this.m_indicators.SeriesRefresh(ind_id);}
   
//--- Возвращает значение указанного буфера по (1) времени, (2) номеру бара указанного по идентификатору индикатора
   double               IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const datetime time)
                          { return this.m_indicators.GetBufferValue(ind_id,buffer_num,time); }
   double               IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const uint shift)
                          { return this.m_indicators.GetBufferValue(ind_id,buffer_num,shift); }

Все эти методы вызывают соответствующие методы, добавленные нами в класс коллекции индикаторов выше.

В конструкторе класса создадим новый таймер для обновления коллекций данных индикаторных буферов:

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

В таймере класса добавим блоки кода для работы с таймсериями данных индикаторных буферов по таймеру и по тику (в тестере):

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- Если это не тестер - работаем с событиями коллекций по таймеру
   if(!this.IsTester())
     {
   //--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекций ордеров, сделок и позиций
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Таймер коллекции аккаунтов
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекции аккаунтов
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Таймер1 коллекции символов (обновление котировочных данных символов в коллекции)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- Если пауза завершилась - обновляем котировочные данные всех символов в коллекции
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Таймер2 коллекции символов (обновление всех данных всех символов в коллекции и отслеживание событий символов и списка символов в окне обзора рынка)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- Если пауза завершилась
         if(cnt4.IsTimeDone())
           {
            //--- обновляем данные и работаем с событиями всех символов в коллекции
            this.SymbolEventsControl();
            //--- Если работаем со списком из обзора рынка - проверяем события окна обзора рынка
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Таймер торгового класса
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком отложенных запросов
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Таймер коллекции таймсерий
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий (обновляем все кроме текущей)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Таймер коллекции таймсерий данных индикаторных буферов
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий индикаторных данных (обновляем все кроме текущей)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
     }
//--- Если тестер - работаем с событиями коллекций по тику
   else
     {
      //--- работаем с событиями коллекций ордеров, сделок и позиций по тику
      this.TradeEventsControl();
      //--- работаем с событиями коллекции аккаунтов по тику
      this.AccountEventsControl();
      //--- обновляем котировочные данные всех символов в коллекции по тику
      this.m_symbols.RefreshRates();
      //--- работаем с событиями всех символов в коллекции по тику
      this.SymbolEventsControl();
      //--- работаем со списком отложенных запросов по тику
      this.m_trading.OnTimer();
      //--- работаем со списком таймсерий по тику
      this.SeriesRefresh(data_calculate);
      //--- работаем со списком таймсерий индикаторных буферов по тику
      this.IndicatorSeriesRefreshAll();
     }
  }
//+------------------------------------------------------------------+

Это все доработки на сегодня.


Тестирование

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

Тестировать будем точно так же, как и в прошлом советнике — четыре индикатора, два из которых стандартные, и два — пользовательские.

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

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

//--- Указатели на объекты данных индикаторов
CDataInd      *data_ma1_0=NULL;
CDataInd      *data_ma1_1=NULL;
CDataInd      *data_ma2_0=NULL;
CDataInd      *data_ma2_1=NULL;
CDataInd      *data_ama1_0=NULL;
CDataInd      *data_ama1_1=NULL;
CDataInd      *data_ama2_0=NULL;
CDataInd      *data_ama2_1=NULL;
//+------------------------------------------------------------------+

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

//--- Создание индикаторов
   ArrayResize(param_ma1,4);
   //--- Имя индикатора 1
   param_ma1[0].type=TYPE_STRING;
   param_ma1[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Период расчёта
   param_ma1[1].type=TYPE_INT;
   param_ma1[1].integer_value=13;
   //--- Горизонтальное смещение
   param_ma1[2].type=TYPE_INT;
   param_ma1[2].integer_value=0;
   //--- Метод сглаживания
   param_ma1[3].type=TYPE_INT;
   param_ma1[3].integer_value=MODE_SMA;
   //--- Создаём индикатор 1
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,1,INDICATOR_GROUP_TREND,param_ma1);
   
   ArrayResize(param_ma2,5);
   //--- Имя индикатора 2
   param_ma2[0].type=TYPE_STRING;
   param_ma2[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Период расчёта
   param_ma2[1].type=TYPE_INT;
   param_ma2[1].integer_value=13;
   //--- Горизонтальное смещение
   param_ma2[2].type=TYPE_INT;
   param_ma2[2].integer_value=0;
   //--- Метод сглаживания
   param_ma2[3].type=TYPE_INT;
   param_ma2[3].integer_value=MODE_SMA;
   //--- Цена расчёта
   param_ma2[4].type=TYPE_INT;
   param_ma2[4].integer_value=PRICE_OPEN;
   //--- Создаём индикатор 2
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,1,INDICATOR_GROUP_TREND,param_ma2);

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаление графических объектов советника по префиксу имени объектов
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Удаление созданных объектов данных индикаторов MA
   if(CheckPointer(data_ma1_0)==POINTER_DYNAMIC)
      delete data_ma1_0;
   if(CheckPointer(data_ma1_1)==POINTER_DYNAMIC)
      delete data_ma1_1;
   if(CheckPointer(data_ma2_0)==POINTER_DYNAMIC)
      delete data_ma2_0;
   if(CheckPointer(data_ma2_1)==POINTER_DYNAMIC)
      delete data_ma2_1;
//--- Удаление созданных объектов данных индикаторов AMA
   if(CheckPointer(data_ama1_0)==POINTER_DYNAMIC)
      delete data_ama1_0;
   if(CheckPointer(data_ama1_1)==POINTER_DYNAMIC)
      delete data_ama1_1;
   if(CheckPointer(data_ama2_0)==POINTER_DYNAMIC)
      delete data_ama2_0;
   if(CheckPointer(data_ama2_1)==POINTER_DYNAMIC)
      delete data_ama2_1;
//--- Деинициализация библиотеки
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+

По сравнению с прошлым советником теперь обработчик OnTick() стал намного лаконичнее:

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

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      engine.EventsHandling();      // Работа с событиями
     }
      
//--- Получаем значения индикаторов по их идентификаторам из коллекций данных их буферов с баров 0 и 1
   double ma1_value0=engine.IndicatorGetBufferValue(MA1,0,0), ma1_value1=engine.IndicatorGetBufferValue(MA1,0,1);
   double ma2_value0=engine.IndicatorGetBufferValue(MA2,0,0), ma2_value1=engine.IndicatorGetBufferValue(MA2,0,1);
   double ama1_value0=engine.IndicatorGetBufferValue(AMA1,0,0), ama1_value1=engine.IndicatorGetBufferValue(AMA1,0,1);
   double ama2_value0=engine.IndicatorGetBufferValue(AMA2,0,0), ama2_value1=engine.IndicatorGetBufferValue(AMA2,0,1);
   
//--- Выводим в комментарий на графике значения индикаторных буферов из объектов данных
   Comment
     (
      "ma1(1)=",DoubleToString(ma1_value1,6),", ma1(0)=",DoubleToString(ma1_value0,6),"  ",
      "ma2(1)=",DoubleToString(ma2_value1,6),", ma2(0)=",DoubleToString(ma2_value0,6),", \n",
      "ama1(1)=",DoubleToString(ama1_value1,6),", ama1(0)=",DoubleToString(ama1_value0,6),"  ",
      "ama1(1)=",DoubleToString(ama2_value1,6),", ama1(0)=",DoubleToString(ama2_value0,6)
     );
   
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
  }
//+------------------------------------------------------------------+

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


Для наглядности на график нанесены те же индикаторы с точно такими же настройками — данные индикаторов в комментариях на графике и в окне данных (Ctrl+D) совпадают и значения на текущем баре обновляются.

Что дальше

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

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

К содержанию

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

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