Написать советник на mql4 пошаговая инструкция

Если вы задавались вопросом «Как написать советник на языке программирования 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.

В этом руководстве по 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);
}

Всем привет!

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

MQL4 — пишем Советник для торговли Паттернов

Что будем писать?

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

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

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

Конструкция нашего советника

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

Поэтому разделим тело советника на несколько частей. Первая часть – фильтр дня недели:

bool TimeFilter(){

if(!MondayTrade&&DayOfWeek()==1) return(false);

if(!TuesdayTrade&&DayOfWeek()==2) return(false);

if(!WednesdayTrade&&DayOfWeek()==3) return(false);

if(!ThursdayTrade&&DayOfWeek()==4) return(false);

if(!FridayTrade&&DayOfWeek()==5) return(false);

return(true);

}

Он будет возвращать false, если текущий день недели запрещен в настройках. Второй блок генерирует сигнал:

int Signal(){

if (!TimeFilter()) return -5;

double Close1 = iClose(_Symbol, signal_period, 1);

double Open1 = iOpen(_Symbol, signal_period, 1);

double Close2 = iClose(_Symbol, signal_period, 2);

double Open2 = iOpen(_Symbol, signal_period, 2);

if (Close2 > Open2 && Close1 < Open1) {

return OP_SELL;

}

if (Close2 < Open2 && Close1 > Open1) {

return OP_BUY;

}

return -5;

}

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

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

bool ExitTime(int dir){

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

if (!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) {

continue;

}

if (OrderSymbol() != Symbol() || OrderMagicNumber() != magic) {

continue;

}

if (TimeCurrent() — OrderOpenTime() > 60 * exit_bars * exit_period) {

return(true);

}

}

return(false);

}

Вот, собственно, и все. Полный код советника вы можете увидеть в видеоуроке.

Результаты

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

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

Заключение

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

Скачать исходник советника из урока

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

С уважением, Дмитрий аkа Silentspec
Tlap.com

Простой эксперт

В этом параграфе рассматриваются принципы построения простого торгующего эксперта.

Задача 29. Создать торгующий эксперт.

Предварительные рассуждения

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

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

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

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

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

Структура простого эксперта

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


Рис. 109. Структурная схема простого эксперта.

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

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

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

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

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

После того, как необходимые ордера закрыты, управление передаётся в блок вычисления
размера новых ордеров. Существует множество алгоритмов для вычисления объема
ордера. Самый простой из них — постоянный, фиксированный лот.
Этот алгоритм удобно включать в программу для тестирования стратегии. Более распространённый
способ определения размера ордера состоит в том, что количество лотов ставится
в зависимость от суммы свободных средств, например, может составлять 30-40%. Если
средств на счёте недостаточно, то программа заканчивает работу, предварительно
уведомив пользователя о причине.

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

В эксперте предусмотрен также анализ ошибок. Если какая-либо торговая операция закончилась
неудачей, то (только в этом случае) управление передаётся в блок обработки ошибок.
Если ошибка, возвращённая сервером или клиентским терминалом, не является непреодолимой,
то производится повторная попытка осуществить торговую операцию. Если же возвращена
критическая ошибка (например, заблокирован счёт), то эксперт должен прекратить
работу. Здесь необходимо напомнить, что в MQL4 не предусмотрено возможности программного
завершения работы экспертов в окне финансового инструмента (в отличие от скриптов,
см. Специальные функции). Всё, что программно можно сделать в эксперте — это завершить исполнение специальной
функции start(). При новом запуске функции start() на новом тике можно проанализировать
значение некоторой переменной — флага, запрещающего торговлю (в данном случае поднятого
в результате появления критической ошибки) и передать управление на завершение
исполнение специальной функции, не допуская, таким образом, формирование новых
торговых приказов. В представленной схеме значение флага анализируется в блоке
предварительной обработки.

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

Рыночные цены находятся в постоянном движении. Состояние рынка в любой момент может
быть условно охарактеризовано либо как тренд — сильное однонаправленное изменение
(повышение или понижение) цены, либо как флэт — боковое движение цены со слабыми
отклонениями от некоторой средней. Эти характеристики рынка являются условными,
поскольку не существует чётких критериев, в соответствии с которыми тренд или флэт
можно было бы идентифицировать. Например, бывают продолжительные боковые движения
цен с сильными отклонениями, которые нельзя отнести ни к флэту ни к тренду. В целом
принято считать, что в основном рынок находится в состоянии бокового движения,
а тренды на рынке занимают около 15-20% времени.


Рис. 110. Флэт и тренд на рынке.

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

Торговые критерии

В данном примере мы попробуем построить трендовый эксперт, т.е. такой, который будет
открывать ордера в сторону движения цены. Для этого среди множества показателей
различных технических индикаторов необходимо найти такие, которые свидетельствуют
о начале тренда. Один из наиболее простых методов поиска торговых критериев основан
на анализе сочетания средних МА с различным периодом усреднения. На Рис. 111 и
Рис. 112 показано расположение двух различных МА (с периодами усреднения 11 и 31)
на различных участках рынка. Средние с небольшим периодом усреднения (красного
цвета) располагаются ближе ценовому графику, они более извилисты и подвижны. В
то же время средние с большим периодом усреднения (синего цвета) более инертны,
имеют большее запаздывание и располагаются дальше от рыночных цен. Обратим внимание
на те места, в которых МА с разным периодом усреднения пересекаются, и попробуем
решить: можно ли факт пересечения МА использовать в качестве торгового критерия.



Рис. 111. Пересечения МА(11) и МА(31) при изменении направления движения цены.

На Рис. 111 показан участок рынка, на котором открытие ордеров в сторону движения
цены по факту пересечения МА оправдано. В точке А красная линия пересекает синюю
линию снизу вверх, и вслед за этим событием рыночная цена некоторое время продолжает
расти. Последующее обратное пересечение МА свидетельствует об изменении направления
движения цены. Если в точке А открыть ордер Buy, а в точке В закрыть его, то в результате
будет получена прибыль, пропорциональная разнице цен А и В.



Рис. 112. Пересечения МА(11) и МА(31) при изменении направления движения цены.

В то же время на рынке попадаются и другие участки, на которых МА также пересекаются,
однако это не приводит к существенному последующему повышению или снижению цены
(Рис. 112). Ордера, открытые по факту пересечения МА на таких участках оказываются
убыточными. Если в точке А открыть Sell, а в точке В его закрыть, то результатом
такой торговой операции окажется убыток. То же можно сказать и об ордере Buy, открытом
в точке В закрытом в точке С.

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

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




Рис. 113. Сильное движение цены может привести к развитию тенденции.

На Рис. 113 показан участок рынка, на котором сильное движение цены привело к продолжению
изменения цены в том же направлении. В качестве показателя «сильного движения»
можно использовать разницу значений МА с различным периодом усреднения. Чем сильнее
движение, тем большее отставание МА с большим периодом усреднения от МА с малым
периодом усреднения. Показательно, что даже сильные скачкообразные изменения цен
с последующим возвратом не приводят к большой разнице значений между различными
МА, т.е. не возникает много нежелательных ложных сигналов. Например, скачкообразное
изменение цены на 50 пунктов, сопровождаемое последующим откатом (в центре Рис. 113), привело к увеличению разницы между МА всего на 20 пунктов. В то же время,
действительно сильное движение (которое обычно не сопровождается значительной коррекцией)
в точке А повлекло за собой увеличение разницы между МА до 25 — 30 пунктов.

Если при достижении некоторого значения разницы между значениями различных МА, например,
в точке А, открыть ордер Buy, то вполне вероятно, что в конечном итоге этот ордер
окажется прибыльным, когда цена достигнет уровня заявленного значения его стоп-приказа.
Используем этот показатель в качестве торгового критерия в нашем эксперте.

Количество ордеров

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

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

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

Отношение торговых критериев

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


Рис. 114. Соотношение критериев открытия и закрытия ордеров (а и b — правильные
критерии, с — неправильные).

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

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

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

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

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

Размер открываемых ордеров

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

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

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

Подробности программирования

Простой трендовый эксперт tradingexpert.mq4, построенный на основе предыдущих рассуждений, может выглядеть
так:





#property copyright "Copyright © Book, 2007"
#property link "http://AutoGraf.dp.ua"


extern double StopLoss =200;
extern double TakeProfit =39;
extern int Period_MA_1=11;
extern int Period_MA_2=31;
extern double Rastvor =28.0;
extern double Lots =0.1;
extern double Prots =0.07;

bool Work=true;
string Symb;

int start()
{
int
Total,
Tip=-1,
Ticket;
double
MA_1_t,
MA_2_t,
Lot,
Lts,
Min_Lot,
Step,
Free,
One_Lot,
Price,
SL,
TP;
bool
Ans =false,
Cls_B=false,
Cls_S=false,
Opn_B=false,
Opn_S=false;


if(Bars < Period_MA_2)
{
Alert("Недостаточно баров в окне. Эксперт не работает.");
return;
}
if(Work==false)
{
Alert("Критическая ошибка. Эксперт не работает.");
return;
}


Symb=Symbol();
Total=0;
for(int i=1; i<=OrdersTotal(); i++)
{
if (OrderSelect(i-1,SELECT_BY_POS)==true)
{
if (OrderSymbol()!=Symb)continue;
if (OrderType()>1)
{
Alert("Обнаружен отложенный ордер. Эксперт не работает.");
return;
}
Total++;
if (Total>1)
{
Alert("Несколько рыночных ордеров. Эксперт не работает.");
return;
}
Ticket=OrderTicket();
Tip =OrderType();
Price =OrderOpenPrice();
SL =OrderStopLoss();
TP =OrderTakeProfit();
Lot =OrderLots();
}
}


MA_1_t=iMA(NULL,0,Period_MA_1,0,MODE_LWMA,PRICE_TYPICAL,0);
MA_2_t=iMA(NULL,0,Period_MA_2,0,MODE_LWMA,PRICE_TYPICAL,0);

if (MA_1_t > MA_2_t + Rastvor*Point)
{
Opn_B=true;
Cls_S=true;
}
if (MA_1_t < MA_2_t - Rastvor*Point)
{
Opn_S=true;
Cls_B=true;
}


while(true)
{
if (Tip==0 && Cls_B==true)
{
Alert("Попытка закрыть Buy ",Ticket,". Ожидание ответа..");
RefreshRates();
Ans=OrderClose(Ticket,Lot,Bid,2);
if (Ans==true)
{
Alert ("Закрыт ордер Buy ",Ticket);
break;
}
if (Fun_Error(GetLastError())==1)
continue;
return;
}

if (Tip==1 && Cls_S==true)
{
Alert("Попытка закрыть Sell ",Ticket,". Ожидание ответа..");
RefreshRates();
Ans=OrderClose(Ticket,Lot,Ask,2);
if (Ans==true)
{
Alert ("Закрыт ордер Sell ",Ticket);
break;
}
if (Fun_Error(GetLastError())==1)
continue;
return;
}
break;
}


RefreshRates();
Min_Lot=MarketInfo(Symb,MODE_MINLOT);
Free =AccountFreeMargin();
One_Lot=MarketInfo(Symb,MODE_MARGINREQUIRED);
Step =MarketInfo(Symb,MODE_LOTSTEP);

if (Lots > 0)
Lts =Lots;
else
Lts=MathFloor(Free*Prots/One_Lot/Step)*Step;

if(Lts < Min_Lot) Lts=Min_Lot;
if (Lts*One_Lot > Free)
{
Alert(" Не хватает денег на ", Lts," лотов");
return;
}


while(true)
{
if (Total==0 && Opn_B==true)
{
RefreshRates();
SL=Bid - New_Stop(StopLoss)*Point;
TP=Bid + New_Stop(TakeProfit)*Point;
Alert("Попытка открыть Buy. Ожидание ответа..");
Ticket=OrderSend(Symb,OP_BUY,Lts,Ask,2,SL,TP);
if (Ticket > 0)
{
Alert ("Открыт ордер Buy ",Ticket);
return;
}
if (Fun_Error(GetLastError())==1)
continue;
return;
}
if (Total==0 && Opn_S==true)
{
RefreshRates();
SL=Ask + New_Stop(StopLoss)*Point;
TP=Ask - New_Stop(TakeProfit)*Point;
Alert("Попытка открыть Sell. Ожидание ответа..");
Ticket=OrderSend(Symb,OP_SELL,Lts,Bid,2,SL,TP);
if (Ticket > 0)
{
Alert ("Открыт ордер Sell ",Ticket);
return;
}
if (Fun_Error(GetLastError())==1)
continue;
return;
}
break;
}

return;
}

int Fun_Error(int Error)
{
switch(Error)
{
case 4: Alert("Торговый сервер занят. Пробуем ещё раз..");
Sleep(3000);
return(1);
case 135:Alert("Цена изменилась. Пробуем ещё раз..");
RefreshRates();
return(1);
case 136:Alert("Нет цен. Ждём новый тик..");
while(RefreshRates()==false)
Sleep(1);
return(1);
case 137:Alert("Брокер занят. Пробуем ещё раз..");
Sleep(3000);
return(1);
case 146:Alert("Подсистема торговли занята. Пробуем ещё..");
Sleep(500);
return(1);

case 2: Alert("Общая ошибка.");
return(0);
case 5: Alert("Старая версия терминала.");
Work=false;
return(0);
case 64: Alert("Счет заблокирован.");
Work=false;
return(0);
case 133:Alert("Торговля запрещена.");
return(0);
case 134:Alert("Недостаточно денег для совершения операции.");
return(0);
default: Alert("Возникла ошибка ",Error);
return(0);
}
}

int New_Stop(int Parametr)
{
int Min_Dist=MarketInfo(Symb,MODE_STOPLEVEL);
if (Parametr < Min_Dist)
{
Parametr=Min_Dist;
Alert("Увеличена дистанция стоп-приказа.");
}
return(Parametr);
}

Описание переменных

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

В блоке 1-2 описаны внешние и глобальные переменные.

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

Блок предварительной обработки

В данном примере предварительная обработка ситуации состоит из двух частей (блок
3-4). Программа заканчивает работу в случае, если в окне финансового инструмента
мало баров; в этом случае не представляется возможным правильно определить (в
блоке 5-6) значения
скользящих средних, необходимых для вычисления торговых критериев.
Кроме того, здесь анализируется значение глобальной переменной Work. При нормальной
работе эксперта значение этой переменной всегда равно true (устанавливается один
раз при инициализации). Если же при выполнении программы возникла критическая (непреодолимая)
ошибка, то эта переменная получает значение false, в результате чего специальная
функция start() заканчивает работу. В дальнейшем это значение никогда не изменяется,
поэтому и последующий код программы не исполняется. В этом случае необходимо остановить
исполнение программы и установить причину возникновения критической ошибки (при
необходимости связаться с дилинговым центром). После того, как ситуация будет
разрешена,
можно снова запустить программу в работу, присоединив эксперт к окну финансового
инструмента.

Учёт ордеров

Рассматриваемый эксперт предполагает возможность работы только с одним рыночным
ордером. Задачей блока учёта ордеров (блок 4-5) является определение характеристик
открытого ордера, если такой ордер есть. В цикле перебора ордеров for опрашиваются
все имеющиеся рыночные и отложенные ордера, а именно от первого (int i=1) до последнего
(i<=OrdersTotal()). На каждой итерации этого цикла с помощью функции OrderSelect()
выбирается очередной ордер. Выбор производится из источника открытых и отложенных
ордеров (SELECT_BY_POS).

   if (OrderSelect(i-1,SELECT_BY_POS)==true) 

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

      if (OrderSymbol()!=Symb)continue;      

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

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

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

В результате исполнения блока учёта ордеров (если все проверки прошли успешно) переменная
Total сохраняет своё значение нулевым, если рыночных ордеров нет, или получает
значение 1, если по нашему финансовому инструменту имеется рыночный ордер. В последнем
случае ряд переменных, поставленных в соответствие характеристикам ордера (номер,
тип, цена открытия, значения стоп-приказов и стоимость ордера), тоже получают свои
значения.

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

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

В начале блока вычисляются значения средних с периодами усреднения Period_MA_1 и
Period_MA_2. Факт значимости какого-либо торгового критерия выражается через значение
соответствующей переменной. Переменные Opn_B и Opn_S означают срабатывание критерия
для открытия ордеров Buy и Sell, а переменные Cls_В и Cls_S — для закрытия. Например,
если критерий для открытия ордера Buy не сработал, то значение переменной Opn_B
остаётся равным false (установленным при инициализации переменной), а если критерий
сработал, то переменная Opn_B получает значение true. В данном случае критерий
закрытия ордера Sell совпадает с критерием открытия ордера Buy и критерий открытия
Sell совпадает с критерием закрытия Buy.

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

Закрытие ордеров

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

Блок закрытия построен на основе бесконечного цикла while, тело которого состоит
из двух аналогичных частей: первая часть предназначена для закрытия ордера Buy,
а вторая — для закрытия Sell. Цикл while используется здесь для того, чтобы в случае
неудачного завершения торговой операции её можно было повторить.

В заголовке первого оператора if вычисляется условие для закрытия ордера Buy ( закрытие
рыночных ордеров Sell происходит по аналогичному алгоритму). Если тип ранее открытого
ордера соответствует типу Buy (см. Типы торговых операций) и признак закрытия ордера Buy является значимым, то управление передаётся в тело
оператора if, где формируется торговый приказ на закрытие. В качестве цены закрытия
ордера в функции OrderClose() указывается значение двухсторонней котировки, соответствующее
типу ордера (см. Требования и ограничения торговых операций). Если торговая операция выполнена успешно, то после сообщения о закрытии ордера
текущая итерация цикла while прерывается, и, таким образом, исполнение блока закрытия
ордеров заканчивается. Если же операция закрытия закончилась неудачей, то вызывается
для исполнения пользовательская функция обработки ошибок Fun_Error() (блок 10-11).

Обработка ошибок

В качестве передаваемого параметра в функции Fun_Error() используется код последней
ошибки, вычисленной с помощью GetLastError(). В зависимости от кода ошибки функция
Fun_Error() возвращает 1 в случае, если ошибка преодолимая и торговую операцию
можно повторить, и 0, если возникла критическая ошибка. Критические ошибки в свою
очередь тоже делятся на два вида — те, после которых ещё можно продолжать исполнение
программы (например, общая ошибка) и те, при появлении которых необходимо прекратить
исполнение любых торговых операций (например, счёт заблокирован).

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

Если при обработке ошибок выяснится, что дальнейшее выполнение программы бессмысленно
(например, если работа ведётся на устаревшей версии клиентского терминала), то
при следующем запуске исполнение специальной функции start() будет прекращено в
блоке предварительной обработки при анализе значения переменной Work.

Вычисление количества лотов для новых ордеров

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

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

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

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

Открытие ордеров

Блок открытия ордеров (блок 8-9) также, как и блок закрытия, представляет бесконечный
цикл while. В заголовке первого оператора if вычисляются условия для открытия
ордера Buy: если по финансовому инструменту нет ордеров (переменная Total равна
0) и признак для открытия ордера Buy является значимым ( Opn_B равно true ), то
управление передаётся в тело оператора if для открытия ордера. В этом случае после
обновления данных вычисляются заявленные цены для стоп-приказов.

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

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

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

Некоторые особенности кода

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

Например, в соответствии с принятой стратегией, эксперт ориентирован на работу только
с одним ордером. Это позволило использовать переменную Ticket как для идентификации
номера закрываемого ордера (в блоке закрытия 6-7), так и для идентификации успешности
исполнения торговой операции при открытии ордера (в блоке открытия 8-9). В данном
случае такое решение вполне приемлемо. Вместе с тем, если рассматриваемый код взять
за основу для реализации другой стратегии (например, позволять встречные ордера),
то придётся дополнительно ввести ещё одну или несколько переменных, чтобы иметь
возможность различать номера открытых ордеров и идентифицировать успешность торговых
операций.

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

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

The world’s financial markets offer you a huge potential for profit and loss. There is always a potential for profit in the market as you can place trades in either direction. Whether your stance be bullish or bearish, the ability for the trader to make money is always present—as is the ability to make a loss.

Far too often, emotions, psychological mind traps and mental discipline stand in the way of profits and are the main reasons 95% (or more) of all new traders lose all their investment capital in the first month. 

Using an Expert Advisor algorithm trading robot in Meta Trader written in the MQL4 language is one way of accessing the market via code, thus taking the emotions out of the picture and working with just the numbers and your program logic. 

Taking emotions out of the equation is one measure, but this does not mean robots cannot lose. In fact, even the big investment banks have had to pull the plug on their trading robots before the loss became fatal. In 2013, Goldman Sachs had serious faults in their trading algorithms that cost $100 million.

You need to be aware, before continuing with this guide and even contemplating using your real, hard-earned cash, that you can lose all your deposit in your brokerage account from your code (and maybe in worst cases more with some brokers if additional charges apply)

You are responsible ultimately for the trades, even if program code is placing them for you. While loss is a possibility, you can also multiply the account and make it grow exponentially while you sleep. 

If you like to run completely on auto-pilot, this could be achieved without any interaction at all. You could literally make money passively whilst you continue with your day-to-day life. 

This strategy of totally hands-free trading is not one I promote—nor is it one I ever use and have found profitable at all. Monitoring an active robot and the current trades, in conjunction with keeping an eye on the economic calendar and events, is very different from letting it off wild and hoping for the best. The wins may be epic, but the unattended losses far grander.

Installing MQL4

Please download MT4 from their website and install it on your machine.

  • Windows users
  • macOS users
  • Linux users

Once MT4 is running, you will need an account with a broker that supports MT4. They will then be able to give you your MT4 login credentials. 

All brokers will offer a demo account, and you are encouraged to always use the demo account for writing and testing your software.

Once you have configured MT4 on your desktop, we can continue to creating our own Expert Advisor algorithm trading robot.

How to Write a Robot

There are many languages which would be suitable for writing an algorithm trading robot from scratch, but the complications that you will encounter are in fact with the API to direct market access that your stock broker will provide—that is how you will actually enable yourself to place the trade orders in the financial markets. 

Processing market data could be achieved in a plethora of languages, and probably to a faster extent than the MT4 MQL4 can run back tests (back tests are a way of testing your algorithm robot; more on this later). For the reasons of ease of functionality and general support for financial software,  I highly recommend using MQL4 (MetaQuotes Language 4), the native language of MetaTrader 4, to write your algorithm trading robot.

MQL4’s Syntax

MQL4 is similar in its form to PHP, C, C++ or VBScript. Below is an example of a function that will be called on every tick of market data:

1
void OnTick()
2
  {
3
       if(Bars<100 || IsTradeAllowed()==false)
4
          return;
5
       if(CalculateCurrentOrders(Symbol())==0){
6
          CheckForOpen();
7
       } else {
8
          CheckForClose();
9
       }
10
  }

Here we check if there has been enough market data loaded with Bars < 100. MQL4 sets predefined variables such as Bars (which contains the number of bars loaded into the chart). 

Additionally, we check with an or || conditional for IsTradeAllowed(). This is a check function to check that the trading context is not currently busy.

Elements of MQL4 such as these predefined variables and chart operation functions like Symbol() make writing your strategy a walk in the park, and for me are why it is really preferable to code algorithm trading robots in MQL4 over other languages.

I recommend you take a look through the MQL4 Reference whenever you have time to get more confident in using MQL4 to further satisfy your needs.

Editing MQL4 Code in the MetaEditor

I recommend using the built-in MetaEditor IDE that comes with the MT4 trading platform. To enter the editor, just right-click on an existing expert advisor in the left-hand navigator pane and select modify from the menu.

The MT4 Trading Terminal and Strategy TesterThe MT4 Trading Terminal and Strategy TesterThe MT4 Trading Terminal and Strategy Tester

The MetaEditor of MT4 will then open, and you will be able to create a new file. This offers the user syntax highlighting and debugging output. 

Important: You will have to compile your mq4 files into ex4 expert advisors using the MetaEditor if you are editing in an external editor. So getting familiar with the MetaEditor is a must.

Our Algorithm

For our strategy, we will begin using a basis of support and resistance from the SMA (Smoothed Moving Average) indicator. The SMA can predict a bullish or bearish entry/exit signal. The smoothed moving average helps us cut out the noise from the market, giving us a clearer view of the direction of the price action.

Uptrend support and downtrend resistanceUptrend support and downtrend resistanceUptrend support and downtrend resistance

In either an uptrend or downtrend, the SMA can behave as a support or resistance, depending on the orientation. When the price is going up, the SMA will behave as a floor of support, and when the price is declining, vice versa as a resistance/ceiling.

Basis for Entry

When we plot two SMAs of different periods—one of a 40 period and one of a 100 period—we can then see how they cross over and create a basis for entry. As we mentioned before, when the SMA is below the price (less than the close price), we consider it a basis for support, and when the SMA is above the price (greater than the close price), we consider this a line of resistance

So, in code, we do the following first to create a method for checking the cross-over of inputs for later determining our basis for entry:

1
//+------------------------------------------------------------------+

2
//| Check for cross over of inputs                                      |

3
//+------------------------------------------------------------------+

4
int CheckForCross(double input1, double input2)
5
{
6
  static int previous_direction = 0;
7
  static int current_dirction = 0;
8

9
  // Up Direction = 1

10
  if(input1 > input2){
11
    current_direction = 1;
12
  }
13

14
  // Down Direction = 2

15
  if(input1 < input2){
16
    current_direction = 2;
17
  }
18

19
  // Detect a direction change

20
  if(current_direction != previous_direction){
21
    previous_direction = current_dirction;
22
    return (previous_direction);
23
  } else {
24
    return (0);
25
  }
26
}

Now we can calculate our SMA using the iMA technical indicator method provided by MQL4 and run that through our CheckForCross function to see if there has been a cross like so:

1
shortSma = iMA(NULL, 0, PeriodOne, 0, MODE_SMMA, PRICE_CLOSE, 0);
2
longSma = iMA(NULL, 0, PeriodTwo, 0, MODE_SMMA, PRICE_CLOSE, 0);
3

4
// Check if there has been a cross on this tick from the two SMAs

5
int isCrossed = CheckForCross(shortSma, longSma);

Here we are using the MODE_SMMA to return us the Smoothed Moving Average from the iMA technical indicator method for our chosen smoothing method. 

If you wish to use another smoothing method, there are several choices such as Simple, Exponential, and Linear-weighted.

As with all support and resistance, the standard trading methodology works here: buy support and sell resistance

So, for our algorithm, we are going to do just that. When there is a cross in either direction, we are going to apply the appropriate direction of trade and enter the market.

1
f(isCrossed == 1){
2
        ticket = OrderSend(Symbol(),OP_BUY, LotsOptimized(),Ask,3,0,0,"Double SMA Crossover",MAGICNUM,0,Blue);
3
        if(ticket > 0){
4
          if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
5
            Print("BUY Order Opened: ", OrderOpenPrice());
6
          }
7
          else
8
            Print("Error Opening BUY Order: ", GetLastError());
9
            return(0);
10
        }
11
    if(isCrossed == 2){
12
      ticket = OrderSend(Symbol(),OP_SELL, LotsOptimized(),Ask,3,0,0,"Double SMA Crossover",MAGICNUM,0,Blue);
13
      if(ticket > 0){
14
        if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
15
          Print("SELL Order Opened: ", OrderOpenPrice());
16
        }
17
        else
18
          Print("Error Opening SELL Order: ", GetLastError());
19
          return(0);
20
      }
21
    }

Here we check for the return of the CheckForCross function we defined prior, after loading it with our two SMAs defined by the iMA technical indicator.

We use OrderSend, which is provided by MQL4, to open the trade. As a best practice, the result is stored in the ticket variable and later checked for a positive return value so as to handle any error that may have been reported from the broker’s side.

Basis for Exit

Like the basis for entry (except in the inverse case), when the SMA creates a death cross, we can use this signal for closure of our trade, if any trades are open. The logic for this would be written as so:

1
// Get the current total orders

2
  total = OrdersTotal();
3
  
4
// Manage open orders for exit criteria

5
  for(cnt = 0; cnt < total; cnt++){
6
    OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
7
    if(OrderType() <= OP_SELL && OrderSymbol() == Symbol()){
8
      // Look for long positions

9
      if(OrderType()==OP_BUY){
10
        // Check for Exit criteria on buy - change of direction

11
        if(isCrossed == 2){
12
          OrderClose(OrderTicket(), OrderLots(), Bid, 3, Violet); // Close the position

13
          return(0);
14
        }
15
      }
16
      else //Look for short positions - inverse of prior conditions

17
      {
18
        // Check for Exit criteria on sell - change of direction

19
        if(isCrossed == 1){
20
          OrderClose(OrderTicket(), OrderLots(), Ask, 3, Violet); // Close the position

21
          return(0);
22
        }
23
      }
24
    }
25
  }

Here we enter a for loop to iterate over all the open orders, although currently we will only trade one trade at a time—this allows us to expand in the future and have multiple open trades if we feel we need it.

This also makes usage of the OrderSelect method easier, as we can use the cnt variable from our for loop. 

Once inside the iteration, we simply look at the current OrderType (checking for a Buy OP_BUY or Sell OP_SELL order) and then apply the conditional logic from the return of our CheckForCross method.

Adding Money Management

Right now our algorithm is simple with a basis for entry and exiting of trades, but still quite dangerously stupid when it comes to fund management. For us to keep the risk under some control, we will only place one-fifth of the tradable equity on each trade, so now we need to factor that into our algorithm. 

1
lot = NormalizeDouble((AccountFreeMargin()/5)/1000.0,1);
2
if(lot<0.1) lot=0.1;
3
return(lot);

This means if you have $10,000 in the account, we only trade with one-fifth at a time ($2,000), so the script will only place a trade with a smaller size lot of 0.1 or 0.2, for example—based on this one-fifth calculation. 

We use AccountFreeMargin and NormalizeDouble to generate this lot size, and if it’s calculated at below the minimal lot size of 0.1, we will set it to 0.1.

As the account grows (or shrinks!), the exposure is only going to be set at one-fifth of the account equity. This means that over-exposure of a fixed amount (e.g. trading a specific fixed lot on any size account) theoretically cannot happen, ergo the risk of margin call from over-trading is removed or greatly reduced. 

Margin call is a very real risk if the market moves against you drastically before returning due to a spike/fluctuation whilst you are not around to deposit more funds.

In layman’s terms, the lot size of the trade will be calculated dynamically by our script to meet the equity size. So the potential for larger and larger profits is very real. 

Note: A nice feature could be to provide a parameter to cap the tradable pool. For example, if you wished only to ever trade with $1,000 of your account, regardless of the available margin, you could set the available tradable to just $1,000 rather than your whole account size. 

Personally I always use features like this when going live with new scripts in order to reduce my exposure to risk, until I am really sure the script is functioning solidly enough to be allowed more funds. 

Programmatically, this requires a parameter and a slight change to the code example above to check for that variable rather than the AccountFreeMargin() value.

Break Even

Additionally, we will attempt to perform some break-even situations, meaning that if the market has changed against us to create a loss substantially from opening, we look to exit with minimal loss and at least break even so as to retain our capital.

This can be achieved by monitoring the loss of our trade and relation to the open price. If the direction changes and our trade is left out of the money, we can attempt to close out the trade as close to the entry price as possible:

1
  bool BreakEven(int MN){
2
    int Ticket;
3

4
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
5
      OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
6

7
      if(OrderSymbol() == Symbol() && OrderMagicNumber() == MN){
8
        Ticket = OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, Green);
9
        if(Ticket < 0) Print("Error in Break Even : ", GetLastError());
10
          break;
11
        }
12
      }
13

14
    return(Ticket);
15
}

The above uses OrderModify to attempt to set the stop loss to the order open price. This is a crude but simple method to ensure that we step out when the price comes back to our entry price. This is only applied when the current unrealised profit is in a loss.

Adding a Break-Even Margin

A margin for break-even could be added by simply adding to the OrderOpenPrice method like so:

1
Ticket = OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice()+10, OrderTakeProfit(), 0, Green);

For this example, we allow 10 points difference in our entry to closure via stop limit.

Note: Direction of trade should be checked for this method—as in whether it should be added to or subtracted from the entry price according to whether it is a buy or sell position.

Another way to ensure our gains are not lost is to use a trailing stop, which will be discussed in detail in another guide.

The Whole Script

Here is the full listing for our expert advisor. We have several parameters available at our disposal such as the take profit level, stop loss, and the SMA periods. 

Please feel free to play with the parameters to see what is working best for your trading situation and fine tune your algorithm. 

Remember: past performance is in no way indicative of the future.

1
//+------------------------------------------------------------------+

2
//|                                                   Double Sma.mq4 |

3
//|                                   Copyright 2017, Tom Whitbread. |

4
//|                                           http://www.gript.co.uk |

5
//+------------------------------------------------------------------+

6
#property copyright   "2017, Tom Whitbread."

7
#property link        "http://www.gript.co.uk"

8
#property description "Smoothed Moving Average sample expert advisor"

9

10
#define MAGICNUM  20131111

11

12
// Define our Parameters

13
input double Lots          = 0.1;
14
input int PeriodOne        = 40; // The period for the first SMA

15
input int PeriodTwo        = 100; // The period for the second SMA

16
input int TakeProfit       = 40; // The take profit level (0 disable)

17
input int StopLoss         = 0; // The default stop loss (0 disable)

18
//+------------------------------------------------------------------+

19
//| expert initialization functions                                  |

20
//+------------------------------------------------------------------+

21
int init()
22
{
23
  return(0);
24
}
25
int deinit()
26
{
27
  return(0);
28
}
29
//+------------------------------------------------------------------+

30
//| Check for cross over of SMA                                      |

31
//+------------------------------------------------------------------+

32
int CheckForCross(double input1, double input2)
33
{
34
  static int previous_direction = 0;
35
  static int current_direction  = 0;
36

37
  // Up Direction = 1

38
  if(input1 > input2){
39
    current_direction = 1;
40
  }
41

42
  // Down Direction = 2

43
  if(input1 < input2){
44
    current_direction = 2;
45
  }
46

47
  // Detect a direction change

48
  if(current_direction != previous_direction){
49
    previous_direction = current_direction;
50
    return (previous_direction);
51
  } else {
52
    return (0);
53
  }
54
}
55

56
//+------------------------------------------------------------------+

57
//| Calculate optimal lot size                                       |

58
//+------------------------------------------------------------------+

59
double LotsOptimized()
60
  {
61
   double lot = Lots;
62
   // Calculate Lot size as a fifth of available free equity.

63
   lot = NormalizeDouble((AccountFreeMargin()/5)/1000.0,1);
64
   if(lot<0.1) lot=0.1; //Ensure the minimal amount is 0.1 lots

65
   return(lot);
66
  }
67

68

69
//+------------------------------------------------------------------+

70
//+ Break Even                                                       |

71
//+------------------------------------------------------------------+

72
bool BreakEven(int MN){
73
  int Ticket;
74

75
  for(int i = OrdersTotal() - 1; i >= 0; i--) {
76
    OrderSelect(i, SELECT_BY_POS, MODE_TRADES);
77

78
    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MN){
79
      Ticket = OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, Green);
80
      if(Ticket < 0) Print("Error in Break Even : ", GetLastError());
81
        break;
82
      }
83
    }
84

85
  return(Ticket);
86
}
87

88
//+------------------------------------------------------------------+

89
//+ Run the algorithm                                               |

90
//+------------------------------------------------------------------+

91
int start()
92
{
93
  int cnt, ticket, total;
94
  double shortSma, longSma, ShortSL, ShortTP, LongSL, LongTP;
95

96
  // Parameter Sanity checking

97
  if(PeriodTwo < PeriodOne){
98
    Print("Please check settings, Period Two is lesser then the first period");
99
    return(0);
100
  }
101

102
  if(Bars < PeriodTwo){
103
    Print("Please check settings, less then the second period bars available for the long SMA");
104
    return(0);
105
  }
106

107
  // Calculate the SMAs from the iMA indicator in MODE_SMMA using the close price

108
  shortSma = iMA(NULL, 0, PeriodOne, 0, MODE_SMMA, PRICE_CLOSE, 0);
109
  longSma = iMA(NULL, 0, PeriodTwo, 0, MODE_SMMA, PRICE_CLOSE, 0);
110

111
  // Check if there has been a cross on this tick from the two SMAs

112
  int isCrossed = CheckForCross(shortSma, longSma);
113

114
  // Get the current total orders

115
  total = OrdersTotal();
116

117
  // Calculate Stop Loss and Take profit

118
  if(StopLoss > 0){
119
    ShortSL = Bid+(StopLoss*Point);
120
    LongSL = Ask-(StopLoss*Point);
121
  }
122
  if(TakeProfit > 0){
123
    ShortTP = Bid-(TakeProfit*Point);
124
    LongTP = Ask+(TakeProfit*Point);
125
  }
126

127
  // Only open one trade at a time..

128
  if(total < 1){
129
    // Buy - Long position

130
    if(isCrossed == 1){
131
        ticket = OrderSend(Symbol(), OP_BUY, LotsOptimized(),Ask,5, LongSL, LongTP, "Double SMA Crossover",MAGICNUM,0,Blue);
132
        if(ticket > 0){
133
          if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
134
            Print("BUY Order Opened: ", OrderOpenPrice(), " SL:", LongSL, " TP: ", LongTP);
135
          }
136
          else
137
            Print("Error Opening BUY  Order: ", GetLastError());
138
            return(0);
139
        }
140
    // Sell - Short position

141
    if(isCrossed == 2){
142
      ticket = OrderSend(Symbol(), OP_SELL, LotsOptimized(),Bid,5, ShortSL, ShortTP, "Double SMA Crossover",MAGICNUM,0,Red);
143
      if(ticket > 0){
144
        if(OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
145
          Print("SELL Order Opened: ", OrderOpenPrice(), " SL:", ShortSL, " TP: ", ShortTP);
146
        }
147
        else
148
          Print("Error Opening SELL Order: ", GetLastError());
149
          return(0);
150
      }
151
    }
152

153
  // Manage open orders for exit criteria

154
  for(cnt = 0; cnt < total; cnt++){
155
    OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
156
    if(OrderType() <= OP_SELL && OrderSymbol() == Symbol()){
157
      // Look for long positions

158
      if(OrderType() == OP_BUY){
159
        // Check for Exit criteria on buy - change of direction

160
        if(isCrossed == 2){
161
          OrderClose(OrderTicket(), OrderLots(), Bid, 3, Violet); // Close the position

162
          return(0);
163
        }
164
      }
165
      else //Look for short positions - inverse of prior conditions

166
      {
167
        // Check for Exit criteria on sell - change of direction

168
        if(isCrossed == 1){
169
          OrderClose(OrderTicket(), OrderLots(), Ask, 3, Violet); // Close the position

170
          return(0);
171
        }
172
      }
173
      // If we are in a loss - Try to BreakEven

174
      Print("Current Unrealized Profit on Order: ", OrderProfit());
175
      if(OrderProfit() < 0){
176
        BreakEven(MAGICNUM);
177
      }
178
    }
179

180
  }
181

182
  return(0);
183
}

Testing It Out

Preparing to test out our applicationsPreparing to test out our applicationsPreparing to test out our applications

We can test the previous days, months, or even years of market data for a financial instrument with ease in the MT4 (Meta Trader 4) environment. However, traders are encouraged not to trust back testing alone, but to use it as a guide to steer their trading strategy and see how effective an idea is. 

Back testing enables traders to check if their thinking holds water, so to speak, before committing more effort and moving forward—writing more of their algorithm into code.

Where to Place the Expert Files?

If you have been using an external text editor to write your advisor, you will need to load your expert advisor into MT4 trading platform to compile it and error check. Simply add the .mq4 file to your MetaTrader installation in the Expert directory, e.g. /MetaTrader4/MQL4/Experts.

The Expert Advisor will then be available inside your MT4 software from the Navigator menu on the left-hand side under the experts section. 

Note: Make sure you are testing on a demo account. A real account will trade with real money, and although the profits will be real, so will the losses.

Back Testing

The results from a backtest show you how well you algorithm performedThe results from a backtest show you how well you algorithm performedThe results from a backtest show you how well you algorithm performed

If you tested a strategy on the last year of cable (GBP/USD) and found the profit ratio to be above 1 (meaning you made money), then you could be onto a good thing. 

Whether this will work out in the real world is a whole other question, and is why forward testing is equally important, as is Z score testing. That’s a much more advanced topic for machine learning your algorithm, which will not be discussed here, but in later more advanced guides.

How to Start a Back Test

Use the shortcut Control-R to open the Strategy Tester or select it from the View menu. The tester pane will open in the bottom of your window.

From here, you can select the algorithm to run in the first dropdown menu, so choose the filename of the Expert advisor you have created here. Next, you can choose the symbol (financial instrument). I will be testing on the forex GBPUSD symbol of the British Pound to US Dollar pair.

We can also select the time period resolution to run on (15-minute period, 1-hour period, 1-day period, and so on). I will be using the 30-minute setting.

Finally, we have an option for modelling on Every Tick, Control Points, or Open Prices only. While writing your script, you can opt for the Open Prices only, as it will rapidly execute your test—although the results won’t be worth banking real money on yet!

For this, when you are looking for a real test before going to forward testing, it is recommended to run on Every Tick. This will take a lot longer to process! (You can leave it running in an MT4 VPN online or of course overnight on your local machine if you wish.)

Modifying the Parameters

We set a couple parameters (PeriodOne and PeriodTwo) for our expert advisor so that the time periods of the two Moving Averages can be modified. 

These can be accessed via the Strategy tester by clicking the Expert Properties button and viewing the input tab. 

A numerical value can be given for each parameter here—the defaults are PeriodOne = 40 and PeriodTwo = 100.

Forward Testing

Once you have tested over historical data, you can start to test with the live market as you have already seen if your ideas weathered the storm from the back test—and (hopefully) have found some confidence in what you believe to be a winning strategy!

In the live market, you may find your strategy falls flat on its face due to elements you had not factored in your prior back tests. Remember the market is always right. Your code is never smarter than the market, and there is no such thing as being lucky in trading. 

A forward test really is the acid test to see if the strategy will be profitable for you to put real money on it.

The forward test is usually best performed on a dedicated VPN for MT4 EA (Expert Advisors) which is often provided free by most FX brokers. The script will run 24/5 whilst the market is open, and you will be able to monitor the trades live by logging in to the account from a terminal as it will run on your mobile device via the MT4 app—or desktop machine via the MT4 software.

High Volatility

What can be a real killer to our scripts is high volatility in the market, which can occur from many outside factors. 

For example, any crash or flash crash, war, election, interest rate hike, bond yields or significant economic report such as the CPI, GDP or changes to the tax system are going to cause big changes throughout the global economy and its many financial markets. So also, indirectly, your trade in one currency pair or security can be affected by another country’s events, which you may not have anticipated at first.

Most recently, Brexit and later Hillary Clinton’s FBI investigation during the run-up to the US elections were examples of high volatility in the market for anyone interested in taking a closer look. 

The Brexit vote caused the British Pound to plummet against the DollarThe Brexit vote caused the British Pound to plummet against the DollarThe Brexit vote caused the British Pound to plummet against the Dollar

Let’s take the case of Brexit. The British public seemed to believe in the majority that a Brexit vote would be highly unlikely. I was not so optimistic and pulled all my pounds out, saving a lot of my equity in the UK due to the plummeting exchange rate. 

As for others, I’m afraid to say they were not so lucky. Hedging on a stay vote—as some of the major banks’ advisors had stated—would of course have resulted in the inverse, and a loss of approximately 15% to those vested in the pound, as they wait for it to recover.

Situations like this are ideal to turn off your trading robot and only trade outside of high market volatility. Coding for this kind of outcome is going to be very hard automatically going on leading or lagging indicators and is better traded after the event or manually. The chances for false or contradictory signals are always higher during big events, and a manual fundamental approach rather than a technical one may be more profitable.

There is nothing wrong with pausing your robot because you are expecting a crisis. It may save you a loss, whilst on the other hand it may make you miss out on a huge win. In my experience, the chances of the win are far slimmer, due to the sheer uncertainty and likelihood of margin call or hitting your stop loss before the trade can progress due to extreme momentary spikes.

Conclusions

We’ve laid down some of the basics of writing a trading algorithm and introduced many new ideas. From walking through the code, I hope you can now see the inner workings of an algorithm in MQL4, and see how a technical indicator like the moving average is used for generating an entry and exit signal. 

In terms of money management, we have gone over the possibility of including break-even conditions and dynamic lot sizing to use one-fifth of the available equity. Feel free to tweak these parts of the code to your desires for risk. We have gone over the back-testing strategy tester of MT4 and opened the doors for the potential of forward testing and even Z-score testing in the future, all of which are vital before going live.

With more fine tuning and research, you can possibly have a very profitable experience in the markets one day soon. Now that you have a good foundational expert advisor as a base script, you can start writing your own strategies into the MQL4 language—and testing out if they really work as well as you feel they ought to, or as some trading books and mentors may allude to!

Going forward, you will definitely want to test your algorithm more before committing real money to it. Once you feel confident enough that you have a good performing script, you may also want to join a community to take your ideas further and help brainstorm, or take apart other algorithms to see their workings and how you could incorporate them into your strategy. 

Sharing expert advisors with other traders is a great way to collaborate and see how other people are writing their algorithm for the plethora of technical indicators out there such as MACD, RSI, CCI, ADX, Bollinger Bands and Ichimoku… the list goes on and on.

Maybe in the future, if you are confident enough, you may wish to sell your script to others, in which case the MT4 marketplace or on your own site could be ideal places to get started!

For doing more testing, I really recommend you download all the previous market data and load it into MT4 so as to be able to do a more thorough back test. For more information on that, please refer to this guide, and have a great time trading!

Disclaimer: This article is not intended as investment or financial advice—it is intended purely as a technical tutorial for software creation and research. The code is provided for educational purposes only, as-is, with no warranty or guarantees.

Did you find this post useful?

Tom Whitbread

Linux System Admin & Fullstack Web Developer — Thailand

Tom Whitbread is web developer and Linux system admin from Somerset, UK, working mainly with Meteor, React, and JavaScript.

Понравилась статья? Поделить с друзьями:
  • Вентолин небулы для ингаляций инструкция цена аналоги
  • Мосгипротранс официальный сайт руководство
  • Гемахол инструкция по применению цена отзывы врачей
  • Глево 500 инструкция по применению от чего назначают таблетки взрослым
  • Антимедитин инструкция по применению в ветеринарии