Содержание
Всем привет. На прошлых уроках мы изучали возможность создания советников по нескольким простым сигналам для открытия ордеров, но что, если нам нужно входить в рынок только в конкретно заданное время? Давайте попробуем добавить данный фильтр в наш код торгового робота. Для разнообразия попробуем использовать индикатор Stochastic как сигнальный. Цель урока языку mql4 — усложнить код с помощью дополнительных плюшек, а также практика, практика и практика. Пройдя все уроки по написанию советников для МТ4 у вас не сможет возникнуть проблемы в создании собственного эксперта по вашей, индивидуальной логике торговли.
Торговая идея будущего советника
Если вы уже приличное время знакомы с рынком Форекс, то возможно вы слышали о такой стратегии торговли, как ночной скальпинг. Также ее называют трейдингом в Азиатскую торговую сессию. Суть системы проста — входить в рынок в ночное время по причине низкой волатильности, когда Европейская и Американские сессии закрыты. Мы будем осуществлять торговлю с 23 часов вечера до 01 часа утра.
Так как наша основная цель это практика, то мы попробуем входить в рынок по сигналу всего одного индикатора — Стохастика. Он прост и уже встроен в терминал MT4. Продажи будут осуществляться в заданное время, когда основная линия индикатора выше отметки 90, т.е. цена в зоне перекупленности. Покупки же, когда линия ниже 10, т.е. индикатор показывает зону перепроданности. Период ТФ установим М15.
Как видите, идея проста и тривиальна по своей логике. Начнем же!
Пишем код MQL4
Я постараюсь объяснить этот урок максимально сжато и не останавливаться на мелочах, которые мы проходили на прошлых занятиях. Если вы не читали мои предыдущие посты и находитесь на начальной стадии обучения программированию, то я очень вам советую не спешить и начать изучение с самых первых уроков, торопиться тут точно не нужно.
Итак, создаем новый советник. Присоединяем к нему встроенную библиотеку для расшифровки возможных ошибок.
0 1 2 3 4 5 6 7 8 9 10 11 |
//+------------------------------------------------------------------+ //| 3.2 Asia Session 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 description "3.2 Asia Session EA" #property version "1.00" #property strict #include <..\Libraries\stdlib.mq4> //библиотека для расшифровки ошибок |
Теперь давайте определимся с внешними переменными, которые вы сможете менять по мере тестирования и оптимизации советника.
- Основные настройки: торговый лот, проскальзывание, СЛ, ТП, комментарий, магик номер. Также сюда мы добавим фильтр спреда, чтобы учитывать его перед открытием будущих ордеров, ведь в ночное время спред очень часто расширяется.
- Настройки индикатора. В нашем случае это Stochastic, у которого есть три основных параметра: K Period, D Period и Slowing. Также мы должны задать зоны, по пересечению которых будет появляться положительный сигнал на вход: StohLevel. Мы укажем его для зоны перекупленности, а для перепроданности он будет равен 100-StohLevel, т.е. зеркально. Чем меньше внешних параметров, тем меньше вариантов прогона оптимизации.
- Настройки времени торговли. Мы должны указать возможный сдвиг по GMT в зависимости от времени брокера. Данный параметр нужен, если вы будете использовать советник у брокеров с разным GMT. Также укажем время начала и окончания торговли в часах и минутах.
В итоге внешние настройки будут иметь вид:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
extern string s0 = "<== General Settings ==>"; //> > > extern double Lot = 0.01; extern int Slippage = 5; extern double StopLoss = 30; extern double TakeProfit = 15; extern string Comments = "DaVinci EA"; extern int MagicNumber = 123456; extern double MaxSpread = 0; extern string s1 = "<== Stochastic Settings ==>"; //> > > extern int StohLevel = 80; extern int InpKPeriod = 15; // K Period extern int InpDPeriod = 5; // D Period extern int InpSlowing = 3; // Slowing extern string s3 = "<== Time Settings ==>"; //> > > extern int GMT_Offset = 0; //GMT Offset extern int Start_Trade_Hour = 23; //Start Trade Hour extern int Start_Trade_Minute = 0; //Start Trade Minute extern int End_Trade_Hour = 1; //End Trade Hour extern int End_Trade_Minute = 0; //End Trade Minute |
Сразу добавим переменные на глобальном уровне, которые нам будут необходимы во время работы всего кода. Это время начала и окончания торговли, время появления новой свечи, значение текущего дня в году и показатель Стохастика.
0 1 2 3 |
datetime StartTime, EndTime; datetime Update_Time = 0; int day_of_year_trade = 0; double Stoh = 0; |
Переходим к функциям обработки событий. Функция OnDeinit нам не понадобится, поэтому ее оставляем пустой. В функцию инициализации OnInit мы пропишем умножение наших внешних переменный в пунктах на десять, если котировки брокера содержат 3 (для JPY) и 5 знаков после запятой.
0 1 2 3 4 5 6 7 8 9 10 |
int OnInit() { if (Digits == 3 || Digits == 5) { Slippage *= 10; StopLoss *= 10; TakeProfit *= 10; MaxSpread *= 10; } return(INIT_SUCCEEDED); } |
Далее начинаем работу с основной функцией OnTick. Нам нужно определить время начала и окончания торговли в зависимости от текущего торгового дня. Определение происходит 1 раз в день (day_of_year_trade), чтобы не загружать код лишними расчетами. Вначале мы получим время открытия текущего дня, для этого воспользуемся нехитрой формулой перевода текущего времени в дату дня. Далее мы должны прибавить к этому времени значение часа и минут из внешних переменных, не забыв о сдвиге по GMT. Это нужно сделать для определения точного времени начала и окончания нашей торговли. Не забываем о том, что значение времени хранится в секундах, поэтому часы и минуты нужно переводить в секунды.
0 1 2 3 4 5 6 7 8 9 |
void OnTick() { if (day_of_year_trade != DayOfYear()) { day_of_year_trade = DayOfYear(); datetime _CurrentDate = StrToTime(TimeToStr(TimeCurrent(),TIME_DATE)); StartTime =_CurrentDate+Start_Trade_Hour*60*60+Start_Trade_Minute*60+GMT_Offset*60*60; EndTime = _CurrentDate+End_Trade_Hour*60*60+End_Trade_Minute*60+GMT_Offset*60*60; } ... |
Идем дальше. На прошлом занятии мы изучили, что модификацию ордера лучше проводить на следующем тике после его открытия, но только в тех случаях, когда у ордера еще нет целей. Также нам нужно произвести подсчет уже открытых ордеров в обоих направлениях, чтобы не позволить появиться лишним сделкам, когда наши позиции еще в рынке. Для всего этого у нас уже есть готовый кусок кода с прошлого урока, который мы просто скопируем сразу ниже кода расчета времени.
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 |
int cnt_b = 0, cnt_s = 0; int _OrdersTotal = OrdersTotal(); for(int pos = _OrdersTotal - 1; pos >= 0; pos--) { //цикл по всем открытым ордерам if(!OrderSelect(pos, SELECT_BY_POS, MODE_TRADES)) { //выделение ордера для получения его данных Print(__FUNCTION__ + ": не удалось выделить ордер! " + ErrorDescription(GetLastError())); } else if(OrderType() <= 2 && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) { if(OrderType() == OP_BUY) cnt_b++; //подсчет ордеров на покупку else cnt_s++; //подсчет ордеров на продажу if(OrderTakeProfit() == 0 || OrderStopLoss() == 0) { //если у ордера нет одной из цели - модификация double SL = 0, TP = 0; if(OrderType() == OP_BUY) { //определение целей текущего ордера SL = OrderOpenPrice()-StopLoss*Point; TP = OrderOpenPrice()+TakeProfit*Point; } else { SL = OrderOpenPrice()+StopLoss*Point; TP = OrderOpenPrice()-TakeProfit*Point; } if(!OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0, clrNONE)) { //модификация ордера int Error = GetLastError(); Print("Ошибка модификации ордера ",Error,": ",ErrorDescription(Error)); //принт от ошибки модификации } else Print("Ордер #" + IntegerToString(OrderTicket()) + " успешно модифицирован"); } } } |
Готово, все ордера советника теперь под надзором советника. Давайте теперь определим, подходит ли конкретно текущее время для торгов нашим советником. Для этого введем пользовательскую функцию IsTime и запишем результат ее расчетов в bool переменную.
0 |
bool _IsTime = IsTime(); //узнаем, подходит ли текущее время для торговли |
Данная функция будет сравнивать время начала торгов со временем окончания. Она учитывает переход на новые сутки, переход между неделями и проверяет ряд условий. Данный код я использую во всех своих советниках, которые основаны на времени торгов.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
bool IsTime() { //проверка разрешенного времени для торговли datetime _TimeCurrent = TimeCurrent(); if(StartTime > EndTime) { if ((_TimeCurrent >= StartTime && _TimeCurrent < EndTime+60*60*60*24) || (_TimeCurrent >= StartTime-60*60*60*24 && _TimeCurrent < EndTime)) { return(true); } } else if(StartTime < EndTime) { if (_TimeCurrent >= StartTime && _TimeCurrent < EndTime) { return(true); } } return(false); } |
Соответственно, если текущее время соответствует времени торгов из настроек, то функция вернет значение true, если же нет, то false.
С определением текущего времени разобрались, открытые ордера пересчитали, осталось получить сигнал индикатора на вход в рынок. Так как мы решили использовать Стохастик, то его значение нам нужно на первой, закрытой свече. По этой причине нет смысла каждый тик его дергать и получать идентичные данные, поэтому мы будем это делать один раз при открытии новой свечи. Мало того, только тогда, когда текущее время входит в рамки времени торговли. Для этого мы воспользуемся переменной глобального уровня Update_Time и будем хранить в ней время открытия текущей свечи, обновляя данные только раз в свечу.
Стохастик уже встроен в код Meta Editor, поэтому мы просто воспользуемся функцией iStochastic для получения его данных в переменную Stoh. Функция содержит следующие параметры:
- Имя символа. Null — текущий.
- Период. 0 — текущий.
- Kperiod, Dperiod, slowing — все эти данные берутся из внешних переменных.
- Метод усреднения. Мы возьмем по классике обычную МАшку — MODE_SMA.
- Тип цены. Берем цену закрытия Close, т.е. пишем 1.
- Индекс линии — значение одной из двух линий индикатора. Мы выбираем основную MODE_MAIN.
- Сдвиг индикатора. Нам нужно сдвинуться на 1 бар назад, поэтому прописываем единицу.
0 1 2 3 4 5 |
if(Update_Time != iTime(NULL,0,0) && _IsTime) { //обновлять данные всех индикаторов раз в период Update_Time = iTime(NULL,0,0); //перезаписываем значение переменной для хранения времени текущей свечи //импорт данных индикатора Stochastic Stoh = iStochastic(NULL,0,InpKPeriod,InpDPeriod,InpSlowing,MODE_SMA,1,MODE_MAIN,1); } |
0 1 2 3 4 5 6 7 8 |
if(_IsTime) { if(cnt_b == 0 && Stoh < 100-StohLevel) { //условие для открытия ордера на покупку OpenTrade(OP_BUY); //открытие ордера на покупку } if(cnt_s == 0 && Stoh > StohLevel) { //условие для открытия ордера на продажу OpenTrade(OP_SELL); //открытие ордера на продажу } } |
Остальную часть кода нет смысла объяснять, если будут вопросы — пиши в комментариях.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
bool OpenTrade(int OP_Type) { //функция для открытия рыночного ордера if(MaxSpread > 0 && (Ask - Bid) > MaxSpread*Point) return(false); double price = (OP_Type == OP_BUY ? Ask : Bid); //определение цены для открытия рыночного ордера color col_type = (OP_Type == OP_BUY ? clrBlue : clrRed); //определение цвета стрелки ордера string op_str = (OP_Type == OP_BUY ? "на покупку" : "на продажу"); //определение текста для принта //открытие ордера int ticket = OrderSend(Symbol(), OP_Type, Lot, price, Slippage, 0, 0, Comments, MagicNumber, 0, col_type); if(ticket > 0) { //Если ордер был открыт Print("Ордер #" + IntegerToString(ticket) + " успешно открыт"); return(true); } else { //при ошибке открытия ордера int Error = GetLastError(); Update_Time = 0; Print("Ошибка открытия ордера ",Error,": ",ErrorDescription(Error)); } return(false); } |
Осталось добавить блок проверки на ошибки в самом конце функции OnTick:
0 1 |
int Error = GetLastError(); //поиск ошибок по завершению тика if(Error != 0) Print("OnTick() Error ",Error,": ",ErrorDescription(Error)); |
Все, можно выдохнуть, поставленная задача нами выполнена.
Добавляем в код возможность перевода в безубыток
Немного подумав, я решил, что нужно еще вас загрузить информацией. Давайте предположим, что цена практически подошла к отметке Take Profit, но он еще не был достигнут. Будет очень обидно, если цена развернется и заденет Стоп Лосс. К слову, чувак на картинке выше не использует БУ в своей торговле.
Чтобы ограничить наши возможные убытки, мы введем в код возможность выставления безубытка при достижении цены указанного процента от значения ТП. Т.е. если цена подошла на 80% к ТП — мы переведем СЛ на уровень БУ с указанным отступом. Для этого давайте добавим две внешние переменные — процент БУ и отступ от него:
0 1 2 |
extern string s2 = "<== Breakeven ==>"; //> > > extern double SetBEDistance = 90; //Set BE Distance % extern int BE_Step = 1; //BE Step |
В функции обработки событий добавляем BE_Step в проверку на количество знаков.
0 1 2 3 |
int OnInit() { if (Digits == 3 || Digits == 5) { BE_Step *= 10; ... |
Так как цена постоянно в движении, то проверку на изменение СЛ нужно осуществлять каждый тик. У нас уже есть блок анализа открытых ордеров. К нему же добавим и проверку на БУ.
0 1 2 3 4 5 6 7 8 9 10 |
for(int pos = _OrdersTotal - 1; pos >= 0; pos--) { //цикл по всем открытым ордерам if(!OrderSelect(pos, SELECT_BY_POS, MODE_TRADES)) { //выделение ордера для получения его данных Print(__FUNCTION__ + ": не удалось выделить ордер! " + ErrorDescription(GetLastError())); } else if(OrderType() <= 2 && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) { if(OrderType() == OP_BUY) cnt_b++; //подсчет ордеров на покупку else cnt_s++; //подсчет ордеров на продажу if(SetBEDistance > 0) CheckBE(); //проверка для перевода в БУ ... |
Осталось прописать саму функцию для перевода СЛ в БУ. Так как ордер уже выделен, то нам будет вполне комфортно работать с его свойствами. Вначале мы узнаем, что за тип ордера перед нами, далее на примере покупок мы уточняем, ниже ли текущий СЛ рассчитанного уровня БУ или нет. Если да, значит он еще не был модифицирован (БУ не активирован). Далее с помощью незамысловатой математики мы высчитаем какой процент до ТП прошла цена, и если он больше или равен заданному в настройках, то нужно активировать БУ. Дополнительной проверкой сравниваем текущий СЛ и рассчитанным, чтобы не дергать сделку в пустую и не получить ошибку, когда они будут одинаковыми. Модифицируем ордер, выставляя ему СЛ равный цене открытия ордера плюс указанный отступ в пунктах. Аналогично и продажи.
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 |
void CheckBE() { //проверка для перевода ордеров в безубыток if(OrderType() == OP_BUY) { //если ордер на покупку double NewSL = NormalizeDouble(OrderOpenPrice()+BE_Step*Point,Digits); if(OrderStopLoss() < NewSL) { //если СЛ еще не был перенесен if((Bid - OrderOpenPrice()) >= TakeProfit*SetBEDistance/100*Point && NewSL != OrderStopLoss()) { if(!OrderModify(OrderTicket(), OrderOpenPrice(), NewSL, OrderTakeProfit(), 0, clrNONE)) { //модификация ордера int Error = GetLastError(); Print("Ошибка модификации ордера ",Error,": ",ErrorDescription(Error)); //принт от ошибки модификации } else Print("Перевод ордера на покупку в Безубыток с отступом " + IntegerToString(BE_Step) + " пунктов."); } } } else if(OrderType() == OP_SELL) { //если ордер на продажу double NewSL = NormalizeDouble(OrderOpenPrice()-BE_Step*Point,Digits); if(OrderStopLoss() > NewSL) { //если СЛ еще не был перенесен if((OrderOpenPrice() - Ask) >= TakeProfit*SetBEDistance/100*Point && NewSL != OrderStopLoss()) { if(!OrderModify(OrderTicket(), OrderOpenPrice(), NewSL, OrderTakeProfit(), 0, clrNONE)) { //модификация ордера int Error = GetLastError(); Print("Ошибка модификации ордера ",Error,": ",ErrorDescription(Error)); //принт от ошибки модификации } else Print("Перевод ордера на продажу в Безубыток с отступом " + IntegerToString(BE_Step) + " пунктов."); } } } } |
Теперь точно все. Остается поставить советник на быструю оптимизацию и посмотреть, какой результат лучше всего себя покажет. Вот, что получилось у меня:
Вполне неплохая картина для советника, написанного за час. Естественно, вы должны понимать, что это не рекомендация для реального счета, полученный прогон не использовал плавающий спред, также это лучший их сотни результатов, да и Стохастик был взят просто как пример для обучения. Вместо него вы можете попробовать применить канальный метод торговли, который очень распространен в ночной торговле, например использовать Bollinger Bands, канал Кельтнера или Дончиана. Простор для творчества огромен.
Заключение
В этом уроке мы значительно усовершенствовали код, изученный ранее и написали советник mql. Научились использовать определенное время для торговли, будь то день, ночь или какая-то отдельная торговая сессия. Познакомились с торговлей в Азиатскую сессию. Импортировали показатели индикатора Стохастик, а также научились переводить ордера в безубыток. Надеюсь вы смогли не уснуть и дочитать этот текст до конца.
На этом все, увидимся в следующих уроках. Всем профитов!
[download url=»http://www.davinci-fx.com/wp-content/uploads/2021/04/3.2-Asia-Session-EA.rar» title=»Скачать примеры урока»]
7 комментариев. Оставить новый
Здравствуйте. А как организовать проверку на закрытие ордера по стоплосу (допустим чтобы при закрытии ордера по стопу переменной присваивалось значение true)?
Добрый вечер. Только с помощью цикла по закрытым ордерам. Т.е. сохраняем количество закрытых ордеров в переменной глобального уровня и каждый тик сравниваем ее с фактическим значением. Как только появился новый ордер в истории, обновляем значение этой переменной и циклом проходим по последним закрытым ордерам. Если расстояние от цены закрытия до цены открытия более определенного значения (СЛ в пунктах к примеру), значит это наш ордер, мы присваиваем bool переменной статус true.
Получится что то типа того:
Доброго дня. Не очень понял вот это место
if (day_of_year_trade != DayOfYear()) {
datetime _CurrentDate = StrToTime(TimeToStr(TimeCurrent(),TIME_DATE));
StartTime =_CurrentDate+Start_Trade_Hour*60*60+Start_Trade_Minute*60+GMT_Offset*60*60;
EndTime = _CurrentDate+End_Trade_Hour*60*60+End_Trade_Minute*60+GMT_Offset*60*60;
}
почему переменная day_of_year_trade не перезадаётся каждый день? Ведь если её не перезадавать то каждый тик будет происходить перезадание StartTime и EndTime.
Да, вы правы, не углядел. Исправлено.
Добрый день. По уроку 3.2 после установки советник сразу входит в рынок, не дожидаясь заданного времени следующего дня. Подскажите, где можно исправить, чтобы вход в рынок осуществлялся на следующий день в заданное время?
Здравствуйте. Вопрос по уроку 3.2 снят. Я сам разобрался.
Александр, мы в вас не сомневались! 😉