- Функции для получения основных свойств текущего графика
- Идентификация графиков
- Получение списка графиков
- Получение символа и таймфрейма произвольного графика
- Обзор функций для работы с полным набором свойств
- Описательные свойства графика
- Проверка состояния основного окна
- Получение количества и признака видимости окон/подокон
- Режимы отображения графика
- Управление видимостью элементов графика
- Отступы по горизонтали
- Горизонтальный масштаб (по времени)
- Вертикальный масштаб (по цене и показаниям индикаторов)
- Цвета
- Управление мышью и клавиатурой
- Открепление окна графика
- Получение координат буксировки MQL-программы на график
- Перевод экранных координат во время/цену и обратно
- Прокрутка графика по оси времени
- Запрос перерисовки графика
- Переключение символа и таймфрейма
- Управление индикаторами на графике
- Открытие и закрытие графиков
- Работа с tpl-шаблонами графика
- Сохранение изображения графика
Работа с tpl-шаблонами графика
Две функции MQL5 API позволяют работать с так шаблонами — файлами с расширением tpl, в которых сохраняется наполнение графиков, то есть все их настройки, вместе с нанесенными объектами, индикаторами и экспертом (если они есть).
bool ChartSaveTemplate(long chartId, const string filename)
Функция сохраняет текущие настройки графика в tpl-шаблон с указанным именем.
График задается идентификатором chartId, 0 означает текущий график.
Имя файла для сохранения шаблона (filename) можно указывать без расширения ".tpl": оно будет добавлено автоматически. Шаблон по умолчанию сохраняется в папку каталог_терминала/Profiles/Templates/ и может быть использован затем для ручного применения в терминале. Однако допустимо указать не просто имя, но и путь относительно каталога MQL5, в частности, начинающийся c "/Files/". Таким образом появится возможность открыть сохраненный шаблон функциями работы с файлами, проанализировать, и при необходимости отредактировать (см. пример ChartTemplate.mq5 далее).
Если одноименный файл по указанному пути уже существует, его содержимое будет перезаписано.
Объединенный пример для сохранения и применения шаблона мы рассмотрим чуть позже.
bool ChartApplyTemplate(long chartId, const string filename)
Функция применяет к графику chartId шаблон из указанного файла.
Поиск файла шаблона осуществляется по следующим правилам:
- Если имя filename содержит путь (начинается с обратной "\\" или прямой "/" косой черты), то шаблон ищется относительно пути каталог_данных_терминала/MQL5.
- Если пути в имени нет, шаблон ищется в том же месте, где расположен исполняемый EX5-файла, в котором происходит вызов функции.
- Если шаблон не найден в первых двух местах, он ищется в стандартной папке для шаблонов каталог_терминала/Profiles/Templates/.
Обратите внимание, что каталог_данных_терминала означает папку, в которой хранятся изменяемые файлы и ее расположение может зависеть от типа операционной системы, имени пользователя и настроек безопасности компьютера. В общем случае она отличается от папки каталог_терминала, хотя в некоторых случаях (например, при работе под учетной записью из группы администраторов) они могут совпадать. Расположение папок каталог_данных_терминала и каталог_терминала можно узнать с помощью функции TerminalInfoString (см. константы TERMINAL_DATA_PATH и TERMINAL_PATH, соответственно).
Вызов ChartApplyTemplate фактически отдает команду, которая поступает в очередь сообщений графика и выполняется только после обработки всех предыдущих команд.
Загрузка шаблона приводит к остановке всех MQL-программ, выполняющихся на графике, включая и ту, которая инициировала загрузку. Если шаблон содержит индикаторы и эксперт, будут запущены их новые экземпляры.
В целях безопасности при применении к графику шаблона с экспертом могут ограничиваться права на торговлю. Если у MQL-программы, которая вызывает функцию ChartApplyTemplate, отсутствуют права на торговлю, то эксперт, загруженный при помощи шаблона, также не будет иметь прав на торговлю вне зависимости от настроек шаблона. Если у MQL-программы, которая вызывает функцию ChartApplyTemplate, есть права на торговлю, а в настройках шаблона права отсутствуют, то советник, загруженный при помощи шаблона, не будет иметь прав на торговлю.
Пример скрипта ChartDuplicate.mq5 позволяет создать копию текущего графика.
void OnStart()
|
Сначала с помощью ChartSaveTemplate создается временный tpl-файл, затем открывается новый график (вызов ChartOpen), и наконец функция ChartApplyTemplate применяет этот шаблон к новому графику.
Однако во многих случаях перед программистом стоит более сложная задача: не просто применить шаблон, а предварительно отредактировать его.
Использование шаблонов позволяет изменять многие свойства графиков, которые недоступны с помощью остальных функций MQL5 API, например, видимость индикаторов в разрезе таймфреймов, порядок подокон индикаторов вместе с нанесенными на них объектами и т.д.
Формат tpl-файла идентичен chr-файлам, используемым терминалом для хранения графиков между сеансами (в папке каталог_терминала/Profiles/Charts/имя_профиля).
Tpl-файл представляет собой текстовый файл с особым синтаксисом. Свойства в нем могут представлять собой пару "ключ=значение", записываемую на одной строке, или своего рода группы, содержащие по несколько свойств "ключ=значение". Такие группы далее будем называть контейнерами, потому что помимо отдельных свойств они могут также содержать и другие, вложенные контейнеры.
Контейнер начинается строкой вида "<tag>", где tag — один из предопределенных типов контейнеров (см. далее), а заканчивается парной строкой вида "</tag>" (названия тегов должны совпадать). Иными словами, формат похож в некотором смысле на XML (без заголовка), в котором все лексические единицы должны записываться на отдельных строках, а свойства тегов указываются не их атрибутами (как в XML — внутри открывающей части "<tag attribute1=value1...>"), а во внутреннем тексте тега.
Перечень поддерживаемых тегов включает, в частности:
- chart — корневой контейнер с основными свойствами графика и всеми подчиненными контейнерами;
- expert — контейнер с общими свойствами эксперта, например, разрешением на торговлю (внутри chart);
- window — контейнер со свойствами окна/подокна и его подчиненными контейнерами (внутри chart);
- object — контейнер со свойствами графического объекта (внутри window);
- indicator — контейнер со свойствами индикатора (внутри window);
- graph — контейнер со свойствами диаграммы индикатора (внутри indicator);
- level — контейнер со свойствами уровня индикатора (внутри indicator);
- period — контейнер со свойствами видимости объекта или индикатора на конкретном таймфрейме (внутри object или indicator);
- inputs — контейнер с настройками (входными переменными) пользовательских индикаторов и экспертов.
Возможный перечень свойств в парах "ключ=значение" довольно обширен и не имеет официальной документации. При необходимости можно разобраться с этими особенностями платформы самостоятельно.
Вот фрагменты из одного tpl-файла (отступы в форматировании сделаны для наглядного представления вложенности контейнеров).
<chart>
|
Для работы с tpl-файлами подготовлен заголовочный файл TplFile.mqh, с помощью которого можно анализировать и модифицировать шаблоны. В нем реализовано 2 класса:
- Container — для чтения и хранения элементов файла с учетом иерархии (вложенности), а также записи в файл после возможной модификации;
- Selector — для последовательного обхода элементов иерархии (объектов Container) в поисках совпадения с неким запросом, который записывается в виде строки, похожей на xpath-селектор ("/path/element[attribute=value]").
Объекты класса Container создаются с помощью конструктора, который принимает первым параметром дескриптор tpl-файла для чтения, а вторым — название тега. По умолчанию название тега равно NULL, что подразумевает корневой контейнер (весь файл целиком). Таким образом, контейнер сам заполняет себя содержимым в процессе чтения файла (см. метод read).
Свойства текущего элемента, то есть пары "ключ=значение", расположенные непосредственно внутри данного контейнера, предполагается складывать в карту MapArray<string,string> properties. Вложенные контейнеры попадают в массив Container *children[].
#include <MQL5Book/MapArray.mqh>
|
В методе read мы построчно читаем и анализируем файл. Если встречается открывающий тег вида "<tag>", мы создаем новый объект-контейнер и продолжаем чтение в нем. Если встречается закрывающий тег вида "</tag>" с совпадающим именем, возвращаем признак успеха (true) — контейнер сформирован. В остальных строках считываем пары "ключ=значение" и складываем в массив properties.
Для поиска элементов в шаблоне разработан класс Selector. В его конструктор передается строка с иерархией искомых тегов. Например, строка "/chart/window/indicator" соответствует графику, в котором есть окно/подокно, а в нем, в свою очередь, любой индикатор. Результатом поиска будет первое совпадение. Данный запрос, как правило, найдет график котировок, потому что он хранится в шаблоне как индикатор с именем "Main" и идет в начале файла, до прочих подокон.
Более практичны запросы с указанием названия и значения конкретного атрибута. В частности, модифицированная строка "/chart/window/indicator[name=Momentum]" будет искать только индикатор Momentum. Данный поиск отличается от вызова ChartWindowFind, потому что здесь имя указано без параметров, в то время как ChartWindowFind использует короткое имя индикатора, в которое обычно включаются значения параметров, а они могут варьироваться.
Для встроенных индикаторов свойство name содержит непосредственно название, а для пользовательских в нем будет написано "Custom Indicator". Ссылка на пользовательский индикатор дается в свойстве path в виде пути к исполняемому файлу, например, "Indicators\MQL5Book\IndTripleEMA.ex5".
Итак, обратимся к внутреннему устройству класса Selector.
class Selector
|
В конструкторе мы раскладываем запрос selector на отдельные составляющие и сохраняем их в массиве path. Текущий компонент пути, который проверяется на соответствие в шаблоне, задается переменной cursor. В начале поиска мы находимся в корневом контейнере (рассматриваем tpl-файл целиком), и cursor равен 0. По мере нахождения совпадений cursor должен будет увеличиваться (см. метод accept ниже).
В классе перегружен оператор [], с помощью которого можно получить i-й фрагмент пути. Здесь также учитывается, что во фрагменте, в квадратных скобках, может быть задана пара "[ключ=значение]".
string operator[](int i) const
|
Метод accept проверяет, совпадает ли название элемента (tag) и его свойства (properties) с теми данными, что указаны в пути селектора для текущей позиции курсора. Запись this[cursor] использует вышеприведенную перегрузку оператора [].
bool accept(const string tag, MapArray<string,string> &properties)
|
Метод вернет false, если название тега не совпадает с текущим фрагментом пути, а также если во фрагменте было указано значение какого-либо параметра и оно не равно или отсутствует в массиве properties. В остальных случаях мы получим совпадение условий, в результате чего курсор будет продвинут вперед (cursor++), а метод вернет true.
Процесс поиска будет успешно завершен, когда курсор достигнет последнего фрагмента в запросе, поэтому нам нужен метод для определения этого момента — isComplete.
bool isComplete() const
|
Также во время анализа шаблона могут возникать ситуации, когда мы прошли по иерархии контейнеров часть пути (то есть нашли несколько совпадений), после чего очередной фрагмент запроса не совпал. В этом случае требуется "вернуться" на предыдущие уровни запроса, для чего реализован метод unwind.
bool unwind()
|
Теперь все готово для организации поиска в иерархии контейнеров (которые мы получаем после чтения tpl-файла) с помощью объекта Selector. Все необходимые действия будет выполнять метод find в классе Container. Он принимает в качестве входного параметра объект Selector и рекурсивно вызывает сам себя, пока имеются совпадения согласно методу Selector::accept. Достижение конца запроса означает успех, и метод find вернет текущий контейнер в вызывающий код.
Container *find(Selector *selector)
|
Обратите внимание, что по мере продвижения по дереву объектов метод find выводит в журнал название тега текущего объекта и количество вложенных объектов, причем делает это с отступом, пропорциональным уровню вложенности объектов. Если элемент подходит под запрос, запись в журнале дополняется словом "accepted".
Также важно отметить, что данная реализация возвращает первый подходящий элемент и не продолжает поиск других кандидатов, а в принципе это может быть полезно для шаблонов, потому что в них часто встречается несколько однотипных тегов внутри одного контейнера. Например, в окне может быть много объектов, и MQL-программа может быть заинтересована в анализе всего списка объектов. Этот аспект предлагается доработать факультативно.
Для упрощения вызова поиска добавлен одноименный метод, принимающий строковый параметр и создающий объект Selector локально.
Container *find(const string selector)
|
Поскольку мы собираемся редактировать шаблон, следует предусмотреть методы модификации контейнера, в частности, для добавления пары "ключ=значение" и нового вложенного контейнера с заданным тегом.
void assign(const string key, const string value)
|
А после редактирования необходимо будет записать содержимое контейнеров обратно в файл (тот же самый или другой). Вспомогательный метод save сохраняет объект в описанном выше tpl-формате: начинает с открывающего тега "<tag>", продолжает выгрузкой всех свойств "ключ=значение" и вызывает save у вложенных объектов, после чего завершает закрывающим тегом "</tag>". Дескриптор файла для сохранения передается в параметре.
bool save(const int h)
|
Высокоуровневый метод записи всего шаблона в файл называется write. Его входной параметр (дескриптор файла) может быть равен 0, что означает запись в тот же файл, откуда производилось чтение. Однако файл для этого должен быть открыт с правами на запись.
Важно отметить, что при перезаписи текстового Unicode-файла MQL5 не пишет начальную метку UTF (так называемый BOM, Byte Order Mark), в связи с чем мы вынуждены делать это сами. В противном случае (без метки), терминал не станет читать и применять наш шаблон.
Если вызывающий код передаст в параметре h другой файл, открытый исключительно на запись в формате Unicode, MQL5 запишет BOM автоматически.
bool write(int h = 0)
|
Для демонстрации возможностей новых классов рассмотрим задачу скрытия окна конкретного индикатора. Как известно, пользователь может этого добиться, сбросив флаги видимости для таймфреймов в диалоге свойств индикатора (закладка Отображение). Программным способом сделать это напрямую не получится. Здесь и приходит на помощь возможность редактировать шаблон.
В шаблоне видимость индикатора для таймфреймов указывается в контейнере <indicator>, внутри которого для каждого видимого таймфрейма записывается свой контейнер <period>. Например, видимость на таймфрейме M15 выглядит так:
<period>
|
Внутри контейнера <period> используются свойства period_type и period_size. period_type — это единица измерения, одна из следующих:
- 0 — минуты;
- 1 — часы;
- 2 — недели;
- 3 — месяцы;
period_size — это количество единиц измерения в таймфрейме. Следует отметить, что дневной таймфрейм обозначается, как 24 часа.
Когда в контейнере <indicator> нет ни одного вложенного контейнера <period>, индикатор выводится на всех таймфреймах.
К книге прилагается скрипт ChartTemplate.mq5, который добавляет на график индикатор Momentum (если он еще отсутствует) и делает его видимым на единственном месячном таймфрейме.
void OnStart()
|
Далее сохраняем шаблон текущего графика в файл, который затем открываем на запись и чтение. Можно было бы выделить под запись отдельный файл.
const string filename = _Symbol + "-" + PeriodToString(_Period) + "-momentum-rw";
|
Получив дескриптор файла, создаем корневой контейнер main и читаем в него весь файл (вложенные контейнеры и все их свойства будут прочитаны автоматически).
Container main(handle);
|
Затем определим селектор для поиска индикатора Momentum. В принципе, более строгий подход потребовал бы также проверять заданный период (14), но в наших классах не реализована поддержка запроса нескольких свойств одновременно (эта возможность оставлена для самостоятельной проработки).
С помощью селектора выполняем поиск, распечатываем найденный объект (просто для справки) и добавляем в него вложенный контейнер <period> с настройками для отображения месячного таймфрейма.
Container *found = main.find("/chart/window/indicator[name=Momentum]");
|
Наконец, записываем модифицированный шаблон в тот же файл, закрываем его и применяем на графике.
main.write(); // или main.write(writer);
|
При запуске скрипта на чистом графике увидим такие записи в жрунале.
ChartSaveTemplate(0,/Files/+filename)=true / ok
|
Здесь видно, что прежде чем найти нужный индикатор (с пометкой "accepted"), алгоритм нашел индикатор в предыдущем, основном окне, но он не подошел, потому что его имя не равно искомому "Momentum".
Если теперь открыть список индикаторов на графике, там будет Momentum, а в его диалоге свойств, на закладке Отображение — единственный включенный таймфрейм Месяц.
К книге прилагается расширенная версия файла TplFileFull.mqh, которая поддерживает разные операций сравнения в условиях отбора тегов и их множественную выборку в массивы. Пример использования можно посмотреть в скрипте ChartUnfix.mq5, снимающем фиксацию размеров всех подокон графика.