Тимур Машнин
Продвинутое использование торговой платформы MetaTrader 5. Создание индикаторов и торговых роботов на MQL5 и Python. Издание 3-е, исправленное и дополненное
Исходный код
Исходный код к этой книге можно посмотреть и скачать по адресу https://github.com/novts/MetaTrader-5-Creating-Trading-Robots-and-Indicators-with-MQL5
Введение
Надеюсь, вы все уже прочитали справочник MQL5 на сайте https://www.mql5.com/ru/docs.
Здесь мы не будем пересказывать этот документ, а сосредоточимся на его практическом использовании. Мы будем лишь позволять себе изредка только его цитирование.
Как сказано в предисловии к справочнику:
Для выполнения конкретных задач по автоматизации торговых операций MQL5-программы разделены на четыре специализированных типа.
И далее идет перечисление: Советник, Пользовательский индикатор, Скрипт, Библиотека и Включаемый файл.
Скрипты используются для выполнения одноразовых действий, обрабатывая только событие своего запуска, и поэтому не будут нам здесь интересны.
Также нам не будут интересны библиотеки, так как использование включаемых файлов более предпочтительно для уменьшения накладных расходов.
Поэтому мы сосредоточимся на создании советников и индикаторов с использованием включаемых файлов. Такова наша цель применения языка программирования MQL5, синтаксис которого, конечно, интересен, но будет нам только в помощь.
На самом деле программирование на языке MQL5 представляет собой яркий пример событийно-ориентированного программирования, так как весь код MQL5-приложения построен на переопределении функций обратного вызова – обработчиков событий клиентского терминала и пользователя.
А уже в коде функций обратного вызова можно использовать либо процедурное программирование, либо объектно-ориентированное программирование. Здесь мы рассмотрим оба этих подхода.
Начало работы
Для начала работы выберем какого-нибудь посредника, чтобы подключиться к его серверу и получать реальные котировки рынка для разработки и тестирования наших MQL5 приложений.
Под посредником мы имеем в виду торгового представителя, юридическое лицо, профессионального участника рынка, имеющего право совершать операции на рынке по поручению клиента и за его счёт или от своего имени и за счёт клиента на основании возмездных договоров с клиентом.
Теперь, что такое рынок?
Существуют разные типы рынков.
Это валютный рынок, это фондовый рынок или рынок ценных бумаг, это товарный рынок, и это рынок фьючерсов и опционов.
Мы с вами сосредоточимся на валютном рынке или рынке форекс.
Что такое рынок форекс?
FOREX – это сокращение от двух слов Foreign Exchange, что означает Валютный Обмен.
В отличие от других рынков, где торговля происходит на биржах, рынок форекс – это внебиржевой рынок межбанковского обмена валюты без какой-либо централизованной площадки.
Участники рынка форекс – это центральные банки, коммерческие банки, инвестиционные банки, брокеры и дилеры, пенсионные фонды, страховые компании, транснациональные корпорации и т. д.
Реально, большая часть сделок по обмену одних валют на другие происходит на ВНЕБИРЖЕВОМ рынке между крупными международными банками с использованием межбанковского информационно-торгового терминала.
И торговля идет на очень большие суммы. Минимальным лотом является сумма в 1 миллион долларов или евро, стандартным – 5 или 10 миллионов долларов.
Такая торговля валютами обеспечивает в первую очередь экспортно-импортные операции клиентов банков, и во вторую, интересы собственных торгово-инвестиционных отделов международных банков.
И совершают банки сделки как на межбанковском внебиржевом рынке, так и на валютных биржах.
Откуда берутся котировки на рынке Форекс?
Если взять, например, фондовый рынок, то там есть специальное учреждение – биржа, где торгуются определённые ценные бумаги (только там и нигде больше), и эта самая биржа и выступает единым центром распространения котировок остальным участникам, в том числе дилинговым центрам.
В случае с Форексом такого центра не существует, рынок не имеет единого места торговли и объединяет всех участников посредством современных средств передачи данных.
Поскольку основной объем торговых операций осуществляется через банковские учреждения, рынок Форекс называют международным межбанковским рынком.
Все крупнейшие участники данного рынка, международные банки, осуществляют котирование и выступают своего рода «двигателями рынка», совершая сделки либо с другими банками, либо с клиентами – инвестиционными фондами, компаниями, физическими лицами.
Все остальные участники рынка Forex запрашивают у них котировки и проводят по ним свои операции.
Выставление котировок по валютным парам международные банки производят, как правило, в электронном режиме.
И котировки формируются как на основе запросов других участников, так и в потоковом режиме (индикативном), когда банк выставляет «справочный» курс, по которому он готов совершить сделку, однако не обязан будет это делать.
Окончательная цена сделки зависит от суммы сделки, статуса участника, текущего положения на рынке и других факторов.
Индикативные и реальные котировки поступают в глобальные информационные системы (Reuters, Bloomberg, Dow Jones и др.), откуда их получают другие пользователи, в том числе и дилинговые центры.
Именно котировки, полученные от обслуживающего дилингового центра, видит трейдер в своем торговом терминале, который он использует в процессе торговли.
Таким образом, если сравнивать Форекс с биржевым рынком, то здесь отсутствует цена, единая для всех без исключения участников.
Зачастую операции совершаются по разным котировкам, причем цена будет более выгодной для второстепенных участников, имеющих налаженные контакты с основными участниками – банками, а также участников, торгующих большими объемами валюты.
В то же время, благодаря высокой ликвидности рынка котировки в большинстве случаев различаются только на 1-2 пункта, что делает практически невозможным пространственный арбитраж, когда участник покупает валюту у одного продавца по какой-либо цене, зная, что он сможет в тот же момент продать её другому покупателю на более выгодных условиях.
Теперь, что такое дилинговый центр форекс?
Дилинговый центр – это небанковская организация, обеспечивающая возможность клиентам с небольшими суммами торгового капитала на условиях маржинальной торговли заключать спекулятивные сделки.
И естественно, перед передачей котировок своим клиентам, дилинговый центр накладывает на них собственный фильтр, включающий, помимо прочего, спред, который будет составлять его заработок.
Теперь, таким образом, дилинговый центр обеспечивает возможность клиентам с небольшими суммами торгового капитала на условиях маржинальной торговли заключать спекулятивные сделки.
Как объясняют сами дилинговые центры, они отправляют на реальный внебиржевой рынок не все клиентские ордера, а только их агрегированную составляющую, превышающую определенный размер. А остальные ордера дилинговый центр сводит с противоположными ордерами, полученными от других клиентов.
На самом деле, как правило, ни один дилинговый центр практически никогда не выводит «сделки» своих клиентов на открытый рынок, потому как знает, что условия игры таковы, что клиент рано или поздно проиграет. Следовательно, выводить сделки на рынок нет никакой надобности.
Таким образом клиент или трейдер торгует не против рынка, а против дилингового центра.
В начале мы сказали, что нам нужен какой-нибудь посредник, чтобы подключиться к его серверу и получать реальные котировки рынка для разработки и тестирования наших MQL5 приложений.
Так как у нас нет миллионов долларов, чтобы непосредственно торговать на Форексе, и мы не можем себе позволить установить свой межбанковский информационно-торговый терминал, в качестве посредника выберем дилинговый центр.
Давайте выберем, например, дилинговый центр Forex Club.
Я не являюсь фанатом данной компании, это просто для нашего кодирования.
Для реальной торговли лучше выбрать, наверное, какой-нибудь банк.
Зарегистрируемся и создадим демо-счет для платформы MetaTrader 5.
Forex Club предлагает два типа счетов:
Немедленное исполнение (Instant Execution)
В этом режиме исполнение рыночного ордера осуществляется по предложенной цене. При отправке запроса на исполнение, платформа автоматически подставляет в ордер текущие цены.
И исполнение по рынку (Market Execution)
В этом режиме исполнения рыночного ордера решение о цене исполнения принимает дилинговый центр без дополнительного согласования с трейдером.
Мы откроем счет – немедленное исполнение (Instant Execution).
Далее скачаем и установим платформу MetaTrader 5.
И подключимся к серверу Forex Club, используя логин и пароль демо счета.
Далее, нажав правой кнопкой мышки на графике и зайдя в свойства, настроим внешний вид графика, как вам нравится.
Мультирыночная платформа MetaTrader 5 позволяет совершать торговые операции на Forex, фондовых биржах и фьючерсами.
С помощью MetaTrader 5 можно также проводить технический анализ котировок, работать с торговыми роботами и копировать сделки других трейдеров.
Более подробно про платформу MetaTrader 5 и про ее интерфейс можно почитать в соответствующей справке.
Мы не будем пересказывать эту справку, так как это было бы слишком нагло брать деньги за книгу, в которой пересказывается общедоступная справка.
Также помимо терминала MetaTrader 5, нас интересует редактор MQL5, который можно открыть либо с помощью ярлыка, либо в меню Сервис терминала MetaTrader 5.
MetaEditor – это современная среда разработки торговых стратегий, интегрированная с платформой MetaTrader.
С помощью MetaEditor можно создавать торговых роботов, технические индикаторы, скрипты, графические панели управления и многое другое.
Для редактора MetaEditor также есть подробная справка, которую мы также не будем пересказывать.
Мы лучше сразу займемся практическим кодированием.
Общая структура индикатора
Для создания основы пользовательского индикатора используем редактор MetaEditor.
Нажмем кнопку меню Создать и в окне мастера выберем Пользовательский индикатор.
Нажмем Далее, введем имя создаваемого индикатора, нажмем Далее и отметим функции, которые мастер должен сгенерировать и в следующем окне нажмем Готово.
В результате будет создан код основы индикатора.
Код индикатора начинается с блока объявления свойств индикатора и различных объектов, используемых индикатором, таких как массивы буферов индикатора, параметры ввода, глобальные переменные, хэндлы используемых технических индикаторов, константы.
Данный блок кода выполняется приложением Торговая Платформа MetaTrader 5 сразу при присоединении индикатора к графику символа.
После блока объявления свойств индикатора, его параметров и переменных, идет описание функций обратного вызова, которые терминал вызывает при наступлении таких событий, как инициализация индикатора после его загрузки, перед деинициализацией индикатора, при изменении ценовых данных, при изменении графика символа пользователем.
Для обработки вышеуказанных событий необходимо описать такие функции как OnInit(), OnDeinit(), OnCalculate() и OnChartEvent().
В функции OnInit() индикатора, как правило, объявленные в начальном блоке массивы связываются с буферами индикатора, определяя его выводимые значения, задаются цвета индикатора, точность отображения значений индикатора, его подписи и другие параметры отображения индикатора. Кроме того, в функции OnInit() индикатора могут получаться хэндлы используемых технических индикаторов и рассчитываться другие используемые переменные.
В функции OnDeinit() индикатора, как правило, с графика символа удаляются графические объекты индикатора, а также удаляются хэндлы используемых технических индикаторов.
В функции OnCalculate() собственно и производится расчет значений индикатора, заполняя ими объявленные в начальном блоке массивы, которые в функции OnInit() индикатора были связаны с буферами индикатора, данные из которых берутся терминалом для отрисовки индикатора. Кроме того, в функции OnCalculate() могут изменяться цвета индикатора и другие параметры его отображения.
В функции OnChartEvent() могут обрабатываться события, генерируемые другими индикаторами на графике, а также удаление пользователем графического объекта индикатора и другие события, возникающие при работе пользователя с графиком.
На этом код индикатора заканчивается, хотя там могут быть также определены пользовательские функции, которые вызываются из функций обратного вызова OnInit(), OnDeinit(), OnCalculate() и OnChartEvent().
Для компиляции нашего индикатора нажмем кнопку Компилировать редактора, при этом в нижнем окне отобразится результат компиляции.
После компиляции наш индикатор автоматически появится в торговом терминале, и мы сможем присоединить его к графику финансового инструмента.
Свойства индикатора
Давайте более подробно рассмотрим свойства индикатора.
Цитата из справочника:
Свойства программ (#property). У каждой mql5-программы можно указать дополнительные специфические параметры #property, которые помогают клиентскому терминалу правильно обслуживать программы без необходимости их явного запуска. В первую очередь это касается внешних настроек индикаторов. Свойства, описанные во включаемых файлах, полностью игнорируются. Свойства необходимо задавать в главном mq5-файле: #property идентификатор значение.
Включаемый файл указывается с помощью ключевого слова #include, после которого следует путь к включаемому файлу.
Включаемый файл – это часто используемый блок кода. Такие файлы могут включаться в исходные тексты экспертов, скриптов, пользовательских индикаторов и библиотек на этапе компиляции. Использование включаемых файлов более предпочтительно, чем использование библиотек, из-за дополнительных накладных расходов при вызове библиотечных функций.
Включаемые файлы могут находиться в той же директории, что и исходный файл, в этом случае используется директива #include с двойными кавычками. Другое место хранения включаемых файлов – в директории <каталог_терминала>\MQL5\Include, в этом случае используется директива #include с угловыми скобками.
В качестве первого свойства индикатора, как правило, указывается имя разработчика, например:
#property copyright
Далее указывается ссылка на сайт разработчика:
#property link
После этого идет описание индикатора, каждая строка которого обозначается с помощью идентификатора description, например:
#property description "Average Directional Movement Index"
Далее указывается версия индикатора:
#property version "1.00"
На этом, как правило, объявление общих свойств индикатора заканчивается.
Индикатор может появляться в окне терминала двумя способами – на графике символа или в отдельном окне под графиком символа.
Свойство:
#property indicator_chart_window
Определяет отрисовку индикатора на графике символа.
А свойство:
#property indicator_separate_window
Определяет вывод индикатора в отдельное окно.
Одно из самых важных свойств индикатора – это количество буферов для расчета индикатора, например:
#property indicator_buffers 6
Данное свойство тесно связано с двумя другими свойствами индикатора – количеством графических построений и видом графических построений.
Количество графических построений – это количество цветных диаграмм, составляющих индикатор.
Например, для индикатора ADX:
#property indicator_plots 3
Индикатор состоит из трех диаграмм (линий) – индикатора направленности +DI, индикатора направленности –DI и самого индикатора ADX.
Вид графических построений – это та графическая форма, из которой составляется график индикатора.
Например, для индикатора ADX:
#property indicator_type1 DRAW_LINE
#property indicator_type2 DRAW_LINE
#property indicator_type3 DRAW_LINE
Таким образом, каждая диаграмма индикатора ADX – это линия.
Графическая форма сопоставляется с графическим построением с помощью номера графического построения, следующего после indicator_type.
Цвет каждого графического построения индикатора задается свойством indicator_colorN.
Например, для индикатора ADX:
#property indicator_color1 LightSeaGreen
#property indicator_color2 YellowGreen
#property indicator_color3 Wheat
Цвет сопоставляется с графическим построением с помощью номера графического построения, следующего после indicator_color.
В справочнике MQL5 есть таблица Web-цветов для определения цвета графического построения.
Вернемся теперь к количеству буферов для расчета индикатора.
Так как данные для построения каждой диаграммы индикатора берутся из своего буфера индикатора, количество заявленных буферов индикатора не может быть меньше, чем заявленное число графических построений индикатора.
Сразу же возникает вопрос, каким образом конкретный массив, представляющий буфер индикатора, сопоставляется с конкретным графическим построением индикатора.
Делается это в функции обратного вызова OnInit() с помощью вызова функции SetIndexBuffer.
Например, для индикатора ADX:
SetIndexBuffer(0,ExtADXBuffer);
SetIndexBuffer(1,ExtPDIBuffer);
SetIndexBuffer(2,ExtNDIBuffer);
Где первый аргумент, это номер графического построения.
Таким образом, массив связывается с диаграммой индикатора, а диаграмма связывается с ее формой и цветом.
Однако с буферами индикатора все немного сложнее.
Их количество может быть заявлено больше, чем количество графических построений индикатора.
Что это означает?
Это означает, что некоторые массивы, представляющие буфера индикатора, используются не для построения диаграмм индикатора, а для промежуточных вычислений.
Например, для индикатора ADX:
SetIndexBuffer(3,ExtPDBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(4,ExtNDBuffer,INDICATOR_CALCULATIONS);
SetIndexBuffer(5,ExtTmpBuffer,INDICATOR_CALCULATIONS);
Такой массив определяется с помощью третьего параметра INDICATOR_CALCULATIONS.
Это дает следующее:
Все дело в частичном заполнении массива.
Если массив, указанный в функции SetIndexBuffer, является динамическим, т.е. объявлен без указания размера, но он привязан к буферу индикатора с помощью функции SetIndexBuffer, клиентский терминал сам заботится о том, чтобы размер такого массива соответствовал ценовой истории.
Рассмотрим это на примере индикатора ADX.
В редакторе MQL5, в окне Navigator (Навигатор), в разделе Indicators->Examples выберем и откроем исходный код индикатора ADX.
В функции OnInit() закомментируем строку:
// SetIndexBuffer(5,ExtTmpBuffer,INDICATOR_CALCULATIONS);
Теперь массив ExtTmpBuffer является просто динамическим массивом.
Откомпилируем код индикатора и присоединим индикатор к графику в терминале MetaTrader 5.
В результате Терминал выдаст ошибку.
array out of range
Это произошло потому, что мы перед заполнением данного массива значениями не указали его размера и не зарезервировали под него память.
Так что его размер был равен нулю, когда мы попытались в него что-то записать.
Статическим мы этот массив сделать тоже не можем, т.е. объявить его сразу с указанием размера, так как значения такого промежуточного массива рассчитываются в функции обратного вызова OnCalculate на основе загруженной в функцию OnCalculate истории цен, а именно массивов open[], high[], low[], и close[].
Но точный размер массивов open[], high[], low[], и close[] неизвестен, он обозначается лишь переменной rates_total.
Хорошо, но мы можем в функции OnCalculate применить функцию ArrayResize, чтобы установить размер массива:
ArrayResize(ExtTmpBuffer,rates_total);
Передав в функцию в качестве аргумента переменную rates_total – количество баров на графике, на котором запущен индикатор.
Теперь после компиляции индикатор заработает как надо.
Но дело в том, что в функции OnCalculate мы сначала рассчитываем индикатор для всей ценовой истории, т.е. для rates_total значений, а затем при поступлении нового тика по символу индикатора, и соответственно вызове функции OnCalculate, мы рассчитываем значение индикатора для этого нового тика по символу и записываем новое значение индикатора в его массив буфера.
Чтобы это реализовать с промежуточным массивом, нужно внимательно следить за его размером и записывать новое значение в конец массива.
Вместо всего этого, проще всего привязать промежуточный массив к буферу индикатора с помощью функции SetIndexBuffer и таким образом решить все эти проблемы.
Аналогичная ситуация возникает, когда значения таких промежуточных массивов заполняются с помощью функции CopyBuffer, когда мы строим пользовательский индикатор на основе других индикаторов.
Функция CopyBuffer распределяет размер принимающего массива под размер копируемых данных.
Если копируется вся ценовая история, то проблем нет и в этом случае использовать INDICATOR_CALCULATIONS необязательно.
Если же мы хотим скопировать только одно новое поступившее значение, функция CopyBuffer определит размер принимающего массива как 1, и нужно будет использовать этот принимающий массив как еще один массив-посредник, из которого уже записывать значение в промежуточный массив индикатора. И в этом случае просто функцией ArrayResize для принимающего массива проблему не решить.
Теперь что нам делать, если мы хотим раскрашивать наши диаграммы индикатора в разные цвета в зависимости от цены?
Во-первых, мы должны указать, что наша графическая форма нашего графического построения является цветной, например:
#property indicator_type1 DRAW_COLOR_LINE
В идентификатор геометрической формы добавляется слово COLOR.
Далее значение свойства #property indicator_buffers увеличивается на единицу и объявляется еще один массив для хранения цвета.
Функцией SetIndexBuffer объявленный дополнительный массив сопоставляется с буфером цвета индикатора, например:
SetIndexBuffer(1,ExtColorsBuffer,INDICATOR_COLOR_INDEX);
В свойстве #property indicator_color, раскрашиваемого графического построения, указывается несколько цветов, например:
#property indicator_color1 Red,Green,Blue
И, наконец, каждому элементу массива, представляющего буфер цвета индикатора, присваивается номер цвета, определенный в свойстве #property indicator_color.