- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Событие OnTrade
Событие OnTrade возникает при изменении списка выставленных ордеров и открытых позиций, истории ордеров и истории сделок. При любом торговом действии (выставлении/срабатывании/удалении отложенного ордера, открытии/закрытии позиции, установке защитных уровней, и т.п.) соответствующим образом изменяется история ордеров и сделок и/или список позиций и текущих ордеров. Инициатором действия может быть пользователь, программа или сервер.
Для получения события в программе необходимо описать соответствующий обработчик.
void OnTrade(void)
В случае отправки торговых запросов с помощью OrderSend/OrderSendAsync один запрос вызовет несколько событий OnTrade, так как обработка обычно происходит в несколько этапов и каждая операция может изменять состояние ордеров, позиций и торговой истории.
В общем случае нет точного соотношения по количеству вызовов OnTrade и OnTradeTransaction. OnTrade вызывается после соответствующих вызовов OnTradeTransaction.
Поскольку событие OnTrade носит обобщенный характер и не конкретизирует суть операции, оно менее популярно у разработчиков MQL-программ: ведь в коде нужно реализовать проверку всех аспектов состояния торгового счета и сравнить его с неким сохраненным состоянием — фактически с прикладным кэшем используемых в торговой стратегии торговых сущностей. В простейшем случае можно, например, запомнить тикет созданного ордера и в обработчике OnTrade опрашивать все его свойства, однако при этом вполне вероятен "холостой" анализ большого количества попутных событий, никак не связанных с конкретным ордером.
О возможности прикладного кэширования торгового окружения и истории мы поговорим в разделе о мультивалютных экспертах.
А сейчас для практического изучения OnTrade займемся экспертом, реализующим стратегию на двух отложенных ордерах OCO ("One Cancels Other"). Он будет выставлять пару стоп-ордеров на пробой диапазона и отслеживать срабатывание одного из них с тем, чтобы убрать другой. Для наглядности предусмотрим поддержку обоих типов торговых событий OnTrade и OnTradeTransaction, так что рабочая логика будет запускаться, по выбору пользователя, либо из одного обработчика, либо из другого.
Исходный код доступен в файле OCO2.mq5. Во входных параметрах: размер лота Volume (по умолчанию, 0, что означает минимальный), дистанция Distance2SLTP в пунктах до места установки каждого из ордеров, и она же определяет защитные уровни, срок истечения Expiration в секундах от времени установки, и переключатель событий ActivationBy (по умолчанию, OnTradeTransaction). Поскольку Distance2SLTP задает и отступ от текущей цены, и расстояние до стоплосса, стоплоссы двух ордеров совпадают и равны цене на момент установки.
enum EVENT_TYPE
|
Для упрощения инициализации структур запросов опишем свою собственную структуру MqlTradeRequestSyncOCO, производую от MqlTradeRequestSync.
struct MqlTradeRequestSyncOCO: public MqlTradeRequestSync
|
На глобальном уровне введем несколько объектов и переменных.
OrderFilter orders; // объект для отбора ордеров
|
Вся торговая логика за исключением момента старта будет запускаться торговыми событиями. В обработчика OnInit настраиваем объекты фильтров и ждем первого тика (ставим FirstTick в true).
int OnInit()
|
Нас интересуют только стоп-ордера (покупки/продажи) и позиции с конкретным магическим номером и текущим символом.
В функции OnTick однократно вызываем основную часть алгоритма, оформленную как RunStrategy (опишем её чуть ниже). Далее эта функция будет вызываться только из OnTrade или OnTradeTransaction.
void OnTick()
|
Например, когда включен режим OnTrade, работает данный фрагмент.
void OnTrade()
|
Обратите внимание, что количество вызовов самого обработчика OnTrade подсчитывается независимо от того, активируется ли стратегия здесь или нет. Аналогичным образом в обработчике OnTradeTransaction считается своё количество событий (даже если они происходят вхолостую). Так сделано, чтобы иметь возможность увидеть в журнале одновременно оба события и их счетчики.
Когда включен режим OnTradeTransaction, очевидно, RunStrategy запускается оттуда.
void OnTradeTransaction(const MqlTradeTransaction &transaction,
|
Следует отметить, что при торговле онлайн сработавший отложенный ордер на некоторое время может пропасть из торгового окружения в процессе переноса из действующих в историю. Когда мы получаем событие TRADE_TRANSACTION_ORDER_DELETE, ордер уже удален из списка действующих, но еще не попал в историю. Он оказывается там только в тот момент, когда мы получаем событие TRADE_TRANSACTION_HISTORY_ADD. Примечательно, что в тестере такой особенности нет, то есть удаленный ордер сразу попадает в историю и доступен там для выделения и чтения свойств уже в фазе TRADE_TRANSACTION_ORDER_DELETE.
В обоих обработчиках торговых событий мы подсчитываем и выводим в журнал их количество вызовов. Для случая OnTrade оно должно будет совпасть со счетчиком ExecutionCount, который мы скоро увидим внутри RunStrategy. А вот для OnTradeTransaction её счетчик и значение ExecutionCount будут существенно отличаться, потому что стратегия здесь вызывается сильно избирательно — по одному типу событий. Из этого можно сделать вывод, что OnTradeTransaction позволяет более эффективно расходовать ресурсы за счет вызова алгоритма, только когда это уместно.
Счетчик ExecutionCount выводится в журнал при выгрузке эксперта.
void OnDeinit(const int r)
|
Теперь, наконец представим функцию RunStrategy. Обещанный счетчик инкрементируется в самом начале.
void RunStrategy()
|
Далее описаны два массива для приема тикетов ордеров и их статусов из объекта-фильтра orders.
ulong tickets[];
|
Для начала запросим ордера, подпадающие под наши условия. Если их 2 — всё хорошо, и делать ничего не надо.
orders.select(ORDER_STATE, tickets, states);
|
Если остался один ордер, значит другой сработал и оставшийся нужно удалить.
if(n > 0) // 1 или 2+ ордера это ошибка, нужно все удалить
|
В противном случае ордеров нет. Следовательно, нужно проверить, нет ли открытой позиции: для этого используем другой объект-фильтр trades, но результаты складываем в тот же приемный массив tickets. При отсутствии позиции выставляем новую пару ордеров.
else // n == 0
|
Запустим эксперт в тестере с настройками по умолчанию, на паре EURUSD. На следующем изображении показан процесс тестирования.
Эксперт с парой отложенных стоп-ордеров по стратегии OCO в тестере
На стадии установки пары ордеров увидим в журнале следующие записи.
buy stop 0.01 EURUSD at 1.11151 sl: 1.10651 tp: 1.11651 (1.10646 / 1.10683) sell stop 0.01 EURUSD at 1.10151 sl: 1.10651 tp: 1.09651 (1.10646 / 1.10683) OnTradeTransaction(1) TRADE_TRANSACTION_ORDER_ADD, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_PLACED), ORDER_TIME_GTC, EURUSD, » » @ 1.11151, SL=1.10651, TP=1.11651, V=0.01 OnTrade(1) OnTradeTransaction(2) TRADE_TRANSACTION_REQUEST OnTradeTransaction(3) TRADE_TRANSACTION_ORDER_ADD, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_PLACED), ORDER_TIME_GTC, EURUSD, » » @ 1.10151, SL=1.10651, TP=1.09651, V=0.01 OnTrade(2) OnTradeTransaction(4) TRADE_TRANSACTION_REQUEST |
Как только один из ордеров срабатывает, происходит вот что:
order [#3 sell stop 0.01 EURUSD at 1.10151] triggered deal #2 sell 0.01 EURUSD at 1.10150 done (based on order #3) deal performed [#2 sell 0.01 EURUSD at 1.10150] order performed sell 0.01 at 1.10150 [#3 sell stop 0.01 EURUSD at 1.10151] OnTradeTransaction(5) TRADE_TRANSACTION_DEAL_ADD, D=2(DEAL_TYPE_SELL), #=3(ORDER_TYPE_BUY/ORDER_STATE_STARTED), » » EURUSD, @ 1.10150, SL=1.10651, TP=1.09651, V=0.01, P=3 OnTrade(3) OnTradeTransaction(6) TRADE_TRANSACTION_ORDER_DELETE, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_FILLED), ORDER_TIME_GTC, » » EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, V=0.01, P=3 OnTrade(4) OnTradeTransaction(7) TRADE_TRANSACTION_HISTORY_ADD, #=3(ORDER_TYPE_SELL_STOP/ORDER_STATE_FILLED), ORDER_TIME_GTC, » » EURUSD, @ 1.10151, SL=1.10651, TP=1.09651, P=3 order canceled [#2 buy stop 0.01 EURUSD at 1.11151] OnTrade(5) OnTradeTransaction(8) TRADE_TRANSACTION_ORDER_DELETE, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_CANCELED), ORDER_TIME_GTC, » » EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01 OnTrade(6) OnTradeTransaction(9) TRADE_TRANSACTION_HISTORY_ADD, #=2(ORDER_TYPE_BUY_STOP/ORDER_STATE_CANCELED), ORDER_TIME_GTC, » » EURUSD, @ 1.11151, SL=1.10651, TP=1.11651, V=0.01 OnTrade(7) OnTradeTransaction(10) TRADE_TRANSACTION_REQUEST |
Ордер #3 удалился сам, а ордер #2 удален (отменен) нашим экспертом.
Если запустить эксперт, изменив в настройках только режим работы через событие OnTrade, мы должны получить полностью аналогичные финансовые результаты (при прочих равных условиях, то есть, например, если не включены случайные задержки в генерации тиков). Единственное, что будет отличаться: количество вызовов функции RunStrategy. Например, за 4 месяца 2022 года на EURUSD,H1 при 88 сделках получим такие приблизительные показатели ExecutionCount (важно соотношение, а не абсолютные величины, связанные с тиками вашего брокера):
- OnTradeTransaction — 132;
- OnTrade — 438;
Это практическое доказательство возможности строить более избирательные алгоритмы на базе OnTradeTransaction по сравнению с OnTrade.
Данная версия эксперта OCO2.mq5 реагирует на действия с ордерами и позициями довольно прямолинейно. В частности, как только предыдущая позиция закроется по стоплоссу или тейкпрофиту, он выставит два новых ордера. Если удалить один из ордеров вручную, эксперт тут же удалит второй, а затем воссоздаст новую пару с отступом от текущей цены. Вы можете улучшить поведение за счет встраивания расписания, аналогичного тому, что сделано в эксперте-сеточнике, и не реагировать на отмененные ордера в истории (хотя, к сожалению, MQL5 не позволяет узнать, был ли ордер снят вручную или программно). Мы же представим иное направление по усовершенствованию этого эксперта в рамках изучения API экономического календаря.
Кроме того, уже в текущей версии доступен интересный режим, связанный с установкой срока истечения отложенных ордеров во входной переменной Expiration. Если пара ордеров не срабатывает, сразу по их истечении выставляется новая пара относительно изменившейся новой текущей цены. В качестве самостоятельного упражнения вы можете попробовать оптимизировать эксперт в тестере, меняя Expiration и Distance2SLTP. Программную работу с тестером, в том числе и в режиме оптимизации, мы затронем в следующей главе.
Ниже представлен один из вариантов настроек (Distance2SLTP=250, Expiration=5000), найденных на промежутке в 16 месяцев, с начала 2021 года по паре EURUSD.
Результаты тестового прогона эксперта OCO2