Обработка торговых событий в MQL5: как открыть/закрыть ордер правильно

,

На чтение потребуется

4 минуты

Многие начинающие и даже опытные разработчики на MQL5 сталкиваются с ситуацией, когда их советник пытается открыть ордер несколько раз подряд или, наоборот, пропускает момент исполнения сделки. Это происходит из-за непонимания асинхронной природы торговли в MетаТрейдере 5 и отсутствия грамотной обработки торговых событий.

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


1. Почему простой «if(OrderSend)» — это путь к ошибкам?

В MQL4 трейдеры привыкли к синхронной торговле: отправил ордер — и тут же получил результат. В MQL5 всё иначе. Торговая система асинхронна. Функции отправки ордеров (OrderSendAsync) только ставят задачу в очередь сервера, но не ждут её исполнения.

Типичная ошибка новичка:

  1. Советник получил сигнал на Buy.
  2. Отправил запрос OrderSend.
  3. Не дождавшись подтверждения от сервера, на следующем тике снова видит тот же сигнал.
  4. Отправляет еще один ордер.
  5. В итоге — открыто 5 позиций вместо одной.

Решение: Использовать событийно-ориентированное программирование. Главный инструмент здесь — функция OnTradeTransaction().

2. Анатомия правильной сделки: структура TRADE_TRANSACTION

Функция OnTradeTransaction() вызывается автоматически при любом изменении состояния торгового запроса. Чтобы понять, что именно произошло (ордер только поставлен в очередь, принят брокером или уже исполнен), нужно анализировать её параметры.

Вот «скелет» правильного обработчика:

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//--- Проверяем тип транзакции
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
     {
      //--- Это сделка (DEAL) была добавлена в историю (значит, ордер исполнился)
      //--- Теперь нужно проверить, принадлежит ли эта сделка нашему эксперту
      if(IsMyDeal(trans.deal))
        {
         //--- Получаем данные по сделке из истории
         ulong deal_ticket = trans.deal;
         //--- Здесь мы точно знаем, что позиция открыта или закрыта
         HandleDealEvent(deal_ticket);
        }
     }
  }

3. Пошаговая инструкция: Отслеживаем открытие позиции

Чтобы поймать момент, когда рыночный ордер (Buy/Sell) превратился в открытую позицию, мы должны реагировать на появление сделки (DEAL_ADD).

Шаг 1. Пишем функцию проверки «нашего» ордера

Нам нужно отсеивать сделки, которые советник совершил вручную, и ловить только те, что инициированы нашей программой. Самый надежный способ — проверять магический номер.

bool IsMyDeal(ulong deal_ticket)
  {
   //--- Запросим историю сделок
   if(HistorySelect(0, TimeCurrent())) // Берем историю от 0 до текущего момента
     {
      //--- Получим свойства сделки по тикету
      ulong order_in_deal = HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
      if(order_in_deal > 0)
        {
         //--- Получим ордер, который породил эту сделку
         if(HistoryOrderSelect(order_in_deal))
           {
            long magic = HistoryOrderGetInteger(order_in_deal, ORDER_MAGIC);
            //--- Сравниваем с магиком нашего советника
            if(magic == InpMagicNumber) // InpMagicNumber - внешняя переменная
              {
               return true;
              }
           }
        }
     }
   return false;
  }

Шаг 2. Реагируем на событие

Теперь внутри OnTradeTransaction() мы можем запустить логику пост-обработки. Например, установить трейлинг-стоп сразу после открытия.

void HandleDealEvent(ulong deal_ticket)
  {
   //--- Определяем тип сделки (Buy или Sell)
   ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket, DEAL_TYPE);

   if(deal_type == DEAL_TYPE_BUY || deal_type == DEAL_TYPE_SELL)
     {
      double open_price = HistoryDealGetDouble(deal_ticket, DEAL_PRICE);
      double volume = HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
      string symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);

      Print("Позиция открыта! Тикет сделки: ", deal_ticket, " Цена: ", open_price);

      // Здесь можно сразу модифицировать позицию, поставив стопы.
      // Для этого нужно найти тикет позиции, связанный с этой сделкой.
      ulong position_ticket = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);
      SetStopLossAndTakeProfit(position_ticket); // Ваша функция модификации
     }
  }

4. Как правильно закрыть ордер и убедиться, что он закрылся

Закрытие позиции ничем не отличается от открытия с точки зрения событий. Вы отправляете запрос на закрытие, а сервер возвращает результат. Но фатальная ошибка многих кодеров — проверка статуса закрытия сразу после вызова PositionClose().

Неправильно:

bool res = PositionClose(_Symbol);
if(res) Print("Закрыто!");
else Print("Ошибка");

Это сработает только в тестере стратегий. На реальном счете res может вернуть true (запрос принят), но позиция еще открыта.

Правильный подход:

  1. Устанавливаем флаг «ожидание закрытия».
  2. В OnTradeTransaction() ловим сделку типа DEAL_TYPE_SELL (для закрытия Long позиции) или DEAL_TYPE_BUY (для закрытия Short позиции) с нашим магиком.
  3. Сбрасываем флаг.

Пример флага состояния:
В глобальной области советника объявите переменную:

bool waiting_for_close = false;
ulong waiting_position_id = 0;

При отправке команды на закрытие:

void ClosePosition(ulong position_id)
  {
   waiting_for_close = true;
   waiting_position_id = position_id;
   //--- Отправляем запрос на закрытие
   MqlTradeRequest req = {0};
   MqlTradeResult res = {0};
   req.action = TRADE_ACTION_DEAL;
   req.position = position_id;
   req.symbol = _Symbol;
   req.volume = GetPositionVolume(position_id);
   req.type = ORDER_TYPE_SELL; // Для примера, нужно определять тип позиции
   req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   req.deviation = 10;
   //... заполнение других полей
   OrderSend(req, res);
  }

И финальная проверка в обработчике:

void OnTradeTransaction(...)
  {
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
     {
      if(IsMyDeal(trans.deal))
        {
         //--- Получаем ID позиции из сделки
         long pos_id = HistoryDealGetInteger(trans.deal, DEAL_POSITION_ID);

         //--- Если мы ждали закрытия именно этой позиции
         if(waiting_for_close && waiting_position_id == (ulong)pos_id)
           {
            //--- Проверяем объем сделки. Если объем сделки равен объему позиции - она закрыта полностью
            double deal_volume = HistoryDealGetDouble(trans.deal, DEAL_VOLUME);
            double pos_volume = GetPositionVolume(pos_id); // Позиции уже может не быть, 0

            if(pos_volume == 0 || deal_volume == pos_volume) // Простое условие для примера
              {
               Print("Позиция ", pos_id, " успешно закрыта.");
               waiting_for_close = false;
               waiting_position_id = 0;
              }
           }
        }
     }
  }

5. Схема работы идеального советника (Check-list)

Чтобы ваш советник работал как часы, придерживайтесь следующей архитектуры:

  1. Блок генерации сигнала (OnTick): Здесь только анализ рынка. Если условия выполнены и нет открытых позиций (или есть сигнал на закрытие), мы выставляем флаги, но не шлем ордера напрямую.
  2. Блок управления ордерами: Вызывается по таймеру или из OnTick, проверяет флаги и отправляет торговые запросы через OrderSend.
  3. Блок обработки событий (OnTradeTransaction): Контрольная точка. Здесь мы ловим факт исполнения наших запросов, обновляем внутренние флаги советника и логируем результат.

Заключение

Использование OnTradeTransaction() — это единственный надежный способ управлять торговлей в современных реалиях MQL5. Запомните главное правило: «Отправил — забудь, поймал событие — обработай».

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

Поделись или сохрани ссылку

Автор статьи

Комментарии

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

Вы добавили товары в корзину?

Мы можем сохранить вашу корзину и напомнить вам позже. Хотите скидку 3%?

Содержание