Проверка корректности запроса: OrderCheck

Для выполнения любой торговой операции MQL-программа должна предварительно заполнить необходимыми данными структуру MqlTradeRequest. Перед отправкой на сервер с помощью торговых функций её имеет смысл проверить на формальную корректность и оценить последствия выполнения запроса, в частности, размер залога, который потребуется, и оставшихся свободных средств. Данную проверку выполняет функция OrderCheck.

bool OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &result)

В случае нехватки средств или ошибочно заполненных параметров функция возвращает false. Кроме того, функция реагирует отказом и при отключенном разрешении торговать — как в терминале в целом, так и для конкретной программы. Конкретный код ошибки смотрите в поле retcode структуры result.

Успешная проверка структуры request и торгового окружения завершается со статусом true, однако это не гарантирует, что запрашиваемая операция непременно выполнится успешно, если её повторить с помощью функций OrderSend или OrderSendAsync. Между вызовами могут измениться торговые условия или у брокера на сервере применены настройки для конкретной внешней торговой системы, которые невозможно учесть в алгоритме формальной проверки, которую делает OrderCheck.

Для получения описания предполагаемого финансового результата следует анализировать поля структуры result.

В отличие от функции OrderCalcMargin, которая рассчитывает оценку требуемого залога только под одну предполагаемую позицию или ордер, OrderCheck учитывает, хоть и в упрощенном режиме, общее состояние торгового счета. Так, она заполняет поле margin в структуре MqlTradeCheckResult и прочие связанные поля (margin_free, margin_level) совокупными показателями, которые сложатся после выполнение приказа. Например, если уже открыта позиция по какому-либо инструменту на момент вызова OrderCheck и проверяемый запрос наращивает позицию, поле margin отразит сумму залога, включая прежние маржинальные обязательства. Если же новый приказ содержит операцию в противоположном направлении, размер маржи не увеличится (в реальности он должен уменьшится, потому что на неттинговом счете позиция таким образом может вообще закрыться, а на счете с хеджингом — применится хеджирующая маржа для встречных позиций, однако столь точный расчет функция не выполняет).

Прежде всего, OrderCheck пригодится программистам на начальном этапе знакомства с торговым API, чтобы экспериментировать с запросами, не отправляя их на сервер.

Протестируем работу функции OrderCheck с помощью простого неторгующего эксперта CustomOrderCheck.mq5. Мы сделали его именно экспертом, а не скриптом, для удобства пользования — так он будет оставаться на графике после прикрепления с текущими настройками, и их легко редактировать, меняя отдельные входные параметры. Если бы это был скрипт, нам пришлось бы каждый раз начинать заново установку нескольких полей со значений по умолчанию.

Чтобы запустить проверку, установим в OnInit таймер.

void OnInit()
{
   // инициируем отложенное выполнение
   EventSetTimer(1);
}

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

void OnTimer()
{
   // выполняем код однократно и ждем новых установок пользователя
   EventKillTimer();
   ...
}

Входные параметры эксперта полностью повторяют набор полей структуры торгового запроса.

input ENUM_TRADE_REQUEST_ACTIONS Action = TRADE_ACTION_DEAL;
input ulong Magic;
input ulong Order;
input string Symbol;    // Symbol (empty = current _Symbol)
input double Volume;    // Volume (0 = minimal lot)
input double Price;     // Price (0 = current Ask)
input double StopLimit;
input double SL;
input double TP;
input ulong Deviation;
input ENUM_ORDER_TYPE Type;
input ENUM_ORDER_TYPE_FILLING Filling;
input ENUM_ORDER_TYPE_TIME ExpirationType;
input datetime ExpirationTime;
input string Comment;
input ulong Position;
input ulong PositionBy;

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

По умолчанию состояние переменных соответствует запросу на открытие позиции минимальным лотом текущего инструмента. В частности, параметр Type в отсутствие явной инициализации получит значение 0, которое равно элементу ORDER_TYPE_BUY структуры ENUM_ORDER_TYPE. В параметре Action мы задали явную инициализацию, потому что 0 не соответствует ни одному элементу перечисления ENUM_TRADE_REQUEST_ACTIONS (первый элемент TRADE_ACTION_DEAL равен 1).

void OnTimer()
{
   ...
   // инициализируем структуры нулями
   MqlTradeRequest request = {};
   MqlTradeCheckResult result = {};
   
   // значения по умолчанию
   const bool kindOfBuy = (Type & 1) == 0;
   const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
   const double volume = Volume == 0 ?
      SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN) : Volume;
   const double price = Price == 0 ?
      SymbolInfoDouble(symbolkindOfBuy ? SYMBOL_ASK : SYMBOL_BID) : Price;
   ...

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

   request.action = Action;
   request.magic = Magic;
   request.order = Order;
   request.symbol = symbol;
   request.volume = volume;
   request.price = price;
   request.stoplimit = StopLimit;
   request.sl = SL;
   request.tp = 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;
   ...

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

Затем вызываем OrderCheck и выводим в журнал структуры request и result. Из последней нас интересует, на самом деле, только поле retcode, поэтому оно дополнительно печатается с "расшифровкой" в виде текста, макросом TRCSTR (TradeRetcode.mqh). Вы также можете анализировать строковое поле comment, но его формат может меняться, так что оно в большей степени подойдёт для отображения пользователю.

   ResetLastError();
   PRTF(OrderCheck(requestresult));
   StructPrint(requestARRAYPRINT_HEADER);
   Print(TRCSTR(result.retcode));
   StructPrint(resultARRAYPRINT_HEADER2);
   ...

Вывод структур обеспечивает вспомогательная функция StructPrint, которая основана на ArrayPrint. Из-за этого мы пока получим "сырое" отображение данных — в частности, элементы перечислений представлены числами "как есть". Позднее мы разработаем функцию для более дружественного (понятного для пользователя) вывода структуры MqlTradeRequest (см. TradeUtils.mqh).

Чтобы облегчить анализ результатов, в начале функции OnTimer выведем текущее состояние счета, а в конце для сравнения — вычислим для заданной торговой операции маржу с помощью функции OrderCalcMargin.

void OnTimer()
{
   PRTF(AccountInfoDouble(ACCOUNT_EQUITY));
   PRTF(AccountInfoDouble(ACCOUNT_PROFIT));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN_FREE));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL));
   ...
   // заполнение структуры MqlTradeRequest
   // вызов OrderCheck и печать результатов
   ...
   double margin = 0;
   ResetLastError();
   PRTF(OrderCalcMargin(Typesymbolvolumepricemargin));
   PRTF(margin);
}

Ниже представлен пример вывода в журнал на "XAUUSD" с настройками по умолчанию.

AccountInfoDouble(ACCOUNT_EQUITY)=15565.22 / ok

AccountInfoDouble(ACCOUNT_PROFIT)=0.0 / ok

AccountInfoDouble(ACCOUNT_MARGIN)=0.0 / ok

AccountInfoDouble(ACCOUNT_MARGIN_FREE)=15565.22 / ok

AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)=0.0 / ok

OrderCheck(request,result)=true / ok

[action] [magic] [order] [symbol] [volume] [price] [stoplimit] [sl] [tp] [deviation] [type] »

       1       0       0 "XAUUSD"     0.01 1899.97        0.00 0.00 0.00           0      0 »

 » [type_filling] [type_time]        [expiration] [comment] [position] [position_by] [reserved]

 »             0           0 1970.01.01 00:00:00 ""                 0             0          0

OK_0

[retcode] [balance] [equity] [profit] [margin] [margin_free] [margin_level] [comment] [reserved]

        0  15565.22 15565.22     0.00    19.00      15546.22       81922.21 "Done"             0

OrderCalcMargin(Type,symbol,volume,price,margin)=true / ok

margin=19.0 / ok

А вот пример с оценкой предполагаемого увеличения маржи на счете, где уже есть открытая позиция, и мы её собираемся удвоить.

AccountInfoDouble(ACCOUNT_EQUITY)=9999.540000000001 / ok

AccountInfoDouble(ACCOUNT_PROFIT)=-0.83 / ok

AccountInfoDouble(ACCOUNT_MARGIN)=79.22 / ok

AccountInfoDouble(ACCOUNT_MARGIN_FREE)=9920.32 / ok

AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)=12622.49431961626 / ok

OrderCheck(request,result)=true / ok

[action] [magic] [order]  [symbol] [volume] [price] [stoplimit] [sl] [tp] [deviation] [type] »

       1       0       0 "PLZL.MM"      1.0 12642.0         0.0  0.0  0.0           0      0 »

 » [type_filling] [type_time]        [expiration] [comment] [position] [position_by] [reserved]

 »              0           0 1970.01.01 00:00:00 ""                 0             0          0

OK_0

[retcode] [balance] [equity] [profit] [margin] [margin_free] [margin_level] [comment] [reserved]

        0  10000.87  9999.54    -0.83   158.26       9841.28        6318.43 "Done"             0

OrderCalcMargin(Type,symbol,volume,price,margin)=true / ok

margin=79.04000000000001 / ok

Попробуйте менять любые параметры запроса и посмотрите, является ли запрос успешным. Некорректные сочетания параметров будут вызывать коды ошибок из стандартного перечня, но поскольку неверных вариантов гораздо больше, чем зарезервированных (самых распространенных ошибок), функция часто может возвращать обобщенный код TRADE_RETCODE_INVALID (10013). В связи с этим рекомендуется реализовать собственные проверки структуры с большей степенью диагностики.

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