Урок №11: Пишем индикатор
На этом уроке мы напишем простой индикатор, который в дальнейшем будем использовать при разработке советника.
А использовать для получения сигнала мы будем уже существующие — Moving Average и MACD. Алгоритм следующий:
Определять направление сделки (покупка или продажа) мы будем по индикатору Moving Average с периодом 100 на дневном графике(Daily), т.е. если текущая цена выше MA(100), то покупаем, а если ниже — продаём.
А для поиска удачных точек входа воспользуемся индикатором MACD.
вход в покупки– индикатор MACD ниже нуля, идет снизу вверх, а его сверху вниз пересекает сигнальная линия.
вход в продажи – индикатор MACD выше нуля, идет cверху вниз, а его снизу вверх пересекает сигнальная линия.
Запускаем MetaEditor и вызываем мастер создания программ.
Отмечаем в списке выбора «Пользовательский индикатор»:
Заполняем пока только поле «Имя», остальное без изменений и нажимаем кнопку «Далее»:
На следующей форме мастера оставляем как есть и нажимаем кнопку «Далее»:
Тут также ничего не меняем и нажимаем «Готово»:
Итак, шаблон нашего будущего индикатора готов:
В основе пользовательских индикаторов, является передача значений индикаторных массивов клиентскому терминалу (для построения индикаторных линий) через буферы обмена.
Буфер — область памяти, содержащая численные значения индикаторного массива.
Стандартом языка MQL4 предусмотрена возможность построения с помощью одного пользовательского индикатора до восьми индикаторных линий. Каждой из индикаторных линий ставится в соответствие один индикаторный массив и один буфер. Каждый из буферов имеет свой индекс. Индекс первого буфера — 0, второго — 1, и т.д., а последнего — 7.
Нам необходимо два буфера для хранения и отображения на графике сигналов для покупок и для продаж, поэтому определим их:
а кроме того, давайте определим два имени «BUY» и «SELL», присвоив им значения «0» и «1» соответственно.
В дальнейшем это облегчит нам восприятие исходного текста индикатора:
Далее в функции OnInit():
С помощью функции SetIndexBuffer() необходимый буфер (рассмотрим случай с индексом 0) ставится в соответствие массиву (в нашем случае «Buy»). Это значит, что для построения первой стрелки клиентский терминал будет принимать данные, заключённые в массиве «Buy», используя для этого нулевой буфер и соответственно для буфера «Sell» — первый буфер.
Функция SetIndexEmptyValue() устанавливает значения «по умолчанию», т.е. те, при которых индикатор не будет ничего показывать на графике, в нашем случае это ноль.
Функция SetIndexStyle() у станавливает новый тип, стиль, ширину и цвет для указанной линии индикатора. Для нашего же случае это стрелки (DRAW_ARROW).
Функция SetIndexArrow() — у станавливает значок для линии индикаторов, имеющей стиль DRAW_ARROW. В качестве параметров в функцию передаётся номер буфера и код символа из шрифта Wingdings.
Функция IndicatorDigits — определяет формат точности (количество знаков после десятичной точки) для визуализации значений индикатора.
Функция IndicatorShortName — устанавливает «короткое» имя пользовательского индикатора для отображения в подокне индикатора.
Функция SetIndexLabel — устанавливает текст описания линии индикатора для отображения информации в окне.
С этим разобрались и далее я предлагаю написать функцию, которая будет определять наличие сигнала на определённом баре. Таким образом в качестве параметра функции мы будем передавать индекс расчитываемого бара.
При получении показаний с индикатора в качестве параметров я намеренно указал переменные (fast_ema_period, slow_ema_period, signal_period и т.д.), а не вписал жёсткие значения. Это позволит нам гибко подбирать параметры при тестировании будущего советника. Следовательно эти параметры необходимо определить и сделать их внешними:
Дополнительно в этом блоке определена переменная MACDOpenLevel. Она нам понадобится для фильтрации от ложных сигналов.
Собственно, остаётся написать небольшой код в функции OnCalculate():
Если раньше индикатор не был присоединён к окну финансового инструмента, то при первом исполнении функции OnCalculate значение переменной counted_bars будет равно нулю:
Это означает, что индикаторный массив не содержит ни одного элемента с ранее определённым значением, поэтому есть необходимость пересчитать весь индикаторный массив от начала до конца. Расчёт массива производится в направлении от самого старого бара к нулевому. Индекс самого старого бара, начиная с которого необходимо производить вычисления, рассчитывается так:
Предположим, что в момент запуска в окне финансового инструмента имеется 200 баров. Эта величина является значением предопределённой переменной Bars. Ранее определённое значение counted_bars равно 0. В результате получится, что индекс i первого не посчитанного бара (самого старого бара, начиная с которого необходимо начать расчёт) равен 199.
Далее в цикле мы проверяем значения индикатора через функцию Signal() и при наличии сигнала записываем в буфер индикатора значение цены текущего пересчитанного бара:
Именно так и получаются сигнальные стрелки на графике самого индикатора:
И полный текст индикатора:
На этом, пожалуй, все. На одном из следующих уроков мы рассмотрим советник на нашем индикаторе.
Исходный текст нашего индикатора доступен для скачивания.
Язык MQL4 для «чайников». Пользовательские индикаторы (часть 1)
Введение
Это четвертая статья из цикла «Язык MQL4 для ‘чайников'». Сегодня мы будем учиться писать пользовательские индикаторы. Мы изучим классификацию свойств индикаторов, посмотрим, как эти свойства влияют на сам индикатор, узнаем про новые функции и оптимизацию, и наконец-то напишем несколько своих индикаторов. Кроме того, в конце статьи вас ждут советы по стилю программирования. Если это первая статья «для чайников», которую вы читаете, то, пожалуйста, прочитайте предыдущие статьи, чтобы у вас не возникало никаких вопросов. Кроме того убедитесь, что вы хорошо разобрались в старом материале, так как в этой статье я не буду объяснять основы.
Какие бывают индикаторы?
Сейчас я покажу вам какие бывают индикаторы. Конечно, вы и сами видели их достаточно, но сейчас я хочу обратить ваше внимание на свойства и параметры индикаторов, чтобы сделать таким образом небольшую классификацию свойств и параметров. Это поможет вам в дальнейшем писать пользовательские индикаторы. Итак, первый простенький индикатор:
Это Скользящее Среднее (Moving Average, MA), часто используемый технический индикатор. Обратите внимание на следующие важные вещи:
Теперь давайте посмотрим на другой индикатор:
Это Процентный Диапазон Вильямса (Williams’ Percent Range, %R). Обратите внимание, что:
Таким образом, существуют следующие свойства индикаторов:
Теперь давайте посмотрим на еще один индикатор:
Как видите, индикатор Объемов (Volumes) рисуется в виде гистограммы. Таким образом, существуют еще несколько видов вывода показателей индикатора. Вот пример другого типа вывода:
индикатор Фракталов (Fractals) рисуется в виде определенных символов. А теперь внимательно посмотрите на следующий индикатор:
Это индикатор Аллигатор (Alligator). Обратите внимание, что индикатор одновременно рисует 3 показателя (линии баланса). Как это работает?? Дело в том, что любой (есть и исключения, но о них в другой раз) индикатор при выводите использует буферы данных.
Теперь давайте подведем итог нашей небольшой экскурсии. Любой индикатор имеет такие свойства:
Убедитесь, что вы хорошо разобрались и понимаете все эти свойства. Сейчас мы воспользуемся Мастером, чтобы создать пользовательский индикатор.
Создание пользовательского индикатора
Запускаем Meta Editor, выбираем Файл->Создать:
Появляется окно Мастера создания советника, выбираем Пользовательский индикатор, нажимаем Далее:
Заполняем поля Имя, Автор и Ссылка. Тут все как обычно, но теперь вы можете добавлять параметры. Что же это такое??
Сейчас (для наглядного примера) мы добавим параметр, который будет указывать, сколько баров должно обрабатываться для расчета показателя нашего индикатора. Где это может быть использовано?? Представьте, например, что ваш индикатор серьезно нагружает процессор, так как проводит много сложных вычислений. При этом вы часто меняете таймфрейм графика и просматриваете лишь 100-200 последних баров. Зачем в таком случае проводить лишние вычисления и ждать несколько лишних секунд?? Вот тут нас и выручит этот параметр. Конечно, в нашем индикаторе не будет ничего сложного и затратного для ресурсов компьютера. Это просто один из вариантов использования параметров индикатора.
Итак, чтобы добавить параметр нужно нажать на кнопку Добавить (1). После этого вы можете изменить название переменной на что-нибудь более подходящее (2). В нашем случае меняем название на barsToProcess (баров для обработки). Также вы можете изменить начальное значение (3), то есть значение по умолчанию. Меняем на 100. Кроме того, вы можете изменить тип переменной, но в нашем случае ничего менять не нужно, так как тип int идеально подходит для наших целей. После внесения всех необходимых изменений нажимаем Далее:
Вуаля!! Ваш первый индикатор готов! Ну и что, что он ничего не рисует, зато сколько кода. Файл с исходным кодом будет размещен в папке с индикаторами: MetaTrader4\experts\indicators.
Разбираем каждую строчку
А теперь давайте посмотри, что для нас создал Meta Editor:
Как всегда, «шапка» из однострочных комментариев включает введенные ранее вами данные. Очень мило, смотрим дальше:
Эта директива указывает, как можно связаться с автором. Вы можете спросить: где же эти данные (имя автора, как связаться), они ведь нигде не отображаются? Да, не отображаются, но они «прошиваются» в исполняемый файл. Если просмотреть исполняемый файл как обычный текст, то вы сможете увидеть эти данные:
Эта директива указывает, что индикатор должен рисоваться в отдельном подокне. Как видите, никаких дополнительных параметров не указывается, как, например, в предыдущей директиве.
Эта директива указывает, сколько буферов данных будет использовать индикатор. Как вы заметили, директивы чем-то похожи на обычные функции: Они тоже принимают какие-то параметры и что-то делают в ответ. Но есть важное отличие: директивы выполняются в первую очередь (еще до начала компиляции).
Это обычный массив. Просто не указывается размерность и не выполняется инициализация. Этот массив в дальнейшем будет настроен как буфер данных.
Дальше у нас идет объявление и описание функций. В отличие от привычного для вас скрипта, в каждом индикаторе имеется 3, а не 1 функция:
Посмотрим, что происходит в каждой функции:
Здесь у нас вызываются 2 важные функции для настройки буфера данных:
Эта функция задает, каким образом рисовать буфер данных. Первый параметр указывает, к какому буферу применить изменение. Обратите внимание, что в этой функции (и подобной ей) нумерация буферов начинается с нуля, а не с единицы как в директивах. Это важный момент, смотрите не наломайте дров. Второй параметр указывает, как рисовать выбранный буфер. В нашем случае используется константа DRAW_LINE, которая указывает, что буфер следует рисовать в виде линии. Конечно, имеются и другие константы, но к ним мы вернемся позже.
Функция деинициализации по умолчанию пуста.
Вот мы и добрались до самой главной функции. Весь основной код размещается здесь. Обратите внимание, что заранее объявлена переменная counted_bars (посчитанные бары), которая инициализируется функцией IndicatorCounted(). Эта переменная обычно используется для оптимизации и ускорения работы индикатора, о чем мы поговорим позже. А сейчас давайте наконец-то что-то нарисуем в окне индикатора!
Дописываем индикатор
Определимся что выводить. Что вообще нам будет показывать индикатор? Что-нибудь простое. Для начала давайте рисовать случайные числа. А что?? Этот индикатор гарантирует вам 50% прибыльных сигналов. Решено.
Идем к нашей функции init() и дописываем код для инициализации генератора случайных чисел:
Инициализация готова, переходим к функции start():
Индикатор будет добавлен к активному графику:
Как видите, все работает, теперь давайте хорошенько разберемся, что делает этот код:
Мы используем цикл for для того, чтобы пройтись по всем элементам буфера данных. Так как каждому элементу буфера соответствует определенный бар, то мы используем цикл, начиная проход с нулевого бара (последнего доступного) и заканчиваем первым доступным, который по счету меньше на единицу, чем переменная Bars (потому что мы считаем бары с нуля).
Каждую итерацию счетчик увеличивается на единицу, и мы двигаемся от последнего до первого доступного бара и одновременно присваиваем каждому элементу буфера (который соответствует своему бару) случайное число от 0 до 1000. Если вам сложно понять, как каждому элементу буфера соответствует свой бар, то попробуйте изменить цикл следующим образом и посмотрите на результат в терминале:
Теперь индикатор будет показывать номер каждого бара, посмотрите:
Как видите, номер бара постепенно увеличивается от последнего к первому (от 0 до Bars). Надеюсь теперь вы поняли, каким образом элементы буфера данных соответствуют барам на графике.
Разбираем каждую строку:
Объявляем переменную counted_bars, которая будет хранить количество посчитанных индикатором баров. На самом деле функция IndicatorCounted() возвращает количество неизмененных баров после прошлого вызова функции start(). Таким образом, если это первый вызов функции start(), то IndicatorBars() возвратит нам 0, так как все бары для нас новые. Если же, это не первый вызов, то очевидно, что изменился только последний бар, поэтому IndicatorBars() возвратит число равное Bars-1.
Еще одна переменная, которая будет использована как ограничитель, то есть позволит циклу завершиться раньше, пропуская уже пересчитанные свечи.
Как уже было сказано, если IndicatorCounted() возвращает 0, то это значит, что функция start() вызывается впервые и все бары для нас «новые» (еще не рассчитывался индикатор для них). Но если это не первый вызов start(), то нам будет возвращено значение равное Bars-1. Так вот, это условие отслеживает как раз такую ситуацию. После чего мы уменьшаем переменную counted_bars на 1. Зачем это делается, ведь измениться может лишь последний бар?? Оказывается, что бывают ситуации, при которых последний тик предыдущего бара оказывается необработанным из-за того, что в момент прихода этого последнего тика обрабатывался предпоследний тик. Поэтому пользовательский индикатор не был вызван и не был рассчитан. Именно поэтому мы уменьшаем на 1 переменную counted_bars, чтобы исключить эту ситуацию.
Здесь мы присваиваем переменной limit (ограничителю) количество последних баров, которые нужно пересчитать. Так как в переменной counted_bars хранится количество свечей, которые уже рассчитаны, то мы просто находим разницу между Bars (всего доступных баров) и counted_bars, чтобы определить, сколько свечей необходимо пересчитать.
Сам цикл почти не изменился. Мы всего лишь поменяли условие выполнения. Теперь цикл будет выполняться пока счетчик i меньше чем ограничитель limit.
Теперь оптимизация завершена. Если вы понаблюдаете за обновленной версией индикатора, то заметите, что при приходе нового тика изменяется показатели лишь для последних баров. Приучите себя всегда использовать такую оптимизацию, даже если ваш индикатор не вычисляет ничего сложного. Это просто хороший тон.
Вы еще не забыли про параметр индикатора (barsToProcess), который мы добавили в мастере. Сейчас самое время чтобы его задействовать. Нужно добавить всего пару строк перед циклом:
Как видите, все достаточно тривиально. Мы проверяем или больше limit чем barsToProcess и если да, то уменьшаем ограничитель через присваивание. В результате, если установить barsToProcess=100, то вы сможете наблюдать похожую картину:
Как видите, рассчитывается только определенное нами количество баров.
Наш индикатор практически готов. Но у нас нет четких сигналов для входа в рынок. Поэтому нужно добавить определенности. Для этой цели нам подойдут уровни.
Разберемся в новых директивах:
Эта директива указывает, что уровень номер 1 следует разместить на отметке 800.0. Обратите внимание, что нумерация буферов начинается с единицы, подобно тому, как это сделано в директивах для настройки буферов. Чтобы настроить другой уровень, следует просто поменять номер уровня в конце директивы:
Есть важное ограничение в настройке внешнего вида уровней. Вы не можете настроить каждый уровень индивидуально. Все настройки применяются ко всем уровням без исключений. Если вам нужно настроить каждый уровень индивидуально, то вам нужно использовать объекты (а сами уровни вообще не использовать), о которых мы поговорим в следующей статье.
Эта директива устанавливает цвет, который будет использоваться для рисования всех уровней.
Эта директива задает толщину для рисования линий всех уровней. Вы можете установить толщину в пределах от 1 до 5. Вы должны помнить, что, если толщина уровня больше 1, то уровни будут рисоваться только сплошной линией. Если вам нужен другой стиль рисования уровней, то вы должны использовать только единичную толщину.
Эта директива задает стиль для рисования линии. Доступны такие предопределенные константы:
Функция iCustom
Прототип функции имеет следующий вид:
Сигнальный индикатор
Сейчас мы с вами напишем еще один простенький индикатор. Итак, представьте себе такую ситуацию. Вы написали довольно сложный индикатор с множеством буферов данных. Многие из них выводятся в отдельном окне, остальные используются для промежуточных расчетов. Вы точно знаете сигналы на покупку и продажу. Но вот незадача: их (сигналы) достаточно сложно отследить. Вам приходится постоянно пялится в монитор, пытаясь выявить очередное пересечение линий, которые находятся выше или ниже уровней. Поэтому вы решаете написать еще один индикатор, который делал бы все это сам, а вам лишь показывал сигналы для входа в рынок. Например, это могли бы быть стрелочки, которые указывают, в каком направлении открывать позиции. Это всего лишь фантазия, которая показывает, где было бы уместно написать сигнальный индикатор. У нас ситуация намного проще, но карта той же масти.
Мы будем писать сигнальный индикатор на основе прошлого индикатора RandomIndicator. Сначала нужно точно определить условия для входа в рынок. Уровни мы ведь не просто так добавляли, поэтому условия будут следующие:
Теперь пришло самое подходящее время для написания нового индикатора. Воспользуйтесь мастером для создания нового пользовательского индикатора. Добавьте один дополнительный параметр, как в прошлый раз:
А последний шаг настройте следующим образом:
Сначала нужно добавить 2 буфера данных, которые будут использоваться, чтобы рисовать сигналы на покупку и продажу в виде стрелочек. Измените тип буферов данных на Arrow. Поменяйте цвета и измените коды символов. Ниже представлены все доступные коды символов:
Также не нужно рисовать индикатор в отдельном окне, так как мы собираемся рисовать сигналы прямо в окне графика цен.
Мы используем 2 буфера данных, потому что мы не можем рисовать разные стрелочки (символы) одним буфером. Каждый буфер данных, который выводится в виде символов, может рисоваться только одним символом. Теперь давайте внимательно посмотрим на код инициализации индикатора:
Остальной код инициализации настраивает буферы аналогично «случайному» индикатору, который мы рассмотрели выше. Теперь давайте допишем код в функцию start():
Весь код до цикла повторяется из «случайного» индикатора. Вообще этот код является стандартным в любом индикаторе и повторяется с небольшими изменениями. Давайте детально разберем цикл:
Сначала мы объявляем переменную randomValue (случайное значение), которой присваиваем значение нашего «случайного» индикатора на текущем баре. Для этого мы используем функцию iCustom:
Если значение «случайного» индикатора больше верхнего уровня (800), то это сигнал на покупку:
Иначе, если сигнала на покупку нет, то:
Если же значение «случайного» индикатора меньше нижнего уровня (200), то это сигнал на продажу:
Иначе, если сигнал на продажу отсутствует, то:
Вот и весь цикл. Откомпилируйте индикатор и запустите его в терминале:
О стиле
Нет, не о том, как правильно подобрать галстук к костюму и рубашке, хотя всегда актуально. Стиль программирования очень важен, если вы не пишите код исключительно для себя. Дело в том, что у каждого программиста свой индивидуальный стиль. Каждый по-разному оформляет циклы, делает разные отступы (а некоторые вообще не делают), объявляет переменные и т.д. Вы должны выработать свой стиль программирования, которого вы будете придерживаться в дальнейшем. Я хочу дать вам пару советов, которые помогут сделать код более читабельным и приятным для восприятия:
Заключение
Сегодня вы кое-чему научились. Вы написали два простых индикатора. Да, они бесполезны, но я ведь не учу вас прибыльно торговать!! Вы получили представление о том, как работают индикаторы, какими параметрами и свойствами они обладают. Вы научились настраивать и работать с буферами. Вы узнали несколько новых функций. Функция iCustom очень важна и будет использоваться в дальнейшем даже в советниках. Если у вас возникают какие-то сложности, то медленно перечитывайте статью еще раз и если проблема осталась, то смело задавайте вопросы на форуме или в комментариях.