Рецепты MQL5 - обработка события TradeTransaction

Denis Kirichenko | 8 сентября, 2014

Введение

В своей статье я хотел бы познакомить читателя с одним из способов контролировать торговые события средствами MQL5. Сразу хочу сказать, что данной теме были посвящены статьи, например «Обработка торговых событий в эксперте при помощи функции OnTrade()». Но в отличие от упомянутого материала я буду использовать другой обработчик – OnTradeTransaction().

Хотелось бы отметить вот какой момент. В текущей версии языка MQL5 формально есть 14 обработчиков событий клиентского терминала. Причём, у программиста есть возможность создавать свои пользовательские события посредством EventChartCustom() и обрабатывать их с помощью OnChartEvent(). Но нигде в Документации нет упоминания такого термина, как «Событийно-ориентированное программирование» (СОП). Ведь именно с учётом принципов СОП создаётся любая программа в MQL5. Возьмите хотя бы любой шаблон будущего советника, когда на шаге «Обработчики событий для советника» пользователю предлагается сделать свой выбор.

Очевидно, что в MQL5 так или иначе используется механизм событийно-ориентированного программирования. В языке могут присутствовать блоки программы, состоящие из двух частей: выборки события и обработки события. Причём, если речь идёт о событиях клиентского терминала, то программисту доступна только вторая часть - обработчик события. Правда, для некоторых событий есть исключения. К ним относятся таймер и пользовательское событие. Контроль данных событий полностью отдан в руки программиста.


1. Событие TradeTransaction

Итак, давайте сузим предмет нашего внимания до заявленной темы и обратимся к официальной информации.

Согласно Документации событие TradeTransaction есть результат выполнения определенных действий с торговым счетом. Само действие состоит из нескольких этапов, которые и определяются транзакциями. Например, одно из самых популярных действий с торговым счётом – открытие позиции посредством рыночного ордера – реализуется по следующим этапам:

  1. Создать торговый приказ;
  2. Проверить торговый приказ;
  3. Отправить торговый приказ на сервер;
  4. Получить ответ о выполнении торгового приказа на сервере.

Но такая последовательность отражает скорее логику работы связки терминал-сервер, что выражается в строчках кода советника. С точки зрения торгового события TradeTransaction открытие позиции по рынку происходит следующим образом:

  1. MQL5-программа получает уведомление от сервера о результате выполненной заявки;
  2. Заявка в форме ордера с уникальным тикетом попадает в список открытых ордеров;
  3. После исполнения ордер удаляется из списка открытых ордеров;
  4. Затем ордер уходит в историю;
  5. Также в истории появляется сделка, являющаяся результатом исполнения ордера.

В итоге на открытие позиции понадобилось 5 вызовов обработчика OnTradeTransaction().

Чуть позже разберёмся с программным кодом, а пока предлагаю обратиться к заголовку функции-обработчика. Он имеет 3 входных параметра.

void  OnTradeTransaction(
   const MqlTradeTransaction&    trans,        // структура торговой транзакции
   const MqlTradeRequest&        request,      // структура запроса
   const MqlTradeResult&         result        // структура ответа
   );

Детально о параметрах написано в Документации. Здесь хотел бы заметить, что параметр структуры торговой транзакции – это своего рода слепок той информации, которую обработчик транзакции смог получить на текущем вызове.

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

В языке MQL5 есть специальное перечисление, которое отвечает за тип торговой транзакции - ENUM_TRADE_TRANSACTION_TYPE. Чтобы узнать, к какому типу относится та или иная торговая транзакция, нужно обратиться к параметру-константе типа MqlTradeTransaction.

struct MqlTradeTransaction
  {
   ulong                         deal;             // Тикет сделки
   ulong                         order;            // Тикет ордера
   string                        symbol;           // Имя торгового инструмента
   ENUM_TRADE_TRANSACTION_TYPE   type;             // Тип торговой транзакции
   ENUM_ORDER_TYPE               order_type;       // Тип ордера
   ENUM_ORDER_STATE              order_state;      // Состояние ордера
   ENUM_DEAL_TYPE                deal_type;        // Тип сделки
   ENUM_ORDER_TYPE_TIME          time_type;        // Тип ордера по времени действия
   datetime                      time_expiration;  // Срок истечения ордера
   double                        price;            // Цена 
   double                        price_trigger;    // Цена срабатывания стоп-лимитного ордера
   double                        price_sl;         // Уровень Stop Loss
   double                        price_tp;         // Уровень Take Profit
   double                        volume;           // Объем в лотах
  };

Четвёртое поле структуры как раз и является искомым перечислением.


2. Обработка позиций

Практически все торговые операции, связанные с обработкой позиций, влекут 5 вызовов обработчика OnTradeTransaction(). К таким операциям относятся:

И лишь одна операция - модификация позиции – вызовет обработчик TradeTransaction два раза.

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

Предварительно нужно будет создать шаблон будущего советника, в котором обязательно должен присутствовать обработчик события TradeTransaction. Свою версию такого шаблона я назвал TradeProcessor.mq5. В него ещё добавил возможность вывода информации в лог о значениях полей структур, являющихся параметрами функции-обработчика. С этими записями придётся, что называется, повозиться, когда будем их анализировать. Зато потом затраты окупятся возможностью видеть всю картину происходящего.

На любом графике терминала MetaTrader 5 нужно запустить советник в режиме отладки.

Откроем вручную позицию и посмотрим на код. Первый вызов обработчика будет таким (рис. 1).


Рис.1. Поле type равно TRADE_TRANSACTION_REQUEST

Рис.1. Поле type равно TRADE_TRANSACTION_REQUEST

В логе появятся следующие записи:

IO      0       17:37:53.233    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
NK      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
RR      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
DE      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тикет ордера: 0
JS      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_STARTED
JN      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY
FD      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Цена: 0.0000
FN      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
HF      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
FQ      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
RR      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Торговый инструмент: 
HD      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
GS      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
DN      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_REQUEST
FK      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.00

В этом блоке интересна только запись, касающаяся типа транзакции. Видим, что тип относился к запросу (TRADE_TRANSACTION_REQUEST).

В блоке «Запрос» можно получить информацию о деталях заявки.

QG      0       17:37:53.233    TradeProcessor (EURUSD,H1)      ---===Запрос===--- HL      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тип выполняемого действия: TRADE_ACTION_DEAL EE      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Комментарий к ордеру: JP      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Отклонение от запрашиваемой цены: 0 GS      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Срок истечения ордера: 1970.01.01 00:00 LF      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Магик советника: 0 FM      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535869 EJ      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Цена: 1.3137 QR      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Уровень Stop Loss ордера: 0.0000 IJ      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Уровень Take Profit ордера: 0.0000 KK      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Уровень StopLimit ордера: 0.0000 FS      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Торговый инструмент: EURUSD RD      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY

В блок «Ответ» попадут данные о результате исполнения заявки.

KG      0       17:37:53.233    TradeProcessor (EURUSD,H1)      ---===Ответ===---
JR      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Код результата операции: 10009
GD      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тикет сделки: 15258202
NR      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535869
EF      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Объём сделки: 0.11
MN      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Цена в сделке: 1.3137
HJ      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Бид: 1.3135
PM      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Аск: 1.3137
OG      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Комментарий к операции: 
RQ      0       17:37:53.233    TradeProcessor (EURUSD,H1)      Идентификатор запроса: 1

Именно на первом вызове можно узнать дополнительную информацию о запросе, проанализировав другие параметры обработчика - структуры запроса и ответа.

Второй вызов обработчика касается того, что ордер добавляется в список открытых ордеров (рис. 2).

Рис.2. Поле type равно TRADE_TRANSACTION_ORDER_ADD

Рис.2. Поле type равно TRADE_TRANSACTION_ORDER_ADD

В логе интерес представляет только блок «Транзакция».

MJ      0       17:41:12.280    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
JN      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
FG      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
LM      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535869
LI      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_STARTED
LP      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY
QN      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Цена: 1.3137
PD      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
NL      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
PG      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
DL      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Торговый инструмент: EURUSD
JK      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
QD      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
IQ      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_ORDER_ADD
PL      0       17:41:12.280    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.11

Здесь видим, что ордер уже получил свой тикет и прочие параметры (символ, цена, объём) и находится в списке открытых ордеров.

Третий вызов обработчика связан с удалением ордера из списка открытых (рис. 3).

Рис.3. Поле type равно TRADE_TRANSACTION_ORDER_DELETE

Рис.3. Поле type равно TRADE_TRANSACTION_ORDER_DELETE

В логе интерес представляет только блок «Транзакция».

PF      0       17:52:36.722    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
OE      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
KL      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
EH      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535869
QM      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_STARTED
QK      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY
HS      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Цена: 1.3137
MH      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
OP      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
EJ      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
IH      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Торговый инструмент: EURUSD
KP      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
LO      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
HG      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_ORDER_DELETE
CG      0       17:52:36.722    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.11

Кроме типа транзакции ничего нового тут нет.

Четвёртый раз обработчик вызывается тогда, когда в истории появляется новый «исторический» ордер (рис. 4).

Рис.4. Поле type равно TRADE_TRANSACTION_HISTORY_ADD

Рис.4. Поле type равно TRADE_TRANSACTION_HISTORY_ADD

Смотрим в лог на блок «Транзакция».

QO      0       17:57:32.234    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
RJ      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
NS      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
DQ      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535869
EH      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_FILLED
RL      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY
KJ      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Цена: 1.3137
NO      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
PI      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
FS      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
JS      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Торговый инструмент: EURUSD
LG      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
KP      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
OL      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_HISTORY_ADD
JH      0       17:57:32.234    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.00

На этом этапе видим, что ордер был исполнен.

И последний (пятый) вызов имеет место при добавлении сделки в историю (рис. 5).

Рис.5. Поле type равно TRADE_TRANSACTION_DEAL_ADD

Рис.5. Поле type равно TRADE_TRANSACTION_DEAL_ADD

В логе снова смотрим только на блок «Транзакция».

OE      0       17:59:40.718    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
MS      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Тикет сделки: 15258202
RJ      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
HN      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535869
LK      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_STARTED
LE      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY
MM      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Цена: 1.3137
PF      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
NN      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
PI      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
DJ      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Торговый инструмент: EURUSD
JM      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
QI      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
CK      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_DEAL_ADD
RQ      0       17:59:40.718    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.11

Тут важным пунктом является тикет сделки.

Представлю шаблоны связок транзакционных типов. Для позиций их будет всего два. Первый выглядит так (рис. 6).

Рис.6. Первый шаблон связок транзакционных типов

Рис.6. Первый шаблон связок транзакционных типов


Этому шаблону подчиняются все торговые операции, связанные с обработкой позиций, кроме единственной – модификации позиции. Последняя операция включает в свой состав обработку следующих двух транзакций (рис. 7).

Рис.7. Второй шаблон связок транзакционных типов

Рис.7. Второй шаблон связок транзакционных типов

Таким образом, модификацию позиции нельзя отследить в истории сделок и ордеров.

Вот, в общем, и всё, что касается позиций.



3. Обработка отложенных ордеров

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

При модификации ордера, как и в случае с модификацией позиции, обработчик будет вызван 2 раза. Три вызова появится при установке и отмене ордера. Четыре раза событие TradeTransaction наступит при удалении ордера или его срабатывании.

Попробуем выставить отложенный ордер. На любом графике терминала MetaTrader 5 нужно снова запустить советник в режиме отладки.

Первый вызов обработчика будет связан с запросом (рис. 8).

Рис.8 Поле type равно TRADE_TRANSACTION_REQUEST

Рис.8. Поле type равно TRADE_TRANSACTION_REQUEST

В логе увидим записи:

IO      0       18:13:33.195    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
NK      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
RR      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
DE      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тикет ордера: 0
JS      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_STARTED
JN      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY
FD      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Цена: 0.0000
FN      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
HF      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
FQ      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
RR      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Торговый инструмент: 
HD      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
GS      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
DN      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_REQUEST
FK      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.00
NS      0       18:13:33.195    TradeProcessor (EURUSD,H1)      

QG      0       18:13:33.195    TradeProcessor (EURUSD,H1)      ---===Запрос===---
IQ      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип выполняемого действия: TRADE_ACTION_PENDING
OE      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Комментарий к ордеру: 
PQ      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Отклонение от запрашиваемой цены: 0
QS      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Срок истечения ордера: 1970.01.01 00:00
FI      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Магик советника: 0
CM      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535983
PK      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Цена: 1.6500
KR      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Уровень Stop Loss ордера: 0.0000
OI      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Уровень Take Profit ордера: 0.0000
QK      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Уровень StopLimit ордера: 0.0000
QQ      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Торговый инструмент: GBPUSD
RD      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY_LIMIT
LS      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип ордера по исполнению: ORDER_FILLING_RETURN
MN      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
IK      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.14
NS      0       18:13:33.195    TradeProcessor (EURUSD,H1)      
CD      0       18:13:33.195    TradeProcessor (EURUSD,H1)      ---===Ответ===---
RQ      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Код результата операции: 10009
JI      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
GM      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535983
LF      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Объём сделки: 0.14
JN      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Цена в сделке: 0.0000
MK      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Бид: 0.0000
CM      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Аск: 0.0000
IG      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Комментарий к операции: 
DQ      0       18:13:33.195    TradeProcessor (EURUSD,H1)      Идентификатор запроса: 1

Второй вызов обработчика добавит ордер в список открытых (рис. 9).

Рис.9. Поле type равно TRADE_TRANSACTION_ORDER_ADDED

Рис.9. Поле type равно TRADE_TRANSACTION_ORDER_ADDED

В логе нас интересует только блок «Транзакция».

HJ      0       18:17:02.886    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
GQ      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
CH      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
RL      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535983
II      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_STARTED
OG      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY_LIMIT
GL      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Цена: 1.6500
IE      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
CO      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
IF      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
PL      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Торговый инструмент: GBPUSD
OL      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
HJ      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
LF      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_ORDER_ADD
FR      0       18:17:02.886    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.14

Третий вызов обработчика обновит данные по выставленному ордеру (рис. 10).

В частности, состояние ордера получит значение ORDER_STATE_PLACED.

Рис.10. Поле type равно TRADE_TRANSACTION_ORDER_UPDATE

Рис.10. Поле type равно TRADE_TRANSACTION_ORDER_UPDATE

В журнале увидим записи по блоку «Транзакция».

HS      0       18:21:27.004    TradeProcessor (EURUSD,H1)      ---===Транзакция===---
GF      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Тикет сделки: 0
CO      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Тип сделки: DEAL_TYPE_BUY
RE      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Тикет ордера: 22535983
KM      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Состояние ордера: ORDER_STATE_PLACED
QH      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Тип ордера: ORDER_TYPE_BUY_LIMIT
EG      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Цена: 1.6500
GL      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Уровень Stop Loss: 0.0000
ED      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Уровень Take Profit: 0.0000
GO      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Цена срабатывания стоп-лимитного ордера: 0.0000
RE      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Торговый инструмент: GBPUSD
QS      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Срок истечения отложенного ордера: 1970.01.01 00:00
JS      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Тип ордера по времени действия: ORDER_TIME_GTC
RD      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Тип торговой транзакции: TRADE_TRANSACTION_ORDER_UPDATE
JK      0       18:21:27.004    TradeProcessor (EURUSD,H1)      Объём в лотах: 0.14

Тут важной строкой является состояние ордера.

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

Установка отложенного ордера потребует провести 3 транзакции (рис. 11).

Рис.11. Транзакции, обрабатывающие создание отложенного ордера

Рис.11. Транзакции, обрабатывающие создание отложенного ордера

Модификация отложенного ордера сгенерирует 2 транзакции (рис. 12).

Рис.12 Транзакции, обрабатывающие изменение отложенного ордера

Рис.12. Транзакции, обрабатывающие изменение отложенного ордера


Если нужно будет удалить отложенный ордер, то обработчик OnTradeTransaction() будет вызван 4 раза (рис. 13).

Рис.13. Транзакции, обрабатывающие удаление отложенного ордера

Рис.13. Транзакции, обрабатывающие удаление отложенного ордера

Отмена отложенного ордера проходит по следующей схеме (рис. 14).

Рис.14. Транзакции, обрабатывающие отмену отложенного ордера

Рис.14. Транзакции, обрабатывающие отмену отложенного ордера


И последняя торговая операция - срабатывание отложенного ордера - породит 4 различных транзакции (рис. 15).

Рис.15. Транзакции, обрабатывающие активацию отложенного ордера

Рис.15. Транзакции, обрабатывающие активацию отложенного ордера

Не буду приводить записи из журнала по каждой комбинации транзакций. При желании читатель может самостоятельно их изучить, запустив код на исполнение.


4. Универсальный обработчик

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

В моём примере используется последовательная обработка транзакций. Однако разработчик языка MQL5 утверждает следующее:

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

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

Если говорить в общем, то позиции и ордера могут иметь общие типы транзакций. Всего имеется 11 типов транзакций. Из них к непосредственной торговле из терминала мало относятся 4 типа:

В статье об этих типах речь идти не будет. Эти типы, как заявляет разработчик, предусмотрены для расширения функциональности на стороне торгового сервера. Автор статьи честно признаётся, что с данными типами не встречался.

Тогда остаётся 7 полноценных типов, которые чаще всего и обрабатываются в блоке OnTradeTransaction().

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

//--- ========== Типы транзакций [START]
   switch(trans_type)
     {
      //--- 1) если это запрос
      case TRADE_TRANSACTION_REQUEST:
        {

         //---
         break;
        }
      //--- 2) если это добавление нового открытого ордера
      case TRADE_TRANSACTION_ORDER_ADD:
        {

         //---
         break;
        }
      //--- 3) если это удаление ордера из списка открытых
      case TRADE_TRANSACTION_ORDER_DELETE:
        {

         //---     
         break;
        }
      //--- 4) если это добавление ордера в историю
      case TRADE_TRANSACTION_HISTORY_ADD:
        {

         //---     
         break;
        }
      //--- 5) если это добавление сделки в историю
      case TRADE_TRANSACTION_DEAL_ADD:
        {

         //---
         break;
        }
      //--- 6) если это модификация позиции
      case TRADE_TRANSACTION_POSITION:
        {

         //---
         break;
        }
      //--- 7) если это изменение открытого ордера
      case TRADE_TRANSACTION_ORDER_UPDATE:
        {

         //---
         break;
        }
     }
//--- ========== Типы транзакций [END]

В зависимости от текущего типа транзакции будем пробовать поставить диагноз обрабатываемой торговой операции. Чтобы определить, с чем мы работаем – с позицией или ордером, поручим case-модулю обработки запроса запоминать тип торговой операции.

Тогда сам этот модуль будет выглядеть так:

//--- 1) если это запрос
      case TRADE_TRANSACTION_REQUEST:
        {
         //---
         last_action=request.action;
         string action_str;

         //--- запрос на что?
         switch(last_action)
           {
            //--- а) по рынку
            case TRADE_ACTION_DEAL:
              {
               action_str="поставить рыночный ордер";
               trade_obj=TRADE_OBJ_POSITION;
               break;
              }
            //--- б) выставить отложенный ордер
            case TRADE_ACTION_PENDING:
              {
               action_str="выставить отложенный ордер";
               trade_obj=TRADE_OBJ_ORDER;
               break;
              }
            //--- в) изменить позицию
            case TRADE_ACTION_SLTP:
              {
               trade_obj=TRADE_OBJ_POSITION;
               //---
               StringConcatenate(action_str,request.symbol,": изменить значения Stop Loss",
                                 " и Take Profit");

               //---
               break;
              }
            //--- г) изменить ордер
            case TRADE_ACTION_MODIFY:
              {
               action_str="изменить параметры отложенного ордера";
               trade_obj=TRADE_OBJ_ORDER;
               break;
              }
            //--- д) удалить ордер
            case TRADE_ACTION_REMOVE:
              {
               action_str="удалить отложенный ордер";
               trade_obj=TRADE_OBJ_ORDER;
               break;
              }
           }
         //---
         if(InpIsLogging)
            Print("Поступил запрос: "+action_str);

         //---
         break;
        }

Несложно заметить несколько переменных.

static ENUM_TRADE_REQUEST_ACTIONS last_action; // рыночное действие на 1-м проходе

Переменная last_action будет запоминать, зачем вообще была инициирована работа обработчика.

static ENUM_TRADE_OBJ trade_obj;               // на 1-м проходе задаёт торговый объект

Переменная trade_obj будет запоминать, что обрабатывалось – позиция или ордер. Для этого нужно будет создать перечисление ENUM_TRADE_OBJ.

Далее, перемещаемся в модуль, который займётся обработкой транзакции типа TRADE_TRANSACTION_ORDER_ADD:

//--- 2) если это добавление нового открытого ордера
      case TRADE_TRANSACTION_ORDER_ADD:
        {
         if(InpIsLogging)
           {
            if(trade_obj==TRADE_OBJ_POSITION)
               Print("Открыть новый рыночный ордер: "+
                     EnumToString(trans.order_type));
            //---
            else if(trade_obj==TRADE_OBJ_ORDER)
               Print("Установить новый отложенный ордер: "+
                     EnumToString(trans.order_type));
           }
         //---
         break;
        }

Здесь всё достаточно просто. Если на первом шаге обрабатывалась позиция, то на текущем появится запись в журнале "Открыть новый рыночный ордер: ", иначе - "Установить новый отложенный ордер:". Кроме информационных, никаких других действий в этом блоке нет.

Теперь смотрим на третий модуль – обработка типа TRADE_TRANSACTION_ORDER_DELETE:

//--- 3) если это удаление ордера из списка открытых
      case TRADE_TRANSACTION_ORDER_DELETE:
        {
         if(InpIsLogging)
            PrintFormat("Удалён из списка открытых ордер: #%d, "+
                        EnumToString(trans.order_type),trans.order);
         //---     
         break;
        }

Модуль также несёт только информационную нагрузку.

Четвёртый case-модуль обрабатывает тип TRADE_TRANSACTION_HISTORY_ADD:

//--- 4) если это добавление ордера в историю
      case TRADE_TRANSACTION_HISTORY_ADD:
        {
         if(InpIsLogging)
            PrintFormat("Добавлен в историю ордер: #%d, "+
                        EnumToString(trans.order_type),trans.order);

         //--- если обрабатывается отложенный ордер
         if(trade_obj==TRADE_OBJ_ORDER)
           {
            //--- если 3-й проход
            if(gTransCnt==2)
              {
               //--- если ордер был отменён, проверить сделки
               datetime now=TimeCurrent();

               //--- запросить история сделок и ордеров
               HistorySelect(now-PeriodSeconds(PERIOD_H1),now);

               //--- попытка найти сделку для ордера
               CDealInfo myDealInfo;
               int all_deals=HistoryDealsTotal();
               //---
               bool is_found=false;
               for(int deal_idx=all_deals;deal_idx>=0;deal_idx--)
                  if(myDealInfo.SelectByIndex(deal_idx))
                     if(myDealInfo.Order()==trans.order)
                        is_found=true;

               //--- если сделка не найдена
               if(!is_found)
                 {
                  is_to_reset_cnt=true;
                  //---
                  PrintFormat("Ордер был отменён: #%d",trans.order);
                 }
              }
            //--- если 4-й проход
            if(gTransCnt==3)
              {
               is_to_reset_cnt=true;
               PrintFormat("Ордер был удалён: #%d",trans.order);
              }
           }
         //---     
         break;
        }

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

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

Пятый case-модуль обрабатывает тип TRADE_TRANSACTION_DEAL_ADD. Это самый большой по размеру строк кода блок программы.

В нём проверяем сделку. Важно выбрать сделку по тикету, чтобы получить доступ к её свойствам. По типу сделки можно узнать была позиция открыта, закрыта и т.д. Также, можно получить информацию о том, что сработал отложенный ордер. Это единственный случай, когда отложенный ордер может породить сделку в контексте работы обработчика события TradeTransaction.

//--- 5) если это добавление сделки в историю
      case TRADE_TRANSACTION_DEAL_ADD:
        {
         is_to_reset_cnt=true;
         //---
         ulong deal_ticket=trans.deal;
         ENUM_DEAL_TYPE deal_type=trans.deal_type;
         //---
         if(InpIsLogging)
            PrintFormat("Добавлена в историю сделка: #%d, "+EnumToString(deal_type),deal_ticket);

         if(deal_ticket>0)
           {
            datetime now=TimeCurrent();

            //--- запросить история сделок и ордеров
            HistorySelect(now-PeriodSeconds(PERIOD_H1),now);

            //--- выбрать сделку по тикету
            if(HistoryDealSelect(deal_ticket))
              {
               //--- проверить сделку
               CDealInfo myDealInfo;
               myDealInfo.Ticket(deal_ticket);
               long order=myDealInfo.Order();

               //--- параметры сделки
               ENUM_DEAL_ENTRY  deal_entry=myDealInfo.Entry();
               double deal_vol=0.;
               //---
               if(myDealInfo.InfoDouble(DEAL_VOLUME,deal_vol))
                  if(myDealInfo.InfoString(DEAL_SYMBOL,deal_symbol))
                    {
                     //--- позиция
                     CPositionInfo myPos;
                     double pos_vol=WRONG_VALUE;
                     //---
                     if(myPos.Select(deal_symbol))
                        pos_vol=myPos.Volume();

                     //--- если был вход в рынок
                     if(deal_entry==DEAL_ENTRY_IN)
                       {
                        //--- 1) открытие позиции
                        if(deal_vol==pos_vol)
                           PrintFormat("\n%s: открыта новая позиция",deal_symbol);

                        //--- 2) добавление к открытой позиции        
                        else if(deal_vol<pos_vol)
                           PrintFormat("\n%s: добавление к текущей позиции",deal_symbol);
                       }

                     //--- если был выход с рынка
                     else if(deal_entry==DEAL_ENTRY_OUT)
                       {
                        if(deal_vol>0.0)
                          {
                           //--- 1) закрытие позиции
                           if(pos_vol==WRONG_VALUE)
                              PrintFormat("\n%s: закрыта позиция",deal_symbol);

                           //--- 2) уменьшение открытой позиции        
                           else if(pos_vol>0.0)
                              PrintFormat("\n%s: уменьшение текущей позиции",deal_symbol);
                          }
                       }

                     //--- если был переворот
                     else if(deal_entry==DEAL_ENTRY_INOUT)
                       {
                        if(deal_vol>0.0)
                           if(pos_vol>0.0)
                              PrintFormat("\n%s: переворот позиции",deal_symbol);
                       }
                    }

               //--- активация ордера
               if(trade_obj==TRADE_OBJ_ORDER)
                  PrintFormat("Активация отложенного ордера: %d",order);
              }
           }

         //---
         break;
        }

Тип транзакции TRADE_TRANSACTION_POSITION является уникальным и обрабатывается только тогда, когда происходит модификация позиции:

//--- 6) если это модификация позиции
      case TRADE_TRANSACTION_POSITION:
        {
         is_to_reset_cnt=true;
         //---
         PrintFormat("Модификация позиции: %s",deal_symbol);
         //---
         if(InpIsLogging)
           {
            PrintFormat("Новая цена slop loss: %0."+
                        IntegerToString(_Digits)+"f",trans.price_sl);
            PrintFormat("Новая цена take profit: %0."+
                        IntegerToString(_Digits)+"f",trans.price_tp);
           }

         //---
         break;
        }

Последний case-модуль включается при обработке типа TRADE_TRANSACTION_ORDER_UPDATE.

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

//--- 7) если это изменение открытого ордера
      case TRADE_TRANSACTION_ORDER_UPDATE:
        {

         //--- если был 1-ый проход
         if(gTransCnt==0)
           {
            trade_obj=TRADE_OBJ_ORDER;
            PrintFormat("Снятие ордера: #%d",trans.order);
           }
         //--- если был 2-ой проход
         if(gTransCnt==1)
           {
            //--- если модификация ордера
            if(last_action==TRADE_ACTION_MODIFY)
              {
               PrintFormat("Изменён отложенный ордер: #%d",trans.order);
               //--- сбросить счётчик
               is_to_reset_cnt=true;
              }
            //--- если удаление ордера
            if(last_action==TRADE_ACTION_REMOVE)
              {
               PrintFormat("Удалить отложенный ордер: #%d",trans.order);

              }
           }
         //--- если был 3-ий проход
         if(gTransCnt==2)
           {
            PrintFormat("Установлен новый отложенный ордер: #%d, "+
                        EnumToString(trans.order_type),trans.order);
            //--- сбросить счётчик
            is_to_reset_cnt=true;
           }

         //---
         break;
        }

Итак, если этот тип появился при первом срабатывании OnTradeTransaction(), то ордер либо был отменён, либо сработал.

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

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

Легко заметить, что в коде используется булевая переменная is_to_reset_cnt. Она выступает в качестве флага для сброса счётчика проходов обработчика OnTradeTransaction().

Вот в принципе и все, что касается обработки события TradeTransaction. Да, ещё добавил бы паузу в начале вызова обработчика. Тогда вероятность того, что в историю ещё не попала сделка или ордер будет минимизирована.


Заключение

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

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