Урок №10: Наш первый советник

Уважаемые коллеги, на этом уроке мы напишем наш первый советник, познакомимся с некоторыми основными функциями языка MQL4, а также напишем несколько своих.

Приступим, и для начала запустим MetaEditor…

 

MetaEditor

 

и создадим шаблон эксперта, ничего не меняя, просто нажимаем кнопку «Далее»…

Мастер создания советника

Обязательно заполняем, как минимум, поле «Имя» — это название нашего будущего советника, поля «Автор» и «Ссылка» не обязательны к заполнению. Добавляем параметры «Lots», «TakeProfit», «StopLoss», обращая внимание тип данных каждого параметра. После заполнения нажимаем кнопку «Далее»:

Мастер создания советника

На следующей форме мастера мы также ничего не отмечаем, просто нажимаем кнопку «Далее»:

Мастер создания советника

И очередное окно мы оставляем без заполнения, нажимаем кнопку «Готово»:

Мастер создания советника

Итак, мы получили шаблон будущего эксперта, с которым будем работать.

MetaEditor

 

Итак, мы предусмотрим наш будущий советник для работы с 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 возвращает количество десятичных знаков после запятой, определяющее точность измерения цены символа текущего графика, таким образом:

  1. Я проверяю количество знаков после запятой у текущей валютной пары и если количество знаков равно 3 или равно 5, то делаю вывод, что мой брокер работает с пятизначными котировками.
  2. Следующим шагом я умножаю TakeProfit и StopLoss на 10. Запись «TakeProfit *= 10;» эквивалентна записи «TakeProfit = TakeProfit * 10;«

 

Продолжим.

Теперь нужно определиться как мы будем открывать позиции, в какую сторону и в какой момент? Покупать или продавать?

Осмелюсь предложить воспользоваться для этих целей индикатором «MovingAverage» и у нас будет следующий алгоритм:

  1. Если текущая цена будет выше, чем значение индикатора, то будем покупать.
  2. Если текущая цена будет ниже, чем значение индикатора, то будем продавать.
  3. Закрытие позиций будет происходить либо по TakeProfit, либо по StopLoss.

Язык программирования MQL4 предоставляет нам ряд функций для работы с индикаторами, и как раз одна из них позволяет получить нам текущее значения индикатора:

Moving Average

Итак, у нас есть функция 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» с заданными параметрами, значения которых определены в параметрах самого эксперта.

Также нам понадобится несколько функций:

  1. Функция подсчёта количества количества ордеров на покупку.
  2. Функция подсчёта количества количества ордеров на продажу.

Добавим их в код советника:

//+------------------------------------------------------------------+
//| Возвращает количество позиций на покупку                         |
//+------------------------------------------------------------------+
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.

 

Written by 

Один комментарий к “Урок №10: Наш первый советник”

Добавить комментарий