- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Полное и частичное закрытие позиции
Технически закрытие позиции можно представить, как торговую операцию, обратную по направлению к той, что использовалась для открытия. Например, для выхода из покупки нужно осуществить продажу (ORDER_TYPE_SELL в поле type), а для выхода из продажи — покупку (ORDER_TYPE_BUY в поле type).
Тип торговой транзакции в поле action структуры MqlTradeTransaction остается прежним — TRADE_ACTION_DEAL.
На счете с хеджингом закрываемую позицию следует указать с помощью тикета в поле position. Для счетов с неттинговым учетом достаточно указать название символа в поле symbol, поскольку на них возможна только одна позиция по символу. Однако закрывать позиции по тикету можно и здесь.
В целях унификации кода имеет смысл заполнять оба поля position и symbol вне зависимости от типа счета.
Также обязательно задать объем в поле volume. Если он равен объему позиции, она будет закрыта полностью. Однако, указав меньшее значение, существует возможность закрыть лишь часть позиции.
В следующей таблице все поля структуры, которые обязательно задавать, помечены звездочкой, а опциональные — плюсом.
Поле |
Неттинг |
Хедж |
---|---|---|
action |
* |
* |
symbol |
* |
+ |
position |
+ |
* |
type |
* |
* |
type_filling |
* |
* |
volume |
* |
* |
price |
*' |
*' |
deviation |
± |
± |
magic |
+ |
+ |
comment |
+ |
+ |
Поле price помечено звездочкой с риской, потому что оно является обязательным только для символов с режимом исполнения по запросу (Request) и немедленным (Instant), а для биржевого (Exchange) и рыночного (Market) исполнения цена в структуре не учитывается.
По аналогичной причине поле deviation помечено знаком '±' — оно имеет эффект только для режимов Instant и Request.
Для упрощения программной реализации закрытия позиции вернемся к нашей расширенной структуре MqlTradeRequestSync в файле MqlTradeSync.mqh. Метод закрытия позиции по тикету имеет следующий код.
struct MqlTradeRequestSync: public MqlTradeRequest
|
Здесь мы первым делом проверяем существование позиции, вызвав функцию PositionSelectByTicket. Дополнительно этот вызов делает позицию выбранной в торговом окружении терминала, что позволяет читать её свойства последующими функциями. В частности, мы узнаем символ позиции из свойства POSITION_SYMBOL и "переворачиваем" её тип из POSITION_TYPE на обратный, чтобы получить нужный тип ордера.
Напомним, что типы позиций в перечислении ENUM_POSITION_TYPE - это POSITION_TYPE_BUY (значение 0) и POSITION_TYPE_SELL (значение 1). В перечислении типов ордеров ENUM_ORDER_TYPE точно такие же значения занимают рыночные операции: ORDER_TYPE_BUY и ORDER_TYPE_SELL. Именно поэтому мы можем приводить первое перечисление ко второму, а для получения обратного направления торговли достаточно переключить нулевой бит с помощью операции исключающего ИЛИ ('^'): из 0 получим 1, а из 1 — 0.
Обнуление поля price означает автоматический выбор правильной текущей цены (Ask или Bid) перед отправкой запроса: это делается чуть позднее, внутри вспомогательного метода setVolumePrices, который вызывается далее по ходу алгоритма, из метода market.
Вызов самого метода _market мы видим парой строк ниже. Метод _market формирует рыночный ордер на полный объем или часть, с учетом всех заполненных полей структуры.
const double total = lot == 0 ? PositionGetDouble(POSITION_VOLUME) : lot;
|
Данный фрагмент слегка упрощен по сравнению с актуальным исходным кодом — там предусмотрена обработка редкой, но возможной ситуации, когда объем позиции превышает максимальный разрешенный объем в одном ордере по символу (свойство SYMBOL_VOLUME_MAX). В таком случае позицию приходится закрывать по частям — несколькими приказами.
Также обратим внимание, что поскольку позиция может закрываться частично, нам пришлось добавить в структуру поле partial, куда кладется планируемый остаток объема после операции. Для полного закрытия это будет, разумеется, 0. Эта информация потребуется для дальнейшей проверки завершения операции.
Для счетов с неттингом имеется вариант метода close, идентифицирующий позицию по имени символа. Он сводится к выделению позиции по символу, получению её тикета и далее обращению к предыдущей версии close.
bool close(const string name, const double lot = 0)
|
В структуре MqlTradeRequestSync у нас предусмотрен метод completed, обеспечивающий при необходимости синхронное ожидание завершения операции. Теперь нам требуется дополнить его для закрытия позиций, в ветви, где action равно TRADE_ACTION_DEAL. Различать открытие позиции и закрытие будем по нулевому значению в поле position: при открытии позиции тикета нет, при закрытии — он есть.
bool completed()
|
Для проверки фактического закрытия позиции в структуру MqlTradeResultSync добавлен метод closed. Перед его вызовом мы записываем тикет позиции в поле result.position, чтобы структура результата могла отслеживать момент, когда соответствующий тикет пропадет из торгового окружения терминала, или когда объем сравняется с result.partial в случае частичного закрытия.
А вот и сам метод closed. Он построен по уже известному принципу: сначала проверка успешности кода возврата сервера, а затем ожидание с помощью метода wait некоторого условия.
struct MqlTradeResultSync: public MqlTradeResult
|
В данном случае для проверки условия исчезновения позиции нам пришлось реализовать новую функцию positionRemoved.
static bool positionRemoved(MqlTradeResultSync &ref)
|
Операцию закрытия позиций протестируем на эксперте TradeClose.mq5, реализующем простую торговую стратегию: при двух последовательных барах в одном направлении входим в рынок, а как только очередной бар закроется в противоположном направлении предыдущему тренду — выходим из рынка. Повторяющиеся сигналы во время продолжительных трендов будут игнорироваться, то есть в рынке будет максимум одна позиция (минимальным лотом) или ни одной.
Никаких настраиваемых параметров у эксперта не будет: только величина проскальзывания (Deviation) и уникальный номер (Magic). Неявными параметрами являются таймфрейм и рабочий символ графика.
Для отслеживания наличия уже открытой позиции воспользуемся функцией GetMyPosition из предыдущего примера TradeTrailing.mq5: напомним, она осуществляет поиск среди позиций по символу и номеру эксперта, и возвращает логический признак true, если подходящая позиция найдена.
Также практически без изменений возьмем и функцию OpenPosition: она открывает позицию в соответствии с типом рыночного ордера, переданного в единственном параметре. Здесь этот параметр будет поступать из алгоритма определения тренда, а ранее (в TrailingStop.mq5) тип ордера задавался пользователем через входную переменную.
Новая функция, реализующая закрытие позиции, — это ClosePosition. Поскольку заголовочный файл MqlTradeSync.mqh взял на себя всю рутину, здесь нам остается, по большому счету, только вызвать метод request.close(ticket) для переданного тикета позиции и дождаться завершения удаления посредством request.completed().
В принципе, последнее можно не делать, если эксперт анализирует ситуацию на каждом тике. В этом случае потенциальная проблема с удалением позиции оперативно вскроется на следующем тике, и эксперт может повторить попытку удаления. Однако данный эксперт имеет торговую логику, основанную на барах, а потому не имеет смысла анализировать каждый тик. Далее мы реализуем специальный механизм для побаровой работы, и в связи с этим мы синхронно контролируем удаление, иначе позиция осталась бы "висеть" целый бар.
ulong LastErrorCode = 0;
|
Можно задаться вопросом, почему бы функции ClosePosition не возвращать 0 в случае успешного удаления позиции, а иначе — непосредственно код ошибки. Этот, на первый взгляд, экономный подход, сделал бы поведение двух функций OpenPosition и ClosePosition различным: в вызывающем коде потребовалось бы вкладывать вызовы этих функций в противоположные по смыслу логические выражения, а это вносило бы путаницу. Кроме того, глобальная переменная LastErrorCode нам в любом случае понадобилась для добавления информации об ошибке внутри функции OpenPosition. Да и проверка в виде if(условие) более органично интерпретируется как успех, нежели if(!условие).
Функция, формирующая торговые сигналы по вышеописанной стратегии, называется GetTradeDirection.
ENUM_ORDER_TYPE GetTradeDirection()
|
Функция возвращает значение типа ENUM_ORDER_TYPE, причем два стандартных элемента (ORDER_TYPE_BUY и ORDER_TYPE_SELL), как и следует ожидать, инициируют покупки и продажи, соответственно. А специальное значение -1 (отсутствующее в перечислении) будет использоваться как сигнал закрытия.
Для активации эксперта, исходя из торгового алгоритма, применим обработчик OnTick. Как мы помним, в принципе существуют другие опции, подходящие для других стратегий, например, таймер для торговли по новостям, события стакана для торговли с учетом объемов.
Сначала разберем функцию в упрощенном виде, без обработки потенциальных ошибок. В самом начале идет блок, обеспечивающий срабатывание дальнейшего алгоритма только при открытии нового бара.
void OnTick()
|
Далее получаем текущий сигнал из функции GetTradeDirection.
const ENUM_ORDER_TYPE type = GetTradeDirection(); |
В случае наличия позиции проверяем, получен ли сигнал на её закрытие и вызываем ClosePosition при необходимости. Если же позиции ещё нет, и есть сигнал войти в рынок, вызываем OpenPosition.
if(GetMyPosition(_Symbol, Magic))
|
Для анализа ошибок потребуется заключить вызовы OpenPosition и ClosePosition в условные операторы, и предпринять некие действия для восстановления рабочего состояния программы. В простейшем случае достаточно повторить запрос на следующем тике, но делать это желательно ограниченное число раз. Поэтому заведем статические переменные со счетчиком и лимитом ошибок.
void OnTick()
|
Важно, что механизм побаровой работы временно отключается, если появились ошибки, так как их желательно преодолеть как можно скорее.
Подсчет ошибок делаем в условных операторах вокруг ClosePosition и OpenPosition.
const ENUM_ORDER_TYPE type = GetTradeDirection();
|
Установка переменной errors в 0 вновь включает механизм побаровой работы и прекращает попытки повторить запрос вплоть до следующего бара.
Макрос IS_TANGIBLE определен в TradeRetcode.mqh как:
#define IS_TANGIBLE(T) ((T) >= TRADE_RETCODE_ERROR) |
Ошибки с меньшими кодами являются операционными, то есть в некотором смысле нормальными. Большие коды требуют анализа и различных действий, в зависимости от причины проблемы: неверные параметры запроса, постоянные или временные запреты в торговом окружении, нехватка средства и так далее. Мы представим усовершенствованный классификатор ошибок в разделе Модификация отложенного ордера.
Запустим эксперт в тестере на XAUUSD,H1 с начала 2022 года, моделирование реальных тиков. На следующем коллаже показан фрагмент графика со сделками, а также кривая баланса.
Результаты тестирования TradeClose на XAUUSD,H1
По отчету и журналу легко убедиться, что связка нашей простой торговой логики и двух операций открытия и закрытия позиций работает исправно.
Помимо простого закрытия позиции платформа поддерживает возможность взаимного закрытия двух встречных позиций на счетах с хеджированием.