- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Функции для чтения свойств сделок из истории
Для чтения свойств сделок предназначены группы функций, организованные по типу свойств: целочисленные, вещественные и строковые. Перед вызовом функций нужно выделить нужный фрагмент истории и тем самым обеспечить доступность сделок с тикетами, которые передаются в первом параметре (ticket) всех функций.
Для каждого типа свойств существует две формы: с непосредственным возвратом значения и путем записи в переменную по ссылке. Вторая форма возвращает true для индикации успеха. Первая форма просто вернет 0 в случае ошибки. Код ошибки — в переменной _LastError.
Целочисленные и совместимые типы свойств (datetime, перечисления) можно получить функцией HistoryDealGetInteger.
long HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property)
bool HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property,
long &value)
Вещественные свойства читаются функцией HistoryDealGetDouble.
double HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property)
bool HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property,
double &value)
Для строковых свойств предназначены функции HistoryDealGetString.
string HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property)
bool HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property,
string &value)
Унифицированное чтение свойств сделки обеспечит класс DealMonitor (DealMonitor.mqh), организованный точно также, как OrderMonitor и PositionMonitor. В качестве базового класса выступает DealMonitorInterface, унаследованный от шаблона MonitorInterface (мы его описывали в разделе Функции для чтения свойств действующих ордеров). Именно на этом уровне задаются конкретные типы ENUM_DEAL_PROPERTY-перечислений в качестве параметров шаблона и специфическая реализация метода stringify.
#include <MQL5Book/TradeBaseMonitor.mqh>
|
Нижеприведенный рабочий класс DealMonitor должен напомнить недавно модифицированный для работы с историей OrderMonitor. Помимо применения HistoryDeal-функций вместо HistoryOrder-функций следует отметить, что для сделок нет необходимости проверять тикет в онлайн-окружении, потому что сделки существуют только в истории.
class DealMonitor: public DealMonitorInterface
|
На основе DealMonitor и TradeFilter легко создать фильтр сделок (DealFilter.mqh). Напомним, что TradeFilter, как базовый класс для многих сущностей, был описан в разделе Отбор ордеров по свойствам.
#include <MQL5Book/DealMonitor.mqh>
|
В качестве обобщенного примера работы с историй рассмотрим скрипт восстановления истории позиций TradeHistoryPrint.mq5.
TradeHistoryPrint
Скрипт будет строить историю для текущего символа графика.
Нам для начала потребуются фильтры сделок и ордеров.
#include <MQL5Book/OrderFilter.mqh>
|
Из сделок мы извлечем идентификаторы позиций и на их основе запросим подробности об ордерах.
Историю можно будет посмотреть целиком или для конкретной позиции, для чего во входных переменных предусмотрим выбор режима и поле ввода для идентификатора.
enum SELECTOR_TYPE
|
Следует помнить, что выборка длинной истории счета может быть накладной, поэтому в рабочих экспертах желательно предусмотреть кеширование полученных результатов обработки истории, вместе с последней временной меткой обработки. При каждом следующем анализе истории можно начинать процесс не с самого начала, а с запомненного момента.
Чтобы красиво, с выравниванием по колонкам выводить информацию о записях истории, имеет смысл представить её как массив структур. Однако наши фильтры уже поддерживают запрос данных с сохранением в особые структуры — кортежи. Поэтому мы применим хитрость: опишем свои прикладные структуры, соблюдая правила кортежей:
- первое поле должно иметь название _1 — оно опционально используется в алгоритме для сортировки;
- в структуре должна быть описана функция size, возвращающая количество полей в ней;
- в структуре должен быть шаблонный метод assign для заполнения полей из свойств переданного объекта-монитора, производного от MonitorInterface.
В стандартных кортежах метод assign описан так:
template<typename M>
|
Первым параметром он принимает массив с идентификаторами свойств, соответствующих полям, которые нас интересуют. Фактически этот тот массив, который передается вызывающим кодом в метод select фильтра (TradeFilter::select) и затем по ссылке попадает в assign. Но поскольку мы сейчас создадим не стандартные кортежи, а свои собственные структуры, "знающие" о прикладной сущности своих полей, мы можем оставить массив с идентификаторами свойств внутри самой структуры и не "гонять" его внутрь фильтра и обратно в метод assign той же структуры.
В частности, для запроса сделок опишем структуру DealTuple с 8-ю полями. Их идентификаторы укажем в статическом массиве fields.
struct DealTuple
|
Данный подход сводит в едином месте идентификаторы и поля для хранения соответствующих значений, что облегчает понимание и сопровождение исходного кода.
Для заполнения полей значениями свойств потребуется слегка модифицированная (упрощенная) версия метода assign — она берет идентификаторы из массива fields, а не из входного параметра.
struct DealTuple
|
Заодно мы преобразуем числовые элементы перечислений ENUM_DEAL_TYPE и ENUM_DEAL_ENTRY в понятные для пользователя строки. Разумеется, это нужно только для вывода в журнал. Для программного анализа следовало бы оставить типы как есть.
Поскольку мы изобрели новый вариант метода assign в своих кортежах, требуется добавить для него новый вариант метода select в классе TradeFilter. Новшество наверняка будет полезно и для других программ, а потому внесем его непосредственно в TradeFilter, а не в некий новый производный класс.
template<typename T,typename I,typename D,typename S>
|
Напомним, что все шаблонные методы не реализуются компилятором, пока к ним не будет обращение в коде с конкретным типом. Поэтому наличие подобных шаблонов в TradeFilter не обязывает вас подключать какие-либо заголовочные файлы с кортежами или описывать похожие структуры, если вы их не используете.
Итак, если раньше для выборки сделок с помощью стандартного кортежа нам нужно было бы писать так:
#include <MQL5Book/Tuples.mqh>
|
То с кастомизированной структурой все намного проще:
DealFilter filter;
|
Аналогично структуре DealTuple опишем структуру для ордеров OrderTuple с 10-ю полями.
struct OrderTuple
|
Теперь все готово для реализации главной функции скрипта — OnStart. В самом начале опишем объекты фильтров сделок и ордеров.
void OnStart()
|
В зависимости от входных переменных выберем либо всю историю, либо специфическую позицию.
if(PositionID == 0 || Type == TOTAL)
|
Далее соберем в массив все идентификаторы позиций, или оставим одну заданную пользователем.
ulong positions[];
|
Вспомогательная функция ArrayUnique оставляет в массиве неповторяющиеся элементы. Для её работы требуется, чтобы исходный массив был отсортирован.
Далее в цикле по позициям запрашиваем сделки и ордера, относящиеся к каждой из них. Сделки сортируются по первому полю структуры DealTuple, то есть по времени. Пожалуй, наиболее интересным является подсчет прибыли/убытка по позиции. Для этого мы суммируем значения поля profit всех сделок.
for(int i = 0; i < n; ++i)
|
В данном коде не анализируются комиссии (DEAL_COMMISSION), свопы (DEAL_SWAP) и сборы (DEAL_FEE) в свойствах сделок. В реальных экспертах это, вероятно, следует делать (зависит от требований стратегии). Мы рассмотрим еще один пример анализа торговой истории в разделе, посвященном тестированию мультивалютных экспертов, и там учтем этот момент.
Вы можете сравнить результаты работы скрипта с таблицей на вкладке "История" в терминале: там в колонке "Прибыль" показывается чистая прибыль по каждой позиции (свопы, комиссии и сборы — в соседних столбцах, но их нужно включить).
Важно отметить, что ордер типа ORDER_TYPE_CLOSE_BY будет отображаться в обеих позициях только в том случае, если в настройках выбрана вся история. Если же была выбрана конкретная позиция, система включит такой ордер только в одну из них (ту, что была указана в торговом запросе первой, в поле position), но не во вторую (которая была указана в position_by).
Ниже показан пример результата работы скрипта для символа с небольшой историей.
Positions total: 3 Position: 1 1253500309 Profit:238.150000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.04 17:34:57 1236049891 1253500309 "BUY" "IN" 1.00000 76.23900 0.00000 [1] 2022.02.14 16:28:41 1242295527 1259788704 "SELL" "OUT" 1.00000 76.42100 238.15000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1253500309 2022.02.04 17:34:57 2022.02.04 17:34:57 "BUY" 1.00000 76.23900 76.23900 » » 0.00 0.00 "" [1] 1259788704 2022.02.14 16:28:41 2022.02.14 16:28:41 "SELL" 1.00000 76.42100 76.42100 » » 0.00 0.00 "" Position: 2 1253526613 Profit:878.030000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.07 10:00:00 1236611994 1253526613 "BUY" "IN" 1.00000 75.75000 0.00000 [1] 2022.02.14 16:28:40 1242295517 1259788693 "SELL" "OUT" 1.00000 76.42100 878.03000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1253526613 2022.02.04 17:55:18 2022.02.07 10:00:00 "BUY_LIMIT" 1.00000 75.75000 75.67000 » » 0.00 0.00 "" [1] 1259788693 2022.02.14 16:28:40 2022.02.14 16:28:40 "SELL" 1.00000 76.42100 76.42100 » » 0.00 0.00 "" Position: 3 1256280710 Profit:4449.040000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.09 13:17:52 1238797056 1256280710 "BUY" "IN" 2.00000 74.72100 0.00000 [1] 2022.02.14 16:28:39 1242295509 1259788685 "SELL" "OUT" 2.00000 76.42100 4449.04000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1256280710 2022.02.09 13:17:52 2022.02.09 13:17:52 "BUY" 2.00000 74.72100 74.72100 » » 0.00 0.00 "" [1] 1259788685 2022.02.14 16:28:39 2022.02.14 16:28:39 "SELL" 2.00000 76.42100 76.42100 » » 0.00 0.00 "" |
Случай с доливкой позиции (две сделки "IN") и её переворота (сделка "INOUT" большего объема) на неттинговом счете показан в следующем фрагменте.
Position: 5 219087383 Profit:0.170000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.29 08:03:33 215612450 219087383 "BUY" "IN" 0.01000 1.10011 0.00000 [1] 2022.03.29 08:04:05 215612451 219087393 "BUY" "IN" 0.01000 1.10009 0.00000 [2] 2022.03.29 08:04:29 215612457 219087400 "SELL" "INOUT" 0.03000 1.10018 0.16000 [3] 2022.03.29 08:04:34 215612460 219087403 "BUY" "OUT" 0.01000 1.10017 0.01000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 219087383 2022.03.29 08:03:33 2022.03.29 08:03:33 "BUY" 0.01000 0.0000 1.10011 » » 0.00 0.00 "" [1] 219087393 2022.03.29 08:04:05 2022.03.29 08:04:05 "BUY" 0.01000 0.0000 1.10009 » » 0.00 0.00 "" [2] 219087400 2022.03.29 08:04:29 2022.03.29 08:04:29 "SELL" 0.03000 0.0000 1.10018 » » 0.00 0.00 "" [3] 219087403 2022.03.29 08:04:34 2022.03.29 08:04:34 "BUY" 0.01000 0.0000 1.10017 » » 0.00 0.00 "" |
Частичную историю на примере конкретных позиций рассмотрим для случая встречного закрытия на счете с хеджированием. Сперва можно просмотреть отдельно первую позицию: PositionID=1276109280. Она будет показана полностью вне зависимости от входного параметра Type.
Positions total: 1 Position: 1 1276109280 Profit:-0.040000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725455 1276109280 "BUY" "IN" 0.01000 1.08344 0.00000 [1] 2022.03.07 12:20:58 1258725503 1276109328 "SELL" "OUT_BY" 0.01000 1.08340 -0.04000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109280 2022.03.07 12:20:53 2022.03.07 12:20:53 "BUY" 0.01000 1.08344 1.08344 » » 0.00 0.00 "" [1] 1276109328 2022.03.07 12:20:58 2022.03.07 12:20:58 "CLOSE_BY" 0.01000 1.08340 1.08340 » » 0.00 0.00 "#1276109280 by #1276109283" |
Также можно посмотреть вторую: PositionID=1276109283. Однако если Type равен "Position", для выделения фрагмента истории используется функция HistorySelectByPosition, и в результате выходной ордер будет только один (несмотря на то, что сделок — две).
Positions total: 1 Position: 1 1276109283 Profit:0.000000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725458 1276109283 "SELL" "IN" 0.01000 1.08340 0.00000 [1] 2022.03.07 12:20:58 1258725504 1276109328 "BUY" "OUT_BY" 0.01000 1.08344 0.00000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109283 2022.03.07 12:20:53 2022.03.07 12:20:53 "SELL" 0.01000 1.08340 1.08340 » » 0.00 0.00 "" |
Если поменять Type на "всю историю", ордер "CLOSE_BY" появится.
Positions total: 1 Position: 1 1276109283 Profit:0.000000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725458 1276109283 "SELL" "IN" 0.01000 1.08340 0.00000 [1] 2022.03.07 12:20:58 1258725504 1276109328 "BUY" "OUT_BY" 0.01000 1.08344 0.00000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109283 2022.03.07 12:20:53 2022.03.07 12:20:53 "SELL" 0.01000 1.08340 1.08340 » » 0.00 0.00 "" [1] 1276109328 2022.03.07 12:20:58 2022.03.07 12:20:58 "CLOSE_BY" 0.01000 1.08340 1.08340 » » 0.00 0.00 "#1276109280 by #1276109283" |
При таких настройках история выбирается полностью, но фильтр оставляет из неё только ордера, у которых в свойства ORDER_POSITION_ID или ORDER_POSITION_BY_ID встречается идентификатор заданной позиции. Для составления условий с логическим ИЛИ в классе TradeFilter добавлен элемент IS::OR_EQUAL — с ним предлагается разобраться самостоятельно.