Поддержка множества символов и таймфреймов

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

Реализуем мультитаймфреймовый индикатор WPR (см. файл UseWPRMTF.mq5), которому также можно назначить расчет на произвольном символе (отличном от графика).

Станем выводить значения WPR заданного периода для всех стандартных таймфреймов из перечисления ENUM_TIMEFRAMES. Количество таймфреймов равно 21, поэтому индикатор будет всегда отображаться на последних 21 барах. Самый правый нулевой бар будет содержать WPR для M1, следующий — WPR для M2, и так далее вплоть до 20-го бара с WPR для месячного таймфрейма. Для облегчения восприятия, раскрасим диаграммы разным цветом: минутные таймфреймы — красным, часовые — зеленым, а дневные и более старшие — синим.

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

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   1
   
#property indicator_type1   DRAW_COLOR_ARROW
#property indicator_color1  clrRed,clrGreen,clrBlue
#property indicator_width1  3
#property indicator_label1  "WPR"

Значения WPR преобразуем в диапазон [-1,+1]. Масштаб подокна выберем с некоторым запасом от диапазона. Уровни со значениями ±0.6 соответствуют стандартным -20 и -80 до преобразования WPR.

#property indicator_maximum    +1.2
#property indicator_minimum    -1.2
   
#property indicator_level1     +0.6
#property indicator_level2     -0.6
#property indicator_levelstyle STYLE_DOT
#property indicator_levelcolor clrSilver
#property indicator_levelwidth 1

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

input int WPRPeriod = 14;
input string WorkSymbol = ""// Symbol
input int Mark = 0;
   
const string _WorkSymbol = (WorkSymbol == "" ? _Symbol : WorkSymbol);

Для удобства кодирования набор таймфреймов перечислен в массиве TF.

#define TFS 21
   
ENUM_TIMEFRAMES TF[TFS] =
{
   PERIOD_M1,
   PERIOD_M2,
   PERIOD_M3,
   ...
   PERIOD_D1,
   PERIOD_W1,
   PERIOD_MN1,
};

Дескрипторы индикаторов для каждого таймфрейма хранятся в массиве Handle.

int Handle[TFS];

Настройку индикаторных буферов и получение дескрипторов выполним в OnInit.

double WPRBuffer[];
double Colors[];
   
int OnInit()
{
   SetIndexBuffer(0WPRBuffer);
   SetIndexBuffer(1ColorsINDICATOR_COLOR_INDEX);
   ArraySetAsSeries(WPRBuffertrue);
   ArraySetAsSeries(Colorstrue);
   PlotIndexSetString(0PLOT_LABEL_WorkSymbol + " WPR");
   
   if(Mark != 0)
   {
      PlotIndexSetInteger(0PLOT_ARROWMark);
   }
   
   for(int i = 0i < TFS; ++i)
   {
      Handle[i] = iCustom(_WorkSymbolTF[i], "IndWPR"WPRPeriod);
      if(Handle[i] == INVALID_HANDLEreturn INIT_FAILED;
   }
   
   IndicatorSetInteger(INDICATOR_DIGITS2);
   IndicatorSetString(INDICATOR_SHORTNAME,
      "%Rmtf" + "(" + _WorkSymbol + "/" + (string)WPRPeriod + ")");
   
   return INIT_SUCCEEDED;
}

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

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &data[])
{
   // ожидание готовности подчиненных индикаторов
   if(!IsDataReady())
   {
      EventSetTimer(1); // если не готово, откладываем расчет
      return prev_calculated;
   }
   if(prev_calculated == 0// инициализация
   {
      ArrayInitialize(WPRBufferEMPTY_VALUE);
      ArrayInitialize(ColorsEMPTY_VALUE);
      // постоянные цвета для последних TFS баров
      for(int i = 0i < TFS; ++i)
      {
         Colors[i] = i < 11 ? 0 : (i < 18 ? 1 : 2);
      }
   }
   else // подготовка нового бара
   {
      for(int i = prev_calculatedi < rates_total; ++i)
      {
         WPRBuffer[i] = EMPTY_VALUE;
         Colors[i] = 0;
      }
   }
   
   if(prev_calculated != rates_total// новый бар
   {
      // чистим метку на самом старом баре, сдвинувшемся влево за TFS баров
      WPRBuffer[TFS] = EMPTY_VALUE;
      // обновляем раскраску баров
      for(int i = 0i < TFS; ++i)
      {
         Colors[i] = i < 11 ? 0 : (i < 18 ? 1 : 2);
      }
   }
   
   // копируем данные из подчиненных индикаторов в свой буфер
   FillData();
   return rates_total;
}

При необходимости инициируем пересчет по таймеру.

void OnTimer()
{
   ChartSetSymbolPeriod(0_Symbol_Period);
   EventKillTimer();
}

А вот и сами функции IsDataReady и FillData (с некоторыми сокращениями).

bool IsDataReady()
{
   for(int i = 0i < TFS; ++i)
   {
      if(BarsCalculated(Handle[i]) != iBars(_WorkSymbolTF[i]))
      {
         Print("Waiting for "_WorkSymbol" "EnumToString(TF[i]));
         return false;
      }
   }
   return true;
}
   
void FillData()
{
   for(int i = 0i < TFS; ++i)
   {
      double data[1];
      // берем последнее актуальное значение (буфер 0, индекс 0)
      if(CopyBuffer(Handle[i], 001data) == 1)
      {
         WPRBuffer[i] = (data[0] + 50) / 50;
      }
   }
}

Откомпилируем индикатор и посмотрим, как он выглядит на графике. Например, создадим три копии для EURUSD, USDRUB и XAUUSD.

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

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

При первом расчете индикатор может потребовать заметного времени на подготовку таймсерий по всем таймфреймам.

Точно такой же в плане расчетной части индикатор UseWPRMTFDashboard.mq5 оформлен в виде популярной у трейдеров панели. В нем для каждого символа предполагается задать собственный вертикальный отступ в параметре Level, на котором в виде линейки маркеров выводятся значения WPR всех таймфреймов, причем значения кодируются цветом. В данной версии значения WPR нормируются в диапазон [0..1], поэтому использование линеек на уровнях, отстоящих друг от друга на несколько десятков (например, на 20, как на скриншоте ниже) позволяет разместить в подокне несколько экземпляров индикатора без наложений (80, 100, 120 и т.д.). Каждая копия — для своего рабочего символа. Кроме того, за счет того что Level больше 1.0, а значения WPR — меньше, они видны в значениях в Окне данных раздельно: слева и справа от десятичной точки.

Подписи для линеек меток обеспечиваются уровнями, динамически добавляемыми в OnInit.

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

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

Изучить исходный код UseWPRMTFDashboard.mq5 и сравнить его с UseWPRMTF.mq5 предлагается самостоятельно. Для генерации палитры цветовых оттенков используется файл ColorMix.mqh.

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

Об эффективности и ресурсоемкости составных индикаторов
 
Продемонстрированный выше подход с генерацией множества вспомогательных индикаторов не является эффективным по быстродействию и по потреблению ресурсов. Это в первую очередь пример интеграции MQL-программ и обмена данными между ними. Но как и любую технологию, её следует использовать уместно.
 
Каждый из двух созданных индикаторов рассчитывает WPR на всех барах таймсерии, а в вызывающий индикатор затем берется только одно последнее значение. Мы зря расходуем и память, и время процессора.
 
Если имеется исходный код вспомогательных индикаторов или известен принцип их работы, наиболее оптимальным является перенос расчетного алгоритма внутрь главного индикатора (или эксперта) и его применение для ограниченной, ближайшей истории минимально необходимой глубины.
 
В некоторых случаях можно обойтись без обращения к старшим таймфреймам, выполняя эквивалентные расчеты на текущем таймфрейме: например, вместо размаха цены на 14 дневных барах (что требует построения полной таймсерии D1) можно взять размах на 14*24 барах H1, при условии круглосуточной торговли и запуска индикатора на графике H1.
 
Вместе с тем, когда в торговой системе используется коммерческий индикатор (без исходного кода), получить из него данные можно только по открытым программным интерфейсам. В этом случае, создание дескриптора и затем чтение данных из индикаторного буфера через CopyBuffer — единственный, но при этом удобный, универсальный способ. Просто всегда следует иметь в виду, что вызов функций API — это более "дорогая" операция, чем манипуляции с собственным массивом внутри MQL-программы и вызовы локальных функций. Если требуется держать открытыми много терминалов, в каждом, вероятно, по набору таких неоптимизированных MQL-программ, и плюс ко всему это происходит с ограничениями на ресурсы, то вероятно падение быстродействия.