Открытие и закрытие графиков

MQL-программа может не только анализировать список графиков, но и модифицировать его: открывать новые или закрывать имеющиеся. Для этих целей выделено 2 функции: ChartOpen и ChartClose.

long ChartOpen(const string symbol, ENUM_TIMEFRAMES timeframe)

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

Если параметр symbol равен NULL, это означает символ текущего графика (на котором выполняется MQL-программа). Значение 0 в параметре timeframe соответствует PERIOD_CURRENT.

Максимально возможное количество одновременно открытых графиков в терминале не может быть больше CHARTS_MAX (100).

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

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

bool ChartClose(long chartId = 0)

Функция закрывает график с указанным идентификатором (значение по умолчанию 0 означает текущий график).

Функция возвращает признак успеха.

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

Для начала нам необходимо составить некий список, ведущий подсчет графиков для конкретной пары символ/таймфрейм. Эта задача поручена функции ChartIdleList, которая во многом похожа на то, что мы видели в скрипте ChartList.mq5. Сам список формируется в массиве-карте MapArray<string,int> chartCounts.

#include <MQL5Book/Periods.mqh>
#include <MQL5Book/MapArray.mqh>
   
#define PUSH(A,V) (A[ArrayResize(AArraySize(A) + 1) - 1] = V)
   
void OnStart()
{
   MapArray<string,intchartCounts;
   ulong duplicateChartIDs[];
   // собираем дубликаты пустых графиков
   if(ChartIdleList(chartCountsduplicateChartIDs))
   {
      ...
   }
   else
   {
      Print("No idle charts.");
   }
}

Попутно функция ChartIdleList заполняет массив duplicateChartIDs с идентификаторами свободных графиков, подходящих под условия закрытия.

int ChartIdleList(MapArray<string,int> &mapulong &duplicateChartIDs[])
{
   // перечисляем графики, пока не закончится их список
   for(long id = ChartFirst(); id != -1id = ChartNext(id))
   {
      // пропускаем объекты
      if(ChartGetInteger(idCHART_IS_OBJECT)) continue;
   
      // получаем основные свойства графика
      const int win = (int)ChartGetInteger(idCHART_WINDOWS_TOTAL);
      const string expert = ChartGetString(idCHART_EXPERT_NAME);
      const string script = ChartGetString(idCHART_SCRIPT_NAME);
      const int objectCount = ObjectsTotal(id);
   
      // подсчитываем количество индикаторов      
      int indicators = 0;
      for(int i = 0i < win; ++i)      
      {
         indicators += ChartIndicatorsTotal(idi);
      }
      
      const string key = ChartSymbol(id) + "/" + PeriodToString(ChartPeriod(id));
      
      if(map[key] == 0     // первый раз всегда считаем новое сочетание символ/ТФ
                           // иначе считаем только пустые графики:
         || (indicators == 0           // без индикаторов
            && StringLen(expert) == 0  // без эксперта
            && StringLen(script) == 0  // без скрипта
            && objectCount == 0))      // без объектов
      {
         const int i = map.inc(key);
         if(map[i] > 1)                // дубликат
         {
            PUSH(duplicateChartIDsid);
         }
      }
   }
   return map.getSize();
}

После того как список для удаления сформирован, вызываем в OnStart функцию ChartClose в цикле по списку.

void OnStart()
{
   ...
   if(ChartIdleList(chartCountsduplicateChartIDs))
   {
      for(int i = 0i < ArraySize(duplicateChartIDs); ++i)
      {
         const ulong id = duplicateChartIDs[i];
         // запрашиваем вывести график на передний план
         ChartSetInteger(idCHART_BRING_TO_TOPtrue);
         // обновляем состояние окон, прокачивая очередь с запросом
         ChartRedraw(id);
         // спрашиваем подтверждения пользователя
         const int button = MessageBox(
            "Remove idle chart: "
            + ChartSymbol(id) + "/" + PeriodToString(ChartPeriod(id)) + "?",
            __FILE__MB_YESNOCANCEL);
         if(button == IDCANCELbreak;   
         if(button == IDYES)
         {
            ChartClose(id);
         }
      }
      ...

Для каждого графика предварительно вызывается функция ChartSetInteger(id, CHART_BRING_TO_TOP, true), чтобы показать пользователю, какое именно окно предполагается закрыть. Поскольку эта функция асинхронная (только помещает команду на активацию окна в очередь событий), требуется дополнительно вызвать ChartRedraw, что обрабатывает все накопившиеся сообщения. После этого пользователю выдается запрос на подтверждение действия. График закрывается только по нажатию Да. Нажатие Нет пропускает текущий график (оставляет его открытым), и цикл продолжается. Нажав кнопку Отмена, можно прерывать цикл досрочно.