Как грамотно создать MTF-версию индикатора ATR в MQL5?

 

Столкнулся со следующей проблемой. Не смог получить значение индикатора ATR с более высокого таймфрейма (D1) из другого индикатора, который работает на таймфрейме H1. Так и не решив ее, решил упростить задачу - написать более простой индикатор "ATR MTF", который должен просто отображать значения ATR с дневного ТФ на графике более низкого ТФ, например, часового. Казалось бы, чего проще, ан нет, значения ATR начинают отображаться только с появлением нового бара, а на истории - нет.

Пожалуйста, если у кого-то есть опыт написания мультитаймфреймовых индикаторов, взгляните на код, что в нем не так или чего в нем не хватает?

//+----------------------------------------------------------------------------------+
//| Индикатор «ATR MTF Indicator»
//+----------------------------------------------------------------------------------+
#property copyright   "Copyright © 2016, «Scriptolog»"
#property link        "https://www.mql5.com/ru/users/scriptolog"
#property version         "1.0"  // текущая версия
#define   SHORT_NAME  "ATR MTF"  // короткое имя индикатора

#property description "Индикатор «ATR MTF» ...\n"

//#property icon "\\Images\\Constructor.ico" // путь к файлу заставки
#property strict

#property   indicator_separate_window
#property   indicator_buffers    1
#property   indicator_plots      1

#property   indicator_label1     "ATR MTF"
#property    indicator_type1     DRAW_LINE
#property   indicator_style1     STYLE_SOLID
#property   indicator_color1     clrYellow
#property   indicator_width1     2

//+------------------------------------------------------------------+
//| Параметрическая форма присвоения мнемонических имен выражениям
//+------------------------------------------------------------------+
#define dts(x) (x==EMPTY_VALUE ? "EMPTY_VALUE" : DoubleToString(x, _Digits)) // преобразовать вещественное число в строку


  sinput string               ATR_                 = "ПАРАМЕТРЫ ИНДИКАТОРА «ATR MTF»"; // .
   input ENUM_TIMEFRAMES      ATR_TIMEFRAME        =  PERIOD_CURRENT;         // ATR. Таймфрейм. Период графика
   input int                  ATR_PERIOD           =   20;                    // ATR. Период усреднения  

// indicator buffers
   double      ATR_MTF[]; // INDICATOR_DATA

   int         MAX_PERIOD;

//+------------------------------------------------------------------+
//| Структура для ATR
//+------------------------------------------------------------------+
   struct   struct_atr {
      int         handle;
      double      value;
      bool        is_current_tf;
      string      tf1_name;
      string      tf2_name;
   };
   struct_atr atr;

//+------------------------------------------------------------------+ 
//| Custom indicator initialization function
//+------------------------------------------------------------------+ 
int OnInit() 
{
// Определить используется режим MTF или нет
   atr.is_current_tf=(PeriodSeconds(PERIOD_CURRENT)==PeriodSeconds(ATR_TIMEFRAME));

   atr.tf1_name=PeriodToString(PERIOD_CURRENT); // Период графика (таймфрейм) в текстовом виде
   atr.tf2_name=PeriodToString(ATR_TIMEFRAME);  // Период графика (таймфрейм) в текстовом виде

   MAX_PERIOD=ATR_TIMEFRAME+1;

// установим индексацию для буфера как в таймсерии
   ArraySetAsSeries(ATR_MTF, true); SetIndexBuffer(0, ATR_MTF, INDICATOR_DATA);

   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, MAX_PERIOD); PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, EMPTY_VALUE); 

// set accuracy
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits); IndicatorSetString(INDICATOR_SHORTNAME, SHORT_NAME);

   ResetLastError();

   atr.handle= iATR(_Symbol, ATR_TIMEFRAME, ATR_PERIOD); 
   if (atr.handle==INVALID_HANDLE) {Print("Не удалось создать хэндл индикатора iATR, код ошибки #", GetLastError()); return(INIT_FAILED);}  // работа индикатора завершается досрочно
   Print("+++ Успешно создан хэндл индикатора  iATR #", atr.handle, ", код ошибки #", GetLastError());

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits); 
   IndicatorSetString(INDICATOR_SHORTNAME, SHORT_NAME);

   return(INIT_SUCCEEDED); // нормальное выполнение инициализации индикатора   
} 
//+------------------------------------------------------------------+
//| Функция деинициализации является обработчиком события Deinit
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Comment(""); Print(TimeCurrent(),": " ,__FUNCTION__," - код причины завершения работы индикатора = ", reason);
   IndicatorRelease(atr.handle); // release indicators
   return;
}
//+------------------------------------------------------------------+
//| 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       &real_volume[],
                const int        &spread[])
{
   int N=rates_total-prev_calculated; if (N>MAX_PERIOD) N-=MAX_PERIOD;

   ArraySetAsSeries(time, true);

   for (int i=N; i>0 && !IsStopped(); i--) {
      datetime time0=time[i];
      ATR_MTF[i]=getATR(i, time0);
   }
   ATR_MTF[0]=EMPTY_VALUE;
   return(rates_total);
}
//+------------------------------------------------------------------+ 
//| Получить значение индикатора iATR()
//+------------------------------------------------------------------+ 
double getATR(int i, datetime time0)
{
   double aATR[];
   double nATR=0; ArraySetAsSeries(aATR, true);  

   datetime aTime[];

   ResetLastError(); // сбросим код ошибки 

   int k;

   if (atr.is_current_tf) { // расчет ATR на текущем ТФ

      k=CopyBuffer(atr.handle, 0, i, 1, aATR); // Обращение по начальной позиции и кол-ву требуемых элементов
      if (k>0) 
         nATR=aATR[0];
      else {
         Print("Не удалось скопировать данные из индикатора iATR, бар #", i, ", код ошибки #", GetLastError()); 
   }  }

   else { // расчет ATR с другого ТФ

   // найдем ближайшую дату и время к заданной в параметре time1
      k=CopyTime(_Symbol, ATR_TIMEFRAME, time0, 1, aTime);

      if (k>0) {
         datetime time1=aTime[0];
         k=CopyBuffer(atr.handle, 0, time1, 1, aATR); // Обращение по начальной дате и кол-ву требуемых элементов
         if (k>0) {
            nATR=aATR[0];
            Print("Искомая дата и время ", TimeToString(time0), " на ТФ=", atr.tf1_name,  " принадлежат бару ", TimeToString(time1), " на ТФ=", atr.tf2_name, " ATR=", dts(nATR));
         }
         else {
            Print("Искомая дата и время ", TimeToString(time0), " на ТФ=", atr.tf1_name,  " принадлежат бару ", TimeToString(time1), " на ТФ=", atr.tf2_name, " значение ATR не получено, код ошибки #", GetLastError());
         // Print("Не удалось скопировать aATR[] для ", TimeToString(time1), ", код ошибки #", GetLastError()); 
         }
      }
      else {
         Print("Не удалось скопировать aTime[] с начальной даты ", TimeToString(time0), ", код ошибки #", GetLastError()); 
      }
   }
   return(nATR); 
} 

//+------------------------------------------------------------------+
//| Возвращает текстовое значение таймфрейма текущего графика
//+------------------------------------------------------------------+
string PeriodToString(ENUM_TIMEFRAMES TF1=PERIOD_CURRENT) // Период графика (таймфрейм) в текстовом виде
{
   string s;
   ENUM_TIMEFRAMES TF=(TF1==PERIOD_CURRENT ? Period() : TF1);
   switch (TF) {
      case PERIOD_M1 : s="M1" ; break;
      case PERIOD_M2 : s="M2" ; break;
      case PERIOD_M3 : s="M3" ; break;
      case PERIOD_M4 : s="M4" ; break;
      case PERIOD_M5 : s="M5" ; break;
      case PERIOD_M6 : s="M6" ; break;
      case PERIOD_M10: s="M10"; break;
      case PERIOD_M12: s="M12"; break;
      case PERIOD_M15: s="M15"; break;
      case PERIOD_M20: s="M20"; break;
      case PERIOD_M30: s="M30"; break;
      case PERIOD_H1 : s="H1" ; break;
      case PERIOD_H2 : s="H2" ; break;
      case PERIOD_H3 : s="H3" ; break;
      case PERIOD_H4 : s="H4" ; break;
      case PERIOD_H6 : s="H6" ; break;
      case PERIOD_H8 : s="H8" ; break;
      case PERIOD_H12: s="H12"; break;
      case PERIOD_D1 : s="D1" ; break;
      case PERIOD_W1 : s="W1" ; break;
      case PERIOD_MN1: s="MN1"; break;
      default        : s="XXX";
   }
   return(s);
}
 

Простой универсальный (но не самый эффективный) способ сделать из любого индикатора МТФ:

input ENUM_TIMEFRAMES                   TimeFrame       = PERIOD_CURRENT;       // * TimeFrame

ENUM_TIMEFRAMES                         TF              = PERIOD_CURRENT;
int                                     RedrawBars      = 0;

int OnInit()
{
        if ( PeriodSeconds( TimeFrame ) <= PeriodSeconds() )
        {
                TF = PERIOD_CURRENT;
                RedrawBars = 1;
        }
        else
        {
                TF = TimeFrame;
                RedrawBars = PeriodSeconds( TF )/PeriodSeconds() + 1;
        }

int OnCalculate()
{
        //--- last counted bar will be recounted
        int limit = rates_total - prev_calculated - 1;
        if ( prev_calculated > 0 ) limit += RedrawBars + 1;
        if ( limit >= rates_total ) limit = rates_total - 1;

        //---
        if ( TF != PERIOD_CURRENT )
        {
                for( int i = limit; i >= 0; i-- )
                {
                        int bar = iBarShift( _Symbol, TF, time[i] );
                        if ( iTime( _Symbol, TF, bar ) > time[i] ) bar ++;

                        buffer[i] = iCustom( _Symbol, TF, WindowExpertName(), ... parameters ...., 0, bar );
                }

                return(rates_total);
        }

        //--- Расчет на текущем ТФ
        for( int i = limit; i >= 0; i-- )
        {
                buffer[i] = some value;
 
Andrey Khatimlianskii: Простой универсальный (но не самый эффективный) способ сделать из любого индикатора МТФ:

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

Но, судя по использованным функциям iBarShift() и WindowExpertName(), Вы предлагаете решении для MQL4. Есть ли аналогичное готовое решение для MQL5?

 
https://www.mql5.com/en/job/74882
 

а есть же примеры в кодбазе, вот например, стохастик

https://www.mql5.com/ru/code/177

Stochastic multi-timeframe
Stochastic multi-timeframe
  • голосов: 25
  • 2010.08.25
  • ak20 ak20
  • www.mql5.com
Это индикатор Stochastic с возможностью расчета данных на любом таймфрейме (выше или ниже таймфрейма текущего графика). Можно использовать все обычные параметры встроенного индикатора iStochastic, дополнительным входным параметром будет второй таймфрейм. В случаях, когда в параметрах индикатора указан таймфрейм, меньший, чем текущий таймфрейм...
 
Maxim Dmitrievsky: а есть же примеры в кодбазе, вот например, стохастик  https://www.mql5.com/ru/code/177

Спасибо, посмотрю.

 

Maxim Dmitrievsky: а есть же примеры в кодбазе, вот например, стохастик  https://www.mql5.com/ru/code/177

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

//+------------------------------------------------------------------+
//| 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       &real_volume[],
                const int        &spread[])
{
   ResetLastError(); // сбросим код ошибки 

// Возвращает кол-во баров в истории по соответствующему символу периоду
   int bars_total     =Bars(_Symbol, ATR_TIMEFRAME);

// Возвращает кол-во баров в истории по соответствующему символу периоду (для запрашиваемого индикатора)
   int bars_calculated=BarsCalculated(atr.handle);

   if (bars_calculated<bars_total) return(0);

...


Правда, осталась проблема обработки нулевого бара в режиме online, но это не беда, порешаем...

 
Eugene Myzrov:

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


Правда, осталась проблема обработки нулевого бара в режиме online, но это не беда, порешаем...

У Вас тут if (bars_calculated<bars_total) return(0);   на  каждом новом баре заданного ТФ    будет выполняться это условие, где Вы всегда возвращаете 0, что будет пересчитывать весь период в цикле заново!


P.S. Решение проблемы с нулевым баром вижу так

   for(int i=N; i>=0 && !IsStopped(); i--)
     {
      datetime time0=time[i];
      ATR_MTF[i]=getATR(i, time0);
     }
   //ATR_MTF[0]=EMPTY_VALUE;
Обработчик события "новый бар"
Обработчик события "новый бар"
  • www.mql5.com
Для создателей индикаторов и экспертов всегда был актуален вопрос написания экономичного кода с точки зрения времени выполнения. Можно подойти к решению этой задачи с разных сторон. Из этой обширной темы в данной статье будет затронут, казалось бы уже решенный вопрос: проверка появления нового бара. Это достаточно популярный способ ограничения...
 
Andrey Khatimlianskii #:

Простой универсальный (но не самый эффективный) способ сделать из любого индикатора МТФ:

на закрывающие скобки дефицит?))