English 中文 Español Deutsch 日本語 Português
Работа с таймсериями в библиотеке DoEasy (Часть 47): Мультипериодные мультисимвольные стандартные индикаторы

Работа с таймсериями в библиотеке DoEasy (Часть 47): Мультипериодные мультисимвольные стандартные индикаторы

MetaTrader 5Торговые системы | 22 июля 2020, 11:43
2 503 4
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

В данной статье рассмотрим создание необходимых методов для создания пользовательского индикатора на основе стандартного индикатора AC (Accelerator Oscillator). Все методы будут работать и для других стандартных индикаторов с небольшими доработками — их создадим в последующих статьях.

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

  • Идентификатор множества буферов одного индикатора — позволит идентифицировать и выбирать все объекты-буферы, принадлежащие одному стандартному индикатору, который использует эти буферы. У нас может в одном пользовательском индикаторе использоваться несколько одинаковых стандартных индикаторов, но с разными параметрами (при создании сложного пользовательского индикатора на основе нескольких стандартных). Этот идентификатор позволит идентифицировать каждый из используемых объектов-буферов по его принадлежности стандартному индикатору.
  • Хэндл индикатора, использующего буфер — в каждом объекте-буфере, который используется для расчёта стандартного индикатора, будем прописывать хэндл созданного стандартного индикатора для работы с ним из любого объекта-буфера, принадлежащего этому индикатору.
  • Тип индикатора, использующего буфер — сюда будем вписывать тип индикатора из перечисления типов индикаторов ENUM_INDICATOR. Тоже позволит идентифицировать и выбирать объекты-буферы по принадлежности к типу стандартного индикатора.
  • Наименование индикатора, использующего буфер — здесь будем хранить имя стандартного индикатора, использующего объект-буфер для вывода его описания.

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


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

В первую очередь дополним файл \MQL5\Include\DoEasy\Datas.mqh данными для вывода сообщений.

Впишем идентификаторы новых сообщений:

   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT,          // Индекс следующего по счёту рисуемого буфера
   MSG_LIB_TEXT_BUFFER_TEXT_ID,                       // Идентификатор буферов индикатора
   MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE,               // Хэндл индикатора, использующего буфер
   MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE,                 // Тип индикатора, использующего буфер
   MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME,                // Период данных буфера (таймфрейм)
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS,                   // Статус буфера
   MSG_LIB_TEXT_BUFFER_TEXT_TYPE,                     // Тип буфера
   MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE,                   // Активен
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE,               // Код стрелки
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT,              // Смещение стрелок по вертикали
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN,               // Количество начальных баров без отрисовки и значений в DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE,                // Тип графического построения
   MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA,                // Отображение значений построения в окне DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_SHIFT,                    // Сдвиг графического построения индикатора по оси времени в барах
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE,               // Стиль линии отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH,               // Толщина линии отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE,               // Размер значка стрелки
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM,                // Количество цветов
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR,                    // Цвет отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE,              // Пустое значение для построения, для которого нет отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL,                   // Символ буфера
   MSG_LIB_TEXT_BUFFER_TEXT_LABEL,                    // Имя индикаторной графической серии, отображаемое в окне DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME,                 // Наименование индикатора, использующего буфер
   

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

   {"Индекс следующего по счёту рисуемого буфера","Index of the next drawable buffer"},
   {"Идентификатор буферов индикатора","Indicator Buffer Id"},
   {"Хэндл индикатора, использующего буфер","Indicator handle that uses the buffer"},
   {"Тип индикатора, использующего буфер","Indicator type that uses the buffer"},
   {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"},
   {"Статус буфера","Buffer status"},
   {"Тип буфера","Buffer type"},
   {"Активен","Active"},
   {"Код стрелки","Arrow code"},
   {"Смещение стрелок по вертикали","Vertical shift of arrows"},
   {"Количество начальных баров без отрисовки и значений в DataWindow","Number of initial bars without drawing and values in the DataWindow"},
   {"Тип графического построения","Type of graphical construction"},
   {"Отображение значений построения в окне DataWindow","Display construction values in the DataWindow"},
   {"Сдвиг графического построения индикатора по оси времени в барах","Shift of indicator plotting along the time axis in bars"},
   {"Стиль линии отрисовки","Drawing line style "},
   {"Толщина линии отрисовки","The thickness of the drawing line"},
   {"Размер значка стрелки","Arrow icon size"},
   {"Количество цветов","The number of colors"},
   {"Цвет отрисовки","The index of a buffer containing the drawing color"},
   {"Пустое значение для построения, для которого нет отрисовки","An empty value for plotting, for which there is no drawing"},
   {"Символ буфера","Buffer Symbol"},
   {"Имя индикаторной графической серии, отображаемое в окне DataWindow","The name of the indicator graphical series to display in the DataWindow"},
   {"Наименование индикатора, использующего буфер","The name of the indicator that uses the buffer"},
   {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"},
   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","The number of indicator buffers is incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","The maximum number of indicator buffers has been reached"},


В файле \MQL5\Include\DoEasy\Defines.mqh допишем все необходимые дополнения для реализуемых сегодня задач.

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

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
//--- "Описание функции с номером строки ошибки"
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Описание функции"
#define COUNTRY_LANG                   ("Russian")                // Язык страны
#define END_TIME                       (D'31.12.3000 23:59:59')   // Конечная дата для запросов данных истории счёта
#define TIMER_FREQUENCY                (16)                       // Минимальная частота таймера библиотеки в милисекундах
#define TOTAL_TRADE_TRY                (5)                        // Количество торговых попыток по умолчанию
#define IND_COLORS_TOTAL               (64)                       // Максимально возможное количество цветов индикаторного буфера
#define IND_BUFFERS_MAX                (512)                      // Максимально возможное количество буферов индикатора
//--- Стандартные звуки

Ранее константа имела наименование TOTAL_TRY. Это не информативно, и так как в дальнейшем у нас могут появиться ещё какие-либо константы, указывающие на количество попыток, то добавление в наименование константы принадлежности попыток к тому или иному действию (в данном случае "TRADE" — принадлежность к торговым попыткам), более информативно, и не потребуется менять название этой константы при добавлении новых констант для иных "количеств попыток"

В перечисление возможных событий таймсерий добавим новое событие:

//+------------------------------------------------------------------+
//| Список возможных событий таймсерий                               |
//+------------------------------------------------------------------+
enum ENUM_SERIES_EVENT
  {
   SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE,        // Нет события
   SERIES_EVENTS_NEW_BAR,                                   // Событие "Новый бар"
   SERIES_EVENTS_MISSING_BARS,                              // Событие "Пропущены бары"
  };
#define SERIES_EVENTS_NEXT_CODE  (SERIES_EVENTS_MISSING_BARS+1)   // Код следующего события после события "Пропущены бары"
//+------------------------------------------------------------------+

И, соответственно, код следующего события теперь будет основан на новой константе.

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

//+------------------------------------------------------------------+
//| Целочисленные свойства буфера                                    |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_INTEGER
  {
   BUFFER_PROP_INDEX_PLOT = 0,                              // Порядковый номер рисуемого буфера
   BUFFER_PROP_STATUS,                                      // Статус (по стилю рисования) буфера (из перечисления ENUM_BUFFER_STATUS)
   BUFFER_PROP_TYPE,                                        // Тип буфера (из перечисления ENUM_BUFFER_TYPE)
   BUFFER_PROP_TIMEFRAME,                                   // Период данных буфера (таймфрейм)
   BUFFER_PROP_ACTIVE,                                      // Флаг использования буфера
   BUFFER_PROP_DRAW_TYPE,                                   // Тип графического построения (из перечисления ENUM_DRAW_TYPE)
   BUFFER_PROP_ARROW_CODE,                                  // Код стрелки для стиля DRAW_ARROW
   BUFFER_PROP_ARROW_SHIFT,                                 // Смещение стрелок по вертикали для стиля DRAW_ARROW
   BUFFER_PROP_LINE_STYLE,                                  // Стиль линии отрисовки
   BUFFER_PROP_LINE_WIDTH,                                  // Толщина линии отрисовки
   BUFFER_PROP_DRAW_BEGIN,                                  // Количество начальных баров без отрисовки и значений в DataWindow
   BUFFER_PROP_SHOW_DATA,                                   // Признак отображения значений построения в окне DataWindow
   BUFFER_PROP_SHIFT,                                       // Сдвиг графического построения индикатора по оси времени в барах
   BUFFER_PROP_COLOR_INDEXES,                               // Количество цветов
   BUFFER_PROP_COLOR,                                       // Цвет отрисовки
   BUFFER_PROP_INDEX_BASE,                                  // Индекс базового буфера данных
   BUFFER_PROP_INDEX_NEXT_BASE,                             // Индекс массива для назначения следующим индикаторным буфером
   BUFFER_PROP_INDEX_NEXT_PLOT,                             // Индекс следующего по счёту рисуемого буфера
   BUFFER_PROP_ID,                                          // Идентификатор множества буферов одного индикатора
   BUFFER_PROP_IND_HANDLE,                                  // Хэндл индикатора, использующего буфер
   BUFFER_PROP_IND_TYPE,                                    // Тип индикатора, использующего буфер
   BUFFER_PROP_NUM_DATAS,                                   // Количество буферов данных
   BUFFER_PROP_INDEX_COLOR,                                 // Индекс буфера цвета
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (23)                      // Общее количество целочисленных свойств буфера
#define BUFFER_PROP_INTEGER_SKIP  (2)                       // Количество неиспользуемых в сортировке свойств буфера
//+------------------------------------------------------------------+
//| Вещественные свойства буфера                                     |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_DOUBLE
  {
   BUFFER_PROP_EMPTY_VALUE = BUFFER_PROP_INTEGER_TOTAL,     // Пустое значение для построения, для которого нет отрисовки
  }; 
#define BUFFER_PROP_DOUBLE_TOTAL  (1)                       // Общее количество вещественных свойств буфера
#define BUFFER_PROP_DOUBLE_SKIP   (0)                       // Количество неиспользуемых в сортировке свойств буфера
//+------------------------------------------------------------------+
//| Строковые свойства буфера                                        |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_STRING
  {
   BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Символ буфера
   BUFFER_PROP_LABEL,                                       // Имя индикаторной графической серии, отображаемое в окне DataWindow
   BUFFER_PROP_IND_NAME,                                    // Наименование индикатора, использующего буфер
  };
#define BUFFER_PROP_STRING_TOTAL  (3)                       // Общее количество строковых свойств буфера
//+------------------------------------------------------------------+

Увеличим общее количество целочисленных свойств с 20 на 23, и строковых свойств с 2 на 3.

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки буферов                            |
//+------------------------------------------------------------------+
#define FIRST_BUFFER_DBL_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP)
#define FIRST_BUFFER_STR_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BUFFER_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_BUFFER_INDEX_PLOT = 0,                           // Сортировать по порядковому номеру рисуемого буфера
   SORT_BY_BUFFER_STATUS,                                   // Сортировать по стилю рисования (статусу) буфера (из перечисления ENUM_BUFFER_STATUS)
   SORT_BY_BUFFER_TYPE,                                     // Сортировать по типу буфера (из перечисления ENUM_BUFFER_TYPE)
   SORT_BY_BUFFER_TIMEFRAME,                                // Сортировать по периоду данных буфера (таймфрейму)
   SORT_BY_BUFFER_ACTIVE,                                   // Сортировать по флагу использования буфера
   SORT_BY_BUFFER_DRAW_TYPE,                                // Сортировать по типу графического построения (из перечисления ENUM_DRAW_TYPE)
   SORT_BY_BUFFER_ARROW_CODE,                               // Сортировать по коду стрелки для стиля DRAW_ARROW
   SORT_BY_BUFFER_ARROW_SHIFT,                              // Сортировать по смещению стрелок по вертикали для стиля DRAW_ARROW
   SORT_BY_BUFFER_LINE_STYLE,                               // Сортировать по стилю линии отрисовки
   SORT_BY_BUFFER_LINE_WIDTH,                               // Сортировать по толщине линии отрисовки
   SORT_BY_BUFFER_DRAW_BEGIN,                               // Сортировать по количеству начальных баров без отрисовки и значений в DataWindow
   SORT_BY_BUFFER_SHOW_DATA,                                // Сортировать по признаку отображения значений построения в окне DataWindow
   SORT_BY_BUFFER_SHIFT,                                    // Сортировать по сдвигу графического построения индикатора по оси времени в барах
   SORT_BY_BUFFER_COLOR_INDEXES,                            // Сортировать по количеству цветов
   SORT_BY_BUFFER_COLOR,                                    // Сортировать по цвету отрисовки
   SORT_BY_BUFFER_INDEX_BASE,                               // Сортировать по индексу базового буфера данных
   SORT_BY_BUFFER_INDEX_NEXT_BASE,                          // Сортировать по индексу массива для назначения следующим индикаторным буфером
   SORT_BY_BUFFER_INDEX_NEXT_PLOT,                          // Сортировать по индексу следующего по счёту рисуемого буфера
   SORT_BY_BUFFER_ID,                                       // Сортировать по идентификатору множества буферов одного индикатора
   SORT_BY_BUFFER_IND_HANDLE,                               // Сортировать по хэндлу индикатора, использующего буфер
   SORT_BY_BUFFER_IND_TYPE,                                 // Сортировать по типу индикатора, использующего буфер
//--- Сортировка по вещественным свойствам
   SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP,      // Сортировать по пустому значению для построения, для которого нет отрисовки
//--- Сортировка по строковым свойствам
   SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP,           // Сортировать по символу буфера
   SORT_BY_BUFFER_LABEL,                                    // Сортировать по имени индикаторной графической серии, отображаемого в окне DataWindow
   SORT_BY_BUFFER_IND_NAME,                                 // Сортировать по наименованию индикатора, использующего буфер
  };
//+------------------------------------------------------------------+


Для фиксации пропущенных баров при, например, потере и восстановлении связи, немного доработаем класс объекта "Новый бар" в файле \MQL5\Include\DoEasy\Objects\Series\NewBarObj.mqh. Всё, что нам нужно сделать — это добавить подсчёт количества баров, прошедших между двумя событиями "Новый бар". Если рассчитанное значение больше 1 — значит были пропущены бары истории, либо их совсем нет на сервере (эта ситуация пока не рассматривается).

В приватную секцию класса добавим четыре новых переменных-членов класса для хранения времени прошлого события "Новый бар" для ручного и автоматического управления временем, для хранения количества секунд и баров, прошедших между двумя событиями "Новый бар"

//+------------------------------------------------------------------+
//| Класс объекта "Новый бар"                                        |
//+------------------------------------------------------------------+
class CNewBarObj : public CBaseObj
  {
private:
   string            m_symbol;                                    // Символ
   ENUM_TIMEFRAMES   m_timeframe;                                 // Таймфрейм
   datetime          m_new_bar_time;                              // Время нового бара для автоматического управления временем
   datetime          m_prev_time;                                 // Прошлое время для автоматического управления временем
   datetime          m_new_bar_time_manual;                       // Время нового бара для ручного управления временем
   datetime          m_prev_time_manual;                          // Прошлое время для ручного управления временем
   datetime          m_prev_new_bar_time;                         // Прошлое время нового бара для автоматического управления временем
   datetime          m_prev_new_bar_time_manual;                  // Прошлое время нового бара для ручного управления временем
   long              m_seconds_between;                           // Количество секунд между двумя событиями "Новый бар"
   int               m_bars_between;                              // Количество баров между двумя событиями "Новый бар"
//--- Возвращает дату текущего бара
   datetime          GetLastBarDate(const datetime time);
public:

В публичной секции класса переименуем методы для установки и возврата таймфрейма объекта (ранее использовалось "Period", а использование "Timeframe" для хранения таймфрейма более информативно), и добавим методы возврата значений вновь объявленных переменных:

public:
//--- Устанавливает (1) символ, (2) таймфрейм
   void              SetSymbol(const string symbol)               { this.m_symbol=(symbol==NULL || symbol==""   ? ::Symbol() : symbol);                     }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe){ this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); }
//--- Сохраняет время нового бара при ручном управлении временем
   void              SaveNewBarTime(const datetime time)          { this.m_prev_time_manual=this.GetLastBarDate(time);                                      }
//--- Возвращает (1) символ, (2) таймфрейм
   string            Symbol(void)                           const { return this.m_symbol;             }
   ENUM_TIMEFRAMES   Timeframe(void)                        const { return this.m_timeframe;          }
//--- Возвращает (1) время нового бара, (2) время прошлого нового бара, количество (3) секунд, (4) баров между двумя последними событиями
   datetime          TimeNewBar(void)                       const { return this.m_new_bar_time;       }
   datetime          TimePrevNewBar(void)                   const { return this.m_prev_new_bar_time;  }
   long              SecondsBetweenNewBars(void)            const { return this.m_seconds_between;    }
   int               BarsBetweenNewBars(void)               const { return this.m_bars_between;       }
//--- Возвращает флаг открытия нового бара при (1) автоматическом, (2) ручном управлении временем
   bool              IsNewBar(const datetime time);
   bool              IsNewBarManual(const datetime time);
//--- Конструкторы
                     CNewBarObj(void) : m_symbol(::Symbol()),
                                        m_timeframe((ENUM_TIMEFRAMES)::Period()),
                                        m_prev_time(0),m_new_bar_time(0),
                                        m_prev_time_manual(0),m_new_bar_time_manual(0) {}
                     CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe);
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),
                                                                              m_timeframe(timeframe),
                                                                              m_seconds_between(0),
                                                                              m_bars_between(0)
  {
   this.m_prev_new_bar_time=this.m_prev_new_bar_time_manual=this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает флаг открытия нового бара                             |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBar(const datetime time)
  {
//--- Получаем время текущего бара
   datetime tm=this.GetLastBarDate(time);
   if(tm<=0)
      return false;
//--- Если прошлое и текущее время равны нулю - это первый запуск
   if(this.m_prev_time+this.m_new_bar_time==0)
     {
      //--- устанавливаем время открытия нового бара,
      //--- устанавливаем время прошлого бара как текущее и возвращаем false
      this.m_new_bar_time=this.m_prev_time=tm;
      return false;
     }
//--- Если прошлое время меньше времени открытия текущего бара - это новый бар
   if(this.m_prev_time>0 && this.m_prev_time<tm)
     {
      this.m_prev_new_bar_time=this.m_prev_time;
      this.m_seconds_between=tm-m_prev_time;
      this.m_bars_between=int(this.m_seconds_between/::PeriodSeconds(this.m_timeframe));
      //--- устанавливаем время открытия нового бара,
      //--- устанавливаем прошлое время как текущее и возвращаем true
      this.m_new_bar_time=this.m_prev_time=tm;
      return true;
     }
//--- в остальных случаях возвращаем false
   return false;
  }
//+------------------------------------------------------------------+

В методе, возвращающем флаг нового бара при ручном управлении, эти данные рассчитывать не нужно — данные о пропущенных барах всегда будут рассчитываться автоматически. Но в этом методе будем сохранять время прошлого "нового бара" при ручном управлении и исправим ошибку присвоения времени нового бара (ранее сохранялось время в переменную для автоматического управления временем):

//+------------------------------------------------------------------+
//| Возвращает флаг открытия нового бара при ручном управлении       |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBarManual(const datetime time)
  {
//--- Получаем время текущего бара
   datetime tm=this.GetLastBarDate(time);
   if(tm<=0)
      return false;
//--- Если прошлое и текущее время равны нулю - это первый запуск
   if(this.m_prev_time_manual+this.m_new_bar_time_manual==0)
     {
      //--- устанавливаем время открытия нового бара,
      //--- устанавливаем время прошлого бара как текущее и возвращаем false
      this.m_new_bar_time_manual=this.m_prev_time_manual=tm;
      return false;
     }
//--- Если прошлое время меньше времени открытия текущего бара - это новый бар
   if(this.m_prev_time_manual>0 && this.m_prev_time_manual<tm)
     {
      this.m_prev_new_bar_time_manual=this.m_prev_time_manual;
      //--- устанавливаем время открытия нового бара и возвращаем true
      //--- Предыдущее время сохранять как текущее необходимо самостоятельно из программы методом SaveNewBarTime()
      //--- До тех пор, пока принудительно из программы не будет установлено прошлое время как текущее,
      //--- метод будет возвращать флаг нового бара, позволяя завершить все необходимые действия на новом баре.
      this.m_new_bar_time_manual=tm;
      return true;
     }
//--- в остальных случаях возвращаем false
   return false;
  }
//+------------------------------------------------------------------+


Достаточно часто можно увидеть в журнале терминала записи от библиотеки об ошибке получения баров истории. Это происходит по причине, что библиотека просматривает всю историю, и даже там, где нет исторических данных на конкретном символе. Об этом выводится запись и осуществляется переход к следующему бару истории. Сделано так для возможности отладки методов библиотеки при работе с таймсериями. Но там, где точно не нужно просматривать ошибки получения исторических данных, мы уберём эти записи. Для этого нам нужно в классе объекта "Бар", в файле \MQL5\Include\DoEasy\Objects\Series\Bar.mqh вписать ещё один конструктор — без параметров:

//+------------------------------------------------------------------+
//| Класс "Бар"                                                      |
//+------------------------------------------------------------------+
class CBar : public CBaseObj
  {
private:
   MqlDateTime       m_dt_struct;                                 // Структура даты
   int               m_digits;                                    // Значение Digits символа
   string            m_period_description;                        // Строковое описание таймфрейма
   long              m_long_prop[BAR_PROP_INTEGER_TOTAL];         // Целочисленные свойства
   double            m_double_prop[BAR_PROP_DOUBLE_TOTAL];        // Вещественные свойства
   string            m_string_prop[BAR_PROP_STRING_TOTAL];        // Строковые свойства

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство бара
   int               IndexProp(ENUM_BAR_PROP_DOUBLE property)     const { return(int)property-BAR_PROP_INTEGER_TOTAL;                        }
   int               IndexProp(ENUM_BAR_PROP_STRING property)     const { return(int)property-BAR_PROP_INTEGER_TOTAL-BAR_PROP_DOUBLE_TOTAL;  }

//--- Возвращает тип бара (бычий/медвежий/нулевой)
   ENUM_BAR_BODY_TYPE BodyType(void)                              const;
//--- Рассчитывает и возвращает размер (1) свечи, (2) тела свечи,
//--- (3) верхней, (4) нижней тени свечи,
//--- (5) верх, (6) низ тела свечи
   double            CandleSize(void)                             const { return(this.High()-this.Low());                                    }
   double            BodySize(void)                               const { return(this.BodyHigh()-this.BodyLow());                            }
   double            ShadowUpSize(void)                           const { return(this.High()-this.BodyHigh());                               }
   double            ShadowDownSize(void)                         const { return(this.BodyLow()-this.Low());                                 }
   double            BodyHigh(void)                               const { return ::fmax(this.Close(),this.Open());                           }
   double            BodyLow(void)                                const { return ::fmin(this.Close(),this.Open());                           }

//--- Возвращает (1) год, (2) месяц, к которому относится бар, (3) день недели,
//--- (4) порядковый номер в году, (5) день, (6) час, (7) минуту бара,
   int               TimeYear(void)                               const { return this.m_dt_struct.year;                                      }
   int               TimeMonth(void)                              const { return this.m_dt_struct.mon;                                       }
   int               TimeDayOfWeek(void)                          const { return this.m_dt_struct.day_of_week;                               }
   int               TimeDayOfYear(void)                          const { return this.m_dt_struct.day_of_year;                               }
   int               TimeDay(void)                                const { return this.m_dt_struct.day;                                       }
   int               TimeHour(void)                               const { return this.m_dt_struct.hour;                                      }
   int               TimeMinute(void)                             const { return this.m_dt_struct.min;                                       }

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство бара
   void              SetProperty(ENUM_BAR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                              }
   void              SetProperty(ENUM_BAR_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;            }
   void              SetProperty(ENUM_BAR_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;            }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство бара
   long              GetProperty(ENUM_BAR_PROP_INTEGER property)  const { return this.m_long_prop[property];                                 }
   double            GetProperty(ENUM_BAR_PROP_DOUBLE property)   const { return this.m_double_prop[this.IndexProp(property)];               }
   string            GetProperty(ENUM_BAR_PROP_STRING property)   const { return this.m_string_prop[this.IndexProp(property)];               }

//--- Возвращает флаг поддержания баром данного свойства
   virtual bool      SupportProperty(ENUM_BAR_PROP_INTEGER property)    { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_DOUBLE property)     { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_STRING property)     { return true; }
//--- Возвращает себя
   CBar             *GetObject(void)                                    { return &this;}
//--- Устанавливает (1) символ, таймфрейм и время бара, (2) параметры объекта-бар
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);

//--- Сравнивает объекты CBar между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-бара)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CBar между собой по всем свойствам (для поиска равных объектов-баров)
   bool              IsEqual(CBar* compared_bar) const;
//--- Конструкторы
                     CBar(){;} 
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source);
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates);
                     
//+------------------------------------------------------------------+ 

Далее, при создании списков таймсерий по символам, мы будем использовать именно этот конструктор для создания нового объекта-бара, принадлежащего указанной таймсерии символа. Ранее параметрические конструкторы пытались самостоятельно брать в истории нужные данные вновь созданного объекта-бара, и при ошибке получения истории из конструктора выводилась отладочная запись в журнал. Простой конструктор без параметров будет создавать пустой объект-бар, который необходимо самостоятельно заполнить данными после успешного его создания. Это будет происходить в методах класса CSeriesDE.

Рассмотрим изменения, которые необходимо внести в листинг данного класса в файле \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh.

В публичную секцию класса добавим метод, возвращающий указатель на объект класса "Новый бар", принадлежащий таймсерии этого класса:

//+------------------------------------------------------------------+
//| Класс "Таймсерия"                                                |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Таймфрейм
   string            m_symbol;                                          // Символ
   string            m_period_description;                              // Строковое описание таймфрейма
   datetime          m_firstdate;                                       // Самая первая дата по символу-периоду на данный момент
   datetime          m_lastbar_date;                                    // Время открытия последнего бара по символу-периоду
   uint              m_amount;                                          // Количество используемых данных таймсерии
   uint              m_required;                                        // Требуемое количество используемых данных таймсерии
   uint              m_bars;                                            // Количество баров в истории по символу и таймфрейму
   bool              m_sync;                                            // Флаг синхронизированности данных
   CArrayObj         m_list_series;                                     // Список-таймсерия
   CNewBarObj        m_new_bar_obj;                                     // Объект "Новый бар"
//--- Устанавливает самую первую дата по символу-периоду на данный момент и новое время открытия последнего бара по символу-периоду
   void              SetServerDate(void)
                       {
                        this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
                        this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
                       }

public:
//--- Возвращает (1) себя, (2) список-таймсерию, (3) объект "Новый бар" таймсерии
   CSeriesDE        *GetObject(void)                                    { return &this;               }
   CArrayObj        *GetList(void)                                      { return &m_list_series;      }
   CNewBarObj       *GetNewBarObj(void)                                 { return &this.m_new_bar_obj; }

//--- Возвращает список баров по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию

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

//--- Создаёт и отправляет событие таймсерии на график управляющей программы
   void              SendEvent(ENUM_SERIES_EVENT event);

И доработаем сам метод, расположенный за пределами тела класса:

//+------------------------------------------------------------------+
//| Создаёт и отправляет событие таймсерии                           |
//| на график управляющей программы                                  |
//+------------------------------------------------------------------+
void CSeriesDE::SendEvent(ENUM_SERIES_EVENT event)
  {
   if(event==SERIES_EVENTS_NEW_BAR)
     {
      int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME);
      CBar *bar=this.m_list_series.At(index);
      if(bar==NULL)
         return;
      ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,bar.Time(),this.Timeframe(),this.Symbol());
     }
   else if(event==SERIES_EVENTS_MISSING_BARS)
     {
      ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_MISSING_BARS,this.m_new_bar_obj.BarsBetweenNewBars(),this.Timeframe(),this.Symbol());
     }
  }
//+------------------------------------------------------------------+

Здесь: в зависимости от переданного в метод значения создаём нужное событие и отправляем его на график управляющей программы. Если создаётся событие "Пропущены бары", то в значении lparam функции EventChartCustom() передаём количество пропущенных баров истории.

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

//+------------------------------------------------------------------+
//| Возвращает объект-бар по времени в таймсерии                     |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const datetime time)
  {
   CBar *obj=new CBar();
   if(obj==NULL)
      return NULL;
   obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time);
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   int index=this.m_list_series.Search(obj);
   delete obj;
   return this.m_list_series.At(index);
  }
//+------------------------------------------------------------------+

Так как у нас теперь есть конструктор без параметров в классе CBar, то для поиска нужного бара будем использовать создание нового объекта-бара при помощи этого конструктора.

Здесь мы просто создаём временный пустой объект-бар, устанавливаем этому объекту требуемые символ, таймфрейм и время бара.
Далее просто: сортируем список объектов-баров по времени и ищем в списке объектов-баров такой объект, у которого данные совпадают с теми, которые мы установили созданному временному объекту-бару.
Метод Search() возвращает индекс найденного объекта в списке, а метод At() возвращает указатель на объект по индексу. В случае, если объект найден не был, то индекс будет иметь значение -1, при этом метод At() вернёт значение NULL.

События новых баров, а теперь и события пропуска баров, мы отлавливаем в методах обновления всех имеющихся таймсерий класса CTimeSeriesDE
в файле \MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh.

Дополним два метода обновления таймсерий блоками кода для определения событий "Пропущены бары":

//+------------------------------------------------------------------+
//| Обновляет указанный список-таймсерию                             |
//+------------------------------------------------------------------+
void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Сбрасываем флаг события таймсерии и очищаем список всех событий таймсерии
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Получаем из списка таймсерию по её таймфрейму
   CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable())
      return;
//--- Обновляем список-таймсерию
   series_obj.Refresh(data_calculate);
   datetime time=
     (
      this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
      data_calculate.rates.time : 
      series_obj.LastBarDate()
     );
//--- Если у объекта-таймсерии есть событие "Новый бар"
   if(series_obj.IsNewBar(time))
     {
      //--- отправляем событие "Новый бар" на график управляющей программы
      series_obj.SendEvent(SERIES_EVENTS_NEW_BAR);
      //--- устанавливаем значения первой даты в истории на сервере и в терминале
      this.SetTerminalServerDate();
      //--- добавляем в список событий таймсерий новое событие "Новый бар"
      //--- при успешном добавлении - устанавливаем флаг события у таймсерии
      if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol()))
         this.m_is_event=true;
         
      //--- Проверяем пропущенные бары
      int missing=series_obj.GetNewBarObj().BarsBetweenNewBars();
      if(missing>1)
        {
         //--- отправляем событие "Пропущены бары" на график управляющей программы
         series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS);
         //--- добавляем в список событий таймсерий новое событие "Пропущены бары"
         this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol());
        }
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Обновляет все списки-таймсерии                                   |
//+------------------------------------------------------------------+
void CTimeSeriesDE::RefreshAll(SDataCalculate &data_calculate)
  {
//--- Сбрасываем флаги необходимости установки первой даты в истории на сервере и в терминале
//--- и флаг события таймсерии и очищаем список всех событий таймсерии
   bool upd=false;
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- В цикле по списку всех используемых таймсерий
   int total=this.m_list_series.Total();
   for(int i=0;i<total;i++) 
     {
      //--- получаем очередной объект-таймсерию по индексу цикла
      CSeriesDE *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0)
         continue;
      //--- обновляем список-таймсерию
      series_obj.Refresh(data_calculate);
      datetime time=
        (
         this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
         data_calculate.rates.time : 
         series_obj.LastBarDate()
        );
      //--- Если у объекта-таймсерии есть событие "Новый бар"
      if(series_obj.IsNewBar(time))
        {
         //--- отправляем событие "Новый бар" на график управляющей программы,
         series_obj.SendEvent(SERIES_EVENTS_NEW_BAR);
         //--- устанавливаем флаг необходимости установки первой даты в истории на сервере и в терминале
         upd=true;
         //--- добавляем в список событий таймсерий новое событие "Новый бар"
         //--- при успешном добавлении - устанавливаем флаг события у таймсерии
         if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol()))
            this.m_is_event=true;
            
         //--- Проверяем пропущенные бары
         int missing=series_obj.GetNewBarObj().BarsBetweenNewBars();
         if(missing>1)
           {
            //--- отправляем событие "Пропущены бары" на график управляющей программы
            series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS);
            //--- добавляем в список событий таймсерий новое событие "Пропущены бары"
            this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol());
           }
        }
     }
//--- Если установлен флаг необходимости установки первой даты в истории на сервере и в терминале -
//--- устанавливаем значения первой даты в истории на сервере и в терминале
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

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

В класс-коллекцию объектов всех таймсерий CTimeSeriesCollection в файле \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh, в его публичной секции добавим объявление метода пересоздания всех таймсерий:

//--- (1) Создаёт, (2) пересоздаёт указанную таймсерию указанного символа, (3) пересоздаёт все таймсерии
   bool                    CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0);
   bool                    ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0);
   bool                    ReCreateSeriesAll(const int rates_total=0,const uint required=0);
//--- Возвращает (1) пустую, (2) не полностью заполненную данными таймсерию

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

//+------------------------------------------------------------------+
//| Пересоздаёт все таймсерии                                        |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::ReCreateSeriesAll(const int rates_total=0,const uint required=0)
  {
//--- В цикле по всем объектам таймсерий символов в коллекции
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной объект таймсерий символа
      CTimeSeriesDE *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      //--- Получаем список всех таймсерий символа
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      //--- В цикле по всем таймсериям символа
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         //--- Получаем очередную таймсерию
         CSeriesDE *series=list.At(j);
         if(series==NULL)
            continue;
         //--- проверяем синхронизацию таймсерии и пересоздаём её
         if(!series.SyncData(required,rates_total))
            return false;
         if(series.Create(required)==0)
            return false;
        }
     }
   return true;
  }
//+------------------------------------------------------------------+

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

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


Методы работы со стандартными индикаторами

В первую очередь доработаем класс объекта абстрактного буфера в файле \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh.

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

//--- Устанавливает (1) код стрелки, (2) смещение стрелок по вертикали, (3) символ, (4) таймфрейм, (5) флаг активности буфера
//--- (6) тип рисования, (7) количество начальных баров без отрисовки, (8) признак отображения значений построения в окне DataWindow,
//--- (9) сдвиг графического построения индикатора по оси времени, (10) стиль линии отрисовки, (11) толщину линии отрисовки,
//--- (12) общее количество цветов, (13) один цвет отрисовки, (14) цвет отрисовки в указанный индекс цвета,
//--- (15) цвета отрисовки из массива цветов, (16) пустое значение, (17) имя графической серии, отображаемое в окне DataWindow
   virtual void      SetArrowCode(const uchar code)                  { return;                                                               }
   virtual void      SetArrowShift(const int shift)                  { return;                                                               }
   void              SetSymbol(const string symbol)                  { this.SetProperty(BUFFER_PROP_SYMBOL,symbol);                          }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)   { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe);                    }
   void              SetActive(const bool flag)                      { this.SetProperty(BUFFER_PROP_ACTIVE,flag);                            }
   void              SetDrawType(const ENUM_DRAW_TYPE draw_type);
   void              SetDrawBegin(const int value);
   void              SetShowData(const bool flag);
   void              SetShift(const int shift);
   void              SetStyle(const ENUM_LINE_STYLE style);
   void              SetWidth(const int width);
   void              SetColorNumbers(const int number);
   void              SetColor(const color colour);
   void              SetColor(const color colour,const uchar index);
   void              SetColors(const color &array_colors[]);
   void              SetEmptyValue(const double value);
   virtual void      SetLabel(const string label);
   void              SetID(const int id)                             { this.SetProperty(BUFFER_PROP_ID,id);                                  }
   void              SetIndicatorHandle(const int handle)            { this.SetProperty(BUFFER_PROP_IND_HANDLE,handle);                      }
   void              SetIndicatorType(const ENUM_INDICATOR type)     { this.SetProperty(BUFFER_PROP_IND_TYPE,type);                          }
   void              SetIndicatorName(const string name)             { this.SetProperty(BUFFER_PROP_IND_NAME,name);                          }
   
//--- Возвращает (1) порядковый номер рисуемого буфера, (2) индекс связанного массива, (3) индекс буфера цвета,
//--- (4) индекс первого свободного связанного массива, (5) индекс следующего рисуемого буфера, (6) период данных буфера (7) статус буфера,
//--- (8) тип буфера, (9) флаг использования буфера, (10) код стрелки, (11) смещение стрелок для стиля DRAW_ARROW,
//--- (12) Количество начальных баров без отрисовки и значений в DataWindow, (13) тип графического построения,
//--- (14) флаг отображения значений построения в окне DataWindow, (15) сдвиг графического построения индикатора по оси времени,
//--- (16) стиль линии отрисовки, (17) толщину линии отрисовки, (18) количество цветов, (19) цвет отрисовки, количество буферов для построения
//--- (20) установленное пустое значение, (21) символ буфера, (22) имя индикаторной графической серии, отображаемое в окне DataWindow
   int               IndexPlot(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT);                 }
   int               IndexBase(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE);                 }
   int               IndexColor(void)                          const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR);                }
   int               IndexNextBaseBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE);            }
   int               IndexNextPlotBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT);            }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME);      }
   ENUM_BUFFER_STATUS Status(void)                             const { return (ENUM_BUFFER_STATUS)this.GetProperty(BUFFER_PROP_STATUS);      }
   ENUM_BUFFER_TYPE  TypeBuffer(void)                          const { return (ENUM_BUFFER_TYPE)this.GetProperty(BUFFER_PROP_TYPE);          }
   bool              IsActive(void)                            const { return (bool)this.GetProperty(BUFFER_PROP_ACTIVE);                    }
   uchar             ArrowCode(void)                           const { return (uchar)this.GetProperty(BUFFER_PROP_ARROW_CODE);               }
   int               ArrowShift(void)                          const { return (int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT);                }
   int               DrawBegin(void)                           const { return (int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN);                 }
   ENUM_DRAW_TYPE    DrawType(void)                            const { return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE);       }
   bool              IsShowData(void)                          const { return (bool)this.GetProperty(BUFFER_PROP_SHOW_DATA);                 }
   int               Shift(void)                               const { return (int)this.GetProperty(BUFFER_PROP_SHIFT);                      }
   ENUM_LINE_STYLE   LineStyle(void)                           const { return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE);     }
   int               LineWidth(void)                           const { return (int)this.GetProperty(BUFFER_PROP_LINE_WIDTH);                 }
   int               ColorsTotal(void)                         const { return (int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES);              }
   color             Color(void)                               const { return (color)this.GetProperty(BUFFER_PROP_COLOR);                    }
   int               BuffersTotal(void)                        const { return (int)this.GetProperty(BUFFER_PROP_NUM_DATAS);                  }
   double            EmptyValue(void)                          const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE);                     }
   string            Symbol(void)                              const { return this.GetProperty(BUFFER_PROP_SYMBOL);                          }
   string            Label(void)                               const { return this.GetProperty(BUFFER_PROP_LABEL);                           }
   int               ID(void)                                  const { return (int)this.GetProperty(BUFFER_PROP_ID);                         }
   int               IndicatorHandle(void)                     const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE);                 }
   ENUM_INDICATOR    IndicatorType(void)                       const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE);        }
   string            IndicatorName(void)                       const { return this.GetProperty(BUFFER_PROP_IND_NAME);                        }
   int               IndicatorBarsCalculated(void)             const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE));}

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

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 const uint index_plot,
                 const uint index_base_array,
                 const int num_datas,
                 const uchar total_arrays,
                 const int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
//--- Сохранение целочисленных свойств
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   this.m_long_prop[BUFFER_PROP_ID]                            = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_HANDLE]                    = INVALID_HANDLE;
   this.m_long_prop[BUFFER_PROP_IND_TYPE]                      = WRONG_VALUE;
   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE      : 
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   : 
      ENUM_DRAW_TYPE(this.Status()+8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                     = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                     = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                        = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                    = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                   = 0;
   this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                    = 0;
   this.m_long_prop[BUFFER_PROP_SHOW_DATA]                     = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
   this.m_long_prop[BUFFER_PROP_SHIFT]                         = 0;
   this.m_long_prop[BUFFER_PROP_LINE_STYLE]                    = STYLE_SOLID;
   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                    = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]                 = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0);
   this.m_long_prop[BUFFER_PROP_COLOR]                         = clrRed;
   this.m_long_prop[BUFFER_PROP_NUM_DATAS]                     = num_datas;
   this.m_long_prop[BUFFER_PROP_INDEX_PLOT]                    = index_plot;
   this.m_long_prop[BUFFER_PROP_INDEX_BASE]                    = index_base_array;
   this.m_long_prop[BUFFER_PROP_INDEX_COLOR]                   = this.GetProperty(BUFFER_PROP_INDEX_BASE)+
                                                                   (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = index_base_array+this.m_total_arrays;
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT]               = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot);
   
//--- Сохранение вещественных свойств
   this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0);
//--- Сохранение строковых свойств
   this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)]      = ::Symbol();
   this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)]       = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL);
   this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME)]    = NULL;

//--- Если не удалось изменить размер массива индикаторных буферов на новый - выводим об этом сообщение с указанием на строку

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

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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства буфера               |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property)
  {
   return
     (
      property==BUFFER_PROP_INDEX_PLOT    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_STATUS        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==BUFFER_PROP_TYPE          ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeBufferDescription()
         )  :
      property==BUFFER_PROP_TIMEFRAME     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTimeframeDescription()
         )  :
      property==BUFFER_PROP_ACTIVE        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetActiveDescription()
         )  :
      property==BUFFER_PROP_DRAW_TYPE     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetDrawTypeDescription()
         )  :
      property==BUFFER_PROP_ARROW_CODE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ARROW_SHIFT   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_LINE_STYLE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetLineStyleDescription()
         )  :
      property==BUFFER_PROP_LINE_WIDTH    ?  
         (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) :
          CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_DRAW_BEGIN    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_SHOW_DATA     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetShowDataDescription()
         )  :
      property==BUFFER_PROP_SHIFT         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR_INDEXES ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_COLOR   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_BASE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_BASE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_PLOT ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ID ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_HANDLE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_TYPE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_NUM_DATAS     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetColorsDescription()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства буфера                   |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING property)
  {
   return
     (
      property==BUFFER_PROP_SYMBOL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      property==BUFFER_PROP_LABEL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"")
         )  :
      property==BUFFER_PROP_IND_NAME   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IndicatorName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorName()+"\"")
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает "пустое" значение для построения,                  |
//| для которого нет отрисовки                                       |
//+------------------------------------------------------------------+
void CBuffer::SetEmptyValue(const double value)
  {
   this.SetProperty(BUFFER_PROP_EMPTY_VALUE,value);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает имя индикаторной графической серии                 |
//+------------------------------------------------------------------+
void CBuffer::SetLabel(const string label)
  {
   this.SetProperty(BUFFER_PROP_LABEL,label);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,label);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает значение из указанного индекса таймсерии              |
//| указанного массива буфера данных                                 |
//+------------------------------------------------------------------+
double CBuffer::GetDataBufferValue(const uint buffer_index,const int series_index) const
  {
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(correct_buff_index);
   if(data_total==0 || series_index<0)
      return this.EmptyValue();
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return this.DataBuffer[correct_buff_index].Array[data_index];
  }
//+------------------------------------------------------------------+
//| Возвращает значение индекса цвета из указанного индекса таймсерии|
//| указанного массива буфера цвета                                  |
//+------------------------------------------------------------------+
int CBuffer::GetColorBufferValueIndex(const int series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0 || series_index<0)
      return WRONG_VALUE;
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]);
  }
//+------------------------------------------------------------------+
//| Возвращает значение цвета из указанного индекса таймсерии        |
//| указанного массива буфера цвета                                  |
//+------------------------------------------------------------------+
color CBuffer::GetColorBufferValueColor(const int series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0 || series_index<0)
      return clrNONE;
   int color_index=this.GetColorBufferValueIndex(series_index);
   return(color_index>WRONG_VALUE ? (color)this.ArrayColors[color_index] : clrNONE);
  }
//+------------------------------------------------------------------+

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

Теперь доработаем класс объекта расчётного буфера в файле \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh.

Методы, возвращающие флаг поддержания объектом-буфером вещественного и строкового свойства, ранее всегда возвращали false — т.е. расчётный буфер не поддерживал свойства этого типа. Сделаем его поддерживающим каждое из этих свойств. А в методе, возвращающем флаг поддержания объектом целочисленных свойств, добавим новые целочисленные свойства для поддержания их объектом расчётного буфера:

//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if(
      property==BUFFER_PROP_INDEX_PLOT       || 
      property==BUFFER_PROP_STATUS           ||  
      property==BUFFER_PROP_TYPE             || 
      property==BUFFER_PROP_INDEX_BASE       || 
      property==BUFFER_PROP_ID               || 
      property==BUFFER_PROP_IND_HANDLE       || 
      property==BUFFER_PROP_IND_TYPE         || 
      property==BUFFER_PROP_INDEX_NEXT_BASE
     ) return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| строковое свойство, возвращает ложь в противном случае           |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   return true;
  }
//+------------------------------------------------------------------+

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

Всю работу с индикаторными буферами для стандартных индикаторов организуем в классе-коллекции индикаторных буферов CBuffersCollection
в файле \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh.

Сегодня сделаем работу по созданию и сопровождению мультисимвольных мультипериодных индикаторных буферов стандартного индикатора AC (Accelerator Oscillator). В последующих статьях, уже на основе протестированного функционала добавим возможность создания и работы с другими стандартными индикаторами.

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

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

//+------------------------------------------------------------------+
//| Коллекция индикаторных буферов                                   |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Список объектов-буферов
   CTimeSeriesCollection  *m_timeseries;                 // Указатель на объект-коллекцию таймсерий
   
//--- Возвращает индекс (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:
//--- Возвращает (1) себя, (2) список таймсерий, (3) список индикаторных буферов (у которых есть идентификатор принадлежности индикатору)
   CBuffersCollection     *GetObject(void)               { return &this;                                       }
   CArrayObj              *GetList(void)                 { return &this.m_list;                                }
   CArrayObj              *GetListBuffersWithID(void);

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

//+------------------------------------------------------------------+
//| Возвращает список индикаторных буферов                           |
//| (у которых есть идентификатор принадлежности индикатору)         |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBuffersWithID(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

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

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

Добавим в публичную же секцию класса объявления методов создания объектов-буферов для работы со стандартными индикаторами:

//--- Создаёт новый буфер (1) "Отрисовка стрелками", (2) "Линия", (3) "Отрезки", (4) "Гистограмма от нулевой линии", 
//--- (5) "Гистограмма на двух индикаторных буферах", (6) "Зигзаг", (7) "Цветовая заливка между двумя уровнями",
//--- (8) "Отображение в виде баров", (9) "Отображение в виде свечей", расчётный буфер
   bool                    CreateArrow(void)             { return this.CreateBuffer(BUFFER_STATUS_ARROW);      }
   bool                    CreateLine(void)              { return this.CreateBuffer(BUFFER_STATUS_LINE);       }
   bool                    CreateSection(void)           { return this.CreateBuffer(BUFFER_STATUS_SECTION);    }
   bool                    CreateHistogram(void)         { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM);  }
   bool                    CreateHistogram2(void)        { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); }
   bool                    CreateZigZag(void)            { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG);     }
   bool                    CreateFilling(void)           { return this.CreateBuffer(BUFFER_STATUS_FILLING);    }
   bool                    CreateBars(void)              { return this.CreateBuffer(BUFFER_STATUS_BARS);       }
   bool                    CreateCandles(void)           { return this.CreateBuffer(BUFFER_STATUS_CANDLES);    }
   bool                    CreateCalculate(void)         { return this.CreateBuffer(BUFFER_STATUS_NONE);       }

//--- Создаёт мультисимвольный мультипериодный индикатор
   int                     CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);
   int                     CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE);
   int                     CreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE);
   int                     CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ama_period,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const int ama_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int bands_period,
                                       const int bands_shift,
                                       const double deviation,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id=WRONG_VALUE);
   int                     CreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id=WRONG_VALUE);
   int                     CreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id=WRONG_VALUE);
   int                     CreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id=WRONG_VALUE);
   int                     CreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id=WRONG_VALUE);
   int                     CreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);

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

Для примера сегодня сделаем работу с индикатором AC. За пределами тела класса напишем реализацию метода создания индикатора AC и его буферов:

//+------------------------------------------------------------------+
//| Создаёт мультисимвольный мультипериодный AC                      |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE)
  {
//--- Создаём хэндл индикатора и устанавливаем идентификатор по умолчанию
   int handle=::iAC(symbol,timeframe);
   int identifier=(id==WRONG_VALUE ? IND_AC : id);
   if(handle!=INVALID_HANDLE)
     {
      //--- Создаём буфер-гистограмму от нулевой линии
      this.CreateHistogram();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры
      CBuffer *buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetShowData(true);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
      
      //--- Создаём расчётный буфер, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры
      buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Как видим, тут всё просто. Если в качестве идентификатора передано значение -1, то идентификатор будет равен значению константы типа стандартного индикатора. При успешном создании индикатора (его хэндл не равен INVALID_HANDLE), создаём объект-буфер с типом рисования "Гистограмма от нулевой линии" и при помощи метода GetLastCreateBuffer(), возвращающего указатель на последний созданный буфер (метод рассмотрим чуть позже), получаем указатель на объект-буфер гистограммы и устанавливаем ему необходимые параметры для его идентификации как буфера для отрисовки данных стандартного индикатора AC.
Далее делаем всё то же самое и для расчётного буфера. В расчётный буфер будем записывать данные индикатора AC, получаемые при обращении к его хэндлу. А хэндл созданного индикатора у нас прописан в свойствах объекта-буфера. И рисуемого, и расчётного — т.е. можно получить любой из этих объектов-буферов и обратиться к индикатору по хэндлу, прописанному в этих объектах, и далее работать с индикатором.

И добавим реализацию метода создания индикатора AD с необходимыми ему объектами-буферами — просто чтобы были видны различия в реализации методов для стандартных индикаторов разного типа:

//+------------------------------------------------------------------+
//| Создаёт мультисимвольный мультипериодный AD                      |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE)
  {
//--- Создаём хэндл индикатора и устанавливаем идентификатор по умолчанию
   int handle=::iAD(symbol,timeframe,applied_volume);
   int identifier=(id==WRONG_VALUE ? IND_AD : id);
   if(handle!=INVALID_HANDLE)
     {
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры
      CBuffer *buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AD);
      buff.SetShowData(true);
      buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accumulation/Distribution");
      
      //--- Создаём расчётный буфер, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры
      buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AD);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accumulation/Distribution");
     }
   return handle;
  }
//+------------------------------------------------------------------+

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

Непосредственно за объявлением методов создания стандартных индикаторов добавим объявления остальных необходимых методов:

//--- Подготавливает данные расчётного буфера указанного стандартного индикатора
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
//--- Очищает данные буфера указанного стандартного индикатора по индексу таймсерии
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
//--- Устанавливает значения для текущего графика в указанный буфер стандартного индикатора по индексу таймсерии в соответствии с символом/периодом объекта-буфера
   bool                    SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE);

//--- Возвращает буфер (1) по имени графической серии, (2) по таймфрейму,
//--- (3) по индексу Plot, (4) по индексу объекта в списке-коллекции, (5) последний созданный,
//--- список буферов (6) по идентификатору, (7) по типу стандартного индикатора, (8) по типу и идентификатору
   CBuffer                *GetBufferByLabel(const string plot_label);
   CBuffer                *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe);
   CBuffer                *GetBufferByPlot(const int plot_index);
   CBuffer                *GetBufferByListIndex(const int index_list);
   CBuffer                *GetLastCreateBuffer(void);
   CArrayObj              *GetListBufferByID(const int id);
   CArrayObj              *GetListBufferByIndType(const ENUM_INDICATOR indicator_type);
   CArrayObj              *GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id);

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

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

//+------------------------------------------------------------------+
//| Возвращает последний созданный буфер                             |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetLastCreateBuffer(void)
  {
   return this.m_list.At(this.m_list.Total()-1);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает список буферов по идентификатору                      |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByID(const int id)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает список буферов по типу стандартного индикатора        |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByIndType(const ENUM_INDICATOR indicator_type)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_IND_TYPE,indicator_type,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает список буферов по типу и идентификатору               |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id)
  {
   CArrayObj *list=this.GetListBufferByIndType(indicator_type);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_ID,id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает список индикаторных буферов                           |
//| (у которых есть идентификатор принадлежности индикатору)         |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBuffersWithID(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Из списка-коллекции объектов-буферов получаем список объектов, у которых свойство "идентификатор" не равно -1.
Указатель на полученный список возвращаем из метода.

Связующим звеном между программой и библиотекой является класс основного объекта библиотеки CEngine.
Внесём необходимые доработки в файл класса \MQL5\Include\DoEasy\Engine.mqh.

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

//--- Пересоздаёт (1) указанную таймсерию указанного символа, (2) все таймсерии коллекции
   bool                 SeriesReCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0)
                          { return this.m_time_series.ReCreateSeries(symbol,timeframe,rates_total,required);      }
   bool                 SeriesReCreateAll(const int rates_total=0,const uint required=0)
                          { return this.m_time_series.ReCreateSeriesAll(rates_total,required);                    }

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

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

//--- Возвращает (1) пустую, (2) не полностью заполненную данными таймсерию
   CSeriesDE           *SeriesGetSeriesEmpty(void)       { return this.m_time_series.GetSeriesEmpty();            }
   CSeriesDE           *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted();      }
//--- Возвращает количество баров таймсерии указанного символа/периода
   int                  SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe);

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

//+------------------------------------------------------------------+
//| Возвращает количество баров таймсерии указанного символа/периода |
//+------------------------------------------------------------------+
int CEngine::SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
   if(series==NULL)
      return WRONG_VALUE;
   return (int)series.Bars();
  }
//+------------------------------------------------------------------+

Получаем указанную таймсерию из класса-коллекции таймсерий и возвращаем количество баров таймсерии.


Ранее у нас был метод, возвращающий последний созданный буфер:

//--- Возвращает буфер по (1) имени графической серии, (2) таймфрейму, индексу (3) Plot, (4) списка-коллекции, (5) последний в списке
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastBuffer(void);

и его реализация:

//+------------------------------------------------------------------+
//| Возвращает индикаторный буфер, расположенный последним           |
//| в списке-коллекции индикаторных буферов                          |
//+------------------------------------------------------------------+
CBuffer *CEngine::GetLastBuffer(void)
  {
   CArrayObj *list=this.GetListBuffers();
   if(list==NULL)
      return NULL;
   return list.At(list.Total()-1);
  }
//+------------------------------------------------------------------+

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

//--- Возвращает буфер по (1) имени графической серии, (2) таймфрейму, индексу (3) Plot, (4) списка-коллекции, (5) последний в списке
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastCreateBuffer(void)                                      { return this.m_buffers.GetLastCreateBuffer();        }

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

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

//--- Создаёт новый буфер (1) "Отрисовка стрелками", (2) "Линия", (3) "Отрезки", (4) "Гистограмма от нулевой линии", 
//--- (5) "Гистограмма на двух индикаторных буферах", (6) "Зигзаг", (7) "Цветовая заливка между двумя уровнями",
//--- (8) "Отображение в виде баров", (9) "Отображение в виде свечей", расчётный буфер
   bool                 BufferCreateArrow(void)                                        { return this.m_buffers.CreateArrow();                }
   bool                 BufferCreateLine(void)                                         { return this.m_buffers.CreateLine();                 }
   bool                 BufferCreateSection(void)                                      { return this.m_buffers.CreateSection();              }
   bool                 BufferCreateHistogram(void)                                    { return this.m_buffers.CreateHistogram();            }
   bool                 BufferCreateHistogram2(void)                                   { return this.m_buffers.CreateHistogram2();           }
   bool                 BufferCreateZigZag(void)                                       { return this.m_buffers.CreateZigZag();               }
   bool                 BufferCreateFilling(void)                                      { return this.m_buffers.CreateFilling();              }
   bool                 BufferCreateBars(void)                                         { return this.m_buffers.CreateBars();                 }
   bool                 BufferCreateCandles(void)                                      { return this.m_buffers.CreateCandles();              }
   bool                 BufferCreateCalculate(void)                                    { return this.m_buffers.CreateCalculate();            }

//--- Методы создания стандартных индикаторов и объектов-буферов для них
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);  }

//--- Инициализирует все рисуемые буферы (1) указанным, (2) установленным объекту-буферу пустым значением

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

Реализация метода подготовки данных расчётного буфера для стандартного индикатора (пока только для AC):

//+------------------------------------------------------------------+
//| Подготавливает данные расчётного буфера                          |
//| указанного стандартного индикатора                               |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return 0;
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

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

Реализация метода очистки данных расчётного буфера для стандартного индикатора по указанному индексу (пока только для AC):

//+------------------------------------------------------------------+
//| Очищает данные буфера указанного стандартного индикатора         |
//| по индексу таймсерии                                             |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
   CArrayObj *list=this.GetListBufferByID(id);
   if(list==NULL)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

Реализация метода заполнения рисуемого буфера на текущем графике данными стандартного индикатора с любого символа/таймфрейма (пока только для AC):

//+------------------------------------------------------------------+
//| Устанавливает значения для текущего графика в указанный буфер    |
//| стандартного индикатора по индексу таймсерии в соответствии      |
//| с символом/периодом объекта-буфера                               |
//+------------------------------------------------------------------+
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)
      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 || list_calc.Total()==0)
      return false;
//--- Объявляем необходимые объекты и переменные
   CBuffer *buffer_data=NULL;
   CBuffer *buffer_calc=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   datetime time_period=0;
   double value0=EMPTY_VALUE, value1=EMPTY_VALUE;

//--- В зависимости от типа стандартного индикатора
   switch((int)ind_type)
     {
      case IND_AC :
        //--- Получаем объекты рисуемого и расчётного буферов
        buffer_data=list_data.At(0);
        buffer_calc=list_calc.At(0);
        if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(0)==0) return false;
        
        //--- Найтиходим индекс бара на периоде, соответствующий времени начала текущего бара
        index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-1) return false;
        //--- Получаем значение по этому индексу из буфера индикатора
        value0=buffer_calc.GetDataBufferValue(0,index_period);
        if(buffer_calc.Symbol()==::Symbol() && buffer_calc.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
           time_period=::iTime(buffer_calc.Symbol(),buffer_calc.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Получаем соответствующий времени бар текущего графика
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
           num_bars=::PeriodSeconds(buffer_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Берём значения для расчёта цвета
        value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-1 ? value0 : buffer_data.GetDataBufferValue(0,series_index_start+num_bars));
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value0 и value1
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data.SetBufferValue(0,index,value0);
           buffer_data.SetBufferColorIndex(index,uchar(value0>value1 ? 0 : value0<value1 ? 1 : 2));
          }
        break;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

Вся логика метода в расчёте данных для AC расписана в комментариях и, надеюсь, понятна.


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

public:
//--- Создаёт и возвращает составной магик из заданного значения магика, идентификаторов первой и второй групп и идентификатора отложенного запроса
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

//--- Обработка событий библиотеки DoEasy
void                    OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Работа с событиями в тестере
void                    EventsHandling(void);

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

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

Я уже перенёс из тестового индикатора эти функции в листинг класса CEngine, сделав из них реализацию объявленных выше методов:

//+------------------------------------------------------------------+
//| Обработка событий библиотеки DoEasy                              |
//+------------------------------------------------------------------+
void CEngine::OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Извлекаем из lparam (1) милисекунды времени события, (2) причину, (3) источник события и (4) устанавливаем точное время события
   ushort msc=this.EventMSC(lparam);
   ushort reason=this.EventReason(lparam);
   ushort source=this.EventSource(lparam);
   long time=::TimeCurrent()*1000+msc;
   
//--- Обработка событий символов
   if(source==COLLECTION_SYMBOLS_ID)
     {
      CSymbol *symbol=this.GetSymbolObjByName(sparam);
      if(symbol==NULL)
         return;
      //--- Количество знаков после запятой в значении события - если long-событие, то 0, иначе - Digits() символа
      int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits());
      //--- Текстовое описание события
      string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx));
      //--- Текстовое значение величины изменения свойства
      string value=::DoubleToString(dparam,digits);
      
      //--- Проверка причин события и просто вывод в журнал его описания
      if(reason==BASE_EVENT_REASON_INC)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     }   
     
//--- Обработка событий аккаунта
   else if(source==COLLECTION_ACCOUNT_ID)
     {
      CAccount *account=this.GetAccountCurrent();
      if(account==NULL)
         return;
      //--- Количество знаков после запятой в значении события - если long-событие, то 0, иначе - Digits() символа
      int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits());
      //--- Текстовое описание события
      string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx));
      //--- Текстовое значение величины изменения свойства
      string value=::DoubleToString(dparam,digits);
      
      //--- Проверка причин события и обработка увеличения средств на заданную величину,
      
      //--- Распечатаем событие в журнал
      if(reason==BASE_EVENT_REASON_INC)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     } 
     
//--- Обработка событий окна обзор рынка
   else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE)
     {
      //--- Событие окна "Обзор рынка"
      string descr=this.GetMWEventDescription((ENUM_MW_EVENT)idx);
      string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam);
      Print(TimeMSCtoString(lparam)," ",descr,name);
     }
     
//--- Обработка событий таймсерий
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- Событие "Новый бар"
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- Событие "Пропущены бары"
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
     }
     
//--- Обработка торговых событий
   else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE)
     {
      //--- Получим список всех торговых событий
      CArrayObj *list=this.GetListAllOrdersEvents();
      if(list==NULL)
         return;
      //--- получим смещение индекса события относительно конца списка
      //--- в тестере смещение передаётся параметром lparam в обработчик событий
      //--- не в тестере - каждое событие отправляется по одному и обрабатывается в OnChartEvent()
      int shift=(this.IsTester() ? (int)lparam : 0);
      CEvent *event=list.At(list.Total()-1-shift);
      if(event==NULL)
      return;
      //--- Начисление кредита
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Дополнительные сборы
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Корректирующая запись
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Перечисление бонусов
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Дополнительные комиссии
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Комиссия, начисляемая в конце торгового дня
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Комиссия, начисляемая в конце месяца
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Агентская комиссия, начисляемая в конце торгового дня
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Агентская комиссия, начисляемая в конце месяца
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисления процентов на свободные средства
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Отмененная сделка покупки
      if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Отмененная сделка продажи
      if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисление дивиденда
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисление франкированного дивиденда
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Начисление налога
      if(event.TypeEvent()==TRADE_EVENT_TAX)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Пополнение средств на балансе
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Снятие средств с баланса
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      
      //--- Отложенный ордер установлен
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Отложенный ордер удалён
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Отложенный ордер активирован ценой
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Отложенный ордер активирован ценой частично
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция открыта
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция открыта частично
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта встречной
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта по StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта по TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции новой сделкой (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции частичным исполнением маркет-ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Разворот позиции частичной активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции новой сделкой (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции частичным исполнением маркет-ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Добавлен объём к позиции частичной активацией отложенного ордера (неттинг)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично встречной
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично по StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Позиция закрыта частично по TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Срабатывание StopLimit ордера
      if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера и StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера и TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение цены установки ордера, StopLoss и TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss и TakeProfit ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение TakeProfit ордера
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss и TakeProfit позиции
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение StopLoss позиции
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Изменение TakeProfit позиции
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
     }
  }
//+------------------------------------------------------------------+
//| Работа с событиями в тестере                                     |
//+------------------------------------------------------------------+
void CEngine::EventsHandling(void)
  {
//--- Если есть торговое событие
   if(this.IsTradeEvent())
     {
      //--- Количество торговых событий, произошедших одновременно
      int total=this.GetTradeEventsTotal();
      for(int i=0;i<total;i++)
        {
         //--- Получим по индексу очередное событие из списка событий, произошедших одновременно
         CEventBaseObj *event=this.GetTradeEventByIndex(i);
         if(event==NULL)
            continue;
         long   lparam=i;
         double dparam=event.DParam();
         string sparam=event.SParam();
         this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
        }
     }
//--- Если есть событие аккаунта
   if(this.IsAccountsEvent())
     {
      //--- Получим список всех событий аккаунта, произошедших одновременно
      CArrayObj* list=this.GetListAccountEvents();
      if(list!=NULL)
        {
         //--- В цикле получаем очередное событие
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- берём событие из списка
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Отправляем событие в обработчик событий
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- Если есть событие коллекции символов
   if(this.IsSymbolsEvent())
     {
      //--- Получим список всех событий символов, произошедших одновременно
      CArrayObj* list=this.GetListSymbolsEvents();
      if(list!=NULL)
        {
         //--- В цикле получаем очередное событие
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- берём событие из списка
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Отправляем событие в обработчик событий
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- Если есть событие коллекции таймсерий
   if(this.IsSeriesEvent())
     {
      //--- Получим список всех событий таймсерий, произошедших одновременно
      CArrayObj* list=this.GetListSeriesEvents();
      if(list!=NULL)
        {
         //--- В цикле получаем очередное событие
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- берём событие из списка
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Отправляем событие в обработчик событий
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Работу этих функций (а теперь уже методов класса CEngine) мы рассматривали в начальных статьях описания библиотеки при создании тестовых советников. По листингу методов видно, что каждое событие (почти) мы просто выводим сообщением в журнал. Соответственно, можно создать список флагов событий в глобальной области видимости и просто устанавливать нужные флаги. В своих же программах легко сделать обработчики каждого из взведённых флагов. Что и сделаем впоследствии.

Таким образом мы избавились от необходимости прописывать в каждой своей программе эти обработчики.

В классе есть обработчик события calculate, который вызывается из индикатора, и если возвращаемое обработчиком значение равно нулю, то это означает, что не все таймсерии, используемые в индикаторе, ещё построены. Индикатор должен при этом выйти из OnCalculate() с кодом возврата 0, что означает ожидание следующего тика и указание, что никакие данные не были посчитаны.

Так как мы добавляем работу со стандартными индикаторами, то должны точно так же проконтролировать, что созданный индикатор был просчитан.
Чтобы узнать количество посчитанных данных, можно воспользоваться функцией BarsCalculated(), которая возвращает количество уже просчитанных данных индикатором. Если данные ещё не просчитаны, функция возвращает значение -1.

Впишем в метод, обрабатывающий событие calculate, проверку успешности просчёта всех созданных стандартных индикаторов в коллекции буферов:

//+------------------------------------------------------------------+
//| Обработчик события Calculate                                     |
//+------------------------------------------------------------------+
int CEngine::OnCalculate(SDataCalculate &data_calculate,const uint required=0)
  {
//--- Если это не индикатор - уходим
   if(this.m_program!=PROGRAM_INDICATOR)
      return 0;
//--- Пересоздание пустых таймсерий
//--- Если хоть одна таймсерия не синхронизирована - возвращаем ноль
   if(!this.SeriesSync(data_calculate,required))
      return 0;
//--- Обновляем таймсерии текущего символа (не в тестере) и
//--- возвращаем либо 0 (в случае, если есть пустые таймсерии), либо rates_total
   if(!this.IsTester())
      this.SeriesRefresh(NULL,data_calculate);
   int res=(this.SeriesGetSeriesEmpty()==NULL ? data_calculate.rates_total : 0);
   
//--- Проверяем количество просчитанных данных стандартных индикаторов
   CArrayObj *list=m_buffers.GetListBuffersWithID();
   if(list!=NULL)
     {
      //--- В цикле по количеству буферов, имеющих идентификатор
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         //--- получаем очередной расчётный буфер, использующийся стандартным индикатором
         CBuffer *buff=list.At(i);
         if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorHandle()==INVALID_HANDLE)
            continue;
         //--- и если данные индикатора ещё не расчитаны - возвращаем ноль
         if(buff.IndicatorBarsCalculated()==WRONG_VALUE)
            return 0;
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

Логика обработки данных созданных индикаторов расписана в листинге метода.

Последним штрихом в доработке библиотеки на сегодня будет добавление периода текущего графика в список используемых таймфреймов.
В файле сервисных функций библиотеки E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Services\DELib.mqh у нас есть функция, подготавливающая список используемых таймфреймов. И если в настройках программы не указан период текущего графика, то библиотека не создаёт его таймсерию. А она нам нужна для работы постоянно.

Дополним функцию CreateUsedTimeframesArray() блоком кода для внесения периода текущего графика в список используемых таймфреймов:

//+------------------------------------------------------------------+
//| Подготавливает массив таймфреймов для коллекции таймсерий        |
//+------------------------------------------------------------------+
bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[])
  {
//--- Если работа с текущим периодом графика - заполним массив строкой описания текущего таймфрейма
   if(mode_used_periods==TIMEFRAMES_MODE_CURRENT)
     {
      ArrayResize(used_periods_array,1,21);
      used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
      return true;
     }
//--- Если работа с предопределённым набором периодов графиков (из строки defined_used_periods)
   else if(mode_used_periods==TIMEFRAMES_MODE_LIST)
     {
      //--- Установим разделитель - запятая (определён в файле Datas.mqh, стр 11)
      string separator=INPUT_SEPARATOR;
      //--- Заполним массив параметров из строки с предопределёнными таймфреймами
      int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array);
      //--- если ничего не нашли - выведем об этом сообщение (при этом автоматически будет выбрана работа с текущим периодом)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         //--- Установим текущий период в массив
         ArrayResize(used_periods_array,1,21);
         used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         return false;
        }
     }
//--- Если работа с полным списком таймфреймов - заполним массив строками описания всех таймфреймов
   else
     {
      ArrayResize(used_periods_array,21,21);
      for(int i=0;i<21;i++)
         used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1)));
     }

//--- Внесём в список используемых периодов таймфрейм текущего графика
   bool f=false;
   for(int i=0;i<ArraySize(used_periods_array);i++)
     {
      if(used_periods_array[i]==TimeframeDescription((ENUM_TIMEFRAMES)Period()))
        {
         f=true;
         break;
        }
     }
   //--- Если в списке используемых периодов нет таймфрейма текущего графика
   if(!f)
     {
      //--- Увеличим массив используемых периодов на 1 и внесём в него период текущего графика
      ArrayResize(used_periods_array,ArraySize(used_periods_array)+1);
      used_periods_array[ArraySize(used_periods_array)-1]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
     }
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+

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

Протестируем создание мультисимвольного мультипериодного стандартного индикатора Accelerator Oscillator.

Тест

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

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

Заголовок индикатора будет таким:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart47.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // Список используемых таймфреймов
CEngine              engine;                                      // Главный объект библиотеки CEngine
string               prefix;                                      // Префикс имён графических объектов
int                  min_bars;                                    // Минимальное количество баров для расчёта индикатора
int                  used_symbols_mode;                           // Режим работы с символами
string               array_used_symbols[];                        // Массив для передачи в библиотеку используемых символов
string               array_used_periods[];                        // Массив для передачи в библиотеку используемых таймфреймов
//+------------------------------------------------------------------+

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

В обработчике OnInit() создадим стандартный индикатор AC с параметрами, указанными во входных параметрах индикатора,
его идентификатором, равным 1
, и буферы для работы с ним:

//+------------------------------------------------------------------+
//| 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
//--- Создаём все необходимые объекты-буферы для построения AO
   engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);

//--- Проверяем количество буферов, указанных в блоке properties
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Создаём массив цветов и зададём всем буферам в коллекции значения цвета не по умолчанию
   color array_colors[]={clrGreen,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);

//--- Выведем краткие описания созданных индикаторных буферов
   engine.BuffersPrintShort();
//--- Установим короткое имя индикатора и разрядность данных
   IndicatorSetString(INDICATOR_SHORTNAME,"AC("+InpUsedSymbols+","+TimeframeDescription(InpPeriod)+")");
   IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+2);
//--- Успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

В OnCalculate() сначала подготавливаем данные расчётного буфера индикатора AC, а затем в основном цикле индикатора заполняем данные рисуемого буфера на текущем графике данными из расчётного буфера индикатора AC:

//+------------------------------------------------------------------+
//| 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.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));
   
//--- Заполняем расчётный буфер данными AO
   CArrayObj *list=engine.GetBuffersCollection().GetListBuffersWithID();
   if(list!=NULL)
     {
      for(int i=0;i<list.Total();i++)
        {
         CBuffer *buff=list.At(i);
         if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
            continue;
         CSeriesDE *series=engine.SeriesGetSeries(buff.Symbol(),buff.Timeframe());
         if(series==NULL)
            return 0;
         ulong used_data=series.AvailableUsedData();
         int copied=engine.GetBuffersCollection().PreparingDataBufferStdInd(IND_AC,1,(int)used_data);
         if(copied<(int)used_data)
            return 0;
        }
     }

//--- Расчёт индикатора
   CBar *bar=NULL;         // Объект-бар для определения направления свечи
   uchar color_index=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);
  }
//+------------------------------------------------------------------+

Это всё, что нужно сделать для расчёта и отображения стандартного индикатора AC на текущем графике, рассчитанного на любом символе/таймфрейме.

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

Полный код индикатора можно посмотреть в прикреплённых к статье файлах.

Скомпилируем индикатор и запустим его на графике EURUSD, M1, предварительно установив в настройках индикатора символ GBPUSD и таймфрейм M5, что говорит об отображении на текущем минутном графике EURUSD данных индикатора AC, рассчитанного на символе GBPUSD с таймфреймом M5:

Для сравнения рядом открыт график GBPUSD, M5 со стандартным индикатором AC на нём.

Что дальше

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

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

К содержанию

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

Работа с таймсериями в библиотеке DoEasy (Часть 35): Объект "Бар" и список-таймсерия символа
Работа с таймсериями в библиотеке DoEasy (Часть 36): Объект таймсерий всех используемых периодов символа
Работа с таймсериями в библиотеке DoEasy (Часть 37): Коллекция таймсерий - база данных таймсерий по символам и периодам
Работа с таймсериями в библиотеке DoEasy (Часть 38): Коллекция таймсерий - реалтайм обновление и доступ к данным из программы
Работа с таймсериями в библиотеке DoEasy (Часть 39): Индикаторы на основе библиотеки - подготовка данных и события таймсерий
Работа с таймсериями в библиотеке DoEasy (Часть 40): Индикаторы на основе библиотеки - реалтайм обновление данных
Работа с таймсериями в библиотеке DoEasy (Часть 41): Пример мультисимвольного мультипериодного индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 42): Класс объекта абстрактного индикаторного буфера
Работа с таймсериями в библиотеке DoEasy (Часть 43): Классы объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 44): Класс-коллекция объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 45): Мультипериодные индикаторные буферы

Прикрепленные файлы |
MQL5.zip (3778.07 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
tuma_news
tuma_news | 26 июл. 2020 в 18:09

Артём, приветик !

Спасибо за вклад  в кодобазу и за проделанную работу!

Я правильно понимаю,что  это не шаблон, в который можно вписать любой нужный мне символ и тайм ?

Это годится только для встроенных индикаторов  в МТ ?

А как быть с пользовательскими индикаторами?

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

Спасибо!

Artyom Trishkin
Artyom Trishkin | 26 июл. 2020 в 18:19
tuma_news:

Артём, приветик !

Спасибо за вклад  в кодобазу и за проделанную работу!

Я правильно понимаю,что  это не шаблон, в который можно вписать любой нужный мне символ и тайм ?

Это годится только для встроенных индикаторов  в МТ ?

А как быть с пользовательскими индикаторами?

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

Спасибо!

Библиотека - не шаблон, и предназначена для помощи в самостоятельной разработке своих программ для MetaTrader.

В статье описывается как при помощи библиотеки создать мультипериодный мультисимвольный пользовательский индикатор, отображающий данные стандартного индикатора (на примере АС) с символа и периода, отличающихся от символа/периода текущего графика. Остальные стандартные индикаторы будут описаны в последующих статьях.
И именно стандартным индикаторам данная часть посвящена.

Для работы со сторонними (не созданными при помощи библиотеки) пользовательскими индикаторами нужен немного иной подход. И это в дальнейших статьях.

SergeiKrasnoff
SergeiKrasnoff | 31 июл. 2020 в 14:42
Спасибо, что помогаете новичкам
Artyom Trishkin
Artyom Trishkin | 31 июл. 2020 в 14:47
SergeiKrasnoff:
Спасибо, что помогаете новичкам
Пожалуйста
Нейросети — это просто (Часть 2): Обучение и тестирование сети Нейросети — это просто (Часть 2): Обучение и тестирование сети
В данной статье мы продолжим изучение нейронных сетей, начатое в предыдущей статье и рассмотрим пример использования в советниках созданного нами класса CNet. Рассмотрены две модели нейронной сети, которые показали схожие результаты как по времени обучения, так и по точности предсказания.
Что такое тренды и какова структура рынков — трендовая или флэтовая? Что такое тренды и какова структура рынков — трендовая или флэтовая?
Трейдеры часто говорят о трендах и флэтах, но очень не многие действительно правильно понимают что-же такое на самом деле тренд / флэт и только единицы способны внятно объяснить эти понятия. Вокруг этих базовых понятий, сложился устойчивый набор предрассудков и заблуждений. Все это несмотря на то, что для заработка необходимо понимать математический и логический смысл. В этой статье мы подробно рассмотрим, что такое тренд, что такое флэт, какова структура рынков трендовая, флэтовая или какая-то другая. Рассмотрим какая должна быть стратегия, чтобы заработать на трендовом рынке, и какая должна быть стратегия, чтобы заработать во время флэта.
Пользовательские символы: основы применения на практике Пользовательские символы: основы применения на практике
Статья посвящена программной генерации пользовательских символов, с помощью которых демонстрируется несколько популярных способов отображения котировок. Предложен вариант малоинвазивной адаптации советников для торговли реальным символом с графика производного пользовательского символа. Исходные коды MQL прилагаются.
Дискретизация ценового ряда, случайная составляющая и "шумы" Дискретизация ценового ряда, случайная составляющая и "шумы"
Мы привыкли анализировать рынок при помощи свечей или баров, которые "нарезают" ценовой ряд через равные промежутки времени. Но насколько сильно такой способ дискретизации искажает реальную структуру рыночных движений? Дискретизировать звуковой сигнал через равные промежутки времени — это приемлемое решение, потому что звуковой сигнал — это функция, меняющаяся от времени. Сам по себе сигнал — это амплитуда, зависящая от времени и это свойство в нем, является фундаментальным.