Событие OnTradeTransaction

Эксперты и индикаторы могут получать уведомления о торговых событиях, если в их коде описана специальная функция обработки OnTradeTransaction.

void OnTradeTransaction(const MqlTradeTransaction &trans,
  const MqlTradeRequest &request, const MqlTradeResult &result)

Первым параметром идет структура MqlTradeTransaction, описанная в предыдущем разделе. Второй и третий параметры — это структуры MqlTradeRequest и MqlTradeResult, которые были представлены ранее в соответствующих разделах.

Структура MqlTradeTransaction, описывающая торговую транзакцию, заполняется по-разному в зависимости от типа транзакции, указанного в поле type. Например, для транзакций типа TRADE_TRANSACTION_REQUEST все остальные поля неважны, а для получения дополнительной информации необходимо анализировать второй и третий параметры функции (request и result). И напротив, для всех остальных типов транзакций два последних параметра функции следует игнорировать.

В случае TRADE_TRANSACTION_REQUEST поле request_id в переменной result содержит идентификатор (сквозной порядковый номер), под которым торговый запрос request зарегистрирован в терминале. Этот номер никак не связан с тикетами ордеров и сделок, а также идентификаторами позиций. В каждом сеансе работы с терминалом нумерация начинается с начала (1). Наличие идентификатора запроса позволяет связать выполненное действие (вызов функций OrderSend или OrderSendAsync) с результатом этого действия, передаваемым в OnTradeTransaction. Позже мы рассмотрим примеры.

Для торговых транзакций, касающихся действующих ордеров (TRADE_TRANSACTION_ORDER_ADD, TRADE_TRANSACTION_ORDER_UPDATE и TRADE_TRANSACTION_ORDER_DELETE) и истории ордеров (TRADE_TRANSACTION_HISTORY_ADD, TRADE_TRANSACTION_HISTORY_UPDATE, TRADE_TRANSACTION_HISTORY_DELETE), в структуре MqlTradeTransaction заполняются следующие поля:

  • order — тикет ордера;
  • symbol — имя финансового инструмента в ордере;
  • type — тип торговой транзакции;
  • order_type — тип ордера;
  • orders_state — текущее состояние ордера;
  • time_type — тип истечения ордера;
  • time_expiration — время истечения ордера (для ордеров с типом истечения ORDER_TIME_SPECIFIED и ORDER_TIME_SPECIFIED_DAY);
  • price — цена в ордере, указанная клиентом/программой;
  • price_trigger — стоп-цена срабатывания стоп-лимитного ордера (только для ORDER_TYPE_BUY_STOP_LIMIT и ORDER_TYPE_SELL_STOP_LIMIT);
  • price_sl — цена Stop Loss ордера (заполняется, если указана в ордере);
  • price_tp — цена Take Profit ордера (заполняется, если указана в ордере);
  • volume — текущий объем ордера (не исполненный), изначальный объем ордера можно узнать из истории ордеров;
  • position — тикет открытой, измененной или закрытой позиции;
  • position_by — тикет встречной позиции (только для ордеров на закрытие встречной позицией).

Для торговых транзакций, касающихся сделок (TRADE_TRANSACTION_DEAL_ADD, TRADE_TRANSACTION_DEAL_UPDATE и TRADE_TRANSACTION_DEAL_DELETE), в структуре MqlTradeTransaction заполняются следующие поля:

  • deal — тикет сделки;
  • order — тикет ордера, на основе которого совершена сделка;
  • symbol — имя финансового инструмента в сделке;
  • type — тип торговой транзакции;
  • deal_type — тип сделки;
  • price — цена совершения сделки;
  • price_sl — цена Stop Loss (заполняется, если указана в ордере, на основе которого совершена сделка);
  • price_tp — цена Take Profit (заполняется, если указана в ордере, на основе которого совершена сделка);
  • volume — объем сделки;
  • position — тикет открытой, измененной или закрытой позиции;
  • position_by — тикет встречной позиции (для сделок на закрытие встречной позицией).

Для торговых транзакций, касающихся изменений позиций (TRADE_TRANSACTION_POSITION), в структуре MqlTradeTransaction заполняются следующие поля:

  • symbol — имя финансового инструмента позиции;
  • type — тип торговой транзакции;
  • deal_type — тип позиции (DEAL_TYPE_BUY или DEAL_TYPE_SELL);
  • price — средневзвешенная цена открытия позиции;
  • price_sl — цена Stop Loss;
  • price_tp — цена Take Profit;
  • volume — объем позиции в лотах;
  • position — тикет позиции;

В описании торговой транзакции передается не вся доступная информация по ордерам, сделкам и позициям (например, комментарий). Для получения расширенной информации следует использовать OrderGet-, HistoryOrderGet-, HistoryDealGet- и PositionGet-функции.

Один торговый запрос, отправленный из терминала вручную или через торговые функции OrderSend/OrderSendAsync, может порождать на торговом сервере несколько последовательных торговых транзакций. При этом очередность поступления уведомлений об этих транзакциях в терминал не гарантирована, поэтому нельзя строить свой торговый алгоритм на ожидании одних торговых транзакций после других.

Торговые события обрабатываются асинхронно, то есть отложенно (по времени) относительно момента генерации. Каждое торговое событие посылается в очередь MQL-программы, и та последовательно "выбирает" их в порядке очереди (сам механизм "выборки" и вызова обработчиков в MQL-коде обеспечивает полностью ядро).

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

Длина очереди транзакций составляет 1024 элемента. В случае, если OnTradeTransaction будет обрабатывать очередную транзакцию слишком долго, старые транзакции в очереди могут быть вытеснены более новыми.

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

Для начала рассмотрим простой пример эксперта TradeTransactions.mq5, который выводит в журнал все торговые события OnTradeTransaction. Его единственный параметр DetailedLog позволяет опционально использовать классы OrderMonitor, DealMonitor, PositionMonitor для вывода всех свойств. По умолчанию эксперт выводит только содержимое заполненных полей структур MqlTradeTransaction, MqlTradeRequest и MqlTradeResult, поступающих в обработчик в виде параметров, причем request и result обрабатываются только для транзакций TRADE_TRANSACTION_REQUEST.

input bool DetailedLog = false// DetailedLog ('true' shows order/deal/position details)
   
void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   static ulong count = 0;
   PrintFormat(">>>% 6d", ++count);
   Print(TU::StringOf(transaction));
   
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      Print(TU::StringOf(request));
      Print(TU::StringOf(result));
   }
   
   if(DetailedLog)
   {
      if(transaction.order != 0)
      {
         OrderMonitor m(transaction.order);
         m.print();
      }
      if(transaction.deal != 0)
      {
         DealMonitor m(transaction.deal);
         m.print();
      }
      if(transaction.position != 0)
      {
         PositionMonitor m(transaction.position);
         m.print();
      }
   }
}

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

Откроем длинную позицию минимальным лотом.

>>>      1

TRADE_TRANSACTION_ORDER_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10947, V=0.01

>>>      2

TRADE_TRANSACTION_DEAL_ADD, D=1279627746(DEAL_TYPE_BUY), »

   » #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296991463

>>>      3

TRADE_TRANSACTION_ORDER_DELETE, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10947, P=1296991463

>>>      4

TRADE_TRANSACTION_HISTORY_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10947, P=1296991463

>>>      5

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10947, #=1296991463

DONE, D=1279627746, #=1296991463, V=0.01, @ 1.10947, Bid=1.10947, Ask=1.10947, Req=7

Продадим удвоенный минимальный лот.

>>>      6

TRADE_TRANSACTION_ORDER_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10964, V=0.02

>>>      7

TRADE_TRANSACTION_DEAL_ADD, D=1279628463(DEAL_TYPE_SELL), »

   » #=1296992157(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.02, P=1296992157

>>>      8

TRADE_TRANSACTION_ORDER_DELETE, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10964, P=1296992157

>>>      9

TRADE_TRANSACTION_HISTORY_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10964, P=1296992157

>>>     10

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.02, ORDER_FILLING_FOK, @ 1.10964, #=1296992157

DONE, D=1279628463, #=1296992157, V=0.02, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=8

Выполним операцию встречного закрытия.

>>>     11

TRADE_TRANSACTION_ORDER_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10964, V=0.01, P=1296991463, b=1296992157

>>>     12

TRADE_TRANSACTION_DEAL_ADD, D=1279628878(DEAL_TYPE_SELL), »

   » #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296991463

>>>     13

TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10947, P=1296991463

>>>     14

TRADE_TRANSACTION_DEAL_ADD, D=1279628879(DEAL_TYPE_BUY), »

   » #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296992157

>>>     15

TRADE_TRANSACTION_ORDER_DELETE, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10964, P=1296991463, b=1296992157

>>>     16

TRADE_TRANSACTION_HISTORY_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10964, P=1296991463, b=1296992157

>>>     17

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_CLOSE_BY, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, #=1296992548, »

   » P=1296991463, b=1296992157

DONE, D=1279628878, #=1296992548, V=0.01, @ 1.10964, Bid=1.10961, Ask=1.10965, Req=9

У нас осталась короткая позиция минимального лота. Закроем её.

>>>     18

TRADE_TRANSACTION_ORDER_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10964, V=0.01, P=1296992157

>>>     19

TRADE_TRANSACTION_ORDER_DELETE, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10964, P=1296992157

>>>     20

TRADE_TRANSACTION_HISTORY_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10964, P=1296992157

>>>     21

TRADE_TRANSACTION_DEAL_ADD, D=1279639132(DEAL_TYPE_BUY), »

   » #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296992157

>>>     22

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10964, #=1297002683, »

   » P=1296992157

DONE, D=1279639132, #=1297002683, V=0.01, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=10

При желании вы можете включить опцию DetailedLog для вывода в журнал всех свойств торговых объектов на момент обработки события. В подробном логе можно заметить разночтения между состоянием объектов, сохраненном в структуре транзакции (на момент её инициирования), и текущим состоянием. Например, при добавлении приказа на закрытие позиции (встречное или обычное) в транзакции указан тикет, по которому объект-монитор уже не сможет ничего прочитать, так как позиция была удалена. В результате увидим в журнале строки вида:

TRADE_TRANSACTION_ORDER_ADD, #=1297777749(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10953, V=0.01, P=1297774881, b=1297776850

...

Error: PositionSelectByTicket(1297774881) failed: TRADE_POSITION_NOT_FOUND

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

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

Этот эксперт предоставляет возможность выбрать направление сделки и объем: если оставить его нулевым, по умолчанию используется минимальный лот текущего символа. Также в параметрах есть дистанция до защитных уровней в пунктах. При заданных параметрах совершается вход в рынок, установка Stop Loss и Take Profit, и затем закрытие позиции — между всеми этапами делается пауза 5 секунд, так что пользователь может вмешаться (например, отредактировать стоплосс вручную), хотя это не обязательно, поскольку мы уже убедились, что ручные операции перехватываются программой.

enum ENUM_ORDER_TYPE_MARKET
{
   MARKET_BUY = ORDER_TYPE_BUY,    // ORDER_TYPE_BUY
   MARKET_SELL = ORDER_TYPE_SELL   // ORDER_TYPE_SELL
};
   
input ENUM_ORDER_TYPE_MARKET Type;
input double Volume;               // Volume (0 - minimal lot)
input uint Distance2SLTP = 1000;

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

int OnInit()
{
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}
   
void OnTimer()
{
   EventKillTimer();
   ...

Все действия выполняются через уже знакомую структуру MqlTradeRequestSync с расширенными возможностями (MqlTradeSync.mqh): неявная инициализация полей правильными значениями, методы buy/sell для рыночных приказов, adjust — для защитных уровней, close — для закрытия позиции.

Шаг 1:

   MqlTradeRequestSync request;
   
   const double volume = Volume == 0 ?
      SymbolInfoDouble(_SymbolSYMBOL_VOLUME_MIN) : Volume;
   
   Print("Start trade");
   const ulong order = (Type == MARKET_BUY ? request.buy(volume) : request.sell(volume));
   if(order == 0 || !request.completed())
   {
      Print("Failed Open");
      return;
   }
   
   Print("OK Open");

Шаг 2:

   Sleep(5000); // ждем 5 секунд (пользователь может редактировать позицию)
   Print("SL/TP modification");
   const double price = PositionGetDouble(POSITION_PRICE_OPEN);
   const double point = SymbolInfoDouble(_SymbolSYMBOL_POINT);
   TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
   const double SL = dir.negative(priceDistance2SLTP * point);
   const double TP = dir.positive(priceDistance2SLTP * point);
   if(request.adjust(SLTP) && request.completed())
   {
      Print("OK Adjust");
   }
   else
   {
      Print("Failed Adjust");
   }

Шаг 3:

   Sleep(5000); // ждем еще 5 секунд
   Print("Close down");
   if(request.close(request.result.position) && request.completed())
   {
      Print("Finish");
   }
   else
   {
      Print("Failed Close");
   }
}

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

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

Результат выполнения двух экспертов представлен в следующем логе с таймингом (для краткости OrderSendTransaction1 помечен как OS1, а TradeTransactions — как TTs).

19:09:08.078  OS1  Start trade

19:09:08.109  TTs  >>>     1

19:09:08.125  TTs  TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »

                   EURUSD, @ 1.10913, V=0.01

19:09:08.125  TTs  >>>     2

19:09:08.125  TTs  TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), »

                   #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10913, V=0.01, »

                   P=1298021794

19:09:08.125  TTs  >>>     3

19:09:08.125  TTs  TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10913, P=1298021794

19:09:08.125  TTs  >>>     4

19:09:08.125  TTs  TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10913, P=1298021794

19:09:08.125  TTs  >>>     5

19:09:08.125  TTs  TRADE_TRANSACTION_REQUEST

19:09:08.125  TTs  TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, »

                   D=10, #=1298021794, M=1234567890

19:09:08.125  TTs  DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, »

                   Req=9

19:09:08.125  OS1  Waiting for position for deal D=1280661362

19:09:08.125  OS1  OK Open

19:09:13.133  OS1  SL/TP modification

19:09:13.164  TTs  >>>     6

19:09:13.164  TTs  TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, »

                   P=1298021794

19:09:13.164  OS1  OK Adjust

19:09:13.164  TTs  >>>     7

19:09:13.164  TTs  TRADE_TRANSACTION_REQUEST

19:09:13.164  TTs  TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09913, »

                   TP=1.11913, D=10, P=1298021794, M=1234567890

19:09:13.164  TTs  DONE, Req=10

19:09:18.171  OS1  Close down

19:09:18.187  OS1  Finish

19:09:18.218  TTs  >>>     8

19:09:18.218  TTs  TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), »

                   EURUSD, @ 1.10901, V=0.01, P=1298021794

19:09:18.218  TTs  >>>     9

19:09:18.218  TTs  TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), »

                   #=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, »

                   SL=1.09913, TP=1.11913, V=0.01, P=1298021794

19:09:18.218  TTs  >>>    10

19:09:18.218  TTs  TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10901, P=1298021794

19:09:18.218  TTs  >>>    11

19:09:18.218  TTs  TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10901, P=1298021794

19:09:18.218  TTs  >>>    12

19:09:18.218  TTs  TRADE_TRANSACTION_REQUEST

19:09:18.218  TTs  TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, »

                   D=10, #=1298022443, P=1298021794, M=1234567890

19:09:18.218  TTs  DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, »

                   Req=11

19:09:18.218  OS1  >>>     1

19:09:18.218  OS1  TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »

                   EURUSD, @ 1.10913, V=0.01

19:09:18.218  OS1  >>>     2

19:09:18.218  OS1  TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), »

                   #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

                   @ 1.10913, V=0.01, P=1298021794

19:09:18.218  OS1  >>>     3

19:09:18.218  OS1  TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10913, P=1298021794

19:09:18.218  OS1  >>>     4

19:09:18.218  OS1  TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10913, P=1298021794

19:09:18.218  OS1  >>>     5

19:09:18.218  OS1  TRADE_TRANSACTION_REQUEST

19:09:18.218  OS1  TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, »

                   D=10, #=1298021794, M=1234567890

19:09:18.218  OS1  DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, »

                   Req=9

19:09:18.218  OS1  >>>     6

19:09:18.218  OS1  TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, »

                   P=1298021794

19:09:18.218  OS1  >>>     7

19:09:18.218  OS1  TRADE_TRANSACTION_REQUEST

19:09:18.218  OS1  TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, »

                   SL=1.09913, TP=1.11913, D=10, P=1298021794, M=1234567890

19:09:18.218  OS1  DONE, Req=10

19:09:18.218  OS1  >>>     8

19:09:18.218  OS1  TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), »

                   EURUSD, @ 1.10901, V=0.01, P=1298021794

19:09:18.218  OS1  >>>     9

19:09:18.218  OS1  TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), »

                   #=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, »

                   SL=1.09913, TP=1.11913, V=0.01, P=1298021794

19:09:18.218  OS1  >>>    10

19:09:18.218  OS1  TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10901, P=1298021794

19:09:18.218  OS1  >>>    11

19:09:18.218  OS1  TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

                   EURUSD, @ 1.10901, P=1298021794

19:09:18.218  OS1  >>>    12

19:09:18.218  OS1  TRADE_TRANSACTION_REQUEST

19:09:18.218  OS1  TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, »

                   D=10, #=1298022443, P=1298021794, M=1234567890

19:09:18.218  OS1  DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, »

                   Req=11

Нумерация событий в программах совпадает (при условии их чистого запуска, как рекомендовалось). Обратите внимание, что одно и то же событие печатается сначала из TTs сразу после выполнения запроса, а второй раз — уже в конце теста, где фактически единым пакетом выводятся все события из очереди в OS1.

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

Сейчас в примерах используется наша модификация структуры MqlTradeRequestSync, целенаправленно использующая "синхронный" вариант OrderSend, и более того — в ней реализован универсальный метод completed, проверяющий успешное завершение запроса. Благодаря этому контролю мы можем, в частности, устанавливать защитные уровни на позицию, потому что "умеем" дожидаться появления её тикета. В рамках такой синхронной концепции (взятой на вооружение в угоду удобству) анализ результатов запросов в OnTradeTransaction в общем-то не нужен. Однако так бывает не всегда.

Когда эксперту требуется отправить много запросов сразу, как в случае примера с установкой сетки ордеров PendingOrderGrid2.mq5, рассмотренной в разделе о свойствах позиций, ожидание "готовности" каждой позиции или ордера может снижать общую производительность эксперта. В подобных случаях рекомендуется использовать функцию OrderSendAsync, но она при успешном выполнении заполняет в структуре MqlTradeResult только поле request_id, с помощью которого затем нужно отслеживать появление ордеров, сделок и позиций в OnTradeTransaction.

Один из наиболее очевидных, но не особо изящных приемов для реализации этой схемы заключается в сохранении идентификаторов запросов или целиком структур отправляемых запросов в массиве, в глобальном контексте. Затем эти идентификаторы можно искать в приходящих транзакциях в OnTradeTransaction, находить тикеты в параметре MqlTradeResult и производить дальнейшие действия. В результате торговая логика оказывается разнесенной по разным функциям. Например, в контексте последнего эксперта OrderSendTransaction1.mq5 данное "разнесение" заключается в том, что фрагменты кода после отправки первого приказа нужно перенести в OnTradeTransaction и обложить многочисленными проверками:

  • на тип транзакции в MqlTradeTransaction (transaction.type);
  • на тип запроса в MqlTradeRequest (request.action);
  • на идентификатора запроса в MqlTradeResult (result.request_id);

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

Пока лишь отметим, что выбор о построении алгоритма вокруг OnTradeTransaction или без него остается за разработчиком. Во многих случаях, когда массовая отправка приказов не нужна, можно оставаться в "синхронной" парадигме программирования. Вместе с тем, OnTradeTransaction является наиболее практичным способом для контроля срабатывания отложенных ордеров и защитных уровней, а также других событий, генерируемых сервером. После небольшой подготовки мы представим два соответствующих примера: финальную модификацию эксперта-сеточника и реализацию известного "сетапа" из двух ордеров OCO — "One Cancels Other" (см. раздел о событии OnTrade).

Альтернатива применению OnTradeTransaction заключается в периодическом анализе торгового окружения, то есть фактически в запоминании количества ордеров и позиций и поиске изменений среди них. Этот подход подойдет для стратегий, основанных на расписаниях или допускающих определенные временные задержки.

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

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

struct MqlTradeRequestSyncpublic MqlTradeRequest
{
   ...
   static bool AsyncEnabled;
   ...
private:
   bool orderSend(const MqlTradeRequest &reqMqlTradeResult &res)
   {
      return AsyncEnabled ? ::OrderSendAsync(reqres) : ::OrderSend(reqres);
   }
};

Устанавливая публичную переменную AsyncEnabled в true или false, можно переключаться из одного режима в другой, например, в том фрагменте кода, где делаются массовые отправки приказов.

Во-вторых, для тех методов структуры, которые возвращали тикет (например, для входа в рынок), следует предусмотреть возврат поля request_id вместо order. Например, внутри методов _pending и _market у нас был оператор:

if(OrderSend(thisresult)) return result.order;

Теперь он заменяется на:

if(orderSend(thisresult)) return result.order ? result.order :
   (result.retcode == TRADE_RETCODE_PLACED ? result.request_id : 0);

Разумеется, когда включен асинхронный режим, мы уже не можем пользоваться методом completed для ожидания готовности результатов запроса сразу после его отправки. Но этот метод в принципе является опциональным — вы можете не использовать его даже при работе через OrderSend.

Итак, с учетом новой модификации файла MqlTradeSync.mqh создадим OrderSendTransaction2.mq5.

Этот эксперт будет отправлять первичный запрос как прежде из OnTimer, а установку защитных уровней и закрытие позиции — в OnTradeTransaction поэтапно. Хотя у нас между этапами не будет на этот раз искусственной задержки, сама последовательность состояний является стандартной для многих экспертов: открыл позицию, модифицировал, закрыл (при выполнении неких рыночных условий, которые здесь оставлены "за кадром").

Отслеживать состояние позволят 2 глобальные переменные: RequestID с идентификатором последнего отправленного запроса (результат которого мы ожидаем) и PositionTicket с тикетом открытой позиции. Когда позиции еще нет или уже нет, тикет равен 0.

uint RequestID = 0;
ulong PositionTicket = 0;

В обработчике OnInit включим асинхронный режим.

int OnInit()
{
   ...
   MqlTradeRequestSync::AsyncEnabled = true;
   ...
}

Функция OnTimer теперь значительно короче.

void OnTimer()
{
   ...
   // отсылаем запрос TRADE_ACTION_DEAL (асинхронно!)
   const ulong order = (Type == MARKET_BUY ? request.buy(volume) : request.sell(volume));
   if(order// в асихронном режиме это теперь request_id
   {
      Print("OK Open?");
      RequestID = request.result.request_id// то же самое, что order
   }
   else
   {
      Print("Failed Open");
   }
}

При успешном выполнении запроса мы получаем только request_id и сохраняем его в переменной RequestID. Печать статусов теперь содержит вопросительный знак, например, "OK Open?", потому что фактический результат еще не известен.

OnTradeTransaction существенно усложнилась из-за проверки результатов и выполнения последующих торговых приказов по условиям. Рассмотрим её постепенно.

В данном случае вся торговая логика переехала внутрь ветви для транзакций типа TRADE_TRANSACTION_REQUEST. Разумеется, разработчик может по желанию использовать и другие типы, но мы используем этот, поскольку он содержит информацию в виде привычной структуры MqlTradeResult, то есть как бы представляет собой отложенную концовку асинхронного вызова OrderSendAsync.

void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   static ulong count = 0;
   PrintFormat(">>>% 6d", ++count);
   Print(TU::StringOf(transaction));
   
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      Print(TU::StringOf(request));
      Print(TU::StringOf(result));
      
      ...
      // здесь весь алгоритм
   }
}

Нас должны интересовать только запросы с идентификатором, который мы ожидаем. Поэтому следующим оператором будет вложенный if. В его блоке мы заранее описываем объект MqlTradeRequestSync, потому что потребуется отправлять очередные торговые запросы согласно плану.

      if(result.request_id == RequestID)
      {
         MqlTradeRequestSync next;
         next.magic = Magic;
         next.deviation = Deviation;
         ...
      }

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

         if(request.action == TRADE_ACTION_DEAL)
         {
            ... // здесь реакция на открытие и закрытие позиции
         }
         else if(request.action == TRADE_ACTION_SLTP)
         {
            ... // здесь реакция на установку SLTP у открытой позиции
         }

Обратите внимание, что TRADE_ACTION_DEAL используется и для открытия, и для закрытия позиции, а потому потребуется еще один if, в котором будем различать эти два состояния в зависимости от значения переменной PositionTicket.

            if(PositionTicket == 0)
            {
               ... // позиции нет, значит это уведомление об открытии 
            }
            else
            {
               ... // позиция есть, значит это закрытие
            }

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

В случае уведомления об открытии позиции блок кода выглядит следующим образом:

            if(PositionTicket == 0)
            {
               // пытаемся получить результаты из транзакции: выделяем ордер по тикету
               if(!HistoryOrderSelect(result.order))
               {
                  Print("Can't select order in history");
                  RequestID = 0;
                  return;
               }
               // получаем идентификатор и тикет позиции
               const ulong posid = HistoryOrderGetInteger(result.orderORDER_POSITION_ID);
               PositionTicket = TU::PositionSelectById(posid);
               ...

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

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

            if(PositionTicket == 0)
            {
               ...
               const double price = PositionGetDouble(POSITION_PRICE_OPEN);
               const double point = SymbolInfoDouble(_SymbolSYMBOL_POINT);
               TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
               const double SL = dir.negative(priceDistance2SLTP * point);
               const double TP = dir.positive(priceDistance2SLTP * point);
               // посылаем запрос TRADE_ACTION_SLTP (асинхронно!)
               if(next.adjust(PositionTicketSLTP))
               {
                  Print("OK Adjust?");
                  RequestID = next.result.request_id;
               }
               else
               {
                  Print("Failed Adjust");
                  RequestID = 0;
               }
            }

Единственное отличие: мы заполняем переменную RequestID идентификатором нового запроса TRADE_ACTION_SLTP.

Получение уведомления о сделке при ненулевом PositionTicket подразумевает, что произошло закрытие позиции.

            if(PositionTicket == 0)
            {
               ... // см. выше
            }
            else
            {
               if(!PositionSelectByTicket(PositionTicket))
               {
                  Print("Finish");
                  RequestID = 0;
                  PositionTicket = 0;
               }
            }

В случае успешного удаления позицию не удастся выделить с помощью PositionSelectByTicket, а потому мы обнуляем RequestID и PositionTicket. Эксперт при этом возвращается в начальное состояние и готов совершить следующий цикл покупки/продажи-модификации-закрытия.

Нам осталось рассмотреть отправку запроса на закрытие позиции. В нашей упрощенной до минимума стратегии это происходит сразу после успешной модификации защитных уровней.

         if(request.action == TRADE_ACTION_DEAL)
         {
            ... // см. выше
         }
         else if(request.action == TRADE_ACTION_SLTP)
         {
            // посылаем запрос TRADE_ACTION_DEAL на закрытие (асинхронно!)
            if(next.close(PositionTicket))
            {
               Print("OK Close?");
               RequestID = next.result.request_id;
            }
            else
            {
               PrintFormat("Failed Close %lld"PositionTicket);
            }
         }

Вот и вся функция OnTradeTransaction. Эксперт готов.

Запустим OrderSendTransaction2.mq5 с настройками по умолчанию на EURUSD. Ниже приведен пример журнала.

Start trade

OK Open?

>>>     1

TRADE_TRANSACTION_ORDER_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10640, V=0.01

>>>     2

TRADE_TRANSACTION_DEAL_ADD, D=1282135720(DEAL_TYPE_BUY), »

   » #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10640, V=0.01, P=1299508203

>>>     3

TRADE_TRANSACTION_ORDER_DELETE, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10640, P=1299508203

>>>     4

TRADE_TRANSACTION_HISTORY_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10640, P=1299508203

>>>     5

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10640, D=10, »

   » #=1299508203, M=1234567890

DONE, D=1282135720, #=1299508203, V=0.01, @ 1.1064, Bid=1.1064, Ask=1.1064, Req=7

OK Adjust?

>>>     6

TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10640, SL=1.09640, TP=1.11640, V=0.01, P=1299508203

>>>     7

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09640, TP=1.11640, »

   » D=10, P=1299508203, M=1234567890

DONE, Req=8

OK Close?

>>>     8

TRADE_TRANSACTION_ORDER_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, »

   » @ 1.10638, V=0.01, P=1299508203

>>>     9

TRADE_TRANSACTION_ORDER_DELETE, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10638, P=1299508203

>>>    10

TRADE_TRANSACTION_HISTORY_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

   » @ 1.10638, P=1299508203

>>>    11

TRADE_TRANSACTION_DEAL_ADD, D=1282135730(DEAL_TYPE_SELL), »

   » #=1299508215(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10638, »

   » SL=1.09640, TP=1.11640, V=0.01, P=1299508203

>>>    12

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10638, D=10, »

   » #=1299508215, P=1299508203, M=1234567890

DONE, D=1282135730, #=1299508215, V=0.01, @ 1.10638, Bid=1.10638, Ask=1.10638, Req=9

Finish

Легко убедиться, что торговая логика работает по плану, и события о транзакциях прибывают строго после отправки каждого следующего приказа. Если теперь параллельно запустить наш новый эксперт и сторонний "перехватчик" транзакций TradeTransactions.mq5, сообщения в журнале от двух экспертов будут появляться синхронно.

Однако переделка из первой "прямопоточной" версии OrderSendTransaction1.mq5 в асинхронную вторую OrderSendTransaction2.mq5 потребовала существенного усложнения кода. Возникает вопрос: нельзя ли как-то совместить принципы последовательного описания торговой логики (прозрачность кода) и параллельной обработки (скорость)?

В принципе, это возможно, но потребует один раз потрудиться над созданием некоего вспомогательного механизма.