- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Модификация отложенного ордера
MetaTrader 5 позволяет модифицировать некоторые свойства отложенного ордера: цену активации, защитные уровни, срок истечения. Основные свойства, такие как тип ордера или объем, изменять нельзя. В таких случаях ордер следует удалить и поставить вместо него другой. Единственный случай, когда тип ордера может быть изменен самим сервером — это активация stoplimit-ордера, который превращается в соответствующий лимитный ордер.
Программная модификация ордеров выполняется операцией TRADE_ACTION_MODIFY: именно эту константу нужно записать в поле action структуры MqlTradeRequest перед отправкой на сервер функцией OrderSend или OrderSendAsync. Тикет модифицируемого ордера указывается в поле order. С учетом action и order, полный перечень обязательных полей для данной операции включает:
- action
- order
- price
- type_time (значение по умолчанию 0 соответствует ORDER_TIME_GTC)
- expiration (значение по умолчанию 0, не важно при ORDER_TIME_GTC)
- type_filling (значение по умолчанию 0 соответствует ORDER_FILLING_FOK)
- stoplimit (только для ордеров типов ORDER_TYPE_BUY_STOP_LIMIT и ORDER_TYPE_SELL_STOP_LIMIT)
Опциональные поля:
- sl
- tp
Если защитные уровни уже были установлены у ордера, их следует указывать, чтобы сохранить. Нулевые значения предписывают удаление Stop Loss и/или Take Profit.
В структуре MqlTradeRequestSync (MqlTradeSync.mqh) реализация модификации ордера находится в методе modify.
struct MqlTradeRequestSync: public MqlTradeRequest
|
Фактическое исполнение запроса, как обычно, производится в методе completed, в выделенной ветке оператора if.
bool completed()
|
Чтобы структура MqlTradeResultSync "знала" новые значения свойств редактируемого ордера и могла их сверить с результатом, мы записываем их в свободные поля (они не заполняются сервером в данном типе запроса). Далее в методе modified структура результата ожидает применения модификации.
struct MqlTradeResultSync: public MqlTradeResult
|
Здесь мы видим, как свойства ордера считываются при помощи функции OrderGetDouble и сравниваются с установленными значениями. Все это происходит по уже привычной схеме: в цикле внутри функции wait, в пределах некоего таймаута msc (1000 миллисекунд по умолчанию).
В качестве примера разберем эксперт PendingOrderModify.mq5, наследующий от PendingOrderSend.mq5 некоторые фрагменты кода: в частности, набор входных параметров и функцию PlaceOrder для создания нового ордера. Она используется при первом запуске, если ордера для заданного сочетания символа и Magic-числа еще нет, тем самым гарантируя, что эксперту есть что модифицировать.
Для поиска подходящего ордера потребовалась новая функция GetMyOrder. Она во многом похожа на функцию GetMyPosition, которая применялась в примере сопровождения позиции (TrailingStop.mq5) для поиска подходящей позиции. Назначение используемых внутри GetMyOrder встроенных функций MQL5 API должно быть в общих чертах ясно по их названиям, а техническое описание будет представлено в отдельных разделах.
ulong GetMyOrder(const string name, const ulong magic)
|
Входной параметр Distance2SLTP теперь отсутствует. Вместо него новый эксперт будет автоматически рассчитывать дневной размах цен и размещать защитные уровни на дистанции половины этого размаха. В начале каждого дня размах будет пересчитываться, также как и новые уровни в полях sl и tp, на основе чего сформируются запросы на модификацию ордера.
Те, отложенные ордера, которые сработают и превратятся в позиции, будут закрываться сами по достижению Stop Loss или Take Profit. В принципе, терминал способен сообщать MQL-программе об активации отложенный ордеров и закрытии позиций, если описать в ней обработчики торговых событий. Это позволило бы, например, не создавать новый ордер при наличии открытой позиции, но и текущая стратегия имеет право на существование, а событиями мы займемся позднее.
Основная логика эксперта "зашита" в обработчике OnTick.
void OnTick()
|
В начале функции пара строк кода обеспечивает однократный запуск алгоритма в начале каждого дня. Для этого мы рассчитываем текущую дату без времени и сравниваем со значением переменной lastDay, куда должна записываться последняя дата успешной работы. Статус успеха или ошибки, разумеется, становится ясен уже в конце функции, поэтому мы вернемся к нему позднее.
Далее делается расчет ценового диапазона за предыдущий день.
const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
|
В зависимости от того, найдется ли ордер или нет в функции GetMyOrder, мы либо создадим новый с помощью PlaceOrder, либо отредактируем имеющийся с помощью ModifyOrder.
uint retcode = 0;
|
Обе функции PlaceOrder и ModifyOrder работают на основе входных параметров эксперта и найденного ценового диапазона. Они возвращают статус запроса, который нужно будет неким образом проанализировать, чтобы решить, какое действие выполнить:
- Обновить переменную lastDay, если запрос успешный (ордер был обновлен и до начала следующего дня эксперт в "спячке");
- Оставить пока в lastDay "старый" день, чтобы повторить попытку на следующих тиках, если есть временные проблемы (например, торговая сессия еще не началась);
- Остановить эксперт, если обнаружены серьезные проблемы (например, выбранный тип ордера или направление торговли не разрешены на символе).
...
|
В разделе о полном и частичном закрытии позиции мы использовали упрощенный анализ с макросом IS_TANGIBLE, который давал ответ в категориях "да" и "нет": ошибка — не ошибка. Очевидно, что такой подход нужно усовершенствовать, и мы к этому вопросу скоро вернемся, а пока сосредоточимся на основном функционале эксперта.
Исходный код функции PlaceOrder практически не изменился по сравнению с предыдущим примером, а ModifyOrder приведена ниже.
Напомним, что расположение ордеров у нас определялось исходя из дневного диапазона, к которому применялась таблица коэффициентов. Сам принцип оставлен без изменений, однако поскольку у нас теперь две функции, работающие с ордерами — PlaceOrder и ModifyOrder — таблица коэффициентов Coefficients вынесена в глобальный контекст. Мы не будем её здесь повторять и сразу перейдем к функции ModifyOrder.
uint ModifyOrder(const ulong ticket, const double range,
|
Ценовые уровни рассчитываются в зависимости от типа ордера и переданного диапазона range.
const ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
После вычисления всех значений создаем объект структуры MqlTradeRequestSync и выполняем запрос.
MqlTradeRequestSync request(symbol);
|
Для анализа retcode, который мы должны выполнить в вызывающем блоке внутри OnTick, был разработан новый механизм, дополнивший файл TradeRetcode.mqh. Все коды возврата сервера поделены на несколько групп "важности", описываемых элементами перечисления TRADE_RETCODE_SEVERITY.
enum TRADE_RETCODE_SEVERITY
|
В упрощенном виде, первая половина соответствует устранимым ошибкам: обычно достаточно подождать некоторое время и выполнить запрос повторно. Вторая половина требует изменить содержимое запроса, проверить настройки счета или символа, разрешения для программы, а в худшем случае — остановить торговлю. Желающие могут провести условную разделительную черту не после SEVERITY_REJECT, как визуально выделено сейчас, а перед ним.
Деление всех кодов по группам выполняет функция TradeCodeSeverity (приводится с сокращениями).
TRADE_RETCODE_SEVERITY TradeCodeSeverity(const uint retcode)
|
Благодаря этому функционалу обработчик OnTick может быть дополнен "умной" обработкой ошибок. В статической переменной RetryFrequency хранится периодичность, с которой программа будет пытаться повторить запрос в случае некритических ошибок. Последний раз, когда делалась такая попытка, запоминается в переменной RetryRecordTime.
void OnTick()
|
После того, как из функции PlaceOrder или ModifyOrder получено значение retcode, мы узнаем его "критичность" и на её основе выбираем одну из трех альтернатив: остановка эксперта, ожидание в течение таймаута или штатная работа (помечаем успешную модификацию ордера текущим днем в lastDay).
const TRADE_RETCODE_SEVERITY severity = TradeCodeSeverity(retcode);
|
В случае повторных проблем, которые отнесены к классу разрешимых, таймаут RetryFrequency постепенно увеличивается с каждой следующей ошибкой, но сбрасывается в 1 секунду при успешной обработке запроса.
Следует отметить, что методы прикладной структуры MqlTradeRequestSync проверяют большое количество сочетаний параметров на корректность и при обнаружении проблем прерывают процесс, не доводя до вызова SendRequest. Такое поведение заложено по умолчанию, но его можно отключить, определив пустой макрос RETURN(X) перед директивой #include с MqlTradeSync.mqh.
#define RETURN(X)
|
При таком макроопределении проверки станут только выводить предупреждения в журнал, но продолжат выполнение методов вплоть до вызова SendRequest.
В любом случае, после вызова того или иного метода структуры MqlTradeResultSync в поле retcode будет помещен код ошибки: либо сервером, либо проверяющими алгоритмами структуры MqlTradeRequestSync (здесь пришелся кстати тот факт, что экземпляр MqlTradeResultSync включен внутрь MqlTradeRequestSync). Запись кодов ошибок и использование макроса RETURN в методах MqlTradeRequestSync были для краткости опущены во фрагментах методов, приведенных в книге. Желающие могут ознакомиться с полным исходным кодом в файле MqlTradeSync.mqh.
Запустим в тестере, в визуальном режиме, эксперт PendingOrderModify.mq5 на XAUUSD,H1 (в режиме всех тиков или реальных тиков). С настройками по умолчанию эксперт будет ставить ордера типа ORDER_TYPE_BUY_STOP минимальным лотом. Убедимся по журналу и торговой истории, что программа выставляет отложенные ордера и модифицирует их в начале каждого дня.
2022.01.03 01:05:00 Autodetected daily range: 14.37 2022.01.03 01:05:00 buy stop 0.01 XAUUSD at 1845.73 sl: 1838.55 tp: 1852.91 (1830.63 / 1831.36) 2022.01.03 01:05:00 OK order placed: #=2 2022.01.03 01:05:00 TRADE_ACTION_PENDING, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1845.73, SL=1838.55, TP=1852.91, ORDER_TIME_GTC, M=1234567890 2022.01.03 01:05:00 DONE, #=2, V=0.01, Bid=1830.63, Ask=1831.36, Request executed 2022.01.04 01:05:00 Autodetected daily range: 33.5 2022.01.04 01:05:00 order modified [#2 buy stop 0.01 XAUUSD at 1836.56] 2022.01.04 01:05:00 OK order modified: #=2 2022.01.04 01:05:00 TRADE_ACTION_MODIFY, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1836.56, SL=1819.81, TP=1853.31, ORDER_TIME_GTC, #=2 2022.01.04 01:05:00 DONE, #=2, @ 1836.56, Bid=1819.81, Ask=1853.31, Request executed, Req=1 2022.01.05 01:05:00 Autodetected daily range: 18.23 2022.01.05 01:05:00 order modified [#2 buy stop 0.01 XAUUSD at 1832.56] 2022.01.05 01:05:00 OK order modified: #=2 2022.01.05 01:05:00 TRADE_ACTION_MODIFY, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1832.56, SL=1823.45, TP=1841.67, ORDER_TIME_GTC, #=2 2022.01.05 01:05:00 DONE, #=2, @ 1832.56, Bid=1823.45, Ask=1841.67, Request executed, Req=2 ... 2022.01.11 01:05:00 Autodetected daily range: 11.96 2022.01.11 01:05:00 order modified [#2 buy stop 0.01 XAUUSD at 1812.91] 2022.01.11 01:05:00 OK order modified: #=2 2022.01.11 01:05:00 TRADE_ACTION_MODIFY, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1812.91, SL=1806.93, TP=1818.89, ORDER_TIME_GTC, #=2 2022.01.11 01:05:00 DONE, #=2, @ 1812.91, Bid=1806.93, Ask=1818.89, Request executed, Req=6 2022.01.11 18:10:58 order [#2 buy stop 0.01 XAUUSD at 1812.91] triggered 2022.01.11 18:10:58 deal #2 buy 0.01 XAUUSD at 1812.91 done (based on order #2) 2022.01.11 18:10:58 deal performed [#2 buy 0.01 XAUUSD at 1812.91] 2022.01.11 18:10:58 order performed buy 0.01 at 1812.91 [#2 buy stop 0.01 XAUUSD at 1812.91] 2022.01.11 20:28:59 take profit triggered #2 buy 0.01 XAUUSD 1812.91 sl: 1806.93 tp: 1818.89 » » [#3 sell 0.01 XAUUSD at 1818.89] 2022.01.11 20:28:59 deal #3 sell 0.01 XAUUSD at 1818.91 done (based on order #3) 2022.01.11 20:28:59 deal performed [#3 sell 0.01 XAUUSD at 1818.91] 2022.01.11 20:28:59 order performed sell 0.01 at 1818.91 [#3 sell 0.01 XAUUSD at 1818.89] 2022.01.12 01:05:00 Autodetected daily range: 23.28 2022.01.12 01:05:00 buy stop 0.01 XAUUSD at 1843.77 sl: 1832.14 tp: 1855.40 (1820.14 / 1820.49) 2022.01.12 01:05:00 OK order placed: #=4 2022.01.12 01:05:00 TRADE_ACTION_PENDING, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1843.77, SL=1832.14, TP=1855.40, ORDER_TIME_GTC, M=1234567890 2022.01.12 01:05:00 DONE, #=4, V=0.01, Bid=1820.14, Ask=1820.49, Request executed, Req=7 |
В любой момент ордер может сработать, после чего позиция через какое-то время закрывается по стоплоссу или тейкпрофиту (как в приведенном выше фрагменте).
В некоторых случаях может возникнуть ситуация, когда позиция еще существует на начало следующего дня, и тогда новый ордер будет создан в дополнение к ней, как на приведенном ниже скриншоте.
Эксперт с торговой стратегией на отложенных ордерах в тестере
Обратите внимание, что из-за того, что мы запрашиваем котировки таймфрейма PERIOD_D1 для расчета дневного диапазона, визуальный тестер открывает соответствующий график, помимо текущего рабочего. Такой сервис работает не только в отношении таймфреймов, отличных от рабочего, но и других символов. Это пригодится, в частности, при разработке мультивалютных экспертов.
Чтобы проверить, как работает обработка ошибок, попробуйте запретить торговлю эксперту. В результате увидим в журнале:
Autodetected daily range: 34.48
|
Данная ошибка относится к критическим, и эксперт останавливает работу.
Для демонстрации одной из более легких ошибок можно было бы использовать обработчик OnTimer, а не OnTick. Тогда запуск этого же эксперта на символах, где торговые сессии занимают лишь часть суток, мог бы периодически генерировать последовательность некритических ошибок о закрытом рынке ("Market closed"). В этом случае эксперт будет продолжать попытки начать торговлю, постоянно увеличивая время ожидания.
Это, в частности, легко проверить и в тестере, который позволяет настраивать произвольные торговые сессии для любого символа. На вкладке Настройки, справа от выпадающего списка Задержки находится кнопка, по нажатию которой открывается диалог Настройка торговли. В нем следует включить опцию Использовать свои настройки и на закладке Торговля добавить хотя бы одну запись в таблицу Неторговые периоды.
Настройка неторговых периодов в тестере
Обратите внимание, что здесь задаются именно неторговые периоды, а не торговые сессии, то есть данная настройка действует с точностью до наоборот по сравнению со спецификацией символов.
Исключить многие потенциальные ошибки, связанные с ограничениями торговли, может предварительный анализ окружения с помощью класса вроде Permissions, представленного в разделе Ограничения и разрешения для операций по счету.