Ошибки не очень приятная вещь, а за ошибки на рынке Форекс еще и приходится платить своими «кровными».
При удалении, модификации или открытии ордера мы задаем нужные нам параметры – цена, размер лота, стоп-лос и тейк-профит. И если хоть один из них будет задан не верно, терминал вернет ошибку и торговая операция не исполнится. Что бы не допускать ошибок наш «эксперт» перед отправкой торгового приказа должен проверить все параметры на корректность.
Итак:
Цена – если это маркет ордера, Buy выставляется по Ask и Sell по Bid.
Размер пункта в валюте котировки. Для текущего инструмента хранится в предопределенной переменной Point. Он может быть разным и зависит как к отдельным финансовым инструментам (например EUR/USD – 0.0001, USD/JPY – 0.01) так и от Дилинговых Центров. В зависимости от ДЦ тая ж валютная пара EUR/USD, в одних ДЦ размер пункта четыре знака после запятой (0.0001) а в других пять (0.00001).
Количество цифр после десятичного точки в цене инструмента. Для текущего инструмента хранится в предопределенной переменной Digits.
Если размер пункта для финансового инструмента предопределен как 0.0001 а в торговом приказе в значении цены после запятой будет больше чем четыре цифры – это вызовет ошибку.
Даже если взять для выставления ордера Ask или Bid часто бывает что терминал дает цену котировки Ask или Bid с ошибкой (лишней цифрой после запятой). Потому всегда перед торговой операцией значение цены нужно округлить до нужного количества цифр после запятой.
Для округления есть функция NormalizeDouble(число для округления, количество цифр после запятой).
Можно задать явно число цифр (для конкретной валютной пары и ДЦ).
Пример:
Price = NormalizeDouble(Ask, 4); // округляем до нужного нам числа цифр после запятой
А лучше так:
Price = NormalizeDouble(Ask, Digits); // округляем до нужного нам числа цифр после запятой
Тоже самое нужно сделать после вычисления лота, стоплоса и тейкпрофита (перед оформлением приказа).
Пример:
Price = Ask + Delta*Point; // вычисляем цену открытия байстопа Price = NormalizeDouble(Price, Digits); // округляем до нужного нам числа цифр после запятой SL = Price - StopLoss*Point; // вычисляем стоплос SL = NormalizeDouble(SL, Digits); // округляем до нужного нам числа цифр после запятой TP = Price + TakeProfit*Point; // вычисляем тейкпрофит TP = NormalizeDouble(TP, Digits); // округляем до нужного нам числа цифр после запятой
При расчете лота нужно учитывать дополнительные параметры:
Минимальный размер лота – минимально разрешенный размер лота.
Min_Lot = MarketInfo(Symb,MODE_MINLOT);// Мин. размер. лотов
Максимальный размер лота – максимально разрешенный размер лота.
Max_L = MarketInfo(Symb,MODE_MAXLOT);// Мax. размер. Лотов
Расчет лота оформим в отдельной функции Lot().
double Lot() { int q; double dLot, Min_Lot, Max_L, One_Lot; One_Lot = MarketInfo(Symb,MODE_MARGINREQUIRED); // Стоим. 1 лота Min_Lot = MarketInfo(Symb,MODE_MINLOT); // Мин. размер. лотов Max_L = MarketInfo(Symb,MODE_MAXLOT); // Мax. размер. лотов dLot = Lot; //--------- // код Вашего манименеджмента (ММ) //--------- if(Min_Lot == 0.1) q = 1; if(Min_Lot == 0.01) q = 2; dLot = NormalizeDouble(dLot,q); // округляем до нужного нам числа цифр после запятой if(dLot < Min_Lot) dLot = Min_Lot; // если лот задан меньше допустимого - задаем минимально возможный if(dLot > Max_L) dLot = Max_L; // если лот задан больше допустимого - задаем максимально возможный if(dLot*One_Lot > AccountFreeMargin()) // Не хватает даже.. { // ..на минимальн. лот:( inf(12,0,dLot); // Сообщение.. return(-1); // ..и выход } return(dLot); // ввозвращаем размер лота }
MarketInfo – С помощью этой функции можно получить разнообразною информацию об финансовых инструментах.
AccountFreeMargin() – свободные средства на Вашем счете.
Минимальная щистанция:
Может быть разной и зависит как от финансового инструмента (например EUR/USD – 2 пункта, GBP/JPY – 16 пунктов) так и от ДЦ.
Min_Dist = MarketInfo(Symb,MODE_STOPLEVEL); //Минимально допустимый уровень стоп-лосса/тейк-профита в пунктах
При попытке например выставить байстоп ордер по GBP/JPY к Ask ближе чем 16 пунктов, терминал вернет ошибку и ордер не будет выставлен.
Стоп-лос, Тейк-профит – если стоп-лос или тейк-профит задан ближе от цены открытия чем параметр «минимальная дистанция», та же ошибка.
При работе с маркет ордером нужно проверять стоп-лос и тейк-профит относительно цены открытия на минимально допустимое расстояние.
При работе с отложенным ордерам, помимо проверки стоп-лоса и тейк-профита нужно еще проверить и расстояние цену открытия относительно текущей цены.
Не забудьте объявить переменные. Теперь наш «эксперт» готов к работе, но Вы думаю еще помните что он делался не для торговли а как пример для изучения основ языка MQL4.
Полный код «советника».
//+------------------------------------------------------------------+ //| TradeOnNews.mq4 | //| Copyright © 2008, MetaQuotes Software Corp. | //| | //+------------------------------------------------------------------+ #property copyright "Copyright © 2008, MetaQuotes Software Corp." #property link "http://www.metaquotes.net" //---- input parameters extern int Magic = 111; //число может быть любым extern string StartTime = "15:30"; // время открытия маркет ордера (по времени MetaTrader) extern string CloseTime = "16:00"; // Время закрытия ордеров extern int Delta = 15; // расстояние от цены для выставления стоп ордеров extern int StopLoss = 20; extern int TakeProfit = 30; extern double Lot = 0.1; int Ticket, day, EP, Min_Dist; string Symb, Text; double Mas_Ord[31][13]; // Массив ордеров int Mas_Tip[6]; // Массив колич. ордеров всех типов // [] тип орд: 0=B,1=S,2=BL,3=SL,4=BS,5=SS int Mas_Tip_T[6]; // Массив колич. ордеров всех типов - открытых сегодня //+------------------------------------------------------------------+ //| expert initialization function | //+------------------------------------------------------------------+ int init() { Symb = Symbol(); // Название фин.инстр. Min_Dist = MarketInfo(Symb,MODE_STOPLEVEL); //Минимально допустимый уровень стоп-лосса/тейк-профита в пунктах return(0); } //+------------------------------------------------------------------+ //| expert start function | //+------------------------------------------------------------------+ int start() { int total; double Price, LT, SL, TP; bool answer; total = OrdersTotal(); // сколько всего ордеров открыто Orders(); // вызываем функцию учета ордеров if(total > 1) // если ордеров больше одного - в нашем случае два { for(int i = 0; i < total; i ++) // начало цикла { if(OrderSelect(i, SELECT_BY_POS) == false) continue; // если ордер не выбран, возвращаемся в начало цыкла if(Magic != OrderMagicNumber()) continue; // если магическое число не совпадает с нашим Magic - возвращаемся в начало цикла Ticket = OrderTicket(); // запоминаем номер ордера if(Mas_Tip[0] > 0 && OrderType() == OP_SELLSTOP) // если ордеров бай больше нуля - есть бай ордер // и есть селстоп { Text ="SellStop"; // для функции inf... что бы вывести в сообщении с каким ордером работаем. inf(5, Ticket, -1); // вызываем информациную функцию... собщаем о попытке закрыть SellStop ордер answer = OrderDelete(Ticket); // удаляем селстоп if(answer == true) // если true значит все прошло успешно { // сообщаем об этом inf(7, Ticket, -1); // вызываем информациную функцию... собщаем что попытка закрыть SellStop ордер удалась return(0); // выходим из start } inf(15, GetLastError()); // если дошли сюда значит есть ошибка... узнаем какая именно return(0); // выходим из start } if(Mas_Tip[1] > 0 && OrderType() == OP_BUYSTOP) // если ордеров сел больше нуля - есть сел ордер // и есть байстоп { Text ="BuyStop"; // для функции inf... что бы вывести в сообщении с каким ордером работаем. inf(5, Ticket, -1); // вызываем информациную функцию... собщаем о попытке закрыть BuyStop ордер answer = OrderDelete(Ticket); // удаляем байстоп if(answer == true) // если true значит все прошло успешно { // сообщаем об этом inf(7, Ticket, -1); // вызываем информациную функцию... собщаем что попытка закрыть BuyStop ордер удалась return(0); // выходим из start } inf(15, GetLastError()); // если дошли сюда значит есть ошибка... узнаем какая именно return(0); // выходим из start } } // конец цикла } //----------------------------------- if(total == 1 && Mas_Tip[4] == 1) // если открыт один ордер и этот ордер байстоп { LT = Lot(); // вызываем функцию расчета лота if(LT == -1) return(0); // если ф-ция Lot() вернула -1 значит нет денег даже на минимальный лот - выходим из start() Price = Bid - Delta*Point; // вычисляем цену открытия селлстопа Price = NormalizeDouble(Price, Digits); // округляем до нужного нам числа цифр после запятой SL = Price + StopLoss*Point; // вычисляем стоплос SL = NormalizeDouble(SL, Digits); // округляем до нужного нам числа цифр после запятой TP = Price - TakeProfit*Point; // вычисляем тейкпрофит TP = NormalizeDouble(TP, Digits); // округляем до нужного нам числа цифр после запятой if(((Bid - Price) > Min_Dist*Point) && ((SL - Price) > Min_Dist*Point) && ((Price - TP) > Min_Dist*Point)) { //проверяем на мин. дистанцию - цену рткрытия, стоп-лос, тейк-профит Expiration(); // вызываем функцию Expiration для расчета продолжительности жизни отложеного ордера Text = "SellStop"; // для функции inf... что бы вывести в сообщении с каким ордером работаем. inf(1, Ticket, -1); // вызываем информациную функцию... собщаем о попытке открыть SellStop ордер Ticket = OrderSend(Symb,OP_SELLSTOP,LT,Price,3,SL,TP,NULL,Magic,EP,Red); // открываем селстоп if(Ticket > 0) // если Ticket больше 0 значит все прошло успешно... Ticket содержит номер ордера { // сообщаем об этом inf(2, Ticket, -1); // вызываем информациную функцию... собщаем что попытка открыть SellStop ордер удалась return(0); // выходим из start() } inf(15, GetLastError()); // если дошли сюда значит есть ошибка... узнаем какая именно return(0); // выходим из start } } //------------------------------- if((total == 0) && (TimeCurrent() > StrToTime(StartTime)) && (TimeCurrent() < StrToTime(StartTime) + 60)) { // если нет ни одного открытого ордера и текущее время перевалило за время открытия и текущее время меньше за время открытия плюс 60 секунд LT = Lot(); // вызываем функцию расчета лота if(LT == -1) return(0); // если ф-ция Lot() вернула -1 значит нет денег даже на минимальный лот - выходим из start Price = Ask + Delta*Point; // вычисляем цену открытия байстопа Price = NormalizeDouble(Price, Digits); // округляем до нужного нам числа цифр после запятой SL = Price - StopLoss*Point; // вычисляем стоплос SL = NormalizeDouble(SL, Digits); // округляем до нужного нам числа цифр после запятой TP = Price + TakeProfit*Point; // вычисляем тейкпрофит TP = NormalizeDouble(TP, Digits); // округляем до нужного нам числа цифр после запятой if(((Price - Ask) > Min_Dist*Point) && ((Price - SL) > Min_Dist*Point) && ((TP - Price) > Min_Dist*Point)) { //проверяем на мин. дистанцию - цену рткрытия, стоп-лос, тейк-профит Expiration(); // вызываем функцию Expiration для расчета продолжительности жизни отложеного ордера Text = "BuyStop"; // для функции inf... что бы вывести в сообщении с каким ордером работаем. inf(1, Ticket, -1); // вызываем информациную функцию... собщаем о попытке открыть BuyStop ордер Comment("Min_Dist = ",Min_Dist,"nLT = ",LT); Ticket = OrderSend(Symb,OP_BUYSTOP,LT,Price,3,SL,TP,NULL,Magic,EP,Blue); //открываем байстоп if(Ticket > 0) // если Ticket больше 0 значит все прошло успешно... Ticket содержит номер ордера { // сообщаем об этом inf(2, Ticket, -1); // вызываем информациную функцию... собщаем что попытка открыть BuyStop ордер удалась return(0); // выходим из start } inf(15, GetLastError()); // если дошли сюда значит есть ошибка... узнаем какая именно return(0); // выходим из start } } return(0); // выходим из start } //+------------------------------------------------------------------+ // расчет лота | //+------------------------------------------------------------------+ double Lot() { int q; double dLot, Min_Lot, Max_L, One_Lot; One_Lot = MarketInfo(Symb,MODE_MARGINREQUIRED); // Стоим. 1 лота Min_Lot = MarketInfo(Symb,MODE_MINLOT); // Мин. размер. лотов Max_L = MarketInfo(Symb,MODE_MAXLOT); // Мax. размер. лотов dLot = Lot; //--------- // код Вашего манименеджмента (ММ) //--------- if(Min_Lot == 0.1) q = 1; if(Min_Lot == 0.01) q = 2; dLot = NormalizeDouble(dLot,q); // округляем до нужного нам числа цифр после запятой if(dLot < Min_Lot) dLot = Min_Lot; // если лот задан меньше допустимого - задаем минимально возможный if(dLot > Max_L) dLot = Max_L; // если лот задан больше допустимого - задаем максимально возможный if(dLot*One_Lot > AccountFreeMargin()) // Не хватает даже.. { // ..на минимальн. лот:( inf(12,0,dLot); // Сообщение.. return(-1); // ..и выход } return(dLot); // ввозвращаем размер лота } //+------------------------------------------------------------------+ // заполняем масив даными об ордерах - сколько, каких и тд. | //+------------------------------------------------------------------+ int Orders() //Інформація про ордера... сколько, каких, цена открытия, стоплос и тд. { int Qnt = 0; // Счётчик количества ордеров Qnt = 0; // Обнуление счётчика ордеров ArrayInitialize(Mas_Ord,0); // Обнуление массива ArrayInitialize(Mas_Tip,0); // Обнуление массива ArrayInitialize(Mas_Tip_T,0); // Обнуление массива for(int i = 0; i < OrdersTotal(); i++)// По рыночн. и отлож. ордерам { if((OrderSelect(i,SELECT_BY_POS) == true) && (OrderSymbol() == Symb)) //Если есть следующ. //.. и наша вал.пара { if(Magic != OrderMagicNumber()) continue; // если магическое число не совпадает с нашим Magic - возвращаемся в начало цикла Qnt++; // Колич. ордеров Mas_Ord[Qnt][1] = OrderOpenPrice(); // Курс открытия орд Mas_Ord[Qnt][2] = OrderStopLoss(); // Курс SL Mas_Ord[Qnt][3] = OrderTakeProfit(); // Курс ТР Mas_Ord[Qnt][4] = OrderTicket(); // Номер ордера Mas_Ord[Qnt][5] = OrderLots(); // Количество лотов Mas_Tip[OrderType()]++; // Кол. ордеров типа Mas_Ord[Qnt][6] = OrderType(); // Тип ордера Mas_Ord[Qnt][7] = OrderMagicNumber(); // Магическое число if(OrderComment() == "") Mas_Ord[Qnt][8] = 0; // Если нет коммент else Mas_Ord[Qnt][8] = 1; // Если есть коммент Mas_Ord[Qnt][9] = OrderOpenTime(); // Время открвтия ордера Mas_Ord[Qnt][10] = OrderCloseTime(); // Время закрытия ордера Mas_Ord[Qnt][11] = OrderExpiration(); // Возвращает дату истечения для выбранного отложенного ордера. if(day == TimeDayOfYear(OrderOpenTime())) // работа с ордером ... { Mas_Tip_T[OrderType()]++; // Кол. ордеров типа... открытых сегодня } } } Mas_Ord[0][0] = Qnt; // Колич. ордеров return; } //+------------------------------------------------------------------+ // | //+------------------------------------------------------------------+ int Expiration() { // если время открытия больше чем время закрытия - додаем одни сутки в секундах(60*60*24) if(StrToTime(StartTime) > StrToTime(CloseTime)) EP = 60*60*24 + StrToTime(CloseTime); else EP = StrToTime(CloseTime); // если время открытия не больше чем время закрытия - не додаем } //----------------------------------------------------------------+ // | //----------------------------------------------------------------+ int inf(int Mess_Number, int Number=0, double Value=0.0) { string Graf_Text; // Строка сообщения switch(Mess_Number) // Переход на сообщение { case 1: Graf_Text = StringConcatenate("Попытка открыть ",Text," ордер. Ожидание ответа.."); break; case 2: Graf_Text = StringConcatenate("Открыт ",Text," ордер: № ",Number); break; case 3: Graf_Text = StringConcatenate("Попытка модифицировать ",Text," ордер: № ",Number,". Ожидание ответа.."); break; case 4: Graf_Text = StringConcatenate("Ордер ",Text," модифицирован: № ",Number); break; case 5: Graf_Text = StringConcatenate("Попытка закрыть ",Text," ордер № ",Number,". Ожидание ответа.."); break; case 6: Graf_Text = StringConcatenate("Закрыт ",Text," ордер: № ",Number); break; case 7: Graf_Text = StringConcatenate("Удалён отложенный ",Text," ордер № ",Number); break; case 8: Graf_Text = StringConcatenate("Ордер ",Number," преобразовался в рыночный"); break; case 9: Graf_Text = StringConcatenate("Переоткрыт ордер ",Number); break; case 10: Graf_Text = StringConcatenate("Частично закрыт ордер ",Number); break; case 11: Graf_Text = StringConcatenate("Новая минимальная дистанция: ",Number); break; case 12: Graf_Text = StringConcatenate("Не хватает денег на ",DoubleToStr(Value,2)," лотов"); break; case 15: switch(Number) // Переход на номер ошибки { case 2: Graf_Text = "Общая ошибка."; break; case 4: Graf_Text = "Торговый сервер занят."; if(IsTesting() == false) Sleep(5000); // если это не тестирование - "засыпаем" на 5 секунд. break; case 129: Graf_Text = "Неправильная цена."; break; case 130: Graf_Text = "Неправильная дистанция."; break; case 135: Graf_Text = "Цена изменилась."; break; case 136: Graf_Text = "Нет цен. Ждём новый тик.."; if(IsTesting() == false) Sleep(1); // если это не тестирование - "засыпаем" на 1 миллисекунду. break; case 137: Graf_Text = "Брокер занят."; if(IsTesting() == false)Sleep(5000); // если это не тестирование - "засыпаем" на 5 секунд. break; case 146: Graf_Text = "Подсистема торговли занята."; if(IsTesting() == false) Sleep(500); // если это не тестирование - "засыпаем" на пол секунды. break; case 5 : Graf_Text = "Старая версия терминала."; break; case 64: Graf_Text = "Счет заблокирован."; break; case 133: Graf_Text = "Торговля запрещена"; break; case 134: Alert("Недостаточно денег для совершения операции."); return(0); // Выход из функции default: Graf_Text = StringConcatenate("Возникла ошибка ",Number); } break; default: Graf_Text = StringConcatenate("default ",Mess_Number); } Alert(Graf_Text); } //+------------------------------------------------------------------+
Исходник: TradeOnNews-0.4
Обработка ошибок — https://forexlab.ru/mql-error-handling/