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

14 августа 2020, 12:45
Artyom Trishkin
0
1 164

Содержание


Концепция

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

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

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

  • Параметр "Тип индикатора" — служит для указания к какому стандартному индикатору относится рисуемый и расчётный объекты-буферы создаваемого индикатора;
  • Параметр "Идентификатор индикатора" — служит для указания к какому из двух созданных индикаторов одного и того же типа принадлежат рисуемый и расчётный объекты-буферы (например, разные индикаторы МА — один имеет идентификатор 1, второй — 2, третий — 3, и т.д. При этом все они имеют один и тот же тип индикатора IND_MA)

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

  • Параметр "Тип индикатора" — служит для указания к какому стандартному индикатору относится рисуемый и расчётный объекты-буферы создаваемого индикатора;
  • Параметр "Идентификатор индикатора" — служит для указания к какому из двух созданных индикаторов одного и того же типа принадлежат рисуемый и расчётный объекты-буферы (например, разные индикаторы МА — один имеет идентификатор 1, второй — 2, третий — 3, и т.д. При этом все они имеют один и тот же тип индикатора IND_MA);
  • Параметр "Линия индикатора" — указывает на конкретную линию индикатора, полученного при помощи указания двух предыдущих параметров

Помимо этого параметра добавим ещё один: "Короткое наименование индикатора", в котором будем хранить наименование стандартного индикатора в соответствии с тем, которое отображается стандартными индикаторами в подокне, но с добавлением символа и периода. Например, для стохастика это наименование будет выглядеть так:

Рис.1. Стандартный индикатор Stochastic Oscillator

Рис.2. Мультисимвольный мультипериодный стандартный индикатор Stochastic Oscillator


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

Поменяем название файла \MQL5\Include\DoEasy\Datas.mqh на \MQL5\Include\DoEasy\Data.mqh (почему так ранее назвал, не знаю, скорее всего — ошибки моего перевода на английский язык), и впишем константы индексов новых сообщений библиотеки:

//--- CBuffer
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE,               // Индекс базового буфера данных
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT,               // Порядковый номер рисуемого буфера
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR,              // Индекс буфера цвета
   MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS,                // Количество буферов данных
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE,          // Индекс массива для назначения следующим индикаторным буфером
   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT,          // Индекс следующего по счёту рисуемого буфера
   MSG_LIB_TEXT_BUFFER_TEXT_ID,                       // Идентификатор буферов индикатора
   MSG_LIB_TEXT_BUFFER_TEXT_IND_LINE_MODE,            // Линия индикатора
   MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE,               // Хэндл индикатора, использующего буфер
   MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE,                 // Тип индикатора, использующего буфер
   MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME,                // Период данных буфера (таймфрейм)
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS,                   // Статус буфера
   MSG_LIB_TEXT_BUFFER_TEXT_TYPE,                     // Тип буфера
   MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE,                   // Активен
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE,               // Код стрелки
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT,              // Смещение стрелок по вертикали
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN,               // Количество начальных баров без отрисовки и значений в DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE,                // Тип графического построения
   MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA,                // Отображение значений построения в окне DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_SHIFT,                    // Сдвиг графического построения индикатора по оси времени в барах
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE,               // Стиль линии отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH,               // Толщина линии отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE,               // Размер значка стрелки
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM,                // Количество цветов
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR,                    // Цвет отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE,              // Пустое значение для построения, для которого нет отрисовки
   MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL,                   // Символ буфера
   MSG_LIB_TEXT_BUFFER_TEXT_LABEL,                    // Имя индикаторной графической серии, отображаемое в окне DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME,                 // Наименование индикатора, использующего буфер
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME_SHORT,           // Короткое наименование индикатора, использующего буфер
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS_NAME,              // Индикаторный буфер с типом графического построения
   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Неправильно указано количество буферов индикатора (#property indicator_buffers)
   MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED,      // Достигнуто максимально возможное количество индикаторных буферов
   MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ,            // Нет ни одного объекта-буфера для стандартного индикатора

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

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

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

Дополним файл \MQL5\Include\DoEasy\Defines.mqh всеми необходимыми новыми данными, требующимися для реализации функционала работы с многобуферными стандартными индикаторами.

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

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Data.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Линии индикатора                                                 |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_LINE_MODE
  {
   INDICATOR_LINE_MODE_MAIN,                                // Главная линия
   INDICATOR_LINE_MODE_SIGNAL,                              // Сигнальная линия
   INDICATOR_LINE_MODE_UPPER,                               // Верхняя линия
   INDICATOR_LINE_MODE_MIDDLE,                              // Средняя линия
   INDICATOR_LINE_MODE_LOWER,                               // Нижняя линия
   INDICATOR_LINE_MODE_JAWS,                                // Линия Jaws
   INDICATOR_LINE_MODE_TEETH,                               // Линия Teeth
   INDICATOR_LINE_MODE_LIPS,                                // Линия Lips
   INDICATOR_LINE_MODE_DI_PLUS,                             // Линия +DI
   INDICATOR_LINE_MODE_DI_MINUS,                            // Линия -DI
  };
//+------------------------------------------------------------------+

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

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

К перечислению целочисленных свойств объекта-буфера добавим новое значение и увеличим количество целочисленных свойств с 23 до 24:

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

Точно так же добавим новое строковое свойство и увеличим их количество до 4:

//+------------------------------------------------------------------+
//| Строковые свойства буфера                                        |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_STRING
  {
   BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Символ буфера
   BUFFER_PROP_LABEL,                                       // Имя индикаторной графической серии, отображаемое в окне DataWindow
   BUFFER_PROP_IND_NAME,                                    // Наименование индикатора, использующего буфер
   BUFFER_PROP_IND_NAME_SHORT,                              // Короткое наименование индикатора, использующего буфер
  };
#define BUFFER_PROP_STRING_TOTAL  (4)                       // Общее количество строковых свойств буфера
//+------------------------------------------------------------------+

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

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

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

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

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

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

//--- Если не удалось изменить размер массива индикаторных буферов на новый - выводим об этом сообщение с указанием на строку
   if(::ArrayResize(this.DataBuffer,(int)this.GetProperty(BUFFER_PROP_NUM_DATAS))==WRONG_VALUE)
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_DRAWING_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError());
      
//--- Если не удалось изменить размер массива цветов на новый (только для не расчётного буфера) - выводим об этом сообщение с указанием на строку
   if(this.TypeBuffer()>BUFFER_TYPE_CALCULATE)
      if(::ArrayResize(this.ArrayColors,(int)this.ColorsTotal())==WRONG_VALUE)
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",(string)::GetLastError());

//--- Для DRAW_FILLING заполняем массив цветов двумя цветами по умолчанию
   if(this.Status()==BUFFER_STATUS_FILLING)
     {
      this.SetColor(clrBlue,0);
      this.SetColor(clrRed,1);
     }

//--- Связывание индикаторных буферов с массивами
//--- В цикле по количеству индикаторных буферов
   int total=::ArraySize(DataBuffer);
   for(int i=0;i<total;i++)
     {
      //--- рассчитываем индекс очередного массива и
      //--- связываем индикаторный буфер по рассчитанному индексу с динамическим массивом,
      //--- находящимся по индексу цикла i в массиве DataBuffer
      int index=(int)this.GetProperty(BUFFER_PROP_INDEX_BASE)+i;
      ::SetIndexBuffer(index,this.DataBuffer[i].Array,(this.TypeBuffer()==BUFFER_TYPE_DATA ? INDICATOR_DATA : INDICATOR_CALCULATIONS));
      //--- Установка всем массивам буферов флага индексации как в таймсерии
      ::ArraySetAsSeries(this.DataBuffer[i].Array,true);
     }
//--- Связывание буфера цвета с массивом (только для не расчётного буфера и не для буфера заливки)
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
     {
      ::SetIndexBuffer((int)this.GetProperty(BUFFER_PROP_INDEX_COLOR),this.ColorBufferArray,INDICATOR_COLOR_INDEX);
      ::ArraySetAsSeries(this.ColorBufferArray,true);
     }
//--- Если буфер расчётный - закончили
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
//--- Установка целочисленных параметров графической серии
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_TYPE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_CODE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_ARROW_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_ARROW_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_DRAW_BEGIN));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHOW_DATA,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHOW_DATA));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE));
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH));
   this.SetColor((color)this.GetProperty(BUFFER_PROP_COLOR));
//--- Установка вещественных параметров графической серии
   ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE));
//--- Установка строковых параметров графической серии
   ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
  }
//+------------------------------------------------------------------+

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if((property==BUFFER_PROP_ARROW_CODE || property==BUFFER_PROP_ARROW_SHIFT) || 
      (
       this.TypeBuffer()==BUFFER_TYPE_CALCULATE && 
       property!=BUFFER_PROP_TYPE && 
       property!=BUFFER_PROP_INDEX_NEXT_BASE && 
       property!=BUFFER_PROP_IND_LINE_MODE && 
       property!=BUFFER_PROP_IND_HANDLE &&
       property!=BUFFER_PROP_IND_TYPE &&
       property!=BUFFER_PROP_ID
      )
     ) return false;
   return true; 
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| строковое свойство, возвращает ложь в противном случае           |
//+------------------------------------------------------------------+
bool CBufferLine::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE && property!=BUFFER_PROP_IND_NAME_SHORT)
      return false;
   return true;
  }
//+------------------------------------------------------------------+

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

Такие (или подобные — в BufferCalculate.mqh) изменения уже внесены во все файлы классов-наследников объекта абстрактного буфера: BufferArrow.mqh, BufferBars.mqh, BufferCalculate.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferSection.mqh, BufferZigZag.mqh и BufferLine.mqh, который мы уже рассмотрели. Ознакомиться с изменениями можно в прикреплённых к статье файлах.

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

//+------------------------------------------------------------------+
//| Создаёт мультисимвольный мультипериодный MA                      |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                 const int ma_period,
                                 const int ma_shift,
                                 const ENUM_MA_METHOD ma_method,
                                 const ENUM_APPLIED_PRICE applied_price,
                                 const int id=WRONG_VALUE)
  {
//--- Создаём хэндл индикатора и устанавливаем идентификатор по умолчанию
   int handle=::iMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price);
   int identifier=(id==WRONG_VALUE ? IND_MA : id);
   color array_colors[1]={clrRed};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_MA);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetShowData(true);
      buff.SetLabel("MA("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Moving Average");
      buff.SetColors(array_colors);
      
      //--- Создаём расчётный буфер, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_MA);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("MA("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Moving Average");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Почитать подробнее о работе метода можно в предыдущей статье. Отмечу, что единственным отличием от ранее рассмотренных таких же методов, является установка нового свойства объекту-буферу — тип линии однобуферного индикатора устанавливаем как "Главная". Такие дополнения были внесены во все ранее написанные методы создания однобуферных стандартных индикаторов в подокне, рассмотренных в прошлой статье, и в методы создания однобуферных стандартных индикаторов в основном окне, которые уже добавлены в файл класса-коллекции объектов-буферов BuffersCollection.mqh — это методы CreateAMA(), CreateDEMA(), CreateFrAMA(), CreateMA(), CreateSAR(), CreateTEMA() и CreateVIDYA().
Да, в конструкторе класса мы и так устанавливаем это значение линии по умолчанию, но здесь я добавил безусловную установку этого свойства для того, чтобы эти методы соответствовали по стилю методам создания многобуферных стандартных индикаторов, которые сейчас и рассмотрим.

Рассмотрим методы создания многобуферных стандартных индикаторов на примере метода создания объекта стандартного индикатора Average Directional Movement Index:

//+------------------------------------------------------------------+
//| Создаёт мультисимвольный мультипериодный ADX                     |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE)
  {
//--- Создаём хэндл индикатора и устанавливаем идентификатор по умолчанию
   int handle=::iADX(symbol,timeframe,adx_period);
   int identifier=(id==WRONG_VALUE ? IND_ADX : id);
   color array_colors[1]={clrLightSeaGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры линии ADX
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);     // Это главная линия индикатора
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      buff.SetLabel(buff.IndicatorShortName());
      buff.SetColors(array_colors);
      
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры линии +DI
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_PLUS);  // Это линия +DI
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      buff.SetLabel("+DI");
      array_colors[0]=clrYellowGreen;
      buff.SetColors(array_colors);
      buff.SetStyle(STYLE_DOT);
      
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры линии -DI
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS); // Это линия -DI
      buff.SetShowData(true);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetIndicatorShortName("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      buff.SetLabel("-DI");
      array_colors[0]=clrWheat;
      buff.SetColors(array_colors);
      buff.SetStyle(STYLE_DOT);
      
      //--- Создаём расчётный буфер линии ADX, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры линии ADX
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_MAIN);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetLabel("ADX("+symbol+","+TimeframeDescription(timeframe)+": "+(string)adx_period+")");
      
      //--- Создаём расчётный буфер линии +DI, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры линии +DI
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_PLUS);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetLabel("+DI");
      
      //--- Создаём расчётный буфер линии -DI, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры линии -DI
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ADX);
      buff.SetLineMode(INDICATOR_LINE_MODE_DI_MINUS);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetIndicatorName("Average Directional Movement Index");
      buff.SetLabel("-DI");
     }
   return handle;
  }
//+------------------------------------------------------------------+

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

Были написаны такие методы для создания объектов многобуферных стандартных индикаторов: CreateADX(), CreateADXWilder(), CreateMACD(), CreateRVI(), CreateStochastic(), CreateBands(), CreateEnvelopes() и CreateFractals(). Методы идентичны вышерассмотренному, и описывать их нет смысла — их можно изучить самостоятельно в прикреплённых к статье файлах.

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

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

//+------------------------------------------------------------------+
//| Подготавливает данные расчётного буфера                          |
//| указанного стандартного индикатора                               |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list_ind==NULL || list_ind.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copied=WRONG_VALUE;
   int idx0=0,idx1=1,idx2=2;
   switch((int)std_ind)
     {
   //--- Однобуферные стандартные индикаторы
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
      
        buffer=list_ind.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copied;
      
   //--- Многобуферные стандартные индикаторы
      case IND_ALLIGATOR   :
      case IND_GATOR       :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_BANDS :
        idx0=1;
        idx1=0;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
   //--- Многобуферные стандартные индикаторы в подокне
      case IND_ADX   :
      case IND_ADXW  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;

      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,0,total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,0,total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

Как видим, все проводимые над объектами-буферами действия, которые идентичны для разных типов стандартных индикаторов, но отличаются от других типов стандартных индикаторов, мы поместили в отдельные блоки обработки — каждому блоку соответствуют свои индикаторы, обработка которых одинакова. Для индикатора Bollinger Bands пришлось менять местами индексы его буферов, так как почему-то индексация его буферов (верхняя линия, средняя линия и нижняя линия отличаются от индексации этих буферов в окне данных — верхняя линия, нижняя линия, средняя линия). Поэтому пришлось вводить дополнительные две переменные idx0 и idx1, в которые прописываем реальные индексы линий стандартного индикатора для каждого из типов. Для всех типов индикаторов индексы их линий идут подряд: 0, 1 и 2, а для Bollinger Bands первые два индекса поменяны местами: 1, 0 и 2.

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

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

//+------------------------------------------------------------------+
//| Очищает данные буфера указанного стандартного индикатора         |
//| по индексу таймсерии                                             |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
//--- Получаем список объектов-буферов по типу и идентификатору
   CArrayObj *list_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   if(list_ind==NULL || list_ind.Total()==0)
      return;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list_ind.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
   //--- Однобуферные стандартные индикаторы
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        buffer=list_ind.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
   //--- Многобуферные стандартные индикаторы
      case IND_ALLIGATOR:
      case IND_GATOR    :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ADX   :
      case IND_ADXW  :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_BANDS    :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ENVELOPES   :
      case IND_FRACTALS    :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;

      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;

      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает значения для текущего графика в буферы указанного  |
//| стандартного индикатора по индексу таймсерии в соответствии      |
//| с символом/периодом объекта-буфера                               |
//+------------------------------------------------------------------+
bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE)
  {
//--- Получаем список объектов-буферов по типу и идентификатору
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
//--- Получаем список рисуемых буферов с идентификатором
   CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Получаем список расчётных буферов с идентификатором
   CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Если любой из списков пустой - уходим
   if(list_data.Total()==0 || list_calc.Total()==0)
      return false;
//--- Объявляем необходимые объекты и переменные
   CBuffer *buffer_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   CBuffer *buffer_calc2=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   uchar clr=color_index;
   long vol0=0,vol1=0;
   datetime time_period=0;
   double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
   double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
   double value20=EMPTY_VALUE, value21=EMPTY_VALUE;

//--- В зависимости от типа стандартного индикатора
   switch((int)ind_type)
     {
   //--- Однобуферные стандартные индикаторы
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        //--- Получаем объекты рисуемого и расчётного буферов
        buffer_data0=list_data.At(0);
        buffer_calc0=list_calc.At(0);
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        //--- Находим индекс бара на периоде, соответствующий времени начала текущего бара
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Получаем значение по этому индексу из буфера индикатора
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Получаем соответствующий времени бар текущего графика
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
           num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Берём значения для расчёта цвета
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           if(ind_type!=IND_BWMFI)
              clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           else
             {
              vol0=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
              vol1=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period+1);
              clr=
                (
                 value00>value01 && vol0>vol1 ? 0 :
                 value00<value01 && vol0<vol1 ? 1 :
                 value00>value01 && vol0<vol1 ? 2 :
                 value00<value01 && vol0>vol1 ? 3 : 4
                );
             }
           buffer_data0.SetBufferColorIndex(index,clr);
          }
        return true;
      
   //--- Многобуферные стандартные индикаторы
      case IND_ADX   :
      case IND_ADXW  :
        //--- Получаем объекты рисуемого и расчётного буферов
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Находим индекс бара на периоде, соответствующий времени начала текущего бара
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Получаем значение по этому индексу из буфера индикатора
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Получаем соответствующий времени бар текущего графика
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
           num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Берём значения для расчёта цвета
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(1,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.GetDataBufferValue(2,series_index_start+num_bars));
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data2.SetBufferValue(2,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_BANDS    :
        //--- Получаем объекты рисуемого и расчётного буферов
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Находим индекс бара на периоде, соответствующий времени начала текущего бара
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Получаем значение по этому индексу из буфера индикатора
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Получаем соответствующий времени бар текущего графика
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
           num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Берём значения для расчёта цвета
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(1,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.GetDataBufferValue(2,series_index_start+num_bars));
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data2.SetBufferValue(2,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        //--- Получаем объекты рисуемого и расчётного буферов
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Находим индекс бара на периоде, соответствующий времени начала текущего бара
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Получаем значение по этому индексу из буфера индикатора
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Получаем соответствующий времени бар текущего графика
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
           num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Берём значения для расчёта цвета
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(1,series_index_start+num_bars));
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        //--- Получаем объекты рисуемого и расчётного буферов
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Находим индекс бара на периоде, соответствующий времени начала текущего бара
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Получаем значение по этому индексу из буфера индикатора
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Получаем соответствующий времени бар текущего графика
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
           num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Берём значения для расчёта цвета
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(1,series_index_start+num_bars));
        //--- В цикле по количеству баров в num_bars заполняем рисуемый буфер значением из расчётного буфера, взятых по индексу index_period
        //--- и устанавливаем цвет рисуемого буфера в зависимости от соотношения значений value00 и value01
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ALLIGATOR:
        break;
      case IND_GATOR    :
        break;
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

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

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

//--- Возвращает описание буфера стандартного индикатора по типу и идентификатору
   string                  GetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id,const ENUM_INDICATOR_LINE_MODE line_mode=INDICATOR_LINE_MODE_MAIN);
//--- Возвращает короткое наименование стандартного индикатора по типу и идентификатору
   string                  GetIndicatorShortNameByTypeID(const ENUM_INDICATOR ind_type,const int id);
   
//--- Конструктор
                           CBuffersCollection();
//--- Получение указателей на коллекцию таймсерий (метод вызывается в методе CollectionOnInit() объекта CEngine)
   void                    OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries;  }
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает описание буфера стандартного индикатора               |
//| по типу и идентификатору                                         |
//+------------------------------------------------------------------+
string CBuffersCollection::GetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id,const ENUM_INDICATOR_LINE_MODE line_mode=INDICATOR_LINE_MODE_MAIN)
  {
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_MODE,line_mode,EQUAL);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.Label();
  }
//+------------------------------------------------------------------+
//| Возвращает короткое наименование стандартного индикатора         |
//| по типу и идентификатору                                         |
//+------------------------------------------------------------------+
string CBuffersCollection::GetIndicatorShortNameByTypeID(const ENUM_INDICATOR ind_type,const int id)
  {
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.IndicatorShortName();
  }
//+------------------------------------------------------------------+

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

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

//--- Подготавливает данные расчётного буфера всех созданных стандартных индикаторов
   bool                 BufferPreparingDataAllBuffersStdInd(void)                         { return this.m_buffers.PreparingDataAllBuffersStdInd();    }

//--- Возвращает описание буфера стандартного индикатора по типу и идентификатору
   string               BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id,const ENUM_INDICATOR_LINE_MODE line_mode)
                          { return this.m_buffers.GetLabelByTypeID(ind_type,id,line_mode);                        }
//--- Возвращает короткое наименование стандартного индикатора по типу и идентификатору
   string               BufferGetIndicatorShortNameByTypeID(const ENUM_INDICATOR ind_type,const int id)
                          { return this.m_buffers.GetIndicatorShortNameByTypeID(ind_type,id);                     }

//--- Выводит краткое описание всех индикаторных буферов коллекции буферов

Методы возвращают результат вызова одноимённых методов класса-коллекции буферов.

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

//+------------------------------------------------------------------+
//| Возвращает описание буфера стандартного индикатора               |
//| по типу и идентификатору                                         |
//+------------------------------------------------------------------+
string CEngine::BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id)
  {
   CArrayObj *list=m_buffers.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.Label();
  }
//+------------------------------------------------------------------+

На сегодня это все изменения, которые необходимо было сделать для создания объектов многобуферных стандартных индикаторов.


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

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

Логика индикаторов по сравнению с уже рассмотренным тестовым индикатором из прошлой статьи, никак не поменялась. Мы лишь добавим вызов методов создания нужных индикаторов в обработчике OnInit().

Сохраним индикатор из прошлой статьи в новой папке \MQL5\Indicators\TestDoEasy\Part49\ под именем TestDoEasyPart49_1.mq5.
Этот индикатор будет создавать и отображать стандартные индикаторы в подокне текущего графика символа. Его обработчик OnInit() будет таким:

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

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

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы для построения выбранного стандартного индикатора
   bool success=false;
   switch(InpIndType)
     {
//--- Однобуферные стандартные индикаторы в подокне
      case IND_AC          :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);                                      break;
      case IND_AD          :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                          break;
      case IND_AO          :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,1);                                      break;
      case IND_ATR         :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,14,1);                                  break;
      case IND_BEARS       :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,13,1);                           break;
      case IND_BULLS       :  success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,13,1);                           break;
      case IND_BWMFI       :  success=engine.BufferCreateBWMFI(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                       break;
      case IND_CHAIKIN     :  success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,3,10,MODE_EMA,VOLUME_TICK,1);       break;
      case IND_CCI         :  success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,14,PRICE_TYPICAL,1);                    break;
      case IND_DEMARKER    :  success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,14,1);                             break;
      case IND_FORCE       :  success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,13,MODE_SMA,VOLUME_TICK,1);           break;
      case IND_MOMENTUM    :  success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                 break;
      case IND_MFI         :  success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,14,VOLUME_TICK,1);                      break;
      case IND_OSMA        :  success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);                break;
      case IND_OBV         :  success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                         break;
      case IND_RSI         :  success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                      break;
      case IND_STDDEV      :  success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,20,0,MODE_SMA,PRICE_CLOSE,1);        break;
      case IND_TRIX        :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                     break;
      case IND_WPR         :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,14,1);                                  break;
      case IND_VOLUMES     :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                     break;
      
//--- Многобуферные стандартные индикаторы в подокне
      case IND_ADX         :  success=engine.BufferCreateADX(InpUsedSymbols,InpPeriod,14,1);                                  break;
      case IND_ADXW        :  success=engine.BufferCreateADXWilder(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_MACD        :  success=engine.BufferCreateMACD(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);                break;
      case IND_RVI         :  success=engine.BufferCreateRVI(InpUsedSymbols,InpPeriod,10,1);                                  break;
      case IND_STOCHASTIC  :  success=engine.BufferCreateStochastic(InpUsedSymbols,InpPeriod,5,3,3,MODE_SMA,STO_LOWHIGH,1);   break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Проверяем количество буферов, указанных в блоке properties
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Создаём массив цветов и зададём всем буферам в коллекции значения цвета не по умолчанию
//--- (закомментировано так как в методах создания стандартных индикаторов по умолчанию уже заданы цвета)
//--- (всегда можно задать нужные цвета либо для всех индикаторов как здесь, либо для каждого индивидуально)
   //color array_colors[]={clrGreen,clrRed,clrGray};
   //engine.BuffersSetColors(array_colors);

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

//--- Установим уровни где они требуются и определим разрядность данных
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    break;
      
      case IND_CCI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER    :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_RSI         :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Установим короткое имя индикатора и разрядность данных
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//---
   CArrayObj *list=engine.GetListBuffers();
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.Print();
     }
   //return INIT_FAILED;
//---
//--- Успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

Скомпилируем индикатор и запустим его на графике EURUSD, H1, предварительно выставив в настройках индикатора используемый символ EURUSD, а используемый таймфрейм 4 Hours. Таким образом, мы будем отображать выбираемые в настройках стандартные индикаторы на графике H1 с графика H4:


Теперь создадим индикатор, отображающий стандартные индикаторы в основном окне графика символа.

Сохраним индикатор из прошлой статьи в новой папке \MQL5\Indicators\TestDoEasy\Part49\ под именем TestDoEasyPart49_2.mq5.
Обработчик OnInit() этого индикатора будет таким:

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

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

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы для построения выбранного стандартного индикатора
   bool success=false;
   switch(InpIndType)
     {
//--- Однобуферные стандартные индикаторы в основном окне
      case IND_AMA         :  success=engine.BufferCreateAMA(InpUsedSymbols,InpPeriod,9,2,30,0,PRICE_CLOSE,1);                break;
      case IND_DEMA        :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,14,0,PRICE_CLOSE,1);                   break;
      case IND_FRAMA       :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,14,0,PRICE_CLOSE,1);                  break;
      case IND_MA          :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,10,0,MODE_SMA,PRICE_CLOSE,1);            break;
      case IND_SAR         :  success=engine.BufferCreateSAR(InpUsedSymbols,InpPeriod,0.02,0.2,1);                            break;
      case IND_TEMA        :  success=engine.BufferCreateTEMA(InpUsedSymbols,InpPeriod,14,0,PRICE_CLOSE,1);                   break;
      case IND_VIDYA       :  success=engine.BufferCreateVIDYA(InpUsedSymbols,InpPeriod,9,12,0,PRICE_CLOSE,1);                break;
      
//--- Многобуферные стандартные индикаторы в основном окне
      case IND_BANDS       :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,20,0,2.0,PRICE_CLOSE,1);              break;
      case IND_ENVELOPES   :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,14,0,MODE_SMA,PRICE_CLOSE,0.1,1); break;
      case IND_FRACTALS    :  success=engine.BufferCreateFractals(InpUsedSymbols,InpPeriod,1);                                break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Проверяем количество буферов, указанных в блоке properties
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Создаём массив цветов и зададём всем буферам в коллекции значения цвета не по умолчанию
//--- (закомментировано так как в методах создания стандартных индикаторов по умолчанию уже заданы цвета)
//--- (всегда можно задать нужные цвета либо для всех индикаторов как здесь, либо для каждого индивидуально)
   //color array_colors[]={clrGreen,clrRed,clrGray};
   //engine.BuffersSetColors(array_colors);

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

//--- Установим уровни где они требуются и определим разрядность данных
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    break;
      
      case IND_CCI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER    :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_RSI         :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Установим короткое имя индикатора и разрядность данных
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//---
   CArrayObj *list=engine.GetListBuffers();
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.Print();
     }
   //return INIT_FAILED;
//---
//--- Успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

Скомпилируем индикатор и запустим его на графике EURUSD, H1, предварительно выставив в настройках индикатора используемый символ EURUSD, а используемый таймфрейм 4 Hours. Таким образом, мы будем отображать выбираемые в настройках стандартные индикаторы на графике H1 с графика H4:



Что дальше

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

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

К содержанию

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

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


Прикрепленные файлы |
MQL5.zip (3794.86 KB)
Непрерывная скользящая оптимизация (Часть 8): Доработка программы и исправление найденных недочетов Непрерывная скользящая оптимизация (Часть 8): Доработка программы и исправление найденных недочетов

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

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

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

Параллельная оптимизация методом роя частиц (Particle Swarm Optimization) Параллельная оптимизация методом роя частиц (Particle Swarm Optimization)

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

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

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