Шрифт
Как написать для Маркета индикатор любых нестандартных графиков
Play

Как написать для Маркета индикатор любых нестандартных графиков

На сегодняшний день самый популярный вид графиков среди трейдеров — японские свечи, которые помогают легко оценить текущую ситуацию на рынке. Свечные графики дают хорошее и наглядное представление о развитии цены в течение промежутка времени, охватываемого одной свечой. Но некоторые трейдеры считают недостатком то, что графики несут в себе компонент времени, и предпочитают иметь дело только с изменением цены. Так появились графики "Крестики-Нолики", "Ренко", "Каги", "Range bars", эквиобъемные графики и т.п.

С помощью оффлайновых графиков, программирования на языке MQL4 и небольшого опыта все эти графики вы можете получить в MetaTrader 4. Есть возможность создавать графики с собственными синтетическими инструментами (которых нет у брокера или даже вовсе в природе) и нестандартными таймфреймами, которых нет в платформе. Большинство разработчиков используют для этого вызовы DLL и сложные схемы. В этой статье мы расскажем, как создать индикаторы вида "два в одном" любой сложности, которые не только не требуют знаний DLL, но и могут быть легко опубликованы в Маркете в виде продукта, поскольку являются полностью независимыми и законченными приложениями.

Примеры из этой статьи вы сможете найти и скачать в виде бесплатных приложений Маркета:

    - индикатор "USDx Chart MT4" строит автономный график, на котором вместо привычных баров и свечей рисуется индекс доллара. - индикатор Renko Chart MT4 создает автономный график Renko, на котором все бары имеют вид Renko "кирпичей". Сами "кирпичи" не имеют теней, а размер "кирпича" задается в настройках.

Индикатору, который создаёт автономный график нестандартного символа и/или периода нет необходимости вызывать DLL — всё решается средствами MQL4. Для этого нужно принять следующую схему работы: один и тот же индикатор может работать как на онлайн-графике, так и на оффлайновом графике. При этом индикатор будет менять свой функционал в зависимости от того, где он работает: на онлайн-графике или в автономном режиме.

На онлайн-графике индикатор работает в режиме "обслуживание": собирает и компонует котировки, создаёт автономный график (как стандартного, так и нестандартного периода), осуществляет его обновление. На автономном графике такой индикатор работает так же, как и любой другой — анализирует котировки и строит различные объекты и фигуры. Отметим, что все примеры в статье базируются на новом штатном скрипте PeriodConverter.mq4 .

1. Индикатор "IndCreateOffline", который будет создавать автономный график

Назовем индикатор IndCreateOffline. В индикаторе будет только один входной параметр, отвечающий за период автономного графика. Поговорим про него немного ниже. Индикатор IndCreateOffline будет выполнять только одну задачу — создавать файл *.hst и открывать автономный график.

В индикаторе будут использоваться две основные функции. Первая используется один раз — в ней создаётся сам файл *.hst и оформляется заголовок файла. Вторая функция служит для записи котировок в файл *.hst.

1.1. Редактирование "шапки" индикатора

Даже если это лень делать сразу, все же нужно вначале добавить описание файла. По прошествии определенного времени оно поможет вспомнить, каково назначение нашего индикатора.

Конечно, теоретически автономный график можно создать с любым периодом. Однако мы ограничим буйную трейдерскую фантазию на случай, если кто-то вдруг заинтересуется периодом эдак 10000000. Введем перечисление, в котором можно выбрать всего четыре варианта — две, три, четыре или шесть минут:

Следующий шаг — добавление в шапку глобальных переменных (не путайте их с глобальными переменными терминала):

1.2. Контроль типа графика

Наш индикатор IndCreateOffline должен запускаться только на онлайн-графике, потому что только так можно обеспечить корректность данных. Определить тип графика, на котором установлен индикатор, можно при помощи свойства CHART_IS_OFFLINE. Добавим после OnCalculate() функцию IsOffline, которая будет возвращать тип графика:

Вызов функции IsOffline будем делать в OnInit():

Обратите внимание, что если индикатор находится на онлайн-графике (IsOffline(ChartID())==false), период которого не равен PERIOD_M1, то в таком случае переменной crash присваивается значение true. Что это даёт: при crash==true индикатор будет оставаться на онлайн-графике, но не будет ничего делать. В этом случае мы получим во вкладке "Эксперты" такое сообщение:

Однако сам индикатор будет оставаться на онлайн-графике и будет ожидать, пока пользователь сменит период на PERIOD_M1.

Почему нам так важен именно период PERIOD_M1 ? Здесь действуют два важных момента.

Момент 1: формирование итогового периода автономного графика. Рассмотрим пример скрипта PeriodConverter.mq4, где высчитывается итоговый период автономного графика :

При таких входных параметрах период онлайн-графика, на который прикрепляется скрипт, равен "PERIOD_M3". При значении InpPeriodMultiplier=3 мы ожидаем получения автономного графика с периодом 3. Однако на самом деле мы получим период автономного графика, равный 9:

Таким образом, чтобы получить период 3, нужно использовать онлайн-график с периодом PERIOD_M1.

Момент 2: запись истории в файл. При формировании файла истории используются данные из массивов-таймсерий Open[], Low[], High[], Volume[], Time[]. Все они используют данные текущего графика по текущему периоду. А что может быть точнее, чем формирование любого искусственного периода на основании данных с графика с периодом "PERIOD_M1"? Правильно: только график с периодом PERIOD_M1.

Изменения индикатора, описанные выше, можно увидеть в файле IndCreateOfflineStep1.mq4 .

1.3. Функция создания заголовка файла истории

Функцию, отвечающую за создание заголовка файла истории, назовём CreateHeader():

offline_period

[in] Период автономного графика.

Возвращаемое значение

true, если файл истории был удачно создан, и false — в случае ошибки.

Полный листинг функции:

Кроме создания самого файла истории "*.hst" и создания заголовка файла (несколько первых служебных строчек), в функции CreateHeader() запоминается хэндл созданного файла в переменную HandleHistory.

1.4. Первая запись истории в *.hst файл

После создания файла *.hst и заполнения его заголовка нужно провести первую запись истории котировок в файл — то есть, нужно заполнить файл всей историей, актуальной на данный момент. За первую запись истории в файл будет отвечать функция FirstWriteHistory() (ее можно просмотреть в индикаторе).

Когда возникает ситуация "первая запись истории" в нашем индикаторе? Логично предположить, что это происходит при первой загрузке индикатора.

Первую загрузку можно (и нужно) контролировать в индикаторе по значению переменной prev_calculated. Значение prev_calculated==0 говорит о том, что происходит именно первая загрузка. Но в то же время prev_calculated==0 может ещё означать, что загрузка не первая, но была подкачана история. Что делать при подкачке истории, мы обговорим при редактировании кода OnCalculate().

1.5. Запись онлайн-котировок

После создания и заполнения заголовка файл *.hst и первой записи истории можно приступать к записи онлайн-котировок. За это отвечает функция CollectTicks() (ее можно просмотреть в индикаторе).

Изменения индикатора, описанные выше, можно увидеть в файле IndCreateOfflineStep2.mq4.

1.6. Редактирование функции OnCalculate()

Введём переменную first_start. Она будет хранить значение true после первого старта. Другими словами при first_start==true мы будет знать, что наш индикатор ещё не создавал файл *.hst.

Алгоритм работы функции OnCalculate() нашего индикатора:

Рис. 1. Алгоритм функции OnCalculate()

Окончательную версию индикатора можно увидеть в файле IndCreateOffline.mq4.

2. Запуск индикатора на автономном графике

2.1. Обновление автономного графика при помощи ChartSetSymbolPeriod()

Важное замечание: для обновления автономного графика вместо ChartRedraw() нужно вызывать ChartSetSymbolPeriod() с текущими параметрами. Вызов ChartSetSymbolPeriod() происходит в функции CollectTicks() c интервалом не чаще 1 раза в 3 секунды.

Также нужно учесть один нюанс работы автономного графика: индикатор, прикреплённый на автономный график, при каждом его обновлении будет получать в своей функции OnCalculate() prev_calculated==0. Эту спефицику нужно запомнить. Ниже будет показан метод, который учитывает данную специфику.

2.2. Режим обслуживания

Что мы хотим получить: один и тот же индикатор должен работать и на обычном онлайн-графике, и на автономном графике. При этом у индикатора меняется поведение в зависимости от того, на каком графике — онлайн или автономном — он находится. Когда индикатор находится на онлайн графике, то его функционал очень похож на индикатор IndCreateOffline.mq4, который мы рассмотрели выше. А вот в автономном режиме он начинает работать как обычный индикатор.

Итак, назовём наш индикатор IndMACDDoubleDuty — он будет построен на основе рассмотренного выше IndCreateOffline.mq4 и стандартного индикатора MACD.mq4. Подготовьте, пожалуйста, черновик будущего индикатора: в MetaEditor'e откройте файл IndCreateOffline.mq4, далее меню "Файл" -> "Сохранить как. " и впишите название индикатора IndMACDDoubleDuty.mq4.

Сразу добавим описание индикатора — наш индикатор теперь создаёт автономные графики и имеет двойное назначение:

В режиме "обслуживание" индикатор будет работать, если он запущен на онлайн графике с периодом PERIOD_M1. В этом режиме он выполняет следующие функции:

  • создаёт *.hst файл и заполняет его;
  • открывает автономный график на базе *.hst файла;
  • при поступлении котировок дописывает историю в *.hst файл и обновляет автономный график.

Соответственно немного изменится OnInit():

Внесём изменения в OnCalculate():

На данном этапе индикатор, при прикреплении его на онлайн график с периодом PERIOD_M1, переходит в режим "обслуживание" и создаёт автономный график.

Изменения индикатора, описанные выше, можно увидеть в файле IndMACDDoubleDutyStep1.mq4.

2.3. Копирование индикатора на автономный график

В индикатор IndMACDDoubleDuty добавим новый функционал: теперь, находясь в режиме "обслуживание", индикатор должен передавать свою копию на созданный автономный график. В этом нам помогут такие функции: ChartSaveTemplate и ChartApplyTemplate. Теперь алгоритм OnCalcalculate() будет выглядеть так:

Рис. 2. Алгоритм функции OnCalculate()

Добавим в код OnCalculate() дополнительный функционал:

Теперь наш индикатор, при прикреплении его на онлайн график с периодом PERIOD_M1, переходит в режим "обслуживание", создаёт автономный график и копирует себя на него.

Изменения индикатора, описанные выше, можно увидеть в файле IndMACDDoubleDutyStep2.mq4.

2.4. Интеграция в MACD.mq4

Наш индикатор уже размещает сам себя на автономном графике, но пока ничего не отображает и ничего не считает. Исправим это: интегрируем наш индикатор в стандартный MACD.mq4.

Сначала вставим входные параметры индикатора MACD.mq4 в наш код:

Затем добавим код в OnInit(). Здесь нужно отметить, что инициализация параметров индикатора MACD должна проходить при любых условиях — и на автономном, и на онлайн-графике, причем даже в том случае, когда период онлайн-графика отличается от PERIOD_M1:

Следующим шагом немного изменим код в начале OnCalculate(). Если мы находимся на онлайн-графике и его период не равен PERIOD_M1 мы должны дать возможность для расчёта параметров MACD. Было так:

Дальше добавим код расчёта параметров индикатора MACD в конец OnCalculate():

2.5. Экономный пересчёт индикатора на автономном графике

Напомню нюанс, описанный в начале раздела 2:

Также нужно учесть один нюанс работы автономного графика: индикатор, прикреплённый на автономный график, при каждом его обновлении будет получать в своей функции OnCalculate() prev_calculated==0. Эту спефицику нужно запомнить. Ниже будет показан метод, который учитывает данную специфику.

И еще одно напоминание: о том, что в OnCalculate() значение prev_calculated==0 может означать две ситуации:

  1. или это первый запуск индикатора;
  2. или была подгружена история.

В обоих случаях индикатор должен пересчитать все бары на графике. Когда это нужно выполнить только один раз (при загрузке) — это нормально. А вот на автономном графике мы будем получать prev_calculated==0 при каждом обновлении (примерно раз в 2-3 секунды), и индикатор будет пересчитывать все бары. Это очень неэкономный расход ресурсов. Поэтому применим небольшую хитрость: когда индикатор находится на автономном графике, он будет хранить и сравнивать количество баров (переменная rates_total) и время самого правого бара на графике.

Шаг 1: В начале OnCalculate() объявим две статические переменные и одну псевдо-переменную:

Шаг 2: В блоке кода расчёта значений индикатора заменим переменную prev_calculated на pseudo_prev_calculated:

Шаг 3: Здесь будем вычислять значение для псевдо-переменной, если индикатор работает на автономном графике.

Теперь наш индикатор на автономном графике рассчитывает свои значения экономно. Кроме того, в этом режиме он корректно обрабатывает разрывы связи: после разрыва рассчитываются только вновь добавленные бары.

2.6. Подкачка истории. Автономный график

Осталось решить, что делать, если на онлайн-графике с нашим индикатором произошла подкачка истории (при этом prev_calculated==0). Я предлагаю решать такую ситуацию через глобальные переменные терминала. Алгоритм работы следующий: если на онлайн-графике получаем prev_calculated==0 (неважно, произошел ли первый запуск или просто подкачка истории), мы просто создаём глобальную переменную. Индикатор на автономном графике при каждом обновлении проверяет наличие глобальной переменной: если она есть (значит, на онлайн графике было prev_calculated==0), то индикатор будет пересчитан полностью и удалит глобальную переменную.

Добавим в шапку индикатора переменную, в которой будем хранить имя будущей глобальной переменной терминала, и в OnInit() генерируем это имя:

Добавим код создания глобальной переменной терминала, если выполняется условие prev_calculated==0 и индикатор находится на онлайн-графике:

И последнее изменение: проверка наличия глобальной переменной из индикатора на автономном графике:

Окончательная версия индикатора, с последними изменениями: IndMACDDoubleDuty.mq4.

3. Индикатор, отображающий Renko-бары

Renko-бары мы будем строить по той же технологии, что использовалась в пунктах 1. Индикатор "IndCreateOffline", который будет создавать автономный график и 2. Запуск индикатора на автономном графике. То есть, в итоге мы получим автономный график, но только в данном случае файл истории *.hst будет содержать бары одинакового размера — их ещё называют "кирпичами". Размер "кирпичей" задаётся в настройках индикатора и измеряется в пунктах.

Рис. 3. Renko-бары

Перед началом построения нужно учитывать несколько правил формирования файла истории *.hst для построения Renko-баров.

3.1. Правила построения Range-баров

Правило 1 : запись OHLC в файл истории *.hst должна быть корректная, особенно это касается значений High и Low. В противном случае терминал не отобразит некорректную запись. Пример корректной и некорректной записи в файл истории *.hst бычьего бара:

Рис. 4. Пример корректной и некорректной записи

Пр авило 2 : хоть мы и строим Range-бары, которые не имеют временной привязки, но сам формат файла истории *.hst требует наличия параметра time — это время начала периода. Поэтому параметр time должен обязательно записываться.

Пр авило 3 : параметр time у всех баров должен отличаться. Если записать для всех баров одинаковый параметр time, то такая запись будет некорректной, и терминал просто не отобразит график. Но есть тут и приятная особенность: параметр time допустимо записывать с разницей в 1 секунду . Например, один бар запишем с параметром time=2016.02.10 09:08: 00 , а следующий — с параметром time=2016.02.10 09:08: 01 .

3.2. Формирование "кирпичей" на исторических данных

Данный метод не претендует на звание совершенного алгоритма. Я выбрал упрощенный подход, поскольку главная задача этой статьи — показать, как формировать файл истории *.hst. В индикаторе "IndRange.mq4", при рисовании кирпичей на основании истории, анализируются значения High[i] и Low[i] текущего периода. То есть, если индикатор "IndRange.mq4" прикрепить на график с периодом M5, то индикатор "IndRange.mq4" будет при первом запуске анализировать историю по текущему периоду M5.

Конечно, при желании вы можете модернизировать алгоритм рисования на основании истории и учитывать движение цен на самом младшем таймфрейме — М1. Общая схема работы:

  • если High[i] больше, чем high предыдущего кирпича на размер кирпича, то выше предыдущего кирпича рисуется один или более кирпичей;
  • если Low[i] меньше чем low предыдущего кирпича на размер кирпича, то выше предыдущего кирпича рисуется один или более кирпичей.

Рис. 5. Предыдущий кирпич — бычий

Рис. 6. Предыдущий кирпич — медвежий

Интересный нюанс: координаты баров (Open, High, Low, Close, Time и объёмы) хранятся в структуре rate, которая объявлена в "шапке" индикатора

и эта структура при каждой записи в файл истории *.hst не обнуляется, а просто переписывается. Благодаря этому легко реализовать алгоритмы, которые представлены на рис. 5 и на рис. 6., и можно дать общую формулу:

  • когда High[i] больше чем high предыдущего кирпича (при этом неважно, бычьим или медвежьим был предыдущий кирпич) на размер кирпича, координаты для нового бара вычисляются по такой схеме:

Open = High; Low = Open; Close = Low + Renko size; High = Close

  • когда Low[i] меньше чем low предыдущего кирпича (при этом неважно, бычьим или медвежьим был предыдущий кирпич) на размер кирпича, координаты для нового бара вычисляются по такой схеме:

Low = Open; High = Open; Close = High - Renko size; Low = Close

А вот так эти формулы выглядят в индикаторе "IndRange.mq4", в функции FirstWriteHistory():

Индикатор "IndRange.mq4" всегда формирует название файла истории *.hst по следующему правилу: "Имя текущего символа"+"7"+".hst". Например для символа "EURUSD" файл истории будет иметь имя "EURUSD7.hst".

3.3. Работа индикатора онлайн

При работе индикатора онлайн нужно вместо цены High[i] анализировать цену Close[0] — цену закрытия на нулевом (самом правом) баре:

4. Индикатор, который будет создавать нестандартный символ — индекс доллара USDx

Важные замечания к алгоритму индикатора "IndUSDx.mq4": индикатор индекса доллара не претендует на абсолютную верность формулы расчёта индекса, ведь для нас сейчас главное — показать, как формировать файл истории *.hst по нестандартному символу. В итоге будет получен автономный график, а бары этого графика будут отображать рассчитанный индекс доллара. Также индикатор "IndUSDx.mq4" создаёт автономный график только один раз: или при первом присоединении индикатора к графику, или после смены периода графика.

Формула для расчёта индекса доллара и набор символов взяты на основании кода: Простой индикатор индекса доллара.

Для формулы расчёта индекса доллара нужно получить данные Time, Open и Close по символам "EURUSD", "GBPUSD", "USDCHF", "USDJPY", "AUDUSD", "USDCAD" и "NZDUSD". Для удобства сохранения и обращения к данным введена структура OHLS (она объявлена в "шапке" индикатора):

В структуре OHLS в качестве элементов выступают массивы для хранения Time, Open и Close. Под описанием структуры сразу объявлены несколько объектов — структур OHLS, в которых будут хранится данные расчётных символов:

В OnInit() происходит вызов функции SelectSymbols():

Функция SelectSymbols(), при помощи SymbolSelect, выбирает символы, которые участвуют в формуле индекса доллара, в окне MarketWatch.

В OnCalculate() при первом запуске вызывается функция CopyCloseSymbols(). Здесь происходит запрос данных по расчётным символам и заполнение структур символов:

Если в функции CopyCloseSymbols() история по символу скачана меньше, чем заданное значение, то выводится сообщение с названием символа и фактическим значением скачанной истории по символу.

В случае удачного заполнения структур произойдет вызов основного функционала — функции FirstWriteHistory(), которая наполняет историей файл *.hst:

Результат работы индикатора "IndUSDx.mq4":

Рис. 7. Индикатор " IndUSDx.mq4

Заключение

Оказалось, что и на автономных графиках, так же, как и на онлайн-графиках, можно осуществлять экономный пересчет индикаторов. Правда, для этого нужно вносить изменения в код индикатора, для учета специфики обновления автономного графика, а именно — учитывать, что при обновлении автономного графика все индикаторы получают в OnCalculate() значение prev_calculate==0.

Также в статье было показано, как формировать файл истории *.hst и за счет этого коренным образом менять параметры отображаемых баров на графике.

📎📎📎📎📎📎📎📎📎📎