Управление индикаторами на графике

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

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

int ChartIndicatorsTotal(long chartId, int window)

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

string ChartIndicatorName(long chartId, int window, int index)

Функция возвращает короткое имя индикатора по номеру index в списке индикаторов, расположенных в указанном окне графика. Под коротким именем понимается имя, заданное в свойстве INDICATOR_SHORTNAME функцией IndicatorSetString (если оно не задано, то по умолчанию, равно названию файла индикатора).

int ChartIndicatorGet(long chartId, int window, const string shortname)

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

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

bool ChartIndicatorAdd(long chartId, int window, int handle)

Функция добавляет в указанное окно графика индикатор с передаваемым в последнем параметре дескриптором. Индикатор и график должны иметь одинаковое сочетание символа и таймфрейма. В противном случае произойдет ошибка ERR_CHART_INDICATOR_CANNOT_ADD (4114).

Чтобы добавить индикатор в новое окно, параметр window должен быть на единицу больше, чем индекс последнего существующего окна, то есть равен свойству CHART_WINDOWS_TOTAL, получаемому через вызов ChartGetInteger. Если значение параметра превышает значение ChartGetInteger(ID,CHART_WINDOWS_TOTAL), то новое окно и индикатор не будут созданы.

Если в основное окно графика добавляется индикатор, который должен быть отрисован в отдельном подокне (например, встроенный iMACD или пользовательский индикатор с указанным свойством #property indicator_separate_window), то такой индикатор может казаться невидимым, хотя и будет присутствовать в списке индикаторов. Это, как правило, означает, что значения данного индикатора не попадают в отображаемый диапазон ценового графика. Значения такого "невидимого" индикатора можно наблюдать в Окне данных и читать с помощью функций из других MQL-программ.

Добавление индикатора на график увеличивает внутренний счетчик его использования из-за привязки к графику. Если MQL-программа хранит у себя его дескриптор, и он больше не нужен, то стоит удалить его путем вызова IndicatorRelease — это фактически уменьшит счетчик, но индикатор останется на графике.

bool ChartIndicatorDelete(long chartId, int window, const string shortname)

Функция удаляет индикатор с указанным коротким именем из окна с номером window на графике chartId. Если в указанном подокне графика существует несколько индикаторов с одинаковым коротким именем, то будет удален первый по порядку.

Если на значениях удаляемого индикатора построены другие индикаторы на этом же графике, то они также будут удалены.

Удаление индикатора с графика не означает, что его расчетная часть также будет удалена из памяти терминала, если в MQL-программе остался дескриптор. Для освобождения дескриптора индикатора используйте функцию IndicatorRelease.

Следующая функция ChartWindowFind возвращает номер подокна, в котором находится индикатор. Существует 2 формы, предназначенные для поиска текущего индикатора на его графике или индикатора с заданным коротким именем на произвольном графике с идентификатором chartId.        

int ChartWindowFind()

int ChartWindowFind(long chartId, string shortname)

Вторую форму можно использовать в скриптах и экспертах.

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

input bool IncludeEmptyCharts = true;
input bool IncludeHiddenWindows = true;

При значении по умолчанию (true) параметр IncludeEmptyCharts предписывает включать в список все графики, включая пустые. Параметр IncludeHiddenWindows по умолчанию задает вывод скрытых окон. Эти настройки соответствуют предыдущей логике скриптов ChartListN.

Для подсчета общего количества индикаторов и индикаторов в подокнах определены переменные indicators и subs.

void ChartList()
{
   ...
   int indicators = 0subs = 0;
   ...

Основные изменения претерпел рабочий цикл по окнам текущего графика.

void ChartList()
{
      ...
      for(int i = 0i < wini++)
      {
         const bool visible = ChartGetInteger(idCHART_WINDOW_IS_VISIBLEi);
         if(!visible && !IncludeHiddenWindowscontinue;
         if(!visible)
         {
            Print("  "i"/Hidden");
         }
         const int n = ChartIndicatorsTotal(idi);
         for(int k = 0k < nk++)
         {
            if(temp == 0)
            {
               Print(header);
            }
            Print("  "i"/"k" [I] "ChartIndicatorName(idik));
            indicators++;
            if(i > 0subs++;
            temp++;
         }
      }
      ...

Здесь мы добавили вызов ChartIndicatorsTotal и ChartIndicatorName. Теперь в списке будут упоминаться MQL-программы всех типов: [E] — эксперты, [S] — скрипты, [I] — индикаторы.

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

Chart List
N, ID, Symbol, TF, #subwindows, *active, Windows handle
0 132358585987782873 EURUSD M15 #1    133538
  1/0 [I] ATR(11)
1 132360375330772909 EURUSD D1     133514
2 132544239145024745 EURUSD M15   *   395646
 [S] ChartList
3 132544239145024732 USDRUB D1     395688
4 132544239145024744 EURUSD H1 #2  active  2361730
  1/0 [I] %R(14)
  2/Hidden
  2/0 [I] Momentum(15)
5 132544239145024746 EURUSD H1     133584
Total chart number: 6, with MQL-programs: 3
Experts: 0, Scripts: 1, Indicators: 3 (main: 0 / sub: 3)

Если сбросить в false оба входных параметра, получим сокращенный список.

Chart List
N, ID, Symbol, TF, #subwindows, *active, Windows handle
0 132358585987782873 EURUSD M15 #1    133538
  1/0 [I] ATR(11)
2 132544239145024745 EURUSD M15   * active  395646
 [S] ChartList
4 132544239145024744 EURUSD H1 #2    2361730
  1/0 [I] %R(14)
Total chart number: 6, with MQL-programs: 3
Experts: 0, Scripts: 1, Indicators: 2 (main: 0 / sub: 2)

В качестве второго примера рассмотрим интересный скрипт ChartIndicatorMove.mq5.

При наложении нескольких индикаторов на график часто через некоторое время выясняется, что порядок расположения индикаторов желательно изменить. MetaTrader 5 не имеет встроенных средств для этого, что вынуждает удалять некоторые индикаторы и добавлять их заново, причем важно сохранить и восстановить настройки. Скрипт ChartIndicatorMove.mq5 предоставляет вариант автоматизации данной процедуры. Важно отметить, что скрипт переносит только индикаторы: если необходимо поменять порядок подокон вместе с графическими объектами (если они есть внутри), то следует воспользоваться tpl-шаблонами.

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

Направление переноса — вверх или вниз — задается во входной переменной MoveDirection. Для её описания подготовлено перечисление DIRECTION.

#property script_show_inputs
   
enum DIRECTION
{
   Up = -1,
   Down = +1,
};
   
input DIRECTION MoveDirection = Up;

Для того чтобы переносить индикатор не в соседнее подокно, а в следующее, то есть фактически менять подокна с индикаторами местами (что обычно и требуется), заведена входная переменная JumpOver.

input bool JumpOver = true;

Цикл по индикаторам целевого окна, получаемого из ChartWindowOnDropped, запускается в OnStart.

void OnStart()
{
   const int w = ChartWindowOnDropped();
   if(w == 0 && MoveDirection == Up)
   {
      Alert("Can't move up from window at index 0");
      return;
   }
   const int n = ChartIndicatorsTotal(0w);
   for(int i = 0i < n; ++i)
   {
      ...
   }
}

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

  • Получаем дескриптор с помощью вызова ChartIndicatorGet;
  • Добавляем его в окно над или под текущим через ChartIndicatorAdd, в соответствии с выбранным направлением, причем при переносе вниз может быть автоматически создано новое подокно;
  • Удаляем индикатор из предыдущего окна с помощью ChartIndicatorDelete;
  • Освобождаем дескриптор, потому что он нам в программе больше не нужен.

      ...
      const string name = ChartIndicatorName(0wi);
      const string caption = EnumToString(MoveDirection);
      const int button = MessageBox("Move '" + name + "' " + caption + "?",
         captionMB_YESNOCANCEL);
      if(button == IDCANCELbreak;
      if(button == IDYES)
      {
         const int h = ChartIndicatorGet(0wname);
         ChartIndicatorAdd(0w + MoveDirectionh);
         ChartIndicatorDelete(0wname);
         IndicatorRelease(h);
         break;
      }
      ...

На следующем изображении показан результат обмена местами подокон с индикаторами WPR и Momentum. Скрипт запускался путем отпускания на верхнем подокне с индикатором WPR, направление движения было выбрано вниз (Down), прыжок (JumpOver) оставлен включенным по умолчанию.

Обмен местами индикаторов в подокнах

Обмен местами индикаторов в подокнах

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