Уважаемые коллеги, на этом уроке мы напишем наш первый советник, познакомимся с некоторыми основными функциями языка MQL4, а также напишем несколько своих.
Приступим, и для начала запустим MetaEditor…
и создадим шаблон эксперта, ничего не меняя, просто нажимаем кнопку «Далее»…
Обязательно заполняем, как минимум, поле «Имя» — это название нашего будущего советника, поля «Автор» и «Ссылка» не обязательны к заполнению. Добавляем параметры «Lots», «TakeProfit», «StopLoss», обращая внимание тип данных каждого параметра. После заполнения нажимаем кнопку «Далее»:
На следующей форме мастера мы также ничего не отмечаем, просто нажимаем кнопку «Далее»:
И очередное окно мы оставляем без заполнения, нажимаем кнопку «Готово»:
Итак, мы получили шаблон будущего эксперта, с которым будем работать.
Итак, мы предусмотрим наш будущий советник для работы с 4-х и 5-значным брокером и тут два варианта как это реализовать. Можно заменить вид параметра «input» на «extern» для того, чтобы можно было из кода советника модифицировать эти значения (значения с видом параметра «input» изменять в коде эксперта нельзя!)
//--- input parameters extern double Lots=0.1; extern int TakeProfit=70; extern int StopLoss=40;
или добавить глобальные переменные «TP» и «SL», в которые скопируем значения «TakeProfit» и «StopLoss» и работать уже с ними.
Я выбираю первый вариант, т.е. меняю «input» на «extern«.
Далее в функции «OnInit» добавляю следующий код:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (Digits == 3 || Digits == 5) { TakeProfit *= 10; StopLoss *= 10; } return(INIT_SUCCEEDED); }
Поясняю:
Функция Digits возвращает количество десятичных знаков после запятой, определяющее точность измерения цены символа текущего графика, таким образом:
- Я проверяю количество знаков после запятой у текущей валютной пары и если количество знаков равно 3 или равно 5, то делаю вывод, что мой брокер работает с пятизначными котировками.
- Следующим шагом я умножаю TakeProfit и StopLoss на 10. Запись «TakeProfit *= 10;» эквивалентна записи «TakeProfit = TakeProfit * 10;«
Продолжим.
Теперь нужно определиться как мы будем открывать позиции, в какую сторону и в какой момент? Покупать или продавать?
Осмелюсь предложить воспользоваться для этих целей индикатором «MovingAverage» и у нас будет следующий алгоритм:
- Если текущая цена будет выше, чем значение индикатора, то будем покупать.
- Если текущая цена будет ниже, чем значение индикатора, то будем продавать.
- Закрытие позиций будет происходить либо по TakeProfit, либо по StopLoss.
Язык программирования MQL4 предоставляет нам ряд функций для работы с индикаторами, и как раз одна из них позволяет получить нам текущее значения индикатора:
Итак, у нас есть функция iMA, из справочника(вызывается по клавише «F1») нам известны её параметры, часть из которых я сразу же переношу во внешние параметры советника для последующей оптимизации параметров самого индикатора:
//+-----------------------------------------------------------------------------+ extern double Lots = 0.1; extern int TakeProfit = 70; extern int StopLoss = 40; // Магический номер открытого ордера extern int Magic = 12345; //+-----------------------------------------------------------------------------+ extern string MA = "Настройки индикатора Moving average"; extern int ma_period = 20; extern int ma_shift = 8; extern ENUM_MA_METHOD ma_method = MODE_SMA; extern ENUM_APPLIED_PRICE applied_price = PRICE_CLOSE; extern int shift = 1; //+-----------------------------------------------------------------------------+
Обратите внимание на то, что я заменил тип «int» у двух параметров на перечисления «ENUM_MA_METHOD» и «ENUM_APPLIED_PRICE». Сделал я это для удобства восприятия самих параметров, согласитесь, что прочитать, к примеру, у параметра ma_method значение «MODE_SMA» гораздо понятней, чем наблюдать простое число. Кроме того я заранее добавил ещё один параметр «Magic». Он нам понадобится для открытия ордера.
Теперь переходим к функции «OnTick»(она выполняется каждый раз при поступлении новой котировки по валютной паре) и получим значение индикатора MovingAverage:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double ma = iMA(NULL, 0, ma_period, ma_shift, ma_method, applied_price, shift); } //+------------------------------------------------------------------+
Мы объявляем переменную «ma» и записываем в неё значение функции «iMA» с заданными параметрами, значения которых определены в параметрах самого эксперта.
Также нам понадобится несколько функций:
- Функция подсчёта количества количества ордеров на покупку.
- Функция подсчёта количества количества ордеров на продажу.
Добавим их в код советника:
//+------------------------------------------------------------------+ //| Возвращает количество позиций на покупку | //+------------------------------------------------------------------+ int CountBuy() { int order_total=0; for(int cnt=0; cnt<OrdersTotal(); cnt++) { if (OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES)) { if (OrderType()==OP_BUY && OrderSymbol()==Symbol() && OrderMagicNumber()== Magic) order_total++; } } return(order_total); } //+------------------------------------------------------------------+ //| Возвращает количество позиций на продажу | //+------------------------------------------------------------------+ int CountSell() { int order_total=0; for(int cnt=0; cnt<OrdersTotal(); cnt++) { if (OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES)) { if (OrderType()==OP_SELL && OrderSymbol()==Symbol() && OrderMagicNumber()== Magic) order_total++; } } return(order_total); } //+------------------------------------------------------------------+
Отлично, теперь разберём каждую строчку функции «CountBuy()«:
int CountBuy() // Объявление функции без параметров, тип возвращаемого значения - int(целое число) { // Объявление переменной order_total с типом int // В этой переменной будет храниться счётчик количества открытых позиций, находящихся в рынке int order_total=0; // Объявляем цикл, для того чтобы пересчитать все ордера, которые мы открыли. // Функция OrdersTotal() возвращает общее количество открытых и отложенных ордеров. for(int cnt=0; cnt<OrdersTotal(); cnt++) { // Для того чтобы определить параметры текущего ордера, его нужно "выбрать" // Функция OrderSelect выбирает ордер для дальнейшей работы с ним. // Возвращает true при успешном завершении функции или false в случае ошибки. // Поэтому сразу проверяем: если ордер "выбран", if (OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES)) { // то далее проверяем // тип ордера == Покупка И валютную пару И его магический номер if (OrderType()==OP_BUY && OrderSymbol()==Symbol() && OrderMagicNumber()== Magic) // и если всё это совпадает, то увеличиваем значение переменной order_total на единицу order_total++; } } // и в завершении пересчёта возвращаем значение переменной order_total return(order_total); }
Точно аналогичное описание применимо и для функции «CountSell()«. Тут всё тоже самое, кроме одного условия:
if (OrderType()==OP_SELL ...
т.е. проверяется тип ордера «на продажу».
Далее вносим следующие дополнения в тело функции «OnTick()»:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Объявляем переменную для работы с тикетом ордера int ticket; // Объявляем перменные для расчёта цен TakeProfit и StopLoss double tp, sl; // Получаем значение индикатора "MovingAverage" double ma = iMA(NULL, 0, ma_period, ma_shift, ma_method, applied_price, shift); // Если текущая цена выше значения индикатора И количество открытых позиций равно нулю, if (Ask > ma && CountBuy() == 0) { // то открываем ордер на покупку с записью его тикета в переменную "ticket" ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, 5, 0, 0, "", Magic, 0, Blue); // Если по какой-то причине ордер открыть не удалось, if (ticket < 1) { // то выводим в журнал информацию об ошибке Print("Ошибка открытия ордера на покупку по цене " + DoubleToStr(Ask)); return; } // Итак, ордер открылся, но у него на данный момент StopLoss и TakeProfit равны нулю // Необходимо модифицировать ордер, а для этого его нужно "выбрать" if (!OrderSelect(ticket, SELECT_BY_TICKET)) { Print("Не удалось выбрать ордер на покупку, ticket = " + IntegerToString(ticket)); return; } // Рассчитываем цену для StopLoss, а функция NormalizeDouble обязательно необходима // для приведения цены к требуемой точности sl = NormalizeDouble(Ask - StopLoss * Point, Digits); // Аналогично действуем с расчётом TakeProfit tp = NormalizeDouble(Ask + TakeProfit * Point, Digits); // Цены StopLoss и TakeProfit расчитаны, теперь модифицируем ордер if (!OrderModify(ticket, OrderOpenPrice(), sl, tp, 0)) { // и если это не удалось, то выведем сообщение об ошибке в журнал Print("Ошибка модификации SL, TP ордера на покупку, ticket = " + IntegerToString(ticket)); return; } Print("Успешная модификация ордера на покупку!"); } }
т.е. мы объявили переменные ticket, sl, tp для дальнейшей работы с ними, а также добавили код для открытия ордера на покупку.
Теперь дополним блоком для открытия ордера на продажу, я обычно копирую уже написанный код для покупок и меняю условия и параметры функций зеркально для продаж:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Объявляем переменную для работы с тикетом ордера int ticket; // Объявляем перменные для расчёта цен TakeProfit и StopLoss double tp, sl; // Получаем значение индикатора "MovingAverage" double ma = iMA(NULL, 0, ma_period, ma_shift, ma_method, applied_price, shift); // Если текущая цена выше значения индикатора И количество открытых позиций на покупку равно нулю, if (Ask > ma && CountBuy() == 0) { // то открываем ордер на покупку с записью его тикета в переменную "ticket" // Ask - это текущая цена для ордера на покупку ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, 5, 0, 0, "", Magic, 0, Blue); // Если по какой-то причине ордер открыть не удалось, if (ticket < 1) { // то выводим в журнал информацию об ошибке Print("Ошибка открытия ордера на покупку по цене " + DoubleToStr(Ask)); return; } // Итак, ордер открылся, но у него на данный момент StopLoss и TakeProfit равны нулю // Необходимо модифицировать ордер, а для этого его нужно "выбрать" if (!OrderSelect(ticket, SELECT_BY_TICKET)) { Print("Не удалось выбрать ордер на покупку, ticket = " + IntegerToString(ticket)); return; } // Рассчитываем цену для StopLoss, а функция NormalizeDouble обязательно необходима // для приведения цены к требуемой точности sl = NormalizeDouble(Ask - StopLoss * Point, Digits); // Аналогично действуем с расчётом TakeProfit tp = NormalizeDouble(Ask + TakeProfit * Point, Digits); // Цены StopLoss и TakeProfit расчитаны, теперь модифицируем ордер if (!OrderModify(ticket, OrderOpenPrice(), sl, tp, 0)) { // и если это не удалось, то выведем сообщение об ошибке в журнал Print("Ошибка модификации SL, TP ордера на покупку, ticket = " + IntegerToString(ticket)); return; } Print("Успешная модификация ордера на покупку!"); } // Условия для открытия ордера на продажу зеркальны // Если текущая цена ниже значения индикатора И количество открытых позиций на продажу равно нулю, if (Bid < ma && CountSell() == 0) { // то открываем ордер на покупку с записью его тикета в переменную "ticket" // Bid - это текущая цена для ордера на продажу ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, 5, 0, 0, "", Magic, 0, Red); // Если по какой-то причине ордер открыть не удалось, if (ticket < 1) { // то выводим в журнал информацию об ошибке Print("Ошибка открытия ордера на продажу по цене " + DoubleToStr(Bid)); return; } // Итак, ордер открылся, но у него на данный момент StopLoss и TakeProfit равны нулю // Необходимо модифицировать ордер, а для этого его нужно "выбрать" if (!OrderSelect(ticket, SELECT_BY_TICKET)) { Print("Не удалось выбрать ордер на продажу, ticket = " + IntegerToString(ticket)); return; } // Рассчитываем цену для StopLoss, а функция NormalizeDouble обязательно необходима // для приведения цены к требуемой точности sl = NormalizeDouble(Bid + StopLoss * Point, Digits); // Аналогично действуем с расчётом TakeProfit tp = NormalizeDouble(Bid - TakeProfit * Point, Digits); // Цены StopLoss и TakeProfit расчитаны, теперь модифицируем ордер if (!OrderModify(ticket, OrderOpenPrice(), sl, tp, 0)) { // и если это не удалось, то выведем сообщение об ошибке в журнал Print("Ошибка модификации SL, TP ордера на продажу, ticket = " + IntegerToString(ticket)); return; } Print("Успешная модификация ордера на продажу!"); } }
На этом, пожалуй, всё. Простейший советник написан, остаётся его скомпилировать и обратить внимание на журнал ошибок:
Ну и конечно же его стоит протестировать, поэтому запускаем терминал и открываем форму тестера стратегий:
После компиляции наш первый советник в окне «Навигатор» и доступен для тестирования на форме тестера стратегий.
Выбираем параметры запуска, т.е. указываем, что тестировать будем советник «First_Expert.ex4», а также валютную пару и период:
Нажимаем кнопку «Старт» и смотрим результат:
На вкладке «График» тестера стратегий можно посмотреть график баланса.
Ну что-ж, для первого раза вполне неплохо, на этом наш урок закончен.
По ссылке доступен для скачивания полный код нашего с вами советника First_Expert.
Один комментарий к “Урок №10: Наш первый советник”