English 中文 Español Deutsch 日本語 Português
Работа с таймсериями в библиотеке DoEasy (Часть 44): Класс-коллекция объектов индикаторных буферов

Работа с таймсериями в библиотеке DoEasy (Часть 44): Класс-коллекция объектов индикаторных буферов

MetaTrader 5Примеры | 1 мая 2020, 11:39
2 543 6
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

Связка <"коллекция таймсерий> — <коллекция буферов"> позволит нам создавать любые мультисимвольные и мультипериодные индикаторы. Этим мы начнём заниматься уже со следующей статьи. А сегодня создадим и опробуем коллекцию индикаторных буферов для создания в индикаторе любых буферов с одним из девяти стилей рисования в любом их количестве. На данный момент максимально возможное количество рисуемых буферов в индикаторе может быть не более 512. Но этого должно с лихвой хватить для создания любых сложных индикаторов с большим количеством графических построений. А при помощи создаваемого функционала создание и обслуживание такого количества графических построений упростится до простого обращения к созданным буферам по их стилю рисования и номеру в порядке создания, либо по индексу буфера в коллекции.


Подготовка данных и доработка объектов-буферов

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

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

   MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE,            // Не удалось изменить размер массива цветов
   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Не удалось добавить объект-буфер в список
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Не удалось создать объект \"Индикаторный буфер\"
   
   MSG_LIB_TEXT_YES,                                  // Да

...

   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Неправильно указано количество буферов индикатора (#property indicator_buffers)
   MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED,      // Достигнуто максимально возможное количество индикаторных буферов

   MSG_LIB_TEXT_BUFFER_TEXT_STATUS_NONE,              // Нет отрисовки

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

   {"Не удалось изменить размер массива цветов","Failed to resize color array"},
   {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"},
   {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},

   {"Да","Yes"},

...

   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","The number of indicator buffers is incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","The maximum number of indicator buffers has been reached"},
   
   {"Нет отрисовки","No drawing"},

В индикаторе может быть максимально использовано 512 буферов.
Добавим в файл \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_TRY                      (5)                        // Количество торговых попыток по умолчанию
#define IND_COLORS_TOTAL               (64)                       // Максимально возможное количество цветов индикаторного буфера
#define IND_BUFFERS_MAX                (512)                      // Максимально возможное количество буферов индикатора
//--- Стандартные звуки

Можно конечно использовать значение "512", но макроподстановка удобнее тем, что если когда-нибудь это значение будет увеличено разработчиками, то для внесения изменений в код не нужно будет искать и исправлять это значение на новое по всем файлам, где есть обращение к данной величине, а просто поменять значение макроподстановки.

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

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

Звучит запутанно, но на практике всё просто. Откроем файл \MQL5\Include\DoEasy\Defines.mqh и внесём необходимые изменения.

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

//+------------------------------------------------------------------+
//| Целочисленные свойства буфера                                    |
//+------------------------------------------------------------------+
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_NUM_DATAS,                                   // Количество буферов данных
   BUFFER_PROP_INDEX_BASE,                                  // Индекс базового буфера данных
   BUFFER_PROP_INDEX_COLOR,                                 // Индекс буфера цвета
   BUFFER_PROP_INDEX_NEXT,                                  // Индекс свободного массива для назначения следующим индикаторным буфером
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (19)                      // Общее количество целочисленных свойств буфера
#define BUFFER_PROP_INTEGER_SKIP  (6)                       // Количество неиспользуемых в сортировке свойств буфера
//+------------------------------------------------------------------+

Здесь два свойства нам нужно сделать возможными для сортировки. Для этого переместим их выше и поменяем количество неиспользуемых в сортировке свойств с 6 на 2:

//+------------------------------------------------------------------+
//| Целочисленные свойства буфера                                    |
//+------------------------------------------------------------------+
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,                                  // Индекс свободного массива для назначения следующим индикаторным буфером
   BUFFER_PROP_NUM_DATAS,                                   // Количество буферов данных
   BUFFER_PROP_INDEX_COLOR,                                 // Индекс буфера цвета
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (19)                      // Общее количество целочисленных свойств буфера
#define BUFFER_PROP_INTEGER_SKIP  (2)                       // Количество неиспользуемых в сортировке свойств буфера
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки буферов                            |
//+------------------------------------------------------------------+
#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,                               // Сортировать по индексу свободного массива для назначения следующим индикаторным буфером
//--- Сортировка по вещественным свойствам
   SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP,      // Сортировать по пустому значению для построения, для которого нет отрисовки
//--- Сортировка по строковым свойствам
   SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP,           // Сортировать по символу буфера
   SORT_BY_BUFFER_LABEL,                                    // Сортировать по имени индикаторной графической серии, отображаемого в окне DataWindow
  };
//+------------------------------------------------------------------+

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

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

Откроем файл \MQL5\Include\DoEasy\Services\Select.mqh и подключим к нему файл класса-буфера:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Методы работы с барами таймсерии                                 |
//+------------------------------------------------------------------+
   //--- Возвращает список баров, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс бара в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property);
   static int        FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property);
   static int        FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property);
   //--- Возвращает индекс бара в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property);
   static int        FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property);
   static int        FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с индикаторными буферами                           |
//+------------------------------------------------------------------+
   //--- Возвращает список буферов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByBufferProperty(CArrayObj *list_source,ENUM_BUFFER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByBufferProperty(CArrayObj *list_source,ENUM_BUFFER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByBufferProperty(CArrayObj *list_source,ENUM_BUFFER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс буфера в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindBufferMax(CArrayObj *list_source,ENUM_BUFFER_PROP_INTEGER property);
   static int        FindBufferMax(CArrayObj *list_source,ENUM_BUFFER_PROP_DOUBLE property);
   static int        FindBufferMax(CArrayObj *list_source,ENUM_BUFFER_PROP_STRING property);
   //--- Возвращает индекс буфера в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindBufferMin(CArrayObj *list_source,ENUM_BUFFER_PROP_INTEGER property);
   static int        FindBufferMin(CArrayObj *list_source,ENUM_BUFFER_PROP_DOUBLE property);
   static int        FindBufferMin(CArrayObj *list_source,ENUM_BUFFER_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

И в самый конец файла впишем все методы, объявленные в теле класса:

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

Работу класса CSelect мы подробно разбирали в третьей статье описания создания библиотеки.

Немного доработаем классы астрактного буфера и его наследников.

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

//+------------------------------------------------------------------+
//|                                                       Buffer.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "..\..\Objects\BaseObj.mqh"
//+------------------------------------------------------------------+

Теперь класс CSelect будет виден в классе CBuffer и во всех его наследниках.

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

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство буфера
   void              SetProperty(ENUM_BUFFER_PROP_INTEGER property,long value)   { this.m_long_prop[property]=value;                                        }
   void              SetProperty(ENUM_BUFFER_PROP_DOUBLE property,double value)  { this.m_double_prop[this.IndexProp(property)]=value;                      }
   void              SetProperty(ENUM_BUFFER_PROP_STRING property,string value)  { this.m_string_prop[this.IndexProp(property)]=value;                      }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство буфера
   long              GetProperty(ENUM_BUFFER_PROP_INTEGER property)        const { return this.m_long_prop[property];                                       }
   double            GetProperty(ENUM_BUFFER_PROP_DOUBLE property)         const { return this.m_double_prop[this.IndexProp(property)];                     }
   string            GetProperty(ENUM_BUFFER_PROP_STRING property)         const { return this.m_string_prop[this.IndexProp(property)];                     }
//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства буфера
   string            GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_BUFFER_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_BUFFER_PROP_STRING property);
//--- Возвращает флаг поддержания буфером данного свойства
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property)          { return true;       }
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)           { return true;       }
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property)           { return true;       }

//--- Сравнивает объекты CBuffer между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-буфера)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CBuffer между собой по всем свойствам (для поиска равных объектов-буферов)
   bool              IsEqual(CBuffer* compared_obj) const;
                     
//--- Устанавливает имя буфера
   void              SetName(const string name)                                  { this.m_name=name;  }

//--- Конструктор по умолчанию
                     CBuffer(void){;}
protected:

Задание пользовательского имени буферу даёт возможность именовать буфер для последующего его поиска в списке-коллекции по этому имени.

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

Исправим это упущение — объявим ещё один метод, возвращающий индекс цвета, установленный буферу в указанной позиции таймсерии, а метод, возвращающий цвет буфера в указанной позиции таймсерии переименуем с GetColorBufferValue() на GetColorBufferValueColor():

//--- Возвращает размер массива буфера данных
   virtual int       GetDataTotal(const uint buffer_index=0)   const;
//--- Возвращает значение из указанного индекса указанного массива буфера (1) данных, (2) индекса цвета, (3) цвета
   double            GetDataBufferValue(const uint buffer_index,const uint series_index) const;
   int               GetColorBufferValueIndex(const uint series_index) const;
   color             GetColorBufferValueColor(const uint series_index) const;
//--- Устанавливает значение в указанный индекс указанному массиву буфера (1) данных, (2) цвета

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

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

//+------------------------------------------------------------------+
//| Возвращает значение индекса цвета из указанного индекса таймсерии|
//| указанного массива буфера цвета                                  |
//+------------------------------------------------------------------+
int CBuffer::GetColorBufferValueIndex(const uint series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==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 uint series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0)
      return clrNONE;
   int color_index=this.GetColorBufferValueIndex(series_index);
   return(color_index>WRONG_VALUE ? (color)this.ArrayColors[color_index] : clrNONE);
  }
//+------------------------------------------------------------------+

Раньше у нас был только один метод, и индекс цвета мы получали прямо внутри него:

//+------------------------------------------------------------------+
//| Возвращает значение из указанного индекса таймсерии              |
//| указанного массива буфера цвета                                  |
//+------------------------------------------------------------------+
color CBuffer::GetColorBufferValue(const uint series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0)
      return clrNONE;
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   int color_index=(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]);
   return (color)this.ArrayColors[color_index];
  }
//+------------------------------------------------------------------+

Теперь этот расчёт вынесен в отдельный метод GetColorBufferValueIndex(), а в методе возврата цвета бара мы вместо расчёта индекса используем вызов этого нового метода.

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

Откроем файл класса объекта буфера стрелок \MQL5\Include\DoEasy\Objects\Indicators\BufferArrow.mqh и объявим эти методы. Заодно добавим ещё два метода для установки и возврата значений в/из массива, назначенного индикаторным буфером:

//+------------------------------------------------------------------+
//| Буфер со стилем рисования "Отрисовка стрелками"                  |
//+------------------------------------------------------------------+
class CBufferArrow : public CBuffer
  {
private:

public:
//--- Конструктор
                     CBufferArrow(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_ARROW,BUFFER_TYPE_DATA,index_plot,index_base_array,1,1,"Arrows") {}
//--- Поддерживаемые целочисленные свойства буфера
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property);
//--- Поддерживаемые вещественные свойства буфера
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property);
//--- Поддерживаемые строковые свойства буфера
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property);
//--- Выводит в журнал краткое описание буфера
   virtual void      PrintShort(void);
   
//--- Устанавливает (1) код стрелки, (2) смещение стрелок по вертикали
   virtual void      SetArrowCode(const uchar code);
   virtual void      SetArrowShift(const int shift);
   
//--- Устанавливает значение в массив буфера данных
   void              SetData(const uint series_index,const double value)               { this.SetBufferValue(0,series_index,value);       }
//--- Возвращает значение из массива буфера данных
   double            GetData(const uint series_index)                            const { return this.GetDataBufferValue(0,series_index);  }
   
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает код стрелки                                        |
//+------------------------------------------------------------------+
void CBufferArrow::SetArrowCode(const uchar code)
  {
   this.SetProperty(BUFFER_PROP_ARROW_CODE,code);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,code);
  }
//+------------------------------------------------------------------+
//| Устанавливает смещение стрелок по вертикали                      |
//+------------------------------------------------------------------+
void CBufferArrow::SetArrowShift(const int shift)
  {
   this.SetProperty(BUFFER_PROP_ARROW_SHIFT,shift);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,shift);
  }
//+------------------------------------------------------------------+

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

В файлы объектов-буферов с одним буфером для отрисовки, а именно: линий (BufferLine.mqh), отрезков (BufferSection.mqh) и гистограммы от нуля (BufferHistogram.mqh) также впишем два метода установки/возврата значений в/из массива буфера данных объекта-буфера:

//--- Устанавливает значение в массив буфера данных
   void              SetData(const uint series_index,const double value)               { this.SetBufferValue(0,series_index,value);       }
//--- Возвращает значение из массива буфера данных
   double            GetData(const uint series_index)                            const { return this.GetDataBufferValue(0,series_index);  }

В файлы объектов-буферов с двумя буферами для отрисовки, а именно: гистограммы на двух массивах данных (BufferHistogram2.mqh), загзага (BufferZigZag.mqh) и заливки между двумя массивами данных (BufferFilling.mqh) впишем четыре метода установки/возврата значений в/из массивов буферов данных объекта-буфера:

//--- Устанавливает значение в (1) нулевой, (2) первый массив буфера данных
   void              SetData0(const uint series_index,const double value)              { this.SetBufferValue(0,series_index,value);       }
   void              SetData1(const uint series_index,const double value)              { this.SetBufferValue(1,series_index,value);       }
//--- Возвращает значение из (1) нулевого, (2) первого массива буфера данных
   double            GetData0(const uint series_index)                           const { return this.GetDataBufferValue(0,series_index);  }
   double            GetData1(const uint series_index)                           const { return this.GetDataBufferValue(1,series_index);  }

В файлы объектов-буферов с четырьмя буферами для отрисовки, а именно: баров (BufferBars.mqh) и свечей (BufferCandlts.mqh) впишем восемь методов установки/возврата значений OHLC в/из массивов буферов данных объекта-буфера:

//--- Устанавливает значение (1) Open, (2) High, (3) Low, (4) Close в соответствующий массив буфера данных
   void              SetDataOpen(const uint series_index,const double value)           { this.SetBufferValue(0,series_index,value);       }
   void              SetDataHigh(const uint series_index,const double value)           { this.SetBufferValue(1,series_index,value);       }
   void              SetDataLow(const uint series_index,const double value)            { this.SetBufferValue(2,series_index,value);       }
   void              SetDataClose(const uint series_index,const double value)          { this.SetBufferValue(3,series_index,value);       }
//--- Возвращает значение (1) Open, (2) High, (3) Low, (4) Close из соответствующего массива буфера данных
   double            GetDataOpen(const uint series_index)                        const { return this.GetDataBufferValue(0,series_index);  }
   double            GetDataHigh(const uint series_index)                        const { return this.GetDataBufferValue(1,series_index);  }
   double            GetDataLow(const uint series_index)                         const { return this.GetDataBufferValue(2,series_index);  }
   double            GetDataClose(const uint series_index)                       const { return this.GetDataBufferValue(3,series_index);  }

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

У нас всё готово для создания класса-коллекции объектов индикаторных буферов.

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

Класс-коллекция объектов-буферов

В папке \MQL5\Include\DoEasy\Collections\ создадим новый файл BuffersCollection.mqh с базовым классом CObject стандартной библиотеки, и сразу же подключим к нему файлы классов базового списка библиотеки и объектов-буферов:

//+------------------------------------------------------------------+
//|                                            BuffersCollection.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Indicators\BufferArrow.mqh"
#include "..\Objects\Indicators\BufferLine.mqh"
#include "..\Objects\Indicators\BufferSection.mqh"
#include "..\Objects\Indicators\BufferHistogram.mqh"
#include "..\Objects\Indicators\BufferHistogram2.mqh"
#include "..\Objects\Indicators\BufferZigZag.mqh"
#include "..\Objects\Indicators\BufferFilling.mqh"
#include "..\Objects\Indicators\BufferBars.mqh"
#include "..\Objects\Indicators\BufferCandles.mqh"
//+------------------------------------------------------------------+
//| Коллекция индикаторных буферов                                   |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {

Давайте наполним тело класса всем необходимым содержимым (благо его не много), и далее рассмотрим назначение всего необходимого:

//+------------------------------------------------------------------+
//| Коллекция индикаторных буферов                                   |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Список объектов-буферов
   
//--- Возвращает индекс следующего (1) рисуемого, (2) базового буфера
   int                     GetIndexNextPlot(void);
   int                     GetIndexNextBase(void);
//--- Создаёт новый объект-буфер и помещает его в список-коллекцию
   bool                    CreateBuffer(ENUM_BUFFER_STATUS status);
   
public:
//--- Возвращает (1) себя, (2) список таймсерий
   CBuffersCollection     *GetObject(void)               { return &this;                                       }
   CArrayObj              *GetList(void)                 { return &this.m_list;                                }
//--- Возвращает количество (1) рисуемых буферов, (2) всех массивов, использующихся для построения всех буферов в коллекции
   int                     PlotsTotal(void);
   int                     BuffersTotal(void);
   
//--- Создаёт новый буфер (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);    }
   
//--- Возвращает буфер по индексу Plot
   CBuffer                *GetBufferByPlot(const int plot_index);
//--- Возвращает буферы по стилю рисования по порядковому номеру
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   CBufferArrow           *GetBufferArrow(const int number);
   CBufferLine            *GetBufferLine(const int number);
   CBufferSection         *GetBufferSection(const int number);
   CBufferHistogram       *GetBufferHistogram(const int number);
   CBufferHistogram2      *GetBufferHistogram2(const int number);
   CBufferZigZag          *GetBufferZigZag(const int number);
   CBufferFilling         *GetBufferFilling(const int number);
   CBufferBars            *GetBufferBars(const int number);
   CBufferCandles         *GetBufferCandles(const int number);
   
//--- Конструктор
                           CBuffersCollection();
  };
//+------------------------------------------------------------------+

Итак, m_list — список, в который будем "складывать" и хранить все создаваемые объекты-буферы. Является наследником класса динамического массива указателей на экземпляры класса CObject.

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

Поясню. При создании буфера для индикатора мы ему задаём номер в окне данных (номер рисуемого буфера) и связываем с double-массивом (индекс базового массива буфера). Почему "базового"? Да просто буфер для отрисовки может использовать несколько массивов. Базовым будет самый первый назначаемый массив в качестве индикаторного. Остальные используемые для отрисовки массивы будут иметь индекс "базовый массив"+N.

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

Первый буфер:

  • Рисуемый буфер — индекс 0
    • Базовый массив — индекс 0
    • Второй массив — индекс 1
    • Массив цвета — индекс 2

Второй буфер:

  • Рисуемый буфер — индекс 1
    • Базовый массив — индекс 3
    • Второй массив — индекс 4
    • Массив цвета — индекс 5

Третий буфер:

  • Рисуемый буфер — индекс 2
    • Базовый массив — индекс 6
    • Второй массив — индекс 7
    • Массив цвета — индекс 8

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

Метод CreateBuffer() создаёт новый буфер и помещает его в список-коллекцию.
Методы GetObject() и GetList() возвращают указатели на объект класса-коллекции и на список объектов-буферов класса-коллекции соответственно.
Методы PlotsTotal() и BuffersTotal() возвращают количество созданных рисуемых буферов в коллекции и общее количество используемых массивов для построения всех рисуемых буферов соответственно.

Публичные методы для создания объектов-буферов с конкретным стилем рисования:

//--- Создаёт новый буфер (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);    }

Методы возвращают результат работы приватного метода создания объекта-буфера CreateBuffer() с указанием стиля рисования создаваемого буфера.

Метод GetBufferByPlot() возвращает указатель на буфер по его индексу рисуемого буфера.

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

//--- Возвращает буферы по стилю рисования по порядковому номеру
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   CBufferArrow           *GetBufferArrow(const int number);
   CBufferLine            *GetBufferLine(const int number);
   CBufferSection         *GetBufferSection(const int number);
   CBufferHistogram       *GetBufferHistogram(const int number);
   CBufferHistogram2      *GetBufferHistogram2(const int number);
   CBufferZigZag          *GetBufferZigZag(const int number);
   CBufferFilling         *GetBufferFilling(const int number);
   CBufferBars            *GetBufferBars(const int number);
   CBufferCandles         *GetBufferCandles(const int number);

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

Мы создали четыре буфера стрелок BufferArrow() с индексами рисуемого буфера 0, 1, 2 и 3.
Затем мы создали пять буферов линий BufferLine() с индексами рисуемого буфера 4, 5, 6, 7 и 8.

Теперь нам нужно поработать со третьим буфером стрелок (который с индексом 2), и с четвёртым буфером линий (с индексом 7).
Чтобы получить указатель на третий буфер стрелок, мы просто получаем его по порядковому номеру (не индексу, а именно номеру). Номер должен отсчитываться от нуля. Т.е., для получения третьего буфера стрелок, мы должны его получить так:

CBufferArrow *buffer_arrow=GetBufferArrow(2); // третий буфер стрелок (0,1,2)

Чтобы получить указатель на четвёртый буфер линий, мы должны получить его так:

CBufferLine *buffer_line=GetBufferLine(3); // четвёртый буфер линий (0,1,2,3)


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

Конструктор класса:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CBuffersCollection::CBuffersCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_BUFFERS_ID);
  }
//+------------------------------------------------------------------+

Очищаем список, устанавливаем списку флаг сортированного списка и устанавливаем типу коллекции идентификатор списка коллекции индикаторных буферов.

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

//+------------------------------------------------------------------+
//| Возвращает индекс следующего рисуемого буфера                    |
//+------------------------------------------------------------------+
int CBuffersCollection::GetIndexNextPlot(void)
  {
//--- Получаем указатель на список, и если по какой-то причине список не создан - возвращаем -1
   CArrayObj *list=this.GetList();
   if(list==NULL)
      return WRONG_VALUE;
//--- Получаем индекс рисуемого буфера с наибольшим значением, и если метод FindBufferMax() вернул -1,
//--- то это означает, что список пустой - возвращаем индекс 0 для самого первого буфера в списке
   int index=CSelect::FindBufferMax(list,BUFFER_PROP_INDEX_PLOT);
   if(index==WRONG_VALUE)
      index=0;
//--- если же индекс не -1,
   else
     {
      //--- получаем объект-буфер из списка по его индексу
      CBuffer *buffer=this.m_list.At(index);
      if(buffer==NULL)
         return WRONG_VALUE;
      //--- Возвращаем индекс, следующий за индексом Plot данного объекта-буфера
      index=buffer.IndexPlot()+1;
     }
//--- Возвращаем значение индекса
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс следующего базового буфера                     |
//+------------------------------------------------------------------+
int CBuffersCollection::GetIndexNextBase(void)
  {
//--- Получаем указатель на список, и если по какой-то причине список не создан - возвращаем -1
   CArrayObj *list=this.GetList();
   if(list==NULL)
      return WRONG_VALUE;
//--- Получаем наибольший индекс следующего массива, который можно назначить индикаторным буфером,
//--- и если метод FindBufferMax() вернул -1,
//--- то это означает, что список пустой - возвращаем индекс 0 для самого первого буфера в списке
   int index=CSelect::FindBufferMax(list,BUFFER_PROP_INDEX_NEXT);
   if(index==WRONG_VALUE)
      index=0;
//--- если же индекс не -1,
   else
     {
      //--- получаем объект-буфер из списка по его индексу
      CBuffer *buffer=this.m_list.At(index);
      if(buffer==NULL)
         return WRONG_VALUE;
      //--- Возвращаем значение индекса следующего массива из свойств данного объекта-буфера
      index=buffer.IndexNextBuffer();
     }
//--- Возвращаем значение индекса
   return index;
  }
//+------------------------------------------------------------------+

Логика двух этих методов идентична, и я её расписал в комментариях к строкам кода.

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

//+------------------------------------------------------------------+
//| Создаёт новый объект-буфер и помещает его в список-коллекцию     |
//+------------------------------------------------------------------+
bool CBuffersCollection::CreateBuffer(ENUM_BUFFER_STATUS status)
  {
//--- Получаем индекс рисуемого буфера и индекс, по которому назначается первый массив буфера в качестве индикаторного
   int index_plot=this.GetIndexNextPlot();
   int index_base=this.GetIndexNextBase();
//--- Если любой из индексов не получен - возвращаем false
   if(index_plot==WRONG_VALUE || index_base==WRONG_VALUE)
      return false;
//--- Если уже достигнуто максимально возможное количество буферов индикатора - сообщаем об этом и возвращаем false
   if(this.m_list.Total()==IND_BUFFERS_MAX)
     {
      ::Print(CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED));
      return false;
     }
//--- Создаём описание стиля рисования буфера
   string descript=::StringSubstr(::EnumToString(status),14);
//--- Объявляем объект абстрактного буфера
   CBuffer *buffer=NULL;
//--- В зависимости от переданного в метод статуса (стиля рисования) создаём объект-буфер
   switch(status)
     {
      case BUFFER_STATUS_ARROW      : buffer=new CBufferArrow(index_plot,index_base);        break;
      case BUFFER_STATUS_LINE       : buffer=new CBufferLine(index_plot,index_base);         break;
      case BUFFER_STATUS_SECTION    : buffer=new CBufferSection(index_plot,index_base);      break;
      case BUFFER_STATUS_HISTOGRAM  : buffer=new CBufferHistogram(index_plot,index_base);    break;
      case BUFFER_STATUS_HISTOGRAM2 : buffer=new CBufferHistogram2(index_plot,index_base);   break;
      case BUFFER_STATUS_ZIGZAG     : buffer=new CBufferZigZag(index_plot,index_base);       break;
      case BUFFER_STATUS_FILLING    : buffer=new CBufferFilling(index_plot,index_base);      break;
      case BUFFER_STATUS_BARS       : buffer=new CBufferBars(index_plot,index_base);         break;
      case BUFFER_STATUS_CANDLES    : buffer=new CBufferCandles(index_plot,index_base);      break;
      default: break;
     }
//--- Если не удалось создать буфер - сообщаем об этом и возвращаем false
   if(buffer==NULL)
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ)," ",descript);
      return false;
     }
//--- Если по какой-то причине не удалось добавить объект-буфер в список коллекции -
//--- сообщаем о неудаче, удаляем созданный объект-буфер и возвращаем false
   if(!this.m_list.Add(buffer))
     {
      ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_BUFFER));
      delete buffer;
      return false;
     }
//--- Устанавливаем имя объекту-буферу и возвращаем true
   buffer.SetName("Buffer"+descript+"("+(string)buffer.IndexPlot()+")");
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает буфер по индексу Plot                                 |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByPlot(const int plot_index)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_INDEX_PLOT,plot_index,EQUAL);
   return(list!=NULL && list.Total()==1 ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает буфер "Отрисовка стрелками" по порядковому номеру     |
//| (0 - самый первый созданный буфер стрелок, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
CBufferArrow *CBuffersCollection::GetBufferArrow(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_ARROW,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает буфер "Линия" по порядковому номеру                   |
//| (0 - самый первый созданный буфер линий, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
CBufferLine *CBuffersCollection::GetBufferLine(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_LINE,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает буфер "Отрезки" по порядковому номеру                 |
//| (0 - самый первый созданный буфер отрезков, 1,2,N - последующие) |
//+------------------------------------------------------------------+
CBufferSection *CBuffersCollection::GetBufferSection(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_SECTION,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает буфер "Гистограмма от нулевой линии" по номеру        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
CBufferHistogram *CBuffersCollection::GetBufferHistogram(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_HISTOGRAM,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает буфер "Гистограмма на двух буферах" по номеру         |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
CBufferHistogram2 *CBuffersCollection::GetBufferHistogram2(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_HISTOGRAM2,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает буфер "ZigZag" по порядковому номеру                  |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
CBufferZigZag *CBuffersCollection::GetBufferZigZag(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_ZIGZAG,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//|Возвращает буфер "Цветовая заливка между двумя уровнями" по номеру|
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
CBufferFilling *CBuffersCollection::GetBufferFilling(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_FILLING,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает буфер "Отображение в виде баров" по порядковому номеру|
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
CBufferBars *CBuffersCollection::GetBufferBars(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_BARS,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+
//|Возвращает буфер "Отображение в виде свечей" по порядковому номеру|
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
CBufferCandles *CBuffersCollection::GetBufferCandles(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_STATUS,BUFFER_STATUS_CANDLES,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }

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

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

//+------------------------------------------------------------------+
//| Возвращает количество рисуемых буферов                           |
//+------------------------------------------------------------------+
int CBuffersCollection::PlotsTotal(void)
  {
   int index=CSelect::FindBufferMax(this.GetList(),BUFFER_PROP_INDEX_PLOT);
   CBuffer *buffer=this.m_list.At(index);
   return(buffer!=NULL ? buffer.IndexPlot()+1 : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает количество всех индикаторных массивов                 |
//+------------------------------------------------------------------+
int CBuffersCollection::BuffersTotal(void)
  {
   int index=CSelect::FindBufferMax(this.GetList(),BUFFER_PROP_INDEX_NEXT);
   CBuffer *buffer=this.m_list.At(index);
   return(buffer!=NULL ? buffer.IndexNextBuffer() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

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

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

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

Откроем файл \MQL5\Include\DoEasy\Engine.mqh и внесём в него требуемые изменения. Здесь нам нужно практически лишь продублировать уже созданные методы класса коллекции индикаторных буферов и добавить вспомогательные методы для удобства работы.

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

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CAccountsCollection  m_accounts;                      // Коллекция аккаунтов
   CSymbolsCollection   m_symbols;                       // Коллекция символов
   CTimeSeriesCollection m_time_series;                  // Коллекция таймсерий
   CBuffersCollection   m_buffers;                       // Коллекция индикаторных буферов
   CResourceCollection  m_resource;                      // Список ресурсов
   CTradingControl      m_trading;                       // Объект управления торговлей
   CPause               m_pause;                         // Объект "Пауза"
   CArrayObj            m_list_counters;                 // Список счётчиков таймера

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

//--- Копирует в массив указанное double-свойство указанной таймсерии указанного символа
//--- Независимо от направления индексации массива, копирование производится как в массив-таймсерию
   bool                 SeriesCopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_BAR_PROP_DOUBLE property,
                                                   double &array[],const double empty=EMPTY_VALUE)
                          { return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,property,array,empty);}

//--- Возвращает (1) коллекцию буферов, (2) список буферов из коллекции буферов, (3) буфер по индексу Plot
   CBuffersCollection  *GetBuffersCollection(void)                                     { return &this.m_buffers;                             }
   CArrayObj           *GetListBuffers(void)                                           { return this.m_buffers.GetList();                    }
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
//--- Возвращает буферы по стилю рисования по порядковому номеру
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   CBufferArrow        *GetBufferArrow(const int number)                               { return this.m_buffers.GetBufferArrow(number);       }
   CBufferLine         *GetBufferLine(const int number)                                { return this.m_buffers.GetBufferLine(number);        }
   CBufferSection      *GetBufferSection(const int number)                             { return this.m_buffers.GetBufferSection(number);     }
   CBufferHistogram    *GetBufferHistogram(const int number)                           { return this.m_buffers.GetBufferHistogram(number);   }
   CBufferHistogram2   *GetBufferHistogram2(const int number)                          { return this.m_buffers.GetBufferHistogram2(number);  }
   CBufferZigZag       *GetBufferZigZag(const int number)                              { return this.m_buffers.GetBufferZigZag(number);      }
   CBufferFilling      *GetBufferFilling(const int number)                             { return this.m_buffers.GetBufferFilling(number);     }
   CBufferBars         *GetBufferBars(const int number)                                { return this.m_buffers.GetBufferBars(number);        }
   CBufferCandles      *GetBufferCandles(const int number)                             { return this.m_buffers.GetBufferCandles(number);     }

//--- Возвращает количество (1) рисуемых буферов, (2) всех индикаторных массивов
   int                  BufferPlotsTotal(void)                                         { return this.m_buffers.PlotsTotal();                 }
   int                  BuffersTotal(void)                                             { return this.m_buffers.BuffersTotal();               }

//--- Создаёт новый буфер (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();              }
   
//--- Возвращает данные буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   double               BufferDataArrow(const int number,const int series_index);
   double               BufferDataLine(const int number,const int series_index);
   double               BufferDataSection(const int number,const int series_index);
   double               BufferDataHistogram(const int number,const int series_index);
//--- Возвращает данные буфера по его порядковому номеру (1) нулевого, (2) первого буфера гистограммы на двух буферах
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   double               BufferDataHistogram20(const int number,const int series_index);
   double               BufferDataHistogram21(const int number,const int series_index);
//--- Возвращает данные буфера по его порядковому номеру (1) нулевого, (2) первого буфера зигзага
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   double               BufferDataZigZag0(const int number,const int series_index);
   double               BufferDataZigZag1(const int number,const int series_index);
//--- Возвращает данные буфера по его порядковому номеру (1) нулевого, (2) первого буфера заливки
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   double               BufferDataFilling0(const int number,const int series_index);
   double               BufferDataFilling1(const int number,const int series_index);
//--- Возвращает данные буфера по его порядковому номеру (1) Open, (2) High, (3) Low, (4) Close буферов баров
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   double               BufferDataBarsOpen(const int number,const int series_index);
   double               BufferDataBarsHigh(const int number,const int series_index);
   double               BufferDataBarsLow(const int number,const int series_index);
   double               BufferDataBarsClose(const int number,const int series_index);
//--- Возвращает данные буфера по его порядковому номеру (1) Open, (2) High, (3) Low, (4) Close буферов свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   double               BufferDataCandlesOpen(const int number,const int series_index);
   double               BufferDataCandlesHigh(const int number,const int series_index);
   double               BufferDataCandlesLow(const int number,const int series_index);
   double               BufferDataCandlesClose(const int number,const int series_index);

//--- Устанавливает данные буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   void                 BufferSetDataArrow(const int number,const int series_index,const double value);
   void                 BufferSetDataLine(const int number,const int series_index,const double value);
   void                 BufferSetDataSection(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram(const int number,const int series_index,const double value);
//--- Устанавливает данные (1) нулевого, (2) первого, (3) всех буферов гистограммы на двух буферах по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования HISTOGRAM2, 1,2,N - последующие)
   void                 BufferSetDataHistogram20(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram21(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram2(const int number,const int series_index,const double value0,const double value1);
//--- Устанавливает данные (1) нулевого, (2) первого, (3) всех буферов зигзага по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования ZIGZAG, 1,2,N - последующие)
   void                 BufferSetDataZigZag0(const int number,const int series_index,const double value);
   void                 BufferSetDataZigZag1(const int number,const int series_index,const double value);
   void                 BufferSetDataZigZag(const int number,const int series_index,const double value0,const double value1);
//--- Устанавливает данные (1) нулевого, (2) первого, (3) всех буферов заливки по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования FILLING, 1,2,N - последующие)
   void                 BufferSetDataFilling0(const int number,const int series_index,const double value);
   void                 BufferSetDataFilling1(const int number,const int series_index,const double value);
   void                 BufferSetDataFilling(const int number,const int series_index,const double value0,const double value1);
//--- Устанавливает данные (1) Open, (2) High, (3) Low, (4) Close, (5) всех буферов баров по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования BARS, 1,2,N - последующие)
   void                 BufferSetDataBarsOpen(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsHigh(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsLow(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsClose(const int number,const int series_index,const double value);
   void                 BufferSetDataBars(const int number,const int series_index,const double open,const double high,const double low,const double close);
//--- Устанавливает данные (1) Open, (2) High, (3) Low, (4) Close, (5) всех буферов свечей по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования CANDLES, 1,2,N - последующие)
   void                 BufferSetDataCandlesOpen(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesHigh(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesLow(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesClose(const int number,const int series_index,const double value);
   void                 BufferSetDataCandles(const int number,const int series_index,const double open,const double high,const double low,const double close);
   
//--- Возвращает цвет буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   color                BufferColorArrow(const int number,const int series_index);
   color                BufferColorLine(const int number,const int series_index);
   color                BufferColorSection(const int number,const int series_index);
   color                BufferColorHistogram(const int number,const int series_index);
   color                BufferColorHistogram2(const int number,const int series_index);
   color                BufferColorZigZag(const int number,const int series_index);
   color                BufferColorFilling(const int number,const int series_index);
   color                BufferColorBars(const int number,const int series_index);
   color                BufferColorCandles(const int number,const int series_index);
   
//--- Возвращает индекс цвета буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   int                  BufferColorIndexArrow(const int number,const int series_index);
   int                  BufferColorIndexLine(const int number,const int series_index);
   int                  BufferColorIndexSection(const int number,const int series_index);
   int                  BufferColorIndexHistogram(const int number,const int series_index);
   int                  BufferColorIndexHistogram2(const int number,const int series_index);
   int                  BufferColorIndexZigZag(const int number,const int series_index);
   int                  BufferColorIndexFilling(const int number,const int series_index);
   int                  BufferColorIndexBars(const int number,const int series_index);
   int                  BufferColorIndexCandles(const int number,const int series_index);

//--- Устанавливает индекс цвета в буфер цвета по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   void                 BufferSetColorIndexArrow(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexLine(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexSection(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexHistogram(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexHistogram2(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexZigZag(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexFilling(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexBars(const int number,const int series_index,const int color_index);
   void                 BufferSetColorIndexCandles(const int number,const int series_index,const int color_index);
   
//--- Устанавливает для торговых классов:

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

//+------------------------------------------------------------------+
//| Возвращает данные буфера стрелок по его порядковому номеру       |
//| (0 - самый первый созданный буфер стрелок, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
double CEngine::BufferDataArrow(const int number,const int series_index)
  {
   CBufferArrow *buff=this.m_buffers.GetBufferArrow(number);
   return(buff!=NULL ? buff.GetData(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные буфера линии по его порядковому номеру         |
//| (0 - самый первый созданный буфер линии, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
double CEngine::BufferDataLine(const int number,const int series_index)
  {
   CBufferLine *buff=this.m_buffers.GetBufferLine(number);
   return(buff!=NULL ? buff.GetData(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные буфера отрезков по его порядковому номеру      |
//| (0 - самый первый созданный буфер отрезков, 1,2,N - последующие) |
//+------------------------------------------------------------------+
double CEngine::BufferDataSection(const int number,const int series_index)
  {
   CBufferSection *buff=this.m_buffers.GetBufferSection(number);
   return(buff!=NULL ? buff.GetData(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные буфера гистограммы от нуля                     |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
double CEngine::BufferDataHistogram(const int number,const int series_index)
  {
   CBufferHistogram *buff=this.m_buffers.GetBufferHistogram(number);
   return(buff!=NULL ? buff.GetData(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные нулевого буфера гистограммы на двух буферах    |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
double CEngine::BufferDataHistogram20(const int number,const int series_index)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   return(buff!=NULL ? buff.GetData0(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные первого буфера гистограммы на двух буферах     |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
double CEngine::BufferDataHistogram21(const int number,const int series_index)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   return(buff!=NULL ? buff.GetData1(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные нулевого буфера зигзага                        |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
double CEngine::BufferDataZigZag0(const int number,const int series_index)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   return(buff!=NULL ? buff.GetData0(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные первого буфера зигзага                         |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
double CEngine::BufferDataZigZag1(const int number,const int series_index)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   return(buff!=NULL ? buff.GetData1(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные нулевого буфера заливки                        |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
double CEngine::BufferDataFilling0(const int number,const int series_index)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   return(buff!=NULL ? buff.GetData0(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные первого буфера заливки                         |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
double CEngine::BufferDataFilling1(const int number,const int series_index)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   return(buff!=NULL ? buff.GetData1(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные Open буфера баров по его порядковому номеру    |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
double CEngine::BufferDataBarsOpen(const int number,const int series_index)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   return(buff!=NULL ? buff.GetDataOpen(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные High буфера баров по его порядковому номеру    |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
double CEngine::BufferDataBarsHigh(const int number,const int series_index)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   return(buff!=NULL ? buff.GetDataHigh(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные Low буфера баров по его порядковому номеру     |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
double CEngine::BufferDataBarsLow(const int number,const int series_index)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   return(buff!=NULL ? buff.GetDataLow(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные Close буфера баров по его порядковому номеру   |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
double CEngine::BufferDataBarsClose(const int number,const int series_index)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   return(buff!=NULL ? buff.GetDataClose(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные Open буфера свечей по его порядковому номеру   |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
double CEngine::BufferDataCandlesOpen(const int number,const int series_index)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   return(buff!=NULL ? buff.GetDataOpen(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные High буфера свечей по его порядковому номеру   |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
double CEngine::BufferDataCandlesHigh(const int number,const int series_index)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   return(buff!=NULL ? buff.GetDataHigh(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные Low буфера свечей по его порядковому номеру    |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
double CEngine::BufferDataCandlesLow(const int number,const int series_index)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   return(buff!=NULL ? buff.GetDataLow(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает данные Close буфера свечей по его порядковому номеру  |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
double CEngine::BufferDataCandlesClose(const int number,const int series_index)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   return(buff!=NULL ? buff.GetDataClose(series_index) : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

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

При помощи метода GetBufferCandles() класса-коллекции буферов получаем указатель на требуемый буфер, и если буфер получен, возвращаем данные из его буфера Close по указанному индексу таймсерии. Иначе — возвращаем "Пустое значение".

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

//+------------------------------------------------------------------+
//| Устанавливает данные буфера стрелок по его порядковому номеру    |
//| (0 - самый первый созданный буфер стрелок, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataArrow(const int number,const int series_index,const double value)
  {
   CBufferArrow *buff=this.m_buffers.GetBufferArrow(number);
   if(buff==NULL)
      return;
   buff.SetData(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера линии по его порядковому номеру      |
//| (0 - самый первый созданный буфер линии, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataLine(const int number,const int series_index,const double value)
  {
   CBufferLine *buff=this.m_buffers.GetBufferLine(number);
   if(buff==NULL)
      return;
   buff.SetData(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера отрезков по его порядковому номеру   |
//| (0 - самый первый созданный буфер отрезков, 1,2,N - последующие) |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataSection(const int number,const int series_index,const double value)
  {
   CBufferSection *buff=this.m_buffers.GetBufferSection(number);
   if(buff==NULL)
      return;
   buff.SetData(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера гистограммы от нуля                  |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram(const int number,const int series_index,const double value)
  {
   CBufferHistogram *buff=this.m_buffers.GetBufferHistogram(number);
   if(buff==NULL)
      return;
   buff.SetData(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные нулевого буфера гистограммы на двух буферах |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram20(const int number,const int series_index,const double value)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   if(buff==NULL)
      return;
   buff.SetData0(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные первого буфера гистограммы на двух буферах  |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram21(const int number,const int series_index,const double value)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   if(buff==NULL)
      return;
   buff.SetData1(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов гистограммы на двух буферах    |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram2(const int number,const int series_index,const double value0,const double value1)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   if(buff==NULL)
      return;
   buff.SetData0(series_index,value0);
   buff.SetData1(series_index,value1);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные нулевого буфера зигзага                     |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataZigZag0(const int number,const int series_index,const double value)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   if(buff==NULL)
      return;
   buff.SetData0(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные первого буфера зигзага                      |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataZigZag1(const int number,const int series_index,const double value)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   if(buff==NULL)
      return;
   buff.SetData1(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов зигзага                        |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataZigZag(const int number,const int series_index,const double value0,const double value1)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   if(buff==NULL)
      return;
   buff.SetData0(series_index,value0);
   buff.SetData1(series_index,value1);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные нулевого буфера заливки                     |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataFilling0(const int number,const int series_index,const double value)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   if(buff==NULL)
      return;
   buff.SetData0(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные первого буфера заливки                      |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataFilling1(const int number,const int series_index,const double value)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   if(buff==NULL)
      return;
   buff.SetData1(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов заливки                        |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataFilling(const int number,const int series_index,const double value0,const double value1)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   if(buff==NULL)
      return;
   buff.SetData0(series_index,value0);
   buff.SetData1(series_index,value1);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера Open баров                           |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataBarsOpen(const int number,const int series_index,const double value)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   if(buff==NULL)
      return;
   buff.SetDataOpen(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера High баров                           |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataBarsHigh(const int number,const int series_index,const double value)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   if(buff==NULL)
      return;
   buff.SetDataHigh(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера Low баров                            |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataBarsLow(const int number,const int series_index,const double value)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   if(buff==NULL)
      return;
   buff.SetDataLow(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера Close баров                          |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataBarsClose(const int number,const int series_index,const double value)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   if(buff==NULL)
      return;
   buff.SetDataClose(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера Open свечей                          |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCandlesOpen(const int number,const int series_index,const double value)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   if(buff==NULL)
      return;
   buff.SetDataOpen(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера High свечей                          |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCandlesHigh(const int number,const int series_index,const double value)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   if(buff==NULL)
      return;
   buff.SetDataHigh(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера Low свечей                           |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCandlesLow(const int number,const int series_index,const double value)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   if(buff==NULL)
      return;
   buff.SetDataLow(series_index,value);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера Close свечей                         |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCandlesClose(const int number,const int series_index,const double value)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   if(buff==NULL)
      return;
   buff.SetDataClose(series_index,value);
  }
//+------------------------------------------------------------------+

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

Есть ещё два отдельных метода, устанавливающие одновременно всем буферам объектов-буферов "Бары" и "Свечи" значения OHLC по указанному индексу таймсерии:

//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов баров                          |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataBars(const int number,const int series_index,const double open,const double high,const double low,const double close)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   if(buff==NULL)
      return;
   buff.SetDataOpen(series_index,open);
   buff.SetDataHigh(series_index,high);
   buff.SetDataLow(series_index,low);
   buff.SetDataClose(series_index,close);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов свечей                         |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCandles(const int number,const int series_index,const double open,const double high,const double low,const double close)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   if(buff==NULL)
      return;
   buff.SetDataOpen(series_index,open);
   buff.SetDataHigh(series_index,high);
   buff.SetDataLow(series_index,low);
   buff.SetDataClose(series_index,close);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает цвет буфера стрелок по его порядковому номеру         |
//| (0 - самый первый созданный буфер стрелок, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
color CEngine::BufferColorArrow(const int number,const int series_index)
  {
   CBufferArrow *buff=this.m_buffers.GetBufferArrow(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера линии по его порядковому номеру           |
//| (0 - самый первый созданный линии свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
color CEngine::BufferColorLine(const int number,const int series_index)
  {
   CBufferLine *buff=this.m_buffers.GetBufferLine(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера отрезков по его порядковому номеру        |
//| (0 - самый первый созданный буфер отрезков, 1,2,N - последующие) |
//+------------------------------------------------------------------+
color CEngine::BufferColorSection(const int number,const int series_index)
  {
   CBufferSection *buff=this.m_buffers.GetBufferSection(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера гистограммы от нуля                       |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
color CEngine::BufferColorHistogram(const int number,const int series_index)
  {
   CBufferHistogram *buff=this.m_buffers.GetBufferHistogram(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера гистограммы на двух буферах               |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
color CEngine::BufferColorHistogram2(const int number,const int series_index)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера зигзага по его порядковому номеру         |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
color CEngine::BufferColorZigZag(const int number,const int series_index)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера заливки по его порядковому номеру         |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
color CEngine::BufferColorFilling(const int number,const int series_index)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера баров по его порядковому номеру           |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
color CEngine::BufferColorBars(const int number,const int series_index)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+
//| Возвращает цвет буфера свечей по его порядковому номеру          |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
color CEngine::BufferColorCandles(const int number,const int series_index)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   return(buff!=NULL ? buff.GetColorBufferValueColor(series_index) : clrNONE);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера стрелок по его порядковому номеру |
//| (0 - самый первый созданный буфер стрелок, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexArrow(const int number,const int series_index)
  {
   CBufferArrow *buff=this.m_buffers.GetBufferArrow(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера линии по его порядковому номеру   |
//| (0 - самый первый созданный линии свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexLine(const int number,const int series_index)
  {
   CBufferLine *buff=this.m_buffers.GetBufferLine(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера отрезков по его порядковому номеру|
//| (0 - самый первый созданный буфер отрезков, 1,2,N - последующие) |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexSection(const int number,const int series_index)
  {
   CBufferSection *buff=this.m_buffers.GetBufferSection(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера гистограммы от нуля               |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexHistogram(const int number,const int series_index)
  {
   CBufferHistogram *buff=this.m_buffers.GetBufferHistogram(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера гистограммы на двух буферах       |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexHistogram2(const int number,const int series_index)
  {
   CBufferHistogram2 *buff=this.m_buffers.GetBufferHistogram2(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера зигзага по его порядковому номеру |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexZigZag(const int number,const int series_index)
  {
   CBufferZigZag *buff=this.m_buffers.GetBufferZigZag(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера заливки по его порядковому номеру |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexFilling(const int number,const int series_index)
  {
   CBufferFilling *buff=this.m_buffers.GetBufferFilling(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера баров по его порядковому номеру   |
//| (0 - самый первый созданный буфер баров, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexBars(const int number,const int series_index)
  {
   CBufferBars *buff=this.m_buffers.GetBufferBars(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс цвета буфера свечей по его порядковому номеру  |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
int CEngine::BufferColorIndexCandles(const int number,const int series_index)
  {
   CBufferCandles *buff=this.m_buffers.GetBufferCandles(number);
   return(buff!=NULL ? buff.GetColorBufferValueIndex(series_index) : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Здесь всё идентично методам, возвращающим цвет, за исключением, что методы возвращают индекс цвета.

Это все доработки класса CEngine, необходимые для тестирования класса-коллекции индикаторных буферов.


Тестируем создание индикатора с использованием коллекции буферов

Для тестирования созданного класса-коллекции буферов возьмём индикатор из прошлой статьи
и сохраним его в новой папке \MQL5\Indicators\TestDoEasy\Part44\ под новым именем TestDoEasyPart44.mq5.

Весь заголовок у нас будет таким:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart44.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_chart_window
#property indicator_buffers 28
#property indicator_plots   10

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
/*sinput*/ ENUM_SYMBOLS_MODE  InpModeUsedSymbols=  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
/*sinput*/   string               InpUsedSymbols    =  "EURUSD,AUDUSD,EURAUD,EURGBP,EURCAD,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
/*sinput*/   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_CURRENT;            // Mode of used timeframes list
/*sinput*/   string               InpUsedTFs        =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   ENUM_INPUT_YES_NO    InpDrawArrow      =  INPUT_YES;  // Draw Arrow
sinput   ENUM_INPUT_YES_NO    InpDrawLine       =  INPUT_NO;   // Draw Line
sinput   ENUM_INPUT_YES_NO    InpDrawSection    =  INPUT_NO;   // Draw Section
sinput   ENUM_INPUT_YES_NO    InpDrawHistogram  =  INPUT_NO;   // Draw Histogram
sinput   ENUM_INPUT_YES_NO    InpDrawHistogram2 =  INPUT_NO;   // Draw Histogram2
sinput   ENUM_INPUT_YES_NO    InpDrawZigZag     =  INPUT_NO;   // Draw ZigZag
sinput   ENUM_INPUT_YES_NO    InpDrawFilling    =  INPUT_NO;   // Draw Filling
sinput   ENUM_INPUT_YES_NO    InpDrawBars       =  INPUT_NO;   // Draw Bars
sinput   ENUM_INPUT_YES_NO    InpDrawCandles    =  INPUT_YES;  // Draw Candles
 
sinput   bool                 InpUseSounds      =  true; // Use sounds
//--- indicator buffers
CArrayObj     *list_buffers;                    // Указатель на список объектов-буферов
//--- global variables
CEngine        engine;                          // Главный объект библиотеки CEngine
string         prefix;                          // Префикс имён графических объектов
int            min_bars;                        // Минимальное количество баров для расчёта индикатора
int            used_symbols_mode;               // Режим работы с символами
string         array_used_symbols[];            // Массив для передачи в библиотеку используемых символов
string         array_used_periods[];            // Массив для передачи в библиотеку используемых таймфреймов
//+------------------------------------------------------------------+

Из блока includes удалим подключение файлов всех объектов-буферов — теперь они уже подключены к библиотеке и здесь этого делать не нужно:

//--- includes
#include <DoEasy\Engine.mqh>
#include <DoEasy\Objects\Indicators\BufferArrow.mqh>        // 1 буфер для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferLine.mqh>         // 1 буфер для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferSection.mqh>      // 1 буфер для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferHistogram.mqh>    // 1 буфер для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferHistogram2.mqh>   // 2 буфера для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferZigZag.mqh>       // 2 буфера для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferFilling.mqh>      // 2 буфера для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferBars.mqh>         // 4 буфера для построения + 1 буфер цвета
#include <DoEasy\Objects\Indicators\BufferCandles.mqh>      // 4 буфера для построения + 1 буфер цвета
//--- Итого 18 буферов для построения + 9 буферов цвета:       27 индикаторных буферов. Из них 9 рисуемых
//--- properties

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

#property indicator_buffers 28
#property indicator_plots   10

Здесь, если предполагается большое количество различных буферов в индикаторе, то можно на начальном этапе не "считать на пальцах", а просто подставить любые значения — далее нам при первом запуске индикатора будут выданы Alert'ы, в которых будет указано верное количество рисуемых и индикаторных буферов в случае, если мы ошибочно напишем неправильное их количество.

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


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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();

//--- Установка глобальных переменных индикатора
   prefix=engine.Name()+"_";
   //--- Получаем индекс максимального используемого таймфрейма в массиве,
   //--- рассчитываем количество баров текущего периода, умещающихся в максимальном используемом периоде
   //--- Используем полученное значение если оно больше 2, иначе используем 2
   int index=ArrayMaximum(ArrayUsedTimeframes);
   int num_bars=NumberBarsInTimeframe(ArrayUsedTimeframes[index]);
   min_bars=(index>WRONG_VALUE ? (num_bars>2 ? num_bars : 2) : 2);

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

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы
   engine.BufferCreateArrow();
   engine.BufferCreateLine();
   engine.BufferCreateSection();
   engine.BufferCreateHistogram();
   engine.BufferCreateHistogram2();
   engine.BufferCreateZigZag();
   engine.BufferCreateFilling();
   engine.BufferCreateBars();
   engine.BufferCreateCandles();
   engine.BufferCreateArrow();
   
//--- Проверяем количество буферов, указанных в блоке properties
   if(engine.BufferPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BufferPlotsTotal());
   if(engine.BuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersTotal());
      
//--- Создаём массив цветов
   color array_colors[]={clrDodgerBlue,clrRed,clrGray};
//--- Получаем указатель на список-коллекцию объектов-буферов и
//--- в цикле по списку зададём буферам значения цвета не по умолчанию
   list_buffers=engine.GetListBuffers();
   for(int i=0;i<list_buffers.Total();i++)
     {
      CBuffer *buff=list_buffers.At(i);
      buff.SetColors(array_colors);
      //--- Распечатаем данные очередного буфера
      buff.Print();
     }
//--- Задаём толщину линии для ZigZag (6-й по счёту рисуемый буфер)
//--- Он имеет индекс 5 -  с учётом начала отсчёта от нуля
   CBuffer *buff_zz=engine.GetBufferByPlot(5);
   if(buff_zz!=NULL)
     {
      buff_zz.SetWidth(2);
     }
//--- Получаем второй буфер стрелок (созданный последним).
//--- Первый буфер стрелок имеет номер 0, второй - номер 1
//--- Устанавливаем размер стрелки 2 и код 161
   CBuffer *buff=engine.GetBufferArrow(1);
   if(buff!=NULL)
     {
      buff.SetWidth(2);
      buff.SetArrowCode(161);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Теперь создание буферов происходит просто — используем методы библиотеки, предназначенные для создания индикаторного буфера того, или иного типа. Все назначения массивов библиотека берёт на себя, а изменить свойства созданных буферов мы сможем после их создания. Сразу проверим указанное количество буферов в #property в заголовке индикатора, и если мы ошиблись при указании числа буферов, то нам будет выдано об этом предупреждение. Такая проверка удобна при создании индикатора, и после её можно удалить из кода.

Для проверки получения доступа к буферу воспользуемся двумя способами:
сначала получим доступ к буферу зигзага по его индексу рисуемого буфера при помощи метода GetBufferByPlot(), в котором нужно указать индекс рисуемого буфера (в данном случае — это индекс 5 для зигзага),
а затем получим доступ к самому последнему буферу стрелок, который был создан в самом конце, и является вторым по счёту буфером стрелок. Доступ к нему получим при помощи метода GetBufferArrow(), в котором нужно указать порядковый номер нужного буфера стрелок (в данном случае — номер 1, так как отсчёт начинается с нуля)

В обработчике OnCalculate() здесь остаётся всё практически без изменения, кроме того, что в буферы свечей и баров будем записывать данные при помощи методов объекта-буфера свечей (поочерёдно запишем данные в Open, High, Low и Close), а в массивы буфера баров запишем все значения OHLC одним разом. Таким образом мы проверим работу всех созданных методов работы с объектами-буферами:

//--- Расчёт индикатора
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- В цикле по количеству буферов в списке
      for(int j=0;j<total;j++)
        {
         //--- получаем очередной буфер и
         CBuffer *buff=list_buffers.At(j);
         //--- очищаем его текущие данные
         buff.ClearData(0);
         //--- Если отрисовка не используется - идём к следующему
         if(!IsUse(buff.Status()))
            continue;
         //--- В зависимости от количества буферов, заполняем их ценовыми данными
         //--- один буфер
         if(buff.BuffersTotal()==1)
            buff.SetBufferValue(0,i,close[i]);
         //--- два буфера - в первый записываем цену open бара, во второй - close
         else if(buff.BuffersTotal()==2)
           {
            buff.SetBufferValue(0,i,open[i]);
            buff.SetBufferValue(1,i,close[i]);
           }
         //--- четыре буфера - в каждый записываем цены OHLC баров
         else if(buff.BuffersTotal()==4)
           {
            //--- Если это буфер свечей
            if(buff.Status()==BUFFER_STATUS_CANDLES)
              {
               //--- создаём указатель на объект-буфер свечей присваиванием ему указателя на объект абстрактного буфера
               //--- и записываем в буферы объекта "свечи" соответствующие данные таймсерий
               CBufferCandles *candle=buff;
               candle.SetDataOpen(i,open[i]);
               candle.SetDataHigh(i,high[i]);
               candle.SetDataLow(i,low[i]);
               candle.SetDataClose(i,close[i]);
              }
            //--- Если это буфер баров - используем доступ к первому (и единственному) созданному объекту-буферу баров
            //--- и записываем в буферы объекта "бары" соответствующие данные таймсерий одним разом
            else
              {
               engine.BufferSetDataBars(0,i,open[i],high[i],low[i],close[i]);
              }
           }
         //--- В зависимости от направления свечи устанавливаем цвет буфера
         if(open[i]<close[i])
            buff.SetBufferColorIndex(i,0);
         else if(open[i]>close[i])
            buff.SetBufferColorIndex(i,1);
         else
            buff.SetBufferColorIndex(i,2);
        }
     }
//--- return value of prev_calculated for next call

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

Скомпилируем индикатор и запустим его на графике символа. Зададим ему в настройках отображение только одного буфера стрелок. Этот буфер должен отображаться точками на графике. Но у нас есть ещё второй буфер стрелок, созданный самым последним, и мы обращались ко второму буферу стрелок в OnInit() для изменения его кода и размера значка:

//--- Получаем второй буфер стрелок (созданный последним).
//--- Первый буфер стрелок имеет номер 0, второй - номер 1
//--- Устанавливаем размер стрелки 2 и код 161
   CBuffer *buff=engine.GetBufferArrow(1);
   if(buff!=NULL)
     {
      buff.SetWidth(2);
      buff.SetArrowCode(161);
     }

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

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

//--- Задаём толщину линии для ZigZag (6-й по счёту рисуемый буфер)
//--- Он имеет индекс 5 -  с учётом начала отсчёта от нуля
   CBuffer *buff_zz=engine.GetBufferByPlot(5);
   if(buff_zz!=NULL)
     {
      buff_zz.SetWidth(2);
     }

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

Проверим:


Как видим, всё задуманное и сделанное работает как и предполагалось.


Что дальше

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

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


Прикрепленные файлы |
MQL5.zip (3763.7 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Реter Konow
Реter Konow | 2 мая 2020 в 09:33
У меня маленькая претензия к изложению материала - пугающе много слов "буфер" в тексте, где ими "напичкано" каждое предложение и каждый комментарий. Немного "режет" глаза. 
Artyom Trishkin
Artyom Trishkin | 2 мая 2020 в 12:33
Реter Konow:
У меня маленькая претензия к изложению материала - пугающе много слов "буфер" в тексте, где ими "напичкано" каждое предложение и каждый комментарий. Немного "режет" глаза. 

А что поделать, если сказ про индикаторные буферы? При этом есть объект-буфер и есть массив-буфер, хранимый в объекте-буфере. :))

RodgFX
RodgFX | 8 мая 2020 в 16:23

Работа действительно эпохальная

Artyom Trishkin
Artyom Trishkin | 8 мая 2020 в 19:43
Vyacheslav Frolov:
Господи, у меня просто голова кругом! Вы такую работу проделываете! А мне как новичку вообще чтобы что -то "вдуплить"... Подскажите пожалуйста, с чего начать, кроме того что с первой части?

С первой части :)

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

Artyom Trishkin
Artyom Trishkin | 8 мая 2020 в 19:43
RodgFX:

Работа действительно эпохальная

Спасибо.
Набор инструментов для ручной разметки графиков и торговли (Часть I). Подготовка - описание структуры и класс вспомогательных функций Набор инструментов для ручной разметки графиков и торговли (Часть I). Подготовка - описание структуры и класс вспомогательных функций
Данной статье я начинаю описывать набор для графической разметки с помощью сочетаний клавиш. Очень удобно: нажал клавишу — появилась линия тренда, нажал другую — появился веер Фибоначчи с нужными параметрами. А также — возможность переключать таймфреймы, менять порядок "слоев" объектов или удалять все объекты с графика.
Мультивалютный мониторинг торговых сигналов (Часть 5): Составные сигналы Мультивалютный мониторинг торговых сигналов (Часть 5): Составные сигналы
В пятой части разработки приложения мониторинга торговых сигналов введем в систему понятие составного сигнала и реализуем необходимый для этого функционал. Ранее в приложении использовались простые сигналы, такие как RSI, WPR, CCI, а также введенная возможность использования собственного индикатора.
О методах поиска зон перекупленности/перепроданности. Часть I О методах поиска зон перекупленности/перепроданности. Часть I
Зоны перекупленности/перепроданности характеризуют определённое состояние рынка, отличающееся ослаблением динамики цен финансовых инструментов. Причём такое негативное изменение динамики наиболее выражено в заключительной стадии развития тренда любого масштаба. А так как величина прибыли при трейдинге напрямую зависит от возможности охвата максимальной амплитуды тренда, то точность выявления таких зон является важнейшей задачей при торговле любым финансовым инструментом.
Работа с таймсериями в библиотеке DoEasy (Часть 43): Классы объектов индикаторных буферов Работа с таймсериями в библиотеке DoEasy (Часть 43): Классы объектов индикаторных буферов
В статье рассмотрим создание классов объектов-индикаторных буферов как наследников абстрактного объекта-буфера, упрощающих объявление и работу с индикаторными буферами при создании собственных программ-индикаторов на основе библиотеки DoEasy.