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

4 августа 2020, 13:21
Artyom Trishkin
2
919

Содержание


Концепция

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

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


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

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

Добавим индекс нового сообщения:

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

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

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

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


Все доработки сегодня коснутся класса-коллекции индикаторных буферов в файле \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh, и, что естественно — класса CEngine.

В файле BuffersCollection.mqh нам необходимо добавить один метод для подготовки данных расчётного буфера всех созданных стандартных индикаторов — чтобы библиотека могла сама этим заниматься по команде из программы. Это упростит код конечной программы — не нужно будет в ней искать и получать требуемые объекты.

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

//--- Подготавливает данные расчётного буфера (1) указанного стандартного индикатора, (2) всех созданных стандартных индикаторов
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
   bool                    PreparingDataAllBuffersStdInd(void);
//--- Очищает данные буфера указанного стандартного индикатора по индексу таймсерии

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

//+------------------------------------------------------------------+
//| Подготавливает данные расчётного буфера                          |
//| всех созданных стандартных индикаторов                           |
//+------------------------------------------------------------------+
bool CBuffersCollection::PreparingDataAllBuffersStdInd(void)
  {
   CArrayObj *list=this.GetListBuffersWithID();
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
   bool res=true;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
         continue;
      CSeriesDE *series=this.m_timeseries.GetSeries(buff.Symbol(),buff.Timeframe());
      if(series==NULL)
         continue;
      int used_data=(int)series.AvailableUsedData();
      int copied=this.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),used_data);
      if(copied<used_data)
         res &=false;
     }
   return res;
  }
//+------------------------------------------------------------------+

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

Здесь мы в первую очередь получаем список всех объектов-буферов, у которых идентификатор не равен значению -1.
Если список пустой, то выводим сообщение, что нет созданных объектов-буферов для стандартных индикаторов и возвращаем false
.
Далее в цикле по количеству всех объектов-буферов, имеющих идентификатор стандартного индикатора, получаем очередной объект-буфер. Таких объектов-буферов у нас для каждого стандартного индикатора имеется как минимум два — расчётный и рисуемый. Нам нужны только расчётные буферы, но в данном цикле мы их не выбираем по причине, что далее — в методе PreparingDataBufferStdInd(), рассмотренным нами в прошлой статье, и который доработаем сегодня чуть позже, как раз и выбирается только расчётный буфер для дальнейшей с ним работы. Поэтому здесь мы не будем повторять этот выбор.
Теперь нам необходимо определить сколько баров у нас имеется в наличии на том символе/периоде, для которого создан полученный объект-буфер. Это значение нам нужно для определения сколько скопировать данных стандартного индикатора в предназначенный для него объект-буфер. Для этого получаем нужную нам таймсерию по значениям символа и таймфрейма объекта-буфера, и берём из неё количество доступных нам данных.
После вызываем метод PreparingDataBufferStdInd() для копирования необходимого количества данных из хэндла индикатора в расчётный объект-буфер.
В случае, если было скопировано меньше данных, чем требуется, то в значение переменной res добавляем значение false. Если хоть один из имеющихся объектов-буферов не будет скопирован в нужном количестве, то в переменной res у нас будет записано значение false. А из метода как раз и возвращаем значение этой переменной. Метод вернёт true в том случае, если все данные всех расчётных объектов-буферов были успешно скопированы.

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

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

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

Рассмотрим внесённые правки в метод создания буферов для стандартного индикатора Accelerator Oscillator:

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

Так как стандартный индикатор Accelerator Oscillator имеет два цвета для отображения своих значений, то объявим массив цветов размером 3, который сразу же инициализируем тремя цветами. Почему тремя? Ну потому, что цвет с индексом 0 отображает восходящие значения линии индикатора, цвет с индексом 1 — нисходящие значения. Но нам нужен ещё один цвет, который отображает равные значения двух смежных баров линии индикатора. И в стандартном Accelerator Oscillator они отображаются цветом восходящих значений. Поэтому и нужны три цвета.
После создания рисуемого объекта-буфера, устанавливаем цвета его отрисовки из данного массива.

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

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

Например, метод создания стандартного индикатора Average True Range:

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

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

  • Accelerator Oscillator
  • Accumulation/Distribution
  • Awesome Oscillator
  • Average True Range
  • Bears Power
  • Bulls Power
  • Chaikin Oscillator
  • Commodity Channel Index
  • DeMarker
  • Force Index
  • Momentum
  • Money Flow Index
  • Moving Average of Oscillator
  • On Balance Volume
  • Relative Strength Index
  • Standart Deviation
  • Triple Exponential Average
  • William's Percent Range
  • Volumes

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

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

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

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

И, что самое интересное — для других однобуферных индикаторов нам необходимо произвести точно такие же действия. И вот тут нам приходит на помощь сам оператор switch — в нём сравниваются значения выражения с константами во всех вариантах case и передаётся управление оператору, который соответствует значению выражения. Каждый кейс завершается либо оператором завершения break, либо оператором возврата return. И если не поставить внутри кейса ни один из этих завершающих операторов, то после обработки значения выражения в нужном кейсе, управление перейдёт к следующему.
Поэтому нам, вместо клонирования однотипных нужных операций во все кейсы, в которых должны проводиться точно такие же операции, достаточно объединить все такие кейсы в один без оператора возврата или завершения:

//+------------------------------------------------------------------+
//| Подготавливает данные расчётного буфера                          |
//| указанного стандартного индикатора                               |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      //--- Однобуферные стандартные индикаторы в подокне
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
 
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

И точно так же поступим в методе очистки данных буфера указанного стандартного индикатора:

//+------------------------------------------------------------------+
//| Очищает данные буфера указанного стандартного индикатора         |
//| по индексу таймсерии                                             |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
//--- Получаем список объектов-буферов по типу и идентификатору
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   if(list==NULL || list.Total()==0)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      //--- Однобуферные стандартные индикаторы в подокне
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

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

//--- В зависимости от типа стандартного индикатора
   switch((int)ind_type)
     {
      //--- Однобуферные стандартные индикаторы
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

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

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

Теперь нам необходимо дополнить класс основного объекта библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh.

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

В публичной секции класса пропишем вызов всех этих методов:
//--- Методы создания стандартных индикаторов и объектов-буферов для них
//--- Создаёт стандартный индикатор Accelerator Oscillator
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);                    }
//--- Создаёт стандартный индикатор Accumulation/Distribution
   bool                 BufferCreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateAD(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);     }
//--- Создаёт стандартный индикатор ADX
   bool                 BufferCreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADX(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);        }
//--- Создаёт стандартный индикатор ADX Wilder
   bool                 BufferCreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADXWilder(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Alligator
   bool                 BufferCreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                              const int jaw_period,const int jaw_shift,
                                              const int teeth_period,const int teeth_shift,
                                              const int lips_period,const int lips_shift,
                                              const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE applied_price,const int id)
                          {
                           return(this.m_buffers.CreateAlligator(symbol,timeframe,
                                                                 jaw_period,jaw_shift,
                                                                 teeth_period,teeth_shift,
                                                                 lips_period,lips_shift,
                                                                 ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Создаёт стандартный индикатор Adaprive Moving Average
   bool                 BufferCreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                        const int ama_period,
                                        const int fast_ema_period,
                                        const int slow_ema_period,
                                        const int ama_shift,
                                        const ENUM_APPLIED_PRICE applied_price,
                                        const int id)
                          { 
                           return(this.m_buffers.CreateAMA(symbol,timeframe,
                                                           ama_period,
                                                           fast_ema_period,slow_ema_period,
                                                           ama_shift,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Создаёт стандартный индикатор Awesome Oscillator
   bool                 BufferCreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAO(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Average True Range
   bool                 BufferCreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateATR(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Bollinger Bands
   bool                 BufferCreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                          const int bands_period,
                                          const int bands_shift,
                                          const double deviation,
                                          const ENUM_APPLIED_PRICE applied_price,
                                          const int id)
                          {
                           return(this.m_buffers.CreateBands(symbol,timeframe,
                                                             bands_period,bands_shift,
                                                             deviation,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Создаёт стандартный индикатор Bears Power
   bool                 BufferCreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBearsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Bulls Power
   bool                 BufferCreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBullsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Chaikin Oscillator
   bool                 BufferCreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          {
                           return(this.m_buffers.CreateChaikin(symbol,timeframe,
                                                                fast_ma_period,slow_ma_period,
                                                                ma_method,applied_volume,id)!=INVALID_HANDLE);
                          }
//--- Создаёт стандартный индикатор Commodity Channel Index
   bool                 BufferCreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateCCI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор DEMA
   bool                 BufferCreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateDEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор DeMarker
   bool                 BufferCreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateDeMarker(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Envelopes
   bool                 BufferCreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id)
                          {
                           return(this.m_buffers.CreateEnvelopes(symbol,timeframe,
                                                                  ma_period,ma_shift,ma_method,
                                                                  applied_price,deviation,id)!=INVALID_HANDLE);
                          }
//--- Создаёт стандартный индикатор Force Index
   bool                 BufferCreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateForce(symbol,timeframe,ma_period,ma_method,applied_volume,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Fractals
   bool                 BufferCreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateFractals(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор FrAMA
   bool                 BufferCreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Gator
   bool                 BufferCreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          {
                           return(this.m_buffers.CreateGator(symbol,timeframe,
                                                              jaw_period,jaw_shift,
                                                              teeth_period,teeth_shift,
                                                              lips_period,lips_shift,
                                                              ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Создаёт стандартный индикатор Ichimoku
   bool                 BufferCreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id)
                          { return(this.m_buffers.CreateIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор BW MFI
   bool                 BufferCreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateBWMFI(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Momentum
   bool                 BufferCreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMomentum(symbol,timeframe,mom_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Money Flow Index
   bool                 BufferCreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateMFI(symbol,timeframe,ma_period,applied_volume,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Moving Average
   bool                 BufferCreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Moving Average of Oscillator
   bool                 BufferCreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор MACD
   bool                 BufferCreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор On Balance Volume
   bool                 BufferCreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateOBV(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Parabolic SAR
   bool                 BufferCreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id)
                          { return(this.m_buffers.CreateSAR(symbol,timeframe,step,maximum,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Relative Strength Index
   bool                 BufferCreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateRSI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор RVI
   bool                 BufferCreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateRVI(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Standart Deviation
   bool                 BufferCreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Stochastic
   bool                 BufferCreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id)
                          { return(this.m_buffers.CreateStochastic(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор TEMA
   bool                 BufferCreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Triple Exponential Average
   bool                 BufferCreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTriX(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор William's Percent Range
   bool                 BufferCreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id)
                          { return(this.m_buffers.CreateWPR(symbol,timeframe,calc_period,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор VIDYA
   bool                 BufferCreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateVIDYA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Создаёт стандартный индикатор Volumes
   bool                 BufferCreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateVolumes(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
                          
//--- Инициализирует все рисуемые буферы (1) указанным, (2) установленным объекту-буферу пустым значением

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

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

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

//--- Возвращает описание буфера стандартного индикатора по типу и идентификатору
   string               BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id);

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

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

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

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

В реализации метода OnDoEasyEvent() в блоке обработки события "Новый бар" немного подправим получение количества копируемых данных:

//--- Обработка событий таймсерий
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- Событие "Новый бар"
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin((int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated()));
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- Событие "Пропущены бары"
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
     }
     
//--- Обработка торговых событий

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

int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());

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

Это все доработки классов библиотеки на сегодня.

Тест

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

  1. Используемый символ, на котором будет создан стандартный индикатор
  2. Используемый период графика (таймфрейм), на котором будет создан стандартный индикатор
  3. Тип создаваемого  однобуферного мультисимвольного мультипериодного стандартного индикатора

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

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

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

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

//--- classes

//--- enums

//--- defines

//--- structures

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

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

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

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

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

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

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

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы для построения выбранного стандартного индикатора
   bool success=false;
   switch(InpIndType)
     {
      case IND_AC       :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_AD       :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                    break;
      case IND_AO       :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_ATR      :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_BEARS    :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_BULLS    :  success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_CHAIKIN  :  success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,3,10,MODE_EMA,VOLUME_TICK,1); break;
      case IND_CCI      :  success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,14,PRICE_TYPICAL,1);              break;
      case IND_DEMARKER :  success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,14,1);                       break;
      case IND_FORCE    :  success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,13,MODE_SMA,VOLUME_TICK,1);     break;
      case IND_MOMENTUM :  success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);           break;
      case IND_MFI      :  success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,14,VOLUME_TICK,1);                break;
      case IND_OSMA     :  success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);          break;
      case IND_OBV      :  success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                   break;
      case IND_RSI      :  success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                break;
      case IND_STDDEV   :  success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,20,0,MODE_SMA,PRICE_CLOSE,1);  break;
      case IND_TRIX     :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);               break;
      case IND_WPR      :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_VOLUMES  :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);               break;
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Проверяем количество буферов, указанных в блоке properties

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

//--- Выведем краткие описания созданных индикаторных буферов
   engine.BuffersPrintShort();
//--- Установим уровни где они требуются и определим разрядность данных
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AC       : digits+=2;   break;
      case IND_AD       : digits=0;    break;
      case IND_AO       : digits+=1;   break;
      case IND_ATR      :              break;
      case IND_BEARS    : digits+=1;   break;
      case IND_BULLS    : digits+=1;   break;
      case IND_CHAIKIN  : digits=0;    break;
      case IND_CCI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_FORCE    : digits+=1;   break;
      case IND_MOMENTUM : digits=2;    break;
      case IND_MFI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_OSMA     : digits+=2;   break;
      case IND_OBV      : digits=0;    break;
      case IND_RSI      :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STDDEV   : digits+=1;   break;
      case IND_TRIX     :              break;
      case IND_WPR      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
      case IND_VOLUMES  : digits=0;    break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Установим короткое имя индикатора и разрядность данных
   string label=engine.BufferGetLabelByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//--- Успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| 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);   // Работа в таймере библиотеки
      engine.EventsHandling();      // Работа с событиями библиотеки
     }
//+------------------------------------------------------------------+
//| OnCalculate Блок кода для работы с индикатором:                  |
//+------------------------------------------------------------------+
//--- Проверка и расчёт количества просчитываемых баров
//--- Если limit = 0, значит новых баров нет - рассчитываем текущий
//--- Если limit = 1, значит появился новый бар - рассчитываем первый и текущий
//--- Если limit > 1, значит первый запуск, или есть изменения в истории - полный перерасчёт всех данных
   int limit=rates_total-prev_calculated;
   
//--- Перерасчёт всей истории
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
      engine.BufferPreparingDataAllBuffersStdInd();
     }
   
//--- Подготовка данных 
//--- Заполняем данными расчётные буферы всех созданных стандартных индикаторов
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   if(!engine.BufferPreparingDataAllBuffersStdInd())
      return 0;

//--- Расчёт индикатора
//--- Основной цикл расчёта индикатора
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(InpIndType,1,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

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

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


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

Что дальше

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

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

К содержанию

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

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

Добрый день.

Какие у вас дальнейшие планы? Будут ли классы для MM? Стандартные трейлинг стопы было бы тоже интересно (по индикаторам и пр). Или вот, например, стопы и, трейлинг стоп в частности, на определенный объем % от позиции. Несколько стопов сразу, это возможно? 

Artyom Trishkin
Artyom Trishkin | 10 авг 2020 в 16:16
leonerd:

Добрый день.

Какие у вас дальнейшие планы? Будут ли классы для MM? Стандартные трейлинг стопы было бы тоже интересно (по индикаторам и пр). Или вот, например, стопы и, трейлинг стоп в частности, на определенный объем % от позиции. Несколько стопов сразу, это возможно? 

Здравствуйте. Такое есть в планах. Но не сразу и не быстро. Ещё нет всех нужных данных в библиотеке.
Пользовательские символы: основы применения на практике Пользовательские символы: основы применения на практике

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

Нейросети — это просто (Часть 2): Обучение и тестирование сети Нейросети — это просто (Часть 2): Обучение и тестирование сети

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

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

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

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

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