Содержание
Салют всем ученикам языка MQL4!
На прошлых уроках мы научились создавать разнообразные советники по классическим и пользовательским индикаторам, торгующие рыночными ордерами. Нам осталось изучить последнюю тему третьего раздела данного курса — написание советника, осуществляющего торговлю отложенными ордерами.
Как мы знаем, отложенные ордера есть как на покупку, так и на продажу, притом в каждом направлении их может быть два типа: Stop или Limit. В данном уроке мы будем использовать их все. Поехали!
Задаем правила торговой системы
Давайте в этот раз попробуем для разнообразия стратегию торговли от ценовых уровней. Так как программно достойно реализовать уровни поддержи и сопротивления не является возможным, а использовать пивоты как-то не особо охота, мы будем торговать по экстремумам индикатора ZigZag. Не скажу, что это прибыльные решение, но для изучения языка пойдет.
Цель сего индикатора — строить линии по локальным максимумам и минимумам в зависимости от его настроек. Суть нашей системы будет в том, что после того, как индикатор обновит свой экстремум, мы будем выставлять отложенный ордер на предыдущем его значении.
Посмотрим на скриншот. Как только линия индикатора закрепилась на отметке 1, советник будет выставлять отложку на отметке 2. И так постоянно при появлении нового экстремума индикатора.
Так как мы понятия не имеем о том, как более прибыльно осуществлять торговлю по такой тривиальной системе, а также нам нужно попробовать разные типы отложенных ордеров, то в советник мы заложим два варианта торговли: на пробой экстремума и на отбой от него. Выбирать какую систему будем с помощью списка во внешних переменных enum.
Первая система будет называться StopOrder и торговать на пробой уровней ZigZag’a. Соответственно, когда уровни сформируются, у нас появятся ордера Buy Stop и Sell Stop. В MQL такой тип заявки именуется как OP_BUYSTOP и OP_SELLSTOP.
Вторая система под кодовым названием LimitOrder будет использовать лимитки, чтобы торговать на отбой от экстремумов индикатора, используя ордера Buy Limit и Sell Limit, которые в коде будут обозначаться как OP_BUYLIMIT и OP_SELLLIMIT.
Так как Зиг Заг бывает очень «бешеным» во флете и количество его кривых может быть немалое, мы также введем в советник функцию удаления всех отложек перед закрытием рынка в Пятницу в 23 часа.
Думаю этого будет достаточно, не хочу усложнять логику урока и взрывать мозг моим читателям простынями из кода.
Начинаем писать код советника
Жмем Ctrl+N и создаем новый советник. Не забываем добавить библиотеку анализа ошибок stdlib в код, чтобы всегда видеть в журнале, где вы ошиблись. Так как мы решили заложить в код две разные стратегии входа — лимитками и стоповыми отложками, то нам нужен наглядный переключатель между ними. Для этого отлично подходит тип перечисления enum. Зададим ему имя pending и два варианта выбора: StopOrder или LimitOrder, и создадим для него внешнюю переменную OrdersType. Остальные переменные будут стандартными: лот, СЛ, ТП, комментарий и магик. В данном случае нам не нужен параметр Slippage, потому что в отложенных ордерах он не используется. Дополнительно добавим внешнюю переменную типа bool DeletePendingsOnFriday, чтобы разрешить или запретить удалять отложки в заданное время Пятницы.
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 |
//+------------------------------------------------------------------+ //| 3.5 Pending Orders 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 <..\Libraries\stdlib.mq4> //библиотека для расшифровки ошибок enum pending { StopOrder = 1, LimitOrder = 2, }; extern string s0 = "<== General Settings ==>"; //> > > extern pending OrdersType = 1; extern double Lot = 0.01; extern double StopLoss = 20; extern double TakeProfit = 30; extern string Comments = "DaVinci EA"; extern int MagicNumber = 23325; extern bool DeletePendingsOnFriday = true; |
Также добавим параметры индикатора Зиг Заг для возможности более точной настройки его значений, а также две переменные глобального уровня.
0 1 2 3 4 5 6 |
extern string s1 = "<== ZigZag Settings ==>"; //> > > extern int ExtDepth = 15; //Depth extern int ExtDeviation = 5; //Deviation extern int ExtBackstep = 3; //Backstep int PipsDivided = 1; datetime Update_Time = 0; |
В функцию обработки событий OnInit как всегда добавляем определение знака после запятой для корректного подсчета значений в пунктах. Функция OnDeinit в этом советнике не нужна.
0 1 2 3 4 5 6 7 8 9 10 |
//+------------------------------------------------------------------+ int OnInit() { if (Digits == 3 || Digits == 5) { StopLoss *= 10; TakeProfit *= 10; PipsDivided = 10; } return(INIT_SUCCEEDED); } |
Переходим в функцию OnTick. Так как значение экстремума индикатора ZigZag закрепляется за свечей только после ее закрытия, нам нет смысла его дергать каждый тик и делать наш код более медленным. Поэтому проверка значений индикатора, а также открытие ордеров будет происходить только раз в свечу.
0 1 2 3 4 5 6 7 8 9 10 11 |
//+------------------------------------------------------------------+ void OnTick() { if(Update_Time != iTime(NULL,0,0)) { //обновлять данные всех индикаторов раз в период Update_Time = iTime(NULL,0,0); //перезаписываем значение переменной для хранения времени текущей свечи //дальнейший код } int Error = GetLastError(); //поиск ошибок по завершению тика if(Error != 0) Print("OnTick() Error ",Error,": ",ErrorDescription(Error)); } |
Зиг Заг не является встроенным индикатором терминала МТ4, поэтому импорт его данных нужно проводить с помощью функции iCustom, которую мы проходили в этом уроке.
Так как индикатор хранить данные в буфере только для тех свечей, на которых закрепились его экстремумы, нам ничего не остается, как прогнать цикл for по последней сотне свечей, чтобы найти два значения, нужных для принятия решения по открытию ордера.
Нам необходимо создать две переменные для хранения первого (ближайшего) значения индикатора (zz1) и следующего за ним (zz2), на котором и будет выставляться отложка. Вначале мы импортируем значение индикатора внутри цикла в переменную zz2 и осуществляем проверку, что zz1 еще не имеет никакого значения. Если это так, значит найденное нами значение сохраняется в нее. Если же zz1 уже определена, то мы просто прерываем цикл, потому что нами найдены оба ближайших экстремума.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if(Update_Time != iTime(NULL,0,0)) { //обновлять данные всех индикаторов раз в период Update_Time = iTime(NULL,0,0); //перезаписываем значение переменной для хранения времени текущей свечи //импорт данных индикатора ZigZag. int k=MathMin(Bars,100); double zz1 = 0, zz2 = 0; for(int i=1; i<k-1; i++) { zz2 = iCustom(NULL,0,"ZigZag",ExtDepth,ExtDeviation,ExtBackstep,0,i); if(zz2 != 0) { //если на данной свече есть значение в буфере индикатора if(zz1 == 0) zz1 = zz2; //сохранение первого значения ближайшего ЗигЗага else break; //останавливаем цикл после нахождения второго значения индикатора } } //дальнейший код } |
Так как индикатор может продолжительное время не обновлять свои экстремумы, то в итоге мы будем получить одно и то же значение переменной zz2. Чтобы избежать повторное открытие ордеров по этой же цене, нам необходимо добавить функцию OrderExist для проверки присутствия рыночных и отложенных сделок с тем же значением цены. Тут все просто, пишется цикл перебора всех ордеров в рынке и сравнивается их цена открытия с ценой индикатора. Стоит отметить, что для отложенных ордеров цена их выставления определяется так же как и цена для рыночных через торговую функцию OrderOpenPrice. Мы допускаем, что рыночный ордер мог быть открыт с небольшим проскальзыванием, поэтому в коде идет сравнение не точного значения цен, а разницы между ними с максимальным отклонением в 5 старых пунктов.
0 1 2 3 4 5 6 7 8 9 10 11 12 |
//+------------------------------------------------------------------+ bool OrderExist(double zz_value) { //проверка наличия существующих ордеров по цене экстремума индикатора. 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() > 5) continue; if(MathAbs(zz_value-OrderOpenPrice()) <= 5*PipsDivided*Point) return(true); //ордер по этой цене уже есть } return(false); //ордера по цене индикатора не найдены } |
Переходим к условию открытия ордера
Для того, чтобы мы вообще смогли рассматривать возможность открытия ордера, нам нужно знать, что значение Зиг Зига для обоих переменных больше нуля, а также функция OrderExist() вернет результат false.
Также нам необходимо программно определить какой тип ордера нужно открыть — Limit или Stop. Это зависит от изначальных настроек советника. Затем дело за малым: мы сравниваем цену Bid (для отложек на покупки) и Ask (для отложек на продажи) со значением переменной zz2, а также оба значения zz1 и zz2 между собой. В итоге у нас получится по два условия для двух типов ордеров:
Stop Orders:
- Если текущая цена Ask ниже значения сигнального Зиг Зага, а также ближайшее значение индикатора zz1 ниже предыдущего zz2, то мы открываем BUY STOP ордер.
- Если текущая цена Bid выше значения сигнального Зиг Зага, а также ближайшее значение индикатора выше предыдущего, то мы открываем SELL STOP ордер.
Limit Orders:
- Если текущая цена Ask выше значения сигнального Зиг Зага, а также ближайшее значение индикатора выше предыдущего, то мы открываем BUY LIMIT ордер.
- Если текущая цена Bid ниже значения сигнального Зиг Зага, а также ближайшее значение индикатора ниже предыдущего, то мы открываем SELL LIMIT ордер.
Для простоты понимая я рекомендую накинуть индикатор Zig Zag на график и просто проверить эти условия самим вручную.
В итоге все описанное выше будет иметь следующий вид:
0 1 2 3 4 5 6 7 8 9 |
if(zz1 > 0 && zz2 > 0 && !OrderExist(zz2)) { if(OrdersType == StopOrder) { if(Ask < zz2 && zz1 < zz2) OpenTrade(OP_BUYSTOP, zz2); //открытие ордера на покупку BUY STOP else if(Bid > zz2 && zz1 > zz2) OpenTrade(OP_SELLSTOP, zz2); //открытие ордера на продажу SELL STOP } else if(OrdersType == LimitOrder) { if(Ask > zz2 && zz1 > zz2) OpenTrade(OP_BUYLIMIT, zz2); //открытие ордера на покупку BUY LIMIT else if(Bid < zz2 && zz1 < zz2) OpenTrade(OP_SELLLIMIT, zz2); //открытие ордера на продажу SELL LIMIT } } |
Данный код вставляется сразу в функцию OnTick сразу после расчета показаний индикатора.
Теперь нам нужно засучить рукава и создать функцию открытия ордеров OpenTrade(). Шаблон для ее создания я взял из предыдущих уроков.
Отложенные ордера немного отличаются от рыночных по логике их параметров. В данном случае мы сразу же пропишем ордерам цели, а не будем их модифицировать после становления рыночными (хотя так тоже можно реализовать, просто немного дольше). Соответственно, вначале нам нужно сделать небольшие расчеты, на которые влияет тип ордера — покупка или продажа.
Порой может случиться ситуация, когда при резком движении цены вверх она становится выше, чем ценовое значение индикатора и при выставлении отложенного ордера Buy Stop может возникнуть ошибка 130 (неправильная цена выставления отложки). Чтобы этого избежать мы введем небольшую проверку с помощью математических функций MathMax и MathMin для всех типов ордеров.
Далее мы определим ТП и СЛ в зависимости от типа ордера, ну и выставим цвет треугольничка для отображения на графике и строковое значение с текстом для журнала.
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 |
//+------------------------------------------------------------------+ bool OpenTrade(int OP_Type, double price) { //функция для открытия рыночного ордера double SL = 0, TP = 0; color col_type = clrNONE; string op_str = ""; if(OP_Type == OP_BUYSTOP || OP_Type == OP_BUYLIMIT) { //корректировка цены отложки, если текущая цена превысила расчетную. if(OP_Type == OP_BUYSTOP) price = MathMax(price, Ask+Point); else price = MathMin(price, Ask-Point); col_type = clrBlue; //определение цвета стрелки ордера op_str = "на покупку"; //определение текста для принта TP = NormalizeDouble(price + TakeProfit*Point,Digits); //рассчитываем Тейк Профит SL = NormalizeDouble(price - StopLoss*Point,Digits); //рассчитываем Стоп Лосс } else { if(OP_Type == OP_SELLSTOP) price = MathMin(price, Bid-Point); //проверка при резких движениях цены else price = MathMax(price, Bid+Point); col_type = clrRed; op_str = "на продажу"; TP = NormalizeDouble(price - TakeProfit*Point,Digits); SL = NormalizeDouble(price + StopLoss*Point,Digits); } //дальнейший код return(false); } |
Остается только открыть отложенный ордер. Как и для рыночного ордера тут используется торговая функция OrderSend. Рассмотрим небольшое отличие в ее заполнение для нашего случая.
- Symbol — все тоже, текущий символ будет Symbol().
- Торговая операция — в нашем случае переменной OP_Type мы передаем один из четырех возможных типов торгового ордера: OP_BUYSTOP, OP_BUYLIMIT, OP_SELLSTOP или OP_SELLLIMIT.
- Лот — берется из внешних настроек.
- Цена — данное значение не нужно путать с текущей рыночной ценой. Цена открытия это значение price нашего индикатора.
- Проскальзывание — ставим ноль, т.к. для отложек оно не актуально.
- SL, TP — ставим значение, которое мы рассчитали выше.
- Комментарий и Магик Номер — берутся из внешних переменных.
- Expiration — дата окончания действия отложенного ордера. При работе с рыночными ордерами этот параметр не используется. Тут же мы можем задать ему необходимое значение в виде даты. К примеру, если нам нужно, чтобы отложенный ордер существовал только 15 минут, мы прописываем значение TimeCurrent()+15*60. Если же нужно указать конкретное значение, то формат даты имеет вид D’2000.01.01 00:00′. Стоит уточнить, что у некоторых брокеров значение экспирации не работает или работает некорректно, поэтому я рекомендую через дополнительный код удалять ваши отложенные ордера, чем 100% надеяться на этот параметр. При 0 — экспирация не произойдет.
- Цвет — цвет для треугольной метки на графике (определен нами выше).
Если ордер успешно открыт — пишем об этом в журнал, если же нет, то обнуляем значение переменной Update_Time для того, чтобы на следующий тик иметь возможность заново попробовать открыть этот ордер, а не ждать новой свечи.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//прошлый код //открытие ордера int ticket = OrderSend(Symbol(), OP_Type, Lot, price, 0, SL, TP, Comments, MagicNumber, 0, col_type); if(ticket > 0) { //Если ордер был выставлен Print("Отложенный ордер #" + IntegerToString(ticket) + " " + op_str + " успешно выставлен"); return(true); } else { //при ошибке int Error = GetLastError(); Update_Time = 0; Print("Ошибка выставления отложенного ордера " + op_str + ": ",Error,": ",ErrorDescription(Error)); } return(false); } |
Удаление отложенных ордеров
Как мы говорили выше, отложенные ордера по нашей стратегии необходимо удалять, чтобы подчищать за собой в конце каждой недели. Для этого создаем пользовательскую функцию DeletePendings().
Функция представляет собой обычный перебор всех выставленных ордеров при условии, что они относятся к нашему советнику и не являются рыночными. Удаление ордеров происходит с помощью торговой функции OrderDelete, которая имеет всего два параметра: тикет удаляемого ордера и цвет стрелки на графике, показывающей место его удаления.
Мы установим дополнительный цикл for в 3 проходки на случай, если с первого раза удаление выдаст ошибку. В этом случае мы сделаем паузу на 3 секунды и обновим данные переменных.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//+------------------------------------------------------------------+ void DeletePendings() { //фукнция удаления отложенных ордеров 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; //условие, чтобы ордера не были рыночными for(int n = 0; n<3; n++) { //три попытки if(OrderDelete(OrderTicket(),clrAqua)) { //удаление отложек Print("Отложенный ордер #" + IntegerToString(OrderTicket()) + " удален по фильтру закрытия Пятницы"); break; } else { //если ордер не закрылся Sleep(3000); //пауза 3 секунды RefreshRates(); //обновление переменный } } } |
Осталось прописать эту функцию и условие ее срабатывания в самом низу OnTick над проверкой на ошибки.
0 1 |
//удаляем отложки в Пятницу после 23 часов if(DeletePendingsOnFriday && DayOfWeek() >=5 && Hour() >= 23) DeletePendings(); |
Поздравляю, наш советник готов. В режиме визуализации на графике это, конечно, выглядит как какая-то вакханалия, но при более детальном разборе на визуализации все становится понятно.
Компилируем полученный код. Если возникли ошибки, то качайте файл с примером в конце этой статьи для сравнения и поиска образовавшейся ошибки.
Давайте проведем быструю оптимизацию по паре EURUSD с таймфреймом H1 и выберем лучшие результаты за три года.
Так себя показали стоповые ордера:
А это результат (если его так можно назвать) тестирования лимитных ордеров:
Как мы видим, конкретно для данной валютной пары и системы, которую я выдумал на ходу, стоповые ордера показали себя значительно лучше. Значит торговля на пробой экстремумов в данном случае лучше, чем торговля на отбой от них. Результат все же далек от идеала, профит фактор низкий и я бы не стал доверять такому советнику свои кровные, по крайней мерее в том виде, каком он есть сейчас. Но! Вы всегда можете доработать и улучшить первоначальную идею.
Заключение
В этом уроке мы написали простенький советник для торговли отложенными ордерами. Научились импортировать данные пользовательского индикатора и попробовали применить все четыре типа отложек в торговле, а также узнали, как их удалять в заданный нами промежуток времени.
На мой взгляд основной минус отложенных ордеров заключается в том, что мы не можем контролировать спред во время их срабатывания в отличие от рыночных ордеров, которые мы просто можем не открывать, пока он не нормализуется.
С другой стороны, как уверяют некоторые брокеры, лимитные отложки не скользят. Это значит, что торгуя отложенными Limit ордерами вы минимизируете риск проскальзывания в отличие от рыночных ордеров, где при сильной волатильности оно может составить десятки пунктов.
Как всегда, напоминаю, что с нашими советниками вы можете ознакомиться в разделе «Советники».
На этом я заканчиваю третий раздел своего курса по MQL4. Дальнейшие уроки будут посвящены отдельным функциям, фишкам и дополнительным особенностям языка, о которых я забыл или не успел упомянуть.
Всем профитов!
[download url=»http://www.davinci-fx.com/wp-content/uploads/2021/05/3.5-Pending-Orders-EA.rar» title=»Скачать примеры урока»]