Комбинирование вывода в главное окно и вспомогательное

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

  • Реализовать родительский индикатор для отдельного окна и выводить там диаграммы, а в главном окне использовать для отображения данных графические объекты. Это плохо, потому что данные из объектов нельзя считывать как из таймсерии, и множество объектов потребляет лишние ресурсы.
  • Разработать для основного окна собственную виртуальную панель (класс), в которой в правильном масштабе отображать таймсерии, которые должны были бы выводиться в подокне.
  • Использовать несколько индикаторов — как минимум один для главного окна и один для подокна — и обмениваться между ними данными через разделяемую память (требуется DLL), ресурсы или базу данных.
  • Дублировать расчеты (использовать общий исходный код) в индикаторах для главного окна и подокна.

Мы представим один из вариантов решения, которое основывается на выходе за рамки одной MQL-программы: необходим дополнительный индикатор со свойством indicator_separate_window. Он у нас фактически уже есть — ведь мы создаем его расчетную часть, запрашивая дескриптор. Осталось лишь неким образом вывести его в отдельном подокне.

В новой (полной) версии UseDemoAll.mq5 мы будем анализировать метаданные запрошенного для создания индикатора в соответствующем элементе перечисления IndicatorType. Напомним, что там помимо прочего закодировано и рабочее окно каждого типа встроенного индикатора. Когда индикатор требует отдельное окно, мы будем создавать таковое с помощью специальных функций MQL5, с которыми еще предстоит познакомиться.

Для пользовательских индикаторов неоткуда взять информацию о рабочем окне. Поэтому добавим входную переменную IndicatorCustomSubwindow, в которой пользователь сможет указать, что требуется подокно.

input bool IndicatorCustomSubwindow = false// Custom Indicator Subwindow

В OnInit скроем буферы, предназначенные для подокна.

int OnInit()
{
   ...
   const bool subwindow = (IND_WINDOW(IndicatorSelector) > 0)
      || (IndicatorSelector == iCustom_ && IndicatorCustomSubwindow);
   for(int i = 0i < BUF_NUM; ++i)
   {
      ...
      PlotIndexSetInteger(iPLOT_DRAW_TYPE,
         i < n && !subwindow ? DrawType : DRAW_NONE);
   }
   ...

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

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

   int handle = ... // получаем дескриптор индикатора, iCustom или IndicatorCreate
 
                    // задаем текущий график (0)
                    // |
                    // |      номер окна ставим равным текущему количеству окон
                    // |                          |
                    // |                          |        передаем сам дескриптор
                    // |                          |                       |
                    // v                          v                       v
   ChartIndicatorAdd(  0, (int)ChartGetInteger(0CHART_WINDOWS_TOTAL), handle); 

Зная о такой возможности, нетрудно выдвинуть идею о вызове ChartIndicatorAdd с передачей ей дескриптора уже готового подчиненного индикатора.

Вторая функция, которая нам потребуется, называется ChartIndicatorName. Она возвращает краткое имя индикатора по его дескриптору. Данное имя соответствует свойству INDICATOR_SHORTNAME, установленному в коде индикатора, и может отличаться от названия файла. Имя потребуется, чтобы подчистить за собой, то есть удалить вспомогательный индикатор и его подокно, после удаления или перенастройки родительского индикатора.

string subTitle = "";
   
int OnInit()
{
   ...
   if(subwindow)
   {
      // показываем новый индикатор в подокне
      const int w = (int)ChartGetInteger(0CHART_WINDOWS_TOTAL);
      ChartIndicatorAdd(0wHandle);
      // сохраняем имя, чтобы удалить индикатор в OnDeinit
      subTitle = ChartIndicatorName(0w0);
   }
   ...
}

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

void OnDeinit(const int)
{
   Print(__FUNCSIG__, (StringLen(subTitle) > 0 ? " deleting " + subTitle : ""));
   if(StringLen(subTitle) > 0)
   {
      ChartIndicatorDelete(0, (int)ChartGetInteger(0CHART_WINDOWS_TOTAL) - 1,
         subTitle);
   }
}

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

Если теперь запустить UseDemoAll и выбрать какой-нибудь индикатор из списка, помеченный звездочкой (т.е. требующий подокна), например, тот же RSI, увидим ожидаемый результат: RSI в отдельном окне.

RSI в подокне, созданном индикатором UseDemoAll

RSI в подокне, созданном индикатором UseDemoAll