- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Полное и частичное закрытие встречных позиций (хедж)
На счетах с хеджированием разрешено одновременно открывать несколько позиций, причем в большинстве случаев эти позиции могут быть и противоположного направления. В некоторых юрисдикциях на хедж-счета накладывают ограничение: одновременно можно иметь только позиции одного направления — выяснить это можно, получив код ошибки TRADE_RETCODE_HEDGE_PROHIBITED при попытке выполнить встречную торговую операцию. Также зачастую данное ограничение коррелирует с настройкой свойства счета ACCOUNT_FIFO_CLOSE, равного true.
Когда две противоположных позиции открыты одновременно, платформа поддерживает механизм их одновременного взаимного закрытия с помощью операции TRADE_ACTION_CLOSE_BY. Для выполнения данного действия в структуре MqlTradeTransaction следует заполнить помимо поля action только два поля — position и position_by с тикетами закрываемых позиций.
Доступность этой возможности зависит от свойства SYMBOL_ORDER_MODE финансового инструмента: в битовой маске разрешенных флагов должен присутствовать SYMBOL_ORDER_CLOSEBY (64).
Данная операция не только упрощает закрытие (одна операция вместо двух), но и позволяет сэкономить один спред.
Как известно, любая новая позиция начинает торговаться с убытка величиной со спред. Например, при покупке какого-либо финансового инструмента сделка заключается по цене Ask, но для сделки выхода, то есть продажи, актуальной является цена Bid. Для короткий позиции — обратная ситуация: сразу после входа по цене Bid мы начинаем отслеживать цену Ask для потенциального выхода.
Если одновременно закрывать позиции штатным способом, их цены выхода окажутся на расстоянии текущего спреда друг от друга. Однако если воспользоваться операцией TRADE_ACTION_CLOSE_BY, то обе позиции закроются без учета текущих цен. Цена, по которой происходит взаимозачет позиций, равна цене открытия позиции position_by (в структуре запроса). Именно она фигурирует в ордере ORDER_TYPE_CLOSE_BY, генерируемом по запросу TRADE_ACTION_CLOSE_BY.
К сожалению, в отчетах в разрезе сделок и позиций цены закрытия и открытия противоположных позиций/сделок отображаются парами одинаковых значений, в зеркальном направлении, из-за чего складывается впечатление о двойной прибыли или убытке. На самом деле финансовый результат операции (разница между ценами с поправкой на лот) записывается только на сделку выхода первой позиции (поле position в структуре запроса). Результат второй сделки выхода всегда равен 0, невзирая на разницу цен.
Еще одно последствие этой асимметричности заключается в том, что от перемены мест тикетов в полях position и position_by, в торговом отчете меняется статистика прибылей и убытков в разрезе длинных и коротких трейдов, например, прибыльных длинных трейдов может стать больше ровно настолько, насколько уменьшилось количество прибыльных коротких трейдов. Но на общий результат, это в принципе не должно влиять, если считать, что от порядка передачи тикетов задержка в исполнении приказа не зависит.
На следующей схеме приведено графическое пояснение процесса (размер спредов намеренно преувеличен).
Учет спреда при встречном закрытии прибыльных позиций
Здесь показан случай прибыльной пары позиций. Если бы позиции имели противоположные направления и находились в убытке, то при их раздельном закрытии спред учёлся бы дважды (в каждой). Встречное закрытие позволяет уменьшить убыток на один спред.
Учет спреда при встречном закрытии убыточных позиций
Встречные позиции не обязаны быть равного объема. Операция встречного закрытия сработает на минимальный из двух объемов.
В файле MqlTradeSync.mqh встречное закрытие реализовано с помощью метода closeby с двумя параметрами, принимающими тикеты позиций.
struct MqlTradeRequestSync: public MqlTradeRequest
|
Для контроля результата закрытия мы запоминаем в переменной result.position тикет меньшей позиции. В методе completed и структуре MqlTradeResultSync уже все готово для синхронного отслеживания закрытия позиции: этот же алгоритм работал и при обычном закрытии позиции.
struct MqlTradeRequestSync: public MqlTradeRequest
|
Встречные позиции обычно используются как замена стоп-приказу или попытка взять прибыль на кратковременной коррекции, оставаясь в рынке и по основному тренду. Вариант использования псевдо-стоп-приказа позволяет отложить решение о реальном закрытии позиций на некоторое время, продолжая анализ движений рынка в надежде на обратный разворот в нужную сторону. Однако следует напомнить, что "локируемые" позиции требуют повышенных залоговых средств, а также подвержены начислению свопов. Именно поэтому сложно представить торговую стратегию, в чистом виде построенную на встречных позициях, которая могла бы послужить примером для данного раздела.
Разовьем идею побаровой стратегии "price action", изложенную в предыдущем примере. Назовем новый эксперт TradeCloseBy.mq5.
Оставим прежний сигнал на вход в рынок при обнаружении двух последовательных свечей, закрывшихся в одном направлении. За его формирование будет отвечать функция GetTradeDirection — без изменений. Однако разрешим повторные входы, если тренд продолжается. Общее максимально разрешенное количество позиций зададим во входной переменной PositionLimit, по умолчанию — 5.
Функция GetMyPositions претерпит некоторые изменения: у неё добавятся два параметра — ссылки на массивы, принимающие тикеты позиций — раздельно покупки и продажи.
#define PUSH(A,V) (A[ArrayResize(A, ArraySize(A) + 1, ArraySize(A) * 2) - 1] = V)
|
Функция возвращает размер наименьшего массива из двух. Когда он больше нуля, у нас появляется возможность закрыть встречные позиции.
Если минимальный массив нулевого размера, функция вернет размер другого массива, но со знаком минус — просто, чтобы дать знать вызывающему коду, что все позиции в одном направлении.
Если позиций нет ни в одном направлении, функция вернет 0.
Открытие позиций останется в ведении функции OpenPosition — без изменений.
Закрытие будет осуществляться только в режиме двух встречных позиций в новой функции CloseByPosition. Иными словами, данный эксперт не способен закрывать позиции по одной, обычным способом. Разумеется, в реальном роботе такой принцип вряд встретится, но для примера встречного закрытия подойдет очень хорошо. Если нам потребуется закрыть одиночную позицию, достаточно открыть для неё встречную (в этот момент плавающая прибыль или убыток фиксируется) и вызвать CloseByPosition для двух.
bool CloseByPosition(const ulong ticket1, const ulong ticket2)
|
Здесь как раз используется описанный выше метод request.closeby с заполнением полей position, position_by и вызовом OrderSend.
Торговая логика описана в обработчике OnTick, который как и раньше анализирует конфигурацию цен лишь в момент формирования нового бара и получает сигнал из функции GetTradeDirection.
void OnTick()
|
Далее заполняем массивы ticketsLong и ticketsShort тикетами позиций по рабочему символу и с заданным Magic-номером. Если функция GetMyPositions вернула значение больше нуля, оно означает количество сформировавшихся пар встречных позиций. Их можно закрыть в цикле с помощью функции CloseByPosition. Сочетание пар в данном случае выбирается случайно (в порядке следования позиций в окружении терминала), однако на практике может быть важно подбирать пары по объемам или таким образом, чтобы сперва закрывались наиболее прибыльные.
ulong ticketsLong[], ticketsShort[];
|
При любом другом значении n следует проверить, есть ли сигнал (возможно, повторный) на вход в рынок, и выполнить его, вызвав OpenPosition.
else if(type == ORDER_TYPE_BUY || type == ORDER_TYPE_SELL)
|
Наконец, если открытые позиции все же есть, но они в одном направлении, мы проверяем, не достигло ли их количество лимита, и если да, то формируем встречную позицию, чтобы на следующем баре "схлопнуть" две из них (тем самым закрыть одну любую позицию из старых).
else if(n < 0)
|
Запустим эксперт в тестере на XAUUSD,H1 с начала 2022 года, с настройками по умолчанию. Ниже представлен внешний вид графика с позициями в процессе работы программы, а также кривая баланса.
Результаты тестирования TradeCloseBy на XAUUSD,H1
В журнале нетрудно найти моменты, когда один тренд заканчивается (покупки с тикетами от #2 до #4), и начинают генерироваться сделки в противоположном направлении (продажа #5), после чего срабатывает встречное закрытие.
2022.01.03 01:05:00 instant buy 0.01 XAUUSD at 1831.13 (1830.63 / 1831.13 / 1830.63) 2022.01.03 01:05:00 deal #2 buy 0.01 XAUUSD at 1831.13 done (based on order #2) 2022.01.03 01:05:00 deal performed [#2 buy 0.01 XAUUSD at 1831.13] 2022.01.03 01:05:00 order performed buy 0.01 at 1831.13 [#2 buy 0.01 XAUUSD at 1831.13] 2022.01.03 01:05:00 Waiting for position for deal D=2 2022.01.03 01:05:00 OK New Order/Deal/Position 2022.01.03 02:00:00 instant buy 0.01 XAUUSD at 1828.77 (1828.47 / 1828.77 / 1828.47) 2022.01.03 02:00:00 deal #3 buy 0.01 XAUUSD at 1828.77 done (based on order #3) 2022.01.03 02:00:00 deal performed [#3 buy 0.01 XAUUSD at 1828.77] 2022.01.03 02:00:00 order performed buy 0.01 at 1828.77 [#3 buy 0.01 XAUUSD at 1828.77] 2022.01.03 02:00:00 Waiting for position for deal D=3 2022.01.03 02:00:00 OK New Order/Deal/Position 2022.01.03 03:00:00 instant buy 0.01 XAUUSD at 1830.40 (1830.16 / 1830.40 / 1830.16) 2022.01.03 03:00:00 deal #4 buy 0.01 XAUUSD at 1830.40 done (based on order #4) 2022.01.03 03:00:00 deal performed [#4 buy 0.01 XAUUSD at 1830.40] 2022.01.03 03:00:00 order performed buy 0.01 at 1830.40 [#4 buy 0.01 XAUUSD at 1830.40] 2022.01.03 03:00:00 Waiting for position for deal D=4 2022.01.03 03:00:00 OK New Order/Deal/Position 2022.01.03 05:00:00 instant sell 0.01 XAUUSD at 1826.22 (1826.22 / 1826.45 / 1826.22) 2022.01.03 05:00:00 deal #5 sell 0.01 XAUUSD at 1826.22 done (based on order #5) 2022.01.03 05:00:00 deal performed [#5 sell 0.01 XAUUSD at 1826.22] 2022.01.03 05:00:00 order performed sell 0.01 at 1826.22 [#5 sell 0.01 XAUUSD at 1826.22] 2022.01.03 05:00:00 Waiting for position for deal D=5 2022.01.03 05:00:00 OK New Order/Deal/Position 2022.01.03 06:00:00 close position #5 sell 0.01 XAUUSD by position #2 buy 0.01 XAUUSD (1825.64 / 1825.86 / 1825.64) 2022.01.03 06:00:00 deal #6 buy 0.01 XAUUSD at 1831.13 done (based on order #6) 2022.01.03 06:00:00 deal #7 sell 0.01 XAUUSD at 1826.22 done (based on order #6) 2022.01.03 06:00:00 Positions collapse initiated 2022.01.03 06:00:00 OK CloseBy Order/Deal/Position |
Интересным артефактом является сделка #3. Внимательный читатель заметит, что она открылась ниже предыдущей, вроде бы нарушая нашу стратегию. На самом деле ошибки здесь нет, и это следствие того, что условия сигналов прописаны максимально просто: только на основе цен закрытия баров. Поэтому разворотная медвежья свеча (D), открывшаяся с гепом вверх и закрывшаяся выше окончания предыдущей бычьей свечи (C), сгенерировала сигнал для покупки. Данная ситуация иллюстрируется следующим скриншотом.
Сделки на восходящем тренде по ценам закрытия
Все свечи в последовательности A, B, C, D, E закрываются выше предыдущей и стимулируют продолжение покупок. Для исключения подобных артефактов следует дополнительно проводить анализ направления самих баров.
Последний момент, на который следует обратить внимание в данном примере, это функция OnInit. Поскольку эксперт использует операцию TRADE_ACTION_CLOSE_BY, здесь выполняются проверки соответствующих настроек счета и рабочего символа.
int OnInit()
|
Если одно из свойств не поддерживает встречное закрытие, эксперт не сможет продолжить работу. При создании рабочих роботов эти проверки, как правило, осуществляются внутри торгового алгоритма и переключают программу в альтернативные режимы, в частности, на одиночное закрытие позиций и поддержание совокупной позиции в случае неттинга.