Режимы отображения графика

Четыре свойства из перечисления ENUM_CHART_PROPERTY_INTEGER описывают режимы отображения графика. Все эти свойства доступны как на чтение через ChartGetInteger, так и на запись через ChartSetInteger, что позволяет менять внешний вид графика.

Идентификатор

Описание

Тип значения

CHART_MODE

Тип графика (свечи, бары или линия)

ENUM_CHART_MODE

CHART_FOREGROUND

Ценовой график на переднем плане

bool

CHART_SHIFT

Режим отступа ценового графика от правого края

bool

CHART_AUTOSCROLL

Режим автоматического перехода к правому краю графика

bool

Для режима CHART_MODE в MQL5 имеется особое перечисление ENUM_CHART_MODE. Его элементы приведены в следующей таблице.

Идентификатор

Описание

Значение

CHART_BARS

Отображение в виде баров

0

CHART_CANDLES

Отображение в виде японских свечей

1

CHART_LINE

Отображение в виде линии, проведенной по ценам Close

2

Реализуем скрипт ChartMode.mq5, который будет отслеживать состояние режимов и выводить в журнал сообщения при обнаружении изменений. Поскольку алгоритмы обработки свойств носят общий характер, вынесем их в отдельный заголовочный файл ChartModeMonitor.mqh, который будем затем подключать к разными тестам.

Основу заложим в абстрактный класс ChartModeMonitorInterface: в нем предоставлены перегруженные get- и set-методы для всех типов. Непосредственно проверку свойств в необходимом объеме должны будут осуществлять производные классы, переопределяя виртуальный метод snapshot.

class ChartModeMonitorInterface
{
public:
   long get(const ENUM_CHART_PROPERTY_INTEGER propertyconst int window = 0)
   {
      return ChartGetInteger(0propertywindow);
   }
   double get(const ENUM_CHART_PROPERTY_DOUBLE propertyconst int window = 0)
   {
      return ChartGetDouble(0propertywindow);
   }
   string get(const ENUM_CHART_PROPERTY_STRING property)
   {
      return ChartGetString(0property);
   }
   bool set(const ENUM_CHART_PROPERTY_INTEGER propertyconst long valueconst int window = 0)
   {
      return ChartSetInteger(0propertywindowvalue);
   }
   bool set(const ENUM_CHART_PROPERTY_DOUBLE propertyconst double value)
   {
      return ChartSetDouble(0propertyvalue);
   }
   bool set(const ENUM_CHART_PROPERTY_STRING propertyconst string value)
   {
      return ChartSetString(0propertyvalue);
   }
   
   virtual void snapshot() = 0;
   virtual void print() { };
   virtual void backup() { }
   virtual void restore() { }
};

Также в классе зарезервированы методы print (например, для вывода в журнал), сохранения текущего состояния (backup) и его восстановления (restore). Они объявлены не абстрактными, а с пустой реализацией, так как не являются обязательными.

Конкретные классы для свойств разных типов имеет смысл определить как единый шаблон, унаследованный от ChartModeMonitorInterface и принимающий параметрические типы значения (T) и перечисления (E). Например, для целочисленных свойств нужно будет установить T=long и E=ENUM_CHART_PROPERTY_INTEGER.

В объекте предусмотрен массив data для хранения пар [ключ,значение] со всеми запрошенными свойствами. Он имеет шаблонный тип MapArray<K,V>, который мы вводили ранее для индикатора IndUnityPercent в разделе Мультивалютные и мультитаймфреймовые индикаторы. Его особенность заключается в том, что помимо обычного доступа к элементам массива по номерам можно использовать адресацию по ключу.

Для заполнения массива в конструктор передается массив целых чисел, которые предварительно проверяются на соответствие идентификаторам заданного перечисления E с помощью метода detect. Все правильные свойства тут же считываются через вызов get и полученные значения сохраняются в карте совместно с их идентификаторами.

#include <MQL5Book/MapArray.mqh>
   
template<typename T,typename E>
class ChartModeMonitorBasepublic ChartModeMonitorInterface
{
protected:
   MapArray<E,Tdata// массив-карта пар [свойство,значение]
   
   // метод проверяет, является ли переданная константа элементом перечисления,
   // и если да, то добавляет его в массив-карту       
   bool detect(const int v)
   {
      ResetLastError();
      EnumToString((E)v); // результирующая строка не используется
      if(_LastError == 0// важно лишь, есть ошибка или нет
      {
         data.put((E)vget((E)v));
         return true;
      }
      return false;
   }
 
public:
   ChartModeMonitorBase(int &flags[])
   {
      for(int i = 0i < ArraySize(flags); ++i)
      {
         detect(flags[i]);
      }
   }
   
   virtual void snapshot() override
   {
      MapArray<E,Ttemp;
      // собираем текущее состояние всех свойств
      for(int i = 0i < data.getSize(); ++i)
      {
         temp.put(data.getKey(i), get(data.getKey(i)));
      }
      
      // сравниваем с предыдущим состоянием, выводим различия
      for(int i = 0i < data.getSize(); ++i)
      {
         if(data[i] != temp[i])
         {
            Print(EnumToString(data.getKey(i)), " "data[i], " -> "temp[i]);
         }
      }
      
      // сохраняем для следующего сравнения
      data = temp;
   }
   ...
};

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

Методы print, backup и restore реализованы максимально просто.

template<typename T,typename E>
class ChartModeMonitorBasepublic ChartModeMonitorInterface
{
protected:
   ...
   MapArray<E,Tstore// бэкап
public:
   ...
   virtual void print() override
   {
      data.print();
   }
   virtual void backup() override
   {
      store = data;
   }
   
   virtual void restore() override
   {
      data = store;
      // восстанавливаем свойства графика
      for(int i = 0i < data.getSize(); ++i)
      {
         set(data.getKey(i), data[i]);
      }
   }

Связка методов backup/restore позволяет сохранить состояние графика перед началом экспериментов с ним, а после завершения работы тестового скрипта, восстановить всё, как было.

Наконец, последний класс в файле ChartModeMonitor.mqh так и называется ChartModeMonitor. Он объединяет в себе три экземпляра ChartModeMonitorBase, создаваемых под имеющиеся сочетания типов свойств. Для них выделен массив m указателей на базовый интерфейс ChartModeMonitorInterface. Сам класс также является производным от него.

#include <MQL5Book/AutoPtr.mqh>
   
#define CALL_ALL(A,Mfor(int i = 0size = ArraySize(A); i < size; ++iA[i][].M
   
class ChartModeMonitorpublic ChartModeMonitorInterface
{
   AutoPtr<ChartModeMonitorInterfacem[3];
   
public:
   ChartModeMonitor(int &flags[])
   {
      m[0] = new ChartModeMonitorBase<long,ENUM_CHART_PROPERTY_INTEGER>(flags);
      m[1] = new ChartModeMonitorBase<double,ENUM_CHART_PROPERTY_DOUBLE>(flags);
      m[2] = new ChartModeMonitorBase<string,ENUM_CHART_PROPERTY_STRING>(flags);
   }
   
   virtual void snapshot() override
   {
      CALL_ALL(msnapshot());
   }
   
   virtual void print() override
   {
      CALL_ALL(mprint());
   }
   
   virtual void backup() override
   {
      CALL_ALL(mbackup());
   }
   
   virtual void restore() override
   {
      CALL_ALL(mrestore());
   }
};

Для упрощения кода здесь использован макрос CALL_ALL, который вызывает указанный метод для всех объектов из массива, причем делает это с учетом перегруженного оператора [] в классе AutoPtr (он применяется для разыменования умного указателя и получения прямого указателя на "охраняемый" объект).

За освобождение объектов обычно отвечает деструктор, но в данном случае было решено применить массив AutoPtr (этот класс был рассмотрен в разделе Шаблоны объектных типов). Это гарантирует автоматическое удаление динамических объектов при штатном освобождении массива m.

Более полная версия монитора с поддержкой номеров подокон поставляется в файле ChartModeMonitorFull.mqh.

На основе класса ChartModeMonitor можно легко реализовать задуманный скрипт ChartMode.mq5. Его задача — проверять состояние заданного набора свойств каждые полсекунды. Пока мы используем здесь бесконечный цикл и Sleep, но скоро научимся реагировать на события на графиках по-другому: за счет уведомлений от терминала.

#include <MQL5Book/ChartModeMonitor.mqh>
   
void OnStart()
{
   int flags[] =
   {
      CHART_MODECHART_FOREGROUNDCHART_SHIFTCHART_AUTOSCROLL
   };
   ChartModeMonitor m(flags);
   Print("Initial state:");
   m.print();
   m.backup();
   
   while(!IsStopped())
   {
      m.snapshot();
      Sleep(500);
   }
   m.restore();
}

Набросьте скрипт на любой график и попробуйте менять режимы с помощью инструментальных кнопок — так доступны все элементы, кроме CHART_FOREGROUND, который можно переключить из диалога свойств (закладка Общие, флаг График сверху).

Кнопки инструментальной панели для переключения режимов графика

Кнопки инструментальной панели для переключения режимов графика

Например, следующий журнал получился в результате переключения отображения со свечей на бары, с баров на линии и обратно на свечи, а затем — включения отступа и автопрокрутки к началу.

Initial state:
    [key] [value]
[0]     0       1
[1]     1       0
[2]     2       0
[3]     4       0
CHART_MODE 1 -> 0
CHART_MODE 0 -> 2
CHART_MODE 2 -> 1
CHART_SHIFT 0 -> 1
CHART_AUTOSCROLL 0 -> 1

Более практичным примером использования свойства CHART_MODE является усовершенствованная версия индикатора IndSubChart.mq5 (с его упрощенной версией IndSubChartSimple.mq5 мы познакомились в разделе Мультивалютные и мультитаймфреймовые индикаторы). Напомним, что индикатор предназначен для отображения котировок стороннего символа в подокне, и ранее нам приходилось запрашивать способ отображения (свечи, бары, линии) у пользователя через входной параметр. Сейчас необходимость в параметре отпала, потому что мы можем автоматически переключать индикатор в тот режим, который используется в основном окне.

Текущий режим хранится в глобальной переменной mode и первый раз присваивается во время инициализации.

ENUM_CHART_MODE mode = 0;
   
int OnInit()
{
   ...
   mode = (ENUM_CHART_MODE)ChartGetInteger(0CHART_MODE);
   ...
}

Детектирование нового режима правильнее всего делать в специально предназначенном для этого обработчике события OnChartEvent — мы изучим его в отдельной главе. На данном этапе важно знать, что при любом изменении графика MQL-программа может получать уведомления от терминала, если в коде будет описана функция с этим предопределенным прототипом (именем и списком параметров). В частности, в её первом параметре передается идентификатор события, описывающий его суть. Нас пока интересует сам график, в связи с чем мы проверяем eventId на равенство CHARTEVENT_CHART_CHANGE. Это нужно, поскольку обработчик также способен отслеживать графические объекты, клавиатуру, мышь и произвольные пользовательские сообщения.

void OnChartEvent(const int eventId,
                  // неиспользуемые здесь параметры
                  const long &, const double &, const string &)
{
   if(eventId == CHARTEVENT_CHART_CHANGE)
   {
      const ENUM_CHART_MODE newmode = (ENUM_CHART_MODE)ChartGetInteger(0CHART_MODE);
      if(mode != newmode)
      {
         const ENUM_CHART_MODE oldmode = mode;
         mode = newmode;
         // изменяем привязку буферов и тип отрисовки на ходу
         InitPlot(0InitBuffers(mode), Mode2Style(mode));
         // TODO: чуть позже сделаем автонастройку цветов
         // SetPlotColors(0, mode);
         if(oldmode == CHART_LINE || newmode == CHART_LINE)
         {
            // переключение на или с режима CHART_LINE требует обновить весь график,
            // потому что меняется количество буферов
            Print("Refresh");
            ChartSetSymbolPeriod(0_Symbol_Period);
         }
         else
         {
            // при переключении между свечами и барами достаточно
            // просто перерисовать график на новый манер,
            // потому что данные не меняются (прежние 4 буфера со значениями)
            Print("Redraw");
            ChartRedraw();
         }
      }
   }
}

Вы можете проверить новый индикатор самостоятельно, набросив его на график и переключая способы отрисовки.

Это еще не все усовершенствования, сделанные в IndSubChart.mq5. Чуть позже, в разделе про цвета графиков мы покажем автоматическую подстройку диаграмм под цветовую схему графика.