Язык MQL4 для "чайников". Пользовательские индикаторы (часть 2)
Это пятая статья из цикла "Язык MQL4 для 'чайников'". Сегодня мы научимся использовать графические объекты - очень мощное средство разработки, которое позволяет существенно расширить возможности индикаторов. Кроме того, вы можете использовать их также в скриптах и советниках. Мы узнаем как создавать объекты, изменять их параметры, проверять ошибки. Конечно, мне не удастся описать полностью все объекты, их слишком много. Но вы получите все необходимые знания, чтобы разобраться в этом самостоятельно. Также в этой статье содержится пошаговое руководство-пример по созданию сложного сигнального индикатора. На основе последнего, вы сможете самостоятельно создавать любые сигнальные индикаторы, которые показывают торговые сигналы на всех периодах по нескольким индикаторам. При этом, многие параметры будут доступны пользователю для настройки, что позволит гибко изменять внешний вид.
Что такое графические объекты?Вы часто сталкиваетесь с ними, когда работаете в терминале Meta Trader 4. Вы можете использовать графические объекты для совершенно разных целей. Трейдеры расставляют уровни поддержки и сопротивления, точки разворота, уровни Фибоначчи и многое другое. Давайте посмотрим на простой пример использования объектов:
На этот график было добавлено 4 графических объекта:
- 2 горизонтальные линии
- текстовый объект
- объект-символ (стрелка)
Сегодня мы научимся добавлять такие объекты с помощью MQL4. Представьте себе, сколько рутинных действий вы можете автоматизировать, используя объекты! Например, вам когда-нибудь приходилось рассчитывать точку разворота, уровни поддержки и сопротивления, а потом вручную их рисовать? Да, работы там немного, но автоматизировав этот процесс на MQL4, терминал сам все рассчитает и нарисует соответствующие уровни. Все что вам нужно будет сделать – это дважды кликнуть по названию скрипта, чтобы все сделали за вас. Кроме того, используя графические объекты, можно писать очень полезные сигнальные индикаторы.
Концепция работы с объектамиВ MQL4 с любыми графическими объектами работают примерно в такой последовательности:
- создание объекта
- изменение его параметров (перемещение, изменение цвета, стиля и т.д.)
- уничтожение объекта
Получается вот такой «жизненный цикл». Сейчас мы рассмотрим каждую из стадий более детально.
Создание графического объектаЧтобы нарисовать любой графический объект используют одну универсальную функцию – ObjectCreate(). Вот ее прототип:
Функция возвращает true, если все нормально, и false, если объект не может быть создан и возникла ошибка. Чтобы узнать код ошибки, нужно использовать функцию GetLastError():
Зачем нужен код ошибки? По нему вы сможете найти описание ошибки и устранить ее при возможности. Все описания кодов находятся: Справочник MQL4 -> Стандартные константы -> Коды ошибок.
Рассмотрим все аргументы функции ObjectCreate():
- name – уникальное название объекта. Вы не можете создать 2 объекта с одним названием. Это название потом будет использоваться в других функциях, чтобы изменять параметры отображения объекта, перемещать его.
- type – тип объекта. Все типы объектов, которые можно создать находятся: Справочник MQL4 -> Стандартные константы -> Типы объектов. Очень важно отметить, что от типа объекта зависит, нужно ли использовать последние аргументы функции. Посмотрите еще раз на прототип. Последним 4 аргументам присвоены значения по-умолчанию. И вот почему: разные объекты требуют различное количество информации, чтобы создать их. Смотрите, все очень просто. Допустим, вам сейчас нужно нарисовать точку. Какая вам информация потребуется? Очевидно, координаты точки. Например, сколько отложить клеток слева и сверху. Все. Этого будет достаточно, не так ли? А чтобы нарисовать прямоугольник, сколько понадобится точек? Правильно, уже целых 2. Это координаты верхней левой точки и правой нижней. Тоже самое и с функцией ObjectCreate(). Она ведь универсальна, поэтому, если она рисует горизонтальную линию, то ей нужны координаты только одной точки, а если это отрезок прямой, то понадобится уже 2. А если вам захочется нарисовать еще и треугольник, то вы задействуете уже 3 точки. Поэтому, создавая какой-то объект, внимательно изучите, сколько точек нужно задать, чтобы его нарисовать.
- window – номер окна, в котором рисовать. Если нужно нарисовать объект на графике цен, то есть в главном окне, то использовать нужно только значение 0.
- time1 – координата Х первой точки. Так как по оси Х в терминале находится время, то нужно указывать значение времени. Например, чтобы узнать время последнего доступного бара, можно использовать предопределенный массив Time[], вот так: Time[0].
- price1 – координата У первой точки. На этой оси в терминале отображается цена, поэтому нужно использовать значения цен. Например, использовать предопределенные массивы Open[], Close[] и т.д.
остальные аргументы – это всего лишь еще 2 пары аналогичных координат, которые определяют точки для рисования более сложных объектов. Если объект простой, то эти параметры не используются.
Пример создания объектов. Рисуем линииТеперь, чтобы хорошенько разобраться, нарисуем парочку линий. Отметим максимальную и минимальную цену за последний день. Для этого создадим новый скрипт и изменим функцию start():
Конечно, мы упустили проверку на ошибки. Так что, если вы введете 2 одинаковых названия для обоих объектов, я не виноват. На деле, когда запустите скрипт, это должно выглядеть примерно так:
Линии нарисованы, но есть нечто, что мне совершенно не нравится! Этот насыщенный красный, посмотрите, просто ужасный цвет, всегда используйте только оттенки. Шутка. Конечно, вам еще много чего может не понравиться. Например, толщина линий, их стиль.
Изменение свойств объекта. Настраиваем внешний вид линийПоэтому существует другая универсальная функция, которая позволяет настроить все параметры любого уже созданного ранее графического объекта. Это – ObjectSet(). Прототип:
Как и предыдущая функция, эта возвращает true, если все прошло нормально и false, если возникли проблемы. Например, вы указали несуществующее название объекта. Рассмотрим все аргументы этой функции:
- name – название уже созданного объекта. Убедитесь, что объект с таким названием уже создан, перед тем как пытаться что-то изменить.
- index – индекс свойства объекта, которое нужно изменить. Все индексы свойств можно найти: Справочник MQL4 -> Стандартные константы -> Свойства объектов. Это ведь тоже универсальная функция. Она работает по такому простому принципу: вы указываете, что изменить, какое свойство, а потом указываете, какое значение присвоить этому свойству.
- value – а это и есть, то самое значение, на которое нужно изменить выбранное свойство. Например, если вы изменяете цвет, то здесь должно быть указано значение какого-то цвета, логично, не так ли?
Теперь давайте изменим наши линии, а именно, цвет, толщину и стиль. Измените функцию start() того же скрипта:
На графике вы должны увидеть что-то похожее на это:
Удаление объектовВам часто понадобится удалять лишние, устарелые и просто ненужные объекты. Для этого существует несколько функций, вот они:
Эта функция удаляет объект с указанным названием. Если вы укажите название объекта, который существует лишь в вашем воображении, то вам будет возвращено false.
Это более продвинутая функция, которая возвращает количество удаленных объектов. Как видите, тут есть даже значения по-умолчанию. Если вы не укажите никаких параметров, то терминал удалит все объекты на активном графике:
Если вы создали объекты в подокне (например, в окне какого-то индикатора), то указав в первом аргументе номер подокна, можно полностью избавится от объектов в нем. Сейчас указывайте в первом аргументе всегда 0, так как с подокнами мы разберемся позже.
Если вам нужно удалить все объекты определенного типа, то укажите во втором аргументе тип объектов-смертников:
Как этим всем правильно пользоваться?Вы можете подумать, что нужно много знать, чтобы разбираться в чем-то подобном. Например, что нужно выучить все эти свойства и типы объектов, и держать их в голове. Чушь! Сейчас я объясню вам, как вы должны писать подобный код, при этом ничего не зная, ведь все есть в справке!
Смотрите. Для начала откройте Инструментарий (CTRL+T). Там внизу есть много закладок, выберите Справка. Теперь представим, что вы хотите нарисовать какой-то новый графический объект, но пока не умеете этого делать. Для этого вы используете функцию ObjectCreate(), напишите ее, аргументы пока пропустите. Теперь курсор подведите внутрь названия функции и нажмите F1. Вуаля! Посмотрите, в справке отобразилась информация об этой функции. То есть даже ничего искать не нужно. Теперь посмотрите на описание функции. После него следует описание всех аргументов, как в этом уроке. Обратите внимание на описание аргумента type:
Да, это обычная ссылка. Просто кликаем по ней и смотрим, какие вообще существуют графические объекты. Допустим, вам понравился эллипс:
Внимательно читаем описание: нам пишут, что нужно 2 координаты. Приступаем:
Также сказано, что свойство OBJPROP_SCALE устанавливает соотношение сторон, поэтому если выставить его в единицу, то получится обычный круг:
Уверен, что такого круга у вас не получилось, так как сначала нужно выставить единичный масштаб в свойствах графика (Нажимаем правую кнопку мыши на любом пустом месте графика и выбираем Свойства):
Как видите все достаточно просто. Важно отметить, что вы можете подвести курсор на любое ключевое слово в коде и нажать F1, после чего вы будете автоматически перемещены на соответствующую статью в справке. Таким образом, вы можете ничего не зная о названиях констант типов и свойств, все равно, быстро и эффективно писать код, используя встроенную справку. В MetaEditor-е есть еще одно важное свойство, которое упрощает написание кода: когда прописываете аргументы в какой-то встроенной функции, нажмите комбинацию CTRL + SHIFT + пробел. После этого появится подсказка с прототипом функции, очень удобно и запоминать ничего не нужно:
Создание графических объектов в подокнахЕсли вы захотите рисовать графические объекты в подокнах графика, например, в окне пользовательского индикатора, то вам нужно будет сначала узнать номер этого подокна. Для наглядного примера напишем простой индикатор, который будет рисовать горизонтальную линию в собственном окне. Создайте новый пользовательский индикатор и допишите код:
Запустите индикатор. И о ужас, никакой линии нет!
Спокойно, поменяйте период графика.
Появилась… Что происходит? Дело в том, что узнать номер подокна в функции init(), когда она запускается первый раз индикатором, нельзя. Возможно, это связано с тем, что подокно еще не создано терминалом во время инициализации. Звучит умно, но как это исправить? Нужно просто все делать в фукции start(), когда окно уже создано, например так:
Теперь все будет рисоваться с первого раза. Из этого всего вы должны запомнить, что номер подокна нужно узнавать в функции start(), а не init().
ПотренируйтесьПопробуйте, используя справку, самостоятельно разобраться в нескольких новых типах графических объектов. После этого напишите скрипт, который будет сам их рисовать и настраивать какие-то параметры по вашему усмотрению. Набейте руку, освойтесь и только после этого читайте дальше.
Пишем сигнальный индикатор. А что это такое?Представьте себе ситуацию. Трейдер использует несколько индикаторов для принятия решений о входе на рынок: Moving Average, Parabolic SAR и Williams’ Percent Range. Это все встроенные в терминал индикаторы, которые вместе выглядят примерно так:
Трейдер постоянно оценивает сложившуюся ситуацию на рынке следующим образом. Он полагает, что входить в рынок нужно тогда, когда имеется сразу 3 сигнала от всех этих индикаторов:
- Если быстрая средняя поднимается выше медленной, то это сигнал на покупку. Если наоборот, то на продажу.
- Если цена ниже показателя Parabolic SAR, то это сигнал на продажу и наоборот.
- Если WPR больше -20, то это сигнал на покупку. Если WPR меньше -80, то это сигнал на продажу.
Трейдеру приходится постоянно проверять все условия, при этом он старается следить за ситуацией на нескольких периодах. Таким образом, его работа превращается в нескончаемую пытку для глаз и ему остается только мечтать о сигнальном индикаторе, который бы делал все проверки сам:
Сегодня мы осуществим мечту этого трейдера. Мы напишем сигнальный индикатор, который будет очень гибко настраиваться (внешний вид). Кроме того, вы без проблем сможете на его основе создать свою модификацию с вашими любимыми индикаторами.
ОсноваПри написании подобного индикатора возникает проблема с рисованием. Ведь все графические объекты рисуются, используя такие координаты, как цена и время. Из-за этого довольно сложно добиться того, чтобы все, что мы рисуем, все время оставалось на своем месте. Пришлось бы постоянно изменять координаты всех объектов. При этом, если вы захотите посмотреть, что происходило раньше и прокрутили бы график, то и вся таблица сигналов также сдвинулась бы. Но из любого правила есть исключение. В арсенале графических объектов есть OBJ_LABEL. Это текстовая метка, которая использует для позиционирования не цену и время, а координаты относительно окна в пикселях. Это очень просто, смотрите:
Мы видим обычную текстовую метку «Х». Если зайти в свойства этого объекта, то вы увидите, что координаты задаются в пикселях. Пиксель – это мельчайшая точка на экране. Обратите внимание, что левый верхний угол окна имеет координаты x=0 и y=0 (0,0). Если увеличивать x, то объект будет двигаться вправо, если уменьшать – влево. Аналогично с координатой y. Если ее увеличивать, то объект будет двигаться вниз, а если уменьшать – вверх. Важно понять и запомнить этот принцип. Чтобы быстрее разобраться, создайте одну метку, передвигайте ее и смотрите, как изменяются координаты в свойствах. Также посмотрите старые котировки, пролистав график. Обратите внимание, что при этом метка неподвижна. Используя такие метки как основу, мы можем создать сигнальный индикатор, который будет избавлен от всех недостатков, о которых писалось выше.
Возможности текстовой меткиНаш сигнальный индикатор будет использовать только текстовые метки, поэтому давайте познакомимся с ее возможностями поближе. Для начала создайте новый индикатор (не используйте буферы данных и параметры) и измените функцию init():
Как видите, все довольно просто. Функцию ObjectCreate() мы будем использовать только при инициализации для создания всех необходимых объектов. А с помощью ObjectSetText() будем изменять внешний вид объектов при каждом изменении цены в функции start(). Также сейчас нужно изменить функцию deinit():
Теперь запустите индикатор и посмотрите на результат:
Мы будем использовать следующие возможности метки:
- изменим шрифт на Wingdings, чтобы нам стали доступны специальные символы-значки (от квадратиков и кружочков до смайликов):
- будем изменять цвет и текст метки
- будем менять расположение и размер метки
Сейчас давайте создадим метки с использованием шрифта Wingdings. Изменим функцию start():
Смотрим на результат:
Рисуем макет таблицы сигналовСейчас давайте нарисуем макет таблицы сигналов. Это будет просто куча квадратиков:
Макет готов. Давайте добавим отступы слева и сверху, чтобы таблица не заслоняла текст терминала:
Оживляем макетТеперь давайте заставим работать хотя бы один из квадратиков. Допустим, верхний левый будет показывать сигнал скользящих средних на минутном таймфрейме (M1). Если сигнал на покупку, то квадратик будем закрашивать в зеленый цвет, если на продажу, то в красный. Для этого нужно изменить функцию start(), поехали:
Оживляем верхний рядПродолжим оживление. Самому левому квадратику соответствует самый маленький таймфрейм – M1. Теперь давайте сделаем так, чтобы каждый следующий квадратик (по горизонтали) отвечал за более крупный таймфрейм. Так, например, второй квадрат будет показывать сигналы на M5, третий – на M15 и так далее до MN1. Конечно, все это мы будем делать в цикле. Подумайте сами. Ведь все что изменяется, это только название метки, к которой мы обращаемся и период. Квадратиков всего 9, поэтому используем один счетчик. Но тут возникает проблема с периодами, так как он изменяется без закономерности. Смотрите сами:
Первая мысль: цикл тут не прокатит. А вот и нет! Все что нужно сделать – это объявить специальный массив в самом начале кода индикатора, смотрите:
Все периоды записаны в массиве, теперь их можно без проблем использовать в цикле:
Мы используем массив period[] как таблицу соответствия значения счетчика «х» и периода. Представьте себе, сколько кода пришлось бы написать, если бы не этот маленький массивчик! Смотрим на результат, первый ряд сигнальных квадратиков готов:
Добавляем надписиОчень неплохо, но непонятно какой квадратик какому таймфрейму соответствует, поэтому создадим поясняющие надписи над квадратиками. Тут мы также применим «массив соответствий», который будет хранить надписи для каждого столбца:
Надписи будем создавать в init() используя цикл:
Добавим немного параметровПора сделать индикатор более гибким, добавить парочку параметров, чтобы пользователь мог сам настраивать внешний вид индикатора:
Также нужно изменить код функций init() и start(), чтобы все работало:
Оживляем остальные рядыВторой ряд у нас будет отвечать за сигналы от Williams’ Percent Range, а третий – от Parabolic SAR. Изменяем функцию start():
Добавляем названия сигналовПришло время подписать каждый ряд. Создадим 3 надписи слева, используя массив, как и раньше:
Изменяем функцию init():
Добавляем возможность изменять угол привязкиСейчас мы дадим возможность пользователю выбирать размещение сигнального индикатора. Сейчас углом привязки является левый верхний. Если изменить свойство метки OBJPROP_CORNER, то поменяется и угол привязки. Это свойство может принимать следующие значения:
- 0 – левый верхний угол
- 1 – правый верхний
- 2 – левый нижний
- 3 – правый нижний
Поэтому мы добавляем новый параметр – corner:
Осталось изменить функцию init():
Добавляем новые параметрыОсталось добавить еще несколько параметров для гибкой настройки внешнего вида нашего индикатора. Вынесем в параметры:
- все доступные цвета
- все коды символов
Сначала объявим все эти параметры в начале кода:
Изменяем функцию init():
Изменяем функцию start():
Изменяем внешний видИндикатор готов. Изменяя входные параметры можно полностью изменить внешний вид:
Домашнее заданиеПридумайте свои условия сигналов и добавьте еще один ряд. Создайте еще несколько параметров. Например, параметр, который определяет размер шрифта для надписей (таймфремов и названий сигналов). Настройте внешний вид индикатора по своему усмотрению.
ЗаключениеСегодня мы научились использовать графические объекты в скриптах и индикаторах. Мы узнали как создавать объекты, изменять их параметры, проверять ошибки. Вы получили все необходимые знания, чтобы разобраться в новых типах графических объектов самостоятельно. Также вы шаг за шагом создали сложный сигнальный индикатор, который можно очень гибко настраивать.