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

17 июня 2020, 17:46
Artyom Trishkin
4
1 672

Содержание


Концепция

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

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

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

Проиллюстрируем данную концепцию номеров объектов-буферов:

  1. Создание буфера "Стрелки". Его номер = 0,
  2. Создание буфера "Линия". Его номер = 0,
  3. Создание буфера "Стрелки". Его номер = 1,
  4. Создание буфера "Зигзаг". Его номер = 0,
  5. Создание буфера "Зигзаг". Его номер = 1,
  6. Создание буфера "Стрелки". Его номер = 2,
  7. Создание буфера "Стрелки". Его номер = 3,
  8. Создание буфера "Линия". Его номер = 1,
  9. Создание буфера "Свеча". Его номер = 0,
  10. Создание буфера "Стрелки". Его номер = 4
Помимо обращения к буферу по его порядковому номеру, к нему можно будет обратиться по его имени графической серии, таймфрейму, индексу в окне данных, по индексу в списке-коллекции. Также мы сможем получить объект-буфер, который был создан и добавлен в список-коллекцию последним, что облегчит установку параметров только что созданному объекту-буферу — создали объект, получили его и задали ему дополнительные свойства.

Доработка классов для работы с индикаторными буферами в мультипериодном режиме

Начиная с билда 2430 в MetaTrader 5 было увеличено количество символов, которые могут находиться в окне "Обзор рынка". Теперь их 5000 вместо тысячи до билда 2430. Для работы со списком символов введём новую макроподстановку в файле Defines.mqh:

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
//--- "Описание функции с номером строки ошибки"
....
//--- Параметры символов
#define CLR_DEFAULT                    (0xFF000000)               // Цвет фона символа в навигаторе по умолчанию
#ifdef __MQL5__
   #define SYMBOLS_COMMON_TOTAL        (TerminalInfoInteger(TERMINAL_BUILD)<2430 ? 1000 : 5000)   // Общее количество рабочих символов MQL5
#else 
   #define SYMBOLS_COMMON_TOTAL        (1000)                     // Общее количество рабочих символов MQL4
#endif 
//--- Идентификаторы типов отложенных запросов
#define PENDING_REQUEST_ID_TYPE_ERR    (1)                        // Тип отложенного запроса, созданного по коду возврата сервера
#define PENDING_REQUEST_ID_TYPE_REQ    (2)                        // Тип отложенного запроса, созданного по запросу
//--- Параметры таймсерий
#define SERIES_DEFAULT_BARS_COUNT      (1000)                     // Требуемое количество данных таймсерий по умолчанию
#define PAUSE_FOR_SYNC_ATTEMPTS        (16)                       // Количество милисекунд паузы между попытками синхронизации
#define ATTEMPTS_FOR_SYNC              (5)                        // Количество попыток получения факта синхронизации с сервером
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+

В файле \MQL5\Include\DoEasy\Collections\SymbolsCollection.mqh заменим зарезервированный размер массива символов с 1000 на значение новой макроподстановки в методе установки списка используемых символов:

//+------------------------------------------------------------------+
//| Устанавливает список используемых символов                       |
//+------------------------------------------------------------------+
bool CSymbolsCollection::SetUsedSymbols(const string &symbol_used_array[])
  {
   ::ArrayResize(this.m_array_symbols,0,SYMBOLS_COMMON_TOTAL);
   ::ArrayCopy(this.m_array_symbols,symbol_used_array);

А в методе создания списка символов заменим в условии цикла контрольное значение с 1000 на новую макроподстановку:

//+------------------------------------------------------------------+
//| Создание списка символов (Обзор рынка или полный список)         |
//+------------------------------------------------------------------+
bool CSymbolsCollection::CreateSymbolsList(const bool flag)
  {
   bool res=true;
   int total=::SymbolsTotal(flag);
   for(int i=0;i<total && i<SYMBOLS_COMMON_TOTAL;i++)
     {

Таким же образом и в методе записи всех используемых символов и таймфреймов в файле \MQL5\Include\DoEasy\Engine.mqh заменим 1000 на макроподстановку:

//+------------------------------------------------------------------+
//| Записывает все используемые символы и таймфреймы                 |
//| в массивы ArrayUsedSymbols и ArrayUsedTimeframes                 |
//+------------------------------------------------------------------+
void CEngine::WriteSymbolsPeriodsToArrays(void)
  {
//--- Получаем список всех созданных таймсерий (создаются по количеству используемых символов)
   CArrayObj *list_timeseries=this.GetListTimeSeries();
   if(list_timeseries==NULL)
      return;
//--- Получаем общее количество созданных таймсерий
   int total_timeseries=list_timeseries.Total();
   if(total_timeseries==0)
      return;
//--- Устанавливаем размер массива используемых символов равным количеству созданных таймсерий, а
//--- размер массива используемых таймфреймов устанавливаем равным максимально-возможному количеству таймфреймов в терминале
   if(::ArrayResize(ArrayUsedSymbols,total_timeseries,SYMBOLS_COMMON_TOTAL)!=total_timeseries || ::ArrayResize(ArrayUsedTimeframes,21,21)!=21)
      return;
//--- Обнуляем оба массива
   ::ZeroMemory(ArrayUsedSymbols);
   ::ZeroMemory(ArrayUsedTimeframes);
//--- Сбрасываем количество добавленных символов и таймфреймов в ноль и
//--- в цикле по общему количеству таймсерий
   int num_symbols=0,num_periods=0;
   for(int i=0;i<total_timeseries;i++)
     {
      //--- получаем очередной объект всех таймсерий одного символа
      CTimeSeriesDE *timeseries=list_timeseries.At(i);
      if(timeseries==NULL || this.IsExistSymbol(timeseries.Symbol()))
         continue;
      //--- увеличиваем количество используемых символов и (переменная num_symbols) и
      //--- записываем наименование символа таймсерии в массив используемых символов по индексу num_symbols-1
      num_symbols++;
      ArrayUsedSymbols[num_symbols-1]=timeseries.Symbol();
      //--- Из объекта всех таймсерий символа получаем список всех его таймсерий
      CArrayObj *list_series=timeseries.GetListSeries();
      if(list_series==NULL)
         continue;
      //--- В цикле по общему количеству таймсерий символа
      int total_series=list_series.Total();
      for(int j=0;j<total_series;j++)
        {
         //--- получаем очередной объект-таймсерию
         CSeriesDE *series=list_series.At(j);
         if(series==NULL || this.IsExistTimeframe(series.Timeframe()))
            continue;
         //--- увеличиваем количество используемых таймфреймов и (переменная num_periods) и
         //--- записываем значение таймфрейма таймсерии в массив используемых таймфреймов по индексу num_periods-1
         num_periods++;
         ArrayUsedTimeframes[num_periods-1]=series.Timeframe();
        }
     }
//--- По окончании цикла изменяем размеры обоих массивов под точное число добавленных символов и таймфреймов
   ::ArrayResize(ArrayUsedSymbols,num_symbols,SYMBOLS_COMMON_TOTAL);
   ::ArrayResize(ArrayUsedTimeframes,num_periods,21);
  }
//+------------------------------------------------------------------+


В свойствах объекта-буфера у нас имеется параметр, указывающий на индекс массива, который будет назначен следующим индикаторным буфером. Это удобно — чтобы при создании одного буфера и добавлении ему нужного количества массивов, сразу же прописать в нём индекс следующего массива, который будет первым (базовым) массивом у следующего буфера. А вот для индекса индикаторного буфера (графического построения) — это значение используется при установке буферу нужных значений функциями PlotIndexSetDouble(), PlotIndexSetInteger() и PlotIndexSetString() — у нас нет свойства в параметрах объекта-буфера. Для удобного расчёта индекса следующего графического построения добавим новое свойство в перечисление целочисленных свойств объекта-буфера в файле Defines.mqh. При этом переименуем свойство, хранящее индекс базового массива следующего объекта-буфера:

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

Соответственно, увеличим количество целочисленных свойств объекта-буфера с 19 до 20.

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

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

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

Теперь в файл \MQL5\Include\DoEasy\Datas.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_TIMEFRAME,                // Период данных буфера (таймфрейм)

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

   {"Индекс базового буфера данных","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"},
   {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"},

Так как мы изменили наименование константы, хранящей индекс следующего базового массива объекта-буфера, то во всех файлах классов объектов-буферов (BufferArrow.mqh, BufferBars.mqh, BufferCandles.mqh, BufferFilling.mqh, BufferHistogram.mqh, BufferHistogram2.mqh, BufferLine.mqh, BufferArrow.mqh, BufferSection.mqh, BufferZigZag.mqh) заменим вхождение строки BUFFER_PROP_INDEX_NEXT на  BUFFER_PROP_INDEX_NEXT_BASE, а заодно доработаем виртуальный метод, выводящий в журнал короткое описание объекта-буфера.
Рассмотрим изменения этого метода на примере класса CBufferArrow в файле \MQL5\Include\DoEasy\Objects\Indicators\BufferArrow.mqh:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание буфера                         |
//+------------------------------------------------------------------+
void CBufferArrow::PrintShort(void)
  {
   ::Print
     (
      CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_BUFFER),"(P",(string)this.IndexPlot(),"/B",(string)this.IndexBase(),"/C",(string)this.IndexColor(),"): ",
      this.GetStatusDescription(true)," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())
     );
  }
//+------------------------------------------------------------------+

Текст сообщения составляется таким образом, чтобы он был более информативным. Например, для класса стрелочного буфера его короткое описание, выводимое этим методом с внесёнными в него изменениями, будет выглядеть так:

Буфер(P0/B0/C1): Отрисовка стрелками EURUSD H1

Здесь:

  • "P" означает "Plot" — номер графического построения,
  • "B" означает "Base" — индекс базового массива, назначенного первым данному буферу (все остальные массивы этого буфера идут в порядке увеличения индекса, начиная от индекса базового массива), 
  • "C" означает "Color" — индекс массива цвета, назначенного этому буферу.

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

Буфер(P0/B0/C1): Отрисовка стрелками EURUSD H1
Буфер(P1/B2/C3): Линия EURUSD H1
Буфер(P2/B4/C5): Отрезки EURUSD H1
Буфер(P3/B6/C7): Гистограмма от нулевой линии EURUSD H1
Буфер(P4/B8/C10): Гистограмма на двух индикаторных буферах EURUSD H1
Буфер(P5/B11/C13): Зигзаг EURUSD H1
Буфер(P6/B14/C16): Цветовая заливка между двумя уровнями EURUSD H1
Буфер(P7/B16/C20): Отображение в виде баров EURUSD H1
Буфер(P8/B21/C25): Отображение в виде свечей EURUSD H1
Буфер[P8/B26/C27]: Расчётный буфер

Такое отображение уже куда нагляднее — видно какому индикаторному буферу назначены массивы с какими индексами (B и C) и какие индексы графических серий назначены этим буферам (P).
Здесь видно, что расчётному буферу (его мы сегодня сделаем) назначен индекс графической серии точно такой же, как и у предыдущего буфера. На самом деле расчётный буфер вообще не имеет графической серии и массива-буфера цвета, и на данный момент сюда выводится отладочная информация — в P8 и C27 показывается какие индексы графической серии и массива-буфера цвета должен будет иметь следующий буфер, созданный после расчётного.
После завершения работы над созданием индикаторных буферов, мы эту информацию поменяем, например на PN, CN или PX, CX, что будет обозначать отсутствие графической серии и массива-буфера цвета у расчётного буфера, или вообще уберём эти обозначения, оставив только B — индекс назначенного буферу массива.

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

//+------------------------------------------------------------------+
//|                                              BufferCalculate.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Buffer.mqh"
//+------------------------------------------------------------------+
//| Расчётный буфер                                                  |
//+------------------------------------------------------------------+
class CBufferCalculate : public CBuffer
  {
private:

public:
//--- Конструктор
                     CBufferCalculate(const uint index_plot,const uint index_array) :
                        CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,1,0,"Calculate") {}
//--- Поддерживаемые целочисленные свойства буфера
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_INTEGER property);
//--- Поддерживаемые вещественные свойства буфера
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_DOUBLE property);
//--- Поддерживаемые строковые свойства буфера
   virtual bool      SupportProperty(ENUM_BUFFER_PROP_STRING property);
//--- Выводит в журнал краткое описание буфера
   virtual void      PrintShort(void);
   
//--- Устанавливает значение в массив буфера данных
   void              SetData(const uint series_index,const double value)               { this.SetBufferValue(0,series_index,value);       }
//--- Возвращает значение из массива буфера данных
   double            GetData(const uint series_index)                            const { return this.GetDataBufferValue(0,series_index);  }
   
  };
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| целочисленное свойство, возвращает ложь в противном случае       |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if(
      property==BUFFER_PROP_INDEX_PLOT       || 
      property==BUFFER_PROP_STATUS           ||  
      property==BUFFER_PROP_TYPE             || 
      property==BUFFER_PROP_INDEX_BASE       || 
      property==BUFFER_PROP_INDEX_NEXT_BASE
     ) return true; 
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| вещественное свойство, возвращает ложь в противном случае        |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Возвращает истину, если буфер поддерживает переданное            |
//| строковое свойство, возвращает ложь в противном случае           |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание буфера                         |
//+------------------------------------------------------------------+
void CBufferCalculate::PrintShort(void)
  {
   ::Print
     (
      CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_BUFFER),"[P",(string)this.IndexPlot(),"/B",(string)this.IndexBase(),"/C",(string)this.IndexColor(),"]: ",
      this.GetTypeBufferDescription()
     );
  }
//+------------------------------------------------------------------+

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

Теперь, когда у нас есть полный набор всех классов-наследников базового объекта-буфера (CBuffer), доработаем и его.

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

//+------------------------------------------------------------------+
//| Класс абстрактного индикаторного буфера                          |
//+------------------------------------------------------------------+
class CBuffer : public CBaseObj
  {
private:
   long              m_long_prop[BUFFER_PROP_INTEGER_TOTAL];                     // Целочисленные свойства
   double            m_double_prop[BUFFER_PROP_DOUBLE_TOTAL];                    // Вещественные свойства
   string            m_string_prop[BUFFER_PROP_STRING_TOTAL];                    // Строковые свойства
   bool              m_act_state_trigger;                                        // Вспомогательный флаг переключения состояния буфера
//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство буфера
   int               IndexProp(ENUM_BUFFER_PROP_DOUBLE property)           const { return(int)property-BUFFER_PROP_INTEGER_TOTAL;                           }
   int               IndexProp(ENUM_BUFFER_PROP_STRING property)           const { return(int)property-BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_DOUBLE_TOTAL;  }
//--- Устанавливает тип графического построения по типу и статусу буфера
   void              SetDrawType(void);
//--- Возвращает скорректированный индекс массива буфера
   int               GetCorrectIndexBuffer(const uint buffer_index) const;

protected:
   struct SDataBuffer { double Array[]; };                                       // Структура для хранения массивов буферов объекта-буфера
   SDataBuffer       DataBuffer[];                                               // Массив всех индикаторных буферов объекта
   double            ColorBufferArray[];                                         // Массив буфера цвета
   int               ArrayColors[];                                              // Массив для хранения цветов



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

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

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

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 CBuffer::SetDrawType(const ENUM_DRAW_TYPE draw_type)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_DRAW_TYPE,draw_type);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_TYPE,draw_type);
  }
//+------------------------------------------------------------------+

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

Так как мы заменили константу BUFFER_PROP_INDEX_NEXT на BUFFER_PROP_INDEX_NEXT_BASE, переименуем и соответствующий метод IndexNextBuffer() на IndexNextBaseBuffer() и добавим ещё один метод, возвращающий индекс следующего рисуемого буфера:

//--- Возвращает (1) порядковый номер рисуемого буфера, (2) индекс связанного массива, (3) индекс буфера цвета,
//--- (4) индекс первого свободного связанного массива, (5) индекс следующего рисуемого буфера, (6) период данных буфера (7) статус буфера,
//--- (8) тип буфера, (9) флаг использования буфера, (10) код стрелки, (11) смещение стрелок для стиля DRAW_ARROW,
//--- (12) Количество начальных баров без отрисовки и значений в DataWindow, (13) тип графического построения,
//--- (14) флаг отображения значений построения в окне DataWindow, (15) сдвиг графического построения индикатора по оси времени,
//--- (16) стиль линии отрисовки, (17) толщину линии отрисовки, (18) количество цветов, (19) цвет отрисовки, количество буферов для построения
//--- (20) установленное пустое значение, (21) символ буфера, (22) имя индикаторной графической серии, отображаемое в окне DataWindow
   int               IndexPlot(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT);              }
   int               IndexBase(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE);              }
   int               IndexColor(void)                          const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR);             }
   int               IndexNextBaseBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE);         }
   int               IndexNextPlotBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT);         }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME);   }

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

//--- Возвращает размер массива буфера данных
   virtual int       GetDataTotal(const uint buffer_index=0)   const;
//--- Возвращает значение из указанного индекса указанного массива буфера (1) данных, (2) индекса цвета, (3) цвета
   double            GetDataBufferValue(const uint buffer_index,const uint series_index) const;
   int               GetColorBufferValueIndex(const uint series_index) const;
   color             GetColorBufferValueColor(const uint series_index) const;
//--- Устанавливает значение в указанный индекс указанному массиву буфера (1) данных, (2) цвета
   void              SetBufferValue(const uint buffer_index,const uint series_index,const double value);
   void              SetBufferColorIndex(const uint series_index,const uchar color_index);
//--- Инициализирует все буферы объекта (1) указанным, (2) установленным объекту пустым значением
   void              InitializeAll(const double value,const uchar color_index);
   void              InitializeAll(void);
//--- Заполняет пустым значением все буферы данных в указанном индексе таймсерии
   void              ClearData(const int series_index);
//--- Заполняет указанный буфер данными из (1) переданной таймсерии, (2) массива
   void              FillAsSeries(const int buffer_index,CSeriesDE *series,const ENUM_SORT_BAR_MODE property);
   void              FillAsSeries(const int buffer_index,const double &array[]);
   
  };
//+------------------------------------------------------------------+

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

Параметрический метод инициализации всех буферов объекта в качестве параметров принимает значение, которым будут инициализированы все массивы объекта-буфера, назначенные индикаторными буферами, и значение индекса цвета, которым будет инициализирован массив, назначенный буфером цвета в объекте-буфере.
Метод без входных параметров инициализирует все массивы объекта-буфера значением, установленным как "пустое" в свойствах объекта-буфера, а массив цвета инициализирует нулём — самым первым из установленных объекту-буферу цветов (либо единственным, если цвет всего один).

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

//+------------------------------------------------------------------+
//| Инициализирует все буферы объекта указанным значением            |
//+------------------------------------------------------------------+
void CBuffer::InitializeAll(const double value,const uchar color_index)
  {
   for(int i=0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++)
      ::ArrayInitialize(this.DataBuffer[i].Array,value);
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::ArrayInitialize(this.ColorBufferArray,(color_index>this.ColorsTotal()-1 ? 0 : color_index));
  }
//+------------------------------------------------------------------+
//| Инициализирует все буферы объекта                                |
//| установленным объекту пустым значением                           |
//+------------------------------------------------------------------+
void CBuffer::InitializeAll(void)
  {
   for(int i=0;i<this.GetProperty(BUFFER_PROP_NUM_DATAS);i++)
      ::ArrayInitialize(this.DataBuffer[i].Array,this.EmptyValue());
   if(this.Status()!=BUFFER_STATUS_FILLING && this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::ArrayInitialize(this.ColorBufferArray,0);
  }
//+------------------------------------------------------------------+

Сначала в цикле по количеству массивов объекта-буфера, назначенных индикаторными буферами, инициализируем каждый очередной массив переданным в метод значением в первом методе и установленным значением для буфера — во втором. Затем, при условии, что этот объект-буфер не является буфером со стилем рисования "Цветовая заливка между двумя уровнями" и объект-буфер не является расчётным буфером (у этих буферов нет буфера цвета), инициализируется массив цвета переданным в метод значением в первом методе и нулём — во втором.

//+------------------------------------------------------------------+
//| Заполняет указанный буфер данными из баров переданной таймсерии  |
//+------------------------------------------------------------------+
void CBuffer::FillAsSeries(const int buffer_index,CSeriesDE *series,const ENUM_SORT_BAR_MODE property)
  {
//--- Если передана пустая таймсерия или  передано свойство, которым нужно заполнить массив, строковое - уходим из метода
   if(series==NULL || property>FIRST_BAR_STR_PROP-1)
      return;
//--- Получаем из переданного объекта-таймсерии список всех его таймсерий
   CArrayObj *list=series.GetList();
   if(list==NULL || list.Total()==0)
      return;
//--- Получаем количество баров в списке-таймсерии
   int total=list.Total();
//--- В цикле от самого последнего элемента списка-таймсерии (от текущего бара)
   int n=0;
   for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--)
     {
      //--- получаем очередной объект-бар по индексу цикла,
      CBar *bar=list.At(i);
      //--- Устанавливаем значение копируемого свойства
      double value=
        (bar==NULL ? this.EmptyValue()                                                    :
         property<FIRST_BAR_DBL_PROP ? bar.GetProperty((ENUM_BAR_PROP_INTEGER)property)   :
         property<FIRST_BAR_STR_PROP ? bar.GetProperty((ENUM_BAR_PROP_DOUBLE)property)    :
         this.EmptyValue()
        );
      //--- рассчитываем индекс, по которому в буфер будет сохранено свойство бара и
      //--- записываем в массив-буфер по рассчитанному индексу значение полученного свойства бара
      n=total-1-i;
      this.SetBufferValue(buffer_index,n,value);
     }
  }
//+------------------------------------------------------------------+
//| Заполняет указанный буфер данными из переданного массива         |
//+------------------------------------------------------------------+
void CBuffer::FillAsSeries(const int buffer_index,const double &array[])
  {
//--- Получаем размер копируемого массива
   int total=::ArraySize(array);
   if(total==0)
      return;
//--- В цикле от самого последнего элемента массива (от текущего бара)
   int n=0;
   for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--)
     {
      //--- рассчитываем индекс, по которому в буфер будет сохранено значение из массива и
      //--- записываем в массив-буфер по рассчитанному индексу значение ячейки массива по индексу i
      n=total-1-i;
      this.SetBufferValue(buffer_index,n,array[i]);
     }
  }
//+------------------------------------------------------------------+

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

Внесём корректировки в закрытый конструктор класса:

//+------------------------------------------------------------------+
//| Закрытый параметрический конструктор                             |
//+------------------------------------------------------------------+
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 int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
//--- Сохранение целочисленных свойств
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   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.GetProperty(BUFFER_PROP_NUM_DATAS);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = this.GetProperty(BUFFER_PROP_INDEX_COLOR)+
                                                                    (this.Status()==BUFFER_STATUS_FILLING || this.TypeBuffer()==BUFFER_TYPE_CALCULATE ? 0 : 1);
   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);

//--- Если не удалось изменить размер массива индикаторных буферов на новый - выводим об этом сообщение с указанием на строку
   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));
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает количество начальных баров                         |
//| без отрисовки и значений в DataWindow                            |
//+------------------------------------------------------------------+
void CBuffer::SetDrawBegin(const int value)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_DRAW_BEGIN,value);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_DRAW_BEGIN,value);
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Устанавливает количество цветов                                  |
//+------------------------------------------------------------------+
void CBuffer::SetColorNumbers(const int number)
  {
   if(number>IND_COLORS_TOTAL || this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   int n=(this.Status()!=BUFFER_STATUS_FILLING ? number : 2);
   this.SetProperty(BUFFER_PROP_COLOR_INDEXES,n);
   ::ArrayResize(this.ArrayColors,n);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_COLOR_INDEXES,n);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает значение в указанный индекс таймсерии              |
//| указанному массиву буфера данных                                 |
//+------------------------------------------------------------------+
void CBuffer::SetBufferValue(const uint buffer_index,const uint series_index,const double value)
  {
   if(this.GetDataTotal(buffer_index)==0)
      return;
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(buffer_index);
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   if(data_index<0)
      return;
   this.DataBuffer[correct_buff_index].Array[data_index]=value;
  }
//+------------------------------------------------------------------+
//| Устанавливает индекс цвета в указанный индекс таймсерии          |
//| массива буфера цвета                                             |
//+------------------------------------------------------------------+
void CBuffer::SetBufferColorIndex(const uint series_index,const uchar color_index)
  {
   if(this.GetDataTotal(0)==0 || color_index>this.ColorsTotal()-1 || this.Status()==BUFFER_STATUS_FILLING || this.Status()==BUFFER_STATUS_NONE)
      return;
   int data_total=this.GetDataTotal(0);
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   if(::ArraySize(this.ColorBufferArray)==0)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF));
   if(data_index<0)
      return;
   this.ColorBufferArray[data_index]=color_index;
  }
//+------------------------------------------------------------------+

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

Теперь дополним класс коллекции индикаторных буферов CBuffersCollection в файле \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh.
Так как сейчас делаем возможность работы с периодами графиков, отличных от текущего, то класс-коллекция буферов должен иметь доступ к классу коллекции таймсерий. Из коллекции таймсерий будем получать все таймсерии, необходимые для расчёта построения буферов индикатора на текущем графике по данным от графиков других периодов.
Чтобы класс-коллекция буферов имел доступ к классу-коллекции таймсерий, поступим просто — передадим в коллекцию индикаторных буферов указатель на коллекцию таймсерий. По этому указателю и будем выбирать из класса необходимые нам данные и методы для их получения.

Точно так же, как заменили наименование константы во всех файлах классов объектов-буферов, здесь уже заменены все вхождения строки "BUFFER_PROP_INDEX_PLOT" на новую константу BUFFER_PROP_INDEX_NEXT_PLOT, а обращение к методу IndexNextBuffer() заменено на его переименованную версию IndexNextBaseBuffer().

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

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Indicators\BufferArrow.mqh"
#include "..\Objects\Indicators\BufferLine.mqh"
#include "..\Objects\Indicators\BufferSection.mqh"
#include "..\Objects\Indicators\BufferHistogram.mqh"
#include "..\Objects\Indicators\BufferHistogram2.mqh"
#include "..\Objects\Indicators\BufferZigZag.mqh"
#include "..\Objects\Indicators\BufferFilling.mqh"
#include "..\Objects\Indicators\BufferBars.mqh"
#include "..\Objects\Indicators\BufferCandles.mqh"
#include "..\Objects\Indicators\BufferCalculate.mqh"
#include "TimeSeriesCollection.mqh"
//+------------------------------------------------------------------+
//| Коллекция индикаторных буферов                                   |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Список объектов-буферов
   CTimeSeriesCollection  *m_timeseries;                 // Указатель на объект-коллекцию таймсерий

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

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

public:
//--- Возвращает (1) себя, (2) список таймсерий
   CBuffersCollection     *GetObject(void)               { return &this;                                       }
   CArrayObj              *GetList(void)                 { return &this.m_list;                                }
//--- Возвращает количество (1) рисуемых буферов, (2) всех массивов, использующихся для построения всех буферов в коллекции
   int                     PropertyPlotsTotal(void);
   int                     PropertyBuffersTotal(void);
   
//--- Создаёт новый буфер (1) "Отрисовка стрелками", (2) "Линия", (3) "Отрезки", (4) "Гистограмма от нулевой линии", 
//--- (5) "Гистограмма на двух индикаторных буферах", (6) "Зигзаг", (7) "Цветовая заливка между двумя уровнями",
//--- (8) "Отображение в виде баров", (9) "Отображение в виде свечей", расчётный буфер
   bool                    CreateArrow(void)             { return this.CreateBuffer(BUFFER_STATUS_ARROW);      }
   bool                    CreateLine(void)              { return this.CreateBuffer(BUFFER_STATUS_LINE);       }
   bool                    CreateSection(void)           { return this.CreateBuffer(BUFFER_STATUS_SECTION);    }
   bool                    CreateHistogram(void)         { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM);  }
   bool                    CreateHistogram2(void)        { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); }
   bool                    CreateZigZag(void)            { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG);     }
   bool                    CreateFilling(void)           { return this.CreateBuffer(BUFFER_STATUS_FILLING);    }
   bool                    CreateBars(void)              { return this.CreateBuffer(BUFFER_STATUS_BARS);       }
   bool                    CreateCandles(void)           { return this.CreateBuffer(BUFFER_STATUS_CANDLES);    }
   bool                    CreateCalculate(void)         { return this.CreateBuffer(BUFFER_STATUS_NONE);       }
   
//--- Возвращает буфер по (1) имени графической серии, (2) таймфрейму, индексу (2) Plot, (3) объекта в списке-коллекции
   CBuffer                *GetBufferByLabel(const string plot_label);
   CBuffer                *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe);
   CBuffer                *GetBufferByPlot(const int plot_index);
   CBuffer                *GetBufferByListIndex(const int index_list);
//--- Возвращает буферы по их статусу по указанному порядковому номеру
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   CBufferArrow           *GetBufferArrow(const int number);
   CBufferLine            *GetBufferLine(const int number);
   CBufferSection         *GetBufferSection(const int number);
   CBufferHistogram       *GetBufferHistogram(const int number);
   CBufferHistogram2      *GetBufferHistogram2(const int number);
   CBufferZigZag          *GetBufferZigZag(const int number);
   CBufferFilling         *GetBufferFilling(const int number);
   CBufferBars            *GetBufferBars(const int number);
   CBufferCandles         *GetBufferCandles(const int number);
   CBufferCalculate       *GetBufferCalculate(const int number);
   
//--- Инициализирует все рисуемые буферы (1) указанным, (2) установленным объекту-буферу пустым значением
   void                    InitializePlots(const double value,const uchar color_index);
   void                    InitializePlots(void);
//--- Инициализирует все расчётные буферы (1) указанным, (2) установленным объекту-буферу пустым значением
   void                    InitializeCalculates(const double value);
   void                    InitializeCalculates(void);
//--- Зададёт всем индикаторным буферам коллекции значения цвета из переданного массива цветов
   void                    SetColors(const color &array_colors[]);
   
//--- Устанавливает значение по индексу таймсерии буферу (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нулевой линии,
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей, (10) расчётному буферу
   void                    SetBufferArrowValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferLineValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferSectionValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferHistogramValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                    SetBufferHistogram2Value(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false);
   void                    SetBufferZigZagValue(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false);
   void                    SetBufferFillingValue(const int number,const int series_index,const double value1,const double value2,bool as_current=false);
   void                    SetBufferBarsValue(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
   void                    SetBufferCandlesValue(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
   void                    SetBufferCalculateValue(const int number,const int series_index,const double value);
   
//--- Устанавливает индекс цвета в буфер цвета по его порядковому номеру
//--- (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   void                    SetBufferArrowColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferLineColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferSectionColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferHistogramColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferHistogram2ColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferZigZagColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferFillingColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferBarsColorIndex(const int number,const int series_index,const uchar color_index);
   void                    SetBufferCandlesColorIndex(const int number,const int series_index,const uchar color_index);
   
//--- Очищает данные буфера по его индексу в списке в указанном баре таймсерии
   void                    Clear(const int buffer_list_index,const int series_index);
//--- Очищает данные по индексу таймсерии буферу (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нулевой линии,
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
   void                    ClearBufferArrow(const int number,const int series_index);
   void                    ClearBufferLine(const int number,const int series_index);
   void                    ClearBufferSection(const int number,const int series_index);
   void                    ClearBufferHistogram(const int number,const int series_index);
   void                    ClearBufferHistogram2(const int number,const int series_index);
   void                    ClearBufferZigZag(const int number,const int series_index);
   void                    ClearBufferFilling(const int number,const int series_index);
   void                    ClearBufferBars(const int number,const int series_index);
   void                    ClearBufferCandles(const int number,const int series_index);
   
//--- Конструктор
                           CBuffersCollection();
//--- Получение указателей на коллекцию таймсерий (метод вызывается в методе CollectionOnInit() объекта CEngine)
   void                    OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries;  }
  };
//+------------------------------------------------------------------+

Итак. Рассмотрим назначения вновь добавленных методов. Под описанием назначения методов сразу будем рассматривать их реализацию, которая, как и у большинства методов классов библиотеки, вынесена за пределы тела класса.

GetIndexLastPlot() возвращает индекс графической серии последнего созданного объекта-буфера:

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

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

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

//+------------------------------------------------------------------+
//| Получает данные нужных таймсерий и баров                         |
//| для работы с одним баром буфера                                  |
//+------------------------------------------------------------------+
int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period)
  {
//--- Получаем таймсерии текущего графика и графика таймфрейма буфера
   CSeriesDE *series_current=this.m_timeseries.GetSeries(buffer.Symbol(),PERIOD_CURRENT);
   CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe());
   if(series_current==NULL || series_period==NULL)
      return WRONG_VALUE;
//--- Получаем объект-бар текущей таймсерии, соответствующий требуемому индексу таймсерии
   CBar *bar_current=series_current.GetBar(series_index);
   if(bar_current==NULL)
      return WRONG_VALUE;
//--- Получаем объект-бар таймсерии периода графика буфера, соответствующий времени, в которое попадает бар таймсерии текущего графика
   CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),NULL,series_period.Timeframe());
   if(bar_period==NULL)
      return WRONG_VALUE;
//--- Записываем индекс бара на текущем таймфрейме, который попадает на время начала бара графика объекта-буфера 
   index_bar_period=bar_period.Index(PERIOD_CURRENT);
//--- Рассчитываем сколько баров текущего таймфрейма входит в один бар периода графика объекта-буфера
//--- и возвращаем это значение (1 если результат равен 0)
   int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe());
   return(num_bars>0 ? num_bars : 1);
  }
//+------------------------------------------------------------------+

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

Методы PropertyPlotsTotal() и PropertyBuffersTotal() — это переименованные для более понятного наименования бывшие методы PlotsTotal() и BuffersTotal(), и возвращают они верные значения для указания их как свойства программы-индикатора в #property indicator_plots и #property indicator_buffers соответственно.
Метод PropertyBuffersTotal() был только переименован, и в нём заменены наименования констант тоже на переименованные (BUFFER_PROP_INDEX_NEXT на BUFFER_PROP_INDEX_NEXT_BASE).
Метод PropertyPlotsTotal() был заново переписан в связи с появлением нового буфера — расчётного, и теперь данные нужно брать иным способом, чем ранее был использован:

//+------------------------------------------------------------------+
//| Возвращает количество рисуемых буферов                           |
//+------------------------------------------------------------------+
int CBuffersCollection::PropertyPlotsTotal(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   return(list!=NULL ? list.Total() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

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

CreateCalculate() служит для создания объекта расчётного (не рисуемого) буфера.
Метод реализован прямо в теле класса, и возвращает результат работы метода создания нового буфера, рассмотренный нами в прошлой статье, в который передаётся статус расчётного буфера:

bool CreateCalculate(void) { return this.CreateBuffer(BUFFER_STATUS_NONE); }

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

//+------------------------------------------------------------------+
//| Возвращает буфер по имени графической серии                      |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByLabel(const string plot_label)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_LABEL,plot_label,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(list.Total()-1) : NULL);
  }
//+------------------------------------------------------------------+

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

Метод GetBufferByTimeframe() возвращает указатель на объект-буфер по значению заданного ему таймфрейма:

//+------------------------------------------------------------------+
//| Возвращает буфер по таймфрейму                                   |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TIMEFRAME,timeframe,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(list.Total()-1) : NULL);
  }
//+------------------------------------------------------------------+

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

Метод GetBufferByListIndex() возвращает указатель на объект-буфер по его индексу в списке-коллекции:

//+------------------------------------------------------------------+
//| Возвращает буфер по индексу списка-коллекции                     |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetBufferByListIndex(const int index_list)
  {
   return this.m_list.At(index_list);
  }
//+------------------------------------------------------------------+

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


Метод GetBufferCalculate() возвращает указатель на объект-буфер по его порядковому номеру (в порядке создания расчётных буферов):

//+------------------------------------------------------------------+
//|Возвращает расчётный буфер по порядковому номеру                  |
//| (0 - самый первый созданный буфер свечей, 1,2,N - последующие)   |
//+------------------------------------------------------------------+
CBufferCalculate *CBuffersCollection::GetBufferCalculate(const int number)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   return(list!=NULL && list.Total()>0 ? list.At(number) : NULL);
  }
//+------------------------------------------------------------------+

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

Перегруженные методы InitializePlots() служат для инициализации всех рисуемих буферов в коллекции:

//+------------------------------------------------------------------+
//| Инициализирует все рисуемые буферы указанным пустым значением    |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializePlots(const double value,const uchar color_index)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll(value,color_index);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Инициализирует все рисуемые буферы установленным                 |
//| объекту-буферу пустым значением                                  |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializePlots(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll();
     }
  }  
//+------------------------------------------------------------------+

Метод инициализирует все рисуемые буферы коллекции значением буфера, установленным для каждого объекта-буфера, и значением индекса цвета, равным нулю.

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

Перегруженные методы InitializeCalculates() служат для инициализации всех расчётных буферов в коллекции:

//+------------------------------------------------------------------+
//| Инициализирует все расчётные буферы указанным пустым значением   |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializeCalculates(const double value)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll(value,0);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Инициализирует все расчётные буферы установленным                |
//| объекту-буферу пустым значением                                  |
//+------------------------------------------------------------------+
void CBuffersCollection::InitializeCalculates(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.InitializeAll();
     }
  }
//+------------------------------------------------------------------+

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

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

Метод SetColors() устанавливает всем рисуемым буферам коллекции значения цвета из массива цветов.

//+------------------------------------------------------------------+
//| Задаёт всем индикаторным буферам коллекции                       |
//| значения цвета из переданного массива цветов                     |
//+------------------------------------------------------------------+
void CBuffersCollection::SetColors(const color &array_colors[])
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list==NULL || list.Total()==0)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.SetColors(array_colors);
     }
  }
//+------------------------------------------------------------------+

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

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

Метод Clear() очищает данные буфера по его индексу в списке-коллекции в указанном баре таймсерии:

//+------------------------------------------------------------------+
//| Очищает данные буфера по его индексу в списке                    |
//| в указанном баре таймсерии                                       |
//+------------------------------------------------------------------+
void CBuffersCollection::Clear(const int buffer_list_index,const int series_index)
  {
   CBuffer *buff=this.GetBufferByListIndex(buffer_list_index);
   if(buff==NULL)
      return;
   buff.ClearData(series_index);
  }
//+------------------------------------------------------------------+

Сначала получаем указатель на объект-буфер по его индексу в списке рассмотренным выше методом GetBufferByListIndex(), затем при помощи метода полученного объекта-буфера ClearData() очищаем все его массивы.

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

void OnInit(CTimeSeriesCollection *timeseries) { this.m_timeseries=timeseries; }

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает значение буферу стрелок по индексу таймсерии       |
//+------------------------------------------------------------------+
void CBuffersCollection::SetBufferArrowValue(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
//--- Получаем объект-буфер стрелок
   CBufferArrow *buff=this.GetBufferArrow(number);
   if(buff==NULL)
      return;
//--- Если стоит флаг использования буфера только как для текущего таймфрейма -
//--- записываем переданное значение в текущий бар буфера и уходим (цвет не используется)
   if(as_current)
     {
      buff.SetBufferValue(0,series_index,value);
      return;
     }
//--- Получаем данные нужных таймсерий и баров и рассчитываем сколько баров текущего таймфрейма входит в один бар периода графика объекта-буфера
   int index_bar_period=series_index;
   int num_bars=this.GetBarsData(buff,series_index,index_bar_period);
   if(num_bars==WRONG_VALUE)
      return;
//--- В цикле по количеству баров рассчитываем индекс очередного бара для текущего графика и
//--- устанавливаем бару по рассчитанному индексу переданное в метод значение и цвет
   for(int i=0;i<num_bars;i++)
     {
      int index=index_bar_period-i;
      if(index<0)
         break;
      buff.SetBufferValue(0,index,value);
      buff.SetBufferColorIndex(index,color_index);
     }
  }  
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Поясню некоторые моменты.

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

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

Методы устроены так, что если объект-буфер имеет значение свойства "таймфрейм" отличное от периода текущего графика, то независимо от того, какой индекс таймсерии был передан в метод, линия индикатора будет отрисована на текущем графике с учётом данных таймфрейма объекта-буфера. Т.е. метод корректно отобразит данные с других периодов графика на текущем. Это при условии, что флаг as_current имеет значение false (по умолчанию). Если этот флаг выставить в true, то метод будет работать только с данными текущего таймфрейма, независимо от того, что объект-буфер имеет иной установленный период графика в своих свойствах.

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

//+------------------------------------------------------------------+
//| Устанавливает индекс цвета в буфер цвета                         |
//| буфера стрелок по индексу таймсерии                              |
//+------------------------------------------------------------------+
void CBuffersCollection::SetBufferArrowColorIndex(const int number,const int series_index,const uchar color_index)
  {
   CBufferArrow *buff=this.GetBufferArrow(number);
   if(buff==NULL)
      return;
   buff.SetBufferColorIndex(series_index,color_index);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Очищает данные буфера стрелок по индексу таймсерии               |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearBufferArrow(const int number,const int series_index)
  {
   CBufferArrow *buff=this.GetBufferArrow(number);
   if(buff==NULL)
      return;
   buff.SetBufferValue(0,series_index,buff.EmptyValue());
  }
//+------------------------------------------------------------------+

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

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

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

Теперь дополним и подправим класс основного объекта библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh. Поступим так же, как и при разборе доработок класса-коллекции буферов: сначала посмотрим внесённые дополнения и изменения в теле класса, а затем разберём каждый из методов.
Полный листинг тела класса отображать не будем, так как он достаточно ёмкий, и отобразим только места с изменениями и дополнениями:

//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine
  {
private:
//--- Код удалён для экономии места в статье
//--- ...
public:
//--- Код удалён для экономии места в статье
//--- ...
//--- Возвращает тип бара указанного символа указанного таймфрейма по (1) индексу, (2) времени
   ENUM_BAR_BODY_TYPE   SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   ENUM_BAR_BODY_TYPE   SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);

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

//--- Возвращает (1) коллекцию буферов, (2) список буферов из коллекции 
   CBuffersCollection  *GetBuffersCollection(void)                                     { return &this.m_buffers;                             }
   CArrayObj           *GetListBuffers(void)                                           { return this.m_buffers.GetList();                    }
//--- Возвращает буфер по (1) имени графической серии, (2) таймфрейму, индексу (3) Plot, (4) списка-коллекции, (5) последний в списке
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastBuffer(void);
//--- Возвращает буферы по стилю рисования по порядковому номеру
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   CBufferArrow        *GetBufferArrow(const int number)                               { return this.m_buffers.GetBufferArrow(number);       }
   CBufferLine         *GetBufferLine(const int number)                                { return this.m_buffers.GetBufferLine(number);        }
   CBufferSection      *GetBufferSection(const int number)                             { return this.m_buffers.GetBufferSection(number);     }
   CBufferHistogram    *GetBufferHistogram(const int number)                           { return this.m_buffers.GetBufferHistogram(number);   }
   CBufferHistogram2   *GetBufferHistogram2(const int number)                          { return this.m_buffers.GetBufferHistogram2(number);  }
   CBufferZigZag       *GetBufferZigZag(const int number)                              { return this.m_buffers.GetBufferZigZag(number);      }
   CBufferFilling      *GetBufferFilling(const int number)                             { return this.m_buffers.GetBufferFilling(number);     }
   CBufferBars         *GetBufferBars(const int number)                                { return this.m_buffers.GetBufferBars(number);        }
   CBufferCandles      *GetBufferCandles(const int number)                             { return this.m_buffers.GetBufferCandles(number);     }
   CBufferCalculate    *GetBufferCalculate(const int number)                           { return this.m_buffers.GetBufferCalculate(number);   }
   
//--- Возвращает количество (1) рисуемых буферов, (2) всех индикаторных массивов
   int                  BuffersPropertyPlotsTotal(void)                                { return this.m_buffers.PropertyPlotsTotal();         }
   int                  BuffersPropertyBuffersTotal(void)                              { return this.m_buffers.PropertyBuffersTotal();       }

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

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

//--- Устанавливает данные буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля, (5) расчётного буфера
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   void                 BufferSetDataArrow(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataLine(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataSection(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataHistogram(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false);
   void                 BufferSetDataCalculate(const int number,const int series_index,const double value);
//--- Устанавливает данные (1) нулевого, (2) первого, (3) всех буферов гистограммы на двух буферах по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования HISTOGRAM2, 1,2,N - последующие)
   void                 BufferSetDataHistogram20(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram21(const int number,const int series_index,const double value);
   void                 BufferSetDataHistogram2(const int number,const int series_index,const double value0,const double value1,const uchar color_index,bool as_current=false);
//--- Устанавливает данные (1) нулевого, (2) первого, (3) всех буферов зигзага по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования ZIGZAG, 1,2,N - последующие)
   void                 BufferSetDataZigZag0(const int number,const int series_index,const double value);
   void                 BufferSetDataZigZag1(const int number,const int series_index,const double value);
   void                 BufferSetDataZigZag(const int number,const int series_index,const double value0,const double value1,const uchar color_index,bool as_current=false);
//--- Устанавливает данные (1) нулевого, (2) первого, (3) всех буферов заливки по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования FILLING, 1,2,N - последующие)
   void                 BufferSetDataFilling0(const int number,const int series_index,const double value);
   void                 BufferSetDataFilling1(const int number,const int series_index,const double value);
   void                 BufferSetDataFilling(const int number,const int series_index,const double value0,const double value1,bool as_current=false);
//--- Устанавливает данные (1) Open, (2) High, (3) Low, (4) Close, (5) всех буферов баров по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования BARS, 1,2,N - последующие)
   void                 BufferSetDataBarsOpen(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsHigh(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsLow(const int number,const int series_index,const double value);
   void                 BufferSetDataBarsClose(const int number,const int series_index,const double value);
   void                 BufferSetDataBars(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
//--- Устанавливает данные (1) Open, (2) High, (3) Low, (4) Close, (5) всех буферов свечей по порядковому номеру созданного буфера
//--- (0 - самый первый созданный буфер со стилем рисования CANDLES, 1,2,N - последующие)
   void                 BufferSetDataCandlesOpen(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesHigh(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesLow(const int number,const int series_index,const double value);
   void                 BufferSetDataCandlesClose(const int number,const int series_index,const double value);
   void                 BufferSetDataCandles(const int number,const int series_index,const double open,const double high,const double low,const double close,const uchar color_index,bool as_current=false);
   
//--- Возвращает цвет буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   color                BufferArrowColor(const int number,const int series_index);
   color                BufferLineColor(const int number,const int series_index);
   color                BufferSectionColor(const int number,const int series_index);
   color                BufferHistogramColor(const int number,const int series_index);
   color                BufferHistogram2Color(const int number,const int series_index);
   color                BufferZigZagColor(const int number,const int series_index);
   color                BufferFillingColor(const int number,const int series_index);
   color                BufferBarsColor(const int number,const int series_index);
   color                BufferCandlesColor(const int number,const int series_index);
   
//--- Возвращает индекс цвета буфера по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   int                  BufferArrowColorIndex(const int number,const int series_index);
   int                  BufferLineColorIndex(const int number,const int series_index);
   int                  BufferSectionColorIndex(const int number,const int series_index);
   int                  BufferHistogramColorIndex(const int number,const int series_index);
   int                  BufferHistogram2ColorIndex(const int number,const int series_index);
   int                  BufferZigZagColorIndex(const int number,const int series_index);
   int                  BufferFillingColorIndex(const int number,const int series_index);
   int                  BufferBarsColorIndex(const int number,const int series_index);
   int                  BufferCandlesColorIndex(const int number,const int series_index);

//--- Устанавливает всем индикаторным буферам коллекции значения цвета из переданного массива цветов
   void                 BuffersSetColors(const color &array_colors[])                     { this.m_buffers.SetColors(array_colors);                   } 
//--- Устанавливает индекс цвета в буфер цвета по его порядковому номеру (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нуля
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
//--- (0 - самый первый созданный буфер со стилем рисования ХХХ, 1,2,N - последующие)
   void                 BufferArrowSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferArrowColorIndex(number,series_index,color_index);       }
   void                 BufferLineSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferLineColorIndex(number,series_index,color_index);        }
   void                 BufferSectionSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferSectionColorIndex(number,series_index,color_index);     }
   void                 BufferHistogramSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferHistogramColorIndex(number,series_index,color_index);   }
   void                 BufferHistogram2SetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferHistogram2ColorIndex(number,series_index,color_index);  }
   void                 BufferZigZagSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferZigZagColorIndex(number,series_index,color_index);      }
   void                 BufferFillingSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferFillingColorIndex(number,series_index,color_index);     }
   void                 BufferBarsSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferBarsColorIndex(number,series_index,color_index);        }
   void                 BufferCandlesSetColorIndex(const int number,const int series_index,const uchar color_index)
                          { this.m_buffers.SetBufferCandlesColorIndex(number,series_index,color_index);     }

//--- Очищает данные буфера по его индексу в списке в указанном баре таймсерии
   void                 BufferClear(const int buffer_list_index,const int series_index)   { this.m_buffers.Clear(buffer_list_index,series_index);     }
//--- Очищает данные по индексу таймсерии буферу (1) стрелок, (2) линии, (3) отрезков, (4) гистограммы от нулевой линии,
//--- (5) гистограммы на двух буферах, (6) зигзага, (7) заливки, (8) баров, (9) свечей
   void                 BufferArrowClear(const int number,const int series_index)         { this.m_buffers.ClearBufferArrow(number,series_index);     }
   void                 BufferLineClear(const int number,const int series_index)          { this.m_buffers.ClearBufferLine(number,series_index);      }
   void                 BufferSectionClear(const int number,const int series_index)       { this.m_buffers.ClearBufferSection(number,series_index);   }
   void                 BufferHistogramClear(const int number,const int series_index)     { this.m_buffers.ClearBufferHistogram(number,series_index); }
   void                 BufferHistogram2Clear(const int number,const int series_index)    { this.m_buffers.ClearBufferHistogram2(number,series_index);}
   void                 BufferZigZagClear(const int number,const int series_index)        { this.m_buffers.ClearBufferZigZag(number,series_index);    }
   void                 BufferFillingClear(const int number,const int series_index)       { this.m_buffers.ClearBufferFilling(number,series_index);   }
   void                 BufferBarsClear(const int number,const int series_index)          { this.m_buffers.ClearBufferBars(number,series_index);      }
   void                 BufferCandlesClear(const int number,const int series_index)       { this.m_buffers.ClearBufferCandles(number,series_index);   }

//--- Выводит краткое описание всех индикаторных буферов коллекции буферов
   void                 BuffersPrintShort(void);
   
//--- Устанавливает для торговых классов:
//--- (1) корректное значение политики исполнения, (2) значение политики исполнения,
//--- (3) корректный тип истечения ордера, (4) тип истечения ордера,
//--- (5) магик, (6) Комментарий, (7) размер проскальзывания, (8) объём, (9) срок истечения ордера,
//--- (10) флаг асинхронной отправки торгового запроса, (11) уровень логирования, (12) количество торговых попыток
   void                 TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetMagic(const uint magic,const string symbol_name=NULL);
   void                 TradingSetComment(const string comment,const string symbol_name=NULL);
   void                 TradingSetDeviation(const ulong deviation,const string symbol_name=NULL);
   void                 TradingSetVolume(const double volume=0,const string symbol_name=NULL);
   void                 TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL);
   void                 TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL);
   void                 TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL);
   void                 TradingSetTotalTry(const uchar attempts)                       { this.m_trading.SetTotalTry(attempts);                     }
   
//--- Возвращает уровень логирования торгового объекта символа торгового класса
   ENUM_LOG_LEVEL       TradingGetLogLevel(const string symbol_name)                   { return this.m_trading.GetTradeObjLogLevel(symbol_name);   }
   
//--- Устанавливает стандартные звуки (symbol==NULL) торговому объекту символа, (symbol!=NULL) торговым объектам всех символов
   void                 SetSoundsStandart(const string symbol=NULL)
                          {
                           this.m_trading.SetSoundsStandart(symbol);
                          }
//--- Устанавливает флаг использования звуков
   void                 SetUseSounds(const bool flag) { this.m_trading.SetUseSounds(flag);   }
//--- Устанавливает звук для указанного типа ордера/позиции и символа. Режим mode указывает для какого события устанавливается звук
//--- (symbol=NULL) торговым объектам всех символов, (symbol!=NULL) торговому объекту указанного символа
   void                 SetSound(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,const string symbol=NULL)
                          {
                           this.m_trading.SetSound(mode,action,sound,symbol);
                          }
//--- Воспроизводит звук по его описанию
   bool                 PlaySoundByDescription(const string sound_description);

//--- Передаёт в торговый класс и класс-коллекцию индикаторных буферов указатели на все необходимые коллекции
   void                 CollectionOnInit(void)
                          {
                           this.m_trading.OnInit(this.GetAccountCurrent(),m_symbols.GetObject(),m_market.GetObject(),m_history.GetObject(),m_events.GetObject());
                           this.m_buffers.OnInit(this.m_time_series.GetObject());
                          }

Перегруженный метод SeriesBarType() возвращает тип указанного бара на указанном символе и периоде графика
по заданному индексу соответствующей таймсерии:

//+------------------------------------------------------------------+
//| Возвращает тип бара указанного символа                           |
//| указанного таймфрейма по индексу                                 |
//+------------------------------------------------------------------+
ENUM_BAR_BODY_TYPE CEngine::SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,index);
   return(bar!=NULL ? bar.TypeBody() : (ENUM_BAR_BODY_TYPE)WRONG_VALUE);
  }  
//+------------------------------------------------------------------+

Получаем требуемый объект-бар из коллекции таймсерий по указанному индексу таймсерии и возвращаем его тип (бычий/медвежий/нулевой/с нулевым телом). При ошибке получения бара возвращается -1.

по времени:

//+------------------------------------------------------------------+
//| Возвращает тип бара указанного символа                           |
//| указанного таймфрейма по времени                                 |
//+------------------------------------------------------------------+
ENUM_BAR_BODY_TYPE CEngine::SeriesBarType(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.TypeBody() : (ENUM_BAR_BODY_TYPE)WRONG_VALUE);
  }  
//+------------------------------------------------------------------+

Получаем требуемый объект-бар из коллекции таймсерий по указанному времени и возвращаем его тип (бычий/медвежий/нулевой/с нулевым телом).
При ошибке получения бара возвращается -1.

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

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

CBuffer *GetBufferByLabel(const string plot_label) { return this.m_buffers.GetBufferByLabel(plot_label); }

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

Метод GetBufferByTimeframe() возвращает указатель на объект-буфер по его свойству "таймфрейм":

CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe) { return this.m_buffers.GetBufferByTimeframe(timeframe);}

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

Метод GetBufferByListIndex() возвращает указатель на объект-буфер по его индексу в списке-коллекции объектов-буферов класса коллекции буферов:

CBuffer *GetBufferByListIndex(const int index_list) { return this.m_buffers.GetBufferByListIndex(index_list);}

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

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

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

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

Метод GetBufferCalculate() возвращает результат работы одноимённого метода класса-коллекции буферов, рассмотренного нами выше:

CBufferCalculate *GetBufferCalculate(const int number) { return this.m_buffers.GetBufferCalculate(number); }

Методы BuffersPropertyPlotsTotal() и BuffersPropertyBuffersTotal() переименованы из ранее имевших название BufferPlotsTotal() и BuffersTotal() соответственно, и возвращают результаты работы методов PropertyPlotsTotal() и PropertyBuffersTotal() класса-коллекции буферов, рассмотренных нами выше:

int BuffersPropertyPlotsTotal(void)   { return this.m_buffers.PropertyPlotsTotal();   }
int BuffersPropertyBuffersTotal(void) { return this.m_buffers.PropertyBuffersTotal(); }

Метод BufferCreateCalculate() возвращает результат работы метода CreateCalculate() класса-коллекции буферов, рассмотренного нами выше:

bool BufferCreateCalculate(void) { return this.m_buffers.CreateCalculate(); }

По поводу метода создания расчётного буфера, хочу уточнить, что на данный момент есть одно ограничение для его использования: все расчётные буферы необходимо создавать после создания всех необходимых рисуемых буферов. Если создать один расчётный буфер между несколькими рисуемыми, то это нарушит последовательность установки массивов для индикаторных буферов, и все рисуемые индикаторные буферы, созданные после расчётного буфера, не будут отображаться. Я пока не нашёл причину такого поведения, но "найти и поправить" стоит на ближайшие задачи.

Перегруженный метод BuffersInitPlots() инициализирует все рисуемые буферы при помощи метода InitializePlots() класса-коллекции буферов, рассмотренного нами выше:

void BuffersInitPlots(const double value,const uchar color_index) { this.m_buffers.InitializePlots(value,color_index); }
void BuffersInitPlots(void)                                       { this.m_buffers.InitializePlots();                  }

Перегруженный метод BuffersInitCalculates() инициализирует все расчётные буферы при помощи метода InitializeCalculates() класса-коллекции буферов, рассмотренного нами выше:

void BuffersInitCalculates(const double value) { this.m_buffers.InitializeCalculates(value); }
void BuffersInitCalculates(void)               { this.m_buffers.InitializeCalculates();      }

Метод BufferSetDataCalculate() устанавливает данные расчётному буферу при помощи метода SetBufferCalculateValue() класса-коллекции объектов-буферов, рассмотренного нами выше:

//+------------------------------------------------------------------+
//| Устанавливает данные расчётного буфера по его порядковому номеру |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataCalculate(const int number,const int series_index,const double value)
  {
   this.m_buffers.SetBufferCalculateValue(number,series_index,value);
  }
//+------------------------------------------------------------------+

Методы BufferSetDataArrow(), BufferSetDataLine(), BufferSetDataSection(), BufferSetDataHistogram(), BufferSetDataHistogram2(), BufferSetDataZigZag(), BufferSetDataFilling(), BufferSetDataBars() и BufferSetDataCandles() были изменены, и устанавливают данные соответствующих буферов при помощи методов класса коллекции объектов-буферов, рассмотренных нами выше:

//+------------------------------------------------------------------+
//| Устанавливает данные буфера стрелок по его порядковому номеру    |
//| (0 - самый первый созданный буфер стрелок, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataArrow(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferArrowValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера линии по его порядковому номеру      |
//| (0 - самый первый созданный буфер линии, 1,2,N - последующие)    |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataLine(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferLineValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера отрезков по его порядковому номеру   |
//| (0 - самый первый созданный буфер отрезков, 1,2,N - последующие) |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataSection(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferSectionValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+
//| Устанавливает данные буфера гистограммы от нуля                  |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram(const int number,const int series_index,const double value,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferHistogramValue(number,series_index,value,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов гистограммы на двух буферах    |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер, 1,2,N - последующие)          |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataHistogram2(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferHistogram2Value(number,series_index,value1,value2,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов зигзага                        |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер зигзага, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataZigZag(const int number,const int series_index,const double value1,const double value2,const uchar color_index,bool as_current=false)
  {
   this.m_buffers.SetBufferZigZagValue(number,series_index,value1,value2,color_index,as_current);
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Устанавливает данные всех буферов заливки                        |
//| по его порядковому номеру                                        |
//| (0 - самый первый созданный буфер заливки, 1,2,N - последующие)  |
//+------------------------------------------------------------------+
void CEngine::BufferSetDataFilling(const int number,const int series_index,const double value1,const double value2,bool as_current=false)
  {
   this.m_buffers.SetBufferFillingValue(number,series_index,value1,value2,as_current);
  }
//+------------------------------------------------------------------+

...

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

...

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

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

Методы BufferArrowColor(), BufferLineColor(), BufferSectionColor(), BufferHistogramColor(), BufferHistogram2Color(), BufferZigZagColor(), BufferFillingColor(), BufferBarsColor(), BufferCandlesColor(),
BufferArrowColorIndex(), BufferLineColorIndex(), BufferSectionColorIndex(), BufferHistogramColorIndex(), BufferHistogram2ColorIndex(), BufferZigZagColorIndex(), BufferFillingColorIndex(), BufferBarsColorIndex() и BufferCandlesColorIndex() были просто переименованы для более удобного их восприятия в программе.

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

Метод BuffersSetColors() устанавливает всем индикаторным буферам коллекции значения цвета из переданного массива цветов при помощи метода SetColors() класса-коллекции объектов-буферов, рассмотренного нами выше:

void BuffersSetColors(const color &array_colors[]) { this.m_buffers.SetColors(array_colors); }

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

void BufferArrowSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferArrowColorIndex(number,series_index,color_index);       }
void BufferLineSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferLineColorIndex(number,series_index,color_index);        }
void BufferSectionSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferSectionColorIndex(number,series_index,color_index);     }
void BufferHistogramSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferHistogramColorIndex(number,series_index,color_index);   }
void BufferHistogram2SetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferHistogram2ColorIndex(number,series_index,color_index);  }
void BufferZigZagSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferZigZagColorIndex(number,series_index,color_index);      }
void BufferFillingSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferFillingColorIndex(number,series_index,color_index);     }
void BufferBarsSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferBarsColorIndex(number,series_index,color_index);        }
void BufferCandlesSetColorIndex(const int number,const int series_index,const uchar color_index)
       { this.m_buffers.SetBufferCandlesColorIndex(number,series_index,color_index);     }

Метод BufferClear() очищает данные буфера по его индексу в списке в указанном баре таймсерии при помощи вызова метода Clear() класса-коллекции объектов-буферов:

void BufferClear(const int buffer_list_index,const int series_index) { this.m_buffers.Clear(buffer_list_index,series_index); }

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

void BufferArrowClear(const int number,const int series_index)      { this.m_buffers.ClearBufferArrow(number,series_index);     }
void BufferLineClear(const int number,const int series_index)       { this.m_buffers.ClearBufferLine(number,series_index);      }
void BufferSectionClear(const int number,const int series_index)    { this.m_buffers.ClearBufferSection(number,series_index);   }
void BufferHistogramClear(const int number,const int series_index)  { this.m_buffers.ClearBufferHistogram(number,series_index); }
void BufferHistogram2Clear(const int number,const int series_index) { this.m_buffers.ClearBufferHistogram2(number,series_index);}
void BufferZigZagClear(const int number,const int series_index)     { this.m_buffers.ClearBufferZigZag(number,series_index);    }
void BufferFillingClear(const int number,const int series_index)    { this.m_buffers.ClearBufferFilling(number,series_index);   }
void BufferBarsClear(const int number,const int series_index)       { this.m_buffers.ClearBufferBars(number,series_index);      }
void BufferCandlesClear(const int number,const int series_index)    { this.m_buffers.ClearBufferCandles(number,series_index);   }

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

//+------------------------------------------------------------------+
//| Выводит краткое описание всех индикаторных буферов коллекции     |
//+------------------------------------------------------------------+
void CEngine::BuffersPrintShort(void)
  {
//--- Получаем указатель на список-коллекцию объектов-буферов
   CArrayObj *list=this.GetListBuffers();
   if(list==NULL)
      return;
   int total=list.Total();
//--- В цикле по количеству буферов в списке
//--- получаем очередной буфер и выводим в журнал его краткое описание
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL)
         continue;
      buff.PrintShort();
     }
  }
//+------------------------------------------------------------------+

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

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

//--- Передаёт в торговый класс и класс-коллекцию индикаторных буферов указатели на все необходимые коллекции
   void                 CollectionOnInit(void)
                          {
                           this.m_trading.OnInit(this.GetAccountCurrent(),m_symbols.GetObject(),m_market.GetObject(),m_history.GetObject(),m_events.GetObject());
                           this.m_buffers.OnInit(this.m_time_series.GetObject());
                          }

Данный метод вызыватется в OnInit() программ, работающих на основе библиотеки.
Вернее — в OnInit() вызывается функция инициализации библиотеки OnInitDoEasy(), в которой, наряду с иными настроечными действиями, вписан и вызов этого метода.

На этом доработка классов библиотеки на сегодня завершена.
Протестируем создание мультипериодного индикатора.

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

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

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

Строку индикатора из прошлой статьи

/*sinput*/   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_CURRENT;            // Mode of used timeframes list

заменим на новую:

/*sinput*/   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list

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

sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_CURRENT;                  // Used chart period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Запишем в переменную InpUsedTFs наименование выбранного в настройках рабочего таймфрейма
   InpUsedTFs=TimeframeDescription(InpPeriod);

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы
   engine.BufferCreateArrow();         // 2 массива
   engine.BufferCreateLine();          // 2 массива
   engine.BufferCreateSection();       // 2 массива
   engine.BufferCreateHistogram();     // 2 массива
   engine.BufferCreateHistogram2();    // 3 массива
   engine.BufferCreateZigZag();        // 3 массива
   engine.BufferCreateFilling();       // 2 массива
   engine.BufferCreateBars();          // 5 массивов
   engine.BufferCreateCandles();       // 5 массивов
   engine.BufferCreateCalculate();     // 1 массив

//--- Проверяем количество буферов, указанных в блоке properties
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Создаём массив цветов и зададём всем буферам в коллекции значения цвета не по умолчанию
   color array_colors[]={clrDodgerBlue,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);
//--- Задаём толщину линии для ZigZag (6-й по счёту рисуемый буфер)
//--- Он имеет индекс 5 -  с учётом начала отсчёта от нуля
   CBuffer *buff_zz=engine.GetBufferByPlot(5);
   if(buff_zz!=NULL)
     {
      buff_zz.SetWidth(2);
     }
     
//--- В цикле по списку объектов-буферов коллекции
   for(int i=0;i<engine.GetListBuffers().Total();i++)
     {
      //--- получаем очередной буфер
      CBuffer *buff=engine.GetListBuffers().At(i);
      if(buff==NULL)
         continue;
      //--- и устанавливаем для него отображение в окне данных в зависимости от настройки его использования
      //--- и выбранный в настройках период графика
      buff.SetShowData(IsUse(buff.Status()));
      buff.SetTimeframe(InpPeriod);
     }

Так как мы всегда в индикаторах будем использовать направление индексации как в таймсериях (это связано с организацией хранения таймсерий в библиотеке), то создадим ещё одну функцию для передачи всех массивов данных из OnCalculate() в библиотеку. Ранее у нас этим занималась функция  CopyData(), в которой массивам устанавливался флаг "как в таймсерии", а затем обратно возвращалось его прошлое состояние:

//+------------------------------------------------------------------+
//| Копирование данных из OnCalculate() второй формы в структуру     |
//+------------------------------------------------------------------+
void CopyData(const int rates_total,
              const int prev_calculated,
              const datetime &time[],
              const double &open[],
              const double &high[],
              const double &low[],
              const double &close[],
              const long &tick_volume[],
              const long &volume[],
              const int &spread[])
  {
//--- Получим флаги индексации массивов как в таймсерии, и если нет, то
//--- установим направление индексации массивам как в таймсерии
   bool as_series_time=ArrayGetAsSeries(time);
   if(!as_series_time)
      ArraySetAsSeries(time,true);
   bool as_series_open=ArrayGetAsSeries(open);
   if(!as_series_open)
      ArraySetAsSeries(open,true);
   bool as_series_high=ArrayGetAsSeries(high);
   if(!as_series_high)
      ArraySetAsSeries(high,true);
   bool as_series_low=ArrayGetAsSeries(low);
   if(!as_series_low)
      ArraySetAsSeries(low,true);
   bool as_series_close=ArrayGetAsSeries(close);
   if(!as_series_close)
      ArraySetAsSeries(close,true);
   bool as_series_tick_volume=ArrayGetAsSeries(tick_volume);
   if(!as_series_tick_volume)
      ArraySetAsSeries(tick_volume,true);
   bool as_series_volume=ArrayGetAsSeries(volume);
   if(!as_series_volume)
      ArraySetAsSeries(volume,true);
   bool as_series_spread=ArrayGetAsSeries(spread);
   if(!as_series_spread)
      ArraySetAsSeries(spread,true);
//--- Скопируем нулевой бар массивов в структуру данных OnCalculate() SDataCalculate
   rates_data.rates_total=rates_total;
   rates_data.prev_calculated=prev_calculated;
   rates_data.rates.time=time[0];
   rates_data.rates.open=open[0];
   rates_data.rates.high=high[0];
   rates_data.rates.low=low[0];
   rates_data.rates.close=close[0];
   rates_data.rates.tick_volume=tick_volume[0];
   rates_data.rates.real_volume=(#ifdef __MQL5__ volume[0] #else 0 #endif);
   rates_data.rates.spread=(#ifdef __MQL5__ spread[0] #else 0 #endif);
//--- Вернём массивам их изначальное направление индексации
   if(!as_series_time)
      ArraySetAsSeries(time,false);
   if(!as_series_open)
      ArraySetAsSeries(open,false);
   if(!as_series_high)
      ArraySetAsSeries(high,false);
   if(!as_series_low)
      ArraySetAsSeries(low,false);
   if(!as_series_close)
      ArraySetAsSeries(close,false);
   if(!as_series_tick_volume)
      ArraySetAsSeries(tick_volume,false);
   if(!as_series_volume)
      ArraySetAsSeries(volume,false);
   if(!as_series_spread)
      ArraySetAsSeries(spread,false);
  }
//+------------------------------------------------------------------+

А далее мы опять "переворачивали" эти массивы для их правильной работы с таймсериями библиотеки в OnCalculate():

//+------------------------------------------------------------------+
//| OnCalculate Блок кода для работы с индикатором:                  |
//+------------------------------------------------------------------+
//--- Установка массивов OnCalculate как таймсерий
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);

Чтобы не делать лишнюю ненужную работу, создадим ещё одну функцию в файле сервисных функций библиотеки, в которой переданные в неё массивы будут получать направление индексации как в таймсерии. Откроем файл \MQL5\Include\DoEasy\Services\DELib.mqh и впишем в него новую функцию:

//+------------------------------------------------------------------+
//| Копирование данных из OnCalculate() второй формы в структуру     |
//| и установка всем массивам флага "как таймсерия"                  |
//+------------------------------------------------------------------+
void CopyDataAsSeries(const int rates_total,
                      const int prev_calculated,
                      const datetime &time[],
                      const double &open[],
                      const double &high[],
                      const double &low[],
                      const double &close[],
                      const long &tick_volume[],
                      const long &volume[],
                      const int &spread[])
  {
//--- установим направление индексации массивам как в таймсерии
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);
//--- Скопируем нулевой бар массивов в структуру данных OnCalculate() SDataCalculate
   rates_data.rates_total=rates_total;
   rates_data.prev_calculated=prev_calculated;
   rates_data.rates.time=time[0];
   rates_data.rates.open=open[0];
   rates_data.rates.high=high[0];
   rates_data.rates.low=low[0];
   rates_data.rates.close=close[0];
   rates_data.rates.tick_volume=tick_volume[0];
   rates_data.rates.real_volume=(#ifdef __MQL5__ volume[0] #else 0 #endif);
   rates_data.rates.spread=(#ifdef __MQL5__ spread[0] #else 0 #endif);
  }
//+------------------------------------------------------------------+

Эта функция делает то же самое, что и предыдущая, но не возвращает массивам их первоначальную индексацию. Теперь вместо вызова из индикатора функции CopyData() будем вызывать новую: CopyDataAsSeries(), что избавит нас от повторной установки нужной индексации массивам:

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

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

//--- Расчёт индикатора
   CBar *bar=NULL;         // Объект-бар для определения направления свечи
   uchar color_index=0;    // Индекс цвета для установки буферу в зависимости от направления свечи

//--- Основной цикл расчёта индикатора
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- Очищаем текущий бар всех созданных буферов
      engine.BufferArrowClear(0,0);
      engine.BufferLineClear(0,0);
      engine.BufferSectionClear(0,0);
      engine.BufferHistogramClear(0,0);
      engine.BufferHistogram2Clear(0,0);
      engine.BufferZigZagClear(0,0);
      engine.BufferFillingClear(0,0);
      engine.BufferBarsClear(0,0);
      engine.BufferCandlesClear(0,0);
      
      //--- Получаем бар таймсерии, соответствующий времени индекса цикла на периоде графика, заданного в настройках
      bar=engine.SeriesGetBar(NULL,InpPeriod,time[i]);
      if(bar==NULL)
         continue;
      //--- Рассчитываем индекс цвета в зависимости от направления свечи с заданного в настройках таймфрейма
      color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2);
      //--- Проверяем в настройках и рассчитываем буфер стрелок
      if(IsUse(BUFFER_STATUS_ARROW))
         engine.BufferSetDataArrow(0,i,bar.Close(),color_index);
      //--- Проверяем в настройках и рассчитываем буфер линий
      if(IsUse(BUFFER_STATUS_LINE))
         engine.BufferSetDataLine(0,i,bar.Open(),color_index);
      //--- Проверяем в настройках и рассчитываем буфер отрезков
      if(IsUse(BUFFER_STATUS_SECTION))
         engine.BufferSetDataSection(0,i,bar.Close(),color_index);
      //--- Проверяем в настройках и рассчитываем буфер гистограммы от нуля
      if(IsUse(BUFFER_STATUS_HISTOGRAM))
         engine.BufferSetDataHistogram(0,i,open[i],color_index);
      //--- Проверяем в настройках и рассчитываем буфер гистограммы на двух буферах
      if(IsUse(BUFFER_STATUS_HISTOGRAM2))
         engine.BufferSetDataHistogram2(0,i,bar.Open(),bar.Close(),color_index);
      //--- Проверяем в настройках и рассчитываем буфер зигзага
      if(IsUse(BUFFER_STATUS_ZIGZAG))
        {
         //--- Устанавливаем значение для зигзага Low бара (для бычьих свечей)
         double value1=bar.Low();
         double value2=value1;
         //--- Если свеча медвежья (индекс цвета = 1) - устанавливаем для зигзага значение High бара
         if(color_index==1)
           {
            value1=value2=bar.High();
           }
         engine.BufferSetDataZigZag(0,i,value1,value2,color_index);
        }
      //--- Проверяем в настройках и рассчитываем буфер заливки
      if(IsUse(BUFFER_STATUS_FILLING))
        {
         //--- Устанавливаем значения границ заливки для бычьих свечей
         double value1=bar.High();
         double value2=bar.Low();
         //--- Если свеча медвежья (индекс цвета = 1) - меняем местами границы заливки для смены цвета
         if(color_index==1)
           {
            value1=bar.Low();
            value2=bar.High();
           }
         engine.BufferSetDataFilling(0,i,value1,value2,color_index);
        }
      //--- Проверяем в настройках и рассчитываем буфер баров
      if(IsUse(BUFFER_STATUS_BARS))
         engine.BufferSetDataBars(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index);
      //--- Проверяем в настройках и рассчитываем буфер свечей
      if(IsUse(BUFFER_STATUS_CANDLES))
         engine.BufferSetDataCandles(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

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



Что дальше

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

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


Прикрепленные файлы |
MQL5.zip (3769.17 KB)
Igor Makanu
Igor Makanu | 17 июн 2020 в 18:27

эта библиотека может уже выполнять хоть какие практические задачи или еще не закончена работа?


хотелось бы увидеть практический пример, например - MACD со старших ТФ в подокне, т.е бросаем индикатор написанный с помощью библиотеки на графики М1... Н1... и видим  в подокне MACD  на D1

Artyom Trishkin
Artyom Trishkin | 17 июн 2020 в 18:34
Igor Makanu:

эта библиотека может уже выполнять хоть какие практические задачи или еще не закончена работа?


хотелось бы увидеть практический пример, например - MACD со старших ТФ в подокне, т.е бросаем индикатор написанный с помощью библиотеки на графики М1... Н1... и видим  в подокне MACD  на D1

Сделано ещё чуть меньше половины запланированного.

Если расчёт MACD будет вынесен в код индикатора, сделанного с помощью библиотеки, то да, получится. Если же нужно стандартный вывести через библиотеку, то... Да тоже, думаю, можно - данные стандартного MACD записывать в индикаторные буферы, созданные на основе библиотеки в OnCalculate(). В примере этой статьи, в буферы индикатора записываются цены. Вот вместо цен - записывать значения MACD. Правда нужно брать MACD с требуемого таймфрейма, что естественно. Дальше, и позже, будут классы для работы со стандартными и пользовательскими индикаторами - там будет уже проще, чем описал сейчас здесь.

Igor Makanu
Igor Makanu | 17 июн 2020 в 18:46
Artyom Trishkin:

Сделано ещё чуть меньше половины запланированного.

Если расчёт MACD будет вынесен в код индикатора, сделанного с помощью библиотеки, то да, получится. Если же нужно стандартный вывести через библиотеку, то... Да тоже, думаю, можно - данные стандартного MACD записывать в индикаторные буферы, созданные на основе библиотеки в OnCalculate(). В примере этой статьи, в буферы индикатора записываются цены. Вот вместо цен - записывать значения MACD. Правда нужно брать MACD с требуемого таймфрейма, что естественно. Дальше, и позже, будут классы для работы со стандартными и пользовательскими индикаторами - там будет уже проще, чем описал сейчас здесь.

ОК, значит нужно подождать

такие задачи - посмотреть индикатор со старшего ТФ (стохастик, MACD или просто МАшку) всегда востребованы на форумах трейдеров, вопрос конечно не как это сделать, а насколько быстро (по скорости написания кода или удобно писать) такие задачи может решать Ваша библиотека

Artyom Trishkin
Artyom Trishkin | 17 июн 2020 в 20:06
Igor Makanu:

ОК, значит нужно подождать

такие задачи - посмотреть индикатор со старшего ТФ (стохастик, MACD или просто МАшку) всегда востребованы на форумах трейдеров, вопрос конечно не как это сделать, а насколько быстро (по скорости написания кода или удобно писать) такие задачи может решать Ваша библиотека

Попробовал выполнить такую задачу без доработки библиотеки. Можно, но нужны лишние телодвижения.

Спасибо за наводку - это как раз недостающий функционал расчётного объекта-буфера. Будет в следующей статье.

Проекты позволяют создавать прибыльных торговых роботов! Но это не точно Проекты позволяют создавать прибыльных торговых роботов! Но это не точно

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

Вычисление математических выражений (Часть 2). Парсеры Пратта и сортировочной станции Вычисление математических выражений (Часть 2). Парсеры Пратта и сортировочной станции

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

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

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

Теория вероятностей и математическая статистика с примерами (Часть I): Основы и элементарная теория Теория вероятностей и математическая статистика с примерами (Часть I): Основы и элементарная теория

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