Как написать индикатор для мт4 пошаговая инструкция

Введение

Это четвертая статья из цикла «Язык MQL4 для ‘чайников'». Сегодня мы будем учиться писать пользовательские индикаторы. Мы изучим классификацию свойств индикаторов, посмотрим, как эти свойства влияют на сам индикатор, узнаем про новые функции и оптимизацию, и наконец-то напишем несколько своих индикаторов. Кроме того, в конце статьи вас ждут советы по стилю программирования. Если это первая статья «для чайников», которую вы читаете, то, пожалуйста, прочитайте предыдущие статьи, чтобы у вас не возникало никаких вопросов. Кроме того убедитесь, что вы хорошо разобрались в старом материале, так как в этой статье я не буду объяснять основы.

Какие бывают индикаторы?

Сейчас я покажу вам какие бывают индикаторы. Конечно, вы и сами видели их достаточно, но сейчас я хочу обратить ваше внимание на свойства и параметры индикаторов, чтобы сделать таким образом небольшую классификацию свойств и параметров. Это поможет вам в дальнейшем писать пользовательские индикаторы. Итак, первый простенький индикатор:

Это Скользящее Среднее (Moving Average, MA), часто используемый технический индикатор. Обратите внимание на следующие важные вещи:

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

Теперь давайте посмотрим на другой индикатор:

Это Процентный Диапазон Вильямса (Williams’ Percent Range, %R). Обратите внимание, что:

  • индикатор рисуется в отдельном подокне
  • как и в предыдущем случае, индикатор выводит лишь один показатель
  • диапазон значений индикатора строго ограничен
  • сама линия рисуется другим стилем, цветом и толщиной

Таким образом, существуют следующие свойства индикаторов:

  • индикатор рисуется: в окне графика цен или в отдельном подокне. Теперь давайте разберемся, почему Скользящее Среднее рисуется на графике цен, а Процентный Диапазон Вильямса в отдельном окне. Вся разница — это диапазон выводимых значений. Обратите внимание, что второй индикатор выводит значения в диапазоне от 0 до -100. Теперь представьте, что мы выводим эти значения в окно цен. Что бы произошло?? Вы бы даже не увидели эту линию, потому что цена выводится в гораздо более скромном диапазоне. В нашем случае: от 0.6805 до 0.7495. И тут дело даже не в этом. Цены — это положительные числа, а показатель у нас отрицательный. Индикаторы рисуются в отдельном подокне, если их показатели не входят в диапазон цен активного графика. Если же диапазон приблизительно совпадает (например, различные виды скользящих средних), то индикатор рисуется в окне графика. В будущем настраивайте это свойство индикатора по этой простой логике. Посмотрите рисунок:

  • индикатор, который выводится в отдельном подокне может быть ограничен в строгом диапазоне. Это значит, что терминал установит фиксированный масштаб на вывод показателя индикатора и даже если значения «выходят» за диапазон, то вы этого просто не увидите. Если же вы отключите это свойство, то терминал будет автоматически изменять масштаб таким образом, чтобы в окно помещались все значения индикатора. Посмотрите рисунок:

  • индикатор может выводить свои значения разным цветом, стилем и толщиной. Вы часто это видели, когда настраивали внешний вид индикаторов в терминале. Здесь имеется одно ограничение: если вы используете толщину линии больше 1, то вы можете использовать только один стиль — сплошную линию.

Теперь давайте посмотрим на еще один индикатор:

Как видите, индикатор Объемов (Volumes) рисуется в виде гистограммы. Таким образом, существуют еще несколько видов вывода показателей индикатора. Вот пример другого типа вывода:

индикатор Фракталов (Fractals) рисуется в виде определенных символов. А теперь внимательно посмотрите на следующий индикатор:

Это индикатор Аллигатор (Alligator). Обратите внимание, что индикатор одновременно рисует 3 показателя (линии баланса). Как это работает?? Дело в том, что любой (есть и исключения, но о них в другой раз) индикатор при выводите использует буферы данных.

Буфер данных — это почти обычный массив. Его отличительная особенность заключается в том, что этим массивом частично управляет терминал. Терминал изменяет массив таким образом, что с появлением каждого нового бара происходит смещение. Это делается для того, чтобы каждый элемент массива соответствовал определенному бару. Максимальное количество выводимых буферов данных в одном индикаторе: 8. Да, сейчас все звучит очень запутанно, но скоро вы поймете, что иначе и быть не могло. Просто запомните, что каждой линии в индикаторе Alligator соответствует свой буфер данных. Также каждый буфер имеет свои параметры, в соответствии с которыми терминал и рисует их. В нашем случае есть 3 буфера, которые можно описать следующим образом:

  1. Первый буфер: рисуется сплошной линией зеленым цветом с толщиной 3.
  2. Второй буфер: рисуется пунктирной линией красным цветом с толщиной 1.
  3. Третий буфер: рисуется сплошной линией синим цветом с толщиной 2.

Индикатору необязательно рисовать какой-то буфер. Он может быть использован для промежуточных расчетов. Поэтому буферов может быть больше, чем вы видите. Но самое главное свойство буфера данных — это то, что каждый элемент буфера соответствует определенному бару на графике цен. Просто запомните это, скоро вы увидите, как это соответствие работает в коде.

Теперь давайте подведем итог нашей небольшой экскурсии. Любой индикатор имеет такие свойства:

  • один или больше буферов данных (хотя это тоже не обязательно) для вывода своих показателей или расчета промежуточных результатов. В свою очередь каждый буфер также имеет свои свойства, которые определяют, как он будет рисоваться и будет ли вообще. Например: рисовать показатель в виде гистограммы, символа или линии; каким цветом и стилем.
  • где должен рисоваться индикатор (в окне графика или в подокне).
  • если индикатор рисуется в подокне, то нужно ли ограничивать диапазон или производить масштабирование автоматически.

Убедитесь, что вы хорошо разобрались и понимаете все эти свойства. Сейчас мы воспользуемся Мастером, чтобы создать пользовательский индикатор.

Создание пользовательского индикатора

Запускаем Meta Editor, выбираем Файл->Создать:

Появляется окно Мастера создания советника, выбираем Пользовательский индикатор, нажимаем Далее:

Заполняем поля Имя, Автор и Ссылка. Тут все как обычно, но теперь вы можете добавлять параметры. Что же это такое??

Параметры — это обычные переменные, которые может настраивать пользователь. И что очень важно, эти переменные можно использовать в коде индикатора. Назначение параметров очевидно — вы позволяете пользователям настраивать некоторые аспекты работы индикатора. Это может быть что угодно. Например: какой таймфрейм использовать, в каком режиме работать, сколько баров брать для усреднения и т.д.

Сейчас (для наглядного примера) мы добавим параметр, который будет указывать, сколько баров должно обрабатываться для расчета показателя нашего индикатора. Где это может быть использовано?? Представьте, например, что ваш индикатор серьезно нагружает процессор, так как проводит много сложных вычислений. При этом вы часто меняете таймфрейм графика и просматриваете лишь 100-200 последних баров. Зачем в таком случае проводить лишние вычисления и ждать несколько лишних секунд?? Вот тут нас и выручит этот параметр. Конечно, в нашем индикаторе не будет ничего сложного и затратного для ресурсов компьютера. Это просто один из вариантов использования параметров индикатора.

Итак, чтобы добавить параметр нужно нажать на кнопку Добавить (1). После этого вы можете изменить название переменной на что-нибудь более подходящее (2). В нашем случае меняем название на barsToProcess (баров для обработки). Также вы можете изменить начальное значение (3), то есть значение по умолчанию. Меняем на 100. Кроме того, вы можете изменить тип переменной, но в нашем случае ничего менять не нужно, так как тип int идеально подходит для наших целей. После внесения всех необходимых изменений нажимаем Далее:

Почти все готово. Теперь нужно указать, как рисовать индикатор: в отдельном окне или в окне графика цен. Также вы можете ограничить диапазон. Ставим галочку напротив Индикатор в отдельном окне. Ниже мы видим пустое поле Индексы(буферы данных). Здесь вы можете добавить, сколько вам нужно буферов данных (максимум 8). Кроме того, вы всегда сможете добавить или убрать буфера позже, изменив код. Нажимаем Добавить, чтобы добавить один буфер. Теперь вы можете изменить то, как будет отображаться буфер: обычная линия, гистограмма, отрезки или стрелки (символы). Оставим все как есть, то есть тип — Line. Настройте цвет для рисования этого буфера. Нажимаем Готово:

Вуаля!! Ваш первый индикатор готов! Ну и что, что он ничего не рисует, зато сколько кода. Файл с исходным кодом будет размещен в папке с индикаторами: MetaTrader4\experts\indicators.

Разбираем каждую строчку

А теперь давайте посмотри, что для нас создал Meta Editor:




Как всегда, «шапка» из однострочных комментариев включает введенные ранее вами данные. Очень мило, смотрим дальше:

#property copyright "Antonuk Oleg"

Вы еще помните директиву препроцессора #define из второй статьи? Мы ее использовали для объявления констант. Так вот, это еще одна директива. Она используется для того, чтобы указать какие-то специфические свойства индикатора. В нашем случае, чтобы указать авторство. Обратите внимание, что сначала идет специальный знак — # (решетка), потом ключевое слово property (без пробела). Property означает свойство. Дальше идет конкретное свойство, которое мы хотим задать, в нашем случае copyright, а потом значение этого свойства. В нашем случае — это строка с вашим именем. С помощью директивы #property можно настроить много специфических аспектов индикатора, в чем вы сейчас убедитесь. Все эти свойства будут заданы по умолчанию. Смотрим дальше:

#property link      "banderass@i.ua"

Эта директива указывает, как можно связаться с автором. Вы можете спросить: где же эти данные (имя автора, как связаться), они ведь нигде не отображаются? Да, не отображаются, но они «прошиваются» в исполняемый файл. Если просмотреть исполняемый файл как обычный текст, то вы сможете увидеть эти данные:

Смотрим дальше:

#property indicator_separate_window

Эта директива указывает, что индикатор должен рисоваться в отдельном подокне. Как видите, никаких дополнительных параметров не указывается, как, например, в предыдущей директиве.

#property indicator_buffers 1

Эта директива указывает, сколько буферов данных будет использовать индикатор. Как вы заметили, директивы чем-то похожи на обычные функции: Они тоже принимают какие-то параметры и что-то делают в ответ. Но есть важное отличие: директивы выполняются в первую очередь (еще до начала компиляции).

#property indicator_color1 DarkOrchid

Указываем цвет по умолчанию для первого буфера. Обратите внимание, что нумерация буферов в директивах начинается с единицы, а не с нуля. Постарайтесь запомнить это, чтобы в дальнейшем не возникало никакой путаницы. Цвет указывается с помощью одного из множества предопределенных названий. Посмотреть ключевые слова для всех доступных цветов можно в справке: Справочник MQL4 -> Стандартные константы -> Набор Web-цветов. Аналогично можно указать цвет и для других буферов, нужно просто изменить номер буфера.

extern int       barsToProcess=100;

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

Смотри далее:

double ExtMapBuffer1[];

Это обычный массив. Просто не указывается размерность и не выполняется инициализация. Этот массив в дальнейшем будет настроен как буфер данных.

Дальше у нас идет объявление и описание функций. В отличие от привычного для вас скрипта, в каждом индикаторе имеется 3, а не 1 функция:

  • init() — эта функция вызывается терминалом только один раз, когда вы запускаете индикатор. Ее назначение: подготовить индикатор для работы. Настроить буфера данных, проверить параметры (мало ли что там мог пользователь ввести) и другие подготовительные действия. Эта функция не является обязательной. Если вы не выполняете никакого кода в ней, то можете ее просто удалить.
  • deinit() — эта функция также вызывается только один раз, когда вы удаляете индикатор с графика. В ней вы должны подготовить индикатор для завершения работы. Например, закрыть открытые файлы, удалить графические объекты с графика (не переживайте, еще научитесь). Эта функция также необязательная.
  • start() — эта функция в отличие от скриптов, в индикаторе вызывается каждый тик. То есть, как только поступают новые котировки от валютной пары, к графику которой вы применили индикатор, так и будет сразу вызвана эта функция. Кроме того, эта функция вызывается при первом запуске индикатора, то есть после функции init().

Посмотрим, что происходит в каждой функции:

int init()
{
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
 
   return(0);
}

Здесь у нас вызываются 2 важные функции для настройки буфера данных:

SetIndexStyle(0,DRAW_LINE);

Эта функция задает, каким образом рисовать буфер данных. Первый параметр указывает, к какому буферу применить изменение. Обратите внимание, что в этой функции (и подобной ей) нумерация буферов начинается с нуля, а не с единицы как в директивах. Это важный момент, смотрите не наломайте дров. Второй параметр указывает, как рисовать выбранный буфер. В нашем случае используется константа DRAW_LINE, которая указывает, что буфер следует рисовать в виде линии. Конечно, имеются и другие константы, но к ним мы вернемся позже.

SetIndexBuffer(0,ExtMapBuffer1);

Эта функция «привязывает» массив к номеру буфера. То есть она указывает, что буфер с указанным номером будет использовать указанный массив для хранения данных. Таким образом, в результате, изменяя элементы этого массива, вы будете изменять значения самого буфера. Фактически массив — это и есть буфер данных. Первый аргументномер буфера, к которому нужно привязать массив (нумерация с нуля). Второй аргументназвание массива, который следует привязать.

return(0);

Завершение функции, возвращаем нуль — инициализация прошла успешно.

int deinit()
{

   

   return(0);
}

Функция деинициализации по умолчанию пуста.

int start()
{
   int counted_bars=IndicatorCounted();

   

   return(0);
}

Вот мы и добрались до самой главной функции. Весь основной код размещается здесь. Обратите внимание, что заранее объявлена переменная counted_bars (посчитанные бары), которая инициализируется функцией IndicatorCounted(). Эта переменная обычно используется для оптимизации и ускорения работы индикатора, о чем мы поговорим позже. А сейчас давайте наконец-то что-то нарисуем в окне индикатора!

Дописываем индикатор

Определимся что выводить. Что вообще нам будет показывать индикатор? Что-нибудь простое. Для начала давайте рисовать случайные числа. А что?? Этот индикатор гарантирует вам 50% прибыльных сигналов. Решено.

Идем к нашей функции init() и дописываем код для инициализации генератора случайных чисел:

int init()
{
 
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
 
   
   MathSrand(TimeLocal());
 
   return(0);
}

Инициализация готова, переходим к функции start():

int start()
{
   int counted_bars=IndicatorCounted();
 
   for(int i=0;i<Bars;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

Компилируем — F7. Запускаем терминал, находим панель Навигатор, выбираем раздел Пользовательские Индикаторы и делаем двойной клик на названии нашего индикатора:

Индикатор будет добавлен к активному графику:

Как видите, все работает, теперь давайте хорошенько разберемся, что делает этот код:

for(int i=0;i<Bars;i++)

Мы используем цикл for для того, чтобы пройтись по всем элементам буфера данных. Так как каждому элементу буфера соответствует определенный бар, то мы используем цикл, начиная проход с нулевого бара (последнего доступного) и заканчиваем первым доступным, который по счету меньше на единицу, чем переменная Bars (потому что мы считаем бары с нуля).

{
   ExtMapBuffer1[i]=MathRand()%1001;
}

Каждую итерацию счетчик увеличивается на единицу, и мы двигаемся от последнего до первого доступного бара и одновременно присваиваем каждому элементу буфера (который соответствует своему бару) случайное число от 0 до 1000. Если вам сложно понять, как каждому элементу буфера соответствует свой бар, то попробуйте изменить цикл следующим образом и посмотрите на результат в терминале:

for(int i=0;i<Bars;i++)
{
   ExtMapBuffer1[i]=i;
}

Теперь индикатор будет показывать номер каждого бара, посмотрите:

Как видите, номер бара постепенно увеличивается от последнего к первому (от 0 до Bars). Надеюсь теперь вы поняли, каким образом элементы буфера данных соответствуют барам на графике.

Давайте вернемся к коду «случайного» индикатора. Если бы вы начали использовать его хотя бы на протяжении нескольких минут, то заметили бы, что каждый тик индикатор будет рисовать абсолютно другой график. То есть каждый тик индикатор выполняет повторные вычисления того, что мы уже посчитали в прошлый раз. Нам это будет неудобно хотя бы потому, что мы не будем видеть даже то, что было тик назад. Но дело даже не в этом, потому что такой индикатор никто использовать не будет — мы всего лишь учимся. Дело в другом. Представьте, что ваш индикатор выполняет очень много сложных вычислений и расчет одного бара занимает много ресурсов процессора. В таком случае, когда появится новая цена, ваш индикатор снова и снова будет рассчитывать показатель для каждого доступного бара, даже если это уже происходило раньше. Понимаете? Вместо того, чтобы один раз посчитать, он будет делать это снова и снова. Устранение подобных проблем, которые связаны с нерациональным использованием ресурсов и называется оптимизацией.

Как же решить эту проблему? Обычно поступают следующим образом. Сначала индикатор рассчитывается на всех доступных свечах, а уже потом по мере поступления котировок, будет пересчитываться показатель только для последней свечи. То есть поступаем рационально — никаких лишних телодвижений. Давайте оптимизируем функцию start(), чтобы она работала именно так:

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
  
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

Разбираем каждую строку:

int counted_bars=IndicatorCounted(),

Объявляем переменную counted_bars, которая будет хранить количество посчитанных индикатором баров. На самом деле функция IndicatorCounted() возвращает количество неизмененных баров после прошлого вызова функции start(). Таким образом, если это первый вызов функции start(), то IndicatorBars() возвратит нам 0, так как все бары для нас новые. Если же, это не первый вызов, то очевидно, что изменился только последний бар, поэтому IndicatorBars() возвратит число равное Bars-1.

limit;

Еще одна переменная, которая будет использована как ограничитель, то есть позволит циклу завершиться раньше, пропуская уже пересчитанные свечи.

   if(counted_bars>0)
      counted_bars--;

Как уже было сказано, если IndicatorCounted() возвращает 0, то это значит, что функция start() вызывается впервые и все бары для нас «новые» (еще не рассчитывался индикатор для них). Но если это не первый вызов start(), то нам будет возвращено значение равное Bars-1. Так вот, это условие отслеживает как раз такую ситуацию. После чего мы уменьшаем переменную counted_bars на 1. Зачем это делается, ведь измениться может лишь последний бар?? Оказывается, что бывают ситуации, при которых последний тик предыдущего бара оказывается необработанным из-за того, что в момент прихода этого последнего тика обрабатывался предпоследний тик. Поэтому пользовательский индикатор не был вызван и не был рассчитан. Именно поэтому мы уменьшаем на 1 переменную counted_bars, чтобы исключить эту ситуацию.

limit=Bars-counted_bars;

Здесь мы присваиваем переменной limit (ограничителю) количество последних баров, которые нужно пересчитать. Так как в переменной counted_bars хранится количество свечей, которые уже рассчитаны, то мы просто находим разницу между Bars (всего доступных баров) и counted_bars, чтобы определить, сколько свечей необходимо пересчитать.

for(int i=0;i<limit;i++)
{
   ExtMapBuffer1[i]=MathRand()%1001;
}

Сам цикл почти не изменился. Мы всего лишь поменяли условие выполнения. Теперь цикл будет выполняться пока счетчик i меньше чем ограничитель limit.

Теперь оптимизация завершена. Если вы понаблюдаете за обновленной версией индикатора, то заметите, что при приходе нового тика изменяется показатели лишь для последних баров. Приучите себя всегда использовать такую оптимизацию, даже если ваш индикатор не вычисляет ничего сложного. Это просто хороший тон.

Вы еще не забыли про параметр индикатора (barsToProcess), который мы добавили в мастере. Сейчас самое время чтобы его задействовать. Нужно добавить всего пару строк перед циклом:

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
   
   if(limit>barsToProcess)
      limit=barsToProcess;
  
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   
   return(0);
}

Как видите, все достаточно тривиально. Мы проверяем или больше limit чем barsToProcess и если да, то уменьшаем ограничитель через присваивание. В результате, если установить barsToProcess=100, то вы сможете наблюдать похожую картину:

Как видите, рассчитывается только определенное нами количество баров.

Наш индикатор практически готов. Но у нас нет четких сигналов для входа в рынок. Поэтому нужно добавить определенности. Для этой цели нам подойдут уровни.

Уровни — это горизонтальные линии, которые рисуются индикатором определенным стилем, цветом и толщиной. Важно отметить, что максимальное количество уровней на одном баре: 8. Кроме того вы можете задавать уровни как с помощью директив, так и используя функции. Предпочтительнее использовать первый вариант, если вы собираетесь настроить уровни по умолчанию. Для динамического изменения уровней во время работы индикатора используйте функции. Итак, разместим 2 уровня: первый на отметке 800, второй — 200. Для этого добавим несколько директив в начале кода индикатора:





#property copyright "Antonuk Oleg"
#property link      "banderass@i.ua"
 
#property indicator_level1 800.0
#property indicator_level2 200.0
#property indicator_levelcolor LimeGreen
#property indicator_levelwidth 2
#property indicator_levelstyle 0
 
#property indicator_separate_window

Разберемся в новых директивах:

#property indicator_level1 800.0

Эта директива указывает, что уровень номер 1 следует разместить на отметке 800.0. Обратите внимание, что нумерация буферов начинается с единицы, подобно тому, как это сделано в директивах для настройки буферов. Чтобы настроить другой уровень, следует просто поменять номер уровня в конце директивы:

#property indicator_level2 200.0

Есть важное ограничение в настройке внешнего вида уровней. Вы не можете настроить каждый уровень индивидуально. Все настройки применяются ко всем уровням без исключений. Если вам нужно настроить каждый уровень индивидуально, то вам нужно использовать объекты (а сами уровни вообще не использовать), о которых мы поговорим в следующей статье.

#property indicator_levelcolor LimeGreen

Эта директива устанавливает цвет, который будет использоваться для рисования всех уровней.

#property indicator_levelwidth 2

Эта директива задает толщину для рисования линий всех уровней. Вы можете установить толщину в пределах от 1 до 5. Вы должны помнить, что, если толщина уровня больше 1, то уровни будут рисоваться только сплошной линией. Если вам нужен другой стиль рисования уровней, то вы должны использовать только единичную толщину.

#property indicator_levelstyle STYLE_SOLID

Эта директива задает стиль для рисования линии. Доступны такие предопределенные константы:

  • STYLE_SOLID — сплошная линия
  • STYLE_DASH — штриховая линия
  • STYLE_DOT — пунктирная линия
  • STYLE_DASHDOT — штрих-пунктирная линия
  • STYLE_DASHDOTDOT — штрих-пунктирная линия с двойными точками

На этом разработка нашего «случайного» индикатора закончена. Давайте сохраним исходный файл с более подходящим названием — randomIndicator.mq4. Перекомпилируйте исходники еще раз. В следующем разделе этот индикатор нам еще пригодиться. Финальная версия с уровнями должна выглядеть примерно так:

Функция iCustom

Сейчас мы познакомимся с очень полезной функцией — iCustom. Она используется для того, чтобы получить показатели любого пользовательского индикатора. То есть, если для встроенных индикаторов мы используем функции для работы с техническими индикаторами, которые мы рассмотрели в прошлой статье (например: iADX(), iMACD и т.д.), то для всех остальных индикаторов (пользовательских индикаторов) мы используем функцию iCustom. Эта функция является универсальной и может использоваться с любым пользовательским индикатором, который соответствует таким требованиям:

  • индикатор откомпилирован и имеет вид исполняемого файла (*.ex4)
  • индикатор размещен в папке MetaTrader 4\experts\indicators

Прототип функции имеет следующий вид:

double iCustom( string symbol, int timeframe, string name, ..., int mode, int shift);

Параметры:

  • symbol – определяет, какой финансовый инструмент (валютную пару) использовать для вычисления показателя пользовательского индикатора. Используйте NULL (или 0), если вам нужен текущий (активный) инструмент (график).
  • timeframe – определяет, на каком тайм фрейме (периоде) использовать индикатор. Используйте 0 для текущего периода или одну из констант (PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1, PERIOD_MN1).
  • name – название исполняемого файла пользовательского индикатора. Указывается только название: не нужно писать расширение(.ex4) или путь к файлу(experts/indicators/). Например, если название исполняемого файла пользовательского индикатора имеет вид «RandomIndicator.ex4», то нужно написать «RandomIndicator». При этом регистр не имеет значения. То есть можете написать «RANDOMindicator» и это тоже сработает.
  • – в этом месте указываются все значения параметров пользовательского индикатора. Например, в нашем индикаторе RandomIndicator всего один параметр — barsToProcess. То есть в нашем случае, мы напишем здесь 100 (или другое подходящее вам значение). Если параметров больше одного, то они указываются в том порядке, в котором они объявлены в пользовательском индикаторе через запятую. Если не очень понятно, то не беспокойтесь, мы сейчас напишем индикатор на основе этой функции.
  • mode – режим работы пользовательского индикатора. На самом деле это всего лишь номер буфера данных, значение которого вы хотите получить. Нумерация производиться с нуля (в отличии от директив). Если пользовательский индикатор имеет всего лишь один буфер данных, то следует указать 0 в качестве этого параметра.
  • shift – определяет, к какому бару применить пользовательский индикатор.

Примеры использования:

ExtMapBuffer[0]=iCustom(NULL,PERIOD_H1,"Momentum",14,0,0);
 
// присваиваем первому элементу массива ExtMapBuffer значение пользовательского 
// индикатора Momentum на последнем доступном баре. При этом используется активный 
// финансовый инструмент на часовом графике. Название исполняемого файла: Momentum. 
// Этот индикатор имеет единственный параметр - период. В нашем случае период 
// равен 14. Также этот индикатор имеет один буфер данных, поэтому используем нуль, 
// чтобы получить доступ к его значениям.
double signalLast=iCustom("EURUSD",PERIOD_D1,"MACD",12,26,9,1,0);
 
// объявляем новую переменную signalLast и присваиваем ей значение пользовательского 
// индикатора MACD на последнем доступном баре. При этом используется пара EURUSD на 
// дневном графике. Название исполняемого файла: MACD. Этот индикатор имеет 3 параметра: 
// период для быстрой средней, период для медленной средней и период для сигнальной линии. 
// Также этот индикатор имеет 2 буфера данных. Первый с значениями главной линии. Второй 
// с значениями сигнальной линии. В нашем случае берется значение сигнальной линии.

Сигнальный индикатор

Сейчас мы с вами напишем еще один простенький индикатор. Итак, представьте себе такую ситуацию. Вы написали довольно сложный индикатор с множеством буферов данных. Многие из них выводятся в отдельном окне, остальные используются для промежуточных расчетов. Вы точно знаете сигналы на покупку и продажу. Но вот незадача: их (сигналы) достаточно сложно отследить. Вам приходится постоянно пялится в монитор, пытаясь выявить очередное пересечение линий, которые находятся выше или ниже уровней. Поэтому вы решаете написать еще один индикатор, который делал бы все это сам, а вам лишь показывал сигналы для входа в рынок. Например, это могли бы быть стрелочки, которые указывают, в каком направлении открывать позиции. Это всего лишь фантазия, которая показывает, где было бы уместно написать сигнальный индикатор. У нас ситуация намного проще, но карта той же масти.

Мы будем писать сигнальный индикатор на основе прошлого индикатора RandomIndicator. Сначала нужно точно определить условия для входа в рынок. Уровни мы ведь не просто так добавляли, поэтому условия будут следующие:

  • если линия поднимается выше верхнего уровня (800.0), то покупаем
  • если линия опускается ниже нижнего уровня (200.0), то продаем

Теперь пришло самое подходящее время для написания нового индикатора. Воспользуйтесь мастером для создания нового пользовательского индикатора. Добавьте один дополнительный параметр, как в прошлый раз:

А последний шаг настройте следующим образом:

Сначала нужно добавить 2 буфера данных, которые будут использоваться, чтобы рисовать сигналы на покупку и продажу в виде стрелочек. Измените тип буферов данных на Arrow. Поменяйте цвета и измените коды символов. Ниже представлены все доступные коды символов:

Также не нужно рисовать индикатор в отдельном окне, так как мы собираемся рисовать сигналы прямо в окне графика цен.

Мы используем 2 буфера данных, потому что мы не можем рисовать разные стрелочки (символы) одним буфером. Каждый буфер данных, который выводится в виде символов, может рисоваться только одним символом. Теперь давайте внимательно посмотрим на код инициализации индикатора:

int init()
{

   SetIndexStyle(0,DRAW_ARROW);
   SetIndexArrow(0,236);
   SetIndexBuffer(0,ExtMapBuffer1);
   SetIndexEmptyValue(0,0.0);
   SetIndexStyle(1,DRAW_ARROW);
   SetIndexArrow(1,238);
   SetIndexBuffer(1,ExtMapBuffer2);
   SetIndexEmptyValue(1,0.0);

   return(0);
}

Обратите внимание, что теперь используется другая константа для типа вывода буфера данных — DRAW_ARROW:

SetIndexStyle(0,DRAW_ARROW);

Также появилось 2 новые функции, которые используются специально для настройки вывода символов. SetIndexArrow используется, чтобы задать каким символом рисовать буфер. Первый аргумент — номер буфера, второй — код символа, которым следует рисовать:

SetIndexArrow(0,236);

SetIndexEmptyValue используется, чтобы указать «пустое» значение. То есть мы указываем, при каком значении не нужно ничего рисовать вообще. Это очень удобно в нашем случае, так как сигналы встречаются далеко не на каждом баре. Это работает следующим образом. Когда вам не нужно выводить стрелочку на текущем баре, вы присваиваете соответствующему элементу буфера данных «пустое» значение, в нашем случае 0. Первый аргумент функции — номер буфера данных. Второй — «пустое» значение:

SetIndexEmptyValue(0,0.0);

Остальной код инициализации настраивает буферы аналогично «случайному» индикатору, который мы рассмотрели выше. Теперь давайте допишем код в функцию start():

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
 
   if(counted_bars>0)
      counted_bars--;
   
   limit=Bars-counted_bars;
   
   if(limit>barsToProcess)
      limit=barsToProcess;
  
   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      
      if(randomValue>800.0)
         ExtMapBuffer1[i]=High[i]+5*Point;
      else
         ExtMapBuffer1[i]=0.0;
         
      if(randomValue<200.0)
         ExtMapBuffer2[i]=Low[i]-5*Point;         
      else
         ExtMapBuffer2[i]=0.0;         
   }
   
   return(0);
}

Весь код до цикла повторяется из «случайного» индикатора. Вообще этот код является стандартным в любом индикаторе и повторяется с небольшими изменениями. Давайте детально разберем цикл:

   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      
      if(randomValue>800.0)
         ExtMapBuffer1[i]=High[i]+5*Point;
      else
         ExtMapBuffer1[i]=0.0;
         
      if(randomValue<200.0)
         ExtMapBuffer2[i]=Low[i]-5*Point;         
      else
         ExtMapBuffer2[i]=0.0;         
   }

Сначала мы объявляем переменную randomValue (случайное значение), которой присваиваем значение нашего «случайного» индикатора на текущем баре. Для этого мы используем функцию iCustom:

double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
 
// получаем значение "случайного" индикатора на i-ом баре. Используем активный график на текущем периоде. 
// Название исполняемого файла индикатора: RandomIndicator. Единственный параметр "случайного" индикатора -
// это количество баров для расчета. В нашем индикаторе также имеется аналогичная переменная, поэтому
// используем ее. В "случайном" индикаторе всего 1 буфер данных, поэтому используем 0, чтобы получить
// доступ к его значениям.

Если значение «случайного» индикатора больше верхнего уровня (800), то это сигнал на покупку:

if(randomValue>800.0)
   ExtMapBuffer1[i]=High[i]+5*Point;

// если есть сигнал на покупку, то присваиваем текущему элементу буфера данных наивысшее
// значение текущего бара. Кроме того добавляем 5 пунктов, чтобы стрелочка была немного 
// выше текущей цены. Предопределенная переменная Point используется, чтобы автоматически
// получить множитель для представления пунктов. Иначе нам пришлось бы писать что-то вроде
// этого: ExtMapBuffer1[i]=High[i]+0.0005; 

Иначе, если сигнала на покупку нет, то:

else
   ExtMapBuffer1[i]=0.0;
 
// если сигнала на покупку нет, то присваиваем текущему элементу буфера
// данных "пустое" значение, которое у нас равно 0.0.
// Теперь у нас не будет выводится никаких символов на этом баре.

Если же значение «случайного» индикатора меньше нижнего уровня (200), то это сигнал на продажу:

if(randomValue<200.0)
   ExtMapBuffer2[i]=Low[i]-5*Point;
 
// если это сигнал на продажу, то присваиваем текущему элементу буфера данных наименьшее
// значение текущего бара. Кроме того уменьшаем значение на 5 пунктов, чтобы стрелочка была 
// немного ниже текущей цены.

Иначе, если сигнал на продажу отсутствует, то:

else
   ExtMapBuffer2[i]=0.0;
 
// если сигнала на продажу нет, то присваиваем текущему элементу буфера
// данных "пустое" значение, чтобы не выводить никаких символов на этом баре.

Вот и весь цикл. Откомпилируйте индикатор и запустите его в терминале:

О стиле

Нет, не о том, как правильно подобрать галстук к костюму и рубашке, хотя всегда актуально. Стиль программирования очень важен, если вы не пишите код исключительно для себя. Дело в том, что у каждого программиста свой индивидуальный стиль. Каждый по-разному оформляет циклы, делает разные отступы (а некоторые вообще не делают), объявляет переменные и т.д. Вы должны выработать свой стиль программирования, которого вы будете придерживаться в дальнейшем. Я хочу дать вам пару советов, которые помогут сделать код более читабельным и приятным для восприятия:

  • не пишите много операций в одну строку через точку с запятой (;).
  • пишите названия переменных и функций на английском языке
  • в названиях переменных используйте заглавные буквы как разделители
  • не злоупотребляйте сокращениями, называя переменные и функции
  • делайте отступы строгой длины, чтобы блоки кода были выровнены
  • в каждом новом теле (цикла, условия) делайте дополнительный отступ
  • группируйте объявления однотипных переменных
  • хорошо комментируйте большие и сложные блоки кода
  • хорошо комментируйте написанные вами функции (их назначение, параметры)

Заключение

Сегодня вы кое-чему научились. Вы написали два простых индикатора. Да, они бесполезны, но я ведь не учу вас прибыльно торговать!! Вы получили представление о том, как работают индикаторы, какими параметрами и свойствами они обладают. Вы научились настраивать и работать с буферами. Вы узнали несколько новых функций. Функция iCustom очень важна и будет использоваться в дальнейшем даже в советниках. Если у вас возникают какие-то сложности, то медленно перечитывайте статью еще раз и если проблема осталась, то смело задавайте вопросы на форуме или в комментариях.

Доброго времени суток!

Мы открываем следующий раздел обучения программированию на языке MQL4. Если вы новичок и только зашли в этот блог статей, то предлагаю вам начать изучение с самого первого поста по этой тематике.

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

Создаем свой первый индикатор

индикатор Mql

Индикатор это программа, которая используется как вспомогательный алгоритм для анализа рынка или результатов торговли. Индикатор может содержать в себе информацию о вашем счете, открытых или закрытых ордерах, или может совершать технический анализ и подавать сигналы для открытия ордеров. Т.е. он может делать все, что и Советник, кроме, непосредственно, торговли. Индикатор может отображаться как на графике, так и в отдельном окне (в подвале). Основное его отличие от скрипта в том, что он выполняет заданные действия каждый тик, пока он присутствует на графике, тогда как скрипт удаляется с графика, как только функция OnStart() закончит свою работу.

Ну что же, давайте создадим индикатор, для этого в меню «Файл» редактора Meta Editor необходимо нажать кнопку Создать, либо воспользоваться сочетанием клавиш Ctrl+N. Выбираем пункт «Пользовательский индикатор» и жмем далее. Общие параметры задаются также, как и в Скрипте — вы вводить имя вашего индикатора, имя автора, ссылку на сайт. Все индикаторы хранятся в корневой папке терминала Indicators, путь к которой выглядит так:

C:\Users\Admin\AppData\Roaming\MetaQuotes\Terminal\<Номер терминала>\MQL4\Indicators

Внешние параметры сейчас не нужно задавать, это можно по необходимости выполнить после. Задали имя, жмем далее.

Нам предлагается выбрать необходимые функции обработки событий. По умолчанию нам всегда нужна функция OnCalculate, галочки на OnTimer и OnChartEvent ставить не нужно, мы изучим их в следующих уроках.

Жмем далее.

Галочку «Индикатор в отдельном окне» ставить не нужно, она нужна для подвальных индикаторов, их мы обязательно разберем в будущем. Раздел Отрисовка нужен для создания буферов для индикаторов. Это массивы, которые содержат в себе информацию о линиях, ценах и значениях отдельного индикатора, т.е. нужны для более сложного кода. Найти их можно в «Окне Данных» (Ctrl+D). В нашем случае буферы не нужны, поэтому пропускаем эту графу. Жмем Готово.

Поздравляю вас, товарищи, наш первый индикатор создан! Теперь давайте пройдемся по его изначальному коду.

С 1 по 5 строчку идет закомментированный код, который служит подсказкой для программиста о созданной программе, а именно наименование созданного индикатора, кому принадлежат права и вебсайт. По желанию эти строчки можно удалить, для самого кода они не нужны.

//+——————————————————————+

//| 2.0 Создаем первый индикатор.mq4 |

//| Copyright (c) DaVinci FX Group |

//| https://www.davinci-fx.com/ |

//+——————————————————————+

Далее в строчках 6-10 идут свойства программы. Более подробно о них было рассказано в уроке Скрипт Hello World. 

#property copyright «Copyright (c) DaVinci FX Group»

#property link «https://www.davinci-fx.com/»

#property version «1.00»

#property strict

#property indicator_chart_window

Тут все просто, это изначальные данные, которые мы передаем всей программе, а именно:

  • copyright — кому принадлежит этот индикатор
  • link — ссылка на сайт автора
  • version — номер версии программы. Меняется по мере дополнения в программу новых возможностей.
  • strict — строгий режим проверки на ошибки при компиляции. Параметр введен разработчиками с 600 версией билда и является рекомендуемым в коде.
  • indicator_chart_window — свойство обозначает, что индикатор будет отображать свои данные на графике, а не в отдельном окне.

Функция OnInit

Обязательной функцией, которая должна быть в каждом коде индикатора является функция обработки событий OnInit. Данная функция запускается сразу после загрузки индикатора или советника. Под загрузкой подразумевается установка программы на график, изменение настроек программы, смена таймфрейма текущего графика, либо торгового символа. Т.е. при каждом изменении пользователем параметров графика или программы — данная функция будет автоматически выполнена и код переинициализирован.

Нужна эта функция, чтобы в ней один раз перед работой основного кода сделать необходимые расчеты и проверки, вот несколько примеров:

  • проверить, чтобы период графика был не выше D1, либо меньше M5;
  • чтобы была нажата кнопка автоторговли;
  • установить настройку буферов индикатора: тип, цвет, толщину линий и т.д.;
  • узнать какой тип счета сейчас: Демо или Реальный и если нужно, ограничить торговлю на реале;
  • подключиться к какому-либо вебсайту и скачать необходимые данные;
  • узнать сколько знаков после запятой у данного счета, выполнить преобразование параметров со старыми пунктами на новые;
  • предварительно удалить лишние графические объекты;
  • сохранить в переменной глобального уровня начальный баланс счета и количество уже открытых ордеров;
  • проверить настройки параметров относительно друг друга, и при необходимости поправить недочеты;
  • отключить часть параметров, если сейчас активирован режим тестирования или оптимизации советника.

Как видно, много чего можно сделать сразу после инициализации кода. К примеру, вам нужно проверить на какой символ установлен индикатор, и если эта пара не входит в ваш перечень, то запретить торговлю. Согласитесь, делать эту проверку каждый тик не целесообразно, это только нагрузит оперативную память компьютера. Функция OnInit же только один раз выполнит нужное действие и станет неактивной.

Тип данных этой функции по умолчанию является int, т.е. целое число. Это значит, что после завершения она должна возвращать (return) какое-то значение. Существует три предопределенных значения, которые эта функция может вернуть:

  • INIT_SUCCEEDED — значение по умолчанию у данной функции. Оно обозначает, что инициализация прошла успешна, работа кода будет продолжена.
  • INIT_FAILED — инициализация завершилась неудачней, дальнейшее продолжение работы невозможно.
  • INIT_PARAMETERS_INCORRECT — нужна для обозначения программистом неверных входных параметров, дальнейшее продолжение работы невозможно.

Если задать функции OnInit тип void, то оператор return прописывать не нужно, по умолчанию инициализация пройдет успешно.

Теперь попробуем на практике запустить эту функцию, запишем несколько принтов в строчку:

int OnInit()

{

Print(«Инициализируем индикатор. Запуск через …»);

Print(«3»);

Print(«2»);

Print(«1»);

Print(«Поехали!»);

return(INIT_SUCCEEDED);

}

Смотрим журнал, все наши 4 сообщения отобразились и после них появилась строка с текстом initialized, которая обозначает, что индикатор запущен в работу.

Теперь попробуем завершить инициализацию неудачей. Для этого введем проверку через условный оператор if, в котором осуществим поиск слова «EURUSD» в наименовании текущей валютной пары. Т.е. если данный индикатор будет установлен на график с парой EURUSD, то ему будет разрешено работать, иначе он снимется с графика. Подставьте этот код в ваш индикатор.

int OnInit()

{

if(StringFind(Symbol(),«EURUSD»,0) == 1) {

Print(«Торговля разрешена только по валютной паре EURUSD»);

return(INIT_FAILED);

}

return(INIT_SUCCEEDED);

}

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

Также стоит заметить, что функцию OnInit можно вызывать вручную через пользовательские функции, если необходимо выполнить повторно инициализацию программного кода.

void Re_Init() {

OnInit();

}

Функция OnCalculate

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

int OnCalculate(const int rates_total,

const int prev_calculated,

const datetime &time[],

const double &open[],

const double &high[],

const double &low[],

const double &close[],

const long &tick_volume[],

const long &volume[],

const int &spread[])

{

return(rates_total);

}

Тип данных функции является int, вернуть она должна целое число. По умолчанию она возвращает значение rates_total. У функции 10 формальных переменных, все они являются константами, т.е. пользователь не имеет возможности изменить их значения, да этого и не нужно.

  • rates_total — параметр содержит в себе количество баров на графике, которые доступны индикатору для расчета.
  • prev_calculated — равняется значению, которое вернула функция OnCalculate на прошлом тике. Для лучшего понимая можно вывести эти данные в принт.

int OnCalculate(const int rates_total,

const int prev_calculated,

const datetime &time[],

const double &open[],

const double &high[],

const double &low[],

const double &close[],

const long &tick_volume[],

const long &volume[],

const int &spread[])

{

Print(«Всего баров: «,Bars,«, rates_total: «,rates_total,«, prev_calculated: «,prev_calculated);

return(rates_total);

}

Т.е. при возврате количества баров rates_total и сравнению этого значения с prev_calculated мы всегда можем знать, когда открылась новая свеча или подгрузились новые свечи и тем самым сможем выполнять действия с пересчетом данных только 1 раз за свечу. prev_calculated всегда будет меньше или равен rates_total. Такой способ используется во всех стандартных индикаторах MT4, например возьмем любой осциллятор. После инициализации индикатор рассчитывает значения для каждой доступной свечи в истории, а уже на следующих тиках расчет идет только для текущего, еще открытого бара и можно видеть, как значение линии индикатора перерисовывается каждый тик, пока свеча не закроется, тогда как остальные линии значений остаются неизменными. Этот метод экономит память программы, не нужно совершать каждый раз лишний пересчет данных на истории.

  • time[] — параметр содержит в себе массив значений времени открытия всех свечей на графике;
  • open[] — массив цены открытия всех свечей на графике;
  • high[] — массив цены High всех свечей на графике;
  • low[] — массив цены Low всех свечей на графике;
  • close[] — массив цены закрытия всех свечей на графике;
  • tick_volume[] — массив содержат историю тикового объема всех свечей на графике. Работает не у всех брокеров;
  • volume[] — массив содержат историю торгового объема всех свечей на графике. Работает не у всех брокеров;
  • spread[] — массив, содержащий историю спреда всех свечей на графике. Я им не пользуюсь, не видел, чтобы он корректно работал.

Направление массивов идет от текущей открытой свечи в сторону самого раннего бара на графике, справа налево. Т.е. если вы хотите узнать значение цены открытия первой закрытой свечи, тогда в квадратных скобках массива нужно указать цифру 1.

int OnCalculate(const int rates_total,

const int prev_calculated,

const datetime &time[],

const double &open[],

const double &high[],

const double &low[],

const double &close[],

const long &tick_volume[],

const long &volume[],

const int &spread[])

{

Print(«Цена открытия [1] свечи: «,open[1]);

return(rates_total);

}

Функция OnDeinit

После того, как пользователем подан запрос на завершение работы индикатора, рекомендуется вначале выполнить деинициализацию советника. Функция обработки событий OnDeinit схожа с OnInit, выполняет свое действие один раз при переинициализации или перед удалением программы с графика. Переинициализация может произойти из-за разрыва связи с сервером брокера, смены символа или таймфрейма, изменением входных параметров. Почему-то по умолчанию при создании индикатора она не появляется в коде, но не проблема, мы добавим ее сами.

void OnDeinit(const int reason)

{

}

Тип данных функции void, она не возвращает никакого значения, хотя бы потому, что его некуда возвращать, это последнее действие, которое производит индикатор или советник перед завершением работы. Функция имеет один формальный параметр — константу reason — характеризующий причину деинициализации. Например, когда вы удаляете программу с графика вручную, то можете видеть сообщение: uninit reason 1. Это значит, что причина удаления имеет номер один, что значит «Программа удалена с графика». Посмотрим все причины удаления программы:

REASON_PROGRAM 0 Эксперт прекратил свою работу, вызвав функцию ExpertRemove()
REASON_REMOVE 1 Программа удалена с графика
REASON_RECOMPILE 2 Программа перекомпилирована
REASON_CHARTCHANGE 3 Символ или период графика был изменен
REASON_CHARTCLOSE 4 График закрыт
REASON_PARAMETERS 5 Входные параметры были изменены пользователем
REASON_ACCOUNT 6 Активирован другой счет либо произошло переподключение к торговому серверу вследствие изменения настроек счета
REASON_TEMPLATE 7 Применен другой шаблон графика
REASON_INITFAILED 8 Признак того, что обработчик OnInit() вернул ненулевое значение
REASON_CLOSE 9 Терминал был закрыт

В индикаторах отображаются не все эти причины, этот полный список скорее для Советников, но лишним он точно не будет сейчас. Теперь вы будете замечать и понимать код деинициализации вашего советника или индикатора в журнале «Эксперты». Если будет желание, можете с помощью справки MQL4 написать небольшой код, который бы переводил эти цифры в текст, чтобы более четко видеть причину вызова функции OnDeinit.

Я использую эту функцию в основном, чтобы:

  • очистить график от объектов, которые создал мой программный код (ведь убирать за собой есть одна из обязанностей программиста);
  • чтобы выдать сообщение о том, что работа завершена;
  • подчистить комментарий Comment
  • выдать финальный расчет, будь то количество ордеров, максимальное количество колен у сетки, текущий спред.

Функцию OnDeinit можно также вызвать в любом месте программного кода вручную, только такой метод не удалит индикатор с графика, а только выполнит код, который содержится в ее теле. Для удаления индикатора программно нужно воспользоваться функцией ChartIndicatorDelete, а для удаления советника — функцией ExpertRemove().

Пишем первый индикатор

Это будет очень простой код, который мы усовершенствуем в следующей статье.

Для начала прописываем в функции OnInit какой-либо текст для практики, чтобы увидеть, что она активируется:

int OnInit()

{

Print(«Инициализация индикатора»);

return(INIT_SUCCEEDED);

}

Далее в функции OnCalculate мы введем комментарий (ссылка на статью о Comment), который будет отображаться в верхнем левом углу графика и будет выдавать нам значение текущего спреда, обновляя данные каждый тик:

int OnCalculate(const int rates_total,

const int prev_calculated,

const datetime &time[],

const double &open[],

const double &high[],

const double &low[],

const double &close[],

const long &tick_volume[],

const long &volume[],

const int &spread[])

{

double spr = MarketInfo(Symbol(),MODE_SPREAD);

Comment(«Текущий спред: «,spr);

return(rates_total);

}

Так как спред рассчитывается в новых пунктах для счетов с 3 или 5 знаками, то мы введем переменную глобального уровня PipsDivided и добавим проверку в функцию OnInit, где PipsDivided будет равняться единице, если счет в старых пунктах и 10, если в новых, чтобы на это значение поделить полученный спред и отображать его по классике с запятой.

int PipsDivided = 1;

int OnInit()

{

if(Digits == 3 || Digits == 5) PipsDivided=10;

Print(«Инициализация индикатора»);

return(INIT_SUCCEEDED);

}

Изменяем расчет переменной spr в теле функции OnCalculate и округляем значение до одного знака после запятой через функцию NormalizeDouble:

double spr = NormalizeDouble(MarketInfo(Symbol(),MODE_SPREAD)/PipsDivided,1);

Чтобы индикатор содержал чуть больше информации, мы введем внешнюю переменную Max_Spread типа double, которые будет нам сигнализировать текстом о том, что спред находится на допустимом уровне (Normal), либо же завышен (Too High) и в рынок сейчас входить не рекомендуется.

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

extern double Max_Spread = 5;

int OnCalculate(const int rates_total,

const int prev_calculated,

const datetime &time[],

const double &open[],

const double &high[],

const double &low[],

const double &close[],

const long &tick_volume[],

const long &volume[],

const int &spread[])

{

double spr = NormalizeDouble(MarketInfo(Symbol(),MODE_SPREAD)/PipsDivided,1);

string condition = » — Normal»;

if(spr >= Max_Spread) condition = » — Too High!»;

Comment(«Текущий спред: «,spr,condition);

return(rates_total);

}

Условий оператор if по желанию можно заменить на оператор ? для сокращения количества строчек программного кода:

string condition = (spr >= Max_Spread ? » — Too High!» : » — Normal»);

Так как значение комментария Comment не очищается само собой после удаления индикатора с графика, то мы вручную очистим его в пригодившейся функции обработки событий OnDeinit, оставив в его значении пустые кавычки:

void OnDeinit(const int reason)

{

Comment(«»);

}

Готовый программный код будет иметь следующий вид:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

//+——————————————————————+

//| 2.0 Создаем первый индикатор.mq4 |

//| Copyright (c) DaVinci FX Group |

//| https://www.davinci-fx.com/ |

//+——————————————————————+

#property copyright «Copyright (c) DaVinci FX Group»

#property link «https://www.davinci-fx.com/»

#property version «1.00»

#property strict

#property indicator_chart_window

extern double Max_Spread = 5;

int PipsDivided = 1;

//+——————————————————————+

int OnInit()

{

if(Digits == 3 || Digits == 5) PipsDivided=10;

Print(«Инициализация индикатора»);

return(INIT_SUCCEEDED);

}

//+——————————————————————+

void OnDeinit(const int reason)

{

Comment(«»);

Print(«Деинициализация индикатора»);

}

//+——————————————————————+

int OnCalculate(const int rates_total,

const int prev_calculated,

const datetime &time[],

const double &open[],

const double &high[],

const double &low[],

const double &close[],

const long &tick_volume[],

const long &volume[],

const int &spread[])

{

double spr = NormalizeDouble(MarketInfo(Symbol(),MODE_SPREAD)/PipsDivided,1);

string condition = (spr >= Max_Spread ? » — Too High!» : » — Normal»);

Comment(«Текущий спред: «,spr,condition);

return(rates_total);

}

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

Подписывайтесь на наши уведомления, чтобы не пропустить новые уроки! Во вложении как всегда код .mq4

0. Начало работы

Форекс — торговые стратегии, советники, индикаторы, видео обучение торговле


Написание MQL индикатора

Господа трейдеры, друзья, коллеги, мы продолжаем наши уроки по MQL. А хотелось бы Вам написать свой собственный форекс индикатор? Наверняка у Вас есть какая-то секретная формула расчёта движения цены, которую Вы бы хотели отразить на графике и не знаете как? В этом  уроке мы с вами научимся писать индикаторы под Metatrader 4, свои собственные, для себя.

Как написать форекс индикатор для Metatrader 4

Домашнее задание

Написание Индикатора домашнее задание

В этом задании я предлагаю реализовать аналог индикатора Moving Average, но не скопировать его, а как раз использовать среднюю цену между High и Low свечи, это ведь не так сложно, правда? Ну а затем уже реализовать в индикаторе свою секретную формулу, с этим уже проблем наверняка не будет. Обсудим все вопросы на форуме:

Тема на форуме

С уважением, Сергей aka xbms
TradeLikeaPro.ru

ru_banner_460_80

Создание

Для создания пользовательских индикаторов используются язык программирования торговых стратегий MetaQuotes Language 4 (MQL4) и редактор редактор MetaEditor. Чтобы запустить редактор советников, необходимо выполнить команду «Создать» контекстного меню окна «Навигатор — Пользовательские индикаторы», команду меню «Сервис — Редактор MetaQuotes Language», нажать клавишу F4 или кнопку Редактор MetaEditor панели «Стандартная». При создании MQL4-программы автоматически откроется мастер создания советников, позволяющий быстро создавать новые MQL4-программы. В нем в качестве типа создаваемого объекта необходимо указать «Пользовательский индикатор»

custom_ind_create

и заполнить поля требуемыми данными:

custom_ind_create_name

  • Имя — название индикатора;
  • Автор — имя автора;
  • Ссылка — адрес сайта разработчика;
  • Параметры — список входных параметров индикатора. Чтобы добавить новый параметр, необходимо нажать кнопку «Добавить», чтобы удалить — кнопку «Удалить».

Затем нужно определить, будет ли новый индикатор создаваться в отдельном подокне и какие у него будут границы. Кроме того, необходимо задать количество и параметры индикаторных массивов. Значения элементов индикаторных массивов используются для отображения на графике линий. Иными словами, при определении индикаторных массивов происходит определение линий будущего индикатора. После этого в редакторе открывается окно нового индикатора с заданными параметрами. Файл с исходным текстом (*.MQ4) индикатора автоматически помещается в папку /EXPERTS/INDICATORS клиентского терминала. С этого момента можно приступать к написанию текста пользовательского индикатора.

По завершении разработки необходимо скомпилировать индикатор. Для этого в редакторе советников следует выполнить команду меню «Файл — Компилировать», нажать клавишу F9 или кнопку Компилировать панели инструментов. В результате успешной компиляции создается исполняемый файл программы с расширением *.EX4, автоматически помещаемый в папку /EXPERTS/INDICATORS. Список пользовательских индикаторов можно просмотреть в окне «Навигатор — Пользовательские индикаторы» клиентского терминала.

Редактирование пользовательских индикаторов

Чтобы приступить к редактированию уже существующего индикатора из терминала, необходимо выполнить команду «Изменить» контекстного меню окна «Навигатор — Пользовательские индикаторы». При этом откроется редактор MetaEditor, в который уже будет загружен исходный код выбранного индикатора. После изменения исходного кода аналитического инструмента необходимо повторно скомпилировать его и получить новый исполняемый EX4-файл. В противном случае в терминале будет использоваться неизмененная, предыдущая версия индикатора.

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

Рождение эксперта. Простейший индикатор на MQL4.

Для того, чтобы научиться самому писать индикаторы на MQL4, необходимо разобраться в том, как происходит построение линий индикатора. Зачем нужны те или иные функции и параметры. Сложного в этом ничего нет, просто надо сесть и попробовать написать свой первый индикатор.

Главное — начать.
Предлагаю начать с самого простого – нарисовать индикатор, который рисует линию, соединяющую цены High баров на графике. Этот, достаточно простой пример, позволит понять принцип построения индикаторов на графике.

Для начала определим специфические параметры индикатора с помощью указателя #property:

#property  copyright "autoforex"
#property  link ""

С этими значениями все понятно, они обычно вставляются во все программы MQL4 и не являются обязательными, но лучше их все же вписать в программу (естественно подставляя свои значения). Эти данные нужны лишь для того, чтобы определить автора скомпилированного файла. Сам файл зашифрован и единственное, что можно разобрать в начале этого файла, открыв его в текстовом редакторе, это информацию, описанную в этих двух строчках.

Внешние настройки индикатора.
Далее определяем основные настройки, влияющие на рисование индикатора:

#property  indicator_chart_window

Указываем терминалу, что индикатор необходимо рисовать в том же окне, где и сам график. Для того, чтобы индикатор строился в отдельном окне необходимо заменить эту строку на — #property indicator_separate_window.

Затем, определяем количество буферов для расчета индикатора:

#property  indicator_buffers 1

Или, другими словами — определяем, сколько линий мы будем рисовать на графике. В нашем простейшем случае, это будет одна линия, поэтому нам нужен всего один буфер. В случае, например, с индикатором «Alligator» на графике изображаются три линии – поэтому требуется три буфера и в коде alligator.mq4, который поставляется вместе с MetaTrader 4, вы обнаружите следующую строчку кода: #property indicator_buffers 3.

Далее задаем цвет — Red (красный), которым будет отображаться линия:

#property  indicator_color1  Red

Для каждой линии можно задать свой цвет. В нашем случае будет использована только одна линия для индикатора, поэтому и цвет задаем только для одной — первой линии. В том же alligator.mq4 цвет линий задан следующим образом:

#property indicator_color1 Blue
#property indicator_color2 Red
#property indicator_color3 Lime

Три линии – три цвета.

Далее определяем толщину линии:

#property  indicator_width1 2

Здесь мы задаем толщину линии индикатора равную 2. Теперь линия под номером один будет изображаться на графике толщиной равной 2.

Затем определяем стиль рисования линии:

#property  indicator_style1 0

Стиль рисования линии 0 – простая линия. Можно выбрать из пяти различных стилей – пунктирная линия, штрихпунктирная и др. Заметьте, что различные стили рисования линии можно применять только в том случае, если ширина данной линии равна единице. Если ширина линии больше единицы, то терминал позволяет рисовать только сплошной линией. Поэтому в нашем случае, т.к. выбрана толщина линии 2 пикселя, мы ставим нулевой стиль. Поставь мы другой стиль, наш индикатор все равно будет нарисован простой линией.

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

Если внешние значения индикатора прописать в коде программы, как это сделали мы для цвета, толщины линии и стиля рисования, то при наложении индикатора на график так же появится окно с настройками. Но в окне уже будут выбраны соответствующие цвет толщина линии и стиль ее рисования, поэтому останется лишь подтвердить выбор, нажав «ОК». Это существенно экономит время даже в нашем, простейшем случае, я уже не говорю об индикаторах, в которых отображаются все восемь линий.

Затем, в программе идет объявление массива Buffer1[]:

Именно этот массив мы свяжем с нашей линией индикатора. Поскольку наша линия в коде фигурирует под номером один, то и название для массива, мы выбрали соответствующее.

Для чего нужен этот массив. Этот массив мы, с помощью функции SetIndexBuffer(), свяжем с предопределенным буфером индикатора. И затем, чтобы нарисовать линию на графике, мы просто запишем в массив Buffer1[] соответствующие значения для данной линии. А терминал, воспринимая наш массив Buffer1[], как массив со значениями, которые следует отобразить на графике, нарисует линию. И любые изменения в массиве Buffer1[] будут отображаться на графике.

Инициализация.
Далее в коде следует функция инициализации индикатора:

int init()
  {
   SetIndexBuffer(0,Buffer1);
   SetIndexStyle(0,DRAW_LINE);
   return(0);
  }

Функция init() – функция инициализации, в данном случае индикатора, вызывается один раз при наложении индикатора на график. Внутри этой функции производят настройку индикатора – выполняют операции, которые необходимо выполнить один раз до запуска индикатора.

SetIndexBuffer(0,Buffer1); — как упоминалось ранее, эта функция связывает массив Buffer1[] с нулевым буфером индикатора. Всего буферов – 8 (считая с 0 до 7), т.е. с помощью одного индикатора можно нарисовать на графике восемь линий. Но т.к. в нашем случае отображается всего лишь одна линия, то нам требуется только один буфер – буфер номер 0. Теперь, чтобы записать информацию в нулевой буфер, тем самым, отобразив ее на графике, необходимо записать ее в массив Buffer1[].

SetIndexStyle(0,DRAW_LINE); — устанавливаем новый тип, стиль, ширину и цвет для линии индикатора (нулевой буфер индикатора).
Первый параметр, равный 0 — номер буфера линии. Второй параметр, равный DRAW_LINE задает тип рисования линии. В нашем случае – простая линия. Третий, четвертый и пятый параметры этой функции – необязательные параметры. С их помощью можно изменить стиль, толщину и цвет отображения линии соответственно. В нашем случае в этом нет необходимости, т.к. нужные нам значения уже определены в начале программы с помощью инструкций #property и поэтому эти параметры отсутствуют в вызове функции SetIndexStyle().

return(0); – завершаем выполнение функции init();

Индикатор.
Теперь переходим к самому главному – рисованию линии индикатора на графике. Давайте, для начала соединим значения High первых семи баров на графике, при этом полный код индикатора примет вид:

#property  copyright "autoforex"
#property  link      ""

#property  indicator_chart_window
#property  indicator_buffers 1
#property  indicator_color1  Red
#property  indicator_width1 2
#property  indicator_style1 0

double     Buffer1[];

int init()
  {
   SetIndexBuffer(0,Buffer1);
   SetIndexStyle(0,DRAW_LINE);
   return(0);
  }

int start()
  {
         Buffer1[0]=High[0];
         Buffer1[1]=High[1];
         Buffer1[2]=High[2];
         Buffer1[3]=High[3];
         Buffer1[4]=High[4];
         Buffer1[5]=High[5];
         Buffer1[6]=High[6];
         Buffer1[7]=High[7];
         Buffer1[8]=High[8];

         return(0);
  }

Вот что мы получим в результате, когда прикрепим индикатор к графику:

Построение индикатора на графике

На рисунке представлен наш индикатор, который нарисован по следующим значениям:

Buffer1[0]=1.4767
Buffer1[1]=1.4707
Buffer1[2]=1.4784
Buffer1[3]=1.4842
Buffer1[4]=1.4857
Buffer1[5]=1.4906
Buffer1[6]=1.4886
Buffer1[7]=1.4966
Buffer1[8]=1.4872
Buffer1[9]=Empty_Value
Buffer1[10]=Empty_Value
и т.д.

Как видите, первые девять баров в точках High, начиная с нулевого, соединены линией индикатора.

Массив Buffer1[].
Несколько слов по поводу размера массива Buffer1[]. Размер массива Buffer1[] равняется количеству баров на текущем графике (т.е. на том, на котором находится индикатор). И, соответственно, совпадает со значением предопределенной переменной Bars. Если, например, у вас на текущем графике 56300 минутных баров, и вы запускаете индикатор на этом графике, то размер массива Buffer1[] будет равен 56300 ячеек. Это также означает, что на этом графике мы сможем построить линию состоящую из 56300 точек.

В нашем простейшем случае мы определили значения первых девяти точек нашего индикатора, поэтому они были построены на графике. Все остальные ячейки массива Buffer1[], по умолчанию, принимают значение равное EMPTY_VALUE (значение пустой величины). Пустые значения не рисуются на графике, вот почему мы видим линию индикатора только до восьмого бара. Все остальные значения индикатора равны пустому значению EMPTY_VALUE и не отображаются на графике.

Построение индикатора.
Теперь давайте разберемся, как происходит построение индикатора на графике.
Мы скомпилировали наш индикатор и наложили его на график, например, EUR/USD H1 для которого уже загружено, например, 674 бара (цифра взята из головы, просто для демонстрации примера).

При этом произойдет следующее:

Будет создан массив Buffer1[] размером 674 ячейки, во все ячейки по умолчанию запишется значение EMPTY_VALUE. Затем выполнится функция start() и в ней первым девяти ячейкам массива Buffer1[] будут присвоены определенные значения цен, которые и отобразятся в виде индикатора на графике.

С приходом нового тика, продолжающего формировать нулевой бар, будет выполнена функция start(). И снова в ней первым девяти ячейкам массива Buffer1[] будут присвоены те же самые значения цен, которые уже записаны в этом массиве (т.к. бары на графике не изменились и значения High этих баров так же не изменились). Исключение составляет лишь нулевая ячейка массива – Buffer[0]. В нее будет записано новое значение величины High нулевого бара (если, конечно, оно изменилось с приходом нового тика).

Получается, что большинство точек индикатора не нуждаются в пересчете и перерисовке. Для того, чтобы оптимизировать построение индикатора, пользуются функцией IndicatorCounted(), … но об этом в другой статье.

Появление нового бара на графике.
Теперь, давайте разберем ситуацию, когда с приходом нового тика начинает формироваться новый бар. В этом случае новый бар, который выглядит как черточка, автоматически становится нулевым баром, а тот, что был нулевым – становится теперь первым, и таким образом смещается вся нумерация баров. При этом значение переменной Bars увеличивается на 1 и становится равной 675, и размер массива Buffer1[] также увеличивается на 1 и тоже становится равным 675.

Кроме того, что размер массива Buffer1[] увеличивается на единицу, все значения хранимые в нем смещаются на одну ячейку – то, что раньше хранилось в ячейке Buffer1[0], копируется в ячейку Buffer1[1], то, что раньше хранилось в Buffer1[1] копируется в Buffer1[2] и т.д. Из ячейки Buffer1[8] значение копируется в Buffer1[9]. Более наглядно этот процесс показан на рисунках:

Построение индикатора на графике
Построение индикатора на графике

Все эти смещения нумераций и перемещения данных в массиве Buffer1[] происходят в автоматическом режиме. После этого начинает выполняться функция start() индикатора. В ней происходит присвоение значений первым девяти ячейкам массива Buffer1[]. Таким образом, первые девять точек индикатора (Buffer1[0] – Buffer1[8]) будут заново определены. Но, т.к. в результате смещения нумерации баров в ячейку Buffer1[9] уже скопировано значение из ячейки Buffer1[8], то наш индикатор будет уже состоять из десяти точек соединенных линией.

Таким образом, при появлении новых баров, линия нашего индикатора будет удлиняться, включая в наш индикатор новые бары.

Функцию start() можно значительно сократить и записать так:

int start()
  {
    Buffer1[0]=High[0];
    return(0);
  }

Такой индикатор будет соединять линией цены High всех вновь появляющихся баров, в реальном времени.

Индикатор на весь график.
Для того чтобы построить линию на протяжении всего графика, а не только на первых или появляющихся в реальном времени барах, нужно воспользоваться циклом и присвоить все необходимые значения соответствующим ячейкам массива Buffer1[]. Для этого перепишем функцию start() следующим образом:

int start()
  {
   for(int i=0;i<Bars;i++)
   {
      Buffer1[i]=High[i];
   }
   return(0);
  }

В данном случае мы перебираем все бары от 0 до Bars-1 и присваиваем ячейкам массива Buffer1[] значения High соответствующего бара. При этом линия индикатора будет нарисована на протяжении всего ценового графика.

Наш индикатор выглядит теперь так:

#property  copyright "autoforex"
#property  link      ""

#property  indicator_chart_window
#property  indicator_buffers 1
#property  indicator_color1  Red
#property  indicator_width1 2
#property  indicator_style1 0

double     Buffer1[];

int init()
  {
   SetIndexBuffer(0,Buffer1);
   SetIndexStyle(0,DRAW_LINE);
   return(0);
  }
int start()
  {
   for(int i=0;i<Bars;i++)
   {
      Buffer1[i]=High[i];
   }
   return(0);
  }

Вот, в принципе, и все. Простейший индикатор построен и теперь, вы можете поэкспериментировать с настройками и параметрами, влияющими на рисование индикатора.

Продолжение следует …

Смирнов П.В.

Понравилась статья? Поделить с друзьями:
  • Дицинон 250 таблетки как принимать инструкция по применению
  • Главный штаб вмф руководство
  • Нилутамид инструкция по применению цена отзывы аналоги
  • Power bank s330 инструкция по эксплуатации на русском
  • Грунт по пластику reoflex инструкция по применению