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

Artyom Trishkin | 21 августа, 2020

Содержание


Концепция

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

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

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

Хочу сразу отметить, что не все стандартные индикаторы сразу поддались такому достаточно простому преобразованию в мультипериодный режим со смещением линии. Те индикаторы, линии которых по замыслу авторов изначально рисуются со смещением (Gator Oscillator и Ichimoku Kinko Hyo), пока не получается отобразить в мультипериодном режиме. А вот индикатор Alligator легко поддался преобразованию. Думаю, что это связано с тем, что в индикаторе Alligator каждой рисуемой линии задаётся своё смещение, мы их выводим с рассчитанным смещением, и всё отображается корректно, а индикаторы Gator и Ichimoku для своего расчёта используют изначально смещённые линии. В любом случае мы найдём причину и создадим эти индикаторы как мультипериодные и мультисимвольные. А пока — чтобы долго не задерживаться на поиске причин (сделаем это параллельно разработке) — мы будем создавать дальнейший функционал библиотеки. Тем более, что совсем скоро будет интереснее — включение данных стандартных индикаторов в объекты-бары класса таймсерии, что позволит быстро искать любые сочетания любых индикаторов на любом символе/периоде графика.

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

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


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

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

  1. Верхняя линия, она же главная, она же Jaws,
  2. Нижняя линия, она же сигнальная, она же Teeth и +DI,
  3. Средняя линия, она же Lips и -DI

Здесь пока не учтены линии индикатора Ichimoku, но это позже — когда будем и его делать мультипериодным.

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

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

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

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

//+------------------------------------------------------------------+
//| Устанавливает сдвиг графического построения индикатора           |
//+------------------------------------------------------------------+
void CBuffer::SetShift(const int shift)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_SHIFT,shift);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+

Теперь метод выглядит так:

//+------------------------------------------------------------------+
//| Устанавливает сдвиг графического построения индикатора           |
//+------------------------------------------------------------------+
void CBuffer::SetShift(const int shift)
  {
   this.SetProperty(BUFFER_PROP_SHIFT,shift);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+

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

В файле \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh объекта-расчётного буфера нам нужно установить начальную точку (бар), с которого начинается копирование данных из буфера стандартного индикатора. Для этого в методе копирования данных указанного индикатора в массив объекта-буфера нужно просто указать значение начала копирования данных со знаком минус:

//+------------------------------------------------------------------+
//| Копирует данные указанного индикатора в массив объекта-буфера    |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

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

Теперь в файле \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh класса-коллекции объектов-буферов доработаем все методы создания стандартных индикаторов, в которых возможно смещение линий индикатора.

Рассмотрим на примере метода для создания объекта стандартного индикатора Alligator:

//+------------------------------------------------------------------+
//| Создаёт мультисимвольный мультипериодный Alligator               |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAlligator(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=WRONG_VALUE)
  {
//--- Рассчитываем и задаём количество баров смещения линии
   int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(PERIOD_CURRENT);
   int shift_jaw=jaw_shift*num_bars;
   int shift_teeth=teeth_shift*num_bars;
   int shift_lips=lips_shift*num_bars;
//--- Создаём хэндл индикатора и устанавливаем идентификатор по умолчанию
   int handle=::iAlligator(symbol,timeframe,jaw_period,shift_jaw,teeth_period,shift_teeth,lips_period,shift_lips,ma_method,applied_price);
   int identifier=(id==WRONG_VALUE ? IND_ALLIGATOR : id);
   color array_colors[1]={clrBlue};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры линии Jaws
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_jaw);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+")");
      buff.SetColors(array_colors);
      
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры линии Teeth
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_teeth);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(string)teeth_period+")");
      array_colors[0]=clrRed;
      buff.SetColors(array_colors);
      
      //--- Создаём буфер-линию
      this.CreateLine();
      //--- Получаем последний созданный объект-буфер (рисуемый) и устанавливаем ему все необходимые параметры линии Lips
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_lips);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(string)lips_period+")");
      array_colors[0]=clrLime;
      buff.SetColors(array_colors);
      
      //--- Создаём расчётный буфер линии Jaws, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры линии Jaws
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_jaw);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+")");
      
      //--- Создаём расчётный буфер линии Teeth, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры линии Teeth
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_teeth);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(string)teeth_period+")");
      
      //--- Создаём расчётный буфер линии Lips, в котором будут храниться данные стандартного индикатора
      this.CreateCalculate();
      //--- Получаем последний созданный объект-буфер (расчётный) и устанавливаем ему все необходимые параметры линии Lips
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_lips);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(string)lips_period+")");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Разберём что у нас здесь.

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

Описанные выше изменения и дополнения для расчёта смещения индикаторной линии уже прописаны во все методы создания стандартных индикаторов, у которых есть параметр, определяющий количество баров смещения линии:
CreateAlligator()
, CreateAMA(), CreateBands(), CreateDEMA(), CreateEnvelopes(), CreateFrAMA(), CreateMA(), CreateStdDev(), CreateTEMA() и CreateVIDYA().

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

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

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

//--- Очищает данные буфера (1) указанного стандартного индикатора, (2) всех созданных стандартных индикаторов по индексу таймсерии
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
   void                    ClearDataAllBuffersStdInd(int series_index);
//--- Устанавливает значения для текущего графика в буферы указанного стандартного индикатора по индексу таймсерии в соответствии с символом/периодом объекта-буфера

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

//+------------------------------------------------------------------+
//| Очищает данные расчётного буфера всех созданных                  |
//| стандартных индикаторов  по индексу таймсерии                    |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataAllBuffersStdInd(int series_index)
  {
   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;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_CALCULATE || buff.IndicatorType()==WRONG_VALUE)
         continue;
      this.ClearDataBufferStdInd(buff.IndicatorType(),buff.ID(),series_index);
     }
  }
//+------------------------------------------------------------------+

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

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

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

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

В блоке кода для трёх буферов индикатора добавили проверку на обработку линий индикатора Bollinger Bands, так как индексация массивов его буферов отличается от индексации буферов всех остальных трёхбуферных стандартных индикаторов. Пока нет обработки индикаторов Gator Oscillator и Ichimoku Kinko Hyo по причинам, описанным в начале статьи.

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

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

      case IND_ALLIGATOR   :
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Здесь точно так же распределены все стандартные индикаторы по соответствующим блокам кода — одно-, двух- и трёхбуферные стандартные индикаторы. Тут нам уже не важно в какой последовательности очищать данные буферов, поэтому Bollinger Bands и Gator Oscillator уже включены в блоки кода обработки двух- и трёхбуферных индикаторов. Ишимоку пока не обрабатываем вообще никак.

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

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

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

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

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

//--- Очищает данные буфера (1) указанного стандартного индикатора, (2) всех созданных стандартных индикаторов по индексу таймсерии
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
   void                    ClearDataAllBuffersStdInd(int series_index);
//--- Устанавливает значения для текущего графика в буферы указанного стандартного индикатора по индексу таймсерии в соответствии с символом/периодом объекта-буфера
   bool                    SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE);
private:
//--- Подготавливает данные указанного стандартного индикатора для установки значений на текущий график символа
   int                     PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                                  CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                                  const ENUM_INDICATOR ind_type,
                                                  const int series_index,
                                                  const datetime series_time,
                                                  int &index_period,
                                                  int &num_bars,
                                                  double &value00,
                                                  double &value01,
                                                  double &value10,
                                                  double &value11,
                                                  double &value20,
                                                  double &value21);
public:

//--- Возвращает буфер (1) по имени графической серии, (2) по таймфрейму,
//--- (3) по индексу Plot, (4) по индексу объекта в списке-коллекции, (5) последний созданный,
//--- список буферов (6) по идентификатору, (7) по типу стандартного индикатора, (8) по типу и идентификатору

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

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

//+------------------------------------------------------------------+
//| Подготавливает данные указанного стандартного индикатора         |
//| для установки значений на текущий график символа                 |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                               CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                               const ENUM_INDICATOR ind_type,
                                               const int series_index,
                                               const datetime series_time,
                                               int &index_period,
                                               int &num_bars,
                                               double &value00,
                                               double &value01,
                                               double &value10,
                                               double &value11,
                                               double &value20,
                                               double &value21)
  {
     //--- Находим индекс бара на периоде, соответствующий времени начала текущего бара
     index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
     if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
        return WRONG_VALUE;
     
     //--- Получаем значение по этому индексу из буфера индикатора
     if(buffer_calc0!=NULL)
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
     if(buffer_calc1!=NULL)
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
     if(buffer_calc2!=NULL)
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
     
     int series_index_start=series_index;
     //--- Для текущего графика никаких расчётов количества обрабатываемых баров делать не нужно - бар всего один
     if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
       {
        series_index_start=series_index;
        num_bars=1;
       }
     else
       {
        //--- Получаем время бара, в который попадает бар с индексом index_period на периоде и символе расчётного буфера
        datetime time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
        if(time_period==0) return false;
        //--- Получаем соответствующий времени бар текущего графика
        series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
        if(series_index_start==WRONG_VALUE) return WRONG_VALUE;
        //--- Рассчитываем количество баров на текущем графике, которые нужно заполнить данными расчётного буфера
        num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
        if(num_bars==0) num_bars=1;
       }
     //--- Записываем значения для расчёта цвета
     if(buffer_calc0!=NULL)
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
     if(buffer_calc1!=NULL)
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
     if(buffer_calc2!=NULL)
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.GetDataBufferValue(0,series_index_start+num_bars));
   
   return series_index_start;
  }
//+------------------------------------------------------------------+

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

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

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

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

Все повторяющиеся ранее из блока в блок расчёты теперь заменены на вызов метода и проверку возвращаемого из него результата. Индикаторы Gator Oscillator и Ichimoku Kinko Hyo здесь мы так же пока не обрабатываем по озвученным выше причинам.

Тест

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

Во внешние параметры индикатора добавим смещение индикаторной линии:

//--- 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   int                  InpShift          =  0;             // Indicator line shift
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

Нам нужно проверить как работает смещение линий индикаторов. Стандартные индикаторы, у которых возможно выводить линию со смещением, все отображают свои данные в главном окне графика (кроме индикатора Gator Oscillator, отображаемый в подокне, но пока нами не реализованный). Поэтому в обработчике OnInit() впишем блок кода для создания объектов стандартных индикаторов, работающих в главном окне:

//--- indicator buffers mapping
//--- Создаём все необходимые объекты-буферы для построения выбранного стандартного индикатора
   bool success=false;
   switch(InpIndType)
     {
//--- Однобуферные стандартные индикаторы в основном окне
      case IND_AMA         :  success=engine.BufferCreateAMA(InpUsedSymbols,InpPeriod,9,2,30,InpShift,PRICE_CLOSE,1);                  break;
      case IND_DEMA        :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                     break;
      case IND_FRAMA       :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                    break;
      case IND_MA          :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,10,InpShift,MODE_SMA,PRICE_CLOSE,1);              break;
      case IND_SAR         :  success=engine.BufferCreateSAR(InpUsedSymbols,InpPeriod,0.02,0.2,1);                                     break;
      case IND_TEMA        :  success=engine.BufferCreateTEMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                     break;
      case IND_VIDYA       :  success=engine.BufferCreateVIDYA(InpUsedSymbols,InpPeriod,9,12,InpShift,PRICE_CLOSE,1);                  break;
      
//--- Многобуферные стандартные индикаторы в основном окне
      case IND_ALLIGATOR   :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN,1);    break;
      case IND_BANDS       :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,20,InpShift,2.0,PRICE_CLOSE,1);                break;
      case IND_ENVELOPES   :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,14,InpShift,MODE_SMA,PRICE_CLOSE,0.1,1);   break;
      case IND_FRACTALS    :  success=engine.BufferCreateFractals(InpUsedSymbols,InpPeriod,1);                                         break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }

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

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

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

Избавимся от этого блока кода в итоговом индикаторе. Для этого в файле сервисных функций библиотеки \MQL5\Include\DoEasy\Services\DELib.mqh напишем новую функцию, в которую перенесём этот блок кода:

//+------------------------------------------------------------------+
//| Устанавливает разрядность и уровни стандартному индикатору       |
//+------------------------------------------------------------------+
void SetIndicatorLevels(const string symbol,const ENUM_INDICATOR ind_type)
  {
   int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   switch(ind_type)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    break;
      
      case IND_CCI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER    :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_RSI         :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
  }
//+------------------------------------------------------------------+

Теперь в обработчике OnInit() индикатора, в самом его конце, достаточно просто вызвать эту функцию, что делает код более простым и наглядным:

//--- Установим короткое имя индикатора, разрядность данных и уровни
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   SetIndicatorLevels(InpUsedSymbols,InpIndType);

//--- Успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Обработчик OnCalculate() оставим без изменений. Полный код тестового индикатора можно посмотреть в прикреплённых к статье файлах.

Скомпилируем индикатор и запустим его на графике EURUSD H1, предварительно задав в настройках использование символа EURUSD H4, зададим смещение индикаторной линии в 4 бара и выберем индикатор Bollinger Bands. Затем выберем в настройках индикатор Alligator:


Как видим, Bollinger Bands верно отображается с указанным смещением в 4 бара, а Alligator не реагирует на смещение в 4 бара — у него заданы значения по умолчанию сразу при его создании в коде OnInit(), равные этим значениям у стандартного индикатора:

//--- Многобуферные стандартные индикаторы в основном окне
      case IND_ALLIGATOR   :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN,1);    break;

И Alligator тоже правильно отображает свои линии со стандартным смещением его линий.


Что дальше

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

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