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

26 июня 2020, 09:09
Artyom Trishkin
0
777

Содержание


Концепция

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

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

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

Доработка классов объектов-буферов для работы с любыми символами

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

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

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

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

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

//+------------------------------------------------------------------+
//| Класс абстрактного индикаторного буфера                          |
//+------------------------------------------------------------------+
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;                                        // Вспомогательный флаг переключения состояния буфера
   uchar             m_total_arrays;                                             // Общее количество массивов-буферов

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

//--- Конструктор по умолчанию
                     CBuffer(void){;}
protected:
//--- Защищённый параметрический конструктор
                     CBuffer(ENUM_BUFFER_STATUS status_buffer,
                             ENUM_BUFFER_TYPE buffer_type,
                             const uint index_plot,
                             const uint index_base_array,
                             const int num_datas,
                             const uchar total_arrays,
                             const int width,
                             const string label);
public:  

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

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

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


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

Для буфера стрелок (\MQL5\Include\DoEasy\Objects\Indicators\BufferArrow.mqh):

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

public:
//--- Конструктор
                     CBufferArrow(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_ARROW,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Arrows") {}

Для буфера линий (\MQL5\Include\DoEasy\Objects\Indicators\BufferLine.mqh):

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

public:
//--- Конструктор
                     CBufferLine(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_LINE,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Line") {}

Для буфера отрезков (\MQL5\Include\DoEasy\Objects\Indicators\BufferSection.mqh):

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

public:
//--- Конструктор
                     CBufferSection(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_SECTION,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,1,"Section") {}

Для буфера гистограммы от нулевой линии (\MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram.mqh):

//+------------------------------------------------------------------+
//| Буфер со стилем рисования "Гистограмма от нулевой линии"         |
//+------------------------------------------------------------------+
class CBufferHistogram : public CBuffer
  {
private:

public:
//--- Конструктор
                     CBufferHistogram(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_HISTOGRAM,BUFFER_TYPE_DATA,index_plot,index_base_array,1,2,2,"Histogram") {}

Для буфера гистограммы на двух индикаторных буферах (\MQL5\Include\DoEasy\Objects\Indicators\BufferHistogram2.mqh):

//+--------------------------------------------------------------------+
//|Буфер со стилем рисования "Гистограмма на двух индикаторных буферах"|
//+--------------------------------------------------------------------+
class CBufferHistogram2 : public CBuffer
  {
private:

public:
//--- Конструктор
                     CBufferHistogram2(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_HISTOGRAM2,BUFFER_TYPE_DATA,index_plot,index_base_array,2,3,8,"Histogram2 0;Histogram2 1") {}

Для буфера зигзага (\MQL5\Include\DoEasy\Objects\Indicators\BufferZigZag.mqh):

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

public:
//--- Конструктор
                     CBufferZigZag(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_ZIGZAG,BUFFER_TYPE_DATA,index_plot,index_base_array,2,3,1,"ZigZag 0;ZigZag 1") {}

Для буфера заливки (\MQL5\Include\DoEasy\Objects\Indicators\BufferFilling.mqh):

//+------------------------------------------------------------------+
//|Буфер со стилем рисования "Цветовая заливка между двумя уровнями" |
//+------------------------------------------------------------------+
class CBufferFilling : public CBuffer
  {
private:

public:
//--- Конструктор
                     CBufferFilling(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_FILLING,BUFFER_TYPE_DATA,index_plot,index_base_array,2,2,1,"Filling 0;Filling 1") {}

Для буфера отрисовки в виде баров (\MQL5\Include\DoEasy\Objects\Indicators\BufferBars.mqh):

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

public:
//--- Конструктор
                     CBufferBars(const uint index_plot,const uint index_base_array) :
                        CBuffer(BUFFER_STATUS_BARS,BUFFER_TYPE_DATA,index_plot,index_base_array,4,5,2,"Bar Open;Bar High;Bar Low;Bar Close") {}

Для буфера отрисовки в виде японских свечей (\MQL5\Include\DoEasy\Objects\Indicators\BufferCandles.mqh):

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

public:
//--- Конструктор
                     CBufferCandles(const uint index_plot,const uint index_base_array) : 
                        CBuffer(BUFFER_STATUS_CANDLES,BUFFER_TYPE_DATA,index_plot,index_base_array,4,5,1,"Candle Open;Candle High;Candle Low;Candle Close") {}

Для расчётного буфера (\MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.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,1,0,"Calculate") {}

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

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

//+------------------------------------------------------------------+
//| Расчётный буфер                                                  |
//+------------------------------------------------------------------+
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,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);  }
   
//--- Копирует данные указанного индикатора в массив объекта-буфера
   int               FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count);
   int               FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const int count);
   int               FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const datetime stop_time);
   
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Копирует данные указанного индикатора в массив объекта-буфера    |
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,start_pos,count,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const int count)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,start_time,count,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const datetime start_time,const datetime stop_time)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,start_time,stop_time,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Получает данные нужных таймсерий и баров                         |
//| для работы с одним баром буфера                                  |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

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

В итоге все исправления сводятся ко всего двум корректировкам в двух строках кода.

Полный листинг исправленного метода:

//+------------------------------------------------------------------+
//| Получает данные нужных таймсерий и баров                         |
//| для работы с одним баром буфера                                  |
//+------------------------------------------------------------------+
int CBuffersCollection::GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period)
  {
//--- Получаем таймсерии текущего графика и графика таймфрейма буфера
   CSeriesDE *series_current=this.m_timeseries.GetSeries(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(),buffer.Symbol(),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);
  }
//+------------------------------------------------------------------+

Всё. Теперь наши объекты-буферы могут работать и в мультисимвольном режиме.

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

Самое подходящее место для такого метода — класс-коллекция таймсерий \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.
Объявим в нём новый метод:

//--- Возвращает объект-бар указанной таймсерии указанного символа указанной позиции (1) по индексу, (2) по времени
//--- объект-бар первой таймсерии, соответствующий времени открытия бара на второй таймсерии (3) по индексу, (4) по времени
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true);
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);

//--- Возвращает индекс бара на графике указанного таймфрейма по индексу бара текущего графика                                 |
   int                     IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe);

//--- Возвращает флаг открытия нового бара указанной таймсерии указанного символа
   bool                    IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0);

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

//+------------------------------------------------------------------+
//| Возвращает индекс бара на графике указанного таймфрейма          |
//| по индексу бара текущего графика                                 |
//+------------------------------------------------------------------+
int CTimeSeriesCollection::IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CSeriesDE *series=this.GetSeries(::Symbol(),(ENUM_TIMEFRAMES)::Period());
   if(series==NULL)
      return WRONG_VALUE;
   CBar *bar=series.GetBar(series_index);
   if(bar==NULL)
      return WRONG_VALUE;
   return ::iBarShift(symbol,timeframe,bar.Time());
  }
//+------------------------------------------------------------------+

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

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

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

Для доступа к этому методу из своих программ, нам нужно дать к нему доступ из класса основного объекта библиотеки CEngine
(\MQL5\Include\DoEasy\Engine.mqh):

//--- Очищает данные по индексу таймсерии буферу (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);   }

//--- Возвращает индекс бара на графике указанного таймфрейма по индексу бара текущего графика
   int                  IndexBarPeriodByBarCurrent(const int series_index,const string symbol,const ENUM_TIMEFRAMES timeframe)
                          { return this.m_time_series.IndexBarPeriodByBarCurrent(series_index,symbol,timeframe);  }

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

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

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

Тест: мультипериодная мультисимвольная скользящая средняя

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

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

Зададим для индикатора вывод данных индикатора в подокно графика, впишем ввод значений символа и периода графика для индикатора, входные параметры для скользящей средней и зададим глобальные переменные для корректировки введённых параметров МА:

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

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
//---
sinput   uint                 InpPeriodMA       =  14;            // MA Period
sinput   int                  InpShiftMA        =  0;             // MA Shift
sinput   ENUM_MA_METHOD       InpMethodMA       =  MODE_SMA;      // MA Method
sinput   ENUM_APPLIED_PRICE   InpPriceMA        =  PRICE_CLOSE;   // MA Applied Price
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds

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

В обработчике OnInit() создадим три объекта-буфера — один для линии МА, второй — для свечей выбранного символа, и третий — расчётный, для хранения данных скользящей средней, полученных с выбранного символа/периода.
Далее установим для буфера свечей описания всех четырёх массивов-буферов для окна данных, и то же самое — для буфера линии скользящей средней. По завершении создадим хэндл индикатора Moving Average:

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

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

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы
   engine.BufferCreateLine();          // 2 массива
   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);
    
//--- Устанавливаем период МА
   period_ma=int(InpPeriodMA<2 ? 2 : InpPeriodMA);

//--- В цикле по списку объектов-буферов коллекции
   for(int i=0;i<engine.GetListBuffers().Total();i++)
     { 
      //--- получаем очередной буфер
      CBuffer *buff=engine.GetListBuffers().At(i);
      if(buff==NULL)
         continue;
      //--- и устанавливаем для него отображение в окне данных в зависимости от настройки его использования,
      //--- выбранный в настройках период графика и символ
      buff.SetShowData(true);
      buff.SetTimeframe(InpPeriod);
      buff.SetSymbol(InpUsedSymbols);
      if(buff.Status()==BUFFER_STATUS_CANDLES)
        {
         string pr=InpUsedSymbols+" "+TimeframeDescription(InpPeriod)+" ";
         string label=pr+"Open;"+pr+"High;"+pr+"Low;"+pr+"Close";
         buff.SetLabel(label);
        }
      if(buff.Status()==BUFFER_STATUS_LINE)
        {
         string label="MA("+(string)period_ma+")";
         buff.SetLabel(label);
        }
     }
     
//--- Выведем краткие описания созданных индикаторных буферов
   engine.BuffersPrintShort();

//--- Создаём хендл МА
   handle_ma=iMA(InpUsedSymbols,InpPeriod,period_ma,InpShiftMA,InpMethodMA,InpPriceMA);
   if(handle_ma==INVALID_HANDLE)
      return INIT_FAILED;
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

Чтобы не копировать весь массив данных МА на каждом тике, нам нужно исходить из того, что переменная limit рассчитывается таким образом, что на первом запуске или при изменении исторических данных, она имеет значение больше 1, на открытии нового бара она имеет значение 1, а в остальное время, на каждом тике — имеет значение ноль.
Мы не можем копировать данные, опираясь на значение limit — ноль баров скопировать нельзя. Значит нам нужно копировать один бар при значении limit равным нулю, а в остальных случаях — копируем столько, сколько указано в limit. Таким образом мы организуем экономное копирование актуальных данных скользящей средней в наш расчётный буфер:

//--- Подготовка данных
   CBufferCalculate *buff_calc=engine.GetBufferCalculate(0);
   int total_copy=(limit<2 ? 1 : limit);
   int copied=buff_calc.FillAsSeries(handle_ma,0,0,total_copy);
   if(copied<total_copy)
      return 0;
        

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

//--- Основной цикл расчёта индикатора
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- Очищаем текущий бар всех созданных буферов
      engine.BufferLineClear(0,0);
      engine.BufferCandlesClear(0,0);
      
      //--- Получаем бар таймсерии, соответствующий времени индекса цикла на периоде графика, заданного в настройках
      bar=engine.SeriesGetBar(InpUsedSymbols,InpPeriod,time[i]);
      if(bar==NULL)
         continue;
      //--- Рассчитываем индекс цвета в зависимости от направления свечи с заданного в настройках таймфрейма
      color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2);
      //--- Рассчитываем буфер линии скользящей средней
      int index=engine.IndexBarPeriodByBarCurrent(i,InpUsedSymbols,InpPeriod);
      if(index<0)
         continue;
      engine.BufferSetDataLine(0,i,buff_calc.GetData(index),color_index);
      //--- Рассчитываем буфер свечей
      engine.BufferSetDataCandles(0,i,bar.Open(),bar.High(),bar.Low(),bar.Close(),color_index);
     }
//--- return value of prev_calculated for next call

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

Запустим индикатор на EURUSD M15, задав в настройках символ GBPUSD M30 и простую скользящую среднюю по ценам Close с периодом 14 и смещением 0:


Для сравнения был открыт график GBPUSD с индикатором Moving Average с теми же параметрами.


Тест: мультипериодный мультисимвольный MACD

Теперь создадим мультисимвольный мультипериодный MACD. Сохраним только что созданный индикатор под новым именем TestDoEasyPart46_2.mq5.

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

Я постарался подробно расписать все действия и логику в комментариях к коду, поэтому здесь посмотрим только основные изменения в сравнении с прошлым индикатором:

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

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
//---
sinput   uint                 InpPeriodFastEMA  =  12;            // MACD Fast EMA Period
sinput   uint                 InpPeriodSlowEMA  =  26;            // MACD Slow EMA Period
sinput   uint                 InpPeriodSignalMA =  9;             // MACD Signal MA Period
sinput   ENUM_APPLIED_PRICE   InpPriceMACD      =  PRICE_CLOSE;   // MA Applied Price
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers
CArrayObj     *list_buffers;                                      // Указатель на список объектов-буферов
//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // Список используемых таймфреймов
CEngine              engine;                                      // Главный объект библиотеки CEngine
string               prefix;                                      // Префикс имён графических объектов
int                  min_bars;                                    // Минимальное количество баров для расчёта индикатора
int                  used_symbols_mode;                           // Режим работы с символами
string               array_used_symbols[];                        // Массив для передачи в библиотеку используемых символов
string               array_used_periods[];                        // Массив для передачи в библиотеку используемых таймфреймов
int                  handle_macd;                                 // Хендл МАCD
int                  fast_ema_period;                             // Период расчёта быстрой EMA
int                  slow_ema_period;                             // Период расчёта медленной EMA
int                  signal_period;                               // Период расчёта сигнальной линии MACD
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Запишем в переменную InpUsedTFs наименование выбранного в настройках рабочего таймфрейма
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();
   IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+1);
//--- Установка глобальных переменных индикатора
   prefix=engine.Name()+"_";
   //--- Рассчитываем количество баров текущего периода, умещающихся в максимальном используемом периоде
   //--- Используем полученное значение если оно больше 2, иначе используем 2
   int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>2 ? num_bars : 2);

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

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы для построения MACD
   engine.BufferCreateHistogram();     // 2 массива
   engine.BufferCreateLine();          // 2 массива
   engine.BufferCreateCalculate();     // 1 массив для данных гистограммы MACD с заданного символа/периода
   engine.BufferCreateCalculate();     // 1 массив для данных сигнальной линии MACD с заданного символа/периода

//--- Проверяем количество буферов, указанных в блоке 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);

//--- Устанавливаем периоды расчёта МАCD
   fast_ema_period=int(InpPeriodFastEMA<1 ? 1 : InpPeriodFastEMA);
   slow_ema_period=int(InpPeriodSlowEMA<1 ? 1 : InpPeriodSlowEMA);
   signal_period=int(InpPeriodSignalMA<1  ? 1 : InpPeriodSignalMA);

//--- Получаем буфер гистограммы (1-й по счёту рисуемый буфер)
//--- Он имеет индекс 0 -  с учётом начала отсчёта от нуля
   CBufferHistogram *buff_hist=engine.GetBufferHistogram(0);
   if(buff_hist!=NULL)
     {
      //--- Задаём толщину линии для гистограммы
      buff_hist.SetWidth(3);
      //--- Устанавливаем описание графической серии для гистограммы
      string label="MACD ("+(string)fast_ema_period+","+(string)slow_ema_period+","+(string)signal_period+")";
      buff_hist.SetLabel(label);
      //--- и устанавливаем для буфера отображение в окне данных и
      //--- выбранный в настройках период графика и символ
      buff_hist.SetShowData(true);
      buff_hist.SetTimeframe(InpPeriod);
      buff_hist.SetSymbol(InpUsedSymbols);
     }
//--- Получаем буфер сигнальной линии (1-й по счёту рисуемый буфер)
//--- Он имеет индекс 0 -  с учётом начала отсчёта от нуля
   CBufferLine *buff_line=engine.GetBufferLine(0);
   if(buff_line!=NULL)
     {
      //--- Задаём толщину сигнальной линии
      buff_line.SetWidth(1);
      //--- Устанавливаем описание графической серии для сигнальной линии
      string label="Signal";
      buff_line.SetLabel(label);
      //--- и устанавливаем для буфера отображение в окне данных и
      //--- выбранный в настройках период графика и символ
      buff_line.SetShowData(true);
      buff_line.SetTimeframe(InpPeriod);
      buff_line.SetSymbol(InpUsedSymbols);
     }
    
//--- Получаем первый расчётный буфер
//--- Он имеет индекс 0 -  с учётом начала отсчёта от нуля
   CBufferCalculate *buff_calc=engine.GetBufferCalculate(0);
   if(buff_calc!=NULL)
     {
      //--- Устанавливаем описание первому расчётному буферу как "временный массив гистограммы MACD""
      buff_calc.SetLabel("MACD_HIST_TMP");
      //--- и устанавливаем для него выбранный в настройках период графика и символ
      buff_calc.SetTimeframe(InpPeriod);
      buff_calc.SetSymbol(InpUsedSymbols);
     }
//--- Получаем второй расчётный буфер
//--- Он имеет индекс 1 -  с учётом начала отсчёта от нуля
   buff_calc=engine.GetBufferCalculate(1);
   if(buff_calc!=NULL)
     {
      //--- Устанавливаем описание второму расчётному буферу как "временный массив сигнальной линии MACD""
      buff_calc.SetLabel("MACD_SIGN_TMP");
      //--- и устанавливаем для него выбранный в настройках период графика и символ
      buff_calc.SetTimeframe(InpPeriod);
      buff_calc.SetSymbol(InpUsedSymbols);
     }
    
//--- Выведем краткие описания созданных индикаторных буферов
   engine.BuffersPrintShort();

//--- Создаём хендл МАCD
   handle_macd=iMACD(InpUsedSymbols,InpPeriod,fast_ema_period,slow_ema_period,signal_period,InpPriceMACD);
   if(handle_macd==INVALID_HANDLE)
      return INIT_FAILED;
//---
   IndicatorSetString(INDICATOR_SHORTNAME,InpUsedSymbols+" "+TimeframeDescription(InpPeriod)+" MACD("+(string)fast_ema_period+","+(string)slow_ema_period+","+(string)signal_period+")");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаление графических объектов индикатора по префиксу имени объектов
   ObjectsDeleteAll(0,prefix);
   Comment("");
  }
//+------------------------------------------------------------------+
//| 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();
     }

//--- Подготовка данных 
   int total_copy=(limit<2 ? 1 : limit);
//--- Получаем указатель на первый расчётный буфер по его номеру
   CBufferCalculate *buff_calc_hist=engine.GetBufferCalculate(0);
//--- Заполняем первый расчётный буфер данными гистограммы MACD
   int copied=buff_calc_hist.FillAsSeries(handle_macd,0,0,total_copy);
   if(copied<total_copy)
      return 0;
//--- Получаем указатель на второй расчётный буфер по его номеру
   CBufferCalculate *buff_calc_sig=engine.GetBufferCalculate(1);
//--- Заполняем второй расчётный буфер данными сигнальной линии MACD
   copied=buff_calc_sig.FillAsSeries(handle_macd,1,0,total_copy);
   if(copied<total_copy)
      return 0;
        
//--- Расчёт индикатора
   CBar *bar=NULL;         // Объект-бар для определения направления свечи
   uchar color_index=0;    // Индекс цвета для установки буферу в зависимости от направления свечи

//--- Основной цикл расчёта индикатора
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      //--- Очищаем текущий бар всех созданных буферов
      engine.BufferHistogramClear(0,0);
      engine.BufferLineClear(0,0);
      
      //--- Получаем бар таймсерии, соответствующий времени индекса цикла на периоде графика, заданного в настройках
      bar=engine.SeriesGetBar(InpUsedSymbols,InpPeriod,time[i]);
      if(bar==NULL)
         continue;
      //--- Рассчитываем индекс цвета в зависимости от направления свечи с заданного в настройках таймфрейма
      color_index=(bar.TypeBody()==BAR_BODY_TYPE_BULLISH ? 0 : bar.TypeBody()==BAR_BODY_TYPE_BEARISH ? 1 : 2);
      //--- Рассчитываем буфер гистограммы MACD
      int index=engine.IndexBarPeriodByBarCurrent(i,InpUsedSymbols,InpPeriod);
      if(index<0)
         continue;
      engine.BufferSetDataHistogram(0,i,buff_calc_hist.GetData(index),color_index);
      //--- Рассчитываем буфер сигнальной линии MACD
      engine.BufferSetDataLine(0,i,buff_calc_sig.GetData(index),color_index);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Цвета столбцов гистограммы и сигнальной линии на каждом баре соответствуют направлению свечи символа/периода, по которым рассчитан MACD:


Индикатор запущен на EURUSD M15 с настройками MACD по умолчанию, рассчитываемого на GBPUSD M30. Для наглядности открыт график GBPUSD M30 с запущенным на нём стандартный MACD с теми же параметрами.

Что дальше

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

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

К содержанию

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

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

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

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

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

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

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

Автоматическое создание документации к программам на MQL5 Автоматическое создание документации к программам на MQL5

Большинство Java программистов знакомы с автоматическим созданием документации, которая может быть создана при помощи программы JavaDocs. В мире C++ также есть несколько автоматических генераторов документации, одними из лидеров являются программы Microsoft's SandCastle и Doxygen. В статье описано, как можно использовать программу Doxygen для создания структурированных файлов справки HTML для программ, написанных на MQL5. Результаты данной работы убедили меня использовать Doxygen (или похожие программы) в будущем для создания документации к любому моему коду на MQL5, это значительно облегчает его понимание и использование.