Библиотека для простого и быстрого создания программ для MetaTrader (Часть VII): События срабатывания StopLimit-ордеров, подготовка функционала для событий модификации ордеров и позиций
Содержание
Концепция
В прошлых частях описания кроссплатформенной библиотеки для MetaTrader 5 и MetaTrader 4 мы подготовили инструментарий для создания user-case функций для быстрого доступа из своих программ к любым данным любых ордеров и позиций на счётах с типом хэдж и неттинг. Это функции для отслеживания событий, происходящих с ордерами и позициями — выставление, удаление и срабатывание отложенных ордеров, открытие и закрытие позиций.
Но пока остаётся нереализованным функционал для отслеживания срабатывания уже выставленных StopLimit-ордеров и модификации рыночных ордеров и позиций.
В данной статье реализуем отслеживание события срабатывания StopLimit-ордера, результатом которого будет выставление Limit-ордера.
Библиотека будет отслеживать такие события и отправлять в программу необходимые сообщения для дальнейшего их использования в ней.
Реализация
При тестировании моментов срабатывания выставленных StopLimit-ордеров я заметил, что это событие никак не отображается в
истории счёта в терминале, и его не получится просто взять, и получить из истории счёта "as is". Поэтому придётся отслеживать состояние существующих ордеров до момента изменения этого состояния (в данном случае — изменение типа выставленного ордера с одним и тем же тикетом).
Подойдём к реализации отслеживания срабатывания StopLimit-ордеров с практической точки зрения — помимо создания данного функционала, сделаем его пригодным для отслеживания и остальных событий по изменению уже существующих ордеров и позиций (изменение цены установки существующих отложенных ордеров, их уровней StopLoss и TakeProfit и этих же уровней у открытой позиции).
Логика подготавливаемого функционала будет такой:
Мы имеем доступ к полному списку всех активных ордеров и позиций на счёте. Из этого же списка мы можем получить и текущее состояние каждого из свойств этих объектов. Для отслеживания изменения контролируемых свойств нам необходимо иметь дополнительный список, в котором будет записано "прошлое" состояние свойств объекта, которое изначально будет равно текущему.
При сравнении свойств объектов из этих двух списков, как только увидим различие в любом из контролируемых свойств — значит имеем факт изменения свойства, и тут же создаём "изменённый" объект, в который записываем как прошлое свойство, так и новое — изменённое, и помещаем этот объект в новый список — "список изменённых объектов".
Этот новый список мы будем далее обрабатывать в классе, отслеживающем события на счёте.
Конечно, можно и сразу отправлять событие при обнаружении изменения свойств объекта, но.., у нас может быть ситуация, в которой несколько объектов были изменены за один тик. И если мы будем обрабатывать изменения сразу, то сможем обработать только изменение самого последнего объекта из пачки. Это нас не устраивает. А значит будем на этом этапе создавать список всех изменённых объектов и проверять размер этого списка в классе-обработчике событий. И в нём будем в цикле обрабатывать каждый изменённый объект из списка изменённых объектов. Таким образом мы не потеряем ни одно из одновременно произошедших событий изменения свойств ордеров и позиций на счёте.
В третьей части описания библиотеки, при создании коллекции рыночных ордеров и позиций мы условились, что для постоянного отслеживания за актуальным состоянием ордеров и позиций нам необходимо обновлять список и хранить текущую и прошлую хэш-сумму, рассчитываемую как тикет+время изменения позиции в миллисекундах и объём. Но для отслеживания изменения состояний свойств ордеров и позиций нам будет недостаточно этих данных для расчёта хэш-суммы
- Для учёта изменения цены установки ордера нам необходимо учитывать эту цену
- Для учёта изменения цен StopLoss и TakeProfit нам необходимо учитывать и эти цены.
Значит, к хэш-сумме добавим эти три цены, но каждую преобразуем в семиразрядное ulong-число — просто отбросим запятую и увеличим на один порядок разрядность числа (для учёта шестизначных котировок): например, если цена равна 1.12345, то число для хэш-суммы будет 1123450.
Закончим с теорией и приступим к реализации.
В файл Defines.mqh добавим перечисления с флагами возможных вариантов изменения свойств ордеров и позиций, и сами варианты, которые будем отслеживать:
//+------------------------------------------------------------------+ //| Список флагов возможных вариантов изменения ордеров и позиций | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE_FLAGS { CHANGE_TYPE_FLAG_NO_CHANGE = 0, // Нет изменений CHANGE_TYPE_FLAG_TYPE = 1, // Изменение типа ордера CHANGE_TYPE_FLAG_PRICE = 2, // Изменение цены CHANGE_TYPE_FLAG_STOP = 4, // Изменение StopLoss CHANGE_TYPE_FLAG_TAKE = 8, // Изменение TakeProfit CHANGE_TYPE_FLAG_ORDER = 16 // Флаг изменения свойств ордера }; //+------------------------------------------------------------------+ //| Возможные варианты изменения ордеров и позиций | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE { CHANGE_TYPE_NO_CHANGE, // Нет изменений CHANGE_TYPE_ORDER_TYPE, // Изменение типа ордера CHANGE_TYPE_ORDER_PRICE, // Изменение цены установки ордера CHANGE_TYPE_ORDER_PRICE_STOP_LOSS, // Изменение цены установки ордера и StopLoss CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT, // Изменение цены установки ордера и TakeProfit CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Изменение цены установки ордера, StopLoss и TakeProfit CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT, // Изменение StopLoss и TakeProfit CHANGE_TYPE_ORDER_STOP_LOSS, // Изменение StopLoss ордера CHANGE_TYPE_ORDER_TAKE_PROFIT, // Изменение TakeProfit ордера CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT, // Изменение StopLoss и TakeProfit позиции CHANGE_TYPE_POSITION_STOP_LOSS, // Изменение StopLoss позиции CHANGE_TYPE_POSITION_TAKE_PROFIT, // Изменение TakeProfit позиции }; //+------------------------------------------------------------------+
Относительно флагов возможных вариантов изменения свойств ордеров и позиций:
- флаг изменения типа ордера будет устанавливаться при срабатывании StopLimit-ордера,
- флаг изменения цены будет устанавливаться при модификации цены установки отложенного ордера,
- флаги изменения стоплосс и тейкпрофит — понятно,
- флаг ордера — для идентификации изменения свойств ордера (не позиции)
В перечислении возможных вариантов модификаций ордеров и позиций собраны все варианты, которые будем в дальнейшем отслеживать. Сегодня сделаем лишь отслеживание события срабатывания StopLimit-ордера (CHANGE_TYPE_ORDER_TYPE).
В перечисление списка возможных торговых событий на счёте ENUM_TRADE_EVENT добавим восемь новых событий, которые будут отправляться в программу при их идентификации:
//+------------------------------------------------------------------+ //| Список возможных торговых событий на счёте | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT = 0, // Нет торгового события TRADE_EVENT_PENDING_ORDER_PLASED, // Отложенный ордер установлен TRADE_EVENT_PENDING_ORDER_REMOVED, // Отложенный ордер удалён //--- члены перечисления, совпадающие с членами перечисления ENUM_DEAL_TYPE //--- (порядок следования констант ниже менять нельзя, удалять и добавлять новые - нельзя) TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT, // Начисление кредита (3) TRADE_EVENT_ACCOUNT_CHARGE, // Дополнительные сборы TRADE_EVENT_ACCOUNT_CORRECTION, // Корректирующая запись TRADE_EVENT_ACCOUNT_BONUS, // Перечисление бонусов TRADE_EVENT_ACCOUNT_COMISSION, // Дополнительные комиссии TRADE_EVENT_ACCOUNT_COMISSION_DAILY, // Комиссия, начисляемая в конце торгового дня TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY, // Комиссия, начисляемая в конце месяца TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY, // Агентская комиссия, начисляемая в конце торгового дня TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY, // Агентская комиссия, начисляемая в конце месяца TRADE_EVENT_ACCOUNT_INTEREST, // Начисления процентов на свободные средства TRADE_EVENT_BUY_CANCELLED, // Отмененная сделка покупки TRADE_EVENT_SELL_CANCELLED, // Отмененная сделка продажи TRADE_EVENT_DIVIDENT, // Начисление дивиденда TRADE_EVENT_DIVIDENT_FRANKED, // Начисление франкированного дивиденда TRADE_EVENT_TAX = DEAL_TAX, // Начисление налога //--- константы, относящиеся к типу сделки DEAL_TYPE_BALANCE из перечисления ENUM_DEAL_TYPE TRADE_EVENT_ACCOUNT_BALANCE_REFILL = DEAL_TAX+1, // Пополнение средств на балансе TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+2, // Снятие средств с баланса //--- Остальные возможные торговый события //--- (менять порядок следования констант ниже, удалять и добавлять новые - можно) TRADE_EVENT_PENDING_ORDER_ACTIVATED = DEAL_TAX+3, // Отложенный ордер активирован ценой TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL, // Отложенный ордер активирован ценой частично TRADE_EVENT_POSITION_OPENED, // Позиция открыта TRADE_EVENT_POSITION_OPENED_PARTIAL, // Позиция открыта частично TRADE_EVENT_POSITION_CLOSED, // Позиция закрыта TRADE_EVENT_POSITION_CLOSED_BY_POS, // Позиция закрыта встречной TRADE_EVENT_POSITION_CLOSED_BY_SL, // Позиция закрыта по StopLoss TRADE_EVENT_POSITION_CLOSED_BY_TP, // Позиция закрыта по TakeProfit TRADE_EVENT_POSITION_REVERSED_BY_MARKET, // Разворот позиции новой сделкой (неттинг) TRADE_EVENT_POSITION_REVERSED_BY_PENDING, // Разворот позиции активацией отложенного ордера (неттинг) TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL, // Разворот позиции частичным исполнением маркет-ордера (неттинг) TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL, // Разворот позиции частичной активацией отложенного ордера (неттинг) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET, // Добавлен объём к позиции новой сделкой (неттинг) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL, // Добавлен объём к позиции частичным исполнением маркет-ордера (неттинг) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING, // Добавлен объём к позиции активацией отложенного ордера (неттинг) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL, // Добавлен объём к позиции частичной активацией отложенного ордера (неттинг) TRADE_EVENT_POSITION_CLOSED_PARTIAL, // Позиция закрыта частично TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS, // Позиция закрыта частично встречной TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL, // Позиция закрыта частично по StopLoss TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP, // Позиция закрыта частично по TakeProfit TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER, // Срабатывание StopLimit ордера TRADE_EVENT_MODIFY_ORDER_PRICE, // Изменение цены установки ордера TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS, // Изменение цены установки ордера и StopLoss TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT, // Изменение цены установки ордера и TakeProfit TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Изменение цены установки ордера, StopLoss и TakeProfit TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT, // Изменение StopLoss и TakeProfit ордера TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, // Изменение StopLoss позиции TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, // Изменение TakeProfit позиции };
И наконец в список перечислений причин событий ENUM_EVENT_REASON добавим новую константу, описывающую срабатывание StopLimit-ордера:
//+------------------------------------------------------------------+ //| Причина события | //+------------------------------------------------------------------+ enum ENUM_EVENT_REASON { EVENT_REASON_REVERSE, // Разворот позиции (неттинг) EVENT_REASON_REVERSE_PARTIALLY, // Разворот позиции частичным исполнением заявки (неттинг) EVENT_REASON_REVERSE_BY_PENDING, // Разворот позиции при срабатывании отложенного ордера (неттинг) EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY, // Разворот позиции при при частичном срабатывании отложенного ордера (неттинг) //--- Все константы, относящиеся к развороту позиции, должны быть в списке выше EVENT_REASON_ACTIVATED_PENDING, // Срабатывание отложенного ордера EVENT_REASON_ACTIVATED_PENDING_PARTIALLY, // Частичное срабатывание отложенного ордера EVENT_REASON_STOPLIMIT_TRIGGERED, // Срабатывание StopLimit-ордера EVENT_REASON_CANCEL, // Отмена EVENT_REASON_EXPIRED, // Истечение срока действия ордера EVENT_REASON_DONE, // Заявка исполнена полностью EVENT_REASON_DONE_PARTIALLY, // Заявка исполнена частично EVENT_REASON_VOLUME_ADD, // Добавление объёма к позиции (неттинг) EVENT_REASON_VOLUME_ADD_PARTIALLY, // Добавление объёма к позиции частичным исполнением заявки (неттинг) EVENT_REASON_VOLUME_ADD_BY_PENDING, // Добавление объёма к позиции при срабатывании отложенного ордера (неттинг) EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY, // Добавление объёма к позиции при частичном срабатывании отложенного ордера (неттинг) EVENT_REASON_DONE_SL, // Закрытие по StopLoss EVENT_REASON_DONE_SL_PARTIALLY, // Частичное закрытие по StopLoss EVENT_REASON_DONE_TP, // Закрытие по TakeProfit EVENT_REASON_DONE_TP_PARTIALLY, // Частичное закрытие по TakeProfit EVENT_REASON_DONE_BY_POS, // Закрытие встречной позицией EVENT_REASON_DONE_PARTIALLY_BY_POS, // Частичное закрытие встречной позицией EVENT_REASON_DONE_BY_POS_PARTIALLY, // Закрытие частичным объёмом встречной позиции EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY, // Частичное закрытие частичным объёмом встречной позиции //--- Константы, относящиеся к типу сделки DEAL_TYPE_BALANCE из перечисления ENUM_DEAL_TYPE EVENT_REASON_BALANCE_REFILL, // Пополнение счёта EVENT_REASON_BALANCE_WITHDRAWAL, // Снятие средств со счёта //--- Список констант соотносится с TRADE_EVENT_ACCOUNT_CREDIT из перечисления ENUM_TRADE_EVENT, смещено на +13 относительно ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3) EVENT_REASON_ACCOUNT_CREDIT, // Начисление кредита EVENT_REASON_ACCOUNT_CHARGE, // Дополнительные сборы EVENT_REASON_ACCOUNT_CORRECTION, // Корректирующая запись EVENT_REASON_ACCOUNT_BONUS, // Перечисление бонусов EVENT_REASON_ACCOUNT_COMISSION, // Дополнительные комиссии EVENT_REASON_ACCOUNT_COMISSION_DAILY, // Комиссия, начисляемая в конце торгового дня EVENT_REASON_ACCOUNT_COMISSION_MONTHLY, // Комиссия, начисляемая в конце месяца EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY, // Агентская комиссия, начисляемая в конце торгового дня EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY, // Агентская комиссия, начисляемая в конце месяца EVENT_REASON_ACCOUNT_INTEREST, // Начисления процентов на свободные средства EVENT_REASON_BUY_CANCELLED, // Отмененная сделка покупки EVENT_REASON_SELL_CANCELLED, // Отмененная сделка продажи EVENT_REASON_DIVIDENT, // Начисление дивиденда EVENT_REASON_DIVIDENT_FRANKED, // Начисление франкированного дивиденда EVENT_REASON_TAX // Начисление налога }; #define REASON_EVENT_SHIFT (EVENT_REASON_ACCOUNT_CREDIT-3)
На этом изменения в файле Defines.mqh завершены.
Так как ранее договорились, что будем создавать и хранить список контрольных ордеров, то в этом списке нам необходимо будет хранить объекты с минимально-достаточным набором свойств для определения момента изменения одного из них у объектов-рыночных ордеров и позиций.
Создадим класс объекта-контрольного ордера.
Создадим в папке библиотеки Collections новый класс с названием файла OrderControl.mqh. Базовым классом сделаем класс стандартной библиотеки CObject и подключим необходимые для работа класса файлы:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: public: COrderControl(); ~COrderControl(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::COrderControl() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::~COrderControl() { } //+------------------------------------------------------------------+
Сразу объявим все необходимые переменные и методы в приватной секции класса:
private: ENUM_CHANGE_TYPE m_changed_type; // Тип изменения ордера MqlTick m_tick; // Структура тика string m_symbol; // Символ ulong m_position_id; // Идентификатор позиции ulong m_ticket; // Тикет ордера long m_magic; // Магический номер ulong m_type_order; // Тип ордера ulong m_type_order_prev; // Предыдущий тип ордера double m_price; // Цена ордера double m_price_prev; // Предыдущая цена ордера double m_stop; // Цена StopLoss double m_stop_prev; // Предыдущая цена StopLoss double m_take; // Цена TakeProfit double m_take_prev; // Предыдущая цена TakeProfit double m_volume; // Объём ордера datetime m_time; // Время установки ордера datetime m_time_prev; // Предыдущее время установки ордера int m_change_code; // Код изменения ордера //--- возвращает факт наличия флага изменения свойства bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Рассчитывает тип изменения параметров ордера void CalculateChangedType(void);
Все переменные-члены класса имеют понятные описания. Поясню о переменной, хранящей структуру тика: при срабатывании StopLimit-ордера нам необходимо будет записать время срабатывания. Так как время требуется записывать в милисекундах, а TimeCurrent() возвращает время без милисекунд, то для того, чтобы получить время последнего тика, на котором произошло срабатывание ордера, с милисекундами, будем использовать стандартную функцию SymbolInfoTick(), которая заполняет структуру тика данными, в числе которых есть и время тика с милисекундами.
Код изменения ордера будет составляться из флагов, описанных нами в перечислении ENUM_CHANGE_TYPE_FLAGS, и будет зависеть от произошедших изменений свойств ордера. Проверкой флагов и созданием кода изменения ордера будет заниматься приватный метод CalculateChangedType(), который будет рассмотрен нами ниже.
В публичной секции класса расположим методы получения и записи данных о прошлом и текущем состоянии свойств контрольного ордера, метод, устанавливающий тип произошедшего изменения свойств ордера, метод, устанавливающий новое состояние изменённого ордера, метод, возвращающий тип произошедшего изменения и метод, проверяющий изменение свойств ордера, устанавливающий и возвращающий тип произошедшего изменения. Данный метод будет вызываться из класса-коллекции рыночных ордеров и позиций для определения факта изменения активных ордеров и позиций.
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Класс контроля ордеров и позиций | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Тип изменения ордера MqlTick m_tick; // Структура тика string m_symbol; // Символ ulong m_position_id; // Идентификатор позиции ulong m_ticket; // Тикет ордера long m_magic; // Магический номер ulong m_type_order; // Тип ордера ulong m_type_order_prev; // Предыдущий тип ордера double m_price; // Цена ордера double m_price_prev; // Предыдущая цена ордера double m_stop; // Цена StopLoss double m_stop_prev; // Предыдущая цена StopLoss double m_take; // Цена TakeProfit double m_take_prev; // Предыдущая цена TakeProfit double m_volume; // Объём ордера datetime m_time; // Время установки ордера datetime m_time_prev; // Предыдущее время установки ордера int m_change_code; // Код изменения ордера //--- возвращает факт наличия флага изменения свойства bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Рассчитывает тип изменения параметров ордера void CalculateChangedType(void); public: //--- Устанавливает (1,2) текущий и прошлый тип (2,3) текущую и прошлую цену, (4,5) текущий и прошлый StopLoss, //--- (6,7) текущий и прошлый TakeProfit, (8,9) текущее и прошлое время установки, (10) объём void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Устанавливает (1) тип изменения, (2) новое текущее состояние void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Проверяет и устанавливает флаги изменения параметров ордера и возвращает тип изменения ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Возвращает (1,2,3,4) идентификатор позиции, тикет, магик и символ, (5,6) текущий и прошлый тип (7,8) текущую и прошлую цену, //--- (9,10) текущий и прошлый StopLoss, (11,12) текущий и прошлый TakeProfit, (13,14) текущее и прошлое время установки, (15) объём ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Возвращает тип изменения ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Конструктор COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+
В конструктор класса будем передавать идентификатор позиции, тикет, магик и символ ордера/позиции. В его списке инициализации сбросим флаги изменения ордера и тип произошедшего изменения, а так же сразу запишем полученные в передаваемых параметрах данные ордера/позиции в соответствующие переменные-члены класса.
За пределами тела класса напишем реализацию объявленных методов.
Приватный метод, рассчитывающий тип изменения параметров ордера/позиции:
//+------------------------------------------------------------------+ //| Рассчитывает тип изменения параметров ордера | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- Если стоит флаг ордера this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- Если сработал StopLimit-ордер this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- Если модифицирована цена установки ордера this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- Если вместе с ценой установки модифицированы StopLoss и TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- Если вместе с ценой установки модифицирован TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- Если вместе с ценой установки модифицирован StopLoss this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Модифицирована только цена установки ордера CHANGE_TYPE_ORDER_PRICE ) : //--- Цена установки не модифицирована //--- Если модифицированы StopLoss и TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- Если модифицирован TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- Если модифицирован StopLoss this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- Нет изменений CHANGE_TYPE_NO_CHANGE ) : //--- Позиция //--- Если у позиции модифицированы StopLoss и TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- Если у позиции модифицирован TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- Если у позиции модифицирован StopLoss this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- Нет изменений CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+
Данный метод записывает в переменную-член класса m_changed_type тип произошедшего изменения из ранее объявленного нами перечисления ENUM_CHANGE_TYPE в зависимости от наличия флагов в составе переменной m_change_code.
Все действия по проверке флагов описаны в комментариях к строкам листинга метода, и должны быть понятны.
Проверку наличия флага в составе переменной m_change_code осуществляет приватный метод
bool IsPresentChangeFlag(const int change_flag const { return (this.m_change_code & change_flag)==change_flag }
В метод передаётся проверяемый флаг, проверяется его наличие в составе m_change_code побитовой операцией И, и возвращается булевый результат сравнения (побитовой операции между значением кода и значением флага) со значением проверяемого флага.
Метод, устанавливающий новое текущее состояние свойств ордера/позиции:
//+------------------------------------------------------------------+ //| Устанавливает новое текущее состояние | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- Новый тип this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- Новая цена this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- Новый StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- Новый TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- Новое время this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
В метод передаётся указатель на ордер/позицию, в котором произошло изменение одного из его свойств.
Так как после определения факта произошедшего изменения одного из свойств ордера или позиции, нам нужно сохранить это новое состояние для дальнейших проверок, то метод просто сначала сохраняет своё текущее состояние свойства как прошлое, а затем в своё текущее состояние записывает значение этого свойства из переданного в метод ордера.
При сохранении времени произошедшего события используем стандартную функцию SymbolInfoTick() для получения времени тика в милисекундах.
Основной метод, вызываемый из класса CMarketCollection, и определяющий произошедшие изменения:
//+------------------------------------------------------------------+ //| Проверяет и устанавливает флаги изменения параметров ордера | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+
В метод передаётся указатель на проверяемый ордер/позицию и инициализируется код изменения. Если передан пустой объект сравниваемого ордера или его тикет не равен тикету текущего контрольного ордера, то возвращаем код отсутствия изменения.
Затем проверяем на неравенсто все отслеживаемые свойства контрольного и проверяемого ордеров, и если оно найдено, то в код изменения добавляется нужный флаг, описывающий данное изменение.
Далее по полностью сформированному коду изменения рассчитывается тип изменения в методе CalculateChangedType() и возвращается в вызывающую программу методом GetChangeType().
Полный листинг класса контрольного ордера:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Класс контроля ордеров и позиций | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Тип изменения ордера MqlTick m_tick; // Структура тика string m_symbol; // Символ ulong m_position_id; // Идентификатор позиции ulong m_ticket; // Тикет ордера long m_magic; // Магический номер ulong m_type_order; // Тип ордера ulong m_type_order_prev; // Предыдущий тип ордера double m_price; // Цена ордера double m_price_prev; // Предыдущая цена ордера double m_stop; // Цена StopLoss double m_stop_prev; // Предыдущая цена StopLoss double m_take; // Цена TakeProfit double m_take_prev; // Предыдущая цена TakeProfit double m_volume; // Объём ордера datetime m_time; // Время установки ордера datetime m_time_prev; // Предыдущее время установки ордера int m_change_code; // Код изменения ордера //--- возвращает факт наличия флага изменения свойства bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Рассчитывает тип изменения параметров ордера void CalculateChangedType(void); public: //--- Устанавливает (1,2) текущий и прошлый тип (2,3) текущую и прошлую цену, (4,5) текущий и прошлый StopLoss, //--- (6,7) текущий и прошлый TakeProfit, (8,9) текущее и прошлое время установки, (10) объём void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Устанавливает (1) тип изменения, (2) новое текущее состояние void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Проверяет и устанавливает флаги изменения параметров ордера и возвращает тип изменения ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Возвращает (1,2,3,4) идентификатор позиции, тикет, магик и символ, (5,6) текущий и прошлый тип (7,8) текущую и прошлую цену, //--- (9,10) текущий и прошлый StopLoss, (11,12) текущий и прошлый TakeProfit, (13,14) текущее и прошлое время установки, (15) объём ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Возвращает тип изменения ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Конструктор COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+ //| Проверяет и устанавливает флаги изменения параметров ордера | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+ //| Рассчитывает тип изменения параметров ордера | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- Если стоит флаг ордера this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- Если сработал StopLimit-ордер this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- Если модифицирована цена установки ордера this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- Если вместе с ценой установки модифицированы StopLoss и TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- Если вместе с ценой установки модифицирован TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- Если вместе с ценой установки модифицирован StopLoss this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Модифицирована только цена установки ордера CHANGE_TYPE_ORDER_PRICE ) : //--- Цена установки не модифицирована //--- Если модифицированы StopLoss и TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- Если модифицирован TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- Если модифицирован StopLoss this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- Нет изменений CHANGE_TYPE_NO_CHANGE ) : //--- Позиция //--- Если у позиции модифицированы StopLoss и TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- Если у позиции модифицирован TakeProfit this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- Если у позиции модифицирован StopLoss this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- Нет изменений CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+ //| Устанавливает новое текущее состояние | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- Новый тип this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- Новая цена this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- Новый StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- Новый TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- Новое время this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
Займёмся доработкой класса-коллекции рыночных ордеров и позиций CMarketCollection.
Нам необходимо сделать отслеживание изменений свойств, произошедших у активных ордеров и позиций. Так как именно в этом классе мы получаем все рыночные ордера и позиции, то логично в нём же и проверять их модификацию.
Подключим файл класса контрольного ордера. В приватной секции класса объявим список для хранения контрольных ордеров и позиций и список для хранения изенённых ордеров и позиций, переменную-член класса для хранения типа изменения ордера, переменную для хранения коэффициента для расчёта преобразования значения цены в значение хэш-суммы.
И объявим приватные методы:
метод для конвертации свойств ордера в число хэш-суммы, метод, добавляющий ордер или позицию в список отложенных ордеров и позиций на счёте, метод, создающий и добавляющий контрольный ордер в список контрольных ордеров и метод, создающий и добавляющий изменённый ордер в список изменённых ордеров, метод для удаления из списка контрольных ордеров ордера по тикету и идентификатору позиции, метод, возвращающий индекс контрольного ордера в списке контрольных ордеров по тикету и идентификатору позиции, и обработчик события изменения существующего ордера/позиции.
В публичной секции класса объявим метод, возвращающий созданный список изменённых ордеров.
//+------------------------------------------------------------------+ //| MarketCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\MarketOrder.mqh" #include "..\Objects\Orders\MarketPending.mqh" #include "..\Objects\Orders\MarketPosition.mqh" #include "OrderControl.mqh" //+------------------------------------------------------------------+ //| Коллекция рыночных ордеров и позиций | //+------------------------------------------------------------------+ class CMarketCollection : public CListObj { private: struct MqlDataCollection { ulong hash_sum_acc; // Хэш-сумма всех ордеров и позиций на счёте int total_market; // Количество маркет-ордеров на счёте int total_pending; // Количество отложенных ордеров на счёте int total_positions; // Количество позиций на счёте double total_volumes; // Общий объём ордеров и позиций на счёте }; MqlDataCollection m_struct_curr_market; // Текущие данные рыночных ордеров и позиций на счёте MqlDataCollection m_struct_prev_market; // Прошлые данные рыночных ордеров и позиций на счёте CListObj m_list_all_orders; // Список отложенных ордеров и позиций на счёте CArrayObj m_list_control; // Список контрольных ордеров CArrayObj m_list_changed; // Список изменённых ордеров COrder m_order_instance; // Объект-ордер для поиска по свойству ENUM_CHANGE_TYPE m_change_type; // Тип изменения ордера bool m_is_trade_event; // Флаг торгового события bool m_is_change_volume; // Флаг изменения общего объёма double m_change_volume_value; // Величина изменения общего объёма ulong m_k_pow; // Коэффициент для преобразования цены в число хэш-суммы int m_new_market_orders; // Количество новых маркет-ордеров int m_new_positions; // Количество новых позиций int m_new_pendings; // Количество новых отложенных ордеров //--- Сохраняет текущие значения состояния данных счёта как прошлые void SavePrevValues(void) { this.m_struct_prev_market=this.m_struct_curr_market; } //--- Конвертирует данные ордера в число для хэш-суммы ulong ConvertToHS(COrder* order) const; //--- Добавляет ордер или позицию в список отложенных ордеров и позиций на счёте, устанавливает данные рыночных ордеров и позиций на счёте bool AddToListMarket(COrder* order); //--- (1) Создаёт и добавляет контрольный ордер в список контрольных ордеров, (2) контрольный ордер в список изменённых контрольных ордеров bool AddToListControl(COrder* order); bool AddToListChanges(COrderControl* order_control); //--- Удаляет из списка контрольных ордеров ордер по тикету и идентификатору позиции bool DeleteOrderFromListControl(const ulong ticket,const ulong id); //--- Возвращает индекс контрольного ордера в списке по тикету и идентификатору позиции int IndexControlOrder(const ulong ticket,const ulong id); //--- Обработчик события изменения существующего ордера/позиции void OnChangeEvent(COrder* order,const int index); public: //--- Возвращает список (1) всех отложенных ордеров и открытых позиций, (2) модифицированных ордеров и позиций CArrayObj* GetList(void) { return &this.m_list_all_orders; } CArrayObj* GetListChanges(void) { return &this.m_list_changed; } //--- Возвращает список ордеров и позиций со временем открытия в диапазоне от begin_time до end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Возвращает список ордеров и позиций по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию CArrayObj* GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } //--- Возвращает количество (1) новых маркет-ордеров, (2) новых отложенных ордеров, (3) новых позиций, (4) флаг произошедшего торгового события (5) величину изменённого объёма int NewMarketOrders(void) const { return this.m_new_market_orders; } int NewPendingOrders(void) const { return this.m_new_pendings; } int NewPositions(void) const { return this.m_new_positions; } bool IsTradeEvent(void) const { return this.m_is_trade_event; } double ChangedVolumeValue(void) const { return this.m_change_volume_value; } //--- Конструктор CMarketCollection(void); //--- Обновляет список отложенных ордеров и позиций void Refresh(void); }; //+------------------------------------------------------------------+
В конструкторе класса впишем очистку и сортировку двух списков — списка контрольных ордеров и списка изменённых ордеров, а так же расчёт коэффициента для расчёта хэш-суммы:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0) { this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN); this.m_list_all_orders.Clear(); ::ZeroMemory(this.m_struct_prev_market); this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE; this.m_list_all_orders.Type(COLLECTION_MARKET_ID); this.m_list_control.Clear(); this.m_list_control.Sort(); this.m_list_changed.Clear(); this.m_list_changed.Sort(); this.m_k_pow=(ulong)pow(10,6); } //+------------------------------------------------------------------+
Метод конвертации свойств ордера в число для расчёта хэш-суммы:
//+------------------------------------------------------------------+ //| Преобразовывает цены ордера и его тип в число для хэш-суммы | //+------------------------------------------------------------------+ ulong CMarketCollection::ConvertToHS(COrder *order) const { if(order==NULL) return 0; ulong price=ulong(order.PriceOpen()*this.m_k_pow); ulong stop=ulong(order.StopLoss()*this.m_k_pow); ulong take=ulong(order.TakeProfit()*this.m_k_pow); ulong type=order.TypeOrder(); ulong ticket=order.Ticket(); return price+stop+take+type+ticket; } //+------------------------------------------------------------------+
В метод передаётся указатель на ордер, данные которого нужно преобразовать в число. Затем double-свойства ордера переводятся в число для хэш-суммы простым умножением на ранее рассчитанный в конструкторе класса коэффициент, все значения свойств складываются и возвращаются как ulong-число.
Изменения коснулись добавления объектов в список ордеров и позиций — теперь эти однотипные строки расположены в одном методе AddToListMarket(). После добавления объекта-ордера в список ордеров и позиций, проверяется наличие такого же ордера в списке контрольных ордеров, и если его там нету, то создаётся объект-контрольный ордер и добавляется в список контрольных ордеров при помощи метода AddToListControl(). Если же проверка наличия контрольного ордера показала его существование, то вызывается метод сравнения свойств текущего ордера со свойствами контрольного OnChangeEvent().
В листинге метода в комментариях к строкам описаны все производимые действия, и помечены в тексте выделением.
//+------------------------------------------------------------------+ //| Обновляет список ордеров | //+------------------------------------------------------------------+ void CMarketCollection::Refresh(void) { ::ZeroMemory(this.m_struct_curr_market); this.m_is_trade_event=false; this.m_is_change_volume=false; this.m_new_pendings=0; this.m_new_positions=0; this.m_change_volume_value=0; this.m_list_all_orders.Clear(); #ifdef __MQL4__ int total=::OrdersTotal(); for(int i=0; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS)) continue; long ticket=::OrderTicket(); //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции int index=this.IndexControlOrder(ticket); ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL) { CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Добавляем объект-позицию в список рыночных ордеров и позиций if(!this.AddToListMarket(position)) continue; //--- Если ордера нет в списке контрольных ордеров и позиций - добавляем if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket()); } } //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Добавляем объект-отложенный ордер в список рыночных ордеров и позиций if(!this.AddToListMarket(order)) continue; //--- Если ордера нет в списке контрольных ордеров и позиций - добавляем if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket()); } } //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } //--- MQ5 #else //--- Позиции int total_positions=::PositionsTotal(); for(int i=0; i<total_positions; i++) { ulong ticket=::PositionGetTicket(i); if(ticket==0) continue; CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Добавляем объект-позицию в список рыночных ордеров и позиций if(!this.AddToListMarket(position)) continue; //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции int index=this.IndexControlOrder(ticket,position.PositionID()); //--- Если ордера нет в списке контрольных ордеров - добавляем if(index==WRONG_VALUE) { if(!this.AddToListControl(position)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add a control position "),position.TypeDescription()," #",position.Ticket()); } } //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств else if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } //--- Ордера int total_orders=::OrdersTotal(); for(int i=0; i<total_orders; i++) { ulong ticket=::OrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE); //--- Маркет-ордер if(type<ORDER_TYPE_BUY_LIMIT) { CMarketOrder *order=new CMarketOrder(ticket); if(order==NULL) continue; //--- Добавляем объект-маркет-ордер в список рыночных ордеров и позиций if(!this.AddToListMarket(order)) continue; } //--- Отложенный ордер else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Добавляем объект-отложенный ордер в список рыночных ордеров и позиций if(!this.AddToListMarket(order)) continue; //--- Получаем индекс контрольного ордера по тикету и идентификатору позиции int index=this.IndexControlOrder(ticket,order.PositionID()); //--- Если ордера нет в списке контрольных ордеров - добавляем if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add a control order "),order.TypeDescription()," #",order.Ticket()); } } //--- Если ордер уже есть в списке контрольных ордеров - проверяем его на предмет изменения свойств else if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } #endif //--- Первый запуск if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE) { this.SavePrevValues(); } //--- Если хэш-сумма всех ордеров и позиций изменилась if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc) { this.m_new_market_orders=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market; this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending; this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions; this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4); this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false); this.m_is_trade_event=true; this.SavePrevValues(); } } //+------------------------------------------------------------------+
Метод, добавляющий ордер или позицию в список рыночных ордеров и позиций коллекции:
//+------------------------------------------------------------------+ //| Добавляет ордер или позицию в список ордеров и позиций на счёте | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListMarket(COrder *order) { if(order==NULL) return false; ENUM_ORDER_STATUS status=order.Status(); if(this.m_list_all_orders.InsertSort(order)) { if(status==ORDER_STATUS_MARKET_POSITION) { this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE_MSC)+this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_positions++; return true; } if(status==ORDER_STATUS_MARKET_PENDING) { this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_pending++; return true; } } else { ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to the list")); delete order; } return false; } //+------------------------------------------------------------------+
В метод передаётся указатель на добавляемый в список коллекции ордер. После добавления ордера в список-коллекцию, в зависимости от статуса ордера изменяются данные структуры, хранящей текущее состояние рыночных ордеров и позиций для последующей проверки и определения изменения количества ордеров и позиций.
- Если это позиция, то к общей хэш-сумме добавляется время изменения позиции и рассчитанное число для хэш-суммы, и увеличивается значение общего количества позиций.
- Если это отложенный ордер, то к общей хэш-сумме добавляется рассчитанное число для хэш-суммы и увеличивается значение общего количества отложенных ордеров.
Метод, создающий контрольный ордер и добавляющий его в список контрольных ордеров:
//+------------------------------------------------------------------+ //| Создаёт и добавляет ордер в список контрольных ордеров | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListControl(COrder *order) { if(order==NULL) return false; COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol()); if(order_control==NULL) return false; order_control.SetTime(order.TimeOpenMSC()); order_control.SetTimePrev(order.TimeOpenMSC()); order_control.SetVolume(order.Volume()); order_control.SetTime(order.TimeOpenMSC()); order_control.SetTypeOrder(order.TypeOrder()); order_control.SetTypeOrderPrev(order.TypeOrder()); order_control.SetPrice(order.PriceOpen()); order_control.SetPricePrev(order.PriceOpen()); order_control.SetStopLoss(order.StopLoss()); order_control.SetStopLossPrev(order.StopLoss()); order_control.SetTakeProfit(order.TakeProfit()); order_control.SetTakeProfitPrev(order.TakeProfit()); if(!this.m_list_control.Add(order_control)) { delete order_control; return false; } return true; } //+------------------------------------------------------------------+
В метод передаётся указатель на рыночный ордер или позицию. Если передан невалидный объект, то возвращаем false.
Затем создаётся новый контрольный ордер, в конструктор которого сразу передаются идентификатор позиции, тикет, магик и символ переданного в метод объекта-ордера, и далее заполняются все его данные, которые нам необходимы для идентификации модификации ордера или позиции.
Если новый контрольный ордер не получилось добавить в список контрольных ордеров, то он удаляется и возвращается false.
Так как мы постоянно добавляем вновь появляющиеся ордера и позиции в
список контрольных ордеров и позиций, то в итоге, после длительной
работы этот список может разрастись. А ведь ордера и позиции живут не
вечно, и их контрольные копии не должны постоянно храниться в списке — ведь ордера или позиции уже нет в рынке, а контрольный ордер продолжает
находиться в списке и занимает память. Для удаления ненужных контрольных
ордеров из списка служит метод, удаляющий контрольный ордер из
списка контрольных ордеров по тикету и идентификатору позиции
DeleteOrderFromListControl().
Отмечу, что метод пока только объявлен, но не реализован. Реализацию сделаем после того, как полностью будет готов весь функционал отслеживания модификации ордеров и позиций.
Метод, возвращающий индекс контрольного ордера в списке контрольных ордеров по тикету и идентификатору позиции:
//+------------------------------------------------------------------+ //| Возвращает индекс ордера по тикету в списке контрольных ордеров | //+------------------------------------------------------------------+ int CMarketCollection::IndexControlOrder(const ulong ticket,const ulong id) { int total=this.m_list_control.Total(); for(int i=0;i<total;i++) { COrderControl* order=this.m_list_control.At(i); if(order==NULL) continue; if(order.PositionID()==id && order.Ticket()==ticket) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
В метод передаётся тикет ордера/позиции и идентификатор позиции. В цикле по всем контрольным ордерам списка ищется контрольный ордер с равными тикетом и идентификатором, и возвращается его индекс в списке контрольных ореров. Если ордер не найден, возвращается -1.
Метод-обработчик события изменения существующего ордера/позиции:
//+------------------------------------------------------------------+ //| Обработчик события изменения существующего ордера/позиции | //+------------------------------------------------------------------+ void CMarketCollection::OnChangeEvent(COrder* order,const int index) { COrderControl* order_control=this.m_list_control.At(index); if(order_control!=NULL) { this.m_change_type=order_control.ChangeControl(order); ENUM_CHANGE_TYPE change_type=(order.Status()==ORDER_STATUS_MARKET_POSITION ? CHANGE_TYPE_ORDER_TAKE_PROFIT : CHANGE_TYPE_NO_CHANGE); if(this.m_change_type>change_type) { order_control.SetNewState(order); if(!this.AddToListChanges(order_control)) { ::Print(DFUN,TextByLanguage("Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to the list of modified orders")); } } } } //+------------------------------------------------------------------+
В метод передаются указатель на проверяемый ордер и индекс соответствующего ему контрольного ордера в списке контрольных ордеров.
Получаем контрольный ордер из списка по его индексу и проверяем наличие изменений свойств контрольного ордера, соответствующих свойствам проверяемого при помощи метода ChangeControl() контрольного ордера. В метод передаётся указатель на контрольный ордер, и при наличии разницы метод возвращает тип изменения, который записывается в переменную-член класса m_change_type.
Далее проверяем статус проверяемого ордера и задаём значение, выше которого будет считаться, что изменение произошло. Для позиции — это значение должно быть больше значения константы CHANGE_TYPE_ORDER_TAKE_PROFIT из перечисления ENUM_CHANGE_TYPE так как все значения, которые равны и меньше значения этой константы, относятся только к отложенному ордеру. Для отложенного ордера значение должно быть больше значения константы CHANGE_TYPE_NO_CHANGE.
Если полученное значение переменной m_change_type больше заданного — значит есть факт модификации, и необходимо сначала сохранить текущее состояние контрольного ордера для последующей проверки, и разместить копию контрольного ордера в список изменённых ордеров для последующей обработки этого списка в классе CEventsCollection.
Метод, создающий изменённый контрольный ордер и добавляющий его в список изменённых ордеров:
//+------------------------------------------------------------------+ //|Создаёт и добавляет контрольный ордер в список изменённых ордеров | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListChanges(COrderControl* order_control) { if(order_control==NULL) return false; COrderControl* order_changed=new COrderControl(order_control.PositionID(),order_control.Ticket(),order_control.Magic(),order_control.Symbol()); if(order_changed==NULL) return false; order_changed.SetTime(order_control.Time()); order_changed.SetTimePrev(order_control.TimePrev()); order_changed.SetVolume(order_control.Volume()); order_changed.SetTypeOrder(order_control.TypeOrder()); order_changed.SetTypeOrderPrev(order_control.TypeOrderPrev()); order_changed.SetPrice(order_control.Price()); order_changed.SetPricePrev(order_control.PricePrev()); order_changed.SetStopLoss(order_control.StopLoss()); order_changed.SetStopLossPrev(order_control.StopLossPrev()); order_changed.SetTakeProfit(order_control.TakeProfit()); order_changed.SetTakeProfitPrev(order_control.TakeProfitPrev()); order_changed.SetChangedType(order_control.GetChangeType()); if(!this.m_list_changed.Add(order_changed)) { delete order_changed; return false; } return true; } //+------------------------------------------------------------------+
В метод передаётся указатель на контрольный ордер, который был модифицирован, и копию которого необходимо разместить в списке изменённых контрольных ордеров и позиций.
Затем создаётся новый контрольный ордер — при создании ему сразу же присваиваются идентификатор позиции, тикет, магик и символ, такие же, как и у изменённого контрольного ордера.
Далее — простое поэлементное копирование свойств изменённого контрольного ордера в свойства вновь созданного.
И наконец — размещаем вновь созданную копию изменённого контрольного ордера в список изменённых ордеров.
Если не удалось разместить вновь созданный ордер в список, то вновь созданный объект-ордер удаляется и возвращается false.
На этом необходимые изменения класса CMarketCollection завершены, очередь за классом CEventsCollection.
В класс-коллекцию событий CEventsCollection нам необходимо добавить обработку ситуации, когда список изменённых ордеров, созданный в классе-коллекции рыночных ордеров и позиций, не пустой, а значит — в нём содержатся изменённые ордера и позиции, и их требуется обработать — создать новое событие и отослать в вызывающую программу об этом сообщение.
В приватную секцию класса к уже существующему методу добавим определение двух методов: нового перегруженного метода создания нового события и метода-обработчика события изменения существующего ордера/позиции, а в метод Refresh() в его параметры добавим передачу в метод списка изменённых ордеров:
//+------------------------------------------------------------------+ //| EventsCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\EventBalanceOperation.mqh" #include "..\Objects\Events\EventOrderPlaced.mqh" #include "..\Objects\Events\EventOrderRemoved.mqh" #include "..\Objects\Events\EventPositionOpen.mqh" #include "..\Objects\Events\EventPositionClose.mqh" //+------------------------------------------------------------------+ //| Коллекция событий счёта | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // Список событий bool m_is_hedge; // Флаг хедж-счёта long m_chart_id; // Идентификатор графика управляющей программы int m_trade_event_code; // Код торгового события ENUM_TRADE_EVENT m_trade_event; // Торговое событие на счёте CEvent m_event_instance; // Объект-событие для поиска по свойству //--- Создаёт торговое событие (1) в зависимости от статуса и (2) типа изменения ордера void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market); void CreateNewEvent(COrderControl* order); //--- Создаёт событие для (1) хеджевого счёта, (2) неттингового счёта void NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); void NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); //--- Выбирает из списка и возвращает список рыночных отложенных ордеров CArrayObj* GetListMarketPendings(CArrayObj* list); //--- Выбирает из списка и возвращает список исторических (1) удалённых отложенных ордеров, (2) сделок, (3) всех закрывающих ордеров CArrayObj* GetListHistoryPendings(CArrayObj* list); CArrayObj* GetListDeals(CArrayObj* list); CArrayObj* GetListCloseByOrders(CArrayObj* list); //--- Возвращает список (1) всех ордеров позиции по её идентификатору, (2) всех сделок позиции по её идентификатору //--- (3) всех сделок на вход в рынок по идентификатору позиции, (4) всех сделок на выход из рынка по идентификатору позиции, //--- (5) всех сделок на разворот позиции по идентификатору позиции CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id); //--- Возвращает суммарный объём всех сделок (1) IN, (2) OUT позиции по её идентификатору double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id); double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id); //--- Возвращает (1) первый, (2) последний и (3) закрывающий ордер из списка всех ордеров позиции, //--- (4) ордер по тикету, (5) рыночную позицию по идентификатору, //--- (6) последнюю и (7) предпоследнюю сделку InOut по идентификатору позиции COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket); COrder* GetPositionByID(CArrayObj* list,const ulong position_id); //--- Возвращает флаг наличия объекта-события в списке событий bool IsPresentEventInList(CEvent* compared_event); //--- Обработчик события изменения существующего ордера/позиции void OnChangeEvent(CArrayObj* list_changes,CArrayObj* list_history,CArrayObj* list_market,const int index); public: //--- Выбирает события из коллекции со временем в диапазоне от begin_time до end_time CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Возвращает полный список-коллекцию событий "как есть" CArrayObj *GetList(void) { return &this.m_list_events; } //--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } //--- Обновляет список событий void Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals); //--- Устанавливает идентификатор графика управляющей программы void SetChartID(const long id) { this.m_chart_id=id; } //--- Возвращает последнее торговое событие на счёте ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } //--- Сбрасывает последнее торговое событие void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- Конструктор CEventsCollection(void); }; //+------------------------------------------------------------------+
За пределами тела класса напишем реализацию новых методов.
Перегруженный метод создания события модификации ордера или позиции:
//+------------------------------------------------------------------+ //| Создаёт торговое событие в зависимости от типа изменения ордера | //+------------------------------------------------------------------+ void CEventsCollection::CreateNewEvent(COrderControl* order) { CEvent* event=NULL; //--- Установлен отложенный StopLimit-ордер if(order.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED; event=new CEventOrderPlased(this.m_trade_event_code,order.Ticket()); } //--- if(event!=NULL) { event.SetProperty(EVENT_PROP_TIME_EVENT,order.Time()); // Время события event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_STOPLIMIT_TRIGGERED); // Причина события (из перечисления ENUM_EVENT_REASON) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrderPrev()); // Тип ордера, срабатывание которого привело к событию event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Тикет ордера , срабатывание которого привело к событию event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder()); // Тип ордера события event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Тикет ордера события event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder()); // Тип первого ордера позиции event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Тикет первого ордера позиции event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID()); // Идентификатор позиции event.SetProperty(EVENT_PROP_POSITION_BY_ID,0); // Идентификатор встречной позиции event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Магический номер встречной позиции event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrderPrev()); // Тип ордера позиции до смены направления event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Тикет ордера позиции до смены направления event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder()); // Тип ордера текущей позиции event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket()); // Тикет ордера текущей позиции event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic()); // Магический номер ордера event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev()); // Время первого ордера позиции event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev()); // Цена, на которой произошло событие event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price()); // Цена установки ордера event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price()); // Цена закрытия ордера event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // Цена StopLoss ордера event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // Цена TakeProfit ордера event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Запрашиваемый объём ордера event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,0); // Исполненный объём ордера event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.Volume()); // Оставшийся (неисполненный) объём ордера event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,0); // Исполненный объём позиции event.SetProperty(EVENT_PROP_PROFIT,0); // Профит event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Символ ордера event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Символ встречной позиции //--- Установка идентификатора графика управляющей программы, расшифровка кода события и установка типа события event.SetChartID(this.m_chart_id); event.SetTypeEvent(); //--- Если объекта-события нет в списке - добавляем if(!this.IsPresentEventInList(event)) { this.m_list_events.InsertSort(event); //--- Отправляем сообщение о событии и устанавливаем значение последнего торгового события event.SendEvent(); this.m_trade_event=event.TradeEvent(); } //--- Если это событие уже есть в списке - удаляем новый объект-событие и выводим отладочное сообщение else { ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list.")); delete event; } } } //+------------------------------------------------------------------+
Метод создания нового события мы рассматривали в пятой части описания библиотеки при создании коллекции событий.
Данный метод устроен практически идентично. Разница лишь в типе ордера, указатель на который передаётся в метод.
В самом начале метода проверяется тип произошедшего с ордером изменения и в соответствии с типом изменения устанавливается код изменения в переменную-член класса m_trade_event_code.
Далее создаётся соответствующее типу изменения событие, заполняются его свойства в соответствии с типом изменения, событие помещается в список событий и отправляется в управляющую программу.
Доработанный метод обновления списка событий:
//+------------------------------------------------------------------+ //| Обновляет список событий | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) { //--- Если списки пустые - выход if(list_history==NULL || list_market==NULL) return; //--- Если событие в рыночном окружении if(is_market_event) { //--- если было изменение свойств ордера int total_changes=list_changes.Total(); if(total_changes>0) { for(int i=total_changes-1;i>=0;i--) { this.OnChangeEvent(list_changes,i); } } //--- если увеличилось количество установленных отложенных ордеров if(new_market_pendings>0) { //--- Получаем список только установленных отложенных ордеров CArrayObj* list=this.GetListMarketPendings(list_market); if(list!=NULL) { //--- Сортируем новый список по времени установки ордера list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Берём в цикле с конца списка количество ордеров, равное количеству новых установленных ордеров (последние N событий) int total=list.Total(), n=new_market_pendings; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем ордер из списка, и если это отложенный ордер - устанавливаем торговое событие COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING) this.CreateNewEvent(order,list_history,list_market); } } } } //--- Если событие в истории счёта if(is_history_event) { //--- Если увеличилось количество исторических ордеров if(new_history_orders>0) { //--- Получаем список только удалённых отложенных ордеров CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { //--- Сортируем новый список по времени удаления ордера list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние N событий) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем ордер из списка, и если это удалённый отложенный ордер и у него нет идентификатора позиции, //--- то это удаление ордера - устанавливаем торговое событие COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } //--- Если увеличилось количество сделок if(new_deals>0) { //--- Получаем список только сделок CArrayObj* list=this.GetListDeals(list_history); if(list!=NULL) { //--- Сортируем новый список по времени совершения сделки list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Берём в цикле с конца списка количество сделок, равное количеству новых сделок (последние N событий) int total=list.Total(), n=new_deals; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем сделку из списка и устанавливаем торговое событие COrder* order=list.At(i); if(order!=NULL) this.CreateNewEvent(order,list_history,list_market); } } } } } //+------------------------------------------------------------------+
Этот метод так же был рассмотрен нами в пятой части описания библиотеки при создании коллекции событий. Отличием от рассмотренного метода является добавление блока кода обработки событий модификации в случае, если размер списка изменённых ордеров ненулевой: в цикле берём из списка каждый изменённый ордер и обрабатываем его в Методе-обработчике события изменения ордера:
//+------------------------------------------------------------------+ //| Обработчик события изменения существующего ордера/позиции | //+------------------------------------------------------------------+ void CEventsCollection::OnChangeEvent(CArrayObj* list_changes,const int index) { COrderControl* order_changed=list_changes.Detach(index); if(order_changed!=NULL) { if(order_changed.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.CreateNewEvent(order_changed); } delete order_changed; } } //+------------------------------------------------------------------+
При обработке списка изменённых ордеров нам необходимо не только получать из списка модифицированный ордер, но и по завершению обработки очередного ордера удалять как сам объект-ордер, так и указатель на него из списка — чтобы по многу раз не обрабатывать одно и то же событие.
К счастью, в Стандартной библиотеке при работе с динамическим массивом указателей на объекты CArrayObj уже предусмотрена такая возможность — метод Detach(), который получает элемент из указанной позиции с удалением его из массива. Т.е., мы одновременно получаем указатель на объёкт, хранящийся в массиве по индексу, и удаляем этот указатель из массива. Если тип изменения равен CHANGE_TYPE_ORDER_TYPE (изменение типа ордера — срабатывание отложенного StopLimit-ордера и превращению его в Limit-ордер), создаём новое событие — срабатывание StopLimit-ордера. По завершению обработки объекта по указателю, полученному при помощи метода Detach(), сам уазатель (уже не нужный нам) просто удаляем.
На этом доработка класса CEventsCollection завершена.
Чтобы все изменения вступили в силу, необходимо в главном объекте библиотеки — в классе CEngine, в его методе TradeEventsControl() — получать список изменённых ордеров из класса-коллекции рыночных ордеров и позиций, записывать его размер и при вызове метода обновления событий Refresh() класса-коллекции событий, проверять дополнительно размер списка изменённых ордеров, а в метод Refresh() коллекции событий передавать список модифицированных ордеров для его обработки:
//+------------------------------------------------------------------+ //| Проверка торговых событий | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Инициализация кода и флагов торговых событий this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Обновление списков this.m_market.Refresh(); this.m_history.Refresh(); //--- Действия при первом запуске if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Проверка изменений в рыночном состоянии и в истории счёта this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes, this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewMarketOrders(),this.m_history.NewDeals()); //--- Получаем последнее торговое событие на счёте this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Так как срабатывание StopLimit-ордера приводит к выставлению Limit-ордера, то это событие будем "квалифицировать" как выставление отложенного ордера, а причиной события будет активация StopLimit-ордера EVENT_REASON_STOPLIMIT_TRIGGERED, константу которой мы уже прописали в файле Defines.mqh в перечислении ENUM_EVENT_REASON.
Для вывода в журнал и отправки в управляющую программу данного события, доработаем класс EventOrderPlased:
Просто добавим обработку причины события EVENT_REASON_STOPLIMIT_TRIGGERED.
//+------------------------------------------------------------------+ //| EventOrderPlased.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Event.mqh" //+------------------------------------------------------------------+ //| Событие установки отложенного ордера | //+------------------------------------------------------------------+ class CEventOrderPlased : public CEvent { public: //--- Конструктор CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {} //--- Поддерживаемые свойства ордера (1) вещественные, (2) целочисленные virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property); virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property); //--- (1) Выводит в журнал краткое сообщение о событии, (2) Отправляет событие на график virtual void PrintShort(void); virtual void SendEvent(void); }; //+------------------------------------------------------------------+ //| Возвращает истину, если событие поддерживает переданное | //| целочисленное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property) { if(property==EVENT_PROP_TYPE_DEAL_EVENT || property==EVENT_PROP_TICKET_DEAL_EVENT || property==EVENT_PROP_TYPE_ORDER_POSITION || property==EVENT_PROP_TICKET_ORDER_POSITION || property==EVENT_PROP_POSITION_ID || property==EVENT_PROP_POSITION_BY_ID || property==EVENT_PROP_TIME_ORDER_POSITION ) return false; return true; } //+------------------------------------------------------------------+ //| Возвращает истину, если событие поддерживает переданное | //| вещественное свойство, возвращает ложь в противном случае | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { if(property==EVENT_PROP_PRICE_CLOSE || property==EVENT_PROP_PROFIT ) return false; return true; } //+------------------------------------------------------------------+ //| Выводит в журнал краткое сообщение о событии | //+------------------------------------------------------------------+ void CEventOrderPlased::PrintShort(void) { int digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),digits) : ""); string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),digits) : ""); string vol=::DoubleToString(this.VolumeOrderInitial(),DigitsLots(this.Symbol())); string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : ""); string type=this.TypeOrderFirstDescription()+" #"+(string)this.TicketOrderEvent(); string event=TextByLanguage(" Установлен "," Placed "); string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),digits); string txt=head+this.Symbol()+event+vol+" "+type+price+sl+tp+magic; //--- Если сработал StopLimit-ордер if(this.Reason()==EVENT_REASON_STOPLIMIT_TRIGGERED) { head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimeEvent())+" -\n"; event=TextByLanguage(" Сработал "," Triggered "); type= ( OrderTypeDescription(this.TypeOrderPosPrevious())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" по цене "," at price ")+DoubleToString(this.PriceEvent(),digits)+" -->\n"+ vol+" "+OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" на цену "," on price ")+DoubleToString(this.PriceOpen(),digits) ); txt=head+this.Symbol()+event+"("+TimeMSCtoString(this.TimePosition())+") "+vol+" "+type+sl+tp+magic; } ::Print(txt); } //+------------------------------------------------------------------+ //| Отправляет событие на график | //+------------------------------------------------------------------+ void CEventOrderPlased::SendEvent(void) { this.PrintShort(); ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol()); } //+------------------------------------------------------------------+
Тут всё достаточно просто и понятно — не будем останавляваться на описании простых действий.
На этом доработка библиотеки для отслеживания срабатывания StopLimit-ордеров завершена.
Тест
Для проверки проведённых доработок, воспользуемя советником из прошлой статьи. Просто переименуем советник TestDoEasyPart06.mq5 из папки \MQL5\Experts\TestDoEasy\Part06 в TestDoEasyPart07.mq5 и сохраним его в новой подпапке \MQL5\Experts\TestDoEasy\Part07.
Скомпилируем, запустим в тестере, выставим StopLimit-ордер и дождёмся его срабатывания:
Что дальше
Реализованный в данной статье функционал, как несложно догадаться, включает в себя возможность быстрого добавления отслеживания иных событий — модификацию свойств отложенных ордеров — их цены установки и уровней StopLoss и TakeProfit, и модификацию уровней StopLoss и TakeProfit у позиций. Что и будем делать в следующей статье.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового
советника. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.
Статьи этой серии:
Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
...без поиска требуемых решений где-то кем-то написанных и опубликованных.
Это значит, что библиотека станет штатной?
Нет. Это означает, что одной библиотеки будет вполне достаточно.
Артем, спасибо!
В методе:
Создается динамически "маркет-ордер"
Потом по ссылке передается в метод :
В методе "AddToListMarket" маркет-ордер не учитывается в хеш_сумме "hash_sum" тогда зачем его заносить и контролировать?
Объясните пожалуйста зачем он нам, если мы всю информацию можем узнать у позиции или отложенного-ордера?
Артем, спасибо!
В методе:
Создается динамически "маркет-ордер"
Потом по ссылке передается в метод :
В методе "AddToListMarket" маркет-ордер не учитывается в хеш_сумме "hash_sum" тогда зачем его заносить и контролировать?
Объясните пожалуйста зачем он нам, если мы всю информацию можем узнать у позиции или отложенного-ордера?
Маркет-ордера - это приказы на установку либо рыночного, либо отложенного ордера. Для текущих потребностей библиотеки (отслеживание изменений) они не нужны, но они присутствуют в списке объектов библиотеки для быстрого их поиска вручную. Например, для определения проскальзываний или задержек при исполнении.