Статьи по MQL4
- Читать Закрыть 1
Знакомство с редактором, в котором и пишутся программы для терминала MetaTrader4.
Театр начинается с вешалки, а программирование в MQL-4 начинается со знакомства с редактором, в котором и пишутся программы для терминала MetaTrader4.
Ведущий рубрики Rosh
Скачать последнюю версию терминала можно с сайта Альпари — Файлы для скачивания. Также на сайте разработчиков имеется форум, на котором можно почерпнуть много полезного — http://www.metaquotes.ru/forum. От себя замечу, что объем дистрибутива очень мал, и занимает менее 3 Mb, что достаточно мало для терминала, предоставляющего достаточно мощные средства для написания индикаторов и советников. В качестве отступления скажу, что дистрибутив Омеги весит порядка 200 Mb, что конечно , внушает уважения, но победить ее очень сложно, начиная с того, что она требует прав Администратора на компьютере и заканчивая ее стоимостью.
После установки MetaTrader4 в нужный терминал, вызвать MetaEditor можно одной кнопкой F4. по умолчанию, язык интерфейса и справочная система на английском языке. Для того, чтобы переключится на русский язык, выбираем меню «View»-«Languages»-«Russian». Язык сразу на русский не сменится, для этого необходимо закрыть MetaEditor и запустить его заново. Объясняется это тем, что при запуске MetaEditor проверяет установку языка и стартует с необходимым языковым файлом интерфейса.
Самый простой способ изучения языка для меня заключался в том, что я открывал какой-нибудь пользовательский индикатор (алгоритм которого я понимал очень хорошо) , идущий в составе дистрибутива, и пытался понять написанный код. Дополнительным источником знаний, как я уже говорил, служит форум разработчиков. Откроем для примера пользовательский индикатор Alligator.mq4, который находится в папке c:Program FilesMetaTrader 4expertsindicators (если ставить MetaTrader4 в папку по умолчанию). Это индикатор, наверное, знают или слышали все новички в трейдинге. Если мы подведем и установим курсор на строчку 70 на слово iMa и нажмем сочетание кнопок Ctrl+F1, то вызовется контекстная справка по данной функции. Эта функция представляет собой вызов значения встроенного технического индикатора Moving Average — скользящее среднее, пожалуй, наиболее известный и популярный технический индикатор .
Далее, нажав кнопку «Синхронизировать» ( при наведении всплывает подсказка «Синхронизировать словарь») мы откроем панель «Навигатора», в котором в списке технических индикаторов активизируется именно строчка Moving Average. Другой способ вызвать панель справку — нажать Ctrl+T. Чтобы вызвать панель «Навигатора» нужно нажать Ctrl+D (Dictionary).

MetaEditor
- Читать Закрыть 2
Объектное представление в MetaTrader4.
В MQL-4 очень трудно программировать, не имея представления об объектно-ориентированном программировании (ООП). Языки ООП являются языками высокого уровня, и именно по этой причине многие трейдеры, писавшие индикаторы и советники на MQL-2 столкнулись с трудностями. На самом деле принципиальных сложностей здесь нет. Сам терминал MetaTrader4 дает много примеров объектов, которые помогают понять эту концепцию. Объект — это структура языка программирования, имеющая определенные свойства. При этом одни объекты могут содержать в себе другие объекты. Например, если в терминале выбрать меню «Окно»-«Окна», появится диалоговое окно , в котором будет список графиков. Этот список отражает открытые инструменты с указанными тайм-фреймами. Первым идет окно с графиком AUDUSD на 4-х часовом тайм-фрейме. То есть, сами окна графиков являются объектами.

Если выбрать первое окно и нажать кнопку F8 («Свойства»), то появится следующее окно, в котором указаны свойства графика (объекта). В нем указаны свойства-атрибуты графика-объекта. Мы видим каким цветом отражается фон, текст, сетка, линейный график, раскраска баров и свечей. Хотя средствами MQL-4 нельзя получить доступ к этим свойствам графика, тем не менее, мы имеем перед собой типичный пример объектного программирования. Но есть много других объектов, доступ к которым можно получить и в терминале и из индикаторов и советников (а также скриптов) на MQL-4. К ним относятся трендовые линии, вертикальные и горизонтальные линии, значки, текстовые метки, текст, фигуры и многое другое. Все эти объекты имеют как минимум одно свойство — цвет, которым они отображаются на графике.

Кроме того, примером объекта являются стандартные (встроенные) индикаторы и пользовательские индикаторы. Если на графике, содержащем индикаторы, нажать комбинацию Ctrl+I, то появится список индикаторов, прикрепленных к этому графику, то есть график-объект содержит индикаторы-объекты.

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

С помощью закладок мы можем перемещаться по свойствам индикатора и задавать входные параметры индикатора (от них зависит значение индикатора на каждом баре), цвет которым отображается каждая линия индикатора, уровни индикатора и указать — на каких тайм-фреймах дозволено отображаться индикатору. Все эти свойства можно изменять не только из терминала, но и управлять поведением индикатора из программ, написанных на MQL-4. Поэтому одним из первых вопросов, которые появляются с изучением языка программирования, является следующий — Как написать свой индикатор и получить из кода индикатора доступ к свойствам объектов, которые размещены на графике пользователем или созданы самим кодом индикатора.
- Читать Закрыть 3
Исполняемые файлы в MQL-4 (скрипты, советники, индикаторы).
В MQL-4 существуют три вида самостоятельно исполняемых файлов, каждый из которых выполняет собственную задачу. По этой же причине, каждый их этих видов располагается в своей директории, скрипты хранятся в папке …MetaTrader 4expertsscripts , индикаторы в папке …MetaTrader 4expertsindicators, а советники в папке …MetaTrader 4experts , которая содержит и папки …scripts и …indicators. Сделано это для того, чтобы уже по месту нахождения файла можно было определить тип файла, так как расширение у них одинаковое — *.mq4.

Каждый из этих типов файлов выполняет только свои функции. Так, отражать на графике различные кривые наиболее простым и экономичным способом можно только из индикаторов, а проводить торговые операции на счету можно только скриптами и советниками. Тут можно сделать маленькое отступление: утверждение о невозможности рисования из скриптов и советников можно, вроде бы, опровергнуть — создавать и наносить графические объекты (линии вертикальные, горизонтальные и трендовые (в виде луча), значки разных стилей и цветов) можно и из скриптов и советников. Но попробуйте переключить тайм-фрейм графика — и все придется делать заново, потому что графические объекты имеют привязку по времени и цене, и на другом тайм-фрейме нужно будет удалять все объекты и рисовать новые.
Индикаторы, скрипты и советники также являются объектами. На график можно повесить советника, который будет в своем коде обращаться к одному или нескольким индикаторам, скрипт также не ограничен в вызовах индикаторов (пользовательских или стандартных). Можно также повесить индикатор, который строится на значениях другого индикатора, а тот в свою очередь вызывает для расчета третий индикатор. Все это в терминах ООП (объектно-ориентированного программирования) можно охарактеризовать так. Объект-график содержит в себе объект-советник, который содержит в себе объекты-индикаторы. Отобразим это представление двумя способами, стандартным и в виде диаграммы. Откроем график EURUSD, тайм-фрейм H1. Повесим на график советник MACD Sample из стандартной поставки MetaTrader4, разрешим ему торговать. Также набросим стандартный индикатор Stochastic Oscillator(5,5,5) , стандартный индикатор Fractals и пользовательский индикатор Zigzag (тоже из стандартной поставки MetaTrader4) . Получим такую картину:

Имеем три индикатора (два стандартных и один пользовательский), каждый из которых работает независимо от остальных. Кроме того, исполняется советник. Работа каждого из них требует машинного времени (ресурсов процессора) и размещения в оперативной памяти (ресурсов памяти). А теперь рассмотрим это под другим ракурсом: График EURUSD , H1 представляет из себя объект-окно графика, в который помещено еще три объекта-индикатора и один объект-эксперт (эти объекты называют дочерними). Объект-график получает ресурсы компьютера и распределяет его между своими дочерними объектами. Если заглянуть в код эксперта, то можно увидеть, что там содержится вызов стандартного индикатора MACD со значениями параметров 12, 26, 9. Обращение из советника к индикатору создает в самом эксперте еще один индикатор MACD(12,29,9), который мы не видим на графике, но, тем не менее, этот индикатор-объект также существует. Кроме того, существуют индикаторы, которые также обращаются в своих расчетах к значениям других индикаторов (стандартные или пользовательские — не имеет значения). То есть, кроме объектов, которые мы видим на графике, могут существовать и другие объекты — невидимые для нас. Но эти объекты также требуют ресурсов компьютера (процессора и оперативной памяти). Таким образом, на самом деле мы имеем не четыре объекта (три индикатора и один советник), а пять — три индикатора и один советник, содержащий один индикатор. Я это изобразил так:

На деле, часто на график набрасывают значительно больше трех индикаторов, и используют советники, использующие вызовы более одного индикатора, и все эти объекты используют ресурсы, отводимые терминалу (программе MetaTrader4). Кроме того, при тестировании индикаторов и советников в одном терминале часто открывают до 10 и более графиков, каждый из которых содержит индикаторы и советник. Поэтому правильное написание исполняемых файлов и экономный алгоритм работы советников, скриптов и индикаторов имеет первостепенное значение. Напоследок приведу скриншот моего терминала в выходные (рынок не работает и котировки не поступают). Открыто 19 окон-графиков, каждый из которых содержит до 4 индикаторов.

Ресурсы процессора (ЦП) практически не используются, а вот память задействовала более 70 мегабайт под работу терминала.
- Читать Закрыть 4
Что такое графики.
Все эти индикаторы, советники и скрипты пишутся для того, чтобы их присоединять к графикам. Поэтому необходимо сразу получить правильное представление о графиках, то есть о том, что они из себя представляют на самом деле и что скрывается за ними. Возьмем график EURUSD с тайм-фреймом D1. То есть, мы имеем график по инструменту EURUSD, каждый бар которого отражает поведение этой валютной пары за день (D1). Получить название инструмент в MQL-4 можно функцией Symbol()), а значение тайм-фрейма с помощью Period().

Эти функции можно использовать в любом файле MQL-4. Получить сведения по ним можно из встроенной справки в MetaEditor. Функция Period() возвращает значение периода в минутах, которое может быть одним из заданных констант: 1 минута, 5 минут, 15 минут, 30 минут и так далее. Вот как это описано в справке:
Перечисление периодов графика
Период графика. Mожет быть любой из следующих величин:
Константа Значение Описание
PERIOD_M1 1 1 минута.
PERIOD_M5 5 5 минут.
PERIOD_M15 15 15 минут.
PERIOD_M30 30 30 минут.
PERIOD_H1 60 1 час.
PERIOD_H4 240 4 часа.
PERIOD_D1 1440 Дни.
PERIOD_W1 10080 Недели.
PERIOD_MN1 43200 Месяцы.
На самом деле, даже если на графике нет ни одного индикатора, мы уже имеем перед собой как минимум один индикатор, так как даже представление цен в окне графика возможно в МТ4 тремя способами — свечи, бары и линии.

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

Если мы в терминале МТ4 нажмем комбинацию Ctrl+S (пункт меню «Сохранить как»), то появится диалоговое окно сохранения истории EURUSD D1 в формате *.csv (формат с разделителями).

Сохраним файл с именем, предлагаемым по умолчанию — EURUSD1440.csv . Как можно догадаться, имя файла по умолчанию содержит в себе имя инструмента (которое можно получить функцией Symbol()) и период 1440 минут (значение функции Period() для дневного тайм-фрейма равно 1440). Далее откроем полученный файл в Excel.
Последние пять строчек выглядят так:
2005.12.19,00:00,1.2029,1.2037,1.1973,1.1999,5166
2005.12.20,00:00,1.2002,1.2011,1.1839,1.1861,6679
2005.12.21,00:00,1.1859,1.1910,1.1800,1.1832,6787
2005.12.22,00:00,1.1830,1.1895,1.1808,1.1871,5284
2005.12.23,00:00,1.1870,1.1883,1.1827,1.1864,4686
Рассмотрим последнюю строчку, все остальные строчки аналогичны. Сначала идет запись 2005.12.23 — это дата 23 декабря 2005 года, Далее через запятую 00:00 — это 00 часов 00 минут. Все вместе означает начало дня 23.12.2005 — время открытия дневной свечи(Time). Далее числа 1.1870 ( цена открытия — Open ), 1.1883 ( максимальная цена за период — High ), 1827 ( минимальная цена за период — Low), 1.1864 (цена закрытия — Close) и 4686( объем за период — Volume). Если мы подведем мышку к последней свече на графике, то всплывет подсказка. Таким образом, можно сделать вывод, что график любого инструмента на любом тайм-фрейме можно представить в виде шести массивов: Time[], Open[], Low[], High[], Close[] и Volume[]. Все индикаторы и советники используют в своей работе только эти данные и более ничего. Первый из них содержит данные типа datetime, следующие четыре массива содержат данные типа double и последний — тип int. Cправка по всем типам содержится в MetaEditor. Доступ к элементам массива производится по индексу, индексы всегда имеют целочисленный тип. Самый последний бар (свеча) по времени всегда имеет нулевой индекс. Выражение Open[0] означает цену открытия нулевого бара (последнего), Time[1] означает дату и время открытия предпоследнего бара, Low[3] — минимальная цена за период на три периода назад и так далее. Важно запомнить, при увеличении индекса мы перемещаемся вглубь истории вплоть до самого раннего бара. Чтобы узнать количество баров, отображаемых на графике по данному инструменту, необходимо обратиться к параметру Bars. Этот параметр всегда содержит количество баров, которые можно увидеть на графике и автоматически обновляется при работе терминала в режиме реального времени. Самый первый бар на графике (дальше всех отстоящий от нас по ремени) будет иметь индекс Bars-1, так как индексация начинается с нуля, об этом не стоит забывать.

И напоследок хочу предупредить, что предопределенная переменная Bars напрямую связана с настройкой «Макс. баров в окне» (то есть обычно не больше этого значения) и не означает «Макс. баров в истории» (на рисунке 250000). Чем больше значения параметра «Макс. баров в окне» (на рисунке 15000), тем больше памяти потребляет терминал. Изменение этого параметра вступает в силу только после перезапуска МТ4.

- Читать Закрыть 5
Создание скрипта.
Мы уже имеем необходимый минимум знаний, чтобы написать простейшую программу на MQL-4. Для этого я выбрал скрипт, а не индикатор или советник, так как в них было бы слишком много новой информации. Сделаем скрипт, который выполняет ту же функцию, что и строка меню терминала «Файл»-«Сохранить как» (Ctrl+S). Для этого начинаем в MetaEditor стандартную процедуру — меню «Файл» — «Создать» (Ctrl+N), появляется диалоговое окно, ставим галочку напротив поля «Пользовательский скрипт»:

Идем «Далее», открывается новое диалоговое окно. Заполняем поля «Имя», «Автор» и «Ссылка». Поля «Автор» и «Ссылка» MetaEditor в дальнейшем запомнит и будет подставлять их значения при создании каждого нового исполняемого файла. Жмем кнопку «Готово» и скрипт создан.

Получили простейший код, сгенерированный «Мастером создания советника».

Код состоит из трех частей:
1. Шапка
2. Директивы компилятору
3. Функция start().
Шапка
кодом не является, так как представляет 5 строк комментариев. Комментарий — любая строка, которая начинается с двойной косой черты, компилятор игнорирует комментарии, ему нет дела до того, есть ли там ошибки или нет. Но комментарии упрощают понимание программ, в них можно делать пояснения о назначении тех или иных функций или переменных. Экономить на них нецелесообразно, так как спустя какое-то время вы и сами не всегда сможете вспомнить все нюансы, заложенные в программе. Мы видим, что автоматом были вставлены как комментарии значения полей «Имя», «Автор» и «Ссылка». При опубликовании кода это будет удобно видеть.
Директивы компилятору

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

Функция start()

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

содержит имя функции и тип возвращаемого значения данной функцией, а

само тело функции.
Тело функции всегда заключено в фигурные скобки, и в конце функции (перед закрывающей фигурной скобкой) практически всегда стоит оператор возврата. Справка по оператору:
В данном случае мы видим, что функция start() возвращает значение 0 (ноль). Это значение мы использовать в данном случае не будем, поэтому можем спокойно закомментировать эту строчку. Наша задача: вывести значения Open, Close, Low, High, Time и Volume для каждого бара(свечи) на графике. Доступ к каждому бару мы можем получить по индексу, таким образом нам необходимо сделать пробег по барам от самого последнего бара до нулевого. Для этого существует оператор цикла for(). Из справки:

На открытом графике обычно присутствует не более Bars баров. Нам необходимо опросить их все, начиная с самого первого по истории, имеющего индекс Bars-1, и заканчивая последним, имеющим нулевой индекс. Объявим переменную целого типа, назовем ее index и напишем оператор цикла:
for (int index=Bars-1; index>=0; index-) { // Выведем High, Low, Open, Close для бара с индексом index }
Разберем цикл:
int index=Bars-1 объявили переменную index и задали для нее начальное значение Bars-1, то есть мы пойдем от самого первого бара на графике
index>=0 задали условие, при котором тело цикла выполняется. Таким образом, пока индекс больше или равно нулю, тело цикла выполняется
index- равносильно записи index= index-1 . Уменьшение значения на 1 называют декрементом, а увеличение инкрементом. Если индекс больше или равен нулю, выполняется тело цикла, после чего индекс уменьшается на единицу. Таким образом, когда-нибудь наступит момент, при котором index будет меньше нуля, в этот момент цикл и прервется.
{ // Выведем High, Low, Open, Close для бара с индексом index } Тело оператора цикла, вместо комментария нам необходимо вставить настоящие операторы вывода значений. Для этого существует оператор Print()
Добавим в тело цикла оператор Print(High[index],««,Low[index],» «,Open[index],» «,Close[index],» бар=»,index); Он выводит в Журнал советников и индикаторов значения High, Low и так далее. Скрипт готов, жмем F5 (Компилировать) — если ошибок нет, то скрипт готов к использованию.

Возвращаемся в терминал, открываем папку «Скрипты» и набрасываем наш скрипт на график EURUSD D1. Открываем окно «Терминал» (Ctrl+T), переходим на закладку «Эксперты» и видим результат работы скрипта. После завершения работы скрипт удаляется с графика : 2006.01.07 23:35:31 OutPrint EURUSD,Daily: removed В журнал экспертов можно выводить любую информацию с помощью оператора Print, при это выводится информация в два столбца. Первый столбец содержит дату и время вывода, а второй столбец содержит название скрипта, название инструмента, тайм-фрейм и двоеточие, после которого выводится результат работы скрипта. В этом простейшем скрипте мы использовали две, пожалуй самые нужные, функции в языке MQL-4, for() и Print(). Если for() используется для выполнения однообразных операций (работа в цикле), то Print() самая удобная функция для протоколирования работы советников и индикаторов, а также для отладки кода. Если код программы не работает, или работает не так, как ожидалось, то самое простое — это использовать вывод в лог переменных программы для анализа ее работы. Обычно этого достаточно для нахождения ошибки в своем коде.

И напоследок, лог журнала закладки «Эксперты» содержат результат работы нашего скрипта и любого другого советника или индикатора. Если нажмем на любой строке правую кнопку мышки, то появится контекстное меню.
Выберем пункт «Открыть», появится диалоговое окно выбора :

Логи экспертов (и индикаторов) хранятся в папке Program FilesMetaTrader 4expertslogs, каждый день создается новый файл. Наш лог (в котором находятся результаты работы скрипта) самый последний по времени и дате. Откроем его:

Таким образом, мы зафиксировали работу нашего скрипта, поэтому Print() очень удобно использовать для отладки кода в онлайне. Сам скрипт можно взять здесь
- Читать Закрыть 6
Скрипт – мостик в Microsoft Excel.
MetaTrader4 дает большие возможности для анализа финансовых инструментов, многие трейдеры считают эту платформой самой лучшей для технического анализа. Но не стоит отказываться и от других возможностей. Запись данных в логфайл МТ4 не всегда удобен, MQL-4 содержит функции работы с файловой системой. Сохраним скрипт OutPrint с новым именем WriteFile. Необходимо изменить алгоритм совсем немного, перед оператором цикла for() вставим функцию открытия файла FileOpen(), после цикла вставим функцию закрытия файла FileClose(), а оператор Print() заменим на оператор записи в файл FileWrite(). Для манипуляций с файлом необходимо предварительно создать ссылку (handle – читается хэндл) объект-файл. Хэндл файла имеет тип int, для этого FileOpen() нужно передать параметр – имя файла, имя файла имеет тип string. Файлы, открываемые в MQL-4 всегда находятся только в папке C:Program FilesMetaTrader 4expertsfiles (кроме файлов, используемых в тестере) в целях безопасности, в другие директории запись запрещена( невозможна). Записывать будем в формате CSV, который хорошо понимает Microsoft Excel. Открытый файл необходимо всегда закрыть в конце работы скрипта, иначе к нему не смогут получить полный доступ другие программы. Попробуем проверить простую идею – о неслучайном поведении цен в разные дни недели. Для этого будем записывать разницу между ценой закрытия и ценой открытия в пунктах – (Close[i]-Open[i])/Point. Point – предопределенная переменная, которая содержит значение пункта(минимально допустимого изменения цены) для текущего инструмента. Кроме того, нам необходимо записать день недели текущего дневного бара TimeDayofWeek(Time[i]). Добавим также год – TimeYear(Time[i]) . Предположим, что на открытии дня мы покупаем, а на закрытии дня закрываем сделку. Так как цены в МТ4 строятся по Bid (цена продажи), а покупки делаются по Ask (цена покупки), то нам необходимо учесть спред по данному инструменту. Спред по символу можно получить функцией MarketInfo(Symbol(),MODE_SPREAD). Напомню, что в операторе for() исполнение идет следующим образом:

Рис. 1
Сначала выполняется инициализация цикла – задается начальное значение счетчика index, я отметил этот блок значком S. Этот блок выполняется только один раз в начале. Далее выполняется проверка условия – блок 1. Если условие выполнилось, происходит выполнение тела цикла - блок 2. И только после выполнения тела цикла происходит изменение счетчика в блоке 3 – index--. Прохождение трех блоков составляет один такт цикла for(). Далее по кругу – 1,2,3 до тех пор, пока выполняется условие блока 1. Как только это условие будет нарушено (в нашем случае счетчик index станет меньше нуля) – цикл завершится и управление будет передано операторам, следующим сразу за циклом for().
Наша задача будет записать в файл все значения с 2001 года по 2005 год включительно (для примера). Но цикл for() в нашем скрипте проходит по всем барам с начала истории и по сегодняшний день. То есть, нам нужно не записывать бары, относящиеся к годам меньше 2001 и больше 2005. Для этих целей есть операторы continue и break. Немного видоизменим скрипт таким образом.
Рис. 2
Добавим оператор условный оператор if() , в котором и будем проверять значение года для каждого бара. Справку по нему, как и по операторам continue и break можно получить в MetaEditor, наш скрипт только помогает понять логику применения этих операторов.
Теперь тело цикла выполняется немного по другому, перед оператором Print() мы поставили две проверки , первая , в случае если год бара меньше 2001, заканчивает работу с текущим индексом и возвращает работу в блок 1 цикла (все следующие операторы тела цикла не выполняются), а второй полностью завершает работу цикла (если год бара с текущим индексом больше 2005).
Осталось добавить блоки открытия и закрытия файла, и преобразовать вывод в лог на запись в файл. Видно, что наш скрипт сейчас состоит из трех операций/блоков.
Рис. 3
В операторе FileWrite() выводимые значения будут разделены точкой с запятой потому, что мы указали «;» в качестве разделителя при открытии файла. Это позволит открыть файл в Microsoft Excel, где каждое значение, разделенное точкой с запятой, будет помещено в свой столбец. Это, конечно, удобно, но остался последний штрих – нам не помешали бы названия столбцов, чтобы лучше понимать цифры. Для этого в блоке открытия файла добавим запись «шапки» выводимой таблицы. И не забудем вывести значения года, дня недели для каждого бара, значение спреда и разницы между ценой открытия и закрытияв пунктах.

Рис. 4
На этом работа в MetaEditor закончена, компилируем и исполняем скрипт на графике EURUSD D1. Запускаем Excel и открываем наш файл.

Рис. 5
Немного поправим границы столбцов и получим такое расположение данных.

Рис. 6
Теперь мы можем использовать простейшие возможности Excel. Включим автофильтр.

Рис. 7
Заголовки столбцов получат возможность фильтровать данные по этим столбцам.

Рис. 8
Для примера, я хочу взять только те дни, которые соответствуют пятнице, то есть, таблица будет отображать те строки, которые приходятся на пятницу за 5 лет с 2001 по 2005 года. Сделаем так:

Рис. 9
То есть, в столбце «День недели» мы выставили условие – равно 5 (5 – пятница в МТ4). Теперь мы можем посчитать все пятницы, сложить значения Close-Open по всем пятницам, для этого активизируем самую нижнюю ячейку в столбце «Close-Open» и нажмем на панели инструментов зачок «Сумма».

Рис. 10
Значения для автосуммирования заполняются автоматически, жмем Enter и получаем сумму в пунктах. То же самое проделываем со столбцом «Спред».

Рис. 11
Мы получили результат: если бы мы покупали бы EURUSD каждую пятницу на открытии все эти пять лет, а на закрытии дня закрывали бы сделку, то после вычета спреда в 783 пункта за эти дни мы получили бы прибыль в 652 пункта (1435-783). Данные условные, так как мы не учли возможность проскальзывания. Можно проверить таким же образом другие дни недели, можно дополнительно включить фильтр по годам

Рис. 12
И напоследок, мы еще можем построить на наших данных цен Open, High, Low, Close диаграмму. Для этого выделим данные за нужный период (я взял небольшую часть 2003 года), нажмем кнопку «Мастер диаграмм», выберем тип «Биржевая».

Рис. 13
Получим примерно такую диаграмму, как не бился , а в МТ4 графики лучше.

Рис. 14
Сам скрипт можно взять здесь
- Читать Закрыть 7
Ордера в MetaTrader4.
Работа эксперта или скрипта, использующего торговые функции, требует получения текущей информации об открытых и отложенных ордерах – уровень открытия, значения Stop Loss и Take Profit, текущей прибыли(или убытка) для конкретного ордера, количество открытых ордеров. Иногда пишутся системы автоматической торговли, где важным является время удержания открытой позиции, в этих случаях также необходимо знать время открытия конкретной позиции. Для целей диверсификации создают системы, которые торгуют по схожим алгоритмам, но на разных инструментах, в таких случаях важно знать – по какому инструменту открыт данный ордер. Или на одном инструменте работают две или более механические торговые системы (МТС), которые еще называют торговыми роботами. В таком случае необходимо как-то различать ордера, открытые разными МТС на одном и том же инструменте. Бывают МТС, в которых приходят сигналы на открытие разной силы значимости(и поэтому открываются ордера большего или меньшего размера позиции) или требующие открытия нескольких ордеров в одном направлении по нескольким отличающимся друг от друга условиям, например, на пробой уровня сопротивления и на отскок от уровня поддержки. Ордера, открытые в одной МТС, но при разных ситуациях также необходимо как-то различать, добавлять к ним строчные комментарии. Кроме того, бывают системы, в которых отложенные ордера, не сработавшие в течение определенного периода времени после установки, необходимо удалять, так как считается, что условия их существования (ордеров) больше не соответствуют уже текущей ситуации. Например, мы выставляем отложенный ордер в день, когда ожидается выход сильных новостей, и необходимо его удалить, если он не сработал в первые минуты после выхода этих новостей. В общем, причин для открытия, закрытия, удаления и удержания ордеров может быть очень много, и практически все эти варианты реализуются в терминале МТ4. В терминале вызовем закладку «Терминал» (Ctrl+T) и выберем закладку «Торговля». На рисунке представлено типичное состояние терминала по торговому счету, на котором одновременно работают несколько МТС.

Рис. 1
Первый столбец содержит номера тикета каждого ордера, далее столбец времени открытия ордера, затем следует столбец типа ордера (buy или sell), за ним столбец размера открытой позиции, цена открытия, уровень Stop Loss, уровень Take Profit, текущая рыночная цена, по которой можно закрыть ордер в данный момент(Ask или Bid в зависимости от типа ордера), накопленные свопы по данному ордеру (с плюсом или минусом), текущая прибыль по данному ордеру, и последний столбец – комментарии к ордеру. Ниже строки, содержащей информацию о текущем Балансе, Эквити (Средства), Марже (Залог), свободной марже (Свободно) находится строчка с информацией об единственном отложенном ордере с тикетом 3313320. Для отложенных ордеров отсутствуют данные о свопе и текущей прибыли, остальные параметры тоже известны. Кроме того, в таблице не показан MagicNumber каждого ордера. MagicNumber – это обычно уникальное целое число, которое выставляется советником при открытии ордера, чтобы этот советник мог отличать свои ордера от ордеров, открытых другими советниками или открытых вручную (для них MagicNumber=0). Чтобы увидеть MagicNumber конкретного ордера, достаточно подвести указатель мыши на нужный ордер, и тогда всплывет подсказка, содержащая номер тикета, номер MN (id 1005 на рисунке) и комментарий к ордеру.

Рис. 2
То есть, у ордера с тикетом 3313336 MagicNumber равен 1005 и указан комментарий «sell stop». Создадим скрипт, который выводит в файл *.csv и в лог журнала все атрибуты ордеров по текущему торговому счету (account). Чтобы это сделать, нам необходимо пройти по все ордерам и запросить эти атрибуты. Сделаем это опять с помощью цикла for(), операторов Print() и FileWrite(). На рисунке из терминала мы видим, что ордера в МТ4 можно представить в виде списка. Мы видим, что количество ордеров равно 13 (12 открытых ордеров + 1 отложенный ордер). Информацию об общем количестве ордеров возвращает функция OrdersTotal(). Действительно, список ордеров хранится в терминале, при этом нужно помнить, что номер позиции в списке начинается с нуля и заканчивается на OrdersTotal()-1. В приведенном рисунке последний ордер в списке будет иметь номер позиции равным 12, но не 13. Для того, чтобы обратиться к ордеру его необходимо предварительно выбрать функцией OrderSelect(). При удачной попытке выбора функция возвращает значение true (истина), иначе false(ложь).

Рис. 3
Я запустил скрипт на демо-счете и можно сравнить расположение ордеров в терминале (то, что мы видим) и расположение ордеров в памяти терминала (результат работы скрипта).

Рис. 4
Видно, что ордера в памяти терминала расположены совсем не так, как мы видим. Но при правильном написании кода это не имеет значения. Я добавил отложенный ордер по USDCAD 0.1 Buy Stop 1.1490 и запустил на терминале доработанный скрипт. Обратите внимание, что имя файла состоит из номера счета, на котором выполнялся скрипт, а после окончания его работы выводятся в лог журнала функцией Print() значения баланса, эквити, маржи и свободных средств на счету. Также подсчитываются количество открытых ордеров в покупку, продажу и четырех видов отложенных ордеров. Сделать вывод на печать этих подсчитанных значений можно аналогично.

Рис. 5
Получилась такая таблица в Microsoft Excel. Ордер, выставленный вручную (USDCAD), имеет MagicNumber равный нулю. Видно, что на одном символе(EURUSD) открыты ордера с разными MagicNumber(1002, 1003, 1005, 1006, 1007), а также то, что на двух разных символах (EURJPY и GBPJPY) имеются ордера с одинаковыми MagicNumber(1009). МТ4 позволяет тестировать на одном торговом счете множество МТС, при этом каждая МТС отличает свои ордера от ордеров других МТС, и от ордеров, выставленных вручную или на другом символе.
Напоследок хочу напомнить самую распространенную ошибку при работе со списком ордеров – обычно ошибочно начинают обработку не с нулевой позиции списка, а с первой, и заканчивают, соответственно, за пределом списка ордеров. В итоге, это дает двойную ошибку – не обрабатывается ордер с нулевой позицией и делается попытка обработать несуществующий ордер на позиции OrdersTotal().
Сам скрипт можно взять OrderList здесь
- Читать Закрыть 8
Закрытие и удаление ордеров.
Теперь мы можем рассмотреть механизм удаления и закрытия рыночных ордеров в МТ4. Мы знаем, что функцией OrderSelect(номер_в_списке, SELECT_BY_POS) мы выбираем ордер, для которого потом получаем параметры. Функции OrderClose() и OrderDelete() требуют обязательный параметр — номер тикета ордера, который мы получим с помощью OrderTicket(). Предположим, что нам необходимо быстро закрыть все открытые ордера и удалить все отложенные. Создадим для этого новый скрипт StopTrade. mq4 . Самый простой способ будет опять таки пройти в цикле for() по списку ордеров и в зависимости от типа ордера закрыть или удалить каждый из них. Тип ордера мы получим функцией OrderType(). Если для удаления отложенного ордера достаточно знать номер тикета, то закрытие ордера требует указания правильной цены, по которой мы будем закрывать ордера типа OP_BUY или OP_SELL. Так как в терминале может быть открыто множество ордеров по разным инструментам, а исполняться скрипт будет на графике одного из них, то нам нужна возможность получения значений Ask и Bid для любого символа. Для этого служит функция MarketInfo() . Выражение MarketInfo(«GBPUSD», MODE_ASK) означает цену Ask для символа GBPUSD. Значение символа получаем из OrderSymbol(). Для подсказки я сделал таблицу списка ордеров, где выделенная строка для ордера на 8-ой позиции в списке ордеров (подсвечена желтым фоном) означает выбор этого ордера функцией OrderSelect(), а снизу разместил названия функций, с помощью которых можно получить необходимые параметры ордера. На рисунке выделена функция OrderTicket() для получения номера тикета для выбранного ордера.

Мы имеем все необходимые данные для написания скрипта. Но самой первой операцией в нашем скрипте будет проверка типа счета, на котором он исполняется. Мы пишем этот скрипт как учебный, и поэтому необходимо защититься от случайного использования его на реальном счете. Для проверки типа счета используем функцию isDemo(), которая возвращает true для демо-счета, в противном случае false.

Восклицательный знак (!) применяется для логических выражений и производит «переворот» : выражение ! true превращается в false, а ! false превращается в true. Выражение ! isDemo() на демосчете вернет значение false ( true превратится в false), а на реальном счету выдаст true. В этом случае скрипт досрочно завершит работу, предварительно выдав предупреждение «На реальном счете работа запрещена». Желательно всегда использовать такую защиту во всех скриптах(совершающих торговые операции) и советниках, чтобы избежать неприятных сюрпризов.
В цикле при переборе ордеров нам нужно будет проверять тип ордера, всего бывает 6 типов ордеров — 2 открытых (Buy или Sell) и 4 отложенных. Buy мы будем закрывать по цене Bid, а Sell по цене Ask для того символа, по которому открыт ордер. Оставшиеся 4 типа отложенных ордера хотелось бы удалять одинаковым способом, не разбирая — buylimit или sellstop ордер перед нами. Если мы посмотрим справку, то увидим что константы 0 и 1 означают открытые ордера, а отложенные ордера имеют OrderType()>1(2,3,4 и 5).
Теперь мы можем для ордеров с OrderType()>1 применять удаление OrderDelete(), а два остальных типа ордера закрывать с помощью OrderClose(). Но писать множество операций сравнения if(OrderType равно n) после OrderSelect() немного некрасиво, сделаем изящней. (Выражение a==b делает сравнение a и b, и если они равны — возвращает true.) Для таких случаев, когда проверяется равенство выражения ограниченному количеству значений, хорошо подходит оператор switch() (переключатель). Получился такой вариант:

Осталось прописать закрытие и удаление. С удалением просто- пишем OrderDelete(ticket), где ticket=OrderTicket(). Для закрытия открытых ордеров получим текущие цены закрытия с помощью MarketInfo(OrderSymbol(), …). Размер закрываемой позиции мы получим из OrderLots(), проскальзывание (slippage) я задал равным 3, цвет рисуемой стрелки закрытия я установил красным (Red). Если вы не помните, какие параметры и в каком порядке необходимо передавать функции, то нажав сочетание клавиш, можно получить подсказку. На рисунке я установил курсов между скобками в OrderClose():

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

Список ордеров в терминале является динамическим, в то время как проход в цикле for() будет статическим — если было сказано пройти от 0(нуля) до total-1 (количество ордеров в списке до начала удаления ордеров минус один) , то так и будет. Если ордеров было 10, и мы начнем с начала списка (с нулевого ордера по списку) , то после прохода от 0 до 4, в списке ордеров останется 5 ордеров, и размещаться они будут опять на позициях от 0 до 5. И проход в цикле от 5 до 9 будет выдавать холостые выстрелы, оставшиеся ордера не будут закрыты или удалены. Правильным будет удаление с конца списка, тогда не будет эффекта обвала. На рисунке показан порядок удаления ордеров с конца списка, размер списка после каждого удаления/закрытия будет уменьшаться, при этом ни один ордер не будет пропущен.

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

Напоследок, зададим горячую клавишу для нашего скрипта. Например, нажатие Alt+A , будет вызывать на исполнение наш скрипт на активном графике. Нажмем правой кнопкой мышки на любом скрипте(индикаторе, советнике) и выберем «Установить горячую клавишу».

Два раза кликнем напротив нашего скрипта в столбце «Управление» и выберем из списка Alt. Затем зададим клавишу (например А).

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

Перед исполнением скрипта были следующие ордера:


- Читать Закрыть 9
Вызовы технических индикаторов.
Любая торговая система состоит из сигналов открытия и закрытия позиций, подтягивания защитных стопов. Большинство экспертов пишутся на основе стандартных индикаторов, входящих в состав МТ4, которые в справке называются техническими индикаторами. Рассмотрим простейшую систему, основанную на пересечении двух скользящих средних (Moving Average). Этот индикатор имеет два параметра — период усреднения(количество баров, используемых для расчета) и метод усреднения (тип цены). Есть еще и третий параметр — смещение, но мы его в данном примере использовать не будем. Ситуация, когда средняя с коротким периодом пересекает среднюю с длинным периодом, обычно используется для продажи, для покупки все наоборот. Сделаем скрипт (CrossMASignals. mq4), который будет наносить на график сигналы покупок в виде синих стрелок, направленных вверх, и сигналы продаж в виде красных стрелок, направленных вниз.

На рисунке видно, что 21 июля 2005 года средняя с периодом 13 (значение 1.2052) пересекла снизу вверх среднюю с периодом 21 (значение 1.2050). Синюю стрелку мы поставим по цене открытия дневного бара 22.07.2005, так как сигналы мы будем принимать по закрывшимся барам, это избавит от ложных сигналов, когда цена может в течение дня несколько раз сходить вверх и вниз. Создадим такую блок-схему:

Хотя проверять пересечение средних в самом начале графика (от бара с индексом Bars-1) и не совсем правильно, но в данном случае ошибку мы не получим и не будем пока разбирать — почему в начале графика значения средних равны нулю.

Нам необходимо в своем коде получить значения этих средних на каждом баре. Зададим две переменные maLong и maShort и будем присваивать им соответствующие значения. Из справки видно, что необходимо указать символ (NULL), тайм-фрейм (0), значения периодов (21 и 13), смещение (у нас будет нулевым), тип усреднения (простое), тип цены (применим цены закрытия) и индекс бара.

Факт пересечения двух средних можно проверить таким образом — на текущем баре быстрая средняя выше медленной средней, а на предыдущем баре наоборот- медленная средняя выше быстрой средней. Поэтому сделаем не две, а четыре переменные maLongCur, maLongPrev, maShortCur, maShortPrev.

Я сделал рисунок, который показывает связь между параметрами индикатора на графике и параметрами технического индикатора, вызываемого в коде. Для любого встроенного индикатора в МТ4 существует его вызов в языке MQL-4 в виде технического индикатора.

Скрипт почти готов, осталось только как-то поставить в нужных местах стрелки. Тут нам на помощь придут функции для работы с графическими объектами. Чтобы создать стрелку, необходимо: 1. создать Объект типа символ 2. задать координаты времени (X) и цены (Y) , к которым привязывается объект 3. указать вид символа и цвет, которым он будет отображаться на графике
Все графические объекты в МТ4 создаются функцией ObjectCreate() . При этом объекту при создании сразу дается уникальное имя, двух объектов с одинаковым именем быть не может. При попытке создать второй объект с уже существующим именем объект создан не будет, ошибка в терминал выведена не будет (но сам факт ошибки проверить можно будет ) чтобы не мешать пользователю. Это самая частая ошибка при работе с графическими объектами, складывается впечатление, что программа не работает, начинаются бесплодные попытки найти ошибку в алгоритме кода, а ее нет — есть ошибка создания множества графических объектов с одинаковыми именами. Мы создаем стрелку функцией ObjectCreate(«уникальное_имя_объекта», OBJ_ARROW, handle_окна, время, цена). Параметр OBJ_ARROW — указывает, что создается объект типа стрелка(символ), handle_окна — указатель на окно, в котором объект будет создан (обычно равен нулю, что указывает на «родное» окно), время и цена — координаты стрелки. После создания стрелки нам необходимо указать код символа — ObjectSet(«уникальное_имя_объекта», OBJPROP_ARROWCODE, код_символа) . Функция ObjectSet() служит для установки свойств графического объекта, для каждого типа объекта требуется свой второй тип параметра, в нашем случае для объекта OBJ_ARROW установить код символа мы можем параметром OBJPROP_ARROWCODE, коды символов можно посмотреть в справке MetaEditor. Кроме того, можно использовать символы из шрифта Wingdings, справка по ним тоже приведена.
Нам подойдут символы с кодом 241 и 242. Далее осталось только задать последнее свойство — цвет символа. Опять используем функцию ObjectSet() и зададим свойство OBJPROP_COLOR либо Red либо Blue по ситуации.

Скрипт готов, но остался последний вопрос — уникальность имени для каждой стрелки. В коде есть объявление строковой переменной arrowName, это имя используется при создании каждой новой стрелки и при изменении атрибутов объектов-стрелок. Самый простой способ решить эту проблему — использовать переменную целого типа, например int arrowCounter, и при каждом создании новой стрелки стрелки увеличивать счетчик стрелок на единицу с помощью arrowCounter++. Уникальное имя будем получать сложением выражения «arrow» и arrowCounter, будем получать имена типа arrow1, arrow2 и так далее.

Скрипт почти окончательно готов, он работает, запускаем на графике EURUSD D1 и мгновенно появляются стрелки. Нажимаем кнопки Ctrl+B — появляется окно со списком графических объектов, прикрепленных к графику.

Мы получили «графический» индикатор, его достоинство — стрелки остаются на своих местах при смене тайм-фрейма, например, при переключении на Weekly. Если мы нажмем кнопку «Правка», то попадем в окно свойств выделенного объекта. Я выбрал стрелку(объект Arrow) с именем arrow1. На вкладках «Общие» и «Параметры» мы видим те значения, что были заданы скриптом — имя объекта, цвет, координаты время и цена, а также код символа — 242.

Единственный неучтенный момент — если мы попробуем запустить скрипт второй раз — он или не сработает или сработает не совсем правильно. Ведь при каждом запуске скрипта создаются объекты с именами arrow1, arrow2 и так далее, но ведь эти объекты уже существуют после запуска скрипта в первый раз. И поэтому, если нам будет необходимо его запустить на том же инструменте, но на другом тайм-фрейме, нам придется вручную предварительно удалить все стрелки, это можно сделать с помощью меню «Графики» — «Объекты» — «Удалить все значки». Это не самый удачный путь, удаление объектов можно проводить функцией ObjectsDeleteAll(номер_окна, тип_объекта). Она возвращает количество объектов, которое было удалено. Выведем это значение на график с помощью функции Comment(строка_вывода). Эта функция удобна для использования в индикаторах и советниках для отражения необходимой текущей информации. Теперь у нас есть скрипт, в котором учтено все для правильной работы.
- Читать Закрыть 10
Функции init() и deinit().
Для того, чтобы приблизиться к написанию первого индикатора, мы немного доработаем скрипт CrossMASignals. Добавим в наш скрипт возможность записи в файл сигналов покупок и продаж, для этого разместим в начале скрипта блок открытия файла, в цикле добавим операцию записей сигналов, в конце пропишем закрытие файла. Назовем этот скрипт CrossMASignals-2. Можно заметить, что бары на графике делятся на два вида – бары, на которых стоят стреки вверх или вниз, и бары, на которых стрелок нет. Будем записывать в файл время открытия бара, номер бара, цена, по которой рисуется стрелка вверх, и цена , по которой рисуется стрелка вниз. То есть, у нас будет csv-файл, содержащий 4 столбца: Time[Индекс_Бара], Индекс_Бара, Цена_Синей_Стрелки, Цена_Красной_Стрелки. Бары, на которых стрелок нет, будут содержать в двух последних столбцах нулевые значения. Я добавил несколько строк в начале скрипта:



и в конце

и в самом конце скрипта:

Получившийся файл можно скачать здесь .
int start()
{
double maLongCur, maLongPrev, maShortCur, maShortPrev;
string arrowName; // здесь будут назначаться уникальное имя объекта-стрелки
int arrowCounter=0;
int deletedArrows;
string FileName;
int FileHandle;
// сформируем имя файла
FileName="CrossMA.csv";
//откроем файл с именем FileName (создадим указатель/handle на него)
FileHandle=FileOpen(FileName,FILE_WRITE | FILE_CSV,";");
if (FileHandle<1)
{
Print("Не удалось открыть файл, ошибка ",GetLastError());
return;
}
// запишем названия столбцов (создание шапки)
FileWrite(FileHandle,"Дата","Номер бара","Стрелка вверх","Стрелка вниз");
// удалим все стрелки, если они есть
deletedArrows=ObjectsDeleteAll(0,OBJ_ARROW);
Comment("Удалено ",deletedArrows," обектов OBJ_ARROW");
// объявим служебные переменные, в которых будем хранить ценовые координаты стрелок
double value1,value2;
//----
for(int i=Bars-1;i>=0;i--)
{
// обнулим службные переменные
value1=0.0;
value2=0.0;
maLongCur=iMA(NULL,0,21,0,MODE_SMA,PRICE_CLOSE,i+1);// медленная средняя на предыдущем баре
maLongPrev=iMA(NULL,0,21,0,MODE_SMA,PRICE_CLOSE,i+2);// медленная средняя на предпредыдущем баре
maShortCur=iMA(NULL,0,13,0,MODE_SMA,PRICE_CLOSE,i+1);// быстрая средняя на предпредыдущем баре
maShortPrev=iMA(NULL,0,13,0,MODE_SMA,PRICE_CLOSE,i+2);// быстрая средняя на предыдущем баре
//Если есть сигнал к покупке на предыдущем баре - поставим синюю стрелку вверх
// по цене открытия бара
if((maShortCur>maLongCur)&&(maShortPrev<maLongPrev))
{
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 241 , стрелка вверх
ObjectSet(arrowName,OBJPROP_ARROWCODE,241);
// зададим цвет стрелке - голубой
ObjectSet(arrowName,OBJPROP_COLOR,Blue);
// увеличить счетчик стрелок
arrowCounter++;
value1=Open[i];//запомним цену голубой стрелки
}
//Если есть сигнал к продаже на предыдущем баре - поставим красную стрелку вниз
// по цене открытия бара
if((maShortCur<maLongCur)&&(maShortPrev>maLongPrev))
{
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 242 , стрелка вниз
ObjectSet(arrowName,OBJPROP_ARROWCODE,242);
// зададим цвет стрелке - красный
ObjectSet(arrowName,OBJPROP_COLOR,Red);
// увеличить счетчик стрелок
arrowCounter++;
value2=Open[i];//запомним цену красной стрелки
}
//запишем данные для текущего бара в файл
FileWrite(FileHandle,TimeToStr(Time[i]),i,value1,value2);
}
//закроем файл (освободим указатель/handle, чтобы файл можно было
//открыть для редактирования другими программами)
if(FileHandle>0) FileClose(FileHandle);
return(0);
}
Если внимательно рассмотреть получившийся код, то можно заметить, что функционально он состоит из трех частей:- Блок подготовки, в котором удаляются старые значки(стрелки) и открывается файл для записи.
- Блок нанесения стрелок на график и записи значений стрелок в файл.
- Завершающий блок, в нем мы закрываем файл.
Изменим наш скрипт в соответствие с этими стандартами и назовем CrossMASignals-3. Я добавил в начало каждой функции оператор Print(), чтобы можно было видеть последовательность выполнения этих функций. Кроме того, переменнуюint FileHandle; // указатель на на файл - глобальная переменная
я вынес в самое начало скрипта, за пределы функций start, init и deinit. Такие переменные называются глобальными, обратиться к таким переменным можно из любого места программы, говорят, что эти переменные видимы на глобальном уровне. Мы к ней обращаемся в init() при создании указателя на файл, в блоке start() для записей в файл и в блоке deinit() при закрытии файла. В то же время переменнуюstring FileName; // имя файла - локальная переменная
оставил в блоке init(), так как больше она нигде не используется. Эта переменная уничтожается в памяти терминала сразу после выполнения блока init() и попытка обратиться к ней в других функциях скрипта (start или deinit) приведет к выдаче сообщения об ошибке. Такие переменные, которые живут только в пределах той функции, в которой они объявлены, называют локальными переменными. Мы даже можем объявить такую же переменную в остальных функциях, и каждая из них не будет знать о существовании другого близнеца, но делать так не рекомендуется, так как будет очень легко самому запутаться в коде.
int FileHandle; // указатель на на файл - глобальная переменная
int init()
{
string FileName; // имя файла - локальная переменная
int deletedArrows;
// сформируем имя файла
Print("Выполняется init()");
FileName="CrossMA.csv";
//откроем файл с именем FileName (создадим указатель/handle на него)
FileHandle=FileOpen(FileName,FILE_WRITE | FILE_CSV,";");
if (FileHandle<1)
{
Print("Не удалось открыть файл, ошибка ",GetLastError());
return;
}
// запишем названия столбцов (создание шапки)
FileWrite(FileHandle,"Дата","Номер бара","Стрелка вверх","Стрелка вниз");
// удалим все стрелки, если они есть
deletedArrows=ObjectsDeleteAll(0,OBJ_ARROW);
Comment("Удалено ",deletedArrows," обектов OBJ_ARROW");
}
//----
int deinit()
{
Print("Выполняется deinit()");
//закроем файл (освободим указатель/handle, чтобы файл можно было
//открыть для редактирования другими программами)
if(FileHandle>0) FileClose(FileHandle);
return(0);
}
//----
int start()
{
double maLongCur, maLongPrev, maShortCur, maShortPrev;
string arrowName; // здесь будут назначаться уникальное имя объекта-стрелки
int arrowCounter=0;// счетчик стрелок
// объявим служебные переменные, в которых будем хранить ценовые координаты стрелок
double value1,value2;
Print("Выполняется start()");
for(int i=Bars-1;i>=0;i--)
{
// обнулим службные переменные
value1=0.0;
value2=0.0;
maLongCur=iMA(NULL,0,21,0,MODE_SMA,PRICE_CLOSE,i+1);// медленная средняя на
//предыдущем баре
maLongPrev=iMA(NULL,0,21,0,MODE_SMA,PRICE_CLOSE,i+2);// медленная средняя на
//предпредыдущем баре
maShortCur=iMA(NULL,0,13,0,MODE_SMA,PRICE_CLOSE,i+1);// быстрая средняя на
//предпредыдущем баре
maShortPrev=iMA(NULL,0,13,0,MODE_SMA,PRICE_CLOSE,i+2);// быстрая средняя на
//предыдущем баре
//Если есть сигнал к покупке на предыдущем баре - поставим синюю стрелку вверх
// по цене открытия бара
if((maShortCur>maLongCur)&&(maShortPrev<maLongPrev))
{
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 241 , стрелка вверх
ObjectSet(arrowName,OBJPROP_ARROWCODE,241);
// зададим цвет стрелке - голубой
ObjectSet(arrowName,OBJPROP_COLOR,Blue);
// увеличить счетчик стрелок
arrowCounter++;
value1=Open[i];//запомним цену голубой стрелки
}
//Если есть сигнал к продаже на предыдущем баре - поставим красную стрелку вниз
// по цене открытия бара
if((maShortCur<maLongCur)&&(maShortPrev>maLongPrev))
{
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 242 , стрелка вниз
ObjectSet(arrowName,OBJPROP_ARROWCODE,242);
// зададим цвет стрелке - красный
ObjectSet(arrowName,OBJPROP_COLOR,Red);
// увеличить счетчик стрелок
arrowCounter++;
value2=Open[i];//запомним цену красной стрелки
}
//запишем данные для текущего бара в файл
FileWrite(FileHandle,TimeToStr(Time[i]),i,value1,value2);
}
return(0);
}
Теперь у нас все готово, но код скрипта на самом деле не очень удобен. У нас жестко задано, что период медленной средней равен 21, а период быстрой средней равен 13. Нет необходимой гибкости, в следующий раз, когда мы захотим проверить другую пару периодов, нам придется менять эти значения в коде и заново компилировать, что не очень удобно. Для таких случаев существуют внешние переменные – параметры индикаторов, советников и скриптов. Введем новые переменные LongPeriod и ShortPeriod, зададим им начальные значения, а сами переменные объявим с ключевым словом extern (внешний).

Вычисление значений средних теперь будет выглядеть так:

Мы уже имеем 4-ый вариант такого простого скрипта.
extern int LongPeriod=21; // период медленной средней
extern int ShortPeriod=13; // период быстрой средней
int FileHandle; // указатель на на файл - глобальная переменная
int init()
{
string FileName; // имя файла - локальная переменная
int deletedArrows;
// сформируем имя файла
Print("Выполняется init()");
FileName="CrossMA.csv";
//откроем файл с именем FileName (создадим указатель/handle на него)
FileHandle=FileOpen(FileName,FILE_WRITE | FILE_CSV,";");
if (FileHandle<1)
{
Print("Не удалось открыть файл, ошибка ",GetLastError());
return;
}
// запишем названия столбцов (создание шапки)
FileWrite(FileHandle,"Дата","Номер бара","Стрелка вверх","Стрелка вниз");
// удалим все стрелки, если они есть
deletedArrows=ObjectsDeleteAll(0,OBJ_ARROW);
Comment("Удалено ",deletedArrows," обектов OBJ_ARROW");
}
//----
int deinit()
{
Print("Выполняется deinit()");
//закроем файл (освободим указатель/handle, чтобы файл можно было
//открыть для редактирования другими программами)
if(FileHandle>0) FileClose(FileHandle);
return(0);
}
//----
int start()
{
double maLongCur, maLongPrev, maShortCur, maShortPrev;
string arrowName; // здесь будут назначаться уникальное имя объекта-стрелки
int arrowCounter=0;// счетчик стрелок
// объявим служебные переменные, в которых будем хранить ценовые координаты стрелок
double value1,value2;
Print("Выполняется start()");
for(int i=Bars-1;i>=0;i--)
{
// обнулим службные переменные
value1=0.0;
value2=0.0;
maLongCur=iMA(NULL,0,LongPeriod,0,MODE_SMA,PRICE_CLOSE,i+1);// медленная средняя
// на предыдущем баре
maLongPrev=iMA(NULL,0,LongPeriod,0,MODE_SMA,PRICE_CLOSE,i+2);// медленная средняя
// на предпредыдущем баре
maShortCur=iMA(NULL,0,ShortPeriod,0,MODE_SMA,PRICE_CLOSE,i+1);// быстрая средняя
// на предпредыдущем баре
maShortPrev=iMA(NULL,0,ShortPeriod,0,MODE_SMA,PRICE_CLOSE,i+2);// быстрая средняя
// на предыдущем баре
//Если есть сигнал к покупке на предыдущем баре - поставим синюю стрелку вверх
// по цене открытия бара
if((maShortCur>maLongCur)&&(maShortPrev<maLongPrev))
{
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 241 , стрелка вверх
ObjectSet(arrowName,OBJPROP_ARROWCODE,241);
// зададим цвет стрелке - голубой
ObjectSet(arrowName,OBJPROP_COLOR,Blue);
// увеличить счетчик стрелок
arrowCounter++;
value1=Open[i];//запомним цену голубой стрелки
}
//Если есть сигнал к продаже на предыдущем баре - поставим красную стрелку вниз
// по цене открытия бара
if((maShortCur<maLongCur)&&(maShortPrev>maLongPrev))
{
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 242 , стрелка вниз
ObjectSet(arrowName,OBJPROP_ARROWCODE,242);
// зададим цвет стрелке - красный
ObjectSet(arrowName,OBJPROP_COLOR,Red);
// увеличить счетчик стрелок
arrowCounter++;
value2=Open[i];//запомним цену красной стрелки
}
//запишем данные для текущего бара в файл
FileWrite(FileHandle,TimeToStr(Time[i]),i,value1,value2);
}
return(0);
}
Но тут появляется новый ньюанс. При запуске советника или индикатора на графике появляется диалоговое окно, в котором можно задать входные параметры, но при запуске скрипта (как в нашем случае) такого нет. В этом случае, если требуется , чтобы скрипт получил входные параметры (предложил их ввести пользователю) используется директива#property show_inputs
Добавим эту строчку и получим пятый окончательный вариант. При использовании его на графике увидим:
Если просто закрыть диалоговое окно, то увидим по закладке «Эксперты», что ни одна из функций не вызывалась, скрипт был удален сразу без исполнения. Откроем файл CrossMA.csv , в нем 4 столбца. Два последних столбца нам понадобятся для написания индикатора.

Сами скрипты можно скачать здесь - Читать Закрыть 11
Создание индикатора.
Теперь мы вплотную подошли к пониманию пользовательских индикаторов в МТ4. Сделаем настоящий, а не графический индикатор по сигналам пересечения двух скользящих средних (МА). Файл CrossMA.csv содержит столбцы «Стрелка вверх» и «Стрелка вниз». Каждому бару однозначно в текущий момент времени соответствует время открытия бара (Time[]), номер бара (который будет увеличиваться с каждым новым завершенным баром) и значения из двух столбцов/массивов, указывающих на наличие красной или синей стрелки, или отсутствие обеих (нулевое значение). Создадим пользовательский индикатор с помощью «Мастера создания советника».

Укажем имя индикатора (CrossMA), автора, линк. Рядом с таблицей параметров жмем кнопку «Добавить», появляется новый параметр ExtParam1 (имя параметра по умолчанию ), даем новому параметру более осмысленное название LongPeriod, как в нашем предыдущем скрипте, тип int оставляем неизменным, начальное значение задаем равным 21.

Добавляем еще один параметр ShortPeriod, работа с внешними параметрами нашего индикатора на этом закончена. Идем «Далее». Чек-бокс «Индикатор в отдельном окне» не трогаем, наши стрелки будут располагаться на графике цен (Чек-боксы «Минимум» и «Максимум» поэтому нам недоступны). Опять жмем «Добавить», в списке индексов на строчке 1 появляется индекс типа «Line» и красного цвета («Red»), меняем тип на «Arrow» цвет на голубой «Blue». Тип символа 217(стрелка вверх) поменяем на 241 для полного сходства со скриптом.

Добавим второй индекс, также изменим все его данные (тип, цвет, символ).

#property copyright "MetaQuotes"
#property link "http://forum.alpari-idc.ru/viewtopic.php?t=48186"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 Blue
#property indicator_color2 Red
//---- input parameters
extern int LongPeriod=21;
extern int ShortPeriod=13;
//---- buffers
double ExtMapBuffer1[];
double ExtMapBuffer2[];
//----
int init()
{
SetIndexStyle(0,DRAW_ARROW);
SetIndexArrow(0,241);
SetIndexBuffer(0,ExtMapBuffer1);
SetIndexEmptyValue(0,0.0);
SetIndexStyle(1,DRAW_ARROW);
SetIndexArrow(1,242);
SetIndexBuffer(1,ExtMapBuffer2);
SetIndexEmptyValue(1,0.0);
//----
return(0);
}
//----
int deinit()
{
//----
//----
return(0);
}
//----
int start()
{
int counted_bars=IndicatorCounted();
return(0);
}Мастер по созданию советников сделал черновую работу, мы видим, что индикатор будет выводиться на графике цен, а не в отдельном окне, индикатор содержит два служебных буфера, указаны их цвета, объявлены массивы
double ExtMapBuffer1[];
в которых, видимо, и будут храниться значения индикатора. Также созданы два входных параметра.
double ExtMapBuffer2[];

По сравнению с тем, как мы создавали скрипт, при создании индикатора была также создана функция init(), в которой производится окончательная настройка нашего индикатора. Индикаторы в МТ4 обладают очень большой гибкостью, каждый индикатор может «нести» в себе до 8 служебных(индикаторных) буферов. При этом достаточно только правильно объявить эти буфера, а остальную заботу о размере этих буферов возьмет на себя терминал. Эти буфера также называются индексами, пользовательский индикатор может выдавать значения для нулевого индекса, первого индекса , второго и так далее до седьмого индекса. Количество индексов мы задаем директивой#property indicator_buffers N
но цвета при этом задаются не с нуля, а с единицы#property indicator_color1 Blue
#property indicator_color2 Red
С помощью SetIndexStyle(номер_индекса, стиль) мы задаем каждому индексу необходимый стиль (каждый индикатор может иметь несколько индексов с разными стилями рисования), указав на стиль Arrow мы далее уточнили код символа (241 и 242) для каждого индекса, и только потом было указано, что индекс 0 – это массив ExtMapBuffer1, индекс 1 – это данные из массива ExtMapBuffer2. Без такого связывания индекса и массива функцией SetIndexBuffer(номер_индекса, имя_массива_буфера) индикатор работать не будет, это одна из ошибок при написании индикаторов. Вторая возможная ошибка (которую очень сложно бывает обнаружить) – объявление буферов типом, отличным от double, например int .

Последней строчкой идет функция SetIndexEmptyValue(номер_индекса, Пустое_Значение). В языках программирования обычно нет понятия "отсутствия значения", если у нас есть понятие "Свет", то понятие "Тьма" в программировании будет не отсутствие Света, а очень мало Света. Поэтому важно указать, что какое-то определенное значение будет означать отсутствие значения (в нашем случае – отсутствие стрелки), обычно таким значением бывает 0.0 (ноль с точностью до одного знака после запятой), хотя можно использовать и другие значения. Осталось только написать алгоритм, который и заполнит индикаторные буфера (индексы) значениями, на которых будут располагаться стрелки. Для этого обратимся к последнему скрипту CrossMASignals-5, скопируем все из блока start(), уберем операции с нанесением стрелок на график и записью в файл. Я также добавил функции Print() в блоки init() и deinit(). Мы практически сделали индикатор, не написав еще кода, а только скопировав некоторые куски из скрипта и закомментировав ненужные места. Если поставить /* в начале фрагмента, а */ в конце фрагмента, то весь фрагмент станет комментарием, которые, как мы знаем, не компилируются. Это бывает удобно при поисках ошибок и необходимости отключать при компиляции большие куски кода.

#property copyright "MetaQuotes"
#property link "http://forum.alpari-idc.ru/viewtopic.php?t=48186"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 Blue
#property indicator_color2 Red
//---- input parameters
extern int LongPeriod=21;
extern int ShortPeriod=13;
//---- buffers
double ExtMapBuffer1[];
double ExtMapBuffer2[];
//----
int init()
{
//---- indicators
Print("Выполняется init()");
SetIndexStyle(0,DRAW_ARROW);
SetIndexArrow(0,241);
SetIndexBuffer(0,ExtMapBuffer1);
SetIndexEmptyValue(0,0.0);
SetIndexStyle(1,DRAW_ARROW);
SetIndexArrow(1,242);
SetIndexBuffer(1,ExtMapBuffer2);
SetIndexEmptyValue(1,0.0);
return(0);
}
//----
int deinit()
{
Print("Выполняется deinit()");
return(0);
}
//----
int start()
{
int counted_bars=IndicatorCounted();
//----
double maLongCur, maLongPrev, maShortCur, maShortPrev;
/*
string arrowName; // здесь будут назначаться уникальное имя объекта-стрелки
int arrowCounter=0;// счетчик стрелок
*/
// объявим служебные переменные, в которых будем хранить ценовые координаты стрелок
double value1,value2;
Print("Выполняется start()");
for(int i=Bars-1;i>=0;i--)
{
// обнулим службные переменные
value1=0.0;
value2=0.0;
maLongCur=iMA(NULL,0,LongPeriod,0,MODE_SMA,PRICE_CLOSE,i+1);// медленная средняя
// на предыдущем баре
maLongPrev=iMA(NULL,0,LongPeriod,0,MODE_SMA,PRICE_CLOSE,i+2);// медленная средняя
// на предпредыдущем баре
maShortCur=iMA(NULL,0,ShortPeriod,0,MODE_SMA,PRICE_CLOSE,i+1);// быстрая средняя
// на предпредыдущем баре
maShortPrev=iMA(NULL,0,ShortPeriod,0,MODE_SMA,PRICE_CLOSE,i+2);// быстрая средняя
// на предыдущем баре
//Если есть сигнал к покупке
// на предыдущем баре - поставим синюю стрелку вверх
// по цене открытия бара
if((maShortCur>maLongCur)&&(maShortPrev<maLongPrev))
{
/*
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 241 , стрелка вверх
ObjectSet(arrowName,OBJPROP_ARROWCODE,241);
// зададим цвет стрелке - голубой
ObjectSet(arrowName,OBJPROP_COLOR,Blue);
// увеличить счетчик стрелок
arrowCounter++;
*/
value1=Open[i];//запомним цену голубой стрелки
}
//Если есть сигнал к продаже на предыдущем баре - поставим красную стрелку вниз
// по цене открытия бара
if((maShortCur<maLongCur)&&(maShortPrev>maLongPrev))
{
/*
// Поставим стрелку
// имя объекта - arrowName
// тип объекта - OBJ_ARROW
// координата по горизонтали - Time[i] время открытия бара
// координата по вертикали - Open[i] цена открытия бара
arrowName="arrow"+arrowCounter;
ObjectCreate(arrowName,OBJ_ARROW,0,Time[i],Open[i]);
// зададим тип стрелки - 242 , стрелка вниз
ObjectSet(arrowName,OBJPROP_ARROWCODE,242);
// зададим цвет стрелке - красный
ObjectSet(arrowName,OBJPROP_COLOR,Red);
// увеличить счетчик стрелок
arrowCounter++;
*/
value2=Open[i];//запомним цену красной стрелки
}
/*
//запишем данные для текущего бара в файл
FileWrite(FileHandle,TimeToStr(Time[i]),i,value1,value2);
*/
}
return(0);
}
Код отлично компилируется, но рисовать наш индикатор ничего не будет. Для этого нам необходимо добавить только две строчки:
Насколько мы помним, CrossMA.csv(после выполнения скрипта CrossMASignals-5) содержит два столбца : «Стрелка вверх» и «Стрелка вниз». Теперь эти столбцы у нас находятся в буферах ExtMapBuffer1[] и ExtMapBuffer2[] .
ExtMapBuffer1[i]=value1;
ExtMapBuffer2[i]=value2;
Компилируем индикатор и запускаем его опять на графике EURUSD D1. У нас снова имеется на графике два набора стрелок (красные и синие), но только стрелки меньше размером и они уже не являются графическими объектами, доступными для редактирования, стрелки стали неотъемлемой частью индикатора и будут перерисовываться при смене тайм-фрейма. Откроем панель «Окно данных» и наведем перекрестие на последнюю синюю стрелку вверх (чтобы получить режим перекрестия, достаточно нажать среднюю кнопку мыши или колесико). Напротив CrossMA находится значение 1.1886 – это и есть значение буфера ExtMapBuffer1 на данном баре, value2 – это значение буфера ExtMapBuffer2 на данном баре, значение для него отсутствует, так как 0.0 является пустым значением ( помнитеSetIndexEmptyValue(0,0.0);
? )
SetIndexEmptyValue(1,0.0);

Как видите, ничего сложного в создании индикатора нет.
Сам индикатор можно скачать здесь - Читать Закрыть 12
Оптимизация индикатора.
Индикатор, который мы создали, несмотря на свой малый размер, обладает большим недостатком. Этот индикатор потребляет слишком много ресурсов процессора. Если посмотреть закладку «Эксперты» (в который видны логи, создаваемые программами MQL-4), то увидим множество выводов функции Print()(речь идет об индикаторе CrossMA-2.mq4):

Так как, функция start() вызывается на каждом тике, цикл for() выполняется при каждом изменении цены. Если вы обратили внимание, «Мастер создания советников» создал переменную counted_bars (посчитанные бары), рассмотрим ее поведение при работе индикатора. Я добавил новую переменную limit, а также сделал вывод на печать .

Логика работы индикатора не изменилась, по-прежнему цикл начинает считать от i=Bars-1 (limit=Bars-1). Но наблюдение за выводом Print() в блоке start() позволяет улучшить работу индикатора.

Видим, что при запуске индикатора, при первом вызове start() counted_bars равен нулю, Bars равен 4413, а limit равен 4412 (на единицу меньше, чем Bars). При втором вызове counted_bars уже равен 4412 и в дальнейшем не меняется на текущем баре. Получается, что значения индикатора рассчитываются полностью в первый раз, а потом на каждом тике происходит ненужный повторный расчет, вместо того, чтобы рассчитывать значения индикатора только на нулевом баре, который еще не завершился. Если мы сможем уклониться от ненужного расчета, то существенно разгрузим процессор компьютера. Представьте, что у нас открыто 10 графиков, и на каждом висят по три подобных неоптимизированных индикатора. При первом запуске counted_bars=0, проверим это условие, и если оно выполняется, то ограничитель limit оставим как есть.

Если индикатор уже рассчитывался, то будем считать только на нулевом баре. Ноль получим как Bars-counted_bars-1 (4413 – 4412 -1).

Новый модернизированный индикатор подтверждает наши предположения:

Теперь, в первый раз индикатор рассчитывается на 4413 барах, а последующие вычисления производятся только для одного (нулевого) бара. Что будет, когда текущий бар завершится и откроется новый бар? Ответ на этот вопрос легко получить самому, если пронаблюдать вывод индикатора на минутном тайм-фрейме. Полезно для любого нового индикатора, который вы написали, вставить именно эту строчку: Print("Bars=",Bars," limit=",limit," counted_bars=",counted_bars); Это позволит сразу увидеть первую возможную ошибку – неоптимизированный алгоритм расчета индикатора. Вторую распространенную ошибку (пережатость алгоритма расчета или переоптимизация) мы рассмотрим позднее.
#property copyright "MetaQuotes"
#property link "http://forum.alpari-idc.ru/viewtopic.php?t=48186"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 Blue
#property indicator_color2 Red
//---- input parameters
extern int LongPeriod=21;
extern int ShortPeriod=13;
//---- buffers
double ExtMapBuffer1[];
double ExtMapBuffer2[];
//----
int init()
{
Print("Выполняется init()");
SetIndexStyle(0,DRAW_ARROW);
SetIndexArrow(0,241);
SetIndexBuffer(0,ExtMapBuffer1);
SetIndexEmptyValue(0,0.0);
SetIndexStyle(1,DRAW_ARROW);
SetIndexArrow(1,242);
SetIndexBuffer(1,ExtMapBuffer2);
SetIndexEmptyValue(1,0.0);
return(0);
}
//----
int deinit()
{
Print("Выполняется deinit()");
return(0);
}
//----
int start()
{
int counted_bars=IndicatorCounted();
int limit; // ограничитель расчетов
double maLongCur, maLongPrev, maShortCur, maShortPrev;
// объявим служебные переменные, в которых будем хранить ценовые координаты стрелок
double value1,value2;
if (counted_bars==0) limit=Bars-1; // посчитанных баров еще нет, будет считать
// с самого начала
if (counted_bars>0) limit=Bars-counted_bars-1; // вычтем из числа доступных
// баров количество
//посчитанных баров и уменьшим на единицу
Print("Bars=",Bars," limit=",limit," counted_bars=",counted_bars);
for(int i=limit;i>=0;i--)
{
// обнулим службные переменные
value1=0.0;
value2=0.0;
maLongCur=iMA(NULL,0,LongPeriod,0,MODE_SMA,PRICE_CLOSE,i+1);// медленная средняя
// на предыдущем баре
maLongPrev=iMA(NULL,0,LongPeriod,0,MODE_SMA,PRICE_CLOSE,i+2);// медленная средняя
// на предпредыдущем баре
maShortCur=iMA(NULL,0,ShortPeriod,0,MODE_SMA,PRICE_CLOSE,i+1);// быстрая средняя
// на предпредыдущем баре
maShortPrev=iMA(NULL,0,ShortPeriod,0,MODE_SMA,PRICE_CLOSE,i+2);// быстрая средняя
// на предыдущем баре
//Если есть сигнал к покупке на предыдущем баре - поставим синюю стрелку вверх
// по цене открытия бара
if((maShortCur>maLongCur)&&(maShortPrev<maLongPrev))
{
value1=Open[i];//запомним цену голубой стрелки
}
//Если есть сигнал к продаже на предыдущем баре - поставим красную стрелку вниз
// по цене открытия бара
if((maShortCur<maLongCur)&&(maShortPrev>maLongPrev))
{
value2=Open[i];//запомним цену красной стрелки
}
ExtMapBuffer1[i]=value1;
ExtMapBuffer2[i]=value2;
}
//----
return(0);
}
На первый взгляд может показаться, что проблема оптимального расчета пользовательского индикатора не настолько серьезна, тем более, что с каждым годом выходят все более мощные процессоры, и новые компьютеры становятся настоящими числомолотилками. Тем не менее, навыки правильного написания индикатора пригодятся в любом виде деятельности, так как они приучают мыслить логически и экономно, а для трейдера эта черта крайне необходима. Чтобы показать важность правильного подхода, мы немного забежим вперед и прогоним простейшего эксперта, который не открывает сделок и вообще не совершает никаких торговых операций, а только содержит вызов нашего индикатора на каждом новом баре один раз. Я создал два советника, один содержит вызовы «правильного» - оптимизированного индикатора, а второй содержит вызовы «неправильного» индикатора, наш самый первый рабочий вариант индикатора. Для вызова пользовательских индикаторов используется функция iCustom(Инструмент,Тайм_фрейм,Имя_индикатора, параметры_пользовательского_индикатора, номер_индекса, номер_бара). Фактически, проверяются первые три параметра и два последних параметра, а остальные параметры, которые расположены между ними, считаются параметрами индикатора и передаются непосредственно пользовательскому индикатору. Правильность передаваемых параметров компилятором не проверяется, так как связывание(вызов) пользовательского индикатора происходит динамически, на лету. Вызов нашего индикатора будет выглядеть так:blueArrow=iCustom(NULL,0,"CrossMA-3",21,13,0,0);
redArrow=iCustom(NULL,0,"CrossMA-3",21,13,1,0);
Вызов пользовательского индикатора без параметров будет равнозначен вызову индикатора с параметрами по умолчанию (в нашем случае 21 и 13), и поэтому такая запись будет аналогична предыдущей:blueArrow=iCustom(NULL,0,"CrossMA-3",0,0);
redArrow=iCustom(NULL,0,"CrossMA-3",1,0);

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

Я сделал два советника, один содержит вызов оптимизированного индикатора (CrossMA-3) с именем TestCrossMA, а второй содержит вызов неоптимизированного индикатора(CrossMA-2) , назван TestCrossMAwrong. Скачать их можно здесь . Сохраните их в папке /experts, скомпилируйте, затем в терминале откройте «Тестер стратегий» (Ctrl+R) , установите необходимые параметры и нажмите кнопку «Старт». На моем компьютере (Celeron-2000) прогон оптимизированного индикатора занял чуть больше 1 секунды, а неоптимизированного более 30 секунд

Этот пример наглядно показывает необходимость оптимального расчета пользовательского индикатора, если запустить эти два советника для тестирования на меньших тайм-фреймах (H4, H1 и так далее), то разрыв будет еще большим.

- Читать Закрыть 13
Построение средней.
Мы узнали, как создать индикатор, который рисует символы/стрелки на пересечениях двух средних с разным периодом. Теперь мы напишем индикатор, который строит простую скользящую среднюю. Скользящие средние одни из самых важных в техническом анализе, на их основе строится подавляющая часть других индикаторов, как стандартных, так и разрабатываемых самими трейдерами. Скользящая средняя характеризуется периодом расчета, типом цен, по которым рассчитывается и смещением. Если мы хотим рассчитать простую среднюю с периодом 3 по ценам закрытия, то мы должны взять сумму: ( цена закрытия сегодня + цена закрытия вчера + цена закрытия позавчера)/3.
МА(3,Close)=(Close[0]+Close[1]+Close[2])/3.
Обратимся опять к «Мастеру создания Советника». Создадим параметр PeriodMA и ShiftMA (смещение).
Следующим шагом добавим индекс , тип и цвет оставим как есть, жмем «Готово».

Получили стандартный шаблон:

Добавим в блок start() часть кода из индикатора CrossMA-3, чтобы не писать заново цикл и служебные переменные.

Мы уже хорошо представляем как происходит работа в цикле for(), теперь мы можем заменить цикл for() на цикл while(). Выносим объявление переменной i (int i=limit;) за пределы for() перед циклом, операцию декремента (i--;) вносим в тело цикла в самый конец, цикл for переименовываем на while:

Как видим, цикл while() полностью заменил цикл for(). Также производится проверка выражения в скобках на истинность, и если оно соблюдается, то выполняется тело цикла. При этом важно не забыть вставить в цикл оператор уменьшения счетчика i, иначе мы получим бесконечный цикл. Компилятор такую ошибку обнаружить не сможет (так как она не синтаксическая, а логическая), и при использовании такого неправильного индикатора (с бесконечным циклом) терминал просто зависнет и перестанет реагировать на действия пользователя. Единственный способ выхода из такой ситуации – закрыть принудительно из диспетчера задач терминал, открыть MetaEditor, исправить индикатор и можно снова запускать MetaTrader4. Но в скриптах использовать бесконечный цикл можно.

Применение цикла while() или for() обычно является делом вкуса, хотя бывают ситуации, когда while() предпочтительней , например, если необходимо двигаясь от текущего бара в глубь истории (в сторону увеличения индекса бара) найти некий бар, удовлетворяющий какому-то условию. При этом необходимо не забыть, что искомый бар может и не быть найденным, а индекс бара будет расти до бесконечности. И чтобы избежать зацикливания, необходимо добавить проверку индекса на превышение числа Bars. Пример такого цикла:

Цикл у нас есть, теперь осталось написать алгоритм вычисления суммы на количестве баров PeriodMA, то есть что-то подобное:
val=(Close[i]+Close[i+1]+Close[i+2]+… Close[i+n])/PeriodMA
Чему будет равно n, если известно PeriodMA (период нашей средней)? Есть люди, которые легко вычислят нужную формулу в уме, я много раз ошибался сам, когда проделывал это без помощи бумаги и ручки, поэтому более надежно будет пойти методом индукции. Времени займет минуту, а правильность результата гарантирует.
Возьмем частный случай, n=5, то есть как будто мы ищем среднее за 5 баров. Тогда наша формула выглядит так:
Val=(Close[i]+Close[i+1]+Close[i+2]+Close[i+3]+Close[i+4])/5
или по-другому:
Val=(Close[i]+Close[i+1]+…+Close[i+PeriodMA-2]+Close[i+PeriodMA-1])/PeriodMA
Такое механическое суммирование мы легко поместим опять-таки в цикл for().

В данном случае мы использовали оба типа циклов, получилось удобное визуальное разделение алгоритма прохода по барам графика и алгоритма суммирования цен. Теперь опять вернемся к началу блока start(). Как видно из рисунка, устанавливать ограничитель расчета limit при первом вызове индикатора в значение Bars-1 нет никакого обоснования, баров в глубине истории нет и усреднять еще нечего. Если мы для примера хотим рассчитать среднюю с периодом 5, то вычислять эту среднюю мы можем начать только с бара Bars-5 (см. статью «Что такое графики»).

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

На этом создание индикатор простой скользящей средней по ценам закрытия можно считать законченным, но иногда возникает соблазн создать максимально оптимизированный индикатор, и делается попытка улучшить и ту часть блока start(), где counted_bars уже больше нуля, то есть первоначальный расчет индикатора уже проводился. Исходя из соображений, что после первого прогона индикатора Bars не изменился, расчет индикатора производился на количестве баров от Bars-PeriodMA до нуля (то есть counted_bars предполагается равным примерно Bars-PeriodMA), пишется следующее :

Так как преследуется цель считать только на нулевом баре, то можно предположить, что limit=0 и 0=Bars-counted_bars-PeriodMA. Отсюда следует, что мы исходим из того, что при втором прогоне индикатора counted_bars+PeriodMA==Bars. На самом деле это не так, это вторая ошибка оптимизации, я называю такой индикатор пережатым. Функция IndicatorCounted() не возвращает количество баров, для которых был сделан расчет значений данным конкретным индикатором, а возвращает количество баров, которые не изменились с последнего вызова индикатора (нулевой бар менялся, иначе бы не пришел этот тик, на котором мы и производим проверку). Поэтому, если Bars=1000, то при первом вызове индикатора IndicatorCounted() вернет 0 (индикатор еще не рассчитывался), а при втором вызове IndicatorCounted() вернет уже Bars-1. Вы сами может проверить это , создав пустой индикатор (в котором не производится вообще никаких расчетов) и вставив в блок start() вызовPrint("Bars=",Bars," counted_bars=",counted_bars);
Если мы все же не заметим этой ошибки и оставим наш индикатор таким как есть(переоптимизированным или пережатым) то произойдет следующее ( пусть у нас Barsравно 1000, а PeriodMA равно 5):- в первый раз индикатор рассчитается правильно от бара Bars-PeriodMA до нулевого бара
- на следующем тике проверка counted_bars окажется больше нуля и ограничитель расчета станет равным limit=1000-999-5= 1-5=-4
- в блок while() (или for(), вид цикла не имеет значения) поступает ограничитель, который меньше нуля и производится проверка while(-4>=0) , проверка оказывается неудачной , и пересчет на нулевом баре не производится.
Выявить такую ошибку можно двумя способами:- запустить индикатор на мелком тайм-фрейме и наблюдать его поведение
- прогнать в тестере советник, содержащий вызов нашего индикатора.

К счастью, в данном случае ошибка расчета индикатора при работе в онлайн быстро становится явной(индикатор просто не рисуется), но в более сложных случаях значение индикатора просто плывет и на глаз увидеть это не всегда можно. Скачать индикаторы Simply MA.mq4 и Simply MA wrong.mq4 можно здесь . - Читать Закрыть 14
Построение средней (окончание).
Мы создали индикатор простой скользящей средней с периодом PeriodMA, теперь нам осталось задействовать параметр ShiftMA, который отвечает за смещение индикатора. Часто в теханализе требуется не только рассчитать значения индикатора, но и сместить их на какое-то количество баров вперед или назад. Это свойство называется смещением. В MQL-4 смещение индикатора достигается очень просто, достаточно любому индикаторному индексу установить признак смещения с помощью функции SetIndexShift(), для этого в блоке init() мы пишем эту простую конструкцию и получаем желаемый результат.

Набрасываем на график наш новый индикатор со смещением ноль и второй индикатор со смещением, например, три. Мы видим, что индикатор, использующий смещение, рисуется на три бара вперед, при отрицательном смещении индикатор будет рисоваться с запаздыванием. Мы можем улучшить индикатор. Когда мы смотрим в Окне Данных (Ctrl+D) значения нашего индикатора, то мы видим, что указывается имя индикатора и значение его для данного бара. Часто индикатор может содержать больше одного индекса, обычно нулевой индекс именуется по имени индикатора, а все остальные индексы именуются как Value1 , Value2 и так далее. Для того, чтобы дать более осмысленные имена индексам, служит функция SetIndexLabel(), которая задает отображаемое имя для указанного индекса. Используем в качестве имени SMA(PeriodMA), таким образом мы показали, что это простая скользящая средняя с периодом PeriodMA. Обычно эта функция также используется в блоке init().

Если мы посмотрим технический индикатор Moving Average, то увидим, что мы не использовали тип цен для расчета нашего индикатора. Обратимся к справке ME, там указано 7 стандартных ценовых констант :

Добавим новый параметр PriceType( тип цены) и изменим блок start() таким образом, чтобы наш индикатор считался не только по ценам открытия. Если мы вместо цен Close[i+k] в цикле for() будем подставлять необходимый тип цены, то больше ничего и не надо. Введем новую вспомогательную переменную double price и будем задавать ее значение в соответствии со справкой МЕ. Сделаем это для унификации пользовательского и технического индикатора, в блоке switch(PriceType) используем в качестве ключей предопределенные ценовые константы.

Кроме того, процедуру формирования метки для индекса в блоке init() сделаем также более гибкой, теперь будем отображать не только период(PeriodMA) и смещение(ShiftMA), но и тип цены, от которой построен индикатор.

На этом построение простой скользящей средней MA.mq4 будет полным.

Теперь рассмотрим еще один вариант средней. Часто бывают индикаторы, когда текущее значение индикатора зависит от предыдущего значения и текущей цены. Можно записать этот так: F[i]=F[i+1]*a+Price[i]*b, где a и b - коэффициенты, обычно меньше 1. Иногда требуют, чтобы a+b=1. Тогда F[i]=F[i+1]*(1-b)+Price[i]*b . Чем меньше коэффициент b, тем меньше реагирует индикатор на изменение цен и больше зависит от предыдущего значения индикатора. Для экспоненциальной скользящей средней(ЭСС) коэффициент b=2/(1+PeriodMA). Видим, что чем больше период ЭСС, тем более плавной будет кривая индикатора. Построим новый индикатор EMA.mq4 на базе MA.mq4. Произведем косметические изменения в блоке init()

В блоке start(), правда, появляется некоторая заминка. В самом начале, когда мы рассчитываем индикатор в первый раз, у нас нет предыдущего значения индикатора. Нам негде взять предыдущее значение и нам придется делать какое-то допущение. Первый вариант – принять соглашение, что для бара с индексом Bars-1 значение индикатора равно значению цены на этом баре (High, Open, Close или другая ценовая константа, например, {High+Low+Close}/3.0 ), а дальше рассчитывать по алгоритму. Второй вариант – для бара с индексом Bars-PeriodMA значение индикатора равно простой скользящей стредней (ПСС), а дальше опять рассчитывать по алгоритму. Первый вариант реализован в пользовательском индикаторе Moving Averages.mq4 , который идет в составе дистрибутива MetaTrader4 и находится в папке MetaTrader 4expertssamplesindicators . Поэтому мы пойдем по второму варианту, чтобы сравнить их между собой.

Блок вычисления простой средней (синего цвета) возьмем из индикатора MA.mq4. Небольшая доводка, и индикатор EMA.mq4 готов.

Видно, что в коде есть два одинаковых участка/блока, которые очень похожи друг на друга. MQL-4 позволяет объявлять свои(пользовательские) функции для однообразных вычислений. Справку по функциям можно посмотреть в «Справочнике MQL4» в разделе «Функции». Создадим функцию GetPrice(int PriceMode, int index), перенесем в нее все вычисления, тип функции зададим double.

Теперь мы заменим эти блоки одной строчкой вызова функции GetPrice() и индикатор EMA2.mq4.mq4 готов. Произведем такие же изменения с индикатором MA.mq4 и назовем его MA2.mq4. Скачать их можно здесь .
- Читать Закрыть 15
Перевод индикатора из MQL-2 в MQL-4.
Рассмотрим на примере несложного индикатора, написанного для терминала MetaTrader3, процесс переноса кода в MQL-4. Хотя сам язык MQL-2 является существенно более простым, но некоторые особенности и его ограничения наложили свой отпечаток на стиль написания сложных индикаторов. Первое отличие- в MQL-2 индикатор мог отрисовывать не более двух индексов, поэтому в случае необходимости приходилось писать несколько родственных индикаторов (вспомним из известных индикаторы Pivot, где кроме точки вращения нужно было рисовать и линии поддержки и сопротивления). В других случаях приходилось создавать индикатор, который строился на вызовах другого пользовательского индикатора, так как была потребность в дополнительных буферах, а использовать массивы не всегда было удобно.
Второе ограничение – из-за низкой скорости работы языка-интерпретатора работа индикатора (вычисления в теле индикатора или эксперта) принудительно завершалась через определенное время, приходилось использовать служебную переменную, в которой хранился индекс последнего рассчитанного бара, и при поступлении нового тика продолжать работу цикла с запомненного значения. В MQL-4 такой потребности нет, язык быстрый и для целей оптимизации была специально создана функция IndicatorsCounted().
Третье ограничение – малое число стилей отрисовки, в МТ3 была линия, гистограмма и стрелки. Сам код индикатора на MQL-2
Зеленым цветом прокомментирована интерфейсная часть, тут никаких сложностей нет. Далее в секции Inputs идут входные параметры , здесь нам необходимо только определиться каким типом их объявить в MQL-4. Переменные ppor(100),mpor(-100) используются для отрисовки второго буфера, а так индикаторные буфера в MQL-4 всегда имеют тип double, то для единообразия объявим их тем же типом. Переменная per является периодом индикатора CCI(), поэтому объявим ее типом int. Переменная t3_period(8) используется для вычисления значения типа double, хотя по смыслу должна означать некий целочисленный период, поэтому можно объявить ее и так и сяк, но я все же объявлю ее с типом double. С переменной b и так все понятно. Переменная x_prd нигде в коде не используется, ее просто выбрасываем. Все переменные (Variable), кроме shift, объявим типом double, это видно из логики вычислений. Все готово для написания индикатора. Начинаем стандартную процедуру:

Имя индикатора я поставил такое же, что было в МТ3, хотя в самом коде стоит имя RoundPrice.

Переменные можем скопировать из кода mql, поменяем Inputs на double, объявим их в интерфейсной части. Можно заметить, что эта часть кода бесполезно вычисляется каждый тик, хотя значения этих переменных не меняются:

Поэтому, мы можем эту часть вычислений поместить в ту часть индикатора, которая выполняется только один раз перед после запуска индикатора – в блок init().
Оператор if() требует минимальной правки.

Оператор SetLoopCount(0); просто игнорируем, он из прошлой жизни и в MQL-4 абсолютно не нужен никогда. Опять копируем код и приводим синтаксис к MQL-4. Открывающий оператор Begin и закрывающий оператор End заменяем фигурными скобками:

Нам осталось только перевести три выделенных блока, и работа, можно сказать, закончена. Операторы SetIndexValue() и SetIndexValue2() в MQL-2 заполняют индикаторные массивы №1 и №2. Поэтому запишем просто:

Технический индикатор iCCI() также переносится несложно, добавим только параметр инструмента и тайм-фрейма. В MQL-2 было два технических индикатора : iCCI() и iCCIEx(), разница между ними только в том, что первый строился только по ценам закрытия, а второму можно было задавать тип цен для расчетов. Можно посмотреть справку МТ3. Заодно отметим четвертое отличие МТ3 от МТ4 – подробная справочная система на русском языке. Делаем последнюю замену – индикатор готов.

Запускаем для сравнения индикатор в обоих терминалах, в МТ3

и в МТ4

Небольшая погрешность объясняется разным способом построения цены. При этом можно заметить, что индикатор в МТ3 рассчитывается заметно медленнее и на более слабых компьютерах временами даже может совсем пропадать. На моем Celeron-2000 после первого расчета окно индикатора несколько раз очищалось и приходилось запускать индикатор заново. Мы сделали, фактически, подстрочный перевод, и теперь можем немного проанализировать код индикатора, чтобы его улучшить. Улучшение будет заключаться в оптимизации механизма расчета индикатора. Первый вопрос возникает с не нужным ,вроде бы, циклом, в котором происходит обнуление обоих индикаторных массивов.

К тому же мы не использовали функцию InditarorCounted(). Закомментируем цикл и введем ограничитель расчета.

Возникает два вопроса, первый – почему при первом расчете, когда counted_bars еще равен нулю, мы устанавливаем limit равным Bars-per? Смотрим ниже по коду и видим, что в расчетах используется вызов технического индикатора iCCI() с периодом per. Набрасываем его на график и видим, что рисоваться индикатор CCI(14) начинает только с 15-го бара с конца, а до этого момента его значения не определены.

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

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

Видим, что «плывут» все переменные. Теперь становится понятно для чего был необходим цикл, в котором обнулялись значения индикатора на каждом тике. Таким образом автор индикатора боролся с этим неприятным эффектом. Из-за этого очищающего цикла у меня пропадал индикатор в терминале МТ3, так как времени, отводимого индикатору в MQL-2 на каждый тик, хватало только на очистку двух буферов и не хватало на повторный полный расчет индикатора.

Нам необходимо зафиксировать (стабилизировать) переменные e1,e2… e6 и наши проблемы будут решены. Расчет каждой из этих переменных для текущего бара зависит от значения этой переменной на предыдущем баре. Это напоминает в какой-то степени алгоритм расчета экспоненциальной скользящей средней, и мы можем применить такой же способ для хранения этих переменных в индикаторных буферах. Для этого объявим e1Buffer[],e2Buffer[]…e6Buffer[].

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

Запускаем два варианта индикатора на графике, спустя два бара вешаем еще один, первый вариант (который «плывет») и видим, что второй вариант не меняет свои показания со временем, в отличие от первого.

Конечно, в какой-то степени рассматриваемый индикатор оказался простым, и нам повезло, что восьми индикаторных буферов оказалось достаточно для решения проблемы с данным индикатором, но на самом деле, если бы потребовалось не 8, а 9 и более индикаторных буферов (индикатор может иметь на данный момент не более восьми встроенных буферов) – то и в этом случае мы смогли бы решить эту проблему. Мы могли бы ввести дополнительные переменные, в которых хранили бы значения на предыдущем баре (что-то вроде prev_e1, prev_e2… prev_e6) или объявить просто массивы. Мне пока не приходилось встречаться с ситуациями, когда нельзя было бы написать индикатор в MQL-4. На этом перенос кода из MQL-2 в MQL-4 можно считать законченным, остальная доводка индикатора принципиально алгоритм уже не меняет. Сами индикаторы можно взять здесь . - Читать Закрыть 16
Знакомство с OrderSend().
Функция OrderSend() в MQL-4 является , пожалуй, самая распространенная, так именно с ее помощью выставляются отложенные ордера и открываются ордера по рынку. Рассмотрим применение этой функции на примере скрипта, который будет разворачивать открытую позицию по конкретному инструменту. Предположим, у нас имеются одна или несколько открытых позиций по EURUSD в одну сторону, в покупку. Происходит резкое движение цены вверх, цена достигает целевого уровня (по нашим предположениям), на котором необходимо фиксировать прибыль (закрывать позиции), и еще желательно открыть позицию в том же объеме в обратном направлении, на продажу. В условиях быстрого рынка, когда цена меняется достаточно быстро, проделать такую операцию вручную быстро не получится. Нам требуется:
- Закрыть последовательно одну или несколько ордеров в покупку.
- Открыть один ордер в продажу с таким же суммарным объемом.
Допустим, в данный момент Bid по EURUSD равен 1.2600. Если закроем каждый из двух ордеров независимо, то получим: buy 1.2000 закрываем по 1.2600 (+600 пунктов) и sell 1.2500 кроем по 1.2603 (Bid + спред 3 пункта) – итого имеем минус 103 пункта. В сумме прибыль 600-103=497 пунктов. Предполагается, что цена между двумя закрытиями не менялась. Если бы мы закрывали один ордер другим, то получили бы 500 пунктов (1.2500-1.2000). Если объемы встречных ордеров не совпадают, то полностью закрывается ордер с меньшим объемом и остается один ордер с объемом равным разнице между взаимозакрываемыми ордерами. То есть, в случае 0.2 buy и 0.1 sell , после частичного закрытия остался бы ордер 0.1 buy по цене покупки. Вот это свойство мы и используем в разворотном скрипте Revers.mq4. Мы проверим наличие ордеров, открытых только в одну сторону, подсчитаем суммарный объем открытой позиции по этому инструменту и откроем удвоенный объем в противоположную сторону. Таким образом, после работы скрипта у нас будут ордер(а) с объемом V1 и ордер в противоположную сторону с объемом V2=2*V1. Затем мы сможем вручную закрыть необходимые ордера и оставить только один ордер с объемом V2-V1=2*V1-V1=V1.
Построим скрипт по принципу преодоления препятствий. Если на каком-то этапе условия не складываются – скрипт досрочно прекращает работу. Первым делом(как обычно) встраиваем защиту от реала:

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

Нет ордеров ( OrdersTotal() равно нулю) – нет смысла продолжать работу, выход. Но даже, если ордера и есть, необходимо убедиться, что присутствуют ордера на нашем символе (график инструмента, на котором мы запускаем скрипт), и эти ордера являются открытыми, а не отложенными. Для этого пройдем в цикле по списку и посчитаем ордера по нашему инструменту. Заодно посчитаем и объемы ордеров в покупку и в продажу, по отдельности.

Видно, что если ордер открыт не по нашему символу (OrderSymbol()!=Symbol()), работа с ордером прекращается и происходит переход к следующему по списку ордеру (оператор continue;) . После прохода по списку ордеров, можно по очереди проверить отдельно наличие ордеров в покупку и ордеров в продажу, но можно проверить оба счетчика одной операцией – сложив счетчики. Если сумма равна нулю – значит открытых ордеров нет, есть только отложенные.

Такой вариант нам тоже не подходит, выходим. Следующая проверка – нам нужно, чтобы были только ордера в покупку, либо только в продажу. Мы хотим иметь однозначно определенную ситуацию. Я проверил это простым умножением счетчиков, если произведение равно нулю – значит имеем только ордера одного вида.

Если скрипт после всех этих проверок еще не завершил работу – значит наступило время открытия встречного ордера с удвоенным размером.

Получение удвоенного ордера сделано не напрямую, то есть, вместо того, чтобы написать reversLots=2*sellLots (для ордеров, открытых в покупку) , была использована сложная конструкция. Для чего это сделано? Размер позиции имеет тип double, и не всегда в компьютерных расчетах 2.0 умножит на 2.0 равно 4.0. Это связано с ошибками округления не целочисленных переменных в двоичной системе счисления. Хотя 2 умножить на 2 всегда равно 4. Поэтому, сначала мы умножаем buyLots на 10 (без точки) и удваиваем полученный целочисленный результат. Например, 0.2(лот)*10*2=4. Получили целочисленный результат четыре. Теперь нам необходимо получить размер reversLots(0.4) в 10 раз меньший с точностью до одного знака до запятой. Для этого мы делим intLots (целые лоты) типа int на 10.0 типа double (обратите внимание на точку) и нормализуем (усекаем) результат до точности в один знак после запятой. В итоге получаем значение 0.4 с необходимой точностью.
Если бы мы разделили 4 на 10 (4/10) , то, так как оба числа являются целыми(int), результат деления вычислялся бы по правилам целочисленной арифметики. Так как 4(целое) на 10 (целое) не делится, то ответ был бы ноль. Это одна из самых распространенных ошибок, нельзя забывать разницу между целочисленным делением и делением вещественных чисел в компьютерных вычислениях. Если вспомнить случай с Буратино, то его не сильно обманывали, когда говорили, что «5 на 3 не делится, вот тебе одна монета».
Итак, размер разворотного ордера вычислен, осталось только отправить торговую команду на сервер. Иногда цены двигаются настолько быстро, что скрипт выполнял вычисления, текущие цены уже совсем не такие как на момент запуска скрипта. Необходимо всегда иметь самые свежие цены, чтобы обезопасить себя от отказа сервера по причине устаревания цены. Для этой цели служит команда RefreshRates(), рекомендую почитать справку по ней. При выполнении этой команды не происходит обращения к торговому серверу брокера, поэтому ее можно использовать так часто, как вам это необходимо, не боясь нарваться на санкции брокера. Наконец, можно отправить торговый приказ OrderSend(). Хотя справка по этой функции содержит всю необходимую информацию, должен выделить две основные ошибки при ее использовании:- часто забывают указать символ, по которому открывается ордер. То есть, просто пропускают первый параметр этой функции (Symbol()), а ведь в MQL-4 можно открывать ордера по «чужому» инструменту (в тестере нельзя). Компилятор эту ошибку прощает и ничего не сообщает, потом пользователь удивляется тому, что ордера не открываются .
- при открытии ордера типа OP_BUY(покупка по рынку) подставляют в качестве цены открытия Bid(цена продажи), для продажи – наоборот. Естественно, ордер вряд ли будет открыт.
На самом деле количество ошибок, возвращаемых терминалом или торговым сервером весьма велико, эти ошибки помогают перенаправить алгоритм в нужное русло. Для того, чтобы узнать код возвращенной ошибки, существует функция GetLastError(). Необходимо опрашивать этой функцией код ошибки каждый раз, когда мы получили ошибочную ситуацию. Так как, в случае неудачи OrderSend() возвращает значение -1, то мы выводим эту ошибку в том случае, если ticket меньше нуля (можно было проверить и ticket==-1). Если ордер открылся нормально, сообщение об ошибке мы не увидим. Скрипт готов, я запускаю его на графике USDCHF, по которому был открыт ордер в покупку в размере 0.1 лота.

Видно, что скрипт открыл противоположный ордер в двойном размере.

В комментарии ордера указано «revers order» - наша метка. Теперь мы можем в любой момент кликнуть на один из этих ордеров, выбрать тип «Закрыть перекрытые ордера»,осталось только нажать желтую кнопку.

На этом первое знакомство с функцией OrderSend() для ордеров, открываемых по рынку, можно считать законченным. Скрипт по статье можно скачать здесь . - Читать Закрыть 17
Принцип работы советника.
У нас уже есть почти все необходимые знания для создания советника. Из торговых функций мы не затрагивали только OrderModify() и OrderCloseBy(). Но прежде чем приступать к написанию сложных советников, необходимо четко представлять себе как работает советник в режиме реального времени, будучи прикреплен к графику инструмента, и как моделируется его работа в тестере при бектесте на исторических данных.
Многие трейдеры пишут индикаторы и советники, не представляя разницы между теорией и практикой. Прогнав советник в тестере и получив заманчивые результаты, они вешают эти советники на онлайновый график, часто позже удивляются несовпадению результатов (иногда разительному) и значениям индикаторов, снятых в онлайне и при бектесте. Поэтому, мы для начала создадим советник, за который нам не придется краснеть, и который одинаково хорошо работает на любом инструменте и на любом тайм-фрейме. Этот советник не сливает, потому что он не будет торговать. Единственное его назначение — вести запись о состоянии счета, поэтому я его назвал BlackBox («Черный ящик»), по аналогии с аппаратурой для записи полета авиалайнера.
Трейдеры придумывают сложные алгоритмы для открытия позиций, еще более сложные алгоритмы для трейлинга(подтягивания) стоплоссов (Stop Loss), проверяют значения индикаторов на «родном» и «чужом» тайм-фрейме, считают количество прибыльных и убыточных сделок в последней серии, в общем, отслеживают самые разнообразные события, которые могут произойти с ценой и с торговым счетом. Но при этом иногда забывают про два самых важных события.
Первое событие — это приход тика, именно по приходу нового тика выполняется каждый раз функция start() советника и индикатора. Это событие очевидно. Второе событие — это закрытие старого бара и зарождение нового. Эти два события между собой не равнозначны с точки зрения написания советника. Очевидно, что значение индикатора меняется в незавершенном баре с каждым изменением цены, поэтому большинство стратегий используют значения с последнего завершенного бара, то есть с первого бара.
Необходимо определить момент, когда старый бар закрылся, и пришел первый тик нового (нулевого) бара. В этот момент мы производим оценку текущей ситуации согласно алгоритму советника и принимаем решение о проведении или непроведении торговых операций. Это событие (рождение нового бара) мы будем проверять пользовательской функцией isNewBar() булевого типа, она будет возвращать true, если зародился новый бар, и false в противном случае. Если новый бар не появился — проводятся рядовые действия, которые можно делать на каждом новом тике.
Создаем новый советник с помощью «Мастера создания Советника». Никаких параметров для своей работы он не требует, поэтому заполняем только имя советника.

В блоке start() пишем несколько строчек простого алгоритма:

Видно, что на каждом тике проверяется факт появления нового бара и в случае положительного значения вызывается функция EveryBar(). Эта функция вызывается только один раз в начале каждого бара (если правильно написана isNewBar()) и в течение всего развития бара больше не вызывается. Кроме того, на каждом тике вызывается функция EveryTick() — в ней могут выполняться любые действия.
Как нам определить появление нового бара? Существуют два стандартных способа.- Каждый бар имеет такую характеристику как Time[] — время отсчета(открытия) нового бара , и если мы будем каждый раз спрашивать значение Time[0] и сравнивать его с тем, что было на предыдущем тике, то в момент появления нового бара эти значения не совпадут.
- Второй вариант — запоминать каждый раз значение предопределенной переменной Bars. При появлении нового бара это значение также изменится.
Оба этих варианта равноправны, но я рассмотрю второй. Пишем функцию:

Обратите внимание, что переменная expertBars (в которой мы запоминаем значение Bars) является глобальной. Только переменные на глобальном уровне помнят свои значения между приходами каждого тика и запуска блока start() (из которого функция isNewBar() вызывается). Если выражение expertBars!=Bars истинно, значит родился новый бар, установим значение res равным true и не забудем запомнить значение Bars на данный момент — в следующий раз в будущем мы будем сравнивать Bars с текущим значением. Таким образом, возвращаемое функцией значение res будет либо true либо false.
Идем далее, сочиняем функцию EveryBar(), чтобы долго не думать, я решил каждый новый бар распечатывать в логе список ордеров, как в скрипте OrderList. mq4 (статья «Ордера в MetaTrader4» . ).

Для этого из функции EveryBar() вызывается новая функция — PrintAllOrders() (распечатать все ордера).

Осталось написать только функцию EveryTick(). Я решил в этой функции вычислять средний профит за все тики, пришедшие в течение бара. Для этого на каждом тике суммируется текущая прибыль по счету (AccountProfit()), подсчитывается количество тиков, и при появлении нового бара сумма профитов делится на количество поступивших тиков, получаем в результате усредненную прибыль за бар.

Немного изменим функцию EveryBar() для вычисления средней прибыли.

Теперь мы с появлением нового бара вычисляем среднюю прибыль и обнуляем переменные SumProfit и ticksBars для дальнейшего из использования с чистого листа на новом баре. Запускаем на графике EURUSD M15 и видим вывод в лог:

Теперь наш советник будет каждые 15 минут выводить данные об ордерах и среднюю прибыль за этот период.

Думаю, логика работы этого советника не вызывает вопросов. Взять код советника можно здесь . - Читать Закрыть 18
Тиковый индикатор в отдельном окне.
Думаю, большинство видело тиковый график в «Обзоре рынка» в терминале МТ4. Также он доступен при ручном выставлении ордеров.

Многие трейдеры по достоинству оценили появление этого вида графика в МТ4 (в МТ3 его не было). Но к хорошему привыкаешь быстро, и уже многим не хватает этого, было много просьб добавить такой вид графика в отдельном окне. Мы попробуем создать тиковый график самостоятельно. Размещать такой график в окне цен не очень удобно, так как будет не совпадать масштаб, поэтому мы его создадим в подокне основного графика. Чтобы добиться внешнего сходства, выберем тип отображения в виде линии. Запускаем «Мастера создания Советника», называем Ticks, внешних параметров нет, на втором шаге ставим чек-бокс «Индикатор в отдельном окне», добавляем индекс типа Line ( цвет я поставил Navy ) , жмем «Готово».

Наша задача крайне проста: приходит новый тик — вызывается функция start(), в этой функции в нулевом баре ставим значение Bid. Пришло 100 тиков — имеем 100 заполненных значений. Единственная проблема — просто так у нас это не получится. Если мы запускаем индикатор на 5 минутном тайм-фрейме, то новый бар у нас будет появляться только каждые 5 минут, а нам необходимо, чтобы на каждый тик индикатор сдвигался влево.

Если для стандартных индикаторов(как встроенных, так и пользовательских) терминал сам производит нужный сдвиг, то для тикового графика нам самим придется продумать механизм сдвига. По сути, мы имеем индикаторный массив ExtMapBuffer1[], значения которого нам необходимо сдвигать вглубь, значение ExtMapBuffer1[0] переносим в ExtMapBuffer1[1], ExtMapBuffer1[1] переносим в ExtMapBuffer1[2], и так далее, в освободившийся индекс ExtMapBuffer1[0] записываем значение Bid.

Таким образом, нужна некая функция ShiftArray() (сдвинуть массив), после которой пишем по нулевому адресу Bid.

Осталось написать функцию ShiftArray() :

Нам пришлось ввести новую глобальную переменную tickCounter, чтобы знать на какую глубину сдвигать значения массива. Индикатор практически готов. Но мы забыли, что при появлении нового бара индикаторный массив сдвигается автоматически. Добавим обработку события «Новый бар» и изменим код:

Функция isNewBar() аналогична той, что используется в советнике BlackBox, просто скопируем ее. Правда, вместо глобальной переменной expertBars я написал myBars.

Теперь индикатор безупречен. Правда, мы не использовали возможности оптимизации, но как можно оптимизировать этот индикатор? Это редкий случай, когда нет никакой пользы от встроенной функции IndicatorCounted() . Но оптимизировать можно и его. Предположим, мы запустили индикатор минут 10 назад, за это время набежало 100 тиков, значит, на каждом новом тике мы сдвигаем 100 значений массива, и при этом количество сдвигаемых значений растет. А спустя сутки этих значений будет тысячи. Помножим это на количество графиков, на которые мы набросим этот индикатор. Возможно, наступит момент, когда все ресурсы компьютера будут уходить на бессмысленный сдвиг десятков тысяч значений в разных массивах. Чтобы этого избежать, я ввел внешний параметр MaxDrawTicks=500 (максимальное число рисуемых тиков) и ввел правило: как только счетчик тиков tickCounter в два раз превысит это значение — происходит «обрезка» массива, значения от MaxDrawTicks+1 до 2*MaxDrawTicks заполняются нулями. Дальше как обычно. Я исходил из того, что пятисот значений на графике обычно достаточно для оценки текущей ситуации, вы можете изменить это число или изменить правило «обрезки».

Индикатор написан без ошибок, оптимизация проведена, что еще нужно для счастья? В терминале, если нажать Ctrl+Y , появляются разделители периодов. Почему бы и нам не сделать подобное. И тогда мы увидим, что графики имеют свое собственное время, которое не совпадает с астрономическим. И по законам внутреннего времени также развиваются периоды тренда, которые сменяются рейнджем или консолидацией. Да и понять по разделителям будет проще- как себя вела цена внутри того или иного бара, как менялись тики в модели флаг или ложный пробой.
Разделители рисовать мы сможем только с помощью объектов, это объект «Вертикальная линия». Теперь при наступлении события «Новый бар» мы рисуем вертикальную линию с уникальным именем, реализуем это функцией SetDelimeter() (установить разделитель). Блок start() окончательно выглядит так:

Функция SetDelimeter() будет создавать объект типа OBJ_VLINE, размещать его в окне индикатора, задавать цвет линии и стиль. Вы можете задать свои параметры цвета и стиля. Вопрос уникальности создаваемых имен решен просто — каждый новый разделитель имеет имя delimeterDate, которое содержит в себе текстовый формат начала нового бара. Таким образом, мы всегда можем подвести курсор к нашему разделителю, и понять — в какой период времени развивалась данная последовательность тиков.

Посмотрев справку в МЕ, вы поймете полностью все тонкости использованных функций для работы с объектом. Осталась последняя деталь — мы породили объекты , теперь нам нужно не забыть двигать их по той же причине, что и индикаторный массив. Немного расширим функцию ShiftArray(), теперь она не только сдвигает индикаторный массив, но и двигает все вертикальные линии, которые найдет на окне индикатора.

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

Я подвел курсор к разделителю и увидел имя вертикальной линии(которое нам дает Time[] этого отрезка времени ).

Видно, что на тиковом графике хорошо видны линии поддержки и сопротивления (1.2644).

Видим, как цена пробила линию поддержки и вновь вернулась вверх. Спустя 10 минут цена все-таки пробивает линию поддержки 1.2620 . Мы можем нанести горизонтальную линию на наш индикатор.
С помощью нашего индикатора мы можем изучить развитие цены в каждый момент времени, не находясь физически в этот момент перед монитором. Думаю, те, кто торгует внутри дня, по достоинству оценят этот индикатор. Скачать индикатор можно здесь . - Читать Закрыть 19
Замечательная функция
« - А где же моя взятка на козырного туза?!
В данный момент имеем:
- Расклад, батенька, расклад »
Из разбора партии в преферанс.- советник BlackBox, который умеет отслеживать два события;
- индикатор Ticks, который отражает тиковый график.
Год назад записать такие моменты можно было бы только находясь перед монитором, или создав сложную конструкцию из нескольких программ. Но даже и в этом случае успех был не гарантирован. Человек не может постоянно отслеживать несколько графиков одновременно, тем более 24 часа в сутки. А программы видео-грабберы могут работать только с одним монитором, а значит на каждый график потребовался бы один монитор.
Но теперь в МТ4 есть такая функция — ScreenShot(), которую я и назвал замечательной функцией.

Сколько раз любой трейдер открывал ордер с выставленным Stop Loss, отходил от терминала на какое-то время, а, вернувшись, обнаруживал, что цена коснулась уровня стопа и ушла в нужном направлении? Или наоборот, открытый ордер достигал какого-то уровня минимальной прибыли, после этого цена возвращалась к уровню открытия, и трейдер не мог решиться закрыть сделку. Чаще всего, в этом случае цена в дальнейшем развивалась в неблагоприятном направлении, но как отличать эти два момента? Или третий вариант — в онлайне торгует советник, алгоритм выходов которого хотелось бы улучшить после наблюдения сделок. В общем, во всех этих случаях хотелось бы сохранять видеоряд, галерею картинок, просматривать которые можно было бы попозже в режиме слайд-шоу.
Логично совместить работу советника BlackBox c записью таких картинок, да еще при этом отслеживать состояние нужных нам индикаторов. Сохраним советника с именем BlackBox-2 и начнем модификацию. В индикаторе Ticks был один недостаток — не отражалась линия Bid. Добавим ее с помощью нашего советника, заодно научимся создавать и перемещать горизонтальную линию, назовем ее bidLine (линия Bid? а). Естественно вставить этот код в функцию EveryTick():

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

Кроме того, ввели новые глобальные переменные: orderShotCounter будет уменьшаться после каждого вызова ScreenShot(), а снимки будут делаться каждый раз, как только tickRateCounter будет достигать значения 0 (то есть, каждые 10 тиков в данном случае). Итого, будет записано NumberShots (100) снимков на каждое событие.

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

Нам пришлось добавить три новые функции. Функция SymbolOrderTotals() простая, она подсчитывает количество открытых ордеров по нашему символу. Функции AddNewOrderLine() и DeleteClosedOrderLine() были добавлены для красоты, первая добавляет в окне индикатора Ticks горизонтальную линию по цене открытия нового ордера, а вторая эту линию удаляет.

Поиск нового ордера выполняется следующим образом: в цикле перебираем все ордера, для каждого ордера по нашему инструменту ищется горизонтальная линия с именем равным тикету ордера, если таковой линии не найдено — значит этот ордер и является нашим новым ордером. В этом случае создаем горизонтальную линию с именем lineName=DoubleToStr(OrderTicket(),0). Пришлось использовать функцию DoubleToStr() для преобразования номера тикета OrderTicket() в тип string.

Цвет линии зависит от типа ордера, ордера в покупку рисуются синим цветом, в продажу — красным цветом. Поиск последнего закрытого ордера производится в обратном порядке: на графике перебираются все горизонтальные линии, и для линий, имеющих описание «order» ищется ордер по тикету. Тут необходимо уточнить, что определить закрытый ордер можно только c помощью функции OrderCloseTime(), для открытых и отложенных ордеров она возвращает значение 0, то есть факта удаления отложенного ордера или закрытия рыночного ордера еще не было.

Фактически мы использовали набор горизонтальных линий как массив для запоминания списка открытых ордеров. У такого способа есть свой плюс: даже если терминал закрыть или сменить профиль — список открытых ордеров сохранится в виде набора объектов — горизонтальных линий.
Последний штрих — посчитаем количество ордеров по нашему инструменту и нанесем линии ордеров (если в этом есть необходимость) при первом запуске советника. Все лишние выводы в лог файл можете закомментировать сами, они сделаны для удобства отладки и пониманию программы. Логика советника не универсальна и он не сможет отслеживать сложные комбинации открытия/закрытия ордеров.

Для того, чтобы сделать снимки во время выхода данных по Non-farm Payrolls, я открыл ордер на продажу на демо-счете за несколько минут до выхода новости, советник обнаружил новый ордер и съемка началась. Серия из 100 снимков в формате 800в600 заняла немногим более 1Мб памяти, но во время сильных движений линия bidLine не успевает следовать за ценой, и терминал может зависать на несколько минут на не достаточно мощных компьютерах. Поэтому, сначала необходимо протестировать работу советника и индикатора в таких условиях, чтобы не возникло помех для торговли на реальном счету.
Код советника выложен здесь . - Читать Закрыть 21
Массивы и технические индикаторы на них
Есть две новости - плохая и хорошая. Плохая - рынок предсказать нельзя.
Хорошая - чтобы зарабатывать деньги, этого и не нужно
Брюс Бэбкок
Наверно, многие слышали эту поговорку в той или иной вариации. Есть противники и сторонники теории эффективного рынка. Одни считают, что цены случайны и заработать на рынках нельзя, другие – что цены обладают свойством прогнозируемости, и это позволяет извлекать из поведения рынка прибыль
В каждой шутке есть доля шутки. Но шутка продолжается. Периодически мы слышим, что можно зарабатывать на случайном блуждании цены, при этом нет необходимости в разработке торговой системы, а требуется только знание математики в пределах первых курсов ВУЗа. Выкладываются результаты работы торговых систем на случайных котировках, результаты тестирования подтверждают – на случайном рынке зарабатывать можно и случайным образом. Я создал таблицу в Excel, которая моделирует 100 сделок на ценах с приращением по закону нормального распределения, и построил 7 столбцов, каждый из которых показывает итоговую прибыль из 20 серий по 100 сделок от заданных StopLoss(SL) и TakeProfit(TP).

Видно, что прибыль при соотношении TP/SL=3 получилась максимальной (472), а при обратном отношении максимальным является убыток (-492). Вы можете провести свои серии испытаний, чтобы сравнить с тем, что получилось у меня. Для этого вы меняете настройки и 20 раз записываете значение «Итога» в нужные ячейки

Возможно, ваши результаты будут совсем не такими, как у меня, но если они окажутся близки хотя бы наполовину? Что делать в этом случае, сразу начинать торговать по принципу случайного открытия сделки с соотношением TP/SL=3 или сначала убедиться – являются ли цены рынка нормально распределенными?
Нормальные распределения считаются наиболее важными в методах математической статистики, рекомендую почитать книгу С.В. Булашева «Статистика для трейдера». Я попробую своими словами объяснить что такое нормальное распределение. Пусть у нас имеется два спортсмена-стрелка, каждый из которых производит по 100 выстрелов в мишень. Мы будем замерять на какое расстояние от центра («десятки») отклонилась пуля. Например, в сантиметрах. Подсчитав все 100 выстрелов, мы получим примерно такую таблицу.

Видно, что частота распределения попаданий имеет колоколообразную форму с пиком в районе 0. Для нормальных распределений центр совпадает с математическим ожиданием (арифметическое среднее). Кроме того, колокол может иметь пологие края или более крутые. Это характеризуется разбросом случайной величины вокруг среднего. Критерием этого разброса является стандартное отклонение (? - сигма), которое мы уже знаем как находить. Из двух стрелков в общем случае лучшим будет тот, у которого стандартное отклонение меньше.
Одним из важных свойств нормального распределения является то, что в интервале 3 сигмы от математического ожидания находятся 99,73% значений, в интервале +- 2 сигмы - 95,45 %, и в интервале +- сигма – 68.27 % . Мы попробуем изучить распределение цен закрытия для любого инструмента с помощью скрипта.
Скрипт будет заполнять массив CloseArray[] значениями Close[i]-Close[i+N], где N я назвал смещением. То есть, мы будем смотреть, как будет меняться распределение приращений при различных N, при N от 1 до 10. Каждое такое заполнение массива я назвал выборкой, каждая выборка будет иметь такие характеристики, как среднее значение (матожидание), стандартное отклонение(сигма), максимальное и минимальное значение, а также процент попадания в одну, две и три сигмы. Эти характеристики будут выводиться в файл *.csv, чтобы потом по ним можно было построить диаграммы распределения.
Для записи основных характеристик служит массив Stat[8]:

Таким образом, мы можем обработать значения Close[i]-Close[i+1] на тайм-фрейме D1. Получается, что для обработки Close[i]-Close[i+2] нам нужно либо создать новый массив Stat2[] либо очистить значения массива Stat[] и заново заполнить. Такой способ не является универсальным, и для таких случаев служат многомерные массивы. Мы лучше объявим массив Stat[10][8], где будем хранить для каждого смещения от 1 до 10 свои 8 характеристик.

Теперь мы имеем двухмерный массив. Но остался еще один момент – возможно, нам потребуется получить статистику не только для дневных баров (D1), но и для других тайм-фреймов. Добавим универсальности с помощью еще одного измерения :

Массивы в MQL-4 могут быть не более, чем 4-мерными, что хватает для большинства задач. Теперь мы можем в цикле проходить по 7 тайм-фреймам (от PERIOD_M1 до PERIOD_D1) и 10 смещениям. Так как индексы по первому измерению могут принимать значения от 0 до 6, то я создал функцию, которая возвращает по номеру тайм-фрейма значение тайм-фрейма в минутах, чтобы это можно было использовать в стандартных функциях.

Сами формулы предельно просты, но код получился немного длинноват.
ссылка по теме
http:www.statsoft.ru/home/portal/glossary/GlossaryTwo/N/NormalDistribution.htm
#property copyright "MetaQuotes"
#property link "http://www.alpari-idc.ru/ru/experts/articles/"
int FileHandle;
int init()
{
string FileName;
Print("Выполняем init()");
// сформируем имя файла, например ,
FileName=Symbol()+"CloseStat.csv";
//откроем файл с именем FileName (создадим указатель/handle на него)
FileHandle=FileOpen(FileName,FILE_WRITE | FILE_CSV,";");
if (FileHandle<1)
{
Print("Не удалось открыть файл, ошибка ",GetLastError());
return;
}
return(0);
}
int deinit()
{
Print("Выполняем deinit()");
//закроем файл (освободим указатель/handle, чтобы файл можно было
//открыть для редактирования другими программами)
if(FileHandle>0) FileClose(FileHandle);
return(0);
}
int start()
{
double CloseArray[];
double Stat[7,10,8]; // 7 тайм-фреймов, 10 смещений, 8 характеристик
// 0 - Число записей для периода и смещения
// 1 - Среднее в пунктах
// 2 - Max в пунктах
// 3 - Min в пунктах
// 4 - Станд. отклонение в пунктах
// 5 - процент попаданий в интервал +- sigma
6 - ------ +- 2 sigma
7 - ------ +- 3 sigma
string rangeDescription[14];
double range[13];
int freequency[14];
int curPeriod;
double Summ,Average,StDev,begin;
int MaxOnArray,MinOnArray;
int i_period,shift,m,z,t;
double sigma,sigma_2,sigma_3;
int BarsPeriod;
for (i_period=0;i_period<7;i_period++) // проход по таймфреймам
{
curPeriod=PeriodNumber(i_period);
BarsPeriod=iBars(NULL,curPeriod);
for (shift=1;shift<=10;shift++) // проход по смещениям
{
ArrayResize(CloseArray,BarsPeriod-shift);
ArraySetAsSeries(CloseArray,true);
MaxOnArray=-10000;
MinOnArray=10000;
Summ=0.0;
StDev=0.0;
for (m=0;m<BarsPeriod-shift;m++) // проход по барам
{
CloseArray[m]=(iClose(NULL,curPeriod,m)-iClose(NULL,curPeriod,m+shift))/Point;
if (CloseArray[m]>MaxOnArray) MaxOnArray=CloseArray[m];
if (CloseArray[m]<MinOnArray) MinOnArray=CloseArray[m];
Summ+=CloseArray[m];
StDev+=MathPow(CloseArray[m],2);
} // проход по барам
StDev=StDev/(BarsPeriod-shift); // среднее от квадаратов
Average=Summ/(BarsPeriod-shift);// простое среднее
StDev=MathPow(StDev-MathPow(Average,2),0.5); // стандартное отклонение sigma
Average=NormalizeDouble(Average,1);
StDev=NormalizeDouble(StDev,1);
MaxOnArray=NormalizeDouble(MaxOnArray,1);
MinOnArray=NormalizeDouble(MinOnArray,1);
Stat[i_period][shift-1][0]=BarsPeriod-shift;
Stat[i_period][shift-1][1]=Average;
Stat[i_period][shift-1][2]=MaxOnArray;
Stat[i_period][shift-1][3]=MinOnArray;
Stat[i_period][shift-1][4]=StDev;
begin=Average-3.0*StDev;
for(t=0;t<13;t++)
{
range[t]=begin;
begin+=0.5*StDev;
if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке
for (t=1;t<13;t++) range[t]");
}
//if (GetLastError()==4002) Print("Имеем выход за пределы массива
в блоке for (t=1;t<13;t++) range[t]");
rangeDescription[0]="меньше "+DoubleToStr(range[0],1);
for (t=1;t<13;t++)
{
rangeDescription[t]="от "+DoubleToStr(range[t-1],1)+" до
"+DoubleToStr(range[t],1);
}
rangeDescription[13]="больше "+DoubleToStr(range[12],1);
if (GetLastError()==4002) Print("Имеем выход за пределы массива в блоке
for (t=1;t<13;t++) rangeDescription[t]");
ArrayInitialize(freequency,0);
for(m=0;m<BarsPeriod-shift;m++)
{
if (CloseArray[m]<=range[0]) freequency[0]++;
for (z=0;z<12;z++)
{
if ((range[z]<CloseArray[m])&&(CloseArray[m]<=range[z+1]))
freequency[z+1]++;
}
if (range[12]<=CloseArray[m]) freequency[13]++;
if (GetLastError()==4002) Print("Имеем выход за пределы массива
в блоке for(m=0;m);
}
//Print("Тайм-фрейм ",curPeriod,"M cмещение ",shift,
//" Среднее=",Average,
//" СтОткл=",StDev," Max=",MaxOnArray," Min=",MinOnArray);
if (FileWrite(FileHandle,"Тайм-фрейм ",curPeriod,"M cмещение ",
shift," Среднее=",Average,
" СтОткл=",StDev," Max=",MaxOnArray," Min=",MinOnArray)<0)
Print("Ошибка записи в файл",GetLastError());
for(z=0;z<14;z++)
{
//Print("в диапазоне ",rangeDescription[z]," найдено ",
//freequency[z]," значений");
FileWrite(FileHandle,z,rangeDescription[z],freequency[z]);
if (GetLastError()==4002) Print("Имеем выход за пределы
массива в блоке for(z=0;z<14;z++)");
}
sigma=freequency[5]+freequency[6]+freequency[7]+freequency[8];
// число попаданий в интервал +-1 ст.отклонение
sigma_2=sigma+freequency[3]+freequency[4]+freequency[9]+freequency[10];
// -/- в интервал +-2 ст.отклонений
sigma_3=sigma_2+freequency[1]+freequency[2]+freequency[11]+freequency[12];
// -/- в интервал +-3 ст.отклонений
sigma=NormalizeDouble(100*sigma/(BarsPeriod-shift),1);
// процент попадания в интервал от -1 до +1 ст.откл.
sigma_2=NormalizeDouble(100*sigma_2/(BarsPeriod-shift),1);
// процент попадания в интервал от -2 до +2
sigma_3=NormalizeDouble(100*sigma_3/(BarsPeriod-shift),1);
// процент попадания в интервал от -3 до +3
Stat[i_period][shift-1][5]=sigma;
Stat[i_period][shift-1][6]=sigma_2;
Stat[i_period][shift-1][7]=sigma_3;
FileWrite(FileHandle,sigma,"% в диапазоне стандартного отклонения ");
FileWrite(FileHandle,sigma_2,"% в диапазоне 2-ух стандартных отклонений ");
FileWrite(FileHandle,sigma_3,"% в диапазоне 3-ех стандартных отклонений ");
FileWrite(FileHandle,"---");
}// проход по смещениям
}// проход по таймфреймам
//----
return(0);
}
int PeriodNumber(int number)
{
int per_min;
switch (number)
{
case 0: per_min=PERIOD_M1;break;
case 1: per_min=PERIOD_M5;break;
case 2: per_min=PERIOD_M15;break;
case 3: per_min=PERIOD_M30;break;
case 4: per_min=PERIOD_H1;break;
case 5: per_min=PERIOD_H4;break;
default: per_min=PERIOD_D1;break;
}
return(per_min);
}
Я выделил блоки для лучшего понимания алгоритма.


Обратите внимание на многочисленное использование конструкции
Дело в том, что при обработке массивов динамических размеров или не двух массивов с разными размерами немудрено сделать ошибку, которая заключается в выходе за пределы массива. Например, я объявил массив
double Array[100]
и попытался обратиться к элементу Array[101](или Array[100]). В этом случае будет сгенерирована ошибка с кодом 4002. Прекращение работы программы она не вызовет, но код этой ошибки будет храниться до тех пор, пока не произойдет вызов GetLastError() (тогда значение внутренней переменной, содержащей код ошибки станет равным нулю) или пока не произойдет другая ошибка. У меня в скрипте была именно эта ошибка, и чтобы точно найти то место, где она происходит, мне пришлось обложить этой ловушкой все циклы. Другого надежного способа локализовать место ошибки нет.
После запуска скрипта будет создан файл ИмяСимволаCloseDist.csv . Открываем его, выделяем необходимые данные по частотам распределения и жмем кнопку «Мастера диаграмм».

Видно, что проверка подтверждает правильность работы скрипта – сумма выделенных ячеек равна количеству значений при заданном тайм-фрейме и смещении.

Выбираем тип диаграммы и жмем «Далее».

Нас все устраивает, жмем опять «Далее».

Если есть желание – заполняем остальные поля, и опять жмем «Далее». Последнее диалоговое окно.
Жмем готово и размещаем окно диаграммы поудобнее.

Теперь мы получили график распределения приращения для EURUSD M15. Видно, что через 10 баров с вероятностью 79% цена окажется не далее 23 пункта(1 сигма ) от текущей, с вероятностью 94.4% не далее 45 пунктов(2 сигмы) от текущей, и с вероятностью 98.2% не далее 67 пунктов(3 сигмы). Сделать вывод о том – соответствует ли данный график графику нормального распределения – вы должны сделать сами.
Скрипт правильный, но мы можем его немного доработать, используя технические индикаторы на массивах. Назовем новый скрипт CloseDist2.mq4 . Кроме того я ввел новую функцию – ArrayCopySeries(). Обратите внимание, что уже не требуется задавать размер массива temp, в который якобы копируются цены закрытия.

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

Дело в том, что ошибка 4066 генерируется только один раз при обновлении истории, второй раз она может появиться только на другом тайм-фрейме или символе.
Сравнительный анализ двух немного разных скриптов, выполняющих одну и ту же задачу, поможет лучше разобраться в использованных функциях. Сами учебные скрипты, надеюсь, тоже пригодятся. Скрипты и файлы Microsoft Excel можно взять здесь .
- Читать Закрыть 22
Знакомство с тестером
… очевидный феномен, который я наблюдал много раз на протяжении многих лет, но для которого у меня нет
неопровержимых доказательств: большинство людей торгуют хуже, чем трейдер, полагающийся исключительно
на случайный выбор.
Уильям Экхардт (Джек Д. Швагер «Новые маги рынка»)
Мы знаем основные торговые функции(кроме OrderModify()), у нас имеются некоторые соображения о простейшей торговой системе на EURUSD, основанной на приращении цены в модели нормального процесса , осталось только проверить. Для этого мы напишем советника (Random Trade.mq4), который будет открывать сделки случайным образом в случайном направлении с жестко заданными параметрами StopLoss и TakeProfit.
Зададим внешний параметр int StDev, который будет означать подразумевать стандартное отклонение в пунктах. Наш StopLoss будет равен этому параметру с неким коэффициентом, который мы будем менять, назовем его SL_K. Соответственно, TakeProfit также будет вычислять как SL_K *StDev . Поэтому параметры SL_K и TP_K также будут внешними (изменяемыми на усмотрение пользователя).
Для моделирования случайной величины в MQL-4 служит функция MathRand(). Чтобы получить случайную величину в интервале от 0 до 1, нам придется разделить ее значение на 32767. Если результат больше 0.5 – мы покупаем по рыночным ценам, если меньше 0.5 – продаем. В принципе, советник уже готов.
Начинаем процесс создания советника с помощью «Мастера создания советника»

Пишем простую функцию GetOrder(), и советник готов. Обратите внимание, что уровни StopLoss(SL) и TakePofit(TP) для открываемого ордера предварительно нормализуется. Так как коэффициенты для вычисления этих уровней необязательно будут целыми, а значит и сами вычисляемые уровни также могут содержат количество знаков после запятой больше, чем Digits, то лучше нам самим округлить эти цены до нужного значения, иначе торговый сервер может просто отвергнуть приказ на открытие ордера. Нормализация всегда необходима, когда уровни цен вычисляются, и желательна, даже когда мы просто берем цены с графика. Также в ордере стоит ненулевой параметр Slippage , для тестирования он влияния не окажет, но при торговле в реальном времени лучше его задавать, чтобы на изменяющемся рынке при открытиях или закрытиях по рынку не получать отказ сервера из-за устаревания цены.

Ну и, конечно, никогда не забываем, что покупки совершаются по цене Ask, а продажи по цене Bid. В качестве комментария мы записываем случайное число Random, на основании которого было принято решение о направлении торговли. Отчасти это сделано для примера, отчасти для контроля правильности алгоритма. Компилируем советника, открываем в терминале панель тестера (Ctrl+R), выбираем нашего советника, нужный символ и нужный тайм-фрейм. Модель тестирования ставим «По ценам открытия(быстрый метод на сформировавшихся барах)». Может сложиться ситуация, что в разворачивающимся списке символов не окажется нужного нам. Тогда необходимо схватить мышкой нужный символ и бросить на панель тестера (модель “Drag & Drop”).

Правда, есть в нем некоторая искусственность, неправильность, если присмотреться. Если ордер открыт, то новый ордер не откроется, пока не произойдет закрытие с прибылью или с убытком, но как только ордер закрылся – тут же открывается новый случайный ордер. Жмем кнопку «Старт», после окончания тестирования открываем закладку «Результаты» и видим подтверждение:

В жизни обычно такого не происходит, хоть в ручной торговле, хоть при торговле советником. Всегда происходит какая-то задержка между закрытием одного и открытием другого ордера. Улучшим нашу модель случайной торговли – добавим запаздывание между этими двумя событиями. Если эта задержка будет жесткой, то мы не полностью реализуем случайную модель торговли, поэтому предлагаю сделать саму паузу между двумя ордерами также случайной. Представим, что при торговле на часовках, в среднем между закрытием одного ордера и открытием другого проходит не более 20 периодов.
Значит нам необходимо как то получать информацию о времени закрытия последнего ордера. Для этого введем две новые переменные: lastTicket (номер тикета последнего ордера) и lastCloseTime (время последнего закрытия). Чтобы не терять эти значения в перерывах между вызовами функции start(), объявим их на глобальном уровне. Также добавим внешний параметр periodTrade, его значение будет нам гарантировать максимальное время между двумя ордерами. Наш советник RandomTrade-2 выглядит теперь так:

В советнике специально добавлено множество закомментированных выводов в лог (Print()), при необходимости для лучшего понимания их можно раскомментировать. Теперь при удачном открытии ордера запоминается его тикет. Позже в функции GetOrder() промежуток времени между последним закрытием и текущим временем сравнивается с заданным периодом в барах periodTrade. Выражение (CurTime()-lastCloseTime)/( periodTrade*Period()*60.0) поначалу будет малым, но с каждым новым баром в тестере будет расти, и случайное число в интервале от 0 до 1 сначала будет иметь минимальную вероятность оказаться меньше этого выражения , но после periodTrade баров вероятность открытия нового ордера станет 100%.
Запускаем новый вариант советника, и видим, что ситуация исправилась. Теперь появилась пауза между сделками.

Но если мы изменим модель «По ценам открытия» на модель «Все тики» или «Контрольные точки», то картина изменится , вот «Результаты» по «Контрольным точкам»

и вот по «Всем тикам»

Вы можете провести тестирования на своем компьютере, количество и порядок сделок у вас будут отличаться от моих, но общий вывод будет такой же – чем более подробное моделирование мы проводим, тем больше получается сделок. Мы получили наглядное доказательство неправильного строительства этого простого советника – от изменения режима моделирования результаты теста очень сильно меняются. Если ваш советник, написанный по какой-то стратегии (а не как у меня на случайности) будет себя вести также – значит у вас имеется серьезный изъян в алгоритме советника. Чтобы получать результаты, не сильно зависящие от режима моделирования, вам необходимо:- закачать как можно более подробную историю за тестируемый период начиная с минуток, и
- в явном виде разделить события EveryBar и EveryTick, как в советнике BlackBox.mq4.
Тут можно возразить- «Конечно, количество сделок на разных моделях будет различаться, так как у нас случайные сделки, и повторятся они не обязаны». На самом деле у нашего советника есть последний недостаток – он не является на самом деле случайным, хотя мы и использовали функцию MathRand(). Каждый раз при каждом прогоне в тестере генерируется одна и та же последовательность псевдослучайных чисел, и поэтому на каждом режиме моделировании мы получаем одну и ту же последовательность сделок. Проверьте сами.
Чтобы получать новую последовательность случайных чисел, необходимо генератор случайных чисел инициализировать с помощью функции MathSrand(). Если мы используем MathSrand(1), то последовательность случайных чисел будет одна, если MathSrand(2) – то другая. Но ведь нам необходимо, чтобы при каждом новом тестировании получать новый ряд сделок, последовательности сделок должны быть разными. Самый простой выход – использовать конструкцию MathSrand(GetTickCount()). Поместим ее в блок init() и задача решена, так как функция GetTickCount() при каждом новом вызове будет возвращать новое значение. Окончательный вариант мы назовем RandomTrade-3.mq4.
Теперь мы можем провести оптимизацию нашего советника, то есть поиск наилучших параметров для получения прибыли, или другими словами – подтверждается ли гипотеза, что при соотношении TP/SL=3 мы не только получим прибыль, но и эта прибыль будет максимальна. Нажимаем кнопку «Свойства эксперта» , выбираем закладку «Входные параметры» и отмечаем как на рисунке.

Не забудем убедиться, что на закладке «Тестирование» чек-бокс «Генетические алгоритмы» не отмечен – он нам сейчас не нужен. Начальный депозит оставим как есть - $10000 , разрешены покупки и продажи (Позиции: Long&Short).

Отмечаем на панели тестера чек-бокс «Оптимизация», модель «По ценам открытия» и получаем что-то подобное.

Прогоняем еще раз – получаем новый график оптимизации результатов.

Хорошо видно, что на одних и тех же параметрах каждый раз получается новая серия случайных сделок, то есть мы добились все таки случайности. Но возникает новый вопрос – как же нам определить – являются ли какие-то параметры более оптимальными, чем другие. Оптимизация советника нам в данном случае не дает однозначного ответа, как быть? Если бы провели при каждом параметре по 100 прогонов советника в тестере, то мы бы смогли как-нибудь сравнить между собой результаты тестирования, полученные при разных параметрах. Мы бы могли усреднять прибыль за 100 прогонов, или количество сделок, или прибыльность.

Результаты оптимизации легко сортируются по возрастанию или убыванию при нажатии на имя столбца. Если мы два раза кликнем на строку с нужными нам параметрами советника, то попадем опять на закладку «Настройки», при этом эти параметры будут использоваться при тестировании советника.

Прогоняем советник с этими параметрами, получаем график.

Видим, что при тестировании кривая Баланса испытывала и подъемы и спады, но прибыль в $3963.66 не обнаружена. Количество сделок и прочие характеристики тестирования тоже не сходятся.

Мы стремились получить случайные сделки – мы их получили. Но наша гипотеза о возможности зарабатывания на случайных входах при определенном соотношении TP/SL так и не проверена. У нас есть последний способ это сделать - добавим пустую переменную (я ее назвал Balk), от которой ничего в алгоритме советника не зависит и сделаем оптимизацию. Эту новую внешнюю переменную я поместил самой первой в секции внешних параметров, чтобы прогон в оптимизаторе начинался с перебора ее значений. Назовем этот вариант советника RandomTrade-4, скомпилируем и зададим параметры оптимизации.

Параметр SL_K я исключил для ускорения процесса (все-таки потребуется 3000 проходов). При тестировании на часовках у меня это заняло 13 минут.

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

Задаем значения для оси X

и для Y
Таким образом, можно очень удобно визуально оценивать влияние параметров советника на результаты тестирования. На этом знакомство с тестером можно считать состоявшимся. Возможно, что результаты тестирования этого советника дадут вам новый взгляд на эпиграф к этой статье. Файлы экспертов находятся здесь . - Читать Закрыть 23
Своя статистика (команда #include)
Люди считают, что без особого труда можно делать кучу денег. Они считают, что вы можете делать 100% в год, ограничиваясь крошечными исследованиями по выходным. Это смешно.
Монро Траут. Глава «Лучшая прибыль при минимальном риске» (Джек Д. Швагер «Новые маги рынка»)
После прогона советника на исторических данных тестер в МТ4 позволяет сохранить отчет о результатах тестирования в формате html, с включением графика Баланса и Эквити, а также списком проведенных торговых операций. Список содержит дату и время открытия и закрытия каждой позиции, объем, тип (покупка или продажа) и результат (прибыль или убыток) каждой операции. Кроме того, в отчете приводятся разнообразные рассчитанные параметры для оценки результатов тестирования эксперта. Для лучшего понимания работы экспертов рекомендуется прочитать статьи на сайте разработчиков:- Особенности написания экспертов
- Strategy Tester: режимы моделирования при тестировании
- Особенности и ограничения тестирования в MetaTrader 4
- Что означают цифры в отчёте тестирования эксперта
Список торговых операций со вкладки «Результаты» можно скопировать в Microsoft Excel и провести дополнительный анализ(провести самостоятельный расчет своих показателей). Но даже и в этом случае у нас будет не полная статистика, к тому же невозможно предусмотреть в тестере варианты на все случаи жизни. Для примера, нам необходимо получать информацию о поле «Комментарий» в ордерах, время жизни позиции (между открытием и закрытием) в минутах.
Также выведем прибыль в пунктах и в валюте депозита из расчета на 0.1 лот (для устранения влияния Money Management). На всякий случай будем также выводить час и день недели открытия/закрытия ордеров. Дополнительно будем различать ордера закрытые по стопу, по тейк-профиту и по рынку. Как видите, список получился достаточно большой.
Где нам проводить обработку истории ордеров? Конечно же в той процедуре, которая выполняется самой последней, то есть в функции deinit(). После бектеста советника имеется история сделок, которую мы можем обработать в цикле. Используем для полигона наш советника RandomTrade4. Создадим два массива double trades[] и cancells[] (для отмененных ордеров), в которые будем записывать атрибуты каждого ордера. В массив string commentsTrades[] будем записывать строковые значения.

Я не стал заполнять массив отмененных ордеров, для них все аналогично:

Пояснения требуются только для использования функции StringSubstr(). Дело в том, что к комментарию ордера при закрытии по стопу тестер или торговый сервер добавляет «[sl]» , а при закрытии по тейк-профиту добавляется «[tp]». Если у вас был ордер с комментарием «проба», то при закрытии по стопу комментарий превратится в «проба[sl]». Таким образом в commentsTrades[cnt][0] записывается первоначальный комментарий, в индекс [1] пишем «[sl]», «[tp]» или «»(пустую строку), если ордер был закрыт по рынку. В индекс [2] пишем тип ордера – «buy» или «sell». Измененный эксперт назовем RandomTrade+Statistika.mq4. В массиве trades[] не обрабатываются индексы 9, 10 и 12. Они могут понадобиться нам в будущем.
Теперь осталось решить – что мы будем анализировать. Наш советник хоть и не несет особой смысловой нагрузки, но попробуем и из него выжать по максимуму. Обычно пишут советник по сигналам некоего индикатора и стараются его каким-либо образом улучшить (путем оптимизации) для увеличения прибыли при торговле на истории. Попробуем пойти обратным путем – через массив случайных сделок увидеть закономерность в показаниях индикатора. Возьмем классический индикатор MACD(12,26,9) и будем записывать в комментарий ордера единицу, если значение главной линии выше нуля, и минус один в противном случае.

Компилируем код советника, прогоняем на EIUUSD H1 с параметром StDev=50 и открываем в Excel csv-файл. Мы попробуем найти закономерность – связь между значением MACD и направлением позиции. После сортировки по типу «buy» и полю «Comment»=-1 видим, что эти сделки нам принесли прибыль в размере $1266.

Проверяем обратную ситуацию, ставим «Comment»=1 – получаем убыток $200.

Меняем тип ордера на «sell» и «Comment» на 1. Получаем опять убыток, но небольшой - $250.

Интересно, последний год с лишним (на этом интервале шло тестирование) идет падение EURUSD, а покупки при условии StopLoss=50 и TakeProfit=150 пунктов являются прибыльными. Может быть мы случайно выявили некую закономерность в поведении этой валютной пары? Чтобы проверить это, мы немного изменим алгоритм нашего советника. Время открытия позиций будет по-прежнему случайным, а направление открытия мы зададим жестко. Если MACD выше нуля – продаем, если ниже – покупаем. Вот такая простая контртрендовая система. Назовем этот вариант RandomTtade+Statistika2.mq4 .

Проверим наше предположение, сделаем оптимизацию, если она покажет, что случайные сделки при таких параметрах в среднем прибыльны – значит мы на правильном пути. Параметры оптимизации:- Balk – от 1 до 100 с шагом 1 (100 проходов для каждой комбинации значащих параметров)
- TP_K – от 1 до 3.
- StDev – от 10 до 100 с шагом 5.
Оптимизация заняла чуть меньше двух часов, «Результаты оптимизации» я скопировал и сохранил в файле Excel.

Но самое главное, на «Графике оптимизации» мы видим, что есть целая серия прибыльных сделок при StDev=65 и TP_K=2. Лучшие результаты получены не при StDev=50, но и не очень далеко от этого значения.

Самое интересное, что если TakeProfit больше стопа в 3 раза (TP_K=3) – прибыль становится меньше. Это еще одна неожиданность. Устанавливаем оптимальные параметры и прогоняем разок в бектесте. Смотрим график:

Делаем еще прогон, опять смотрим:

Еще разок, опять видим прибыль:

«Отчет» тестера для одного из прогонов в виде html-файла прилагается.

Неужели все так просто? Даже не верится. Внимательно просматриваем сделки и замечаем, что после закрытия одной сделки тут же открывается другая. Каким-то образом при таком изменении кода мы потеряли главное достоинство нашего эксперта – случайность сделок. Или нет? Сделаем простую проверку, сначала прогоним в тестере нашего эксперта с оптимальными параметрами в режиме LongOnly (Только покупки),

а затем в режиме ShortOnly (Только продажи):

Нас учили: от перестановки мест слагаемых сумма не меняется. А тут не только нет прибыли, но и количество сделок не совпадает – отдельные покупки и продажи дали примерно по 300 сделок, всего около 600, а в режиме Long&Short мы имели примерно 400 сделок. С большой долей уверенности мы можем констатировать, что была произведена скрытая подгонка советника. Чтобы это опровергнуть, вам как минимум необходимо изменить код таким образом, чтобы появилась случайная пауза между сделками, как максимум – научить эксперта различать направление уже открытой позиции.
Возможно, нам понадобится проводить такой же анализ для других советников. В этом случае мы можем в функцию deinit() нового эксперта вставлять кусок кода из этого советника. Но есть и другой путь – использование директивы #include. Директива #include(включить, добавить) имеет силу только при компиляции кода. Когда компилятор встречает эту команду, то он пытается найти указанный файл в папке "C:Program FilesMetaTrader 4expertsinclude" и вставить весь код из этого файла в код советника.
Покажем это на примере. Весь код в блоке deinit() сначала вынесем в отдельную функцию HistoryCalculate(). В блоке deinit() теперь присутствует только вызов этой функции:

Сохраняем это вариант под именем RandonTrade+Statistika3.mq4. Если компиляция проходит успешно – переходим к созданию заголовочного файла Statistika.mqh. Запускаем «Мастера по созданию советника», выбираем тип «Заголовочный файл»:

Задаем имя файла, имя автора и линк (ссылку):

Получили шаблон заголовочного файла, который содержит примеры вызова динамической библиотеки dll, скомпилированного файла mq4 – ex4 и просто определения константы(#define).

Копируем определение нашей функции HistoryCalculate(), при этом не забываем перенести объявления переменных StatBars и stat_FileHandle, а также имени файла статистики TradesFileName:

В нашем же советнике функцию HistoryCalculate() и переменные StatBars, stat_FileHandle, TradesFileName удаляем, добавляем строчку #include :

Сохраняем советника с именем RandonTrade+Statistika4.mq4 и заголовочный файл. Удобство этого подхода в том, что если вы отладили некий код, который хотели бы использовать многократно в своих разработках, то впоследствии легко добавляете его в свой новый код на стадии компиляции. Поэтому, если впоследствии вам будет необходимо изменить код заголовочного файла (*.mqh), то вы не должны забывать о необходимости перекомпиляции всех программ, использующих этот файл.
Напоследок вставим в файл Statistika.mqh определение ненужной функции Placebo(), вызывать которую мы не будем.

Сохраняем файл и компилируем заново наш советник. Получаем сообщение:

Компилятор определил, что эта функция не используется и удалил ее из исполняемого файла. Поэтому, один заголовочный файл может содержать большую библиотеку разнообразных функций на все случаи жизни, на размер конечного исполняемого файла это не влияет, будут использованы только необходимые функции. Файлы экспертов находятся здесь . - Читать Закрыть 24
Техника скользящего стопа
Первое правило бизнеса: защищайте свои инвестиции
Этикет банкира. 1775 год
До сих пор в советнике мы использовали только ордера с жестко установленным уровнем StopLoss и TakeProfit. Таким образом, открыв покупку, мы ждем срабатывания одного из двух уровней – либо мы получим прибыль, либо получим убыток. Третьего не дано. Но многие трейдеры считают более важным в торговой системе не вход в сделку, а систему выхода. Для более оптимального управления открытой позицией трейдеры используют скользящий стоп (Trailing Stop). Это позволяет передвигать уровень защитного стопа вслед за ценой, и в случае внезапного разворота забрать часть бумажной ( незафиксированной ) прибыли, вместо того, чтобы получить убыток по первоначальному стопу.
Прежде чем заняться написанием функции скользящего стопа, мы исправим все недостатки нашего советника торгующего по случайным сигналам. Для этого нам придется написать его фактически заново (Назовем его RTS.mq4 – Random Trade + Statistika).

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

Блок №1 реализован через проверку нового бара, если isNewBar() возвращает true (появился новый бар) , то сначала проверяется наличие сигнала на открытие позиции в покупку (SignalExist(OP_BUY)), и если такой сигнал есть, то делается попытка открыть ордер в покупку (GetOrder(OP_BUY)) . Аналогично делается проверка на открытие позиции в продажу. Теперь сигналы в покупку и продажу у нас разделены и не влияют друг на друга, мы избавились от недостатка предыдущей версии.
Обратите внимание ,что в качестве параметров я использовал зарезервированные слова из MQL-4 (OP_BUY и OP_SELL). Это облегчает понимание кода и вам самим, и тем, кто будет читать ваш код. Если бы я использовал вместо этого конкретные значения, то вам пришлось бы открывать справку, чтобы вспомнить, что означает параметр 0 при вызове функции GetOrder(0) .

Кроме того, в дальнейшем учет тонкостей открытия позиций OP_BUY и OP_SELL позволит нам отдельно оптимизировать советник в режимах LongOnly и ShortOnly.
Блок №2 состоит из простого вызова функции EveryTick(), которая также пока является пустой. В предыдущем советнике в функции deinit() мы использовали вызов функции HistoryCalculate(), я немного изменил вызов, чтобы расчет статистики производился только в тестере.

При работе в онлайне эта функция теперь не вызывается, но есть еще и оптимизация. При оптимизации она нам также не требуется. Добавим проверку IsOptimization().

Первой напишем функцию получения сигнала, для вычисления времени закрытия последнего времени добавили новую функцию – GetLastCloseTime().

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

Советник вроде полностью готов, но не хватает еще чуть-чуть. В предыдущей версии мы проверяли наличие открытых ордеров

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

Теперь мы можем снова проверить нашу гипотезу о том, что при параметрах StopLoss=65 пунктов и TakeProfit=130 пунктов нужно совершать сделки против показаний индикатора MACD. Сохраним эту версию под именем RTS2.mq4. Запустим вновь тестирование на EURUSD H1 и получим один из результатов.

Прибыли нет, соотношение между прибыльными и убыточными сделками отражает соотношение между SL и TP. Но количество сделок в покупку и продажу теперь не зависит от модели LongOnly и ShortOnly. Теперь мы можем заняться написанием функции TrailingStop().
Основная функция скользящего стопа – защита бумажной прибыли с помощью передвижения уровня StopLoss в направлении открытой позиции. Если мы передвигаем стоп слишком быстро, то случайное движение против нашей позиции может выбить нас из сделки, после чего цена может опять пойти в нашем направлении. А если стоп передвигать слишком медленно – то неблагоприятный разворот может съесть большую часть нашей прибыли. Как видите, от алгоритма зависит все. Поэтому, сначала выдвигаем некую идею, а потом пытаемся ее запрограммировать.
Существует множество алгоритмов трейлинга, я рассмотрю только два. Я их для себя назвал динамический и статический. Динамический трейлинг – это перемещение защитного стопа при каждом изменении цены, если цена прыгнула в нашем направлении на 100 пунктов (допустим вверх для покупки) , то и защитный стоп тут же будет подтянут (вверх) на те же 100 пунктов. Статический трейлинг – это перемещение стопа по некоторым условиям, независящим от текущей цены. Например, перемещение стопов на каждом новом баре по какому-то алгоритму. Это позволяет игнорировать дерганье цены внутри бара.
Сначала реализуем трейлинг стоп динамический.

В этой функции все просто и дополнительного описания не требуется. Если TS=50, то текущий стоп будет на уровне 1.2643.

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

Эту версию советника я сохранил под именем RTS+TS.mq4 (TS – Trailing Stop). Скомпилируем и прогоним на EURUSD H1.

Теперь мы можем рассмотреть вариант статического трейлинга.Введем понятие риска. Риск – это потенциальный убыток, который равен ходу цены от уровня открытия к уровню стопа. Это конечно некорректно, но не в этом суть. Идея будет такая: уровень риска должен уменьшаться пропорционально квадрату от времени. Если с момента открытия позиции прошло X времени и риск равен Y, то через X*2 времени риск должен уменьшиться в 4 раза. Грубо говоря, мы попытаемся реализовать скользящий стоп по аналогии с индикатором Parabolic.

Многие пытаются реализовать в советниках трейлинг на основе показаний индикатора Parabolic SAR. Но у этого способа есть один недостаток – индикатору все равно где вы вошли в позицию, фактически мы подстраиваемся под индикатор. Поэтому мы напишем алгоритм, который будет действительно менять уровень защитного стопа по принципу параболы и будет привязан к нашей точке входа.
Пусть в момент открытия ордера мы выставляем StopLoss на расстоянии S пунктов. При этом мы считаем, что если мы открыли сделку в правильном направлении, то через N баров мы уже будем в безубытке (уровень StopLoss можно будет передвинуть на уровень открытия ордера). Из школьной физики мы помним, что при движении по параболе расстояние S=(a*t^2)/2+V*t+C. Параметр а – ускорение, параметр V- начальная скорость, она у нас равна нулю, и параметр С – начальное смещение, оно равно значению стопа со знаком минус. Тогда S=(a*t^2)/2 или a=2*S/t^2 .

Если у нас первоначальный стоп равен 50 пунктов, а позиция должна выходить в безубыток через 5 баров, то a=(2*50)/5^2=100/25=4пункта/бар^2. Через 1 бар у нас стоп будет 4*1/2-50=-48, то есть на расстоянии 48 пунктов от цены открытия. Через 2 бара 4*2^2/2-50=-42 . Через 3 бара стоп 32 пункта, через 4 бара – 18 пунктов, через 5 баров: 4*5^2/2-50=0 – можем поставить ордер в безубыток , если к этому времени нас не закроет по стопу. Алгоритм есть, осталось написать код.

Функцию start() слегка меняем:

Компилируем советник RTS+TS2.mq4 и тестируем при тех же условиях и параметрах. Увы, этот вид трейлинг стопа не принес нам улучшения результатов. Возможно, это не та стратегия, где подходит параболический трейлинг. Мы можем еще немного изменить функцию EveryTick().

Тогда тестирование на других моделях («Все тики», например) изменит поведение нашего советника кардинально. Вы можете сами поэкспериментировать.Все варианты экспертов находятся здесь . - Читать Закрыть 25
Видит око, да код неймет
Готовая МТС, в виде программы на MQL-4 - это результат кропотливой работы. Сначала из наблюдений появляется некая идея, затем эта идея проверяется с помощью ручного тестирования на истории. Записываются дата и цена входа, причина выхода, подсчитываются прибыли и убытки. Если ручное тестирование подтвердило потенциальную прибыльность торгового подхода, наступает момент формализации алгоритма торговой системы.
Но не всегда получается формализовать сигналы входа, часто это очень сложная задача. Человек визуально может оценить текущую ситуацию в комплексе, а вот переложить правила на язык алгоритма затрудняется. Получается, что МТ4 тут бессилен? Частичный выход из этой ситуации есть. Если бы могли вручную подготовить сигналы для советника (вход по рынку, выставление отложенных ордеров и т.п.) , а с помощью тестера нашли бы оптимальные значения для защитного стопа, уровня Take Profit, перебрали разные варианты трейлинга…
Это неплохой вариант, но перспектива ручного набивания в файл даты, типа и направления сигнала может напрочь отбить охоту. Мы попробуем решить первую часть задачи (создание файла сигналов) с помощью нанесения на график стрелок разного вида и цвета. Потом все эти стрелки мы обработаем и запишем в файл их координаты. Открываем график, выбираем тип стрелки.

Наносим стрелку в нужное место и задаем ее цвет и записываем описание сигнала (sell limit).

Следующая стрелка должна иметь сигнал «buy limit» и быть синего цвета. А всего таких стрелок должно быть как минимум 50-60… Оптимизм немного угас. На каждую стрелку требуется 8-10 кликов мышки (посчитайте сами). Но у нас есть способ немного облегчить этот процесс, для этого мы используем скрипт с бесконечным циклом. Назовем его SmartArrow.mq4 (идея MetaQuotes). Создаем с помощью мастера:

Пока мы не дадим приказ о завершении работы скрипта, он так и будет в бесконечном цикле что-то делать (смотрите справку по IsStopped()). Этот скрипт ни на долю секунды не оставляет процессор в покое. Поэтому первым делом вставим в цикл функцию Sleep(1000), которая освобождает процессор хотя бы на 1 секунду.

Следующий наш шаг – научиться обрабатывать какое-то событие. Мы не можем скриптом выбрать в меню тип стрелки и «прилепить» ее к курсору, но мы можем отслеживать перемещение графических объектов на графике. Вставляем функцию ArrowPosChanged().

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

Факт изменения координаты можно выяснить сравнением значения координаты в текущий момент времени и в какой-то предыдущий. Поэтому введем глобальные переменные curPrice, prevPrice, сurrTime и prevTime. Получился такой код:

Мы добавили проверку, если стрелки с именем «SmartArrow» нет на графике – то скрипт завершает свою работу. Проверка первого вызова функции не влияет на работу скрипта, делает алгоритм более правильным – нет ложного возврата значения true при первом вызове функции ArrowPosChanged(). Запускаем для проверки скрипт:

Теперь дадим скрипту для работы стрелку с именем «SmartArrow», для этого просто переименуем любую стрелку на графике.

Запускаем скрипт и получаем сообщение (я добавил вывод Comment()).

Сохраняем это вариант скрипта и продолжаем дорабатывать. Первым делом при запуске скрипта создадим нашу стрелку «SmartArrow» автоматически и зададим для нее все атрибуты. Для этого добавим функцию init().

Мы пишем сигналы для советника, тестирование советника начинается не ранее сотового бара с конца, поэтому мы и создали нашу стрелку почти в самом конце графика. Удаляем наш скрипт (вы не забыли, он все еще крутится на графике?), компилируем SmartArrow2.mq4 и запускаем на графике

Запуск проходит удачно, закладка «Эксперты» подтверждает, срабатывает блок проверки первого вызова функции.

Проматываем график в самый конец , ищем зеленый кружок и не находим… Что не так, почему не работает? С помощью горячих клавиш Ctrl+B просматриваем список объектов, находим нашу стрелку – она там же, где и была!

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

Компилируем, запускаем и смотрим закладку «Эксперты»: ошибки нет. Ошибка в нашем алгоритме, ситуацию, когда стрелки нет на графике при запуске скрипта мы обрабатываем, а наличие этой стрелки дает сбой. Изменим еще раз код init():

Теперь код работает и мы находим зеленую стрелку там, где ей и положено быть. Осталось только написать обработку события «Перемещение стрелки». Условимся так: если стрелка оказывается ниже Low бара , то превращаем ее в стрелку «Вверх»(код 241) синего цвета, если выше High бара, то будет стрелка «Вниз» (код 242) красного цвета (мы отмечаем поступление сигналов limit). Вы можете выбрать любые другие коды для стрелок, в справке МЕ они показаны:

Если стрелка находится между High и Low – просто ругаемся на неумелое манипулирование стрелкой. При этом не забываем выводить в комментарии координаты стрелки в терминах время/цена.

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

Смотрим в справке «Коды ошибок» и узнаем, что «Объект не существует». То есть, для программы объект недоступен, так как он занят пользователем, и поэтому возвращается такая ошибка. Причина понятна, теперь нужно придумать как разрешить такую исключительную ситуацию. Можно было бы в случае возврата ошибки 4202 запускать цикл (бесконечный или с конечным числом попыток) установки нужного свойства, но всегда можно найти более простое решение. Посмотреть его можно в SmartArrow3.mq4.
Наконец мы победили визуальные ошибки, теперь нам получить файл сигналов. Наиболее напрашивающимся решением будет использование массивов. Каждый раз, когда определяется изменение координат стрелки, меняются свойства стрелки. В этот момент мы могли бы записывать в массив эти характеристики: время, цена и тип/код стрелки (вверх вниз). Объявим массив Arrows[1000][3]. Тысячи стрелок нам должно хватить. Какого типа должен быть массив? Время и код стрелки имеют тип int, а цена имеет тип double. Рассмотрим оба варианта.

Вариант с типом double прост:

Вариант с целочисленным массивом лишь немного сложнее. Нам нужно цену с Digits знаками после запятой привести к целому числу. Тут возможны два варианта:

Наконец, мы прошли по всем точкам/сигналам. Теперь нам нужно закончить работу скрипта и расставить все запомненные стрелки. Это наиболее удобно сделать в deinit().

После удаления скрипта на графике будут расставлены все наши стрелки. Первую часть – расстановка стрелок на графике – мы выполнили. Окончательный вариант сохранен как SmartArrow5.mq4. Все варианты скриптов находятся здесь - Читать Закрыть 26
Чтение из файла
Ограничивайте убытки и дайте прибыли расти
Почему мы сразу в нашем скрипте не создали файл сигналов (при его завершении в deinit())? На это есть две причины:- наши сигналы обнаруживаются только по факту перемещения стрелки SmartArrow на графике, а так как сигналы могут быть достаточно редкие, то при прокрутке графика мы не всегда сможем «перепрыгнуть» с одного сигнала на другой. Поэтому нам приходится применять технику тройного прыжка в легкой атлетике – делать промежуточную остановку (установку лишнего сигнала). Если бы такая функция была бы встроена в терминал МТ4, то значки мы могли бы ставить одним кликом на графике.
- если сигналов для тестирования необходимо нанести слишком много, то, вероятно, будут паузы между сеансами работы со скриптом. Мы можем делать несколько файлов в формате *.csv и сшивать их с помощью Excel.
Создадим отдельный скрипт для записи сигналов в файл. Поправляем положение стрелок на графике, удаляем ненужные (вынужденные лишние сигналы) и запускаем скрипт ArrowsToFile.mq4. Для его написания я скопировал готовые куски кода из WriteFile.mq4 , изменил только немного init().

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

Для проверки скрипта добавим на график трендовую линию с именем SmartArrow555.

Запускаем скрипт на графике, мои 12 стрелок были обработаны.

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

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

Сохраненный шаблон найдем в одной из папок МТ4:

Передаем этот файл другому человеку, он помещает в такую же папку на своем компьютере, открывает шаблон (signals.tpl) и видит в своем терминале полную копию вашего графика. На этом мы закончили с записью сигналов в файл, и нам предстоит научиться считывать эти данные из нашего файла.
Если есть операция записи в файл, то должна быть и обратная – чтение из файла. С массивами все понятно, мы можем всегда получить размер массива и организовать цикл for() для получения значений элементов массива по индексу элемента. Когда же мы открываем файл, мы обычно не знаем заранее сколько строк или значений в нем записано. Поэтому необходим какой-то стоп-сигнал, чтобы прервать цикл операций чтения из файла. Смотрим справочную систему и видим:

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

Если бы это был индикатор или советник, то этот код привел бы к зависанию терминала! Важно: бесконечный цикл разрешается только в скриптах. Мы получили бесконечный цикл, так как не можем достигнуть конца файла. Необходима операция «прокрутки» файла, функция чтения из текстового файла FileReadString() как раз должна это обеспечить. Я не знал, как она работает, и решил проверить методом проб и ошибок.

Файл сигналов у нас маленький, поэтому через закладку «Эксперт» мы все должны увидеть. В Excel я насчитал 13 строк и 52 значения (13*4). Запускаю скрипт на любом графике и вижу лог:

Видим, что ожидаемые значения отличаются от напечатанных на единицу. Значит, скрипт делает один холостой ход, прежде чем определяет конец строки и конец файла. Это знание поможет нам для заполнения массива сигналов в советнике. Сохраним скрипт под именем ReadFile.mq4.
Советник для тестирования ручных сигналов мы писать не будем, возьмем RTS+TS3.mg4, назовем его CutTheLosses.mq4 и переопределим функцию init(). Функцию SignalExist() пока сделаем пустой.

Если мы знаем структуру текстового файла, то правильно прочитать его не сложно. Теперь мы имеем массив сигналов и можем приступить к тестированию советника. Для этого нам нужно научить функцию SignalExist() извлекать из Signals[] нужные сигналы. Используем простой алгоритм – в каждый момент времени пробегаем по всему массиву и сравниваем текущее время Time[0] со временем в Signals[i][0], если значения совпадают – проверяем тип сигнала и по нему выдаем ответ.

Функцию GetOrder() переделаем незначительно: удалим случайную составляющую, запись в комментарий ордера оставим прежней – вдруг пригодится в будущем.

Обратите внимание, в конструкцию else мы внесли дополнительную проверку на тип ордера. Если когда-нибудь мы передадим в эту функцию в качестве параметра OP_BUYLIMIT или OP_SELL_STOP, то это предотвратит нас в будущем от ошибочного открытия ордера в продажу (OP_SELL).
Советник готов, но если его запустить в тестере на EURUSD H1 (откуда мы брали сигналы), то ничего не выйдет. Смотрим закладку «Журнал» в тестере и видим:

Код ошибки (4103) подтверждает невозможность открытия файла. Дело в том, что все файловые операции в тестере производятся в своей собственной папке, поэтому скопируем наш файл в папку /tester/files .

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

Теперь даже человек, который не написал и строчки кода (но прочитал данную статью), может создать свой файл сигналов, взять советник подобного класса, и, перебрав различные системы выхода (или трейлинга), проверить свою новую систему входов. А если система входов с трудом поддается формализации, то, наверно, это будет единственный путь (и на мой взгляд, не самый худший).
Все использованные файлы доступны здесь - Читать Закрыть 27
Глобальные переменные
Наш последний советник обладает только одним недостатком. В блоке init() имеется процедура открытия файла и чтения данных из него. При однократном прогоне в тестере это не страшно, но если мы захотим провести оптимизацию, то возникают две проблемы:- падает скорость оптимизации, так как операции чтения из файлов на порядок (несколько порядков) медленнее, чем чтение из оперативной памяти;
- многократные операции открытия/закрытия файлов нежелательны для жесткого диска.
Для этих случаев существуют специальные переменные – Global Variables. В отличие от глобальных переменных на уровне одного кода/программы, эти переменные являются глобальными на уровне терминала. Таким образом, мы можем создать некую глобальную переменную в скрипте и задать ее значение (GlobalVariableSet()), а потом в индикаторе или советнике проверить это значение (GlobalVariableGet()).
В отличие от остальных видов переменных, эти глобальные переменные сохраняются и при закрытии терминала(в течение 4 недель). Предположим, наш советник должен открывать только одну позицию в день, и если факт открытия мы будем хранить в обычной переменной, то при закрытии терминала или внезапной пропаже питания значение этой переменной (назовем ее TradeOpen=true) не сохранится. И тогда при новом запуске терминала переменная TradeOpen примет значение по умолчанию (false). И советник вновь автоматически откроет новую позицию, что запрещено алгоритмом. Применение по настоящему глобальной переменной решает эту задачу.
Для освоения методики применения глобальных переменных сначала создадим скрипт. Этот скрипт будет создавать глобальный массив значение. Важно: глобальные переменные могут иметь только тип double. Не существует возможности создать массив глобальных переменных, но мы можем легко обойти это ограничение. Функции проверки и задания значений глобальных переменных оперируют именем этой переменной, заданной в текстовом формате. Поэтому, значение для массива по индексу мы можем задать как Имя_массива+индекс.
Назовем скрипт GlobalArray.mq4. Вместо глобального массива мы создадим массив глобальных переменных:

Код демонстрирует типичную работу с глобальными переменными – сначала проверяем наличие нужной переменной (GlobalVariableCheck(Имя_переменной)), а потом задаем ее значение. Запускаем скрипт на любом графике, жмем F3 и видим окно со списком глобальных переменных.

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

Берем советник из предыдущей статьи, сохраняем его под именем CTL+GV.mq4(Global Variables). Делаем минимальные изменения в функции init(). Первым делом проверяем наличие глобальной переменной с именем FileOpened (файл уже открывали) , и если этой переменной еще нет – открываем файл, создаем множество глобальных переменных, содержащих сигналы на открытие позиций.

Также меняем немного функцию определения сигнала:

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

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

Важно: глобальные переменные являются едиными и для терминала и для тестера. Поэтому, если вам необходимо в тестере оптимизировать значения глобальных переменных, и в то же время эти глобальные переменные используются на онлайновых графиках, то лучше поставить две копии МТ4. В одной проводить тестирование, а другая будет работать с вашим демо или реальным счетом.
Вариантов использования глобальных переменных множество, вот некоторые из них:- а) запомнить время последнего сигнала (на открытие позиции, значение индикатора);
- б) запомнить текущее число подряд идущих прибыльных сделок;
- в) передать из одного советника в другой какие-либо параметры (например, вероятность пробития важного уровня на другом инструменте);
- г) оптимальные параметры последней оптимизации в тестере и так далее.
Каждый сам может в какой-то момент понять необходимость их использования для повышения устойчивости работы МТС. Файлы доступны на форуме
- Читать Закрыть 28
Поиск экстремумов цены
Одни трейдеры в своей торговле опираются на показания индикаторов, другие отвергают индикаторы и говорят, что лучший индикатор – это цена. Но и те и другие считают важным использование в своей торговле разнообразных уровней. Одним из самых распространенных методов является выявление максимальной и минимальной цены на определенном временном интервале. В MQL-IV для этих целей есть функции Highest() и Lowest().
Предположим, что 20 октября 2006 года нам необходимо знать максимальную цену за 26 последних часов. Для этого я нанес на график фигуру Прямоугольник, чтобы визуально видеть нужный мне диапазон свечей.

Рис. 1
Для того, чтобы нанести объект «Прямоугольник» на график, я сделал следующее: выбрал Меню «Вставка» - «Фигура» - «Прямоугольник».

Рис. 2
Левой кнопкой мыши отметил верхний левый угол Прямоугольника, и не отпуская кнопки довел до правого нижнего угла желаемого Прямоугольника. Кроме того, я через свойства объекта задал (правой кнопкой мышки на выделенном объекте) установил цвет Прямоугольника.

Рис. 3
Чтобы получить точную копию моего Прямоугольника, вы можете задать его координаты:

Рис. 4
Много полезных сведений о возможностях терминала вы можете почерпнуть из статей на сайте разработчиков:
Секреты клиентского терминала MetaTrader 4
Секреты клиентского терминала MetaTrader 4: Система оповещений
Секреты клиентского терминала MetaTrader 4: Индикаторы
Секреты клиентского терминала MetaTrader 4: Библиотека файлов в MetaEditor'еИз рисунка видно, что в Прямоугольник не попала часовая свеча со временем открытия 20-00 19 октября 2006 года. Это я сделал специально, чтобы разобраться с синтаксисом функции Highest(). Справочная система MetaEditor сообщает

Рис. 5
Функция соответствует требованиям для практически всех функций в MQL-IV: сначала идет символ, для которого применяется функция, затем период (тайм-фрейм). То есть, эта функция позволяет вычислить значение на «неродном» символе и «неродном тайм-фрейме» - привычные уже для МТ4 универсальность и удобство. Дальше – указатель таймсерии, ведь мы можем искать не только самый высокий High[], но и самый высокий Close[], или даже самый высокий Low[]. Следующий параметр – count – указывает на какой выборке ищется значение. Последний параметр start – с какого бара мы смотрим искомую выборку.
Попробуем написать скрипт, который нарисует нам такой же объект «Прямоугольник» на часовом графике EURUSD. Прямоугольник охватывает 26 баров, его вверх будет проходить по максимальной цене за 26 баров, низ прямоугольника будет равен минимальной цене за 26 баров, правый край находится на нулевом баре(индекс бара равен нулю), левый край находится на 26 баре с индексом равным 25 (не забываем про это!). Через функцию Highest() мы найдем индекс того бара, который имеет максимальную цену High[], для этого мы напишем Highest(Symbol(),Period(),MODE_HIGH, 26,0). Мы указали на поиск бара с самым высоким High[] на протяжении 26 баров начиная с нулевого. Или надо было написать 25?
Сделаем скрипт TestHighestLowest.mq4 и посмотрим. Сначала мы вручную добавим нужные внешние параметры.

Рис. 6
Вы видите, что я ввел новую переменную типа color, это позволит нам в будущем указывать цвет прямоугольника, а также переменную backGround булевого типа, ее мы будем использовать при задании желаемого типа отображения – будет прямоугольник иметь заливку или нет. Значение firstBar подставляется как параметр start для функции Highest() и Lowest().
Дописываем функцию start():

Рис. 7
Необходимо разобраться, почему именно так вычисляется время первой координаты time1. А также почему время открытия firstBar оказалось временем второй координаты при создании объекта прямоугольник (OBJ_RECTANGLE).
Кроме того, мы не просто пытаемся создать объект прямоугольник, но и анализируем результат этой попытки. Часто пишут однозначный код, который не предусматривает непредвиденного поведения программы, если это станет привычкой, то в будущем гарантирует многие часы в поисках ошибок в своих программах. Обработка непредвиденной ситуации (в нашем случае неудачной попытки создания прямоугольника) называется обработкой исключения, сами ситуации – исключениями. В данном примере мы просто выводим в лог сообщение с кодом ошибки.
Код готов, запускаем его на нашем графике и немного меняем входной параметр:

Рис. 8
Получаем следующий результат:

Рис. 9
Видим, что наш скрипт действительно показывает максимальную и минимальную цену за 26 последних баров начиная от нулевого. Жмем Ctrl+B, выбираем наш прямоугольник и смотрим его свойства:

Рис. 10
Я специально для примера выбирал пограничный случай, попробуем теперь исполнить скрипт со значением countBars=27. Запускаем, но ничего не происходит:

Рис. 11
Вот и обработка ошибки пригодилась, значение кода ошибки можно посмотреть в справке MetaEditor’а. Удалим прямоугольник, созданный при первом запуске скрипта и заново запустим скрипт:

Рис. 12
Видим, что максимум цены за 27 баров увеличился, значит мы правильно поняли назначение параметров. Необходимо только иметь ввиду, что если в искомой выборке окажутся два бара с одинаковыми экстремумами, функции Highest() и Lowest() вернут указание на тот бар, который имеет больший индекс, то есть укажут на более старый бар. В некоторых алгоритмах это может иметь большое значение.
У нашего скрипта есть одно неудобство – нам нужно указывать начальный бар (firstBar) числовым значением. Если нам нужно будет нарисовать такой прямоугольник где-то в глубине истории, то выяснение индекса нужного бара станет практически невозможным или придется придумывать средства для выяснения индекса этого бара. Существует целый класс задач, когда скрипту нужно передать индекс необходимого бара программным путем, для этого в MQL-IV есть специальные функции PriceOnDropped() и TimeOnDropped():

Рис. 13
Введем функцию init() и добавим переменные прямо из примера, а также добавим вывод многострочного комментария:

Рис. 14
Комбинация "косая черта и n" означает перевод строки, и ее наличие позволяет делать многострочные комментарии. Компилируем новый вариант скрипта и бросаем его на график (все остальные объекты я удалил):

Рис. 15
Если мы просто выполним скрипт (не бросая на график, а двойным кликом мышки как обычно), то получим другую картинку:

Рис. 16
Видно, что мы можем определить и способ запуска скрипта и координаты точки на графике, куда он был брошен. Используем новые знания и назовем новый вариант скрипта как TestHighestLowest2.mq4.

Рис. 17
Запускаем новый вариант скрипта и получаем желаемый результат:

Рис. 18
На этом знакомство с четырьмя новыми функциями можно считать законченным. Скрипты доступны на форуме
- Читать Закрыть 29
Некоторые способы постройки каналов
Многие трейдеры используют значение максимумов и минимумов за определенный период времени для создания торговой системы. Из известных - это торговая система Черепашек и каналы Дончиана. Мы рассмотрим алгоритм создания таких каналов с использованием функции iHighest() и iLowest() . Общепринятое название такого канала PriceChannel (ценовой канал), так и назовем индикатор. Наш индикатор будет показывать текущие уровни максимума и минимума за период N баров в виде линий. Вызываем «Мастер создания советников» и заполняем нужные поля.

Рис. 1
В данном случае я выбрал значение 20 – именно период в двадцать дней использовался в системе Черепах для торговли на прорывах.

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

Рис. 3
Я немного дописал код для лучшего понимания и пользования. Осталось только написать код заполнения буферов значениями.

Рис. 4
Функции iHighest() и iLowest() заменили устаревшие функции Highest() и Lowest(). Код получился очень простой, компилируем и прикрепляем к графику.

Рис. 5
Через окно DataWindow мы можем посмотреть значения любого буфера индикатора на нужную дату. Единственный недостаток данного индикатора (на мой взгляд) – отсутствие визуального подтверждения прорыва 20-дневного диапазона. Чтобы исправить этот недостаток, мы немного изменим код индикатора и назовем его PriceChannel-2.mq4.

Рис. 6
Новая версия индикатора выглядит более наглядно.

Рис. 7
Разница между двумя версиями видна при их сравнении.

Рис. 8
Есть множество стратегий, которые основываются на пробитии некоторого диапазона.

Рис. 9
Причем, как трендовые, так и контртрендовые, основанные на ложных пробитиях. Недостатком данного индикатора является фиксированный период, за который ищутся экстремумы. Существуют разные способы приспособить индикаторы данного типа под текущую рыночную ситуацию. Было бы интересно, если бы в фазе флэта (консолидации) период нашего индикатора увеличивался, а при развитии тренда – уменьшался. Таким образом, мы были бы немного застрахованы от ложного прорыва флэта и преждевременного выхода из позиции по тренду.
Одним из таких способов является использование индикатора ADX(). Популярным значением периода для этого индикатора является значение в 14 дней. Если присмотреться к этому индикатору, то видно, что во флете его значения очень малы, а при тренде нередко превышают значения 40.

Рис. 10
На одном из форумов я услышал про использование значения 150/ADX(14) в качестве переменного периода для индикатора PriceChannel. Изменим код нашего индикатора и назовем его ADXChannell.mq4.

Рис. 11
Характер ценового канала изменился.

Рис. 12
Можно придумать правила открытия позиций по тренду при касании ценой противоположной стороны канала.
Пока мы рассматривали только индикаторы, рисующие две линии – границы канала. Но иногда трейдеры добавляют третью линию, которая находится точно посередине между верхней и нижней границей канала. Назовем ее ватерлинией или нулевой линией (или линией баланса). Сделаем третью версию индикатора – PriceChannel3.mq4. Для этого достаточно добавить третий буфер и код для расчета его значений.
Рис. 13
Набросим новый индикатор на график.

Рис. 14
Тут можно спорить – получили ли мы дополнительную информацию от добавления средней линии или нет. Но я хочу обратить внимание на другое – в данном случае мы вычислили среднюю линию канала на основании его границ. Но есть индикаторы, которые строятся наоборот – берется некая линия (это может быть некая скользящая средняя) и от нее строятся границы канала по каким-то правилам.
Кроме обычных средних, в качестве средней линии могут быть использованы сложные алгоритмы, например адаптивная скользящая средняя Кауфмана - АМА ( Adaptive Moving Average by Perry Kaufman ). Этот индикатор достаточно хорошо знаком многим трейдерам, найти его несложно. Поэтому в качестве примера я решил взять другой индикатор – FRAMA. Прочитать о нем можно здесь - Fractal Adaptive Moving Average by John Ehlers . Сам код индикатора можно написать и по-другому, мой вариант не является классическим.
Можно сравнить среднюю линию PriceChannel и FRAMA.

Рис. 15
Единственное достоинство, которое мы видим на глаз сразу – более плавный ход FRAMA по сравнению со средней линией PriceChannel. Попробуем построить канал от FRAMA. Для этого нам необходимо правило, по которому мы будем откладывать границы канала от FRAMA. Предлагаю для этого использовать отклонения цены закрытия Close от значения FRAMA. А точнее, стандартное отклонение этого распределения.
Итак, имеем: 1. значения индикатора FRAMA, которое мы будем показывать 2. отклонения цены Close от FRAMA, которые мы не будем показывать 3. верхняя граница канала, которую мы будем показывать 4. нижняя граница канала, которую мы будем показывать.
Значит, наш индикатор должен выводить (рисовать) три буфера и иметь один скрытый (вспомогательный) буфер для расчетов. МТ4 легко позволяет это, но нужно помнить, что неотображаемые буферы должны быть последними (иметь максимальный индекс).
Рис. 16
Для того, чтобы построить границы канала, сначала мы должны в цикле посчитать значения FRAMA и отклонения, и только потом в отдельном цикле мы можем вычислять границы канала.

Рис. 17
Для примера я использовал одну из функций, которая рассчитывается на массиве значений – iStdDevOnArray(). Полученный индикатор со значениями по умолчанию выглядит так.

Рис. 18
Все индикаторы находятся здесь .

