- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Событие 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)
|
Запустим его на графике 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
|
Стратегия запускается однократно, для чего используется 1-секундный таймер, который выключается в собственном обработчике.
int OnInit()
|
Все действия выполняются через уже знакомую структуру MqlTradeRequestSync с расширенными возможностями (MqlTradeSync.mqh): неявная инициализация полей правильными значениями, методы buy/sell для рыночных приказов, adjust — для защитных уровней, close — для закрытия позиции.
Шаг 1:
MqlTradeRequestSync request;
|
Шаг 2:
Sleep(5000); // ждем 5 секунд (пользователь может редактировать позицию)
|
Шаг 3:
Sleep(5000); // ждем еще 5 секунд
|
Промежуточные ожидания не только дают возможность успеть рассмотреть процесс, но и демонстрируют важный аспект программирования на 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 MqlTradeRequestSync: public MqlTradeRequest
|
Устанавливая публичную переменную AsyncEnabled в true или false, можно переключаться из одного режима в другой, например, в том фрагменте кода, где делаются массовые отправки приказов.
Во-вторых, для тех методов структуры, которые возвращали тикет (например, для входа в рынок), следует предусмотреть возврат поля request_id вместо order. Например, внутри методов _pending и _market у нас был оператор:
if(OrderSend(this, result)) return result.order; |
Теперь он заменяется на:
if(orderSend(this, result)) return result.order ? result.order :
|
Разумеется, когда включен асинхронный режим, мы уже не можем пользоваться методом completed для ожидания готовности результатов запроса сразу после его отправки. Но этот метод в принципе является опциональным — вы можете не использовать его даже при работе через OrderSend.
Итак, с учетом новой модификации файла MqlTradeSync.mqh создадим OrderSendTransaction2.mq5.
Этот эксперт будет отправлять первичный запрос как прежде из OnTimer, а установку защитных уровней и закрытие позиции — в OnTradeTransaction поэтапно. Хотя у нас между этапами не будет на этот раз искусственной задержки, сама последовательность состояний является стандартной для многих экспертов: открыл позицию, модифицировал, закрыл (при выполнении неких рыночных условий, которые здесь оставлены "за кадром").
Отслеживать состояние позволят 2 глобальные переменные: RequestID с идентификатором последнего отправленного запроса (результат которого мы ожидаем) и PositionTicket с тикетом открытой позиции. Когда позиции еще нет или уже нет, тикет равен 0.
uint RequestID = 0;
|
В обработчике OnInit включим асинхронный режим.
int OnInit()
|
Функция OnTimer теперь значительно короче.
void OnTimer()
|
При успешном выполнении запроса мы получаем только request_id и сохраняем его в переменной RequestID. Печать статусов теперь содержит вопросительный знак, например, "OK Open?", потому что фактический результат еще не известен.
OnTradeTransaction существенно усложнилась из-за проверки результатов и выполнения последующих торговых приказов по условиям. Рассмотрим её постепенно.
В данном случае вся торговая логика переехала внутрь ветви для транзакций типа TRADE_TRANSACTION_REQUEST. Разумеется, разработчик может по желанию использовать и другие типы, но мы используем этот, поскольку он содержит информацию в виде привычной структуры MqlTradeResult, то есть как бы представляет собой отложенную концовку асинхронного вызова OrderSendAsync.
void OnTradeTransaction(const MqlTradeTransaction &transaction,
|
Нас должны интересовать только запросы с идентификатором, который мы ожидаем. Поэтому следующим оператором будет вложенный if. В его блоке мы заранее описываем объект MqlTradeRequestSync, потому что потребуется отправлять очередные торговые запросы согласно плану.
if(result.request_id == RequestID)
|
Рабочих типов запроса у нас только два, поэтому добавляем для них еще один вложенный if.
if(request.action == TRADE_ACTION_DEAL)
|
Обратите внимание, что TRADE_ACTION_DEAL используется и для открытия, и для закрытия позиции, а потому потребуется еще один if, в котором будем различать эти два состояния в зависимости от значения переменной PositionTicket.
if(PositionTicket == 0)
|
В рассматриваемой торговой стратегии нет "доливок" (для неттинга) или нескольких позиций (для хеджинга), из-за чего данная часть логически проста. В реальных экспертах потребуется гораздо больше различных оценок промежуточных состояний.
В случае уведомления об открытии позиции блок кода выглядит следующим образом:
if(PositionTicket == 0)
|
Для простоты мы опустили здесь проверку ошибок, в частности реквотов, но пример их обработки можно увидеть в прилагаемом исходном коде. Напомним, что все эти проверки уже были реализованы в методах структуры MqlTradeRequestSync, но работают они только в синхронном режиме, и поэтому нам приходится их повторять в явном виде.
Дальнейший фрагмент кода по установке защитных уровней почти не изменился.
if(PositionTicket == 0)
|
Единственное отличие: мы заполняем переменную RequestID идентификатором нового запроса TRADE_ACTION_SLTP.
Получение уведомления о сделке при ненулевом PositionTicket подразумевает, что произошло закрытие позиции.
if(PositionTicket == 0)
|
В случае успешного удаления позицию не удастся выделить с помощью PositionSelectByTicket, а потому мы обнуляем RequestID и PositionTicket. Эксперт при этом возвращается в начальное состояние и готов совершить следующий цикл покупки/продажи-модификации-закрытия.
Нам осталось рассмотреть отправку запроса на закрытие позиции. В нашей упрощенной до минимума стратегии это происходит сразу после успешной модификации защитных уровней.
if(request.action == TRADE_ACTION_DEAL)
|
Вот и вся функция 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 потребовала существенного усложнения кода. Возникает вопрос: нельзя ли как-то совместить принципы последовательного описания торговой логики (прозрачность кода) и параллельной обработки (скорость)?
В принципе, это возможно, но потребует один раз потрудиться над созданием некоего вспомогательного механизма.