Отправка торгового запроса: OrderSend и OrderSendAsync

Для выполнения торговых операций MQL5 API предоставляет две функции: OrderSend и OrderSendAsync. Они, также как и OrderCheck, выполняют в терминале формальную проверку параметров запроса, переданных в виде структуры MqlTradeRequest, а затем, в случае успеха, отправляют запрос на сервер.  

Различие между двумя функциями заключается в следующем. OrderSend дожидается постановки приказа в очередь на обработку на сервере и получает оттуда значащие данные в полях структуры MqlTradeResult, передаваемой вторым параметром функции. OrderSendAsync сразу же возвращает управление вызывающему коду, не заботясь о том, что ответит сервер. При этом из всех полей структуры MqlTradeResult, помимо retcode, важной информацией заполняется только request_id. Используя этот идентификатор запроса, MQL-программа может получать последующую информацию о ходе обработки этого запроса в событии OnTradeTransaction. Альтернативный подход — периодически анализировать списки ордеров, сделок и позиций, причем это можно делать и в цикле, задавшись некоторым таймаутом на случай проблем со связью.

Важно отметить, что, несмотря на суффикс "Async" в названии второй функции, первая функция без этого суффикса также не является в полном смысле синхронной. Дело в том, что результат обработки приказа сервером, в частности, совершение сделки (или, вероятно, нескольких сделок на основании одного приказа) и открытие позиции, в общем случае происходит асинхронно — во внешней торговой системе. Таким образом, функция OrderSend также требует отложенного сбора и анализа последствий выполнения запроса, который MQL-программ должна, при необходимости, реализовать сама. Мы рассмотрим пример действительно синхронной отправки запроса и приема всех его результатов позднее (см. MqlTradeSync.mqh).

bool OrderSend(const MqlTradeRequest &request, MqlTradeResult &result)

Функция возвращает true в случае успешной базовой проверки структуры request в терминале и нескольких дополнительных проверок на сервере. Однако это лишь свидетельствует о принятии ордера сервером и не гарантирует успешного выполнения торговой операции.

Торговый сервер может заполнить в возвращаемой структуре result значения полей deal или order, если эти данные будут ему известны в момент формирования ответа на вызов OrderSend. Однако в общем случае события исполнения сделок или выставления лимитных ордеров, соответствующих ордеру, могут произойти уже после того, как ответ будет отправлен MQL-программе, в терминал. Поэтому для любого типа торгового запроса при получении результата выполнения OrderSend необходимо проверять код возврата торгового сервера retcode и код ответа внешней торговой системы retcode_external (при необходимости), которые доступны в возвращаемой структуре result. На их основе следует принять решение об ожидании незавершенных действий на сервере или предпринять собственные действия.

Каждый принятый ордер хранится на торговом сервере в ожидании обработки, пока не наступит какое-либо из событий, влияющих на его жизненный цикл:

  • исполнение при появлении встречного запроса,
  • срабатывание при поступлении цены исполнения,
  • истечение срока действия,
  • отмена пользователем или MQL-программой,
  • удаление брокером (например, при клиринге или нехватке средств, по Stop Out)

Прототип OrderSendAsync полностью повторяет OrderSend.

bool OrderSendAsync(const MqlTradeRequest &request, MqlTradeResult &result)

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

Внимание! В тестере функция OrderSendAsync работает как OrderSend. Это затрудняет отладку отложенной обработки асинхронных запросов.

Функция возвращает true по факту успешной отсылки запроса на сервер MetaTrader 5, однако это не означает, что запрос дошел до сервера и был принят для обработки. При этом в приёмной структуре result код ответа содержит значение TRADE_RETCODE_PLACED (10008) — "ордер размещен".

Сервер при обработке полученного запроса отправит терминалу ответное сообщение об изменении текущего состояния позиций, ордеров и сделок, которое приводит к генерации события OnTrade в MQL-программе. Там она может проанализировать новое торговое окружение и историю счета — далее мы рассмотрим соответствующие примеры.

Также подробности исполнения торгового запроса на сервере можно отслеживать при помощи обработчика OnTradeTransaction. При этом следует учитывать, что в результате исполнения одного торгового запроса обработчик OnTradeTransaction будет вызван несколько раз. Например, при отсылке запроса на покупку по рынку, он принимается на обработку сервером, для счета создается соответствующий ордер на покупку, происходит исполнение ордера и заключение сделки, в результате чего он удаляется из списка открытых и добавляется в историю ордеров, далее сделка добавляется в историю и создается новая позиция. Для каждого из этих событий будет вызвана функция OnTradeTransaction.

Для начала рассмотрим простой пример эксперта CustomOrderSend.mq5. Он позволяет задать во входных параметрах все поля запроса, что аналогично CustomOrderCheck.mq5, но далее отличается тем, что отправляет запрос на сервер вместо простой проверки в терминале. Запускайте эксперт на демо-счете. После завершения экспериментов не забудьте удалить эксперт с графика или закрыть график, чтобы не отправлять тестовый запрос при каждом следующем запуске терминала.

В новом примере есть и несколько других усовершенствований. Прежде всего добавлен входной параметр Async.

input bool Async = false;

Эта опция предназначена для выбора того, какой функцией запрос будет отсылаться на сервер. По умолчанию параметр равен false, и используется функция OrderSend. Если задать его равным true, будет вызываться OrderSendAsync.

Кроме того, с этого примера мы начнем описывать и пополнять специальный набор функций в заголовочном файле TradeUtils.mqh, который пригодится для упрощения кодирования роботов. Все функции помещены в пространство имен TU (от "Trade Utilities"), и первыми из них представим функции для удобного вывода в журнал структур MqlTradeRequest и MqlTradeResult.

namespace TU
{
   string StringOf(const MqlTradeRequest &r)
   {
      SymbolMetrics p(r.symbol);
      
      // главный блок: действие, тип, символ      
      string text = EnumToString(r.action);
      if(r.symbol != NULLtext += ", " + r.symbol;
      text += ", " + EnumToString(r.type);
      // блок объемов
      if(r.volume != 0text += ", V=" + p.StringOf(r.volumep.lotDigits);
      text += ", " + EnumToString(r.type_filling);
      // блок всех цен
      if(r.price != 0text += ", @ " + p.StringOf(r.price);
      if(r.stoplimit != 0text += ", X=" + p.StringOf(r.stoplimit);
      if(r.sl != 0text += ", SL=" + p.StringOf(r.sl);
      if(r.tp != 0text += ", TP=" + p.StringOf(r.tp);
      if(r.deviation != 0text += ", D=" + (string)r.deviation;
      // блок истечения отложенных ордеров
      if(IsPendingType(r.type)) text += ", " + EnumToString(r.type_time);
      if(r.expiration != 0text += ", " + TimeToString(r.expiration);
      // блок модификации
      if(r.order != 0text += ", #=" + (string)r.order;
      if(r.position != 0text += ", #P=" + (string)r.position;
      if(r.position_by != 0text += ", #b=" + (string)r.position_by;
      // вспомогательные данные
      if(r.magic != 0text += ", M=" + (string)r.magic;
      if(StringLen(r.comment)) text += ", " + r.comment;
      
      return text;
   }
   
   string StringOf(const MqlTradeResult &r)
   {
      string text = TRCSTR(r.retcode);
      if(r.deal != 0text += ", D=" + (string)r.deal;
      if(r.order != 0text += ", #=" + (string)r.order;
      if(r.volume != 0text += ", V=" + (string)r.volume;
      if(r.price != 0text += ", @ " + (string)r.price
      if(r.bid != 0text += ", Bid=" + (string)r.bid
      if(r.ask != 0text += ", Ask=" + (string)r.ask
      if(StringLen(r.comment)) text += ", " + r.comment;
      if(r.request_id != 0text += ", Req=" + (string)r.request_id;
      if(r.retcode_external != 0text += ", Ext=" + (string)r.retcode_external;
      
      return text;
   }
   ...
};

Суть функций — предоставить в кратком, но удобном виде все значащие (непустые) поля: они выводятся в одну строку с уникальным обозначением каждого.

Как можно заметить, в функции для MqlTradeRequest используется класс SymbolMetrics. Он упрощает нормализацию нескольких цен или объемов по одному и тому же инструменту. Напомним, что нормализация цен и объемов — обязательное условие подготовки корректного торгового запроса.

   class SymbolMetrics
   {
   public:
      const string symbol;
      const int digits;
      const int lotDigits;
      
      SymbolMetrics(const string s): symbol(s),
         digits((int)SymbolInfoInteger(sSYMBOL_DIGITS)),
         lotDigits((int)MathLog10(1.0 / SymbolInfoDouble(sSYMBOL_VOLUME_STEP)))
      { }
         
      double price(const double p)
      {
         return TU::NormalizePrice(psymbol);
      }
      
      double volume(const double v)
      {
         return TU::NormalizeLot(vsymbol);
      }
   
      string StringOf(const double vconst int d = INT_MAX)
      {
         return DoubleToString(vd == INT_MAX ? digits : d);
      }
   };

Непосредственно нормализация величин поручена вспомогательным функциям NormalizePrice и NormalizeLot (принцип работы последней идентичен тому, что мы видели в файле LotMarginExposure.mqh).

   double NormalizePrice(const double priceconst string symbol = NULL)
   {
      const double tick = SymbolInfoDouble(symbolSYMBOL_TRADE_TICK_SIZE);
      return MathRound(price / tick) * tick;
   }

С подключенным файлом TradeUtils.mqh пример CustomOrderSend.mq5 приобретает следующий вид (опущенные фрагменты кода '...' остались без изменений с CustomOrderCheck.mq5).

void OnTimer()
{
   ...
   MqlTradeRequest request = {};
   MqlTradeCheckResult result = {};
   
   TU::SymbolMetrics sm(symbol);
   
   // заполняем структуру запроса
   request.action = Action;
   request.magic = Magic;
   request.order = Order;
   request.symbol = symbol;
   request.volume = sm.volume(volume);
   request.price = sm.price(price);
   request.stoplimit = sm.price(StopLimit);
   request.sl = sm.price(SL);
   request.tp = sm.price(TP);
   request.deviation = Deviation;
   request.type = Type;
   request.type_filling = Filling;
   request.type_time = ExpirationType;
   request.expiration = ExpirationTime;
   request.comment = Comment;
   request.position = Position;
   request.position_by = PositionBy;
   
   // отправляем запрос и выводим результат
   ResetLastError();
   if(Async)
   {
      PRTF(OrderSendAsync(requestresult));
   }
   else
   {
      PRTF(OrderSend(requestresult));
   }
   Print(TU::StringOf(request));
   Print(TU::StringOf(result));
}

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

С настройками по умолчанию эксперт создает запрос на покупку минимального лота текущего инструмента по рынку, причем делает это функцией OrderSend.

OrderSend(request,result)=true / ok

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.12462

DONE, D=1250236209, #=1267684253, V=0.01, @ 1.12462, Bid=1.12456, Ask=1.12462, Request executed, Req=1

Как правило, при разрешенных торгах эта операция должна завершиться успешно (статус DONE, комментарий "Request executed"). В структуре result мы сразу получили номер сделки D.

Если открыть настройки эксперта и заменить значение параметра Async на true, мы отправим аналогичный запрос, но уже функцией OrderSendAsync.

OrderSendAsync(request,result)=true / ok

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.12449

PLACED, Order placed, Req=2

В этом случае статус равен PLACED, и номер сделки на момент возврата функции не известен. Мы знаем только уникальный идентификатор запроса Req=2. Чтобы получить номер сделки и позиции, необходимо перехватить сообщение TRADE_TRANSACTION_REQUEST с таким же идентификатором запроса в обработчике OnTradeTransaction, куда в качестве параметра поступит заполненная структура MqlTradeResult.

С точки зрения пользователя оба запроса должны выполниться одинаково быстро.

Сравнить скорость работы этих двух функций непосредственно в коде MQL-программы можно будет с помощью другого примера эксперта (см. раздел о синхронных и асинхронных запросах), который мы рассмотрим после изучения модели торговых событий.

Следует отметить, что торговые события посылаются в обработчик OnTradeTransaction (при его наличии в коде), независимо от того, какая функция используется для отправки запросов — OrderSend или OrderSendAsync. Просто в случае применения OrderSend некоторая или вся информация о выполнении приказа сразу доступна в приемной структуре MqlTradeResult. Однако в общем случае результат распределен по времени и по объемам, например, при "заливке" одного ордера в несколько сделок. Тогда полную информацию можно получить из торговых событий или анализируя историю сделок и ордеров.

Если попробовать отправить заведомо некорректный запрос, например, изменить тип ордера на отложенный ORDER_TYPE_BUY_STOP, получим сообщение об ошибке, потому что для таких ордеров следует использовать действие TRADE_ACTION_PENDING, а кроме того они должны располагаться на удалении от текущей цены (у нас же по умолчанию подставляется рыночная). Перед этим тестом важно не забыть вернуть режим запросов на синхронный (Async = false), чтобы сразу увидеть ошибку в структуре MqlTradeResult по завершению вызова OrderSend — в противном случае OrderSendAsync вернула бы true, но ордер всё равно не был бы установлен, причем информацию об этом программа могла бы получить только в OnTradeTransaction, которого у нас пока нет.

OrderSend(request,result)=false / TRADE_SEND_FAILED(4756)

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, @ 1.12452, ORDER_TIME_GTC

REQUOTE, Bid=1.12449, Ask=1.12452, Requote, Req=5

В данном случае ошибка сообщает о неверной цене "Requote".

Примеры использования функций для выполнения конкретных торговых действий будут представлены в следующих разделах.