MQL5 — Простой советник на индикаторах

Всем привет! Сегодня мы продолжим работать над нашим советником, добавим в него простейшие торговые сигналы по индикатору и модернизируем различные функции советника для удобства его эксплуатации.


Предыдущие уроки


MQL5 Программирование: Пишем Простой советник на индикаторе

Логика в обработчике OnInit

Событие Init генерируется сразу после загрузки эксперта или индикатора. Обработчик OnInit имеет два варианта – с возвратом значения и без. С возвратом:

int  OnInit(void);

Версия без возврата результата оставлена только для совместимости со старыми кодами:

void  OnInit(void);

Функция OnInit() типа void всегда означает удачную инициализацию и не рекомендуется к использованию. Стоит использовать версию с кодом возврата.

Модифицируем нашу функцию OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- проверим на корректность значение ma_period
   if (ma_period <= 0 || lot <= 0 || deviation < 0 || tp < 0 || sl < 0) {
      PrintFormat("Недопустимое значение входного параметра");
      return (INIT_PARAMETERS_INCORRECT);
   }
   stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   //--- если уровень минимально допустимого отступа в пунктах от текущей цены закрытия не задан
   if (stop_level <= 0) {
      stop_level = 50; // зададим отступ в 50 пунктов от текущей цены закрытия
   }
   price_level = stop_level * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   
   //--- при оптимизации 
   if (MQLInfoInteger(MQL_OPTIMIZATION)) {
      //--- проверим объем доступной оперативной памяти для агента
      int available_memory_mb = TerminalInfoInteger(TERMINAL_MEMORY_TOTAL);
      if (available_memory_mb < 2000) {
         PrintFormat("Недостаточный объем памяти для агента тестирования: %d MB", available_memory_mb);
         return (INIT_AGENT_NOT_SUITABLE);
      }
   }
   //--- проверим подключение к торговому серверу
   if (!TerminalInfoInteger(TERMINAL_CONNECTED)) {
      Print("Нет подключения к торговому серверу!");
      return (INIT_FAILED);
   }
   //--- проверим разрешение на использование DLL
   if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) || !MQLInfoInteger(MQL_DLLS_ALLOWED)) {
      Print("Использование DLL запрещено!");
      return (INIT_FAILED);
   }
   //--- проверим разрешение на торговлю
   if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)) {
      Print("Торговля запрещена!");
      return (INIT_FAILED);
   }
   //--- проверим наличие индикатора
   ma_handle = iMA(_Symbol, _Period, ma_period, 0, MODE_SMA, PRICE_CLOSE);
   if (ma_handle == INVALID_HANDLE) {
      PrintFormat("Не удалось создать хэндл индикатора MA. Код ошибки %d", GetLastError());
      return (INIT_FAILED);
   }
   Print("Номер билда запущенного терминала: ", TerminalInfoInteger(TERMINAL_BUILD));
   Print("Наличие подключения к MQL5.community: ", TerminalInfoInteger(TERMINAL_COMMUNITY_CONNECTION));
   
   //--- инициализация эксперта прошла удачно 
   return(INIT_SUCCEEDED);
}

Модификация функции открытия позиций

Вместо ранее написанных функций MarketBuy и MarketSell мы напишем более универсальную:

//+------------------------------------------------------------------+
//| Покупка или продажа по рынку                                     |
//| int type:                                                        |
//| ORDER_TYPE_BUY                                                   |
//| ORDER_TYPE_SELL                                                  |
//+------------------------------------------------------------------+
void openMarketOrder(ENUM_ORDER_TYPE type)
{
   //--- сбросим код последней ошибки в ноль
   ResetLastError();
   MqlTradeRequest trade_request = {}; // Инициализация структуры торгового запроса
   trade_request.action = TRADE_ACTION_DEAL; // Тип - по рынку
   trade_request.symbol = _Symbol; // Текущий инструмент
   trade_request.volume = lot; // Объем
   if (type == ORDER_TYPE_BUY) {
      trade_request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Цена открытия
   } else {
      trade_request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID); // Цена открытия
   }
   trade_request.type = type; // Тип ордера
   trade_request.type_filling = ORDER_FILLING_FOK; // Политика исполнения (немедленно или отмена)
   trade_request.deviation = deviation; // допустимое отклонение от цены
   trade_request.magic = magic_number; // MagicNumber ордера
   
   MqlTradeResult trade_result = {}; // Инициализация структуры результата
   MqlTradeCheckResult check_result = {}; // Инициализация структуры проверки запроса
   // Проверка структуры торгового запроса
   bool res_check = OrderCheck(trade_request, check_result);
   if (!res_check) {
      Print(__FUNCTION__, " - результат проверки отрицательный, код ответа: ", check_result.retcode);
      Print(__FUNCTION__, " - расшифровка кода ответа: ", resultRetcodeText(check_result.retcode));
      Print(__FUNCTION__, " - код ошибки: ", GetLastError());
      ResetLastError();
   }
   // Отправка торгового запроса
   bool res_send = OrderSendAsync(trade_request, trade_result);
   if (!res_send) {
      uint answer = trade_result.retcode;
      Print(__FUNCTION__, " - результат отправки отрицательный, код ответа:", answer);
      Print(__FUNCTION__, " - расшифровка кода ответа:", resultRetcodeText(answer));
      Print(__FUNCTION__, " - код ошибки: ", GetLastError());
      ResetLastError();
   }
}

Торговая логика в методе OnTick

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   IndicatorRelease(ma_handle);
   Print(__FUNCTION__, " - Код причины деинициализации:", reason);
   Print(__FUNCTION__, " - Причина деинициализации:", getUninitReasonText(reason));
}

Метод OnTick будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   if (Bars(_Symbol,_Period) < ma_period * 2) { // общее количество баров на графике
      Print("На графике меньше ", ma_period * 2, " баров, советник не будет работать!");
      return;
   }
   
   bool is_new_bar = isNewBar();

   //--- советник должен проверять условия совершения новой торговой операции только на новом баре
   if (is_new_bar == false) {
      return;
   }
   
   MqlTick latest_price;       // Будет использоваться для текущих котировок
   MqlTradeRequest mrequest;    // Будет использоваться для отсылки торговых запросов
   MqlTradeResult mresult;      // Будет использоваться для получения результатов выполнения торговых запросов
   MqlRates rates[];           // Будет содержать цены, объемы и спред для каждого бара
   
   // Установим индексацию в массивах котировок и индикаторов, как в таймсериях
   ArraySetAsSeries(ma, true);
   ArraySetAsSeries(rates, true);

   //--- Получить текущее значение котировки в структуру типа MqlTick
   if (!SymbolInfoTick(_Symbol, latest_price)) {
      Alert("Ошибка получения последних котировок - ошибка: ",GetLastError());
      return;
   }

   //--- Получить исторические данные последних 3-х баров
   if (CopyRates(_Symbol,_Period,0,3,rates) < 0) {
      Alert("Ошибка копирования исторических данных - ошибка: ", GetLastError());
      return;
   }

   //--- Используя хэндлы индикаторов, копируем новые значения индикаторных буферов в массивы
   if (CopyBuffer(ma_handle, 0, 0, 3, ma) < 0) {
      Print("Ошибка копирования буферов индикатора MA - номер ошибки:",GetLastError());
      return;
   }
   bool buy_condition = (ma[2] < ma[1] && rates[1].close > ma[1]);
   bool sell_condition = (ma[2] > ma[1] && rates[1].close < ma[1]);
   
   if (buy_condition) {
      openMarketOrder(ORDER_TYPE_BUY);
   }
   
   if (sell_condition) {
      openMarketOrder(ORDER_TYPE_SELL);
   }
   
   modifySlTp();
}

Как видите, проверку на открытие нового бара мы вынесли в отдельную функцию для удобства:

//+------------------------------------------------------------------+
//| Для сохранения значения времени бара мы используем               |
//| static-переменную Old_Time.                                      |
//| При каждом выполнении функции OnTick мы будем сравнивать время   |
//| текущего бара с сохраненным временем.                            |
//| Если они не равны, это означает, что начал строиться новый бар.   |
//| Timer function                                                   |
//+------------------------------------------------------------------+
bool isNewBar()
{
   static datetime old_time;
   datetime new_time[1];
   bool is_new_bar = false;

   // копируем время текущего бара в элемент new_time[0]
   int copied = CopyTime(_Symbol, _Period, 0, 1, new_time);
   if (copied > 0) { // ok, успешно скопировано
      if (old_time != new_time[0]) { // если старое время не равно
         is_new_bar = true;   // новый бар
         if (MQL5InfoInteger(MQL5_DEBUGGING)) {
            Print("Новый бар", new_time[0], "старый бар", old_time);
         }
         old_time = new_time[0];   // сохраняем время бара
      }
   } else {
      Alert("Ошибка копирования времени, номер ошибки =",GetLastError());
      ResetLastError();
      return is_new_bar;
   }
   
   return is_new_bar;
}

Функция модификации стоп-лоссов и тейк-профитов

Как вы заметили, при открытии позиции стоп-лоссы и тейк-профиты не установлены. Поэтому нам нужна функция, которая модифицирует стоп-лоссы и тейк-профиты открытых позиций:

//+------------------------------------------------------------------+
//| Модификация значений Stop Loss и Take Profit у открытой позиции  |
//+------------------------------------------------------------------+
void modifySlTp()
{
   //--- объявление запроса и результата
   MqlTradeRequest request;
   MqlTradeResult  result;
   int total = PositionsTotal(); // количество открытых позиций
   //--- перебор всех открытых позиций
   for (int i = 0; i < total; i++) {
      //--- параметры ордера
      ulong  position_ticket = PositionGetTicket(i);// тикет позиции
      string position_symbol = PositionGetString(POSITION_SYMBOL); // символ 
      int    digits = (int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // количество знаков после запятой
      ulong  magic = PositionGetInteger(POSITION_MAGIC); // MagicNumber позиции
      double volume = PositionGetDouble(POSITION_VOLUME);    // объем позиции
      double position_sl = PositionGetDouble(POSITION_SL);  // Stop Loss позиции
      double position_tp = PositionGetDouble(POSITION_TP);  // Take Profit позиции
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);  // тип позиции
      
      //--- если MagicNumber совпадает, Stop Loss и Take Profit не заданы
      if(magic == magic_number && position_sl == 0 && position_tp == 0 && position_symbol == _Symbol) {
         //--- вычисление текущих ценовых уровней
         double price = PositionGetDouble(POSITION_PRICE_OPEN);
         //--- вычисление и округление значений Stop Loss и Take Profit
         if (type == POSITION_TYPE_BUY) {
            position_sl = NormalizeDouble(price - price_level - sl * _Point, digits);
            position_tp = NormalizeDouble(price + price_level + tp * _Point, digits);
         } else {
            position_sl = NormalizeDouble(price + price_level + sl * _Point, digits);
            position_tp = NormalizeDouble(price - price_level - tp * _Point, digits);
         }
         //--- обнуление значений запроса и результата
         ZeroMemory(request);
         ZeroMemory(result);
         //--- установка параметров операции
         request.action = TRADE_ACTION_SLTP; // тип торговой операции
         request.position = position_ticket;   // тикет позиции
         request.symbol = position_symbol;     // символ 
         request.sl = position_sl;                // Stop Loss позиции
         request.tp = position_tp;                // Take Profit позиции
         request.magic = magic_number;         // MagicNumber позиции
         //--- вывод информации о модификации
         PrintFormat("Modify #%I64d %s %s",position_ticket,position_symbol,EnumToString(type));
         //--- отправка запроса
         if (!OrderSend(request, result)) {
            uint answer = result.retcode;
            Print(__FUNCTION__, " - результат отправки отрицательный, код ответа:", answer);
            Print(__FUNCTION__, " - расшифровка кода ответа:", resultRetcodeText(answer));
            Print(__FUNCTION__, " - код ошибки: ", GetLastError());
            ResetLastError();
         }
         //--- информация об операции   
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
        }
    }
}

Закрытие открытых позиций

И последняя функция в этом уроке – функция закрытия позиций встречными:

//+------------------------------------------------------------------+
//| Модификация значений Stop Loss и Take Profit у открытой позиции  |
//+------------------------------------------------------------------+
void closeAll()
{
   //--- объявление запроса и результата
   MqlTradeRequest request;
   MqlTradeResult  result;
   int total = PositionsTotal(); // количество открытых позиций   
   //--- перебор всех открытых позиций
   for (int i = total-1; i >= 0; i--) {
      //--- параметры ордера
      ulong  position_ticket = PositionGetTicket(i);                                      // тикет позиции
      string position_symbol = PositionGetString(POSITION_SYMBOL);                        // символ 
      int    digits = (int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS);              // количество знаков после запятой
      ulong  magic = PositionGetInteger(POSITION_MAGIC);                                  // MagicNumber позиции
      double volume = PositionGetDouble(POSITION_VOLUME);                                 // объем позиции
      ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);    // тип позиции

      //--- если MagicNumber совпадает
      if (magic == magic_number && position_symbol == _Symbol) {
         //--- обнуление значений запроса и результата
         ZeroMemory(request);
         ZeroMemory(result);
         //--- установка параметров операции
         request.action = TRADE_ACTION_DEAL;        // тип торговой операции
         request.position = position_ticket;          // тикет позиции
         request.symbol = position_symbol;          // символ 
         request.volume = volume;                   // объем позиции
         request.deviation = deviation;            // допустимое отклонение от цены
         request.magic = magic_number;             // MagicNumber позиции
         //--- установка цены и типа ордера в зависимости от типа позиции
         if (type == POSITION_TYPE_BUY) {
            request.price = SymbolInfoDouble(position_symbol, SYMBOL_BID);
            request.type = ORDER_TYPE_SELL;
         } else {
            request.price = SymbolInfoDouble(position_symbol, SYMBOL_ASK);
            request.type = ORDER_TYPE_BUY;
         }
         //--- вывод информации о закрытии
         PrintFormat("Close #%I64d %s %s",position_ticket,position_symbol,EnumToString(type));
         //--- отправка запроса
         if (!OrderSend(request, result)) {
            uint answer = result.retcode;
            Print(__FUNCTION__, " - результат отправки отрицательный, код ответа:", answer);
            Print(__FUNCTION__, " - расшифровка кода ответа:", resultRetcodeText(answer));
            Print(__FUNCTION__, " - код ошибки: ", GetLastError());
            ResetLastError();
         }
         //--- информация об операции   
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
        }
    }
}

Заключение

Итак, мы написали простой индикаторный советник, который получает сигналы на покупку и продажу и модифицирует тейк-профиты и стоп-лоссы. Также мы добавили в обработчик OnInit различные проверки, которые повышают безопасность торговли и удобство использования эксперта. Мы добавили в советник индикатор и создали простейшие условия для генерации сигналов на вход. В следующем уроке допишем функции для открытия отложенных ордеров и попробуем написать трейлинг-стоп.

На этом все, всем пока!

Скачать советник из урока

Тема на форуме

С уважением, Дмитрий аkа Silentspec
Tlap.com

Уроки по MQL5 , , ,