Как написать советника форекс

Если вы задавались вопросом «Как написать советник на языке программирования MQL4», то данный пост создан именно для вас. Сегодня мы будем создавать самый простой из всевозможных советников для МТ4, который будет определять незамысловатое условие на вход, открывать ордер и модифицировать его цели.

Алгоритм программирования советника на MQL4

Выше представлена схема процесса работы торгового робота, который мы планируем написать. Любой советник должен иметь как минимум три функции обработки событий:

OnInit()

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

OnDeinit()

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

OnTick()

Данная функция новая в наших уроках. Раздел OnTick генерирует события исключительно для экспертов и не может быть вызвана в индикаторах или скриптах. Ее тип данных void и набор параметров отсутствует. По своей логике работы она схожа с функцией для индикаторов OnCalculate,  только она не возвращает никакого значения после завершения. Задача у нее одна — запускаться каждый новый тик и проходить циклом весь написанный код в ней от начала до конца. Так как любой советник должен проверять условия для входа/выхода из рынка, считать количество открытых ордеров и выполнять их сопровождение, то можно уверенно сказать, что функция OnTick является самой важной в коде любого эксперта.

Продолжим разбирать алгоритм работы. Вначале советник инициализируется. Далее запускается функция OnTick, в которой выполняются все дальнейшие действия. Для данного советника сначала необходимо проверить наличие уже открытых им ордеров. Если они есть — дальнейший расчет и поиск условия на вход не выполняются, потому что в рынке у нас должен быть только один ордер за раз. Если же открытых сделок нет, то идет определение направления будущей позиции. В зависимости от него запускается пользовательская функция на открытие Buy или Sell ордера. Если по какой-то причине сделка не смогла открыться, расчет возвращается назад в функцию OnTick, чтобы попробовать выставить ордер снова. Если же ордер открылся, то он модифицируется (выставляется Тейк-Профит и Стоп-Лосс). На этом алгоритм заканчивает свою работу по работе с ордерами, потому что счетчик новых ордеров уже будет учитывать этот открывшийся ордер, делая проверку каждый тик. И только после того, как ордер закроется по достижению своей цели (ТП или СЛ), цикл проверки условия на открытие ордера запустится снова. Функция OnDeinit запустится только тогда, когда вы удалите советник с графика.

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

Торговая система «Монетка»

форекс тс монетка

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

Период советника установим М15. Мы будем входить в рынок по воле случая, или, если хотите, удачи. Мы начнем условно подкидывать монетку и смотреть на результат этого несложного действия. Орел или Решка — только два исхода событий будет у советника. Вариант «ребро» в данном случае не рассматривается 🙂

Зачем использовать такую простую систему? Чтобы понять, что произойдет в результате этого эксперимента и ответить себе на вопрос: можно ли заработать на Форекс входя в рынок наобум? Получится ли прибыльно торговать не имея четко спланированной торговой системы?

Проверка советника на ошибки: GetLastError()

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

Чтобы получить номер возможной ошибки, нужно вызвать функцию GetLastError(). Она имеет тип int и возвращает целое число, значение системной переменной _LastError. Тут нужно обратить внимание, что на языке MQL4 после вызова функции GetLastError ее значение обнуляется и повторный вызов ее в том же месте кода вернет значение 0, поэтому ее значение нужно сохранять в отдельную переменную.

Язык MQL различает как минимум 150 разных ошибок. Соответственно каждый возвращаемый номер имеет свою расшифровку. Ноль значит это ошибки нет и все отлично, 134 значит, что недостаточно денег на счете и т.д.

Чтобы получить примерную расшифровку ошибки на английском языке, нужно обратиться ко встроенной библиотекой stdlib.mq4, которая находится в папке Libraries терминала. Для этого нужно воспользоваться препроцессором включение файлов #include. Для этого прописываем импорт данной библиотеке в самом начале кода вне функций.

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

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

#property version «1.00»

#property strict

#include <..Librariesstdlib.mq4> //библиотека для расшифровки ошибок

Теперь осталось прописать проверку в самом конце функции обработки событий OnTick()

int Error = GetLastError(); //поиск ошибок по завершению тика

if(Error != 0) Print(«OnTick() Error «,Error,«: «,ErrorDescription(Error));

В этом коде вначале сохраняется значение ошибки в переменную Error, далее проверяется, чтобы ошибка точно была (значение не было равно нулю) и если это так, то выводится принт в журнал с номером ошибки и ее расшифровка.

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

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

Чтобы создать советник, нажмите «Файл»  — «Создать». Затем выберите вариант «Советник (шаблон)», клик по кнопке Далее, придумываете ему имя, гордо записывая себя, как автора и добавляете ссылку на ваш (или наш) сайт. Параметры добавлять не нужно, мы их пропишем вручную, тык по кнопке Далее. Обработчики событий нам не нужны, поэтому еще раз клик на Далее. Функция OnTester полезная при оптимизации сов, но в этом уроке она не понадобится (мы рассмотрим ее в будущих статьях). Жмем Готово.

Программа создаст стандартный шаблон из описания и трех функций обработки событий: OnInit, OnDeinit и OnTick.

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

  • Lot — торговый лот, с которым будет открываться ордер. По умолчанию задаем его минимальным. Тип переменной будет double, т.к. это лот не может быть целым числом.
  • Slippage — максимально допустимое проскальзывание в пунктах, больше которого ордер не будет открыт.
  • Stop Loss — Стоп Лосс (ограничение убытка) для каждого ордера советника. По умолчанию задается в пунктах.
  • Take Profit — Тейк Профит нужен для закрытие ордера при достижении заданной прибыли. По умолчанию задается в пунктах.
  • Comments — текстовый комментарий, с которым будет открыт ордер. Его можно будет увидеть в окнах «Торговля» и «История Счета» в столбце «Комментарий». Он полезен, если на счете торгует несколько советников, либо используется ручная торговля. Некоторые брокеры в комментарий прописывают проскальзывания при открытии конкретного ордера. Также он хранит информацию, если ордер был частично закрыт.
  • MagicNumber — номер (целое число), который задается всем ордерам конкретного советника, открытым по текущему символу. Магик нужен для того, чтобы советник мог отличить свои сделки от чужих. Для каждой пары и советника рекомендуется задавать свой уникальный магик. Ордера, открытые вручную имеют нулевой магик. Магик также можно увидеть на мониторинге сервисов MyFxBook или FxBlue, и сделать по нему фильтрацию.

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

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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

//| 3.0 Coin EA |

//| Copyright (c) DaVinci FX Group |

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

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

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

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

#property version «1.00»

#property strict

#include <..Librariesstdlib.mq4>

extern string s0 = «<== General Settings ==>»; //> > >

extern double Lot = 0.01;

extern int Slippage = 5;

extern double StopLoss = 20;

extern double TakeProfit = 20;

extern string Comments = «DaVinci EA»;

extern int MagicNumber = 123123;

Переходим к функции OnInit. Так как на Форекс до сих пор существуют типы счетов с котировками, у которых разное количество знаков после запятой, то необходимо добавить проверку для тех параметров, значение которых указываются в пунктах. Изначально мы задали значения проскальзывания, Стоп Лосса и Тейк Профита в старых пунктах. Если же у валютной пары три знака (для JPY) или пять (для остальных валют) после запятой, то нам необходимо перевести старые пункты в новые, умножив это значение на десять.

int OnInit() {

if (Digits == 3 || Digits == 5) {

Slippage *= 10;

StopLoss *= 10;

TakeProfit *= 10;

}

return(INIT_SUCCEEDED);

}

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

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

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

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

int CountOrder() {

int orders=0;

for(int i=OrdersTotal()-1;i>=0;i—){

if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)==false) continue;

if(OrderSymbol() !=_Symbol || OrderMagicNumber() != MagicNumber) continue;

if(OrderType() > 1) continue;

orders++;

}

return orders;

}

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

void OnTick() {

if(CountOrder() == 0) { //проверка, чтобы не было открытых ордеров

// дальнейший код

}

int Error = GetLastError();

if(Error != 0) Print(«OnTick() Error «,Error,«: «,ErrorDescription(Error));

}

Далее нам необходимо определиться с направлением сделки. Как говорилось выше, мы будем входить по системе «Монетка», т.е. нам нужно рандомно (наугад) определить направление входа. С этим поможет справиться функция MathRand(), она возвращает псевдослучайное целое число в диапазоне от 0 до 32767. Так как нам нужно получить только два значения: 0 (для покупок) или 1 (для продаж), то просто добавим знак %, с помощью которого получим остаток от деления на 2. Каждый тик результат будет случайно определяться между 0 и 1.

int TradeDirection = MathRand()%2; //поиск рандомного направления сделки

Пользовательская функция OpenTrade()

С условием на вход определились, осталось в зависимости от него осуществить покупку или продажу. Для этого введем еще одну пользовательскую функцию OpenTrade(). Ей будет передаваться один параметр — направление на вход. В зависимости от направления сделки нужно определить:

  • Price — цена открытия ордера. Рыночные ордера на покупку открываются по цене Ask, а закрываются по цене Bid. И наоборот, рыночные ордера на продажу открываются по цене Bid, и закрываются по цене Ask.
  • Цвет стрелочки да пометки открытия ордера на графике. По обычаю покупки имеют синий цвет, продажи красный.
  • Текст для открытия ордера.

Для упрощения работы мы воспользуемся условным оператором ? 

bool OpenTrade(int OP_Type) {

double price = (OP_Type == OP_BUY ? Ask : Bid);

color col_type = (OP_Type == OP_BUY ? clrBlue : clrRed);

string op_str = (OP_Type == OP_BUY ? «на покупку» : «на продажу»);

Print(«Открываем ордер « + op_str);

//дальнейший код

return(false);

}

Переходим к самой главной торговой функции OrderSend. Она и отвечает за открытие рыночного или отложенного ордера. Имеет тип данных int и возвращает тикет открытого ордера в случае успешного выполнения, либо же ноль в случае неудачи. Функция имеет ряд параметров, которые ей необходимо передать:

  • symbol — символ, по которому необходимо открыть ордер. Текущий символ задается как Symbol().
  • cmd — торговая операция, т.е. один из шести типов ордера, который нужно открыть: рыночные (buy или sell) и отложенные (buylimit, selllimit, buystop, sellstop).
  • volume — размер торгового лота.
  • price — цена открытия ордера. Для рыночных ордеров нами она рассчитана выше.
  • slippage — проскальзывание в пунктах, берется из внешней переменной.
  • stoploss — Стоп Лосс рассчитанный по цене котировок.
  • takeprofit — Тейк Профит рассчитанный по цене котировок.
  • comment — комментарий к ордеру, берется из внешней переменной.
  • magic — магик номер, берется из внешней переменной.
  • expiration — срок жизни ордера. Нужен только для отложенных ордеров, поэтому оставляется равный нулю.
  • arrow_color — цвет стрелочки на графике при открытии ордера.

Открываем ордер.

int ticket = OrderSend(Symbol(), OP_Type, Lot, price, Slippage, 0, 0, Comments, MagicNumber, 0, col_type);

Пометка: после появления современных технологий торговли, таких как ECN, STP и NDD, цели для ордера нельзя выставлять сразу при подаче заявки на открытие ордера. Его нужно модифицировать только после успешного открытия сделки. Для старых типов счетов можно задать его сразу, но зачем, когда можно провести модификацию и после.

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

Для расчета цены Стоп Лосса по текущим котировкам:

  • для покупок от текущей цены открытия ордера (OrderOpenPrice) вычитаем значение переменной StopLoss, умноженной на Point.
  • для продаж к текущей цене открытия ордера (OrderOpenPrice) прибавляем значение переменной StopLoss, умноженной на Point.

Для расчета цены Тейк Профит по текущим котировкам:

  • для покупок к текущей цене открытия ордера прибавляем значение переменной TakeProfit, умноженной на Point.
  • для продаж от текущей цены открытия ордера вычитаем значение переменной TakeProfit, умноженной на Point.

Модификацию выполняем с помощью торговой функции OrderModify. Она содержит почти такие же параметры, как и функция OrderSend, плюс ей передается номер тикета ордера, который нужно модифицировать.

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

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

bool OpenTrade(int OP_Type) {

double price = (OP_Type == OP_BUY ? Ask : Bid);

color col_type = (OP_Type == OP_BUY ? clrBlue : clrRed);

string op_str = (OP_Type == OP_BUY ? «на покупку» : «на продажу»);

Print(«Открываем ордер « + op_str);

int ticket = OrderSend(Symbol(), OP_Type, Lot, price, Slippage, 0, 0, Comments, MagicNumber, 0, col_type);

if(ticket > 0) { //Если ордер был открыт

if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)) {

double SL = 0, TP = 0;

if(OP_Type == OP_BUY) {

SL = OrderOpenPrice()-StopLoss*Point;

TP = OrderOpenPrice()+TakeProfit*Point;

}

else {

SL = OrderOpenPrice()+StopLoss*Point;

TP = OrderOpenPrice()-TakeProfit*Point;

}

Print(«Модифицируем ордер « + op_str);

if(!OrderModify(ticket, price, SL, TP, 0, clrNONE)) {

int Error = GetLastError();

Print(«Ошибка модификации ордера «,Error,«: «,ErrorDescription(Error));

}

else return(true);

}

}

else {

int Error = GetLastError();

Print(«Ошибка открытия ордера «,Error,«: «,ErrorDescription(Error));

}

return(false);

}

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

Нам осталось вернуться в функцию OnTick и добавить эту пользовательскую функцию в его тело.

void OnTick() {

if(CountOrder() == 0) { //проверка, чтобы не было открытых ордеров

int TradeDirection = MathRand()%2; //поиск рандомного направления сделки

if(TradeDirection == OP_BUY) {

if(OpenTrade(OP_BUY)) Print(«Ордер на покупку открыт и модифицирован»);

}

else if(TradeDirection == OP_SELL) {

if(OpenTrade(OP_SELL)) Print(«Ордер на продажу открыт и модифицирован»);

}

}

int Error = GetLastError(); //поиск ошибок по завершению тика

if(Error != 0) Print(«OnTick() Error «,Error,«: «,ErrorDescription(Error));

}

Проверка советника в тестере

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

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

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

Все выглядит хорошо, значит делаем вывод, что код написан без ошибок. По крайней мере видимых.

Посмотрим на график тестирования системы «Монетка» и сделаем выводы.

Заключение

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

Одной из целей данного урока было доказать, что необдуманная торговля не в состоянии принести прибыль. В данном примере мы использовали рандомный вход с абсолютно одинаковыми ТП и СЛ, равными 20 пунктов каждый. Т.е. при таком раскладе, если 55% сделок были бы прибыльными, то график доходности шел вверх. В нашем случае кривая прибыли нацелена строго вниз, что говорит нам самую простую истину: вы никогда не заработаете на рынке, если у вас не будет четкой и строго сформулированной торговой системы. Форекс это далеко не казино и на волю случая тут нельзя полагаться. Без четких правил входа, сопровождения ордеров и выхода из сделки вас ждет неудача.

На этом все, код советника приложен ниже. Если пост был вам полезен, то вы знаете, что делать! Оставляйте комментарии и делитесь им в тематических сообществах. Всем профитов!

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

Блог для успешных трейдеров на рынке Форекс. Полезный сайт о Forex — DaVinci FX Group.

Введение

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

1. Торговая стратегия

Что будет делать наш советник:

  • Он будет следить за некоторыми индикаторами и при определенном условии (или условиях) помещать торговый запрос (на продажу или покупку) в зависимости от условий.

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

  1. Мы будем использовать индикатор Moving Average (скользящие средние) с периодом 8 (вы можете выбрать любой период, но в данной стратегии мы будем использовать период 8).
  2. Мы хотим, чтобы наш советник покупал, если 8-периодная скользящая средняя (далее для удобства будем называть ее MA-8) возрастает и текущая цена закрытия находится выше ее; советник должен продавать, когда MA-8 падает и цена закрытия находится ниже MA-8.
  3. Также мы собираемся использовать другой индикатор, называемый Average Directional Movement (ADX) с периодом 8 для определения факта наличия тренда на рынке. Это нужно для того, чтобы входить в рынок, когда он находится в состоянии тренда. Для того, чтобы это реализовать, мы будем помещать торговый запрос (на покупку или продажу) при наступлении условий, указанных выше, а также при значениях ADX, больших 22. Если ADX>22, но уменьшается или ADX<22, мы не будем помещать торговые запросы даже при наступлении условий, изложенных в пункте 2.
  4. Мы хотим защитить себя установкой ордеров Stop Loss в 30 пунктов, Take Proft установим на уровне 100 пунктов.
  5. Также мы хотим, чтобы советник проверял возможности для продажи/покупки только при формировании нового бара, при этом советник должен помещать ордер на покупку только в случае сигнала на покупку и отсутствия открытых длинных позиций. Аналогично в случае продажи — условия на продажу и отсутствие открытых коротких позиций.

Стратегия разработана, теперь время начать писать код.

2. Пишем советник

2.1 Мастер MQL5

Начнем с запуска редактора MetaQuotes Language Editor 5. Затем нажимаем Ctrl-N или на кнопку «Создать» в панели инструментов.

Рисунок 1. Создание нового документа MQL5

Рисунок 1. Создание нового документа MQL5

В окне Мастера MQL5 выбираем «Советник» и нажимаем «Далее», как показано на рис. 2:

Рисунок 2. Выбор типа создаваемой программы

Рисунок 2. Выбор типа создаваемой программы

В следующем окне в поле «Имя» напишите имя, которое вы хотите дать вашему советнику, я написал «My_First_EA«. Вы можете указать свое имя в поле «Автор» и адрес в виде ссылки на ваш сайт или e-mail (если есть).

Рисунок 3. Общие свойства советника

Рисунок 3. Общие параметры советника

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

Рисунок 4. Входные параметры советника

Рисунок 4. Входные параметры советника

В нашем советнике нам нужно иметь возможность изменять Stop Loss, Take Profit, ADX Period and Moving Average Period, так что укажем их здесь.

Дважды кликнем мышкой по колонке «Имя» в параметрах и напишем наименование параметра, аналогично в колонках «Тип» и «Начальное значение» укажем тип данных параметра и начальные значения.

После этого, результат будет примерно следующий:

Рисунок 5. Типы данных входных параметров советника

Рисунок 5. Типы данных входных параметров советника

Как видно, мы выбрали тип integer (int) для всех параметров. Рассмотрим подробнее типы данных.

  • char: Целый тип char занимает в памяти 1 байт (8 бит) и позволяет выразить в двоичной системе счисления 2^8 значений=256. Тип char может содержать как положительные, так и отрицательные значения. Диапазон изменения значений составляет от -128 до 127.
  • uchar: Целый тип uchar также занимает в памяти 1 байт, как и тип char, но в отличие от него, uchar предназначен только для положительных значений. Минимальное значение равно нулю, максимальное значение равно 255. Первая буква u в названии типа uchar является сокращением слова unsigned (беззнаковый).
  • short: Целый тип short имеет размер 2 байта(16 бит) и, соответственно, позволяет выразить множество значений равное 2 в степени 16:  2^16=65 536. Так как тип short является знаковым и содержит как положительные, так и отрицательные значения, то диапазон значений находится между -32 768 и 32 767.
  • ushort: Беззнаковым типом short является тип ushort, который также имеет размер 2 байта. Минимальное значение равно 0, максимальное значение 65 535.
  • int: Целый тип int имеет размер 4 байта (32 бита). Минимальное значение -2 147 483 648, максимальное значение 2 147 483 647.
  • uint: Беззнаковый целый тип uint занимает в памяти 4 байта и позволяет выражать целочисленные значения от 0 до 4 294 967 295.
  • long: Целый тип long имеет размер 8 байт (64 бита). Минимальное значение -9 223 372 036 854 775 808, максимальное значение 9 223 372 036 854 775 807.
  • ulong: Целый тип ulong также занимает 8 байт и позволяет хранить значения от 0 до 18 446 744 073 709 551 615.

Как видно из описания различных типов данных, беззнаковые целые (uint) не предназначены для хранения отрицательных значений, любые попытки установить отрицательные значения могут привести к непредсказуемым результатам. Например, если вы хотите хранить отрицательные значения, нельзя для них использовать переменные типа uchar, uint, ushort, ulong.

Вернемся к нашему советнику. Для значений, меньших 127 или 255, для экономии памяти можно использовать значения типа char or uchar, соответственно, однако для удобства мы зададим их значения как тип int.

После того, как закончено определение необходимых входных параметров индикатора, нажмем на кнопку «Finish» и MetaQuotes Editor5 создаст шаблон кода, представленный ниже:

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

В верхней части кода (заголовок) определяются свойства советника. Как видно, это значения, которые были установлены в Мастере MQL5 на рис. 3.

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


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

  • #define
    Директива #define используется для определения констант. Записывается в виде:
  • #define identifier token_string
    Это означает, что компилятор заменит в коде переменные
    identifier численным значением, равным token_string.

Например:

#define ABC               100
#define COMPANY_NAME      «MetaQuotes Software Corp.»

В данном случае COMPANY_NAME будет означать строку «MetaQuotes Software Corp.», вместо ABC будет подразумеваться число, равное 100.

Более подробнее о директивах препроцессора можно прочитать в руководстве по MQL5. Идем далее.

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

 

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

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

Далее идет функция инициализации советника. Это функция вызывается первой после запуска советника или смены графика и вызывается только один раз.

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

Например, можно проверить, достаточно ли баров на графике для работы нашего советника и т.п.

Также это лучшее место для получения хэндлов технических индикаторов, которые будут использоваться (в нашем случае это индикаторы ADX и Moving Average).


Функция OnDeinit вызывается при удалении советника с графика.

В нашем советнике, в данной функции мы будем освобождать хэндлы индикаторов, созданных в разделе инициализации.


Данная функция производит обработку события NewTick, которое генерируется при приходе новой котировки для символа.

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

Отметим, что советник не сможет производить торговые операции, если в авто-трейдинг не разрешен в клиентском терминале:

Рисунок 6. Авто-торговля включена

Рисунок 6. Торговля советником разрешена

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

2.2. Раздел входных параметров

input int      StopLoss=30;      
input int      TakeProfit=100;   
input int      ADX_Period=8;     
input int      MA_Period=8;      
input int      EA_Magic=12345;   
input double   Adx_Min=22.0;     
input double   Lot=0.1;          

int adxHandle; 
int maHandle;  
double plsDI[],minDI[],adxVal[]; 
double maVal[]; 
double p_close; 
int STP, TKP; 

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

// глобальные переменные …

Это однострочный комментарий.

/*

  Это многострочный комментарий

*/

Это многострочный комментарий. Многострочные комментарии начинаются с пары символов «/*» и заканчиваются «*/».

При компиляции кода комментарии игнорируются компилятором.

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

Рисунок 7. Входные параметры советника

Рисунок 7. Входные параметры советника

Вернемся к нашему коду.

Мы решили добавить дополнительные параметры в наш советник. Параметр EA_Magic (Magic Number)  будет использован для всех ордеров нашего советника. Минимальное значение ADX задано как переменная типа double. Значения типа double используются для констант, которые, наряду с целой частью, также могут содержать и дробную часть.

Например:

double mysum = 123.5678;

double b7 = 0.09876;

Количество лотов для торговли (Lot) представляет собой объем финансового инструмента, который мы хотим торговать.

Далее мы также объявили дополнительные переменные, которые будут использованы следующим образом: переменная adxHandle будет использоваться для хранения хэндла индикатора ADX, переменная maHandle для хэндла индикатора Moving Average. Динамические массивы plsDI[], minDI[], adxVal[] are будут использованы для хранения значений +DI, -DI и самого значения ADX для каждого бара графика. Численные значения индикатора Moving Average для каждого бара графика будут храниться в динамическом массиве maVal[].

Кстати, что представляют собой динамические массивы? Динамический массив — это массив, объявленный без указания размера. Другими словами, в квадратных скобках при его описании нет конкретного числа, указывающего его размер.

С другой стороны, для статических массивов их размер определяется при объявлении.

Пример:

double allbars[20]; // этот массив содержит 20 элементов — от 0 до 19

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

Переменные STP и TKP нужны для установки значений Stop Loss и Take Profit ордеров нашего советника.

2.3. Секция инициализации советника

int OnInit()
  {

   adxHandle=iADX(NULL,0,ADX_Period);

   maHandle=iMA(_Symbol,_Period,MA_Period,0,MODE_EMA,PRICE_CLOSE);

   if(adxHandle<0 || maHandle<0)
     {
      Alert("Ошибка при создании индикаторов - номер ошибки: ",GetLastError(),"!!");
     }

Далее мы получаем хэндлы индикаторов, используя соответствующие функции индикаторов.

Хэндл индикатора ADX получаем при помощи функции iADX. В качестве аргументов ей передается символ графика symbol (NULL  также означает символ текущего графика), период/таймфрейм (0 означает таймфрейм текущего графика), период индикатора ADXкоторый будет использоваться для вычисления индикатора (ADX_Period мы определили в разделе входных параметров индикатора):

int  iADX(
   string          symbol,       
   ENUM_TIMEFRAMES  period,       
   int            adx_period     
   );

Хэндл индикатора Moving Average получаем при помощи функции iMA. Аргументы этой функции следующие:

  • symbol — Символьное имя инструмента, на данных которого будет вычисляться индикатор (можно использовать _symbol, symbol() или NULL для текущего символа).
  • period — Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, (можно использовать  _period, period() или 0 для таймфрейма текущего графика).
  • ma_period — Период усреднения для вычисления скользящего среднего (который мы определили ранее в разделе входных параметров индикатора).
  • ma_shift — Сдвиг индикатора относительно ценового графика (мы используем 0).
  • ma_method — Метод усреднения. Может быть любым из значений MODE_SMA, MODE_EMA, MODE_SMMA или MODE_LWMA.
  • applied_price — Используемая цена. Может быть любой из ценовых констант ENUM_APPLIED_PRICE или хендлом другого индикатора.
int  iMA(
   string              symbol,        
   ENUM_TIMEFRAMES      period,        
   int                 ma_period,    
   int                 ma_shift,     
   ENUM_MA_METHOD       ma_method,    
   ENUM_APPLIED_PRICE   applied_price  
   );

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

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

 

Мы решили хранить значения Stop Loss и Take Profit в определенных ранее переменных STP и TKP. Почему мы это сделали?

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

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

Предопределенной переменной _Digits или функцией Digits(). Для 3-х и 5-ти значных котировок мы умножаем значения Stop Loss и Take Profit на 10.

2.4. Раздел деинициализации советника


Поскольку эта функция вызывается при прекращении работы советника или удалении советника с графика, здесь мы освобождаем хэндлы индикаторов, созданные в процессе инициализации. Мы создали два индикатора, ADX и Moving Average.

Для их удаления мы используем функцию IndicatorRelease(). Эта функция имеет лишь один параметр (хэндл индикатора).

bool  IndicatorRelease(
   int       indicator_handle,     // хэндл индикатора
   );

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

2.5 Раздел OnTick советника

Первое, что мы здесь делаем — проверяем достаточно ли баров на текущем графике. Количество баров на любом графике можно узнать при помощи функции Bars. У нее есть два входных параметра, первый — symbol, (символ текущего графика можно получить используя предопределенную переменную _Symbol или функцию Symbol()) и period  или timeframe  текущего графика (для текущего графика  — предопределенная переменная _Period или функция Period()).

При количестве баров на графике менее 60, наш советник не будет работать и выйдет из функции OnTick. Функция Alert показывает сообщение в отдельном окне. Эта функция выводит значения аргументов/параметров, разделенных запятыми. В нашем случае выводится только одно значение в виде строки и завершается работа функции OnTick.



void OnTick()
  {

   if(Bars(_Symbol,_Period)<60) 
     {
      Alert("На графике меньше 60 баров, советник не будет работать!!");
      return;
     }





   static datetime Old_Time;
   datetime New_Time[1];
   bool IsNewBar=false;


   int copied=CopyTime(_Symbol,_Period,0,1,New_Time);
   if(copied>0) 
     {
      if(Old_Time!=New_Time[0]) 
        {
         IsNewBar=true;   
         if(MQL5InfoInteger(MQL5_DEBUGGING)) Print("Новый бар",New_Time[0],"старый бар",Old_Time);
         Old_Time=New_Time[0];   
        }
     }
   else
     {
      Alert("Ошибка копирования времени, номер ошибки =",GetLastError());
      ResetLastError();
      return;
     }


   if(IsNewBar==false)
     {
      return;
     }


   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) 
     {
      Alert("На графике менее 60 баров, советник работать не будет!!");
      return;
     }


   MqlTick latest_price;       
   MqlTradeRequest mrequest;    
   MqlTradeResult mresult;      
   MqlRates mrate[];           
   ZeroMemory(mrequest);       

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

Мы начнем с объявления статической переменной Old_Time, в которой будем хранить время бара. Мы определили ее статической, поскольку нам нужно, чтобы ее значение сохранялось при новом вызове функции. Тогда у нас будет возможность проверять ее значение с переменной New_Time, которая также объявлена типа datetime, но в виде массива из одного элемента, она будет использоваться для хранения времени текущего бара. Также мы объявляем переменную IsNewBar типа boolean, и устанавливаем ее значение в false. Ее значение будет установлено в true только в  случае определения факта появления нового бара.

Для получения времени бара используется функция CopyTime. Она копирует время бара в массив New_Time, состоящий из одного элемента. В случае успеха, мы сравниваем значение времени бара с сохраненным ранее временем предыдущего бара. Если они различны, это означает, что появился новый бар и переменная IsNewBar устанавливается в true, а значение текущего времени бара сохраняется в переменной Old_Time.

Таким образом, переменная IsNewBar будет указывать на факт появления нового бара. Если ее значение равно false, мы завершаем выполнение функции OnTick.

Обратите внимание на строчку:

if(MQL5InfoInteger(MQL5_DEBUGGING)) Print("Новый бар",New_Time[0],"старый бар",Old_Time);

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

Следующее, что мы собираемся cделать — проверить наличие достаточного количества баров для работы. Зачем делать это снова?

Мы хотим быть уверены в том, что наш советник работает корректно.

Следует отметить, что функция OnInit вызывается только один раз при присоединении советника к графику, а функция OnTick вызывается каждый раз при поступлении нового тика (ценовой котировки).

Как можно видеть, мы это делаем по-другому. Мы сохраняем общее количество баров в истории в новой переменной Mybars, определенной внутри функции OnTick:

   int Mybars=Bars(_Symbol,_Period);

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

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

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

MqlTick

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

struct MqlTick
  {
   datetime     time;          //
Время последнего обновления цен
   double       bid;           //
Текущая цена Bid
   double       ask;           //
Текущая цена Ask
   double       last;          //
Текущая цена последней сделки (Last)
   ulong        volume;        //
Объем для текущей цены Last
  };

Любая переменная, объявленная типа может быть легко использована для получения текущих значений цен Ask, Bid, Last и Volume, достаточно вызвать функцию SymbolInfoTick.

Мы объявили переменную latest_price как структуру MqlTick, так что мы можем использовать ее для получения цен Bid и Ask.

MqlTradeRequest

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

struct MqlTradeRequest
  {
   ENUM_TRADE_REQUEST_ACTIONS    action;       //
Тип выполняемого действия
   ulong                         magic;        //
Идентификатор magic number эксперта
   ulong                         order;        //
Тикет ордера
   string                        symbol;       //
Имя торгового инструмента
   double                        volume;       //
Запрашиваемый объем сделки в лотах
   double                        price;        //
Цена
   double                        stoplimit;    //
Уровень StopLimit ордера
   double                        sl;           //
Уровень Stop Loss ордера
   double                        tp;           //
Уровень Take Profit ордера
   ulong                         deviation;    //
Максимально приемлемое отклонение от запрашиваемой цены
   ENUM_ORDER_TYPE               type;          //
Тип ордера
   ENUM_ORDER_TYPE_FILLING       type_filling;  //
Тип ордера по исполнению
   ENUM_ORDER_TYPE_TIME          type_time;     //
Тип ордера по времени действия
   datetime                      expiration;    //
Срок истечения ордера (для ордеров типа ORDER_TIME_SPECIFIED)
   string                        comment;       //
Комментарий к ордеру
  };

Любая переменная, объявленная как структура MqlTradeRequest может быть использована для отправки запросов на совершение торговых операций. В нашем случае мы объявили переменную mrequest как структуру MqlTradeRequest.

MqlTradeResult

Результат выполнения торговой операции возвращается в специальную предопределенную структуру типа MqlTradeResult. Любая переменная типа MqlTradeResult может быть использована для доступа к результату выполнения торгового запроса.

struct MqlTradeResult
  {
   uint     retcode;          //
Код результата операции
   ulong    deal;             //
Тикет сделки, если она совершена
   ulong    order;            //
Тикет ордера, если он выставлен
   double   volume;           //
Объем сделки, подтверждённый брокером
   double   price;            //
Цена в сделке, подтверждённая брокером
   double   bid;              //
Текущая рыночная цена Bid
   double   ask;              //
Текущая рыночная цена Ask
   string   comment;          //
Комментарий брокера к операции (по умолчанию заполняется расшифровкой)
  };

В нашем случае переменная mresult объявлена как структура тип MqlTradeResult.

MqlRates

Цены (Open, Close, High, Low), время, объем каждого бара, и спред символа хранятся в этой структуре. Любой массив, определенный как массив типа MqlRates может быть использован для хранения значений цен, объемов и спредов по символу.

struct MqlRates
  {
   datetime time;         //
Время начала периода
   double   open;         //
Цена открытия
   double   high;         //
Наивысшая цена за период
   double   low;          //
Наименьшая цена за период
   double   close;        //
Цена закрытия
   long     tick_volume;  //
Тиковый объем
   int      spread;       // С
пред
   long     real_volume;  //
Биржевой объем
  };

Для наших целей мы определили массив mrate[], который будет использоваться для хранения этой информации.


   ArraySetAsSeries(mrate,true);

   ArraySetAsSeries(plsDI,true);

   ArraySetAsSeries(minDI,true);

   ArraySetAsSeries(adxVal,true);

   ArraySetAsSeries(maVal,true);

Здесь мы устанавливаем индексацию как в таймсериях для всех массивов, которые будут использоваться нами. Это позволит нам быть уверенными в том, что скопированные массивы будут иметь нумерацию как в таймсериях (справа налево т.е. 0, 1, 2, 3 и т.д.). Это производится при помощи функции ArraySetAsSeries().

bool  ArraySetAsSeries(
   
void  array[],     // массив по ссылке
   bool  set          // true означает обратный порядок индексации
   );

Следует отметить, что это можно сделать однократно в функции инициализации советника. Тем не менее, для последовательности изложения я решил рассмотреть этот вопрос здесь.

   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("Ошибка получения последних котировок - ошибка:",GetLastError(),"!!");
      return;
     }

Мы используем функцию SymbolInfoTick для получения текущих котировок. Эта функция имеет два аргумента — символ графика и структура типа MqlTick (latest_price). Снова, проверяем корректность выполнения функции и выводим сообщение в случае ошибки.

   if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
     {
      Alert("Ошибка копирования исторических данных - ошибка:",GetLastError(),"!!");
      return;
     }

Затем, при помощи функции CopyRates мы копируем информацию последних трех баров в массив типа MqlRates. Функция CopyRates используется для получения исторических данных по указанному символу, периоду и запрашиваемому количеству данных, которые затем помещаются  в массив типа MqlRates.

int  CopyRates(
   string           symbol_name,       // имя символа
   ENUM_TIMEFRAMES  timeframe,         // период
   int              start_pos,         // откуда начнем
   int              count,             // количество данных для копирования
   MqlRates         rates_array[]      // массив, куда будут скопированы данные
   );

Имя символа и текущий таймфрей получаем используя предопределенные переменные _Symbol и _Period. Начиная с текущего бара Bar 0, имеющего индекс 0, мы возьмем только три бара: Bars 0, 1, и 2. Результат будет помещен в наш массив mrate[].

Массив mrate[] теперь содержит все данные по ценам, времени, объемам и спредам для баров 0, 1 и 2. Поэтому для того, чтобы получить нужное свойство любого бара, мы используем выражение типа:

mrate[bar_number].bar_property

например, для каждого из этих баров:

mrate[1].time   // время начала бара 1
mrate[1].open   // цена открытия бара 1
mrate[0].high   // наибольшая цена бара 0 (текущий бар), и т.д.

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

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

int  CopyBuffer(
   int       indicator_handle,     
   int       buffer_num,           
   int       start_pos,            
   int       count,                
   double    buffer[]              
   );

В качестве хэндла индикатора указывается хэндл, полученный в функции OnInit. Что касается номеров буферов индикатора, то индикатор ADX имеет 3 (три) буфера:

  • 0 — MAIN_LINE,
  • 1 — PLUSDI_LINE,
  • 2 — MINUSDI_LINE. 

Индикатор The Moving Average имеет только 1 (один) буфер:

  • 0 – MAIN_LINE.

Начиная с текущего бара (0), мы копируем также еще два бара. Таким образом, полное количество баров равно 3 (бары 0-й,1-й и 2-й). Массив buffer[] в параметре функции CopyBuffer, это массив, куда будут помещены данные. В нашем случае это динамические массивы adxVal, plsDI, minDI и maVal.

   if(CopyBuffer(adxHandle,0,0,3,adxVal)<0 || CopyBuffer(adxHandle,1,0,3,plsDI)<0
      || CopyBuffer(adxHandle,2,0,3,minDI)<0)
     {
      Alert("Ошибка копирования буферов индикатора ADX - номер ошибки:",GetLastError(),"!!");
      return;
     }
   if(CopyBuffer(maHandle,0,0,3,maVal)<0)
     {
      Alert("Ошибка копирования буферов индикатора Moving Average - номер ошибки:",GetLastError());
      return;
     }

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

Важно отметить, что функции CopyBuffer() and the CopyRates() возвращают общее количество скопированных данных или -1 в случае ошибки. Вот почему мы проверяем возвращаемые значения, они будут меньше 0 в случае ошибки.

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

Для того, чтобы реализовать это, сначала объявим две переменные типа boolean (Buy_opened и Sell_opened), которые будут установлены в TRUE в случае наличия соответствующих открытых позиций.

    bool Buy_opened=false;  //  
    bool Sell_opened=false; 
    
    if (PositionSelect(_Symbol) ==true)  
    {
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            Buy_opened = true;  
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            Sell_opened = true; 
         }
    }

Для того, чтобы узнать наличие открытой позиции, мы использовали функцию PositionSelect, которая возвращает TRUE в случае наличия открытой позиции по указанному символу и FALSE при отсутствии открытой позиции.

bool  PositionSelect(
   string  symbol      
   );

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

В случае, если функция вернула TRUE (позиция существует), мы хотим проверить ее тип (покупка или продажа). Для этого мы используем функцию PositionGetInteger, она возвращает тип открытой позиции, в случае, если в качестве параметра задан запрос свойства POSITION_TYPE. В результате возвращается одно из значений перечисления  ENUM_POSITION_PROPERTY_INTEGER: POSITION_TYPE_BUY или POSITION_TYPE_SELL.

long  PositionGetInteger(
   ENUM_POSITION_PROPERTY  property_id    
   );

В нашем случае, мы используем это для того, чтобы определить факт наличия уже открытой позиции. Если открыта позиция на продажу, мы устанавливаем значение переменной Sell_opened в TRUE, если открыта позиция на покупку, мы устанавливаем значение переменной Buy_opened в TRUE. В дальнейшем мы используем значения этих переменных при проверке условий открытия позиций.

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

p_close=mrate[1].close;  

После того, как это сделано, перейдем к следующему шагу.


   bool Buy_Condition_1 = (maVal[0]>maVal[1]) && (maVal[1]>maVal[2]); 
   bool Buy_Condition_2 = (p_close > maVal[1]);         
   bool Buy_Condition_3 = (adxVal[0]>Adx_Min);          
   bool Buy_Condition_4 = (plsDI[0]>minDI[0]);          


   if (Buy_Condition_1 && Buy_Condition_2)
     {
      if(Buy_Condition_3 && Buy_Condition_4)
        {
         
         if (Buy_opened) 
         {
            Alert("Уже есть позиция на покупку !!!");
            return; 
         }
         mrequest.action = TRADE_ACTION_DEAL;                                 
         mrequest.price = NormalizeDouble(latest_price.ask,_Digits);          
         mrequest.sl = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); 
         mrequest.tp = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); 
         mrequest.symbol = _Symbol;                                         
         mrequest.volume = Lot;                                            
         mrequest.magic = EA_Magic;                                        
         mrequest.type = ORDER_TYPE_BUY;                                     
         mrequest.type_filling = ORDER_FILLING_FOK;                          
         mrequest.deviation=100;                                           
         
         OrderSend(mrequest,mresult);

Рассмотрим условия покупки.

Отметим, что выражения, приведенные выше, соответствуют стратегии, которая обсуждалась ранее. Мы объявили переменные типа bool для каждого из условий, которые должны быть выполнены перед установкой ордера. Переменные типа bool могут принимать только одно из значений: TRUE или FALSE.

Поэтому, наша стратегия на покупку может быть представлена как одновременное выполнение четырех условий. Если условие выполнено, то соответствующая переменная примет значение TRUE, иначе FALSE. Рассмотрим подробнее каждое из условий.

bool Buy_Condition_1 = (maVal[0]>maVal[1]) && (maVal[1]>maVal[2]);

Здесь мы проверяем значение скользящей средней MA-8 на барах 0, 1 и 2. Если значение MA-8 на текущем баре больше, чем на предыдущем (бар 1), и при этом значение MA-8 на баре 1 больше, чем на баре 2, это означает, что скользящая средняя MA-8 возрастает. Это одно из условий покупки.

bool Buy_Condition_2 = (p_close > maVal[1]); 

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

bool Buy_Condition_3 = (adxVal[0]>Adx_Min);

Теперь мы хотим проверить, является ли значение ADX (ADX для бара 0), больше, чем минимальное значение, указанное во входных параметрах советника. Если это условие выполнено, это означает, что текущее значение ADX больше минимального необходимого, также нам нужно убедиться в том, что значение plusDI больше, чем minusDI. Это производится следующим выражением:

bool Buy_Condition_4 = (plsDI[0]>minDI[0]);

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

  • Поле action, являющееся типом торговой операции задается как TRADE_ACTION_DEAL, поскольку мы помещаем ордер в режиме немедленного исполнения. Для модификации ранее установленного отложенного ордера нужно указать TRADE_ACTION_MODIFY, для его удаления — TRADE_ACTION_REMOVE. Для цены открытия ордера мы используем значение поля Ask структуры latest_price. Значение цены Stop Loss получается вычистанием из цены Ask заданного значения StopLoss в пунктах. Цена Take Profit вычисляется прибавлением заданного значения TakeProfit в пунктах. Как видно, при указании цен мы использовали функцию NormalizeDouble, которая округляет числа до заданной точности — при отправке запроса на торговый сервер следует указывать нормализованные цены.
  • В поле symbol указывается текущий символ (_Symbol или Symbol()),
  • В поле type — тип ордера, здесь ORDER_TYPE_BUY. Для ордера на продажу нужно указать тип ORDER_TYPE_SELL.
  • В поле type_filling — тип  исполнения ордера, значение ORDER_FILLING_FOK означает что сделка должна быть выполнена в указанном объеме по указанной (или лучше) цене. Если на рынке отсутствует возможность исполнить указанный объем, ордер не будет выполнен.

У функции OrderSend() два аргумента — переменные типа  MqlTradeRequest и MqlTradeResult.

bool  OrderSend(
   MqlTradeRequest&  request      // структура запроса
   MqlTradeResult&   result       // структура ответа
   );

Как видно, мы использовали наши переменные mrequest и mresult в качестве аргументов функции OrderSend().

         
         if(mresult.retcode==10009 || mresult.retcode==10008) 
           {
            Alert("Ордер Buy успешно помещен, тикет ордера #:",mresult.order,"!!");
           }
         else
           {
            Alert("Запрос на установку ордера Buy не выполнен - код ошибки:",GetLastError());
            return;
           }

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

Код возврата торгового сервера 10009 означает, что запрос OrderSend был успешно выполнен, а код 10008 показывает, что ордер помещен в очередь на исполнение. Поэтому мы должны проверить любой из этих вариантов — в таком случае мы уверены в том, что ордер был выполнен или помещен в очередь на исполнение.

Для проверки условий для продажи, мы производим проверку, противоположную той, которая была для покупки, за исключением того, что DI- должен быть больше, чем DI+.


   bool Sell_Condition_1 = (maVal[0]<maVal[1]) && (maVal[1]<maVal[2]);  
   bool Sell_Condition_2 = (p_close <maVal[1]);                         
   bool Sell_Condition_3 = (adxVal[0]>Adx_Min);                         
   bool Sell_Condition_4 = (plsDI[0]<minDI[0]);                         
   
 
   if (Sell_Condition_1 && Sell_Condition_2)
       {
         if (Sell_Condition_3 && Sell_Condition_4)
           {
            
            if (Sell_opened) 
            {
               Alert("Уже есть позиция на продажу!!!");
               return; 
            }
            mrequest.action = TRADE_ACTION_DEAL;                                
            mrequest.price = NormalizeDouble(latest_price.bid,_Digits);          
            mrequest.sl = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); 
            mrequest.tp = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); 
            mrequest.symbol = _Symbol;                                         
            mrequest.volume = Lot;                                            
            mrequest.magic = EA_Magic;                                        
            mrequest.type= ORDER_TYPE_SELL;                                     
            mrequest.type_filling = ORDER_FILLING_FOK;                          
            mrequest.deviation=100;                                           
            
            OrderSend(mrequest,mresult);

Точно так же, в разделе выше, мы объявили переменные типа bool для каждого из условий, которые должны удовлетворяться для помещения ордера на продажу. Поэтому торговая стратегия для продажи также состоит из четырех условий. Если условие выполняется, соответствующая переменная устанавливается в TRUE, иначе FALSE. Как и для случая с покупкой, рассмотрим их подробней.

   bool Sell_Condition_1 = (maVal[0]<maVal[1]) && (maVal[1]<maVal[2]);

Здесь мы проверяем значения MA-8 для баров 0, 1 и 2. Если значение MA-8 текущего бара (0) меньше, чем значение предыдущего бара 1, а также MA-8 для бара 1 меньше, чем значение для бара 2, это значит, что MA-8 падает. В этом случае одно условий для продажи удовлетворяется.

   bool Sell_Condition_2 = (p_close <maVal[1]); 

В этом выражении проверяется условие того, что цена закрытия меньше чем значение MA-8 соответствующего бара (бара 1). Если цена закрытия меньше значения скользящей средней, это значит второе условие удовлетворяется. Затем проверяются следующие условия.

   bool Sell_Condition_3 = (adxVal[0]>Adx_Min); 

Здесь проверяется условие того, что текущее значение ADX (бара 0) больше чем значение, указанное во входных параметрах советника. Также нужно проверить условие того, что значение MinusDI больше, чем plusDI. Это делается следующим образом:

bool Sell_Condition_4 = (plsDI[0]<minDI[0]);

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

Главное отличие в способе вычисления цен Stop Loss и Take Profit. Поскольку производится продажа, используется Bid цена, которая была получена ранее в структуру latest_price. Также здесь указан другой тип ордера — ORDER_TYPE_SELL.

Аналогично, мы используем функцию NormalizedDouble для цены ордера и цен StopLoss и TakeProfit — всегда нужно использовать нормализованные цены при отсылке запроса на торговый сервер.

Так же, как и для ордеров Buy, мы должны проверить результат торгового запроса. Мы используем тот же код, как и для случая покупки.

         
         if(mresult.retcode==10009 || mresult.retcode==10008) 
           {
            Alert("Ордер Sell успешно помещен, тикет ордера #:",mresult.order,"!!");
           }
         else
           {
            Alert("Запрос на установку ордера Sell не выполнен - код ошибки:",GetLastError());
            return;
           }

3. Отладка и тестирование советника

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

3.1 Отладка

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

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

Рисунок 8. Настройка параметров отладки

Рисунок 8. Настройка параметров отладки

После появления окна «Параметры», выберите валютную пару, нужный период/таймфрейм и нажмите кнопку OK:

Рисунок 9. Установка параметров отладки

Рисунок 9. Установка параметров отладки

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

Для того, чтобы добавить точку останова, нужно перейти к строке, на которой нужно остановиться. В левой части редактора, рядом с границей редактора кода, нужно дважды кликнуть мышкой, при этом появится маленький голубой круг с белым квадратом внутри (рис. 10). Альтернативным вариант добавления точки останова — перейти на строку и нажать клавишу F9. Для того, чтобы убрать точку останова следует повторно нажать F9, либо дважды кликнуть по кругу.

Рисунок 10. Ставим точку останова

Рисунок 10. Ставим точку останова

В нашем коде мы собираемся установить точки останова на 5 различных строк кода. 

Для удобства описания, они пронумерованы от 1 до 5.

Установим эти 5 точек останова на строки, указанные на рис. 11. Точку останова 1 мы установили ранее.

Рисунок 11. Установка дополнительных точек останова

Рисунок 11. Установка дополнительных точек останова

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

Для начала запуска режима отладки, нажмите клавишу F5 или зеленкую кнопку в панели инструментов редактора MetaEditor:

Рисунок 12. Запуск отладчика

Рисунок 12. Запуск отладчика

Сначала редактор откомпилирует код, если при компиляции не возникло ошибок, он покажет их в отчете во вкладке «Ошибки»:

Рисунок 13. Отчет компиляции

Рисунок 13. Отчет компиляции

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

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

Рисунок 14. Установка входных параметров советника для отладки

Рисунок 14. Установка входных параметров советника для отладки

В левом верхнем углу графика видно, что советник присоеден к графику.

При запуске функции OnTick(), произойдет остановка работы советника, он остановится в точку останова 1.

Рисунок 15. Режим отладки: остановка работы советника в первой точке останова

Рисунок 15. Режим отладки: остановка работы советника в первой точке останова

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

Перед тем, как продолжить, имеет смысл познакомиться с командами отладчика. Если посмотреть на панель инструментов редактора MetaEditor, можно увидеть, что теперь стали доступны три кнопки, которые ранее были серого цвета. Причина этого в том, что теперь мы находимся в режиме отладки. Эти три команды используются для исполнения в режиме отладки (Step into, Step over, Step out)»

Рисунок 16. Режим отладки: команда "Step into"

Рисунок 16. Режим отладки: команда «Step into» (Шаг с заходом)

Команда Step Into (Шаг с заходом) переходит к следующему шагу, при этом производится заход внутрь любой вызываемой функции в коде. Для исполнения данной команды, нажмите эту кнопку или клавишу F11. Далее мы будем использовать данную команду при пошаговой отладке нашего кода.

Рисунок 17. Режим отладки: команда "Step over"

Рисунок 17. Режим отладки: команда «Step over» (Шаг с обходом)

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

Рисунок 18. Режим отладки: команда "Step out"

Рисунок 18. Режим отладки: команда «Step out» (Шаг наружу)

Для перехода к выполнению одного шага программы на один уровень выше есть команда Step Out (Шаг наружу), которая вызывается нажатием на соответствующую кнопку или комбинацией клавиш Shift+F11.

В нижней части редактора, вы видите окно «Инструменты»(Toolbox). Вкладка «Отладка» содержит следующие колонки:

  • Файл : Имя файла, с которым производится работа.
  • Функция : Имя функции, которая вызывается в настоящий момент.
  • Строка : Номер строки кода.
  • Выражение : В этой колонке вы можете указать любое выражение или переменную, за значениями которых вы желаете наблюдать в процессе выполнения программы.
  • Значение : В этой колонке показываются текущие значения указанных выражений/переменных.
  • Тип : В данной колонке показан тип данных, для которых установлен режим наблюдения.

Вернемся к процессу отладки…

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

  • Old_Time (старое время бара)
  • New_Time[0] (время текущего бара)
  • copied (количество скопированных данных времени)
  • IsNewBar (флаг, показывающий появление нового бара)
  • Mybars (полное количество баров в истории) – наш советник использует это значение

Также можно добавить в мониторинг и другие переменные, например значения технических индикаторов ADX, MA-8 и т.д.

Для добавления выражения/переменной в список наблюдаемых, дважды щелкните на колонке «Выражение» или используйте пункт «Добавить» контекстного меню и укажите наименование переменных или выражения, которые нужно включить в режим наблюдение

Рисунок 19. Монитор выражений

Рисунок 19. Монитор выражений

Укажите переменные/выражения для наблюдения:

Рисунок 20. Добавление выражений или переменных для наблюдения

Рисунок 20. Добавление выражений или переменных для наблюдения

Идем дальше…

Рисунок 21. Конманда "Step into" (Шаг с заходом) в действии

Рисунок 21. Конманда «Step into» (Шаг с заходом) в действии

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

Рисунок 22. Наблюдение за выражениями или переменными

Рисунок 22. Наблюдение за выражениями или переменными

Рисунок 23. Наблюдение за выражениями или переменными

Рисунок 23. Наблюдение за выражениями или переменными

Рисунок 24. Наблюдение за выражениями или переменными

Рисунок 24. Наблюдение за выражениями или переменными

При наступлении нового тика, мы заходим в функцию OnTick(). В случае объявления переменных статическими, переменные останутся декларированными, их значения сохраняться.при новом вызове функции OnTick мы будем наблюдать следующее:

Рисунок 25. Значения переменных при новом вызове функции OnTick

Рисунок 25. Значения переменных при новом вызове функции OnTick

Теперь запустим программу снова, на этот раз без точек останова. 

Рисунок 26. В режиме отладки при новом баре советник выводит сообщение

Рисунок 26. В режиме отладки при новом баре советник выводит сообщение

При каждом баре программа будет проверять условия на покупку/продажу, при наступлении условий будет производиться торговля и выводиться сообщение о результатах выполнения торговой операции:

Рисунок 27. Торговля советника

Рисунок 27. Торговля советника

Я думаю, можно оставить советник поработать еще несколько минут и попить кофе. Вернувшись обратно и сделав немного денег (шутка), нажмите красную кнопку Stop в MetaEditor для остановки процесса отладки.

Рисунок 25. Остановка режима отладки

Рисунок 28. Остановка режима отладки

Мы убедились, что наш советник работает, однако отметим, что клиентский терминал должен быть подключен к Интернет, в противном случае, он не будет работать.

3.2 Тестируем стратегию нашего советника

Теперь мы хотим проверить нашего советника используя встроенный Тестер стратегий клиентского терминала. Для запуска Тестера Стратегий нажмите клавишу Ctrl-R или выберите пункт «Тестер стратегий» в меню «Вид» главного меню, как показано на рисунке 26:

Рисунок 25. Запуск Тестера стратегий

Рисунок 29. Запуск Тестера стратегий

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

Рисунок 27. Окно Тестера стратегий

Рисунок 30. Окно Тестера стратегий

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

Рисунок 28. Вкладка "Настройки" Тестера

Рисунок 31. Вкладка «Настройки» Тестера

  1. Выберите советника, который нужно протестировать.
  2. Выбор валютной пары для тестирования.
  3. Выбор периода/таймфрейма для тестирования (выберем H2).
  4. Выберите «Select Custom» чтобы производить тестирование на указанном историческом интервале.
  5. Выбор дат начала и окончания тестирования.
  6. Выберем режим торговли «Обычный».
  7. Выбор начального депозита для тестирования.
  8. Не будем использовать оптимизицию параметров советника (Отключена)
  9. Нажмите кнопку «Старт», когда будете готовы начать тестирование.

Перед тем, как нажать кнопку «Старт», посмотрим на другие вкладки Тестера.

Вкладка «Агенты»

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

Рисунок 29. Вкладка "Агенты" Тестера стратегий.

Рисунок 32. Вкладка «Агенты» Тестера стратегий

Для одного агента в процессе тестирования вкладка «Агенты» имеет вид:

Рисунок 30. Агенты Тестера стратегий в процессе тестирования

Рисунок 33. Агенты Тестера стратегий в процессе тестирования

Вкладка «Журнал»

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

Рисунок 31. Вкладка "Журнал" Тестера стратегий показывает активность советника при тестировании

Рисунок 34. Вкладка «Журнал» Тестера стратегий показывает активность советника при тестировании

Вкладка «Входные параметры»

Здесь можно указать входные параметры советника.

Рисунок 32. Вкладка "Входные параметры" советника в Тестере стратегий

Рисунок 35. Вкладка «Входные параметры» советника в Тестере стратегий

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

  • Старт — начальное значение параметра
  • Шаг — шаг изменения параметра
  • Стоп — конечное значение параметра для тестера.

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

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

Вкладка «График»

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

Рисунок 33. График результатов тестирования

Рисунок 36. График результатов тестирования

Вкладка «Результаты»

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

Рисунок 34. Отчет результатов Тестера стратегий

Рисунок 37. Отчет результатов Тестера стратегий

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

Нажав правую кнопку мыши во вкладке «Результаты», вы увидите контектное меню. Выберите пункт «Сохранить как отчет«:

Рисунок 35. Сохранение результатов тестирования

Рисунок 38. Сохранение результатов тестирования

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

Для того, чтобы увидеть результаты торговли на графике, выберите пункт «Открыть график» и увидите график вида:

Рисунок 36. График с результатами тестирования на истории

Рисунок 39. График с результатами тестирования на истории

Мы успешно написали и протестировали наш советник на истории и теперь имеем заготовку для дальнейшей работы. Можно опять вернуться во вкладку «Настройки» Тестера стратегий и протестировать его на другом временном интервале.

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

Выводы

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

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

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

Happy coding.

Все мы знаем, что основное достоинство терминала Metatrader 4 – это возможность создания роботов и торговля с их помощью. 

Далеко не все трейдеры владеют программированием на mql4/5. Разработчики программы Forex Tester смогли решить эту проблему. Теперь можно создать робота или реализовать торговую идею в виде индикатора на ресурсе «Визуальный конструктор стратегий» (Visual Strategy Builder). Без навыков программирования.

Как это сделать, плюсы и минусы бесплатного (на текущий момент) конструктора роботов – в нашем материале.

Visual Strategy Builder — Автоматизируем Стратегии без программирования

Постоянные читатели нашего сайта знакомы с Forex Tester 4 – уникальным симулятором торгов, выступающим в роли тренажера, тестера и анализатора. Программа имела функцию написания на заказ индикаторов и советников, совместимых с Metatrader. Теперь разработчики платформы решили дать каждому трейдеру шанс самостоятельно создать собственные стратегии. 

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

Сервис Visual Strategy Builder расположен по адресу: https://tools.forextester.com/

Зачем нужен Visual Strategy Builder?

Visual Strategy Builder (VSB) представляет собой программную оболочку с набором инструментов теханализа, которые хорошо знакомы пользователям Metatrader 4. В отличие от этой торговой платформы VSB позволяет задавать в индикаторах правила открытия позиции и установки ордеров тейк-профит и стоп-лосс.

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

Например, можно получить реальный торговый результат по стратегии пересечений скользящих средних линий. Такой советник создается в VSB за 5 минут, потом его можно экспортировать в Metatrader 4 или Forex Tester.

Разобравшись с созданием одного индикатора в Visual Strategy Builder, трейдер может в несколько кликов создать рабочую автоматизированную стратегию и тоже проверить ее в тестере. Если советник показывает положительные результаты, то по его сигналам можно торговать на реальном счете через Metatrader 4.

Кстати, тестировать стратегию однозначно лучше через Forex Tester – там точнее котировки и можно быстро эмулировать сессию за любой торговый день. Например, выбрав какой-нибудь «черный вторник», чтобы воочию понять, как аномальная волатильность повлияет на настройки мани менеджмента.

Профессиональные трейдеры могут экспортировать код советников прямо в VSB, соединять его с созданными там программами или редактировать в Metaeditor и отправлять обратно в Metatrader 4.

Visual Strategy Builder будет полезен тем, кто ищет новые идеи – программа поддерживает библиотеку стратегий пользователей. Любой желающий без навыков программирования сразу поймет «внутренности алгоритма» по составу индикаторов и описанным правилам торговых сигналов. Тут же можно самостоятельно убедиться в результативности торговой системы.

Как работать на платформе Visual Strategy Builder

Сервис Visual Strategy Builder расположен по адресу: https://tools.forextester.com/

Использование VSB требует регистрации – это быстрая и несложная процедура. Введите адрес электронной почты и придумайте пароль для входа. Он должен быть не менее 8 символов, содержать буквы разного регистра, почтовый ящик лучше указать gmail.

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

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

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

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

  • Уровни (Ценность);
  • Конкретная цена;
  • Диапазон (цены открытия, закрытия, максимумы и минимумы свечей);
  • Открытие по дням недели, месяца (опция время);
  • По конкретному объему или его абстрактному критерию;
  • Таймфрейм.

Перечисленные опции можно объединять друг с другом, накладывая различные условия выполнения сделки, например, пробой уровня только до американской сессии и т. д. Определитесь с главным инструментом своей стратегии и перетащите его в окно «Состояние 1».

Как только он там отобразится, можно приступать к редактированию параметров. Наведите курсор на поле присоединенного Bollinger Bands (BB), чтобы увидеть эту опцию.

После ее нажатия появится таблица со стандартными настройками линий BB. Стоит отметить одну особенность: для каждой из них сигналы на открытие позиций прописываются отдельно.

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

Теперь конкретизируем тип позиции и условия мани менеджмента с помощью пункта меню на левой панели «Действия». Перетаскиваем в поле ордер на продажу и редактируем его параметры.

Пройдем по пунктам снизу вверх – индикатор на графике предусматривает многократное открытие позиции, поэтому ставим «Нет» на предложение открыть ордер один раз. Магический номер необходим, если открытие позиций по сигналам Bollinger Bands будет пересекаться с работающими на графике другими советниками.

Остальные опции понятны, следует только внимательно отнестись к пунктам: 25 – это для четырехзначной системы котировок. При пяти знаках умножайте значения пунктов на 10.

Для нижней линии Боллинджера потребуется создать второе правило, опцию можно выбрать слева вверху, повторив все вышеописанные операции со следующими изменениями:

  • В настройках индикатора выбираем нижнюю линию;
  • Кроссовер – снизу вверх;
  • Тип ордера, открываемого по рынку – «Покупка».

Полученную стратегию можно сохранить и экспортировать в ForexTester или Metatrader 4. В последнем случае используйте латинские буквы в названии файла. Стратегия сохраняется в файл ex4 в папку, выбранную трейдером.

Запустить написанный советник можно сразу после его установки в Metatrader 4. Это делается обычным способом, описанным на нашем сайте.

Заключение

Основной плюс Конструктора стратегий от Forex Tester – простота интерфейса и функциональность шаблонов. Это позволяет трейдеру частично автоматизировать стратегии или дополнить ручную торговлю дополнительными сигналами, которые приходилось искать визуально на графике.

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

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

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

С уважением, Алексей Вергунов
Tlap.com

Come to my page!

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

progr

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

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

1. Разработку форекс робота начинаем с идеи

Разработка робота для торговли на рынках – это непрерывный поиск идей и, как частное, поиск Святого Грааля. Вопреки утверждениям непросвещенных граждан, Грааль находится достаточно просто – скажем «спасибо» технологическому прогрессу. Попробуйте вбить в гугл «Святой Грааль Форекс» – скорее всего, советник вашей мечты находится на первой странице выдачи, к тому же абсолютно бесплатен.

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

Написать форекс советника

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

2. 4 важных вопроса перед разработкой

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

  • В чем состоит ваша торговая идея?
  • Какие задачи будет решать советник, и какое объективное преимущество это может дать в торговле?
  • Возможно, вам нужен графический индикатор или вспомогательный скрипт?
  • Насколько идея реализуема технически, и можете ли вы самостоятельно оценить ее сложность?

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

[info_block align=»right» linkText=»Форекс советники» linkUrl=»https://fortrader.org/forex-ea-testing/» imageUrl=»http://files.fortrader.org/uploads/2016/08/robot.jpg»]Исследование торговых роботов с возможностью скачать советника.[/info_block]

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

3. Конструктор советников – не проходим мимо!

У новичков на валютном рынке часто возникает множество вопросов, ответы на которые бывают не совсем очевидны, либо являются лишь вопросом неопытности оного. Будучи начинающим трейдером, достаточно прочитать одну книгу Билла Вильямса, чтобы на всю жизнь подменить понятие торгового хаоса торговым порядком. Затем такие люди удивляются, почему вот здесь да не купить, а вот здесь да не продать, и заказывают у программистов советник по пересечению двух скользящих средних. А ведь на начальных этапах обучения это вполне можно отдать на откуп готовым инструментам разработки. Одной из таких как раз является MQL5 Wizard – мастер по созданию советников для MetaTrader 5.

Для реализации простых вещей Мастер идеален. Вам не нужно обладать никакими магическими способностями – навыки программирования здесь не требуются. Несколько простых шагов, и советник готов. Уже готовый советник можно протестировать в том же терминале MetaTrader. Держите комбайн по производству простецких Граалей.

4. MQL5 Wizard. Сделать советник за 5 минут

Итак, для начала запускаем MetaTrader 5 и переходим в редактор приложений (кнопка F4). На верхнем меню жмем кнопку «Создать» и в появившимся диалоге выбираем генерацию нового советника.

Мастер советников MetaTrader

Дальше нужно указать название советника и основные входные параметры.

Мастер советников MetaTrader

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

Параметры сигналов советника

Собственно, все стандартные сигналы хорошо описаны в онлайн справке на официальном сайте. Там же можно скачать и кастомные.

Варианты торговых сигналов советника

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

параметры модуля сигналов

Обычный трейлинг стоп или закрытие сделки по значениям индикатора – на ваш выбор.

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

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

5. Хочу программировать непростого советника? Написание технического задания

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

Написание ТЗ для робота

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

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

6. Напишите мне советник по индикатору

Пример плохого задания для создания торгового советника – его отсутствие. Само намерение заказчика может быть и понятно: есть индикатор, нужно написать торгового робота, ну а дальше что?

[info_block align=»right» linkText=»Вникайте в процесс!»]»Старайтесь вникать в процесс работы и всего, что непосредственно касается реализации идеи вашего советника»[/info_block]

Как закрывать сделку? Как ее сопровождать? Какие выбирать объемы? Что делать с проскальзываниями и реквотами? Нужно понимать, что программист не обладает экстрасенсорными способностями. Если вы такого найдете, обязательно дайте знать, такие люди не должны пропадать.

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

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

7. Выбор исполнителя

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

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

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

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

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

[info_block align=»right» linkText=»Все в ваших руках»]»В ваших интересах контролировать процесс создания советника вашей мечты. Просите проводить промежуточные этапы работ, участвуйте в бета-тесте и не брезгуйте узнавать технические подробности.»[/info_block]

Один из типичных примеров – недопонимание форматов файлов и их значения. Вроде: «У меня тут есть .dll библиотека (.exe файл или .ex4 программа), не могли бы вы быстренько поправить то и то, а я вам за это копеечку закину? Нет? А почему? Василий мне сделал советника за 5 баксов, а вы плохой разработчик значит!».

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

8. Сдача советника – включайтесь в работу!

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

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

Принять участие в разработке своего робота

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

Если задача небольшая, исполнитель вполне может пойти на уступки, и исправить работу в соответствии с новым ТЗ. Если работа проделана масштабная – что-либо менять уже поздно. Частая проблема – тотальная отрешенность в процессе разработки, например, отказ от участия в бета-тестировании: «Зачем вы мне скинули советника с ошибками, я что за вас тестировать еще должен?» или: «Откуда я знаю, как этот ваш тестер запускать, я программист что-ли?».

[info_block align=»right» linkText=»Безопасный Мартингейл» linkUrl=»https://fortrader.org/learn/forex-trader/bezopasnyj-martingejl-kak-otdelnymi-elementami-martingejla-uvelichit-pribylnost-strategii.html» imageUrl=»http://files.fortrader.org/uploads/2016/07/coins-730×487.jpg»]Как отдельными элементами Мартингейла увеличить прибыльность стратегии?[/info_block]

Для адекватного человека такие заявления режут слух, исполнитель же впадает в депрессию. Разработка торгового робота – это, в первую очередь, работа. Если вы ищете готовое решение, существует магазин готовых советников, где советника за вас и протестируют, и вымоют до блеска, попутно документируя его историческую успешность. В таком случае, обращаться на фриланс-биржу не имеет смысла.

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

Заключение

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

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

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

Вам также будет интересно

  • Как из запаздывающего индикатора сделать работающий Грааль?
  • ECN или MTF – выбираем агрегатор ликвидности
  • Сетка ордеров на форекс: 2 варианта популярной стратегии

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

Начнём с создания шаблона советника(как это сделать мы разбирали на уроке №10).

Опишем входные параметры эксперта:

extern double Lots       = 0.1;    // объём сделки в лотах
extern int    StopLoss   = 40;     // ограничение убытка в пунктах 
extern int    TakeProfit = 70;     // профит в пунктах
extern int    Magic      = 123;    // магический номер ордеров
extern int    Slippage   = 5;      // проскальзывание в пунктах
extern string comment    = "Мой первый советник";

Значения StopLoss, TakeProfit и Slippage, присвоенные в параметрах, указаны для 4-х значного брокера, соответственно, чтобы эти же значения корректно работали у 5-ти значного ДЦ их нужно умножить на 10, что мы и сделаем в функции OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   if (Digits == 3 || Digits == 5)
   {
       StopLoss     *= 10;
       TakeProfit   *= 10;
       Slippage     *= 10;
   }
   return(INIT_SUCCEEDED);
}

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

Параметры индикатора "MyIndicator"

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

//+------------------------------------------------------------------+
extern double Lots            = 0.1; // объём сделки в лотах
extern int    StopLoss        = 40;  // ограничение убытка в пунктах
extern int    TakeProfit      = 70;  // профит в пунктах
extern int    Magic           = 123; // магический номер ордеров
extern int    Slippage        = 5;   // проскальзывание в пунктах
extern string comment         = "Мой первый советник";
//+------------------------------------------------------------------+
extern int    MA_Period       = 100;
extern double MACDOpenLevel   = 5;
extern int    fast_ema_period = 8;   // период быстрой средней 
extern int    slow_ema_period = 16;  // период медленной средней 
extern int    signal_period   = 9;   // период сигнальной линии 
//+------------------------------------------------------------------+

Переходим к функции OnTick и пишем заготовку обработчика, где описываю алгоритм работы эксперта:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if (CountBuy() == 0)
    {
       BuySignal = ...
       
       if (BuySignal > 0)
       {
           CloseSell();

           // Open buy order 
           
       }
    }

    if (CountSell() == 0)
    {
       SellSignal = ...

       if (SellSignal > 0)
       {
           CloseBuy();

           // Open sell order
           
       }
    }
}

Итак, по шагам:

  1. if (CountBuy() == 0) — я проверяю есть-ли у меня ордера на покупку, находящиеся в рынке, и если нет, то шаг 2.
  2. BuySignal = … — проверяем наличие сигнала на покупку по индикатору, кстати, это будет переменная, которую необходимо объявить заранее.
  3. if (BuySignal > 0) — если есть сигнал на покупку, то переходим к шагу 4.
  4. CloseSell() — закрываем открытые ордера на продажу, если они есть и переходим к шагу 5.
  5. Открываем ордер на покупку.

Точно такой же блок делается и для продаж.

Исходя из написанного алгоритма сразу видно, что предварительно нам потребуется несколько функций: CountBuy(), CountSell(), CloseBuy(), CloseSell(), а также написать код получения сигнала с индикатора.

Объявим в коде несколько переменных для дальнейшей работы и необходимые функции:

double BuySignal, SellSignal,
       SL, TP;
int    ticket;
bool   res;

Функция CountBuy():

//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на покупку                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountBuy() 
{  // объявляем переменную, в которой будем хранить количество ордеров
   // с типом OP_BUY 
   int count = 0;

   // Объявляем цикл с перебором ордеров   
   for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
   {
      // Если удалось выбрать ордер, находящийся в рынке
      if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
      {
         // у этого ордера совпадает валютная пара и его магический номер
         if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
         {
            // а также ордер является ордером на покупку,
            if (OrderType() == OP_BUY) 
            // то увеличиваем значение счётчика count на единицу
               count++;
         }
      }
   }
   // возвращаем количество ордеров на покупку
   return (count);
}

Аналогично пишется функция CountSell(), разве что проверяется тип ордера OP_SELL:

//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на продажу                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountSell() 
{
   int count = 0;
   
   for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
   {
      if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
      {
         if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
         {
            if (OrderType() == OP_SELL) 
               count++;
         }
      }
   }
   return (count);
}
//+------------------------------------------------------------------+

Далее пишем функцию CloseBuy():

//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на покупку, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseBuy()
{
   // объявляем цикл, в котором выполним перебор все ордеров
   // находящихся в рынке
   for(int index = OrdersTotal()-1; index >= 0; index--)
   {
      // если удалось выбрать рыночный ордер
      if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
      {
            // если валютная пара ордера, магический номер ордера и тип ордера совпадают
            // с тем, что нам необходимо,
            if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY)
                 // то пробуем закрыть ордер по рыночной цене
                  if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
                     // иначе выводим в журнал регистрации сообщение об ошибке
                     Print("Ошибка закрытия ордера на покупку, ticket = " + DoubleToStr(OrderTicket()));
      }
   }
}

Пишем практически всё то же самое и для закрытия ордеров на продажу, разница лишь в типе проверяемого ордера и цене закрытия, функция CloseSell():

//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на продажу, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseSell()
{
   for(int index = OrdersTotal()-1; index >= 0; index--)
   {
      if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
      {
            if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL)
                  if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
                     Print("Ошибка закрытия ордера на продажу, ticket = " + DoubleToStr(OrderTicket()));
      }
   }
}

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

Для получения сигнала от любого пользовательского индикатора используется функция iCustom, обратившись к справке (F1) видим формат вызова функции:

iCustom

Теперь мы можем с легкостью получить сигнал на покупку в нашем советнике:

BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);

заодно и сигнал на продажу:

SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);

кстати, обратите внимание на предпоследний параметр mode, который мы передаём в функцию iCustom — это номер буфера индикатора, где хранятся сигналы покупок и продаж.

Таким образом наша функция OnTick() будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if (CountBuy() == 0)
    {
       // Получаем сигнал на покупку
       BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);
       
       // если есть сигнал индикатора
       if (BuySignal > 0)
       {
           // закрываем открытые ордера SELL
           CloseSell();
           
           // Open buy order
           // открываем ордер на покупку
           ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, comment, Magic, 0, Blue);

           // если ордер был успешно открыт,
           if (ticket > 0)
           {
               // то его необходимо выбрать, для дальнейшей установки ему StopLoss и TakeProfit
               if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
               {
                  // рассчитываем StopLoss и нормализуем цену
                  SL = NormalizeDouble(Ask-StopLoss*Point, Digits);

                  // рассчитываем TakeProfit и нормализуем цену
                  TP = NormalizeDouble(Ask+TakeProfit*Point, Digits);
                  
                  // попытаемся установить ордеру рассчитанные уровни StopLoss и TakeProfit
                  res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
                  if (!res)
                     // и если не удалось, то выведем сообщение в журнал регистрации для дальнейшего анализа причин
                     Print("Ошибка модификации ордера на покупку, ASK=" + DoubleToStr(Ask) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
               }
           }
       }
    }

    if (CountSell() == 0)
    {
       SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);

       if (SellSignal > 0)
       {
           CloseBuy();
           
           ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, comment, Magic, 0, Red);
           if (ticket > 0)
           {
               if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
               {
                  SL = NormalizeDouble(Bid+StopLoss*Point, Digits);
                  TP = NormalizeDouble(Bid-TakeProfit*Point, Digits);
                  res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
                  if (!res)
                     Print("Ошибка модификации ордера на продажу, BID=" + DoubleToStr(Bid) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
               }
           }
       }
    }
}

Собственно говоря, на этом всё, разработка советника на пользовательском индикаторе завершена.

Полный код эксперта:

//+------------------------------------------------------------------+
//|                                                    My_Expert.mq4 |
//|                              Copyright 2017, xbms, http://mql.su |
//|                                              mailto:xbms@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, xbms"
#property link      "http://mql.su"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
extern double     Lots            = 0.1;
extern int        StopLoss        = 40;
extern int        TakeProfit      = 70;
extern int        Magic           = 123;
extern int        Slippage        = 5;
extern string     comment         = "Мой первый советник";
//+------------------------------------------------------------------+
extern int        MA_Period       = 100;
extern double     MACDOpenLevel   = 5;
extern int        fast_ema_period = 8;   // период быстрой средней 
extern int        slow_ema_period = 16;  // период медленной средней 
extern int        signal_period   = 9;   // период сигнальной линии 
//+------------------------------------------------------------------+
double BuySignal, SellSignal,
SL, TP;
int    ticket;
bool   res;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
if (Digits == 3 || Digits == 5)
{
StopLoss     *= 10;
TakeProfit   *= 10;
Slippage     *= 10;
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
if (CountBuy() == 0)
{
// Получаем сигнал на покупку
BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);
// если есть сигнал индикатора
if (BuySignal > 0)
{
// закрываем открытые ордера SELL
CloseSell();
// Open buy order
// открываем ордер на покупку
ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, comment, Magic, 0, Blue);
// если ордер был успешно открыт,
if (ticket > 0)
{
// то его необходимо выбрать, для дальнейшей установки ему StopLoss и TakeProfit
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
// рассчитываем StopLoss и нормализуем цену
SL = NormalizeDouble(Ask-StopLoss*Point, Digits);
// рассчитываем TakeProfit и нормализуем цену
TP = NormalizeDouble(Ask+TakeProfit*Point, Digits);
// попытаемся установить ордеру рассчитанные уровни StopLoss и TakeProfit
res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
if (!res)
// и если не удалось, то выведем сообщение в журнал регистрации для дальнейшего анализа причин
Print("Ошибка модификации ордера на покупку, ASK=" + DoubleToStr(Ask) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
}
}
}
}
if (CountSell() == 0)
{
SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);
if (SellSignal > 0)
{
CloseBuy();
ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, comment, Magic, 0, Red);
if (ticket > 0)
{
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
SL = NormalizeDouble(Bid+StopLoss*Point, Digits);
TP = NormalizeDouble(Bid-TakeProfit*Point, Digits);
res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
if (!res)
Print("Ошибка модификации ордера на продажу, BID=" + DoubleToStr(Bid) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
}
}
}
}
}
//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на покупку, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseBuy()
{
// объявляем цикл, в котором выполним перебор все ордеров
// находящихся в рынке
for(int index = OrdersTotal()-1; index >= 0; index--)
{
// если удалось выбрать рыночный ордер
if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
{
// если валютная пара ордера, магический номер ордера и тип ордера совпадают
// с тем, что нам необходимо,
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY)
// то пробуем закрыть ордер по рыночной цене
if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
// иначе выводим в журнал регистрации сообщение об ошибке
Print("Ошибка закрытия ордера на покупку, ticket = " + DoubleToStr(OrderTicket()));
}
}
} 
//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на продажу, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseSell()
{
for(int index = OrdersTotal()-1; index >= 0; index--)
{
if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL)
if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
Print("Ошибка закрытия ордера на продажу, ticket = " + DoubleToStr(OrderTicket()));
}
}
} 
//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на покупку                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountBuy() 
{  // объявляем переменную, в которой будем хранить количество ордеров
// с типом OP_BUY 
int count = 0;
// Объявляем цикл с перебором ордеров   
for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
{
// Если удалось выбрать ордер, находящийся в рынке
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
{
// у этого ордера совпадает валютная пара и его магический номер
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
{
// а также ордер является ордером на покупку,
if (OrderType() == OP_BUY) 
// то увеличиваем значение счётчика count на единицу
count++;
}
}
}
// возвращаем количество ордеров на покупку
return (count);
}
//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на продажу                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountSell() 
{
int count = 0;
for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
{
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
{
if (OrderType() == OP_SELL) 
count++;
}
}
}
return (count);
}
//+------------------------------------------------------------------+

Исходный код советника доступен для скачивания My_Expert.mq4

Как написать Форекс советник самому? Таким вопросом, скорее всего, задаются многие трейдеры желающие освоить создание торговых роботов, помогающих торговать на валютном рынке.

И если честно, сделать это по сути не сложно, при том, что преимущества написания собственного робота очевидны:

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

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

Итак, какой же совет дают начинающим создателям роботов опытные трейдеры?

пишем советник по базовым индикаторам

Как написать Форекс советник самому? Выбираем стратегию для старта

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

ВЫБИРАЙТЕ ЛУЧШИХ! ЭТИХ БРОКЕРОВ МЫ ПРОТЕСТИРОВАЛИ ДЛЯ ВАС:

Если Вы задумались над тем, как написать Форекс советник самому, то специалисты дают такие советы:

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

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

Поэтому тем, кто задумался над вопросом: «Как написать Форекс советник самому?», специалисты дают еще один совет – эффективнее всего создавать роботов, используя уже опробованные и доказавшие свою надежность высокую надежность стратегии торговли, которые неоднократно были проверены во время ручной торговли в течение долгого времени.

Как написать Форекс советник используя программирование в MQL4 и MQL5?

Чтобы написать Форекс советник самому и в дальнейшем использовать его бесплатно, Вам необходимо обладать базовыми знаниями языка программирования. К таким языкам относятся MQL4 и MQL5.

Версия MQL4 — является встроенным языком программирования, разработанным «MetaQuotes Software Corp». Кстати, на основе данного языка и создается большинство торгово-информационных терминалов. С помощью этого языка можно писать собственных экспертов (Expert Advisors), которые автоматизируют процесс торговли и наилучшим образом подходят для реализации своих стратегий. Помимо этого при помощи MQL4, Вы можете создавать «под себя» библиотеки функций, различные скрипты и технические индикаторы.

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

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

написание советника базово, по индикаторам и бесплатно

MQL5, также является языком программирования, только более современным и более высокого уровня. Синтаксис данного языка, имеет максимальное приближение к «С++», что дает возможность создавать программное обеспечение в стиле ООП (объектно-ориентированного программирования).

Для написания советников посредством MQL5, Вам также необходимо использовать среду разработки «MetaEditor», включающую в себя отладку, автозавершение, сниппеты, профилактику, MQL5 Storage (встроенное версионное хранилище) и многое другое. Массу вспомогательной информации, касающейся, как непосредственно языка, так и программирования на нем, можно найти на сайте mql5.

Также, для тех, кто задается вопросом, как написать Форекс советник самому, во многом поможет справочный материал, размещаемый непосредственно в торговом терминале, вызываемый нажатием клавиши «F1», а также справки редактора «MetaQuotes Language». Для их вызова в терминале нажмите клавишу «F4», а затем «F1».

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

Упростить для себя задачу написания советника можно несколькими способами. К примеру, использованием уже готовых блоков (библиотек функций), которые уже были созданы другими более опытными трейдерами или программистами. Еще один способ, это применение специализированного программного обеспечения. Здесь используются альтернативные программы, которые упрощают непосредственно процесс написания роботов, что-то вроде Мастера настроек. Такие программы, помогающие создавать Форекс советник самому, даже без знания основ языков программирования,  также можно скачать в Интернете бесплатно.

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

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

«А будет ли Ваш советник работать правильно?». Ведь торговые терминалы не оснащены ни отладчиками программ, ни баггерами (debugger).

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

напиши советник сам, с Forex индикаторами

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

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

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

ОБЯЗАТЕЛЬНО ПОСМОТРИТЕ:
Как написать Forex советник на основе индикатора?

В этом руководстве по MQL4 вы научитесь программировать свой собственный торговый советник в MetaEditor.

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

  • int — это обычное число. Например: 1, 15, 521.
  • double — число с десятичным знаком. Например: 1,154, 0,0144, 255,145.
  • string — строка, слово, фраза. Например: «Ордер на покупку», «Ордер на продажу успешно размещен».
  • bool — принимает значения либо false, либо true.

Скоро вы увидите, как их можно использовать.

Торговый советник три белых солдата

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

Мы добавим TakeProfit, StopLoss, LotSize, Slippage и MagicNumber. Все они являются типом int, кроме LotSize. LotSize должен иметь тип double.

мой торговый советник

Затем нажимаем «Далее» и «Готово». Мы видим, что MetaEditor сгенерировал файл с вашими предопределенными переменными.

Вы можете увидеть 3 области. Области OnInit(), OnDeinit() и OnTick(). Весь код, который вы пишете, попадает в одну из этих областей.

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

Если вы скомпилируете советника с помощью F7 и вернетесь в Metatrader, вы сможете найти советника в «Навигаторе» MetaTrader (Crtl + N). Если вы попытаетесь присоединить его к графику и перейдете на вкладку «Входные параметры», вы сможете увидеть наши предопределенные переменные.

торговый советник: входные параметры

Мы научимся программировать советника, который торгует по паттерну 3 белых солдата. Это простая стратегия, когда советник открывает сделку на покупку, когда последние 3 свечи были бычьими. Давайте начнем!

Добавим следующие строчки кода:

void OnTick() { 
if(Close[1] &gt; Open[1] &amp;&amp; Close[2] &gt; Open[2] &amp;&amp; Close[3] &gt; Open[3]) { //Ордер на покупку } 
}

Для этой стратегии мы используем предопределенную переменную Close[], который является типом double. Close[] дает нам цену закрытия (например, 1.24577) для каждого свечи текущего графика. Close[0] означает текущую цену закрытия. Close[1] — предыдущую цену, и так далее. То же самое справедливо для предопределенных переменных Open[], Low[] и High[].

//Ордер на покупку — это подсказка для нас, что мы создали здесь функцию открытия ордера на покупку. Две обратных черты (//) означают, что эта строка закомментирована. Советник будет игнорировать ее при компиляции.

Если вы поместите курсор на предопределенную функцию или переменную и нажмете F1, откроется справка по MQL4, где вы сможете прочитать, что делает эта данная функция или переменная.

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

if(Close[1] > Open[1] && Close[2] > Open[2] && Close[3] > Open[3])
      {
         //Ордер на покупку
         OrderSend(_Symbol,OP_BUY,LotSize,Ask,Slippage,Ask-StopLoss*_Point,Ask+TakeProfit*_Point,"Покупка",MagicNumber);
      }

Что из себя представляет функция OrderSend?

  • _Symbol — возвращает текущий торговый инструмент, к которому прикреплен советник.
  • OP_BUY — мы хотим открыть ордер на покупку.
  • LotSize — размер лота, который мы определили в самом начале.
  • Ask — текущая цена Ask, по которой открываются ордера на покупку.
  • Slippage — размер проскальзывания.
  • StopLoss — это наш StopLoss. Или цена Ask за вычетом цены StopLoss. Запомните, что StopLoss — это целое число (500 пунктов), а Ask — это цена (например, 1.20521). Теперь, чтобы вычесть стоп-лосс из цены Ask, мы должны умножить его на 0,00001 или 0,001. Мы делаем это с помощью предопределенной переменной _Point, которая отражает текущие значением пунктов торгового инструмента.
  • TakeProfit — то же самое, как и StopLoss.
  • Комментарий — комментарий, который должен появиться в журнале, когда торговый советник разместит наш ордер.
  • MagicNumer — магическое число, которое было определено нами вначале.

Нажмите F7, чтобы скомпилировать советник и проверить результат его работы в тестере стратегий. Вы получите что-то вроде этого:

торговый советник открывает ордера на каждом тике

Наш торговый советник выставляет ордера на покупку на каждом тике, если 3 последних бара были бычьими. Это не то что мы хотим, но, по крайней мере, что-то происходит.

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

Давайте перейдем к концу нашего файла и запрограммируем новую функцию под названием TotalOpenOrders. Эта функция будет иметь тип int. Она будет возвращать количество открытых ордеров.

int TotalOpenOrders()
{
   int total_orders = 0;
 
   for(int order = 0; order < OrdersTotal(); order++) 
   {
      if(OrderSelect(order,SELECT_BY_POS,MODE_TRADES)==false) break;
 
      if(OrderMagicNumber() == MagicNumber && OrderSymbol() == _Symbol)
         {
            total_orders++;
         }
   }
 
   return(total_orders);
}

Затем добавим данную функцию в область OnTick и добавим проверку на количество открытых ордеров. Если общее количество сделок равно 0, наш торговый советник продолжает свою работу с кодом внутри скобок.

   if(TotalOpenOrders() == 0)
   { 
      if(Close[1] > Open[1] && Close[2] > Open[2] && Close[3] > Open[3])
      {
         //Ордер на покупку         
OrderSend(_Symbol,OP_BUY,LotSize,Ask,Slippage,Ask-StopLoss*_Point,Ask+TakeProfit*_Point),"Покупка",MagicNumber);
      }
   }

торговый советник открывает только один ордер

Что из себя представляет функция TotalOpenOrders?

В этой функции у нас есть счетчик (total_orders), который в конце возвращает общее количество открытых ордеров. Также у нас есть цикл for, который перебирает все открытые ордера. Если советник найдет ордер, соответствующий вашему символу и MagicNumer, счетчик увеличится на 1 (это записывается с помощью ++).

Теперь у нас все еще есть проблема. Если свеча слишком длинная, советник открывает вторую сделку на покупку непосредственно после того, как первая сделка на покупку закрывается с помощью StopLoss или TakeProfit. Мы должны добавить вторую функцию «IsNewBar».

Эта функция будет представлять тип bool. Она вернет true, если в Metatrader сгенерирована новая свеча, иначе она вернет false.

Давайте поместим эту функцию прямо над функцией TotalOpenOrder и вызовем ее в области OnTick вместе с TotalOpenOrders().

void OnTick()
  {
//---
 
   if(TotalOpenOrders() == 0 && IsNewBar() == true)
   { 
      if(Close[1] > Open[1] && Close[2] > Open[2] && Close[3] > Open[3])
      {
         //Ордер на покупку
         OrderSend(_Symbol,OP_BUY,LotSize,Ask,Slippage,Ask-StopLoss*_Point,Ask+TakeProfit*_Point,"Покупка",MagicNumber);
      }
   }
 
  }
//+------------------------------------------------------------------+
 
//Проверка новой свечи
bool IsNewBar()   
{        
      static datetime RegBarTime=0;
      datetime ThisBarTime = Time[0];
 
      if (ThisBarTime == RegBarTime)
      {
         return(false);
      }
      else
      {
         RegBarTime = ThisBarTime;
         return(true);
      }
}   
 
// Возвращаем количество открытых ордеров
int TotalOpenOrders()
{
   int total_orders = 0;
 
   for(int order = 0; order < OrdersTotal(); order++) 
   {
      if(OrderSelect(order,SELECT_BY_POS,MODE_TRADES)==false) break;
 
      if(OrderMagicNumber() == MagicNumber && OrderSymbol() == _Symbol)
         {
            total_orders++;
         }
   }
 
   return(total_orders);
}

4-5 или 2-3 значные котировки

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

Сейчас мы используем предопределенную переменную _Point для преобразования TakeProfit и StopLoss. Но мы хотим создать функцию, которая предоставит нам одинаковое значение для 4 и 5 и соответственно 2-х и 3-х торговых инструментов. Для начала мы создаем глобальные переменные MyPoint и MySlippage. Поместим их прямо под нашими входными параметрами:

//--- глобальные переменные
double MyPoint;
int    MySlippage;

Теперь мы создаем функцию, которая сохраняет правильное значение в этих 2 переменных (MyPoint и MySlippage) и помещаем их в наши пользовательские функции:

//Получаем My Points   
double MyPoint()
{
   double CalcPoint = 0;
 
   if(_Digits == 2 || _Digits == 3) CalcPoint = 0.01;
   else if(_Digits == 4 || _Digits == 5) CalcPoint = 0.0001;
 
   return(CalcPoint);
}
 
 
//Получаем My Slippage
double MySlippage()
{
   double CalcSlippage = 0;
 
   if(_Digits == 2 || _Digits == 4) CalcSlippage = Slippage;
   else if(_Digits == 3 || _Digits == 5) CalcSlippage = Slippage * 10;
 
   return(CalcSlippage);
}

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

int OnInit()
  {
//---
   MyPoint = MyPoint();
   MySlippage = MySlippage();
 
//---
   return(INIT_SUCCEEDED);
  }

Теперь мы можем возвратить наши TakeProfit, StopLoss и Slippage к номальным значениям:

//--- входные параметры
input int      TakeProfit=50;
input int      StopLoss=50;
input double   LotSize=0.1;
input int      Slippage=3;
input int      MagicNumber=5555;

Далее мы заменяем все Slippage и _Point в функциях OnTick нашими 2 новыми глобальными переменными MyPoint и MySlippage. Таким образом, функция OrderSend будут выглядеть следующим образом:

//Ордер на покупку
OrderSend(_Symbol,OP_BUY,LotSize,Ask,MySlippage,Ask-StopLoss*MyPoint,Ask+TakeProfit*MyPoint,"Ордер на покупку",MagicNumber);

Торговый советник и ECN брокеры

На ECN брокерах мы не можем отправить ордер с TakeProfit или StopLoss. Сначала нам придется отправить наш ордер без них, а затем изменить его.

Сначала мы сохраняем номер тикета из только что открытого ордера в переменной int ticket. Затем мы проверяем, получен ли мы данный ticket от нашего брокера. Далее мы вызываем функцию OrderModify. Функция OrderModify возвращает true или false, которые мы сохраняем в переменной bool res. Мы проверяем результат с помощью if (! Res) (так же, как if (res == false)) и выводим соответствующее сообщение.

   if(TotalOpenOrders() == 0 && IsNewBar() == true)
   { 
      // Buy Logic
      if(Close[1] > Open[1] && Close[2] > Open[2] && Close[3] > Open[3])
      {
         //Ордер на покупку
         int ticket = OrderSend(_Symbol,OP_BUY,LotSize,Ask,MySlippage,0,0,"Ордер на покупку",MagicNumber);
            if(ticket<0)
            {
               Print("Ошибка #",GetLastError());
            }
            else
            {
               Print("Ордер размещен успешно");
            }
 
         // Изменяем наш ордер
         bool res = OrderModify(ticket,OrderOpenPrice(),Ask-StopLoss*MyPoint,Ask+TakeProfit*MyPoint,0,Blue);
            if(!res)
            {
               Print("Ошибка=",GetLastError());
            }
            else
            {
               Print("Ордер изменем успешно.");
            }
      }

Теперь наш код будет работать на всех ECN и не ECN брокерах.

Очистим наш код

Наш исходный код выглядит сейчас слишком сложным. Но мы можем его упростить. Для этого мы помещаем торговую логику и OpenOrder / ModifyOrder в пользовательскую функцию и вызываем ее в области OnTick().

   if(TotalOpenOrders() == 0 && IsNewBar() == true)
   { 
      // Проверяем параметры на вход
      if(BuySignal() == true)
         {
            OpenBuy();
         }
         }

Добавляем индикаторы

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

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

//--- индикаторы
double RSI;
double MA;

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

//Инициализируем индикаторы.
void InitIndicators()
{
   // Индикатор RSI
   RSI = iRSI(_Symbol,PERIOD_CURRENT,14,PRICE_CLOSE,1);
 
   // Скользящая средняя
   MA = iMA(_Symbol,PERIOD_CURRENT,200,0,MODE_SMA,PRICE_CLOSE,1);
}

Мы вызываем функции iRSI и iMA и сохраняем эти цены в переменных. В обоих функциях мы сначала используем текущий символ и таймфрейм, для которых мы хотим получить показания индикаторов. В нашем случае это символ и таймфрейм (период), к которому мы прикрепляем советник.

Для RSI мы хотим, чтобы период 14 рассчитывался по цене закрытия. Скользящая средняя должна быть периодом 200, Сдвиг 0. Что такое 1 в конце? Здесь мы определяем, от какой свечи мы хотим получать эти значения. 0 означает текущую свечу, 1 — предыдущую и т. д.

Чтобы эта функция работала, мы должны вызывать ее в разделе OnTick:

   if(TotalOpenOrders() == 0 && IsNewBar() == true)
   { 
      // Инициализируем индикаторы
      InitIndicators();
 
      // Сигнал на вход
      if(BuySignal() == true)
         {
            OpenBuy();
         }
   }

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

// Вход на покупку
bool BuySignal()
{
   if(RSI <= 30 && Low[1] >= MA)
   {
      return(true);
   }
   else
   {
      return(false);
   }
}

Изменяем параметры индикаторов

На данный момент, если мы хотим изменить, скажем, период RSI от 14 до 20, мы должны сделать это в исходном коде. Теперь я покажу вам, как вы можете изменить все параметры индикаторов как обычный входной параметр в Свойствах эксперта. Для этого мы пишем:

//--- входные параметры
input int            TakeProfit=50;
input int            StopLoss=50;
input double         LotSize=0.1;
input int            Slippage=3;
input int            MagicNumber=5555;
 
//--- индикаторы
sinput string        indi = "";                // ------ Индикаторы -----  
input int            RSI_Period = 14;          // RSI период
input int            RSI_Level  = 30;          // Значение RSI
input int            MA_Period  = 200;         // MA период
input ENUM_MA_METHOD MA_Method  = MODE_SMA;    // MA метод

Теперь мы должны связать эти параметры с нашими индикаторами и торговой логикой. Для этого мы изменим наш код на следующий:

 // индикатор RSI
   RSI = iRSI(_Symbol,PERIOD_CURRENT,RSI_Period,PRICE_CLOSE,1);
 
   // Скользящая средняя
   MA = iMA(_Symbol,PERIOD_CURRENT,MA_Period,0,MA_Method,PRICE_CLOSE,1);
   if(RSI &lt;= RSI_Level &amp;&amp; Low[1] &gt;= MA)

Вот полный код нашего первого торгового советника:

//+------------------------------------------------------------------+
//|                                                  My First EA.mq4 |
//|                                     
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "https://traderblog.net/"
#property link      "https://traderblog.net/"
#property strict
 
//--- входные параметры
input int            TakeProfit=50;
input int            StopLoss=50;
input double         LotSize=0.1;
input int            Slippage=3;
input int            MagicNumber=5555;
 
//--- индикаторы
sinput string        indi = "";                // ------ Индикаторы -----  
input int            RSI_Period = 14;          // RSI период
input int            RSI_Level  = 30;          // Значение RSI
input int            MA_Period  = 200;         // MA период
input ENUM_MA_METHOD MA_Method  = MODE_SMA;    // MA метод
 
//--- глобальные переменные
double MyPoint;
int    MySlippage;
 
//--- индикаторы
double RSI;
double MA;
 
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   MyPoint = MyPoint();
   MySlippage = MySlippage();
 
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
 
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
 
   if(TotalOpenOrders() == 0 && IsNewBar() == true)
   { 
      // Инициализиуем индикаторы
      InitIndicators();
 
      // Проверяем ордер на покупку
      if(BuySignal() == true)
         {
            OpenBuy();
         }
 
   }
 
  }
//+------------------------------------------------------------------+
//| Пользовательские функции                                           
//+------------------------------------------------------------------+
 
 
// Инициализируем индикаторы
void InitIndicators()
{
   // индикатор RSI
   RSI = iRSI(_Symbol,PERIOD_CURRENT,RSI_Period,PRICE_CLOSE,1);
 
   // Скользящая средняя
   MA = iMA(_Symbol,PERIOD_CURRENT,MA_Period,0,MA_Method,PRICE_CLOSE,1);
}
 
 
// Логика на покупку
bool BuySignal()
{
   if(RSI <= RSI_Level && Low[1] >= MA)
   {
      return(true);
   }
   else
   {
      return(false);
   }
} 
 
// Размещаем ордер на покупку
void OpenBuy()
{
   // Open Buy Order
   int ticket = OrderSend(_Symbol,OP_BUY,LotSize,Ask,MySlippage,0,0,"Ордер на покупку",MagicNumber);
 
      if(ticket<0)
      {
         Print("Ошибка",GetLastError());
      }
      else
      {
         Print("Ордер успешно открыт");
      }
 
   // Modify Buy Order
   bool res = OrderModify(ticket,OrderOpenPrice(),Ask-StopLoss*MyPoint,Ask+TakeProfit*MyPoint,0);
 
      if(!res)
      {
         Print("Ошибка",GetLastError());
      }
      else
      {
         Print("Ордер успешно изменен.");
      }
}
 
 
// Рассчитываем котировки 
double MyPoint()
{
   double CalcPoint = 0;
 
   if(_Digits == 2 || _Digits == 3) CalcPoint = 0.01;
   else if(_Digits == 4 || _Digits == 5) CalcPoint = 0.0001;
 
   return(CalcPoint);
}
 
 
// Рассчитываем проскальзывание
int MySlippage()
{
   int CalcSlippage = 0;
 
   if(_Digits == 2 || _Digits == 4) CalcSlippage = Slippage;
   else if(_Digits == 3 || _Digits == 5) CalcSlippage = Slippage * 10;
 
   return(CalcSlippage);
}
 
 
// Проворяем свечу
bool IsNewBar()   
{        
   static datetime RegBarTime=0;
   datetime ThisBarTime = Time[0];
 
   if (ThisBarTime == RegBarTime)
   {
      return(false);
   }
   else
   {
      RegBarTime = ThisBarTime;
      return(true);
   }
}   
 
 
// Возвращаем количество открытых ордеров
int TotalOpenOrders()
{
   int total_orders = 0;
 
   for(int order = 0; order < OrdersTotal(); order++) 
   {
      if(OrderSelect(order,SELECT_BY_POS,MODE_TRADES)==false) break;
 
      if(OrderMagicNumber() == MagicNumber && OrderSymbol() == _Symbol)
         {
            total_orders++;
         }
   }
 
   return(total_orders);
}

Понравилась статья? Поделить с друзьями:
  • Как написать советника для mt4 для автоматической торговли
  • Как написать советник для metatrader 5
  • Как написать сова никогда не спит чтобы забанили vk
  • Как написать собственный учебник
  • Как написать собственный троян