Урок №12: Пишем советник по индикатору

На прошлом уроке мы с вами писали индикатор, который указывает входы в рынок. Соответственно сегодня мы займёмся разработкой эксперта, использующего его сигналы.

Начнём с создания шаблона советника(как это сделать мы разбирали на уроке №10).

Опишем входные параметры эксперта:

extern double Lots       = 0.1;    // объём сделки в лотах
extern int    StopLoss   = 40;     // ограничение убытка в пунктах 
extern int    TakeProfit = 70;     // профит в пунктах
extern int    Magic      = 123;    // магический номер ордеров
extern int    Slippage   = 5;      // проскальзывание в пунктах
extern string comment    = "Мой первый советник";

Значения StopLoss, TakeProfit и Slippage, присвоенные в параметрах, указаны для 4-х значного брокера, соответственно, чтобы эти же значения корректно работали у 5-ти значного ДЦ их нужно умножить на 10, что мы и сделаем в функции OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   if (Digits == 3 || Digits == 5)
   {
       StopLoss     *= 10;
       TakeProfit   *= 10;
       Slippage     *= 10;
   }
   return(INIT_SUCCEEDED);
}

 

Начало нашего советника положено, что уже хорошо. Давайте теперь рассмотрим входные параметры индикатора, а заодно и номера буферов для сигналов:

Параметры индикатора "MyIndicator"

Обычно я просто копирую параметры индикатора в код своего эксперта, в дальнейшем это позволит нам оптимизировать сам советник. В итоге получаем:

//+------------------------------------------------------------------+
extern double Lots            = 0.1; // объём сделки в лотах
extern int    StopLoss        = 40;  // ограничение убытка в пунктах
extern int    TakeProfit      = 70;  // профит в пунктах
extern int    Magic           = 123; // магический номер ордеров
extern int    Slippage        = 5;   // проскальзывание в пунктах
extern string comment         = "Мой первый советник";
//+------------------------------------------------------------------+
extern int    MA_Period       = 100;
extern double MACDOpenLevel   = 5;
extern int    fast_ema_period = 8;   // период быстрой средней 
extern int    slow_ema_period = 16;  // период медленной средней 
extern int    signal_period   = 9;   // период сигнальной линии 
//+------------------------------------------------------------------+

Переходим к функции OnTick и пишем заготовку обработчика, где описываю алгоритм работы эксперта:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if (CountBuy() == 0)
    {
       BuySignal = ...
       
       if (BuySignal > 0)
       {
           CloseSell();

           // Open buy order 
           
       }
    }

    if (CountSell() == 0)
    {
       SellSignal = ...

       if (SellSignal > 0)
       {
           CloseBuy();

           // Open sell order
           
       }
    }
}

Итак, по шагам:

  1. if (CountBuy() == 0) — я проверяю есть-ли у меня ордера на покупку, находящиеся в рынке, и если нет, то шаг 2.
  2. BuySignal = … — проверяем наличие сигнала на покупку по индикатору, кстати, это будет переменная, которую необходимо объявить заранее.
  3. if (BuySignal > 0) — если есть сигнал на покупку, то переходим к шагу 4.
  4. CloseSell() — закрываем открытые ордера на продажу, если они есть и переходим к шагу 5.
  5. Открываем ордер на покупку.

Точно такой же блок делается и для продаж.

Исходя из написанного алгоритма сразу видно, что предварительно нам потребуется несколько функций: CountBuy(), CountSell(), CloseBuy(), CloseSell(), а также написать код получения сигнала с индикатора.

Объявим в коде несколько переменных для дальнейшей работы и необходимые функции:

double BuySignal, SellSignal,
       SL, TP;
int    ticket;
bool   res;

Функция CountBuy():

//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на покупку                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountBuy() 
{  // объявляем переменную, в которой будем хранить количество ордеров
   // с типом OP_BUY 
   int count = 0;

   // Объявляем цикл с перебором ордеров   
   for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
   {
      // Если удалось выбрать ордер, находящийся в рынке
      if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
      {
         // у этого ордера совпадает валютная пара и его магический номер
         if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
         {
            // а также ордер является ордером на покупку,
            if (OrderType() == OP_BUY) 
            // то увеличиваем значение счётчика count на единицу
               count++;
         }
      }
   }
   // возвращаем количество ордеров на покупку
   return (count);
}

Аналогично пишется функция CountSell(), разве что проверяется тип ордера OP_SELL:

//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на продажу                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountSell() 
{
   int count = 0;
   
   for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
   {
      if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
      {
         if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
         {
            if (OrderType() == OP_SELL) 
               count++;
         }
      }
   }
   return (count);
}
//+------------------------------------------------------------------+

Далее пишем функцию CloseBuy():

//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на покупку, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseBuy()
{
   // объявляем цикл, в котором выполним перебор все ордеров
   // находящихся в рынке
   for(int index = OrdersTotal()-1; index >= 0; index--)
   {
      // если удалось выбрать рыночный ордер
      if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
      {
            // если валютная пара ордера, магический номер ордера и тип ордера совпадают
            // с тем, что нам необходимо,
            if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY)
                 // то пробуем закрыть ордер по рыночной цене
                  if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
                     // иначе выводим в журнал регистрации сообщение об ошибке
                     Print("Ошибка закрытия ордера на покупку, ticket = " + DoubleToStr(OrderTicket()));
      }
   }
}

Пишем практически всё то же самое и для закрытия ордеров на продажу, разница лишь в типе проверяемого ордера и цене закрытия, функция CloseSell():

//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на продажу, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseSell()
{
   for(int index = OrdersTotal()-1; index >= 0; index--)
   {
      if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
      {
            if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL)
                  if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
                     Print("Ошибка закрытия ордера на продажу, ticket = " + DoubleToStr(OrderTicket()));
      }
   }
}

 

На текущий момент нам осталось получить сигналы от индикаторы и написать код, открывающий ордера по его сигналам.

Для получения сигнала от любого пользовательского индикатора используется функция iCustom, обратившись к справке (F1) видим формат вызова функции:

iCustom

Теперь мы можем с легкостью получить сигнал на покупку в нашем советнике:

BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);

заодно и сигнал на продажу:

SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);

кстати, обратите внимание на предпоследний параметр mode, который мы передаём в функцию iCustom — это номер буфера индикатора, где хранятся сигналы покупок и продаж.

 

Таким образом наша функция OnTick() будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if (CountBuy() == 0)
    {
       // Получаем сигнал на покупку
       BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);
       
       // если есть сигнал индикатора
       if (BuySignal > 0)
       {
           // закрываем открытые ордера SELL
           CloseSell();
           
           // Open buy order
           // открываем ордер на покупку
           ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, comment, Magic, 0, Blue);

           // если ордер был успешно открыт,
           if (ticket > 0)
           {
               // то его необходимо выбрать, для дальнейшей установки ему StopLoss и TakeProfit
               if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
               {
                  // рассчитываем StopLoss и нормализуем цену
                  SL = NormalizeDouble(Ask-StopLoss*Point, Digits);

                  // рассчитываем TakeProfit и нормализуем цену
                  TP = NormalizeDouble(Ask+TakeProfit*Point, Digits);
                  
                  // попытаемся установить ордеру рассчитанные уровни StopLoss и TakeProfit
                  res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
                  if (!res)
                     // и если не удалось, то выведем сообщение в журнал регистрации для дальнейшего анализа причин
                     Print("Ошибка модификации ордера на покупку, ASK=" + DoubleToStr(Ask) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
               }
           }
       }
    }

    if (CountSell() == 0)
    {
       SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);

       if (SellSignal > 0)
       {
           CloseBuy();
           
           ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, comment, Magic, 0, Red);
           if (ticket > 0)
           {
               if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
               {
                  SL = NormalizeDouble(Bid+StopLoss*Point, Digits);
                  TP = NormalizeDouble(Bid-TakeProfit*Point, Digits);
                  res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
                  if (!res)
                     Print("Ошибка модификации ордера на продажу, BID=" + DoubleToStr(Bid) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
               }
           }
       }
    }
}

 

Собственно говоря, на этом всё, разработка советника на пользовательском индикаторе завершена.

Полный код эксперта:

//+------------------------------------------------------------------+
//|                                                    My_Expert.mq4 |
//|                              Copyright 2017, xbms, http://mql.su |
//|                                              mailto:xbms@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, xbms"
#property link      "http://mql.su"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
extern double     Lots            = 0.1;
extern int        StopLoss        = 40;
extern int        TakeProfit      = 70;
extern int        Magic           = 123;
extern int        Slippage        = 5;
extern string     comment         = "Мой первый советник";
//+------------------------------------------------------------------+
extern int        MA_Period       = 100;
extern double     MACDOpenLevel   = 5;
extern int        fast_ema_period = 8;   // период быстрой средней 
extern int        slow_ema_period = 16;  // период медленной средней 
extern int        signal_period   = 9;   // период сигнальной линии 
//+------------------------------------------------------------------+
double BuySignal, SellSignal,
SL, TP;
int    ticket;
bool   res;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
if (Digits == 3 || Digits == 5)
{
StopLoss     *= 10;
TakeProfit   *= 10;
Slippage     *= 10;
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
if (CountBuy() == 0)
{
// Получаем сигнал на покупку
BuySignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 0, 1);
// если есть сигнал индикатора
if (BuySignal > 0)
{
// закрываем открытые ордера SELL
CloseSell();
// Open buy order
// открываем ордер на покупку
ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, 0, 0, comment, Magic, 0, Blue);
// если ордер был успешно открыт,
if (ticket > 0)
{
// то его необходимо выбрать, для дальнейшей установки ему StopLoss и TakeProfit
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
// рассчитываем StopLoss и нормализуем цену
SL = NormalizeDouble(Ask-StopLoss*Point, Digits);
// рассчитываем TakeProfit и нормализуем цену
TP = NormalizeDouble(Ask+TakeProfit*Point, Digits);
// попытаемся установить ордеру рассчитанные уровни StopLoss и TakeProfit
res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
if (!res)
// и если не удалось, то выведем сообщение в журнал регистрации для дальнейшего анализа причин
Print("Ошибка модификации ордера на покупку, ASK=" + DoubleToStr(Ask) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
}
}
}
}
if (CountSell() == 0)
{
SellSignal = iCustom(NULL, 0, "MyIndicator", MA_Period, MACDOpenLevel, fast_ema_period, slow_ema_period, signal_period, 1, 1);
if (SellSignal > 0)
{
CloseBuy();
ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage, 0, 0, comment, Magic, 0, Red);
if (ticket > 0)
{
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
{
SL = NormalizeDouble(Bid+StopLoss*Point, Digits);
TP = NormalizeDouble(Bid-TakeProfit*Point, Digits);
res = OrderModify(OrderTicket(), OrderOpenPrice(), SL, TP, 0);
if (!res)
Print("Ошибка модификации ордера на продажу, BID=" + DoubleToStr(Bid) + ", SL=" + DoubleToStr(SL) + ", TP=" + DoubleToStr(TP)); 
}
}
}
}
}
//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на покупку, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseBuy()
{
// объявляем цикл, в котором выполним перебор все ордеров
// находящихся в рынке
for(int index = OrdersTotal()-1; index >= 0; index--)
{
// если удалось выбрать рыночный ордер
if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
{
// если валютная пара ордера, магический номер ордера и тип ордера совпадают
// с тем, что нам необходимо,
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_BUY)
// то пробуем закрыть ордер по рыночной цене
if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
// иначе выводим в журнал регистрации сообщение об ошибке
Print("Ошибка закрытия ордера на покупку, ticket = " + DoubleToStr(OrderTicket()));
}
}
} 
//+------------------------------------------------------------------+
//| Фукнция закрывает все ордера на продажу, находящиеся в рынке     |
//+------------------------------------------------------------------+
void CloseSell()
{
for(int index = OrdersTotal()-1; index >= 0; index--)
{
if (OrderSelect(index, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic && OrderType() == OP_SELL)
if (!OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), Slippage, Black))
Print("Ошибка закрытия ордера на продажу, ticket = " + DoubleToStr(OrderTicket()));
}
}
} 
//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на покупку                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountBuy() 
{  // объявляем переменную, в которой будем хранить количество ордеров
// с типом OP_BUY 
int count = 0;
// Объявляем цикл с перебором ордеров   
for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
{
// Если удалось выбрать ордер, находящийся в рынке
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
{
// у этого ордера совпадает валютная пара и его магический номер
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
{
// а также ордер является ордером на покупку,
if (OrderType() == OP_BUY) 
// то увеличиваем значение счётчика count на единицу
count++;
}
}
}
// возвращаем количество ордеров на покупку
return (count);
}
//+------------------------------------------------------------------+
//| Функция возвращает количество ордеров на продажу                 |
//| находящихся в рынке                                              |
//+------------------------------------------------------------------+
int CountSell() 
{
int count = 0;
for (int trade = OrdersTotal() - 1; trade >= 0; trade--) 
{
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic)
{
if (OrderType() == OP_SELL) 
count++;
}
}
}
return (count);
}
//+------------------------------------------------------------------+

Исходный код советника доступен для скачивания My_Expert.mq4

 

 

Written by 

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