Многие начинающие и даже опытные разработчики на MQL5 сталкиваются с ситуацией, когда их советник пытается открыть ордер несколько раз подряд или, наоборот, пропускает момент исполнения сделки. Это происходит из-за непонимания асинхронной природы торговли в MетаТрейдере 5 и отсутствия грамотной обработки торговых событий.
В этой статье мы разберем, как перестать полагаться на глобальные переменные и таймеры, и научимся правильно отлавливать момент исполнения ордера с помощью обработчика событий OnTradeTransaction().
1. Почему простой «if(OrderSend)» — это путь к ошибкам?
В MQL4 трейдеры привыкли к синхронной торговле: отправил ордер — и тут же получил результат. В MQL5 всё иначе. Торговая система асинхронна. Функции отправки ордеров (OrderSendAsync) только ставят задачу в очередь сервера, но не ждут её исполнения.
Типичная ошибка новичка:
- Советник получил сигнал на Buy.
- Отправил запрос
OrderSend. - Не дождавшись подтверждения от сервера, на следующем тике снова видит тот же сигнал.
- Отправляет еще один ордер.
- В итоге — открыто 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 (запрос принят), но позиция еще открыта.
Правильный подход:
- Устанавливаем флаг «ожидание закрытия».
- В
OnTradeTransaction()ловим сделку типаDEAL_TYPE_SELL(для закрытия Long позиции) илиDEAL_TYPE_BUY(для закрытия Short позиции) с нашим магиком. - Сбрасываем флаг.
Пример флага состояния:
В глобальной области советника объявите переменную:
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)
Чтобы ваш советник работал как часы, придерживайтесь следующей архитектуры:
- Блок генерации сигнала (OnTick): Здесь только анализ рынка. Если условия выполнены и нет открытых позиций (или есть сигнал на закрытие), мы выставляем флаги, но не шлем ордера напрямую.
- Блок управления ордерами: Вызывается по таймеру или из
OnTick, проверяет флаги и отправляет торговые запросы черезOrderSend. - Блок обработки событий (OnTradeTransaction): Контрольная точка. Здесь мы ловим факт исполнения наших запросов, обновляем внутренние флаги советника и логируем результат.
Заключение
Использование OnTradeTransaction() — это единственный надежный способ управлять торговлей в современных реалиях MQL5. Запомните главное правило: «Отправил — забудь, поймал событие — обработай».
Внедрив описанные выше шаблоны, вы навсегда избавитесь от проблемы дублирования ордеров и научитесь мгновенно реагировать на изменения состояния счета, что критически важно для скальперских стратегий и систем с жестким риск-менеджментом.

Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.