- Главное событие экспертов: OnTick
- Основные принципы и понятия: ордер, сделка, позиция
- Типы торговых операций
- Типы ордеров
- Режимы исполнения ордеров по цене и объемам
- Сроки действия отложенных ордеров
- Расчет залога для будущего ордера: OrderCalcMargin
- Оценка прибыли торговой операции: OrderCalcProfit
- Структура торгового запроса MqlTradeRequest
- Структура проверки запроса MqlTradeCheckResult
- Проверка корректности запроса: OrderCheck
- Результат отправки запроса: структура MqlTradeResult
- Отправка торгового запроса: OrderSend и OrderSendAsync
- Совершение покупки или продажи
- Модификация уровней Stop Loss и/или Take Profit позиции
- Трейлинг стоп
- Полное и частичное закрытие позиции
- Полное и частичное закрытие встречных позиций (хедж)
- Установка отложенного ордера
- Модификация отложенного ордера
- Удаление отложенного ордера
- Получение списка действующих ордеров
- Свойства ордеров (действующих и в истории)
- Функции для чтения свойств действующих ордеров
- Отбор ордеров по свойствам
- Получение списка позиций
- Свойства позиций
- Функции для чтения свойств позиций
- Свойства сделок
- Выборка ордеров и сделок из истории
- Функции для чтения свойств ордеров из истории
- Функции для чтения свойств сделок из истории
- Типы торговых транзакций
- Событие OnTradeTransaction
- Синхронные и асинхронные запросы
- Событие OnTrade
- Контроль за изменениями торгового окружения
- Особенности создания мультисимвольных экспертов
- Ограничения и преимущества экспертов
- Создание заготовки эксперта в Мастере MQL
Расчет залога для будущего ордера: OrderCalcMargin
Прежде чем отправлять торговый приказ на сервер, MQL-программа может вычислить размер залога, который потребуется для планируемой торговой операции, с помощью функции OrderCalcMargin. Рекомендуется всегда это делать во избежание чрезмерной нагрузки на депозит.
bool OrderCalcMargin(ENUM_ORDER_TYPE action, const string symbol,
double volume, double price, double &margin)
Функция вычисляет размер маржи, необходимой для указанного типа ордера action и финансового инструмента symbol в объеме volume лотов. При этом учитываются настройки текущего счета, но не учитываются имеющиеся отложенные ордера и открытые позиции. Перечисление ENUM_ORDER_TYPE было представлено в разделе Типы ордеров.
Значение маржи (в валюте счета) записывается в передаваемый по ссылке параметр margin.
Следует особо подчеркнуть, что это — оценка маржи для отдельно взятой новой позиции или ордера, а не общая величина залога, каким он станет после исполнения. Более того, оценка делается, как если бы на текущем счете не было других отложенных ордеров и открытых позиций. В реальности значение маржи зависит от многих факторов, включая другие ордера и позиции, и может меняться при изменении рыночного окружения (например, кредитного плеча).
Функция возвращает признак успеха (true) или ошибки (false). Код ошибки можно привычным образом получить из переменной _LastError.
Функцию OrderCalcMargin можно использовать только в экспертах и скриптах. Для расчета маржи в индикаторах нужно реализовать альтернативный способ, например, запускать вспомогательный эксперт в объекте-графике с передачей ему параметров и получением результата через механизм событий или самостоятельно описать в MQL5 вычисления по формулам согласно типам инструментов. В следующем разделе мы приведем пример такой реализации, вместе с оценкой потенциальной прибыли/убытка.
Мы могли бы написать простой скрипт, который вызывает OrderCalcMargin для символов из Обзора рынка, и сравнить значения маржи для них. Вместо этого слегка усложним задачу и рассмотрим заголовочный файл LotMarginExposure.mqh, который позволяет оценить загрузку депозита и уровень маржи после открытия позиции с предопределенным уровнем риска. Чуть позже мы познакомимся с функцией OrderCheck, способной предоставить аналогичную информацию. Однако наш алгоритм дополнительно сможет решать обратную задачу — выбирать размер лота по заданным уровням загрузки или риска.
Применение новых возможностей продемонстрировано в неторгующем эксперте LotMarginExposureTable.mq5.
В принципе, тот факт, что MQL-программа реализована как эксперт не означает, что в ней обязательно должны выполняться торговые операции. Очень часто, как и в нашем случае, в виде эксперта создаются различные утилиты. Их преимущество по сравнению со скриптами в том, что они остаются на графике и могут бесконечно долго выполнять свои функции в ответ на те или иные события.
В новом эксперте мы задействуем навыки создания интерактивного графического интерфейса с помощью объектов. Проще говоря, для заданного списка символов эксперт выведет на график таблицу с несколькими колонками маржинальных показателей, причем по каждой из колонок таблицу можно сортировать. Перечень колонок мы приведем чуть позже.
Поскольку анализ лотов, маржи, загрузки депозита является общей востребованной задачей, выделим реализацию в отдельный заголовочный файл LotMarginExposure.mqh.
Все функции файла объединены в пространство имен во избежание конфликтов и для наглядности (указание контекста перед вызовом внутренней функции дает знать о происхождении и размещении этой функции).
namespace LEMLR
|
Аббревиатура LEMLR означает "Lot, Exposure, Margin Level, Risk".
Основные расчеты выполняются в функции Estimate. Учитывая прототип встроенной функции OrderCalcMargin, легко предположить, что в параметрах функции Estimate потребуется передать название символа, тип ордера, объем и цену. Но это не все, что нам понадобится.
bool Estimate(const ENUM_ORDER_TYPE type, const string symbol, const double lot,
|
Мы предполагаем оценить несколько показателей торговой операции, которые взаимосвязаны и могут рассчитываться в разных направлениях, в зависимости от того, что пользователь ввел как исходные данные, а что хочет рассчитать. Например, с помощью вышеперечисленных параметров легко узнать новый уровень маржи и загрузку счета. Напомним, что их формулы похожи с точностью "до наоборот":
Ml = money / margin * 100
|
Здесь переменной margin обозначена сумма маржи, для получения которой достаточно вызвать OrderCalcMargin.
Однако трейдеры часто предпочитают исходить из предопределенного уровня загрузки или маржи, и рассчитывать для них объем. Более того, существует не менее популярный подход с расчетом лота на основе риска. Под риском понимается размер потенциального убытка от торговли при неблагоприятном движении цены, в результате чего будет уменьшаться содержимое другой переменной из вышеприведенных формул — money.
Для вычисления убытка важно знать волатильность финансового инструмента на торговом периоде (длительности стратегии) или дистанцию предполагаемого пользователем стоп-лосса.
Поэтому перечень параметров функции Estimate расширяется.
bool Estimate(const ENUM_ORDER_TYPE type, const string symbol, const double lot,
|
В параметре exposure укажем желаемую загрузку депозита в процентах, а в параметре riskLevel — часть депозита (тоже в процентах), которой мы готовы рискнуть. Для расчетов на основе риска можно передать размер стоп-лосса в пунктах в параметре riskPoints. Когда он равен 0, в дело вступает параметр riskPeriod: в нем указывается период, для которого алгоритм автоматически рассчитает диапазон котировок символа в пунктах. Наконец, в параметре money можно задать произвольный размер свободных средств для оценки лота. Некоторые трейдеры условно делят депозит между несколькими роботами. Когда money равно 0, функция заполнит эту переменную свойством AccountInfoDouble(ACCOUNT_MARGIN_FREE).
Осталось решить, как возвращать результаты работы функции. Поскольку она способна оценить много торговых показателей и несколько вариантов объема, имеет смысл определить структуру SymbolLotExposureRisk.
struct SymbolLotExposureRisk
|
Поле lot в структуре содержит переданный в функцию Exposure лот, если он не равен 0. Если переданный лот — нулевой, вместо него подставляется свойство символа SYMBOL_VOLUME_MIN.
Под расчетные значения объемов исходя из загрузки депозита и риска выделено по два поля: с суффиксом Raw (lotFromExposureRaw, lotFromRiskOfStopLossRaw) и без него (lotFromExposure, lotFromRiskOfStopLoss). Raw-поля содержат "чистый арифметический" результат, который может не соответствовать спецификации символа. В полях без суффикса лоты нормализованы с учетом минимума, максимума и шага. Такое дублирование полезно, в частности, для тех случаев, когда расчет выдает значения меньше минимального лота (например, lotFromExposureRaw равно 0.023721 при минимуме 0.1, из-за чего lotFromExposure сводится к нулю): тогда по содержимому Raw-поля можно оценить, сколько средств добавить или насколько повысить риск, чтобы добраться до минимального лота.
Опишем последний выходной параметр функции Estimate как ссылку на данную структуру. В теле функции постепенно заполним все её поля. Прежде всего, получим размер маржи для одного лота с помощью вызова OrderCalcMargin и сохраним в локальную переменную lot1margin.
bool Estimate(const ENUM_ORDER_TYPE type, const string symbol, const double lot,
|
Если входная цена не указана, т.е. price равно 0, вспомогательная функция GetCurrentPrice возвращает подходящую цену на основе типа ордера: для покупок будет взято свойство символа SYMBOL_ASK, и для продаж — SYMBOL_BID. Эта и другие вспомогательные функции здесь опущены, с их содержимым можно ознакомиться в прилагаемых исходных кодах.
Если расчет маржи завершился ошибкой или получено нулевое значение, функция Estimate вернет false.
Следует иметь в виду, что нулевая маржа может быть нормой или ошибкой, в зависимости от инструмента и типа ордера. Так для биржевых тикеров отложенные ордера облагаются залогом, а для внебиржевых — нет (то есть залог 0 корректен). Этот нюанс следует учитывать в вызывающем коде: он должен запрашивать маржу только по таким сочетаниям символов и типов операций, для которых она имеет смысл и предполагается ненулевой.
Имея залог для одного лота, мы можем рассчитать количество лотов для обеспечения заданной загрузки депозита.
double usedMargin = 0;
|
Вспомогательная функция NormalizeLot приведена ниже.
Для того чтобы получить лот в зависимости от риска и волатильности потребуется чуть больше вычислений.
const double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
Здесь мы находим стоимость одного пункта инструмента и диапазон его изменений за указанный период, после чего уже рассчитываем лот.
Наконец, получим загрузку счета и уровень маржи для заданного лота.
r.lot = lot <= 0 ? SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN) : lot;
|
В случае успешного расчета функция вернет true.
А вот и функция NormalizeLot с сокращениями (все проверки на 0 для простоты опущены). Подробности про соответствующие свойства можно найти в разделе Разрешенные объемы торговых операций.
double NormalizeLot(const string symbol, const double lot)
|
Приведенная выше реализация Estimate не учитывает поправки на перекрывающиеся позиции — как правило, они приводят к уменьшению залога, поэтому текущая оценка загрузки счета и уровня маржи может быть более пессимистичной, чем получится в реальности, но это обеспечивает дополнительную защиту. Желающие могут добавить код для анализа состава уже замороженных средств счета (их общая сумма содержится в свойстве счета ACCOUNT_MARGIN) в разбивке по позициям и ордерам: тогда станет возможным учесть потенциальный взаимозачет маржи с новым ордером (например, учтётся только наибольшая позиция из встречных или применится пониженная ставка хеджированной маржи, см. подробности в разделе Маржинальные требования).
Пришло время применить оценку маржи и лотов на практике в эксперте LotMarginExposureTable.mq5. С учетом того, что Raw-поля мы будем показывать только в тех случаях, когда нормализация лотов привела к их обнулению, общее количество колонок в результирующей таблице показателей равно 8.
#include <MQL5Book/LotMarginExposure.mqh>
|
Во входных параметрах предусмотрим задание типа ордера, перечня анализируемых символов (список с разделителем-запятой), размер доступных средств, а также лот, целевую загрузку депозита, уровень маржи и риска.
input ENUM_ORDER_TYPE Action = ORDER_TYPE_BUY;
|
Для отложенных типов ордеров необходимо обязательно выбирать биржевые символы, поскольку для иных символов будет получена нулевая маржа, что спровоцирует ошибку в функции Estimate. Если список символов оставлен пустым, эксперт обработает только символ текущего графика. Нулевые по умолчанию значения в параметрах Money и Lot означают, соответственно, текущий размер свободных средств на счете и минимальный лот по каждому символу.
Значение 0 в параметре RiskPoints предписывает получение диапазона цен за RiskPeriod (по умолчанию неделя).
Входной параметр UpdateFrequency задает периодичность повторения пересчета в секундах. Если оставить его равным нулю, пересчет выполняется на каждом новом баре.
input int UpdateFrequency = 0; // UpdateFrequency (sec, 0 - once per bar) |
В глобальном контексте описаны: массив символов (позднее заполняемый парсингом входного параметра WorkList) и временная метка последнего успешного расчета.
string symbols[];
|
При запуске включаем секундный таймер.
void OnInit()
|
В обработчике таймера обеспечиваем первый вызов основного расчета в OnTick, если OnTick еще не был вызван по приходу тика — такое может быть, например, на выходных или на спокойном рынке. Также OnTimer является точкой входа для повторных расчетов с заданной частотой.
void OnTimer()
|
В обработчике OnTick прежде всего проверяем входные параметры и преобразуем список символов в массив строк. В случае обнаружения проблем в lastTime записывается признак ошибки: значение -1, и обработка последующих тиков прерывается в самом начале.
void OnTick()
|
В частности, ошибкой считается, если входные значения Exposure и RiskLevel не лежат в диапазоне от 0 до 100, присущем процентам. В случае нормальных входных данных обновляем временную метку, описываем структуру LEMLR::SymbolLotExposureRisk для приема расчетных показателей из функции LEMLR::Estimate (по одному символу), а также двумерный массив LME (от "Lot Margin Exposure") для сбора показателей по всем символам.
lastTime = UpdateFrequency > 0 ? TimeCurrent() : iTime(NULL, 0, 0);
|
В цикле по символам вызываем функцию LEMLR::Estimate и заполняем массив LME.
for(int i = 0; i < ns; i++)
|
В качестве индексов массива использованы элементы специального перечисления LME_FIELDS, которое предоставляет одновременно названия и номера для показателей из структуры.
enum LME_FIELDS // 10 полей + 3 дополнительных свойства символа
|
Свойства SYMBOL_VOLUME_MIN и SYMBOL_TRADE_CONTRACT_SIZE добавляются для справки. Название символа "упаковывается" в приблизительное значение типа double с помощью функции pack2double, чтобы впоследствии реализовать унифицированную сортировку по любому из полей, включая и названия.
double pack2double(const string s)
|
На этом этапе мы могли бы уже запустить эксперт и распечатать результаты в журнале, примерно так.
ArrayPrint(LME); |
Но смотреть все время в журнал неудобно. Кроме того, единое форматирование величин из разных колонок, и тем более представление "упакованных" строк в double никак нельзя назвать дружественным. Поэтому был разработан класс табло (Tableau.mqh) для отображения произвольной таблицы на графике. Помимо того что при подготовке таблицы мы можем сами управлять форматом каждого поля (в перспективе — подсвечивать разным цветом), этот класс позволяет интерактивно сортировать таблицу по любой колонке: первый щелчок мышью сортирует в одном направлении, второй — в обратном, третий — отменяет сортировку.
Здесь мы не будем описывать класс подробно. При необходимости можно ознакомиться с его исходным кодом. Важно лишь отметить, что интерфейс строится на базе графических объектов. Фактически ячейки таблицы формируются объектами типа OBJ_LABEL, и все их свойства уже знакомы читателю. Однако кое-какие технические приемы, использованные в исходном коде табло — в частности, работа с графическими ресурсами и измерение отображаемого текста, будут представлены позднее, в седьмой части.
Конструктор класса Tableau принимает несколько параметров:
- prefix — префикс для имен создаваемых графических объектов;
- rows — количество строк;
- cols — количество колонок;
- height — высота строки в пикселях (-1 означает удвоенный размер шрифта);
- width — ширина ячейки в пикселях;
- c — угол графика для привязки объектов;
- g — зазор в пикселях между ячейками;
- f — размер шрифта;
- font — название шрифта для обычных ячеек;
- bold — название жирного шрифта для заголовков;
- bgc — цвет фона;
- bgt — прозрачность фона.
class Tableau
|
Большинство этих параметров пользователь может задать во входных переменных эксперта LotMarginExposureTable.mq5.
input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER;
|
Количество колонок таблицы у нас определено заранее, количество строк равно количеству символов, плюс верхняя строка с заголовками.
Важно отметить, что шрифты для таблицы следует выбирать без пропорционального начертания букв, поэтому в переменной MotoTypeFontsHint приводится подсказка с набором стандартных моноширинных шрифтов Windows.
Заполнение созданных графических объектов данными производит метод fill класса Tableau.
bool fill(const string &data[], const string &hint[]) const; |
В него наш эксперт передает массив строк data, которые получены из массива LME путем серии преобразований через StringFormat, а также массив hint со всплывающими подсказками для заголовков.
На следующем изображении представлен фрагмент графика с работающим экспертом при настройках по умолчанию, но со списком символов "EURUSD,USDRUB,USDCNH,XAUUSD,XPDUSD".
Уровни загрузки депозита и маржи при минимальном лоте по каждому символу
В левой колонке выводятся имена символов. В качестве заголовка первой колонки отображается сумма средств (в данном случае, свободных на счете в текущий момент, т.к. во входном параметре Money оставлено значение 0). При наведении курсора мыши на название колонки можно увидеть всплывающую подсказку с пояснением.
В последующих колонках:
- L(E) — лот, вычисленный для уровня загрузки E депозита 5% после сделки;
- L(R) — лот, вычисленный при риске R на 5% депозита после неудачной торговли (диапазон в пунктах и сумма риска — в последней колонке);
- E% — загрузка депозита после входа минимальным лотом;
- M% — уровень маржи после входа минимальным лотом;
- MinL — минимальный лот для каждого символа;
- Contract — размер контракта (1 лот) для каждого символа;
- Risk — прибыль/убыток в деньгах при торговле 1 лотом и этот же диапазон в пунктах.
В колонках E% и M% в данном случае использованы минимальные лоты, поскольку входной параметре Lot равен 0 (по умолчанию).
Видно, что при загрузке депозита 5% торговля возможна для всех выбранных символов, кроме "XPDUSD". Для последнего получился объем 0.03272, что меньше минимального лота 0.1, в связи с чем результат заключен в скобки. Если разрешим загрузку 20% (введем 20 в параметр Exposure), получим для "XPDUSD" минимальный лот 0.1.
Если введем в параметр Lot значение 1, увидим в таблице обновленные значения в колонках E% и M% (загрузка увеличится, уровень маржи упадет).
Уровни загрузки депозита и маржи при единичном лоте по каждому символу
На последнем скриншоте, иллюстрирующем работу эксперта, приведен большой набор "голубых фишек" российской биржи MOEX с сортировкой по объему, рассчитанному для 5%-загрузки депозита (2-я колонка). Среди нестандартных настроек можно отметить, что Lot=10, а период для вычисления ценового диапазона и риска равен MN1. Фон сделан полупрозрачным белым, привязка — к левому верхнему углу графика.
Лоты, загрузка депозита и уровень маржи для инструментов MOEX