Библиотека для простого и быстрого создания программ для MetaTrader (Часть VIII): События модификации ордеров и позиций
Содержание
В прошлой статье мы реализовали отслеживание событий срабатывания StopLimit-ордеров. Как несложно было догадаться, весь функционал, используемый для определения этих событий, мы сделали расширяемым — чтобы на его основе можно было легко дописать и поиск остальных необходимых нам событий.
Событие срабатывания StopLimit-ордера мы "квалифицировали" как установку нового отложенного ордера, что в принципе логично — новый тип ордера — новое событие его размещения. В данной статье мы будем отслеживать события совсем иного плана — модификацию уже существующих ордеров и позиций, о выставлении и открытии которых мы уже получали события в программе. А это значит, что нам необходимо создать ещё один класс-наследник от класса абстрактного события CEvent — класс события-модификации.
Класс события модификации
Начнём как обычно — с подготовки требуемых констант перечислений.
Откроем файл библиотеки Defines.mqh, и в целочисленных свойствах ордера — в макроподстановке, содержащей количество неиспользуемых целочисленных свойств ордера при сортировке по этим свойствам, впишем количество пропускаемых свойств равным нулю — все целочисленные свойства ордера нам в дальнейшем потребуются. Ранее у нас при поиске и сортировке пропускалось одно свойство — последнее из списка констант перечисления: ORDER_PROP_DIRECTION — тип ордера по его направлению (помним, что все неиспользуемые свойства мы располагаем в конце списка констант перечисления). В данной статье и далее нам это свойство потребуется для поиска всех однонаправленных отложенных ордеров в списке рыночной коллекции.
//+------------------------------------------------------------------+ //| Целочисленные свойства ордера, сделки, позиции | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = 0, // Тикет ордера ORDER_PROP_MAGIC, // Мэджик ордера ORDER_PROP_TIME_OPEN, // Время открытия (MQL5 Время сделки) ORDER_PROP_TIME_CLOSE, // Время закрытия (MQL5 Время исполнения или снятия - ORDER_TIME_DONE) ORDER_PROP_TIME_OPEN_MSC, // Время открытия в милисекундах (MQL5 Время сделки в мсек.) ORDER_PROP_TIME_CLOSE_MSC, // Время закрытия в милисекундах (MQL5 Время исполнения или снятия - ORDER_TIME_DONE_MSC) ORDER_PROP_TIME_EXP, // Дата экспирации ордера (для отложенных ордеров) ORDER_PROP_STATUS, // Статус ордера (из перечисления ENUM_ORDER_STATUS) ORDER_PROP_TYPE, // Тип ордера/сделки ORDER_PROP_REASON, // Причина или источник сделки/ордера/позиции ORDER_PROP_STATE, // Состояние ордера (из перечисления ENUM_ORDER_STATE) ORDER_PROP_POSITION_ID, // Идентификатор позиции ORDER_PROP_POSITION_BY_ID, // Идентификатор встречной позиции ORDER_PROP_DEAL_ORDER_TICKET, // Тикет ордера, на основании которого выполнена сделка ORDER_PROP_DEAL_ENTRY, // Направление сделки – IN, OUT или IN/OUT ORDER_PROP_TIME_UPDATE, // Время изменения позиции в секундах ORDER_PROP_TIME_UPDATE_MSC, // Время изменения позиции в милисекундах ORDER_PROP_TICKET_FROM, // Тикет родительского ордера ORDER_PROP_TICKET_TO, // Тикет дочернего ордера ORDER_PROP_PROFIT_PT, // Профит в пунктах ORDER_PROP_CLOSE_BY_SL, // Признак закрытия по StopLoss ORDER_PROP_CLOSE_BY_TP, // Признак закрытия по TakeProfit ORDER_PROP_GROUP_ID, // Идентификатор группы ордеров/позиций ORDER_PROP_DIRECTION, // Тип по направлению (Buy, Sell) }; #define ORDER_PROP_INTEGER_TOTAL (24) // Общее количество целочисленных свойств #define ORDER_PROP_INTEGER_SKIP (0) // Количество неиспользуемых в сортировке свойств ордера //+------------------------------------------------------------------+
Ну и раз мы убрали пропуск свойства, то необходимо добавить и возможность сортировки по этому свойству, впишем критерий сортировки по направлению ордера в перечисление возможных критериев сортировки ордеров и сделок:
//+------------------------------------------------------------------+ //| Возможные критерии сортировки ордеров и сделок | //+------------------------------------------------------------------+ #define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) #define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { //--- Сортировка по целочисленным свойствам SORT_BY_ORDER_TICKET = 0, // Сортировать по тикету ордера SORT_BY_ORDER_MAGIC = 1, // Сортировать по магику ордера SORT_BY_ORDER_TIME_OPEN = 2, // Сортировать по времени открытия ордера SORT_BY_ORDER_TIME_CLOSE = 3, // Сортировать по времени закрытия ордера SORT_BY_ORDER_TIME_OPEN_MSC = 4, // Сортировать по времени открытия ордера в милисекундах SORT_BY_ORDER_TIME_CLOSE_MSC = 5, // Сортировать по времени закрытия ордера в милисекундах SORT_BY_ORDER_TIME_EXP = 6, // Сортировать по дате экспирации ордера SORT_BY_ORDER_STATUS = 7, // Сортировать по статусу ордера (маркет-ордер/отложенный ордер/сделка/балансная,кредитная операция) SORT_BY_ORDER_TYPE = 8, // Сортировать по типу ордера SORT_BY_ORDER_REASON = 9, // Сортировать по причине/источнику сделки/ордера/позиции SORT_BY_ORDER_STATE = 10, // Сортировать по состоянию ордера SORT_BY_ORDER_POSITION_ID = 11, // Сортировать по идентификатору позиции SORT_BY_ORDER_POSITION_BY_ID = 12, // Сортировать по идентификатору встречной позиции SORT_BY_ORDER_DEAL_ORDER = 13, // Сортировать по ордеру, на основание которого выполнена сделка SORT_BY_ORDER_DEAL_ENTRY = 14, // Сортировать по направлению сделки – IN, OUT или IN/OUT SORT_BY_ORDER_TIME_UPDATE = 15, // Сортировать по времени изменения позиции в секундах SORT_BY_ORDER_TIME_UPDATE_MSC = 16, // Сортировать по времени изменения позиции в милисекундах SORT_BY_ORDER_TICKET_FROM = 17, // Сортировать по тикету родительского ордера SORT_BY_ORDER_TICKET_TO = 18, // Сортировать по тикету дочернего ордера SORT_BY_ORDER_PROFIT_PT = 19, // Сортировать по профиту ордера в пунктах SORT_BY_ORDER_CLOSE_BY_SL = 20, // Сортировать по признаку закрытия ордера по StopLoss SORT_BY_ORDER_CLOSE_BY_TP = 21, // Сортировать по признаку закрытия ордера по TakeProfit SORT_BY_ORDER_GROUP_ID = 22, // Сортировать по идентификатору группы ордеров/позиций SORT_BY_ORDER_DIRECTION = 23, // Сортировать по направлению (Buy, Sell) //--- Сортировка по вещественным свойствам SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Сортировать по цене открытия SORT_BY_ORDER_PRICE_CLOSE = FIRST_ORD_DBL_PROP+1, // Сортировать по цене закрытия SORT_BY_ORDER_SL = FIRST_ORD_DBL_PROP+2, // Сортировать по цене StopLoss SORT_BY_ORDER_TP = FIRST_ORD_DBL_PROP+3, // Сортировать по цене TaleProfit SORT_BY_ORDER_PROFIT = FIRST_ORD_DBL_PROP+4, // Сортировать по профиту SORT_BY_ORDER_COMMISSION = FIRST_ORD_DBL_PROP+5, // Сортировать по комиссии SORT_BY_ORDER_SWAP = FIRST_ORD_DBL_PROP+6, // Сортировать по свопу SORT_BY_ORDER_VOLUME = FIRST_ORD_DBL_PROP+7, // Сортировать по объёму SORT_BY_ORDER_VOLUME_CURRENT = FIRST_ORD_DBL_PROP+8, // Сортировать по невыполненному объему SORT_BY_ORDER_PROFIT_FULL = FIRST_ORD_DBL_PROP+9, // Сортировать по критерию профит+комиссия+своп SORT_BY_ORDER_PRICE_STOP_LIMIT= FIRST_ORD_DBL_PROP+10, // Сортировать по цене постановки Limit ордера при срабатывании StopLimit ордера //--- Сортировка по строковым свойствам SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Сортировать по символу SORT_BY_ORDER_COMMENT = FIRST_ORD_STR_PROP+1, // Сортировать по комментарию SORT_BY_ORDER_EXT_ID = FIRST_ORD_STR_PROP+2 // Сортировать по идентификатору ордера во внешней торговой системе }; //+------------------------------------------------------------------+
Для создания идентификатора события, мы, как вы помните, используем код события, состоящий в свою очередь из набора флагов, которые в совокупности и указывают на тип произошедшего события. Так как мы будем отслеживать события модификации, то нам необходимо добавить необходимые флаги в перечисление флагов торгового события:
//+------------------------------------------------------------------+ //| Список флагов торговых событий на счёте | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT_FLAGS { TRADE_EVENT_FLAG_NO_EVENT = 0, // Нет события TRADE_EVENT_FLAG_ORDER_PLASED = 1, // Отложенный ордер установлен TRADE_EVENT_FLAG_ORDER_REMOVED = 2, // Отложенный ордер удалён TRADE_EVENT_FLAG_ORDER_ACTIVATED = 4, // Отложенный ордер активирован ценой TRADE_EVENT_FLAG_POSITION_OPENED = 8, // Позиция открыта TRADE_EVENT_FLAG_POSITION_CHANGED= 16, // Позиция изменена TRADE_EVENT_FLAG_POSITION_REVERSE= 32, // Разворот позиции TRADE_EVENT_FLAG_POSITION_CLOSED = 64, // Позиция закрыта TRADE_EVENT_FLAG_ACCOUNT_BALANCE = 128, // Балансная операция (уточнение в типе сделки) TRADE_EVENT_FLAG_PARTIAL = 256, // Частичное исполнение TRADE_EVENT_FLAG_BY_POS = 512, // Исполнение встречной позицией TRADE_EVENT_FLAG_PRICE = 1024, // Модификация цены установки TRADE_EVENT_FLAG_SL = 2048, // Исполнение по StopLoss TRADE_EVENT_FLAG_TP = 4096, // Исполнение по TakeProfit TRADE_EVENT_FLAG_ORDER_MODIFY = 8192, // Модификация ордера TRADE_EVENT_FLAG_POSITION_MODIFY = 16384, // Модификация позиции }; //+------------------------------------------------------------------+
В список возможных торговых событий на счёте добавим события модификации ордеров и позиций (ранее мы уже добавляли некоторые события в список, но это было предварительное объявление констант):
//+------------------------------------------------------------------+ //| Список возможных торговых событий на счёте | //+------------------------------------------------------------------+ 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_ORDER_STOP_LOSS, // Изменение StopLoss ордера TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT, // Изменение TakeProfit ордера TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT, // Изменение StopLoss и TakeProfit позиции TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, // Изменение StopLoss позиции TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, // Изменение TakeProfit позиции }; //+------------------------------------------------------------------+
Так как сегодня будем делать новый класс-наследник абстрактного класса-события CEvent, то нам потребуется задать ещё один статус события — "модификация" для нового класса-наследника. Впишем его в список перечисления статусов событий:
//+------------------------------------------------------------------+ //| Статус события | //+------------------------------------------------------------------+ enum ENUM_EVENT_STATUS { EVENT_STATUS_MARKET_POSITION, // Событие рыночной позиции (открытие, частичное открытие, частичное закрытие, добавление объёма, разворот) EVENT_STATUS_MARKET_PENDING, // Событие рыночного отложенного ордера (установка) EVENT_STATUS_HISTORY_PENDING, // Событие исторического отложенного ордера (удаление) EVENT_STATUS_HISTORY_POSITION, // Событие исторической позиции (закрытие) EVENT_STATUS_BALANCE, // Событие балансной операции (начисление баланса, снятие средств и события из перечисления ENUM_DEAL_TYPE) EVENT_STATUS_MODIFY // Событие модификации ордера/позиции }; //+------------------------------------------------------------------+
И дополним список перечисления причин событий причиной события "модификация":
//+------------------------------------------------------------------+ //| Причина события | //+------------------------------------------------------------------+ 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_MODIFY, // Модификация 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) //+------------------------------------------------------------------+
Чтобы нам в любой момент знать, что у нас изменилось в свойствах ордера или позиции, в вещественные свойства события добавим цены свойств ордера или позиции до модификации (цены после модификации будут браться из уже существующих свойств), свойства для записи текущих цен на момент события, а также изменим значения количества вещественных свойств события с 10 на 15 и добавим количество неиспользуемых свойств при поиске и сортировке (данные о модификации и цены на момент события модификации не будем использовать для поиска и сортировки):
//+------------------------------------------------------------------+ //| Вещественные свойства события | //+------------------------------------------------------------------+ enum ENUM_EVENT_PROP_DOUBLE { EVENT_PROP_PRICE_EVENT = EVENT_PROP_INTEGER_TOTAL, // Цена, на которой произошло событие EVENT_PROP_PRICE_OPEN, // Цена открытия ордера/сделки/позиции EVENT_PROP_PRICE_CLOSE, // Цена закрытия ордера/сделки/позиции EVENT_PROP_PRICE_SL, // Цена StopLoss ордера/сделки/позиции EVENT_PROP_PRICE_TP, // Цена TakeProfit ордера/сделки/позиции EVENT_PROP_VOLUME_ORDER_INITIAL, // Запрашиваемый объём ордера EVENT_PROP_VOLUME_ORDER_EXECUTED, // Исполненный объём ордера EVENT_PROP_VOLUME_ORDER_CURRENT, // Оставшийся объём ордера EVENT_PROP_VOLUME_POSITION_EXECUTED, // Текущий исполненный объём позиции после сделки EVENT_PROP_PROFIT, // Профит //--- EVENT_PROP_PRICE_OPEN_BEFORE, // Цена установки ордера до модификации EVENT_PROP_PRICE_SL_BEFORE, // Цена StopLoss до модификации EVENT_PROP_PRICE_TP_BEFORE, // Цена TakeProfit до модификации EVENT_PROP_PRICE_EVENT_ASK, // Цена Ask в момент события EVENT_PROP_PRICE_EVENT_BID, // Цена Bid в момент события }; #define EVENT_PROP_DOUBLE_TOTAL (15) // Общее количество вещественных свойств события #define EVENT_PROP_DOUBLE_SKIP (5) // Количество неиспользуемых в сортировке свойств события //+------------------------------------------------------------------+
Для верного расчёта индекса первого строкового свойства события в перечислении критериев сортировки событий, изменим расчёт значения соответствующей макроподстановки:
//+------------------------------------------------------------------+ //| Возможные критерии сортировки событий | //+------------------------------------------------------------------+ #define FIRST_EVN_DBL_PROP (EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_INTEGER_SKIP) #define FIRST_EVN_STR_PROP (EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_INTEGER_SKIP+EVENT_PROP_DOUBLE_TOTAL-EVENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_EVENTS_MODE { //--- Сортировка по целочисленным свойствам SORT_BY_EVENT_TYPE_EVENT = 0, // Сортировать по типу события SORT_BY_EVENT_TIME_EVENT = 1, // Сортировать по времени события SORT_BY_EVENT_STATUS_EVENT = 2, // Сортировать по статусу события (из перечисления ENUM_EVENT_STATUS) SORT_BY_EVENT_REASON_EVENT = 3, // Сортировать по причине события (из перечисления ENUM_EVENT_REASON) SORT_BY_EVENT_TYPE_DEAL_EVENT = 4, // Сортировать по типу сделки события SORT_BY_EVENT_TICKET_DEAL_EVENT = 5, // Сортировать по тикету сделки события SORT_BY_EVENT_TYPE_ORDER_EVENT = 6, // Сортировать по типу ордера, на основании которого открыта сделка события (последний ордер позиции) SORT_BY_EVENT_TICKET_ORDER_EVENT = 7, // Сортировать по тикету ордера, на основании которого открыта сделка события (последний ордер позиции) SORT_BY_EVENT_TIME_ORDER_POSITION = 8, // Сортировать по времени ордера, на основании которого открыта сделка позиции (первый ордер позиции) SORT_BY_EVENT_TYPE_ORDER_POSITION = 9, // Сортировать по типу ордера, на основании которого открыта сделка позиции (первый ордер позиции) SORT_BY_EVENT_TICKET_ORDER_POSITION = 10, // Сортировать по тикету ордера, на основании которого открыта сделка позиции (первый ордер позиции) SORT_BY_EVENT_POSITION_ID = 11, // Сортировать по идентификатору позиции SORT_BY_EVENT_POSITION_BY_ID = 12, // Сортировать по идентификатору встречной позиции SORT_BY_EVENT_MAGIC_ORDER = 13, // Сортировать по магическому номеру ордера/сделки/позиции SORT_BY_EVENT_MAGIC_BY_ID = 14, // Сортировать по магическому номеру встречной позиции //--- Сортировка по вещественным свойствам SORT_BY_EVENT_PRICE_EVENT = FIRST_EVN_DBL_PROP, // Сортировать по цене, на которой произошло событие SORT_BY_EVENT_PRICE_OPEN = FIRST_EVN_DBL_PROP+1, // Сортировать по цене открытия позиции SORT_BY_EVENT_PRICE_CLOSE = FIRST_EVN_DBL_PROP+2, // Сортировать по цене закрытия позиции SORT_BY_EVENT_PRICE_SL = FIRST_EVN_DBL_PROP+3, // Сортировать по цене StopLoss позиции SORT_BY_EVENT_PRICE_TP = FIRST_EVN_DBL_PROP+4, // Сортировать по цене TakeProfit позиции SORT_BY_EVENT_VOLUME_ORDER_INITIAL = FIRST_EVN_DBL_PROP+5, // Сортировать по первоначальному объёму SORT_BY_EVENT_VOLUME_ORDER_EXECUTED = FIRST_EVN_DBL_PROP+6, // Сортировать по текущему объёму SORT_BY_EVENT_VOLUME_ORDER_CURRENT = FIRST_EVN_DBL_PROP+7, // Сортировать по оставшемуся объёму SORT_BY_EVENT_VOLUME_POSITION_EXECUTED = FIRST_EVN_DBL_PROP+8, // Сортировать по оставшемуся объёму SORT_BY_EVENT_PROFIT = FIRST_EVN_DBL_PROP+9, // Сортировать по профиту //--- Сортировка по строковым свойствам SORT_BY_EVENT_SYMBOL = FIRST_EVN_STR_PROP, // Сортировать по символу ордера/позици/сделки SORT_BY_EVENT_SYMBOL_BY_ID // Сортировать по символу встречной позици }; //+------------------------------------------------------------------+
Доработаем класс абстрактного события CEvent.
Так как в классах-наследниках абстрактного события CEvent мы выводим информацию в журнал, то требуется знать количество цифр после запятой в котировке символа, на котором произошло событие — Digits() символа. Чтобы не получать его каждый раз в каждом из наследников, просто получим его единожды в родительском классе.
В приватной секции класса объявим переменную-член класса для хранения значения Digits() символа события, а в конструкторе класса, в его списке инициализации инииализируем эту переменную:
//+------------------------------------------------------------------+ //| Класс абстрактного события | //+------------------------------------------------------------------+ class CEvent : public CObject { private: int m_event_code; // Код события //--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство события int IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; } protected: ENUM_TRADE_EVENT m_trade_event; // Торговое событие bool m_is_hedge; // Флаг хедж-счёта long m_chart_id; // Идентификатор графика управляющей программы int m_digits; // Digits() символа int m_digits_acc; // Количество знаков после запятой для валюты счета long m_long_prop[EVENT_PROP_INTEGER_TOTAL]; // Целочисленные свойства события double m_double_prop[EVENT_PROP_DOUBLE_TOTAL]; // Вещественные свойства события string m_string_prop[EVENT_PROP_STRING_TOTAL]; // Строковые свойства события //--- возвращает факт наличия флага в торговом событии bool IsPresentEventFlag(const int event_code) const { return (this.m_event_code & event_code)==event_code; } //--- Защищённый параметрический конструктор CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket); public: //--- Конструктор по умолчанию CEvent(void){;}
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0) { this.m_long_prop[EVENT_PROP_STATUS_EVENT] = event_status; this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] = (long)ticket; this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS); this.m_chart_id=::ChartID(); } //+------------------------------------------------------------------+
В методы, возвращающие описания целочисленных и вещественных свойств, добавим описания новых свойств события:
//+------------------------------------------------------------------+ //| Возвращает описание целочисленного свойства события | //+------------------------------------------------------------------+ string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property) { return ( property==EVENT_PROP_TYPE_EVENT ? TextByLanguage("Тип события","Event's type")+": "+this.TypeEventDescription() : property==EVENT_PROP_TIME_EVENT ? TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property)) : property==EVENT_PROP_STATUS_EVENT ? TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\"" : property==EVENT_PROP_REASON_EVENT ? TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription() : property==EVENT_PROP_TYPE_DEAL_EVENT ? TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_DEAL_EVENT ? TextByLanguage("Тикет сделки","Deal's ticket")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_TYPE_ORDER_EVENT ? TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TYPE_ORDER_POSITION ? TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_ORDER_POSITION ? TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_TICKET_ORDER_EVENT ? TextByLanguage("Тикет ордера события","Event's order ticket")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_POSITION_ID ? TextByLanguage("Идентификатор позиции","Position ID")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_POSITION_BY_ID ? TextByLanguage("Идентификатор встречной позиции","Opposite position's ID")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_MAGIC_ORDER ? TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property) : property==EVENT_PROP_MAGIC_BY_ID ? TextByLanguage("Магический номер встречной позиции","Magic number of opposite position")+": "+(string)this.GetProperty(property) : property==EVENT_PROP_TIME_ORDER_POSITION ? TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(property)) : property==EVENT_PROP_TYPE_ORD_POS_BEFORE ? TextByLanguage("Тип ордера позиции до смены направления","Type order of position before changing direction")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_ORD_POS_BEFORE ? TextByLanguage("Тикет ордера позиции до смены направления","Ticket order of position before changing direction")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_TYPE_ORD_POS_CURRENT ? TextByLanguage("Тип ордера текущей позиции","Type order of current position")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_ORD_POS_CURRENT ? TextByLanguage("Тикет ордера текущей позиции","Ticket order of current position")+": #"+(string)this.GetProperty(property) : EnumToString(property) ); } //+------------------------------------------------------------------+ //| Возвращает описание вещественного свойства события | //+------------------------------------------------------------------+ string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property) { int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS); int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL)); return ( property==EVENT_PROP_PRICE_EVENT ? TextByLanguage("Цена на момент события","Price at the time of the event")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_OPEN ? TextByLanguage("Цена открытия","Price open")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_CLOSE ? TextByLanguage("Цена закрытия","Price close")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_SL ? TextByLanguage("Цена StopLoss","Price StopLoss")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_TP ? TextByLanguage("Цена TakeProfit","Price TakeProfit")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_VOLUME_ORDER_INITIAL ? TextByLanguage("Начальный объём ордера","Order initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_VOLUME_ORDER_EXECUTED ? TextByLanguage("Исполненный объём ордера","Order executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_VOLUME_ORDER_CURRENT ? TextByLanguage("Оставшийся объём ордера","Order remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_VOLUME_POSITION_EXECUTED ? TextByLanguage("Текущий объём позиции","Position current volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_PROFIT ? TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc) : property==EVENT_PROP_PRICE_OPEN_BEFORE ? TextByLanguage("Цена открытия до модификации","Price open before modification")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_SL_BEFORE ? TextByLanguage("Цена StopLoss до модификации","Price StopLoss before modification")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_TP_BEFORE ? TextByLanguage("Цена TakeProfit до модификации","Price TakeProfit before modification")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_EVENT_ASK ? TextByLanguage("Цена Ask в момент события","Price Ask at the time of the event")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_EVENT_BID ? TextByLanguage("Цена Bid в момент события","Price Bid at the time of the event")+": "+::DoubleToString(this.GetProperty(property),dg) : EnumToString(property) ); } //+------------------------------------------------------------------+
В метод, возвращающий наименования торговых событий, добавим описание отсутствия события, описания вновь добавленных событий и описание неизвестного события:
//+------------------------------------------------------------------+ //| Возвращает наименование торгового события | //+------------------------------------------------------------------+ string CEvent::TypeEventDescription(void) const { ENUM_TRADE_EVENT event=this.TypeEvent(); return ( event==TRADE_EVENT_NO_EVENT ? TextByLanguage("Нет торгового события","No trade event") : event==TRADE_EVENT_PENDING_ORDER_PLASED ? TextByLanguage("Отложенный ордер установлен","Pending order placed") : event==TRADE_EVENT_PENDING_ORDER_REMOVED ? TextByLanguage("Отложенный ордер удалён","Pending order removed") : event==TRADE_EVENT_ACCOUNT_CREDIT ? TextByLanguage("Начисление кредита","Credit") : event==TRADE_EVENT_ACCOUNT_CHARGE ? TextByLanguage("Дополнительные сборы","Additional charge") : event==TRADE_EVENT_ACCOUNT_CORRECTION ? TextByLanguage("Корректирующая запись","Correction") : event==TRADE_EVENT_ACCOUNT_BONUS ? TextByLanguage("Перечисление бонусов","Bonus") : event==TRADE_EVENT_ACCOUNT_COMISSION ? TextByLanguage("Дополнительные комиссии","Additional commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY ? TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY ? TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY ? TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY ? TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") : event==TRADE_EVENT_ACCOUNT_INTEREST ? TextByLanguage("Начисления процентов на свободные средства","Interest rate") : event==TRADE_EVENT_BUY_CANCELLED ? TextByLanguage("Отмененная сделка покупки","Canceled buy deal") : event==TRADE_EVENT_SELL_CANCELLED ? TextByLanguage("Отмененная сделка продажи","Canceled sell deal") : event==TRADE_EVENT_DIVIDENT ? TextByLanguage("Начисление дивиденда","Dividend operations") : event==TRADE_EVENT_DIVIDENT_FRANKED ? TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") : event==TRADE_EVENT_TAX ? TextByLanguage("Начисление налога","Tax charges") : event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL ? TextByLanguage("Пополнение средств на балансе","Balance refill") : event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL ? TextByLanguage("Снятие средств с баланса","Withdrawals") : event==TRADE_EVENT_PENDING_ORDER_ACTIVATED ? TextByLanguage("Отложенный ордер активирован ценой","Pending order activated") : event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL ? TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially") : event==TRADE_EVENT_POSITION_OPENED ? TextByLanguage("Позиция открыта","Position is open") : event==TRADE_EVENT_POSITION_OPENED_PARTIAL ? TextByLanguage("Позиция открыта частично","Position is open partially") : event==TRADE_EVENT_POSITION_CLOSED ? TextByLanguage("Позиция закрыта","Position closed") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL ? TextByLanguage("Позиция закрыта частично","Position closed partially") : event==TRADE_EVENT_POSITION_CLOSED_BY_POS ? TextByLanguage("Позиция закрыта встречной","Position closed by opposite position") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS ? TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") : event==TRADE_EVENT_POSITION_CLOSED_BY_SL ? TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss") : event==TRADE_EVENT_POSITION_CLOSED_BY_TP ? TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL ? TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP ? TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit") : event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET ? TextByLanguage("Разворот позиции по рыночному запросу","Position reversal by market request") : event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING ? TextByLanguage("Разворот позиции срабатыванием отложенного ордера","Position reversal by triggered a pending order") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET ? TextByLanguage("Добавлен объём к позиции по рыночному запросу","Added volume to position by market request") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to the position by activation of a pending order") : event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL ? TextByLanguage("Разворот позиции частичным исполнением запроса","Position reversal by partially completed of market request") : event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL ? TextByLanguage("Разворот позиции частичным срабатыванием отложенного ордера","Position reversal by partially triggered a pending order") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL ? TextByLanguage("Добавлен объём к позиции частичным исполнением запроса","Added volume to position by partially completed of market request") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to the position by partially triggered a pending order") : event==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER ? TextByLanguage("Сработал StopLimit-ордер","StopLimit order triggered.") : event==TRADE_EVENT_MODIFY_ORDER_PRICE ? TextByLanguage("Модифицирована цена установки ордера ","Modified order price") : event==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS ? TextByLanguage("Модифицированы цена установки и StopLoss ордера","Modified of order price and StopLoss") : event==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT ? TextByLanguage("Модифицированы цена установки и TakeProfit ордера","Modified of order price and TakeProfit") : event==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT ? TextByLanguage("Модифицированы цена установки, StopLoss и TakeProfit ордера","Modified of order price, StopLoss and TakeProfit") : event==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT ? TextByLanguage("Модифицированы цены StopLoss и TakeProfit ордера","Modified of order StopLoss and TakeProfit") : event==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS ? TextByLanguage("Модифицирован StopLoss ордера","Modified order StopLoss") : event==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT ? TextByLanguage("Модифицирован TakeProfit ордера","Modified order TakeProfit") : event==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT ? TextByLanguage("Модифицированы цены StopLoss и TakeProfit позиции","Modified of position StopLoss and TakeProfit") : event==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS ? TextByLanguage("Модифицирован StopLoss позиции","Modified position StopLoss") : event==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT ? TextByLanguage("Модифицирован TakeProfit позиции","Modified position TakeProfit") : EnumToString(event) ); } //+------------------------------------------------------------------+
В метод, возвращающий описание причины события добавим две новые причины:
//+------------------------------------------------------------------+ //| Возвращает наименование причины сделки/ордера/позиции | //+------------------------------------------------------------------+ string CEvent::ReasonDescription(void) const { ENUM_EVENT_REASON reason=this.Reason(); return ( reason==EVENT_REASON_ACTIVATED_PENDING ? TextByLanguage("Активирован отложенный ордер","Pending order activated") : reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY ? TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered") : reason==EVENT_REASON_STOPLIMIT_TRIGGERED ? TextByLanguage("Срабатывание StopLimit-ордера","StopLimit-order triggered") : reason==EVENT_REASON_MODIFY ? TextByLanguage("Модификация","Modified") : reason==EVENT_REASON_CANCEL ? TextByLanguage("Отмена","Canceled") : reason==EVENT_REASON_EXPIRED ? TextByLanguage("Истёк срок действия","Expired") : reason==EVENT_REASON_DONE ? TextByLanguage("Рыночный запрос, выполненный в полном объёме","Fully completed market request") : reason==EVENT_REASON_DONE_PARTIALLY ? TextByLanguage("Выполненный частично рыночный запрос","Partially completed market request") : reason==EVENT_REASON_VOLUME_ADD ? TextByLanguage("Добавлен объём к позиции","Added volume to position") : reason==EVENT_REASON_VOLUME_ADD_PARTIALLY ? TextByLanguage("Добавлен объём к позиции частичным исполнением заявки","Volume added to the position by partially completed request") : reason==EVENT_REASON_VOLUME_ADD_BY_PENDING ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to position by pending order's triggered") : reason==EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY ? TextByLanguage("Добавлен объём к позиции частичной активацией отложенного ордера","Added volume to position by pending order's triggered partially") : reason==EVENT_REASON_REVERSE ? TextByLanguage("Разворот позиции","Position reversal") : reason==EVENT_REASON_REVERSE_PARTIALLY ? TextByLanguage("Разворот позиции частичным исполнением заявки","Position reversal by partially completed of the request") : reason==EVENT_REASON_REVERSE_BY_PENDING ? TextByLanguage("Разворот позиции при срабатывании отложенного ордера","Position reversal on a triggered pending order") : reason==EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY ? TextByLanguage("Разворот позиции при при частичном срабатывании отложенного ордера","Position reversal on a partially triggered pending order") : reason==EVENT_REASON_DONE_SL ? TextByLanguage("Закрытие по StopLoss","Close by StopLoss triggered") : reason==EVENT_REASON_DONE_SL_PARTIALLY ? TextByLanguage("Частичное закрытие по StopLoss","Partially close by StopLoss triggered") : reason==EVENT_REASON_DONE_TP ? TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit triggered") : reason==EVENT_REASON_DONE_TP_PARTIALLY ? TextByLanguage("Частичное закрытие по TakeProfit","Partially close by TakeProfit triggered") : reason==EVENT_REASON_DONE_BY_POS ? TextByLanguage("Закрытие встречной позицией","Closed by opposite position") : reason==EVENT_REASON_DONE_PARTIALLY_BY_POS ? TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position") : reason==EVENT_REASON_DONE_BY_POS_PARTIALLY ? TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") : reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY ? TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position") : reason==EVENT_REASON_BALANCE_REFILL ? TextByLanguage("Пополнение баланса","Balance refill") : reason==EVENT_REASON_BALANCE_WITHDRAWAL ? TextByLanguage("Снятие средств с баланса","Withdrawals from the balance") : reason==EVENT_REASON_ACCOUNT_CREDIT ? TextByLanguage("Начисление кредита","Credit") : reason==EVENT_REASON_ACCOUNT_CHARGE ? TextByLanguage("Дополнительные сборы","Additional charge") : reason==EVENT_REASON_ACCOUNT_CORRECTION ? TextByLanguage("Корректирующая запись","Correction") : reason==EVENT_REASON_ACCOUNT_BONUS ? TextByLanguage("Перечисление бонусов","Bonus") : reason==EVENT_REASON_ACCOUNT_COMISSION ? TextByLanguage("Дополнительные комиссии","Additional commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY ? TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY ? TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY ? TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY ? TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") : reason==EVENT_REASON_ACCOUNT_INTEREST ? TextByLanguage("Начисления процентов на свободные средства","Interest rate") : reason==EVENT_REASON_BUY_CANCELLED ? TextByLanguage("Отмененная сделка покупки","Canceled buy deal") : reason==EVENT_REASON_SELL_CANCELLED ? TextByLanguage("Отмененная сделка продажи","Canceled sell deal") : reason==EVENT_REASON_DIVIDENT ? TextByLanguage("Начисление дивиденда","Dividend operations") : reason==EVENT_REASON_DIVIDENT_FRANKED ? TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") : reason==EVENT_REASON_TAX ? TextByLanguage("Начисление налога","Tax charges") : EnumToString(reason) ); } //+------------------------------------------------------------------+
В публичной секции класса, в разделе упрощённого доступа к свойствам события добавим методы, возвращающие добавленные новые свойства:
//+------------------------------------------------------------------+ //| Методы упрощённого доступа к свойствам объекта-события | //+------------------------------------------------------------------+ //--- Возвращает (1) тип события, (2) время события в милисекундах, (3) статус события, (4) причину события, (5) тип сделки, (6) тикет сделки, //--- (7) тип ордера, на основании которого исполнена сделка, (8) тип открывающего ордера позиции, (9) тикет последнего ордера позиции, //--- (10) тикет первого ордера позиции, (11) идентификатор позиции, (12) идентификатор встречной позиции, (13) магик, (14) магик встречной, (15) время открытия позиции ENUM_TRADE_EVENT TypeEvent(void) const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT); } long TimeEvent(void) const { return this.GetProperty(EVENT_PROP_TIME_EVENT); } ENUM_EVENT_STATUS Status(void) const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT); } ENUM_EVENT_REASON Reason(void) const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT); } ENUM_DEAL_TYPE TypeDeal(void) const { return (ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT); } long TicketDeal(void) const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT); } ENUM_ORDER_TYPE TypeOrderEvent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT); } ENUM_ORDER_TYPE TypeFirstOrderPosition(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION); } long TicketOrderEvent(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT); } long TicketFirstOrderPosition(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION); } long PositionID(void) const { return this.GetProperty(EVENT_PROP_POSITION_ID); } long PositionByID(void) const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID); } long Magic(void) const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER); } long MagicCloseBy(void) const { return this.GetProperty(EVENT_PROP_MAGIC_BY_ID); } long TimePosition(void) const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION); } //--- При смене направления позиции возвращает (1) тип ордера предыдущей позиции, (2) тикет ордера предыдущей позиции //--- (3) тип ордера текущей позиции, (4) тикет ордера текущей позиции //--- (5) тип и (6) тикет позиции до смены направления, (7) тип и (8) тикет позиции после смены направления ENUM_ORDER_TYPE TypeOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE); } long TicketOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); } ENUM_ORDER_TYPE TypeOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT); } long TicketOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);} ENUM_POSITION_TYPE TypePositionPrevious(void) const { return PositionTypeByOrderType(this.TypeOrderPosPrevious()); } ulong TicketPositionPrevious(void) const { return this.TicketOrderPosPrevious(); } ENUM_POSITION_TYPE TypePositionCurrent(void) const { return PositionTypeByOrderType(this.TypeOrderPosCurrent()); } ulong TicketPositionCurrent(void) const { return this.TicketOrderPosCurrent(); } //--- Возвращает (1) цену, на которой произошло событие, (2) цену открытия, (3) цену закрытия, //--- (4) цену StopLoss, (5) цену TakeProfit, (6) профит, (7) Запрашиваемый объём ордера, //--- 8) Исполненный объём ордера, (9) Оставшийся объём ордера, (10) исполненный объём позиции double PriceEvent(void) const { return this.GetProperty(EVENT_PROP_PRICE_EVENT); } double PriceOpen(void) const { return this.GetProperty(EVENT_PROP_PRICE_OPEN); } double PriceClose(void) const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE); } double PriceStopLoss(void) const { return this.GetProperty(EVENT_PROP_PRICE_SL); } double PriceTakeProfit(void) const { return this.GetProperty(EVENT_PROP_PRICE_TP); } double Profit(void) const { return this.GetProperty(EVENT_PROP_PROFIT); } double VolumeOrderInitial(void) const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL); } double VolumeOrderExecuted(void) const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED); } double VolumeOrderCurrent(void) const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT); } double VolumePositionExecuted(void) const { return this.GetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED); } //--- При модификации цен возвращает (1) цену установки ордера, (2) StopLoss, (3) TakeProfit до модификации double PriceOpenBefore(void) const { return this.GetProperty(EVENT_PROP_PRICE_OPEN_BEFORE); } double PriceStopLossBefore(void) const { return this.GetProperty(EVENT_PROP_PRICE_SL_BEFORE); } double PriceTakeProfitBefore(void) const { return this.GetProperty(EVENT_PROP_PRICE_TP_BEFORE); } double PriceEventAsk(void) const { return this.GetProperty(EVENT_PROP_PRICE_EVENT_ASK); } double PriceEventBid(void) const { return this.GetProperty(EVENT_PROP_PRICE_EVENT_BID); } //--- Возвращает (1) символ, (2) символ встречной позиции string Symbol(void) const { return this.GetProperty(EVENT_PROP_SYMBOL); } string SymbolCloseBy(void) const { return this.GetProperty(EVENT_PROP_SYMBOL_BY_ID); } //+------------------------------------------------------------------+
Так как большинство свойств класса заполняются в классе-коллекции событий после создания нового объекта класса-события в методе CreateNewEvent(), а далее устанавливается тип события посредством вызова метода SetTypeEvent() класса CEvent, то установку значения Digits() символа события сделаем в методе SetTypeEvent() класса CEvent наряду с определением событий-модификаций:
//+------------------------------------------------------------------+ //| Расшифровывает код события и устанавливает торговое событие | //+------------------------------------------------------------------+ void CEvent::SetTypeEvent(void) { //--- Установка Digits() символа события this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); //--- Отложенный ордер установлен (проверяем на равенство коду события, так как здесь может быть только один флаг) if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED) { this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED; this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Отложенный ордер удалён (проверяем на равенство коду события, так как здесь может быть только один флаг) if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED) { this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED; this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Модифицирован отложенный ордер if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_MODIFY)) { //--- Если модифицирована цена установки if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_PRICE)) { this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE; //--- Если модифицированы StopLoss и TakeProfit if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT; //--- Если модифицирован StopLoss else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS; //--- Если модифицирован TakeProfit else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT; } //--- Если цена установки не модифицирована else { //--- Если модифицированы StopLoss и TakeProfit if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT; //--- Если модифицирован StopLoss else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_STOP_LOSS; //--- Если модифицирован TakeProfit else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT; } this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Модифицирована позиция if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_MODIFY)) { //--- Если модифицированы StopLoss и TakeProfit if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT; //--- Если модифицирован StopLoss else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_STOP_LOSS; //--- Если модифицирован TakeProfit else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT; this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Позиция открыта (Проверяем наличие множества флагов в коде события) if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED)) { //--- Если это изменение существующей позиции if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CHANGED)) { //--- Если это отложенный ордер активирован ценой if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED)) { //--- Если это разворот позиции if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_REVERSE)) { //--- проверяем флаг частичного открытия и устанавливаем торговое событие //--- "разворот позиции активацией отложенного ордера" или "разворот позиции частичной активацией отложенного ордера" this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_REVERSED_BY_PENDING : TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Если это добавление объёма к позиции else { //--- проверяем флаг частичного открытия и устанавливаем торговое событие //--- "добавлен объём к позиции активацией отложенного ордера" или "добавлен объём к позиции частичной активацией отложенного ордера" this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING : TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //--- Если изменение позиции было осуществлено сделкой ро рынку else { //--- Если это разворот позиции if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_REVERSE)) { //--- проверяем флаг частичного открытия и устанавливаем торговое событие "разворот позиции" или "разворот позиции частичным исполнением" this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_REVERSED_BY_MARKET : TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Если это добавление объёма к позиции else { //--- проверяем флаг частичного открытия и устанавливаем торговое событие "добавлен объём к позиции" или "добавлен объём к позиции частичным исполнением" this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET : TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } } //--- Если это открытие новой позиции else { //--- Если это отложенный ордер активирован ценой if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED)) { //--- проверяем флаг частичного открытия и устанавливаем торговое событие "отложенный ордер активирован" или "отложенный ордер активирован частично" this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- проверяем флаг частичного открытия и устанавливаем торговое событие "Позиция открыта" или "Позиция открыта частично" this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //--- Позиция закрыта (Проверяем наличие множества флагов в коде события) if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED)) { //--- если позиция закрыта по StopLoss if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) { //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта по StopLoss" или "Позиция закрыта по StopLoss частично" this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- если позиция закрыта по TakeProfit else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) { //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта по TakeProfit" или "Позиция закрыта по TakeProfit частично" this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- если позиция закрыта встречной else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS)) { //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта встречной" или "Позиция закрыта встречной частично" this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Если позиция закрыта else { //--- проверяем флаг частичного закрытия и устанавливаем торговое событие "Позиция закрыта" или "Позиция закрыта частично" this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //--- Балансная операция на счёте (уточняем событие по типу сделки) if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE) { //--- Инициализируем торговое событие this.m_trade_event=TRADE_EVENT_NO_EVENT; //--- Возьмём тип сделки ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT); //--- если сделка - балансная операция if(deal_type==DEAL_TYPE_BALANCE) { //--- проверим профит сделки и установим событие (пополнение или снятие средств) this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL); } //--- Остальные типы балансной операции совпадают с перечислением ENUM_DEAL_TYPE начиная от DEAL_TYPE_CREDIT else if(deal_type>DEAL_TYPE_BALANCE) { //--- установим это событие this.m_trade_event=(ENUM_TRADE_EVENT)deal_type; } this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //+------------------------------------------------------------------+
В листинге метода, в комментариях к коду расписаны все необходимые проверки и действия, так что не будем отвлекаться на описание уже описанных действий. Думаю, здесь всё достаточно просто и понятно для самостоятельного изучения.
На этом доработка класса абстрактного события завершена.
Забегая вперёд: при проверке отслеживания модификации цен установки отложенных ордеров в тестовом советнике, возникла необходимость найти самый далёкий от цены ордер. Просматривая свойства ордеров я понял, что быстрого и однозначного решения в библиотеке для этого нет. Выход — мы будем использовать одно из дополнительных целочисленных свойств ордера — профит в пунктах. Для отложенных ордеров это будет дистанцией ордера от цены в пунктах. Таким образом, для нахождения ордера, находящегося от цены дальше всех, мы просто будем искать ордер с наибольшей "прибылью" (дистанцией) в пунктах.
Ксати — к этой же ситуации относится и поиск всех отложенных ордеров по их направлению — чтобы найти самый далёкий от цены отложенный ордер, мы выбираем все ордера в одном направлении и фильтруем полученный список по наибольшей дистанции. В итоге получим один ордер из всех ордеров разного типа, но в одном направлении (BuyLimit, BuyStop и BuyStopLimit — все имеют одно направление — Buy. Для Sell — наоборот).
Изменим метод получения типа ордера по его направлению в листинге класса абстрактного ордера Order.mqh:
//+------------------------------------------------------------------+ //| Возвращает профит ордера в пунктах | //+------------------------------------------------------------------+ int COrder::ProfitInPoints(void) const { MqlTick tick={0}; string symbol=this.Symbol(); if(!::SymbolInfoTick(symbol,tick)) return 0; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)this.TypeOrder(); double point=::SymbolInfoDouble(symbol,SYMBOL_POINT); if(type==ORDER_TYPE_CLOSE_BY || point==0) return 0; if(this.Status()==ORDER_STATUS_HISTORY_ORDER) return int(type==ORDER_TYPE_BUY ? (this.PriceClose()-this.PriceOpen())/point : type==ORDER_TYPE_SELL ? (this.PriceOpen()-this.PriceClose())/point : 0); else if(this.Status()==ORDER_STATUS_MARKET_POSITION) { if(type==ORDER_TYPE_BUY) return int((tick.bid-this.PriceOpen())/point); else if(type==ORDER_TYPE_SELL) return int((this.PriceOpen()-tick.ask)/point); } else if(this.Status()==ORDER_STATUS_MARKET_PENDING) { if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT) return (int)fabs((tick.bid-this.PriceOpen())/point); else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT) return (int)fabs((this.PriceOpen()-tick.ask)/point); } return 0; } //+------------------------------------------------------------------+
Здесь мы по сути добавили лишь проверку на отложенные ордера, и возвращаем дистанцию в пунктах от цены установки ордера до текущей цены.
Добавим вывод описания дистанции от цены до отложенного ордера в пунктах в метод описания целочисленных свойств класса абстрактнго ордера:
//+------------------------------------------------------------------+ //| Возвращает описание целочисленного свойства ордера | //+------------------------------------------------------------------+ string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property) { return ( //--- Общие свойства property==ORDER_PROP_MAGIC ? TextByLanguage("Магик","Magic")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET ? TextByLanguage("Тикет","Ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_FROM ? TextByLanguage("Тикет родительского ордера","Ticket of parent order")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_TO ? TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN ? TextByLanguage("Время открытия","Time open")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==ORDER_PROP_TIME_CLOSE ? TextByLanguage("Время закрытия","Time close")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==ORDER_PROP_TIME_EXP ? TextByLanguage("Дата экспирации","Date of expiration")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : (this.GetProperty(property)==0 ? TextByLanguage(": Не задана",": Not set") : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)) ) : property==ORDER_PROP_TYPE ? TextByLanguage("Тип","Type")+": "+this.TypeDescription() : property==ORDER_PROP_DIRECTION ? TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() : property==ORDER_PROP_REASON ? TextByLanguage("Причина","Reason")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+this.GetReasonDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_ID ? TextByLanguage("Идентификатор позиции","Position identifier")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ORDER_TICKET ? TextByLanguage("Сделка на основании ордера с тикетом","Deal by order ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ENTRY ? TextByLanguage("Направление сделки","Deal entry")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+this.GetEntryDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_BY_ID ? TextByLanguage("Идентификатор встречной позиции","Identifier opposite position")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN_MSC ? TextByLanguage("Время открытия в милисекундах","Opening time in milliseconds")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_CLOSE_MSC ? TextByLanguage("Время закрытия в милисекундах","Closing time in milliseconds")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_UPDATE ? TextByLanguage("Время изменения позиции","Position change time")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(this.GetProperty(property)!=0 ? ::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) : "0") ) : property==ORDER_PROP_TIME_UPDATE_MSC ? TextByLanguage("Время изменения позиции в милисекундах","Time to change the position in milliseconds")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0") ) : property==ORDER_PROP_STATE ? TextByLanguage("Состояние","Statе")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": \""+this.StateDescription()+"\"" ) : //--- Дополнительное свойство property==ORDER_PROP_STATUS ? TextByLanguage("Статус","Status")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": \""+this.StatusDescription()+"\"" ) : property==ORDER_PROP_PROFIT_PT ? ( this.Status()==ORDER_STATUS_MARKET_PENDING ? TextByLanguage("Дистанция от цены в пунктах","Distance from price in points") : TextByLanguage("Прибыль в пунктах","Profit in points") )+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_CLOSE_BY_SL ? TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : property==ORDER_PROP_CLOSE_BY_TP ? TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : property==ORDER_PROP_GROUP_ID ? TextByLanguage("Идентификатор группы","Group's identifier")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property is not support") : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
Здесь: проверяется статус ордера, и если это действующий отложенный ордер, то выводится сообщение о дистанции, иначе — выводится сообщение о прибыли в пунктах.
Изменения класса абстрактного ордера на этом завершены.
Теперь нам необходимо создать ещё один клас-наследник класса абстрактного события CEvent. Это будет класс события-модификации.
В шестой статье при реализации работы на неттинговых счетах был доработан класс-событие открытия позиции: в класс CEventPositionOpen добавлен метод, создающий текст краткого сообщения в зависимости от состояния события и наличия некоторых свойств у объекта-события.
При создании нового события-модификации мы поступим аналогично — будем проверять тип события-модификации и создавать текст события в зависимости от полученного типа. Кроме того, при отправке события на график управляющей программы нам необходимо определиться какую цену передавать в параметре dparam функции EventChartCustom(). Если в классе события-открытия позиции мы этим параметром передавали цену открытия, то в классе события модификации возможны несколько вариантов изменения цен, и нам необходимо решить какую из цен будем отправлять в параметре dparam пользовательского события:
- Может быть изменена только цена установки ордера — отправляем новую цену установки отложенного ордера,
- может быть изменена цена установки ордера и StopLoss — отправляем новую цену установки отложенного ордера,
- может быть изменена цена установки ордера и TakeProfit — отправляем новую цену установки отложенного ордера,
- может быть изменена цена установки ордера, StopLoss и TakeProfit — отправляем новую цену установки отложенного ордера,
- может быть изменён StopLoss ордера — отправляем новую цену StopLoss,
- может быть изменён TakeProfit ордера — отправляем новую цену TakeProfit.
- Может быть изменён StopLoss позиции — отправляем StopLoss позиции,
- Может быть изменён TakeProfit позиции — отправляем TakeProfit позиции,
- Может быть изменён StopLoss и TakeProfit позиции — отправляем цену открытия позиции.
Таким образом видим, что при изменении только одной цены мы отправляем в событие именно изменённую цену, а при одновременном изменении нескольких цен — отправляем только цену открытия позиции или установки ордера (которая в свою очередь тоже может быть изменённой). В своей программе всегда можно будет уточнить изменение каждой из цен (при их одновременной модификации) по типу произошедшего события-модификации.
В каталоге библиотеки \MQL5\Include\DoEasy\Objects\Events в новом файле EventModify.mqh создадим новый класс CEventModify.
Базовым классом для него укажем класс абстрактного события CEvent.
Не забудем подключить к файлу класса-модификации файл класса абстрактного события.
Так как класс у нас будет небольшой, то сразу приведу его полный листинг для самостоятельного изучения — тем более, что идентичный класс нами был рассмотрен в шестой части описания библиотеки при реализации изменений класса CEventPositionOpen.
//+------------------------------------------------------------------+ //| EventModify.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 CEventModify : public CEvent { private: double m_price; // Цена, отправляемая в событие //--- Создаёт и возвращает краткое сообщение события string EventsMessage(void); public: //--- Конструктор CEventModify(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MODIFY,event_code,ticket),m_price(0) {} //--- Поддерживаемые свойства ордера (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 CEventModify::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 CEventModify::SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { if(property==EVENT_PROP_PRICE_CLOSE || property==EVENT_PROP_PROFIT ) return false; return true; } //+------------------------------------------------------------------+ //| Выводит в журнал краткое сообщение о событии | //+------------------------------------------------------------------+ void CEventModify::PrintShort(void) { ::Print(this.EventsMessage()); } //+------------------------------------------------------------------+ //| Отправляет событие на график | //+------------------------------------------------------------------+ void CEventModify::SendEvent(void) { this.PrintShort(); ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.m_price,this.Symbol()); } //+------------------------------------------------------------------+ //| Создаёт и возвращает краткое сообщение события | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) заголовок, (2) магический номер string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : ""); string text=""; //--- Модифицирована цена отложенного ордера if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+magic; this.m_price=this.PriceOpen(); } //--- Модифицирована цена отложенного ордера и его StopLoss else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+TextByLanguage(" и"," and")+" StopLoss: "+sl+magic; this.m_price=this.PriceOpen(); } //--- Модифицирована цена отложенного ордера и его TakeProfit else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } //--- Модифицирована цена отложенного ордера, его StopLoss и TakeProfit else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+", StopLoss: "+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } //--- Модифицирован StopLoss отложенного ордера else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+magic; this.m_price=this.PriceStopLoss(); } //--- Модифицирован TakeProfit отложенного ордера else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован TakeProfit: ",": modified TakeProfit: ")+tp+magic; this.m_price=this.PriceTakeProfit(); } //--- Модифицирован StopLoss и TakeProfit отложенного ордера else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } //--- Модифицирован StopLoss позиции else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS) { string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+magic; this.m_price=this.PriceStopLoss(); } //--- Модифицирован TakeProfit позиции else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT) { string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent(); string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован TakeProfit: ",": modified TakeProfit: ")+tp+magic; this.m_price=this.PriceTakeProfit(); } //--- Модифицирован StopLoss и TakeProfit позиции else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT) { string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } return head+this.Symbol()+" "+text; } //+------------------------------------------------------------------+
Теперь нам необходимо в классе-коллекции событий определять события модификации уже имеющихся ордеров и позиций, создавать новое событие и добавлять его в список-коллекцию событий.
Внесём необходимые доработки в класс CEventsCollection в файле EventsCollection.mqh из папки библиотеки \MQL5\Include\DoEasy\Collections.
Подключим файл нового класса-события модификации.
В приватной секции класса объявим переменную-член класса — структуру для хранения данных тика — её будем использовать для получения данных о последних ценах события-модификации.
//+------------------------------------------------------------------+ //| 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" #include "..\Objects\Events\EventModify.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; // Объект-событие для поиска по свойству MqlTick m_tick; // Структура последнего тика
И в конструкторе класса инициализируем структуру тика:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT) { this.m_list_events.Clear(); this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT); this.m_list_events.Type(COLLECTION_EVENTS_ID); this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); this.m_chart_id=::ChartID(); ::ZeroMemory(this.m_tick); } //+------------------------------------------------------------------+
В седьмой части описания библиотеки мы сделали перегруженный метод создания нового события — теперь у нас их два — метод для создания событий при изменении количества ордеров и позиций на счёте, и метод, создающий новое событие при изменении уже существующего ордера или позиции — их модификации.
Второй метод нам необходимо дописать, чтобы он мог отслеживать события модификации ордеров и позиций (в седьмой статье метод обрабатыал только событие срабатывания StopLimit-ордера).
Добавим строки кода, обрабатывающие событие модификации ордера или позиции, и строки сохранения свойсва ордера или позиции до модификации:
//+------------------------------------------------------------------+ //| Создаёт торговое событие в зависимости от типа изменения ордера | //+------------------------------------------------------------------+ void CEventsCollection::CreateNewEvent(COrderControl* order) { if(!::SymbolInfoTick(order.Symbol(),this.m_tick)) { Print(DFUN,TextByLanguage("Не удалось получить текущие цены по символу события ","Failed to get current prices by event symbol "),order.Symbol()); return; } 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()); } //--- Модификация else { //--- Модифицирована цена отложенного ордера if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE; //--- Модифицирована цена отложенного ордера и его StopLoss else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_STOP_LOSS) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_SL; //--- Модифицирована цена отложенного ордера и его TakeProfit else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_TP; //--- Модифицирована цена отложенного ордера, его StopLoss и TakeProfit else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP; //--- Модифицирован StopLoss отложенного ордера else if(order.GetChangeType()==CHANGE_TYPE_ORDER_STOP_LOSS) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_SL; //--- Модифицирован TakeProfit отложенного ордера else if(order.GetChangeType()==CHANGE_TYPE_ORDER_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_TP; //--- Модифицирован StopLoss и TakeProfit отложенного ордера else if(order.GetChangeType()==CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP; //--- Модифицирован StopLoss позиции else if(order.GetChangeType()==CHANGE_TYPE_POSITION_STOP_LOSS) this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_SL; //--- Модифицирован TakeProfit позиции else if(order.GetChangeType()==CHANGE_TYPE_POSITION_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_TP; //--- Модифицирован StopLoss и TakeProfit позиции else if(order.GetChangeType()==CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP; //--- Создаём событие модификации event=new CEventModify(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,PositionTypeByOrderType((ENUM_ORDER_TYPE)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_PRICE_OPEN_BEFORE,order.PricePrev()); // Цена установки ордера до модификации event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLossPrev()); // Цена StopLoss до модификации event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfitPrev()); // Цена TakeProfit до модификации event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask); // Цена Ask в момент события event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid); // Цена Bid в момент события 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; } } } //+------------------------------------------------------------------+
Обработка условий разных типов модификации достаточно проста и понятна, и описана в комментариях к коду — в зависимости от типа изменения ордера/позиции создаётся код события из набора флагов, и этот код отправляется в конструктор класса CEventModify при создании нового события-модификации.
Хочу отметить, что блоки кода сохранения новых свойств ордера/позиции, выделенные цветом прописаны во всех методах сохранения свойств ордера/позиции данного класса. И чтобы здесь не занимать место на идентичные строки кода, мы их рассматривать не будем — всё есть в файлах в конце статьи.
У нас всё готово для тестирования событий модификации существующих ордеров и позиций.
Тест событий модификации ордеров и позиций
Для тестирования нам необходимо будет дополнить существующий набор кнопок тестового советника из седьмой статьи.
Добавим в набор его кнопок ещё три кнопки и обработчики их нажатия: Set StopLoss, Set TakeProfit и Trailing All.
Первые две кнопки будут устанавливать стоплосс и тейкпрофит всем ордерам и позициям, у которых эти уровни отсутствуют, третья же кнопка будет у нас иметь два положения — Вкл/Выкл, т.е., при её нажатии она останется нажатой и начнут работать две функции-трала. В результате советник начнёт тралить стопы всех позиций и подтягивать за ценой все активные отложенные ордера. Повторное нажатие кнопки отключит оба трала.
Возьмём советник TestDoEasyPart07.mq5 из расположения \MQL5\Experts\TestDoEasy\Part07 и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part08 под именем TestDoEasyPart08.mq5.
В перечисление кнопок добавим три новые константы и изменим общее количество кнопок с 17 на 20 в соответствующей макроподстановке:
//--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_PROFIT_WITHDRAWAL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20)
Во входные параметры добавим переменные для указания дистанции уровня StopLoss от цены, шага трала, количество пунктов прибыли для начала трейлинга и размеры StopLoss и TakeProfit в пунктах для их установки по нажатию соответствующих кнопок (параметры InpStopLoss и InpTakeProfit используются для установки стоп-уровней сразу при открытии позиции/выставлении отложенного ордера).
Добавим в список глобальных переменных необходимые переменные для хранения значений вновь добавленных входных переменных, и переменную-флаг, указывающую на активность функций трейлингов:
//--- input variables input ulong InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 50; // StopLoss in points input uint InpTakeProfit = 50; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 0; // Slippage in points input double InpWithdrawal = 10; // Withdrawal funds (in tester) input uint InpButtShiftX = 40; // Buttons X shift input uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) //--- global variables CEngine engine; CTrade trade; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; //+------------------------------------------------------------------+
Так как советник тестовый, то при отладке библиотеки часто бывают ситуации критического завершения программы. В таких ситуациях все построенные графические объекты (кнопки) остаются на графике, и при повторном запуске советника после исправления ошибки, которая привела к критическому завершению программы, советник не может построить кнопки, и приходится запускать его повторно — чтобы он в своём обработчике OnDeinit() сначала удалил имеющиеся объекты с графика, и при следующем запуске уже мог на чистом графике заново построить все кнопки.
В обработчик OnInit() советника добавим проверку на присутствие на графике кнопок, зададим значения переменным функций трейлинга и стоп-уровней, и после построения всех кнопок, проверим флаг активности кнопки трейлинга и включим кнопку, если флаг установлен.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Вызов данной функции выводит в журнал список констант перечисления, //--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант //EnumNumbersTest(); //--- check for undeleted objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- set global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- create buttons if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- set button trailing ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- setting trade parameters trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Напишем функцию для определения наличия на графике графического объекта с заданным префиксом и функцию контроля состояния кнопок — просто для удобства восприятия кода вынесем этот контроль из обработчика OnTick() советника в отдельную функцию:
//+------------------------------------------------------------------+ //| Возвращает флаг наличия объекта с префиксом | //+------------------------------------------------------------------+ bool IsPresentObects(const string object_prefix) { for(int i=ObjectsTotal(0)-1;i>=0;i--) if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE) return true; return false; } //+------------------------------------------------------------------+ //| Контроль состояния кнопок | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+
Внесём изменения в функцию установки состояния объекта-кнопки:
//+------------------------------------------------------------------+ //| Устанавливает состояние кнопки | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); if(name==butt_data[TOTAL_BUTT-1].name) { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } } //+------------------------------------------------------------------+
Здесь:
устанавливаем состояние кнопки (нажата/отжата),
если это самая последняя кнопка и
если она имеет состояние "нажата", то изменим цвет фона объекта-кнопки,
иначе — вернём цвет фона в состояние "не нажата".
Так как у нас появились три новые кнопки, то допишем в функцию создания текста кнопок из их названия преобразование имён новых объектов-кнопок в их текст:
//+------------------------------------------------------------------+ //| Преобразует перечисление в текст кнопки | //+------------------------------------------------------------------+ string EnumToButtText(const ENUM_BUTTONS member) { string txt=StringSubstr(EnumToString(member),5); StringToLower(txt); StringReplace(txt,"set_take_profit","Set TakeProfit"); StringReplace(txt,"set_stop_loss","Set StopLoss"); StringReplace(txt,"trailing_all","Trailing All"); StringReplace(txt,"buy","Buy"); StringReplace(txt,"sell","Sell"); StringReplace(txt,"_limit"," Limit"); StringReplace(txt,"_stop"," Stop"); StringReplace(txt,"close_","Close "); StringReplace(txt,"2"," 1/2"); StringReplace(txt,"_by_"," by "); StringReplace(txt,"profit_","Profit "); StringReplace(txt,"delete_","Delete "); return txt; } //+------------------------------------------------------------------+
Теперь нам необходимо обрабатывать нажатие трёх новых кнопок. Для этого в функцию обработки нажатия кнопок PressButtonEvents() — в самом её конце (после блока кода обработки нажатия кнопки вывода средств) — допишем строки кода:
//--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- Если программа запущена в тестере if(MQLInfoInteger(MQL_TESTER)) { //--- Эмулируем вывод средств TesterWithdrawal(withdrawal); } } //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету if(button==EnumToString(BUTT_SET_STOP_LOSS)) { SetStopLoss(); } //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету if(button==EnumToString(BUTT_SET_TAKE_PROFIT)) { SetTakeProfit(); } //--- Подождём 1/10 секунды Sleep(100); //--- "Отожмём" кнопку (если это не кнопка трейлинга) if(button!=EnumToString(BUTT_TRAILING_ALL)) ButtonState(button_name,false); //--- Если нажата кнопка BUTT_TRAILING_ALL else { //--- Поставим цвет активной кнопки ButtonState(button_name,true); trailing_on=true; } //--- перерисуем чарт ChartRedraw(); } //--- Вернём цвет неактивной кнопки (если это кнопка трейлинга) else if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,false); trailing_on=false; //--- перерисуем чарт ChartRedraw(); } } //+------------------------------------------------------------------+
Как видим, здесь вызываются две новые функции: SetStopLoss() и SetTakeProfit() для установки соответствующих уровней ордерам и позициям:
//+------------------------------------------------------------------+ //| Установка StopLoss всем ордерам и позициям | //+------------------------------------------------------------------+ void SetStopLoss(void) { if(stoploss_to_modify==0) return; //--- Установка StopLoss всем позициям, где его нету CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify); trade.PositionModify(position.Ticket(),sl,position.TakeProfit()); } //--- Установка StopLoss всем отложенным ордерам, где его нету list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),sl,order.TakeProfit(),trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+ //| Установка TakeProfit всем ордерам и позициям | //+------------------------------------------------------------------+ void SetTakeProfit(void) { if(takeprofit_to_modify==0) return; //--- Установка TakeProfit всем позициям, где его нету CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify); trade.PositionModify(position.Ticket(),position.StopLoss(),tp); } //--- Установка TakeProfit всем отложенным ордерам, где его нету list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),order.StopLoss(),tp,trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+
Функции достаточно просты. Рассмотрим на примере установки TakeProfit всем ордерам и позициям, где он не установлен:
Сначала проверяем какой задан размер установки стоп-уровней в пунктах, и если нулевой, то сразу выходим — изменять нечего.
Затем получаем список только активных рыночных позиций и фильтруем его по цене TakeProfit, равной нулю, т.е., по отсутствию TakeProfit у позиции.
И далее в цикле по итоговому списку получаем из него позиции, рассчитываем для каждой корректный TakeProfit при помощи сервисной функции, рассмотренной нами в четвёртой части описания библиотеки и отправляем его в метод модификации позиции класса CTrade стандартной библиотеки.
Для установки TakeProfit у ордеров, мы получаем уже список действующих отложенных ордеров и производим с ними описанные выше действия.
Осталось написать функции трейлинга стопов позиций и цен установки ордеров:
//+------------------------------------------------------------------+ //| Трал стопа позиции с наибольшей прибылью | //+------------------------------------------------------------------+ void TrailingPositions(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Рассчитываем новый StopLoss double sl=NormalizeDouble(tick.bid-trailing_stop,Digits()); //--- Если цена и отложенный от неё уровень StopLevel выше нового StopLoss (соблюдена дистанция по StopLevel) if(tick.bid-stop_level>sl) { //--- Если новый уровень StopLoss выше, чем шаг трала, отложенный от текущего StopLoss if(buy.StopLoss()+trailing_step<sl) { //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss if(trailing_start==0 || buy.ProfitInPoints()>(int)trailing_start) trade.PositionModify(buy.Ticket(),sl,buy.TakeProfit()); } } } } //--- Выбираем из списка только позиции Sell CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Рассчитываем новый StopLoss double sl=NormalizeDouble(tick.ask+trailing_stop,Digits()); //--- Если цена и отложенный от неё уровень StopLevel ниже нового StopLoss (соблюдена дистанция по StopLevel) if(tick.ask+stop_level<sl) { //--- Если новый уровень StopLoss ниже, чем шаг трала, отложенный от текущего StopLoss или у позиции ещё не установлен StopLoss if(sell.StopLoss()-trailing_step>sl || sell.StopLoss()==0) { //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss if(trailing_start==0 || sell.ProfitInPoints()>(int)trailing_start) trade.PositionModify(sell.Ticket(),sl,sell.TakeProfit()); } } } } } //+------------------------------------------------------------------+ //| Трал самых дальних отложенных ордеров | //+------------------------------------------------------------------+ void TrailingOrders(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Получаем список всех установленных ордеров CArrayObj* list=engine.GetListMarketPendings(); //--- Выбираем из списка только ордера в направлении Buy CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL); //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах) list_buy.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Получаем индекс ордера в направлении Buy с наибольшей дистанцией int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Если ордер установлен ниже цены (BuyLimit), и его необходимо "поднимать" за ценой if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT) { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.ask-trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel) if(price<tick.ask-stop_level) { //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price>buy.PriceOpen()+trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),buy.PriceStopLimit()); } } } //--- Если ордер установлен выше цены (BuyStop и BuyStopLimit), и его необходимо "опускать" за ценой else { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.ask+trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel) if(price>tick.ask+stop_level) { //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price<buy.PriceOpen()-trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(buy.PriceStopLimit()>0 ? price-distance_stoplimit*Point() : 0)); } } } } } //--- Выбираем из списка только ордера в направлении Sell CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL); //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах) list_sell.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Получаем индекс ордера в направлении Sell с наибольшей дистанцией int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Если ордер установлен выше цены (SellLimit), и его необходимо "опускать" за ценой if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT) { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.bid+trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel) if(price>tick.bid+stop_level) { //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price<sell.PriceOpen()-trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),sell.PriceStopLimit()); } } } //--- Если ордер установлен ниже цены (SellStop и SellStopLimit), и его необходимо "поднимать" за ценой else { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.bid-trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel) if(price<tick.bid-stop_level) { //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price>sell.PriceOpen()+trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(sell.PriceStopLimit()>0 ? price+distance_stoplimit*Point() : 0)); } } } } } } //+------------------------------------------------------------------+
В функциях нет ничего для нас нового — все необходимые действия описаны прямо в коде в его комментариях, и думаю, не возникнет вопросов при самостоятельном изучении кода.
Так как у нас теперь стало на три кнопки больше, то в функции создания панели кнопок был подкорректирован расчёт координат кнопок (можно поглядеть в итоговом листинге).
И в обработчике OnTick() сделаем вызов всех функций трейлинга:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Инициализация последнего торгового события static ENUM_TRADE_EVENT last_event=WRONG_VALUE; //--- Если работа в тестере if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); PressButtonsControl(); } //--- Если последнее торговое событие изменилось if(engine.LastTradeEvent()!=last_event) { last_event=engine.LastTradeEvent(); } //--- Если установлен флаг трейлинга if(trailing_on) { TrailingPositions(); TrailingOrders(); } } //+------------------------------------------------------------------+
Полный листинг тестового советника:
//+------------------------------------------------------------------+ //| TestDoEasyPart08.mq5 | //| 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" //--- includes #include <DoEasy\Engine.mqh> #include <Trade\Trade.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_PROFIT_WITHDRAWAL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20) //--- structures struct SDataButt { string name; string text; }; //--- input variables input ulong InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 50; // StopLoss in points input uint InpTakeProfit = 50; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 0; // Slippage in points input double InpWithdrawal = 10; // Withdrawal funds (in tester) input uint InpButtShiftX = 40; // Buttons X shift input uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) //--- global variables CEngine engine; CTrade trade; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Вызов данной функции выводит в журнал список констант перечисления, //--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант //EnumNumbersTest(); //--- check for undeleted objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- set global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- create buttons if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- set button trailing ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- setting trade parameters trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete objects ObjectsDeleteAll(0,prefix); Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Инициализация последнего торгового события static ENUM_TRADE_EVENT last_event=WRONG_VALUE; //--- Если работа в тестере if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); PressButtonsControl(); } //--- Если последнее торговое событие изменилось if(engine.LastTradeEvent()!=last_event) { last_event=engine.LastTradeEvent(); } //--- Если установлен флаг трейлинга if(trailing_on) { TrailingPositions(); TrailingOrders(); } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(MQLInfoInteger(MQL_TESTER)) return; if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0) { PressButtonEvents(sparam); } if(id>=CHARTEVENT_CUSTOM) { ushort event=ushort(id-CHARTEVENT_CUSTOM); Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam); } } //+------------------------------------------------------------------+ //| Возвращает флаг наличия объекта с префиксом | //+------------------------------------------------------------------+ bool IsPresentObects(const string object_prefix) { for(int i=ObjectsTotal(0)-1;i>=0;i--) if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE) return true; return false; } //+------------------------------------------------------------------+ //| Контроль состояния кнопок | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+ //| Создаёт панель кнопок | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=30,const int shift_y=0) { int h=18,w=84,offset=2; int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; int x=cx,y=cy; int shift=0; for(int i=0;i<TOTAL_BUTT;i++) { x=x+(i==7 ? w+2 : 0); if(i==TOTAL_BUTT-6) x=cx; y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-6 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Создаёт кнопку | //+------------------------------------------------------------------+ bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8) { if(ObjectFind(0,name)<0) { if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); return false; } ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n"); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray); return true; } return false; } //+------------------------------------------------------------------+ //| Возвращает состояние кнопки | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Устанавливает состояние кнопки | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); if(name==butt_data[TOTAL_BUTT-1].name) { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } } //+------------------------------------------------------------------+ //| Преобразует перечисление в текст кнопки | //+------------------------------------------------------------------+ string EnumToButtText(const ENUM_BUTTONS member) { string txt=StringSubstr(EnumToString(member),5); StringToLower(txt); StringReplace(txt,"set_take_profit","Set TakeProfit"); StringReplace(txt,"set_stop_loss","Set StopLoss"); StringReplace(txt,"trailing_all","Trailing All"); StringReplace(txt,"buy","Buy"); StringReplace(txt,"sell","Sell"); StringReplace(txt,"_limit"," Limit"); StringReplace(txt,"_stop"," Stop"); StringReplace(txt,"close_","Close "); StringReplace(txt,"2"," 1/2"); StringReplace(txt,"_by_"," by "); StringReplace(txt,"profit_","Profit "); StringReplace(txt,"delete_","Delete "); return txt; } //+------------------------------------------------------------------+ //| Обработка нажатий кнопок | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Преобразуем имя кнопки в её строковый идентификатор string button=StringSubstr(button_name,StringLen(prefix)); //--- Если кнопка в нажатом состоянии if(ButtonState(button_name)) { //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy if(button==EnumToString(BUTT_BUY)) { //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Открываем позицию Buy trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp); } //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Получаем корректную цену установки ордера относительно уровня StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Устанавливаем ордер BuyLimit trade.BuyLimit(lot,price_set,Symbol(),sl,tp); } //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Получаем корректную цену установки ордера относительно уровня StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- Устанавливаем ордер BuyStop trade.BuyStop(lot,price_set,Symbol(),sl,tp); } //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Получаем корректную цену установки ордера BuyStop относительно уровня StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Рассчитываем цену установки ордера BuyLimit относительно уровня установки BuyStop с учётом уровня StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit); //--- Устанавливаем ордер BuyStopLimit trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell else if(button==EnumToString(BUTT_SELL)) { //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- Открываем позицию Sell trade.Sell(lot,Symbol(),0,sl,tp); } //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Получаем корректную цену установки ордера относительно уровня StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- Устанавливаем ордер SellLimit trade.SellLimit(lot,price_set,Symbol(),sl,tp); } //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Получаем корректную цену установки ордера относительно уровня StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- Устанавливаем ордер SellStop trade.SellStop(lot,price_set,Symbol(),sl,tp); } //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Получаем корректную цену установки ордера SellStop относительно уровня StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Рассчитываем цену установки ордера SellLimit относительно уровня установки SellStop с учётом уровня StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit); //--- Устанавливаем ордер SellStopLimit trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Получаем тикет позиции Buy и закрываем позицию по тикету trade.PositionClose(position.Ticket()); } } } //--- Если нажата кнопка BUTT_CLOSE_BUY2: Закрыть половину Buy с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Рассчитываем закрываемый объём и закрываем половину позиции Buy по тикету if(engine.IsHedge()) trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0)); else trade.Sell(NormalizeLot(position.Symbol(),position.Volume()/2.0)); } } } //--- Если нажата кнопка BUTT_CLOSE_BUY_BY_SELL: Закрыть Buy с максимальной прибылью встречной Sell с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- Получаем список всех открытых позиций CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- Получаем список всех открытых позиций CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Sell list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- Выбираем позицию Buy с наибольшей прибылью COrder* position_buy=list_buy.At(index_buy); //--- Выбираем позицию Sell с наибольшей прибылью COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- Закрываем позицию Buy встречной позицией Sell trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket()); } } } //--- Если нажата кнопка BUTT_CLOSE_SELL: Закрыть Sell с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Sell list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Получаем тикет позиции Sell и закрываем позицию по тикету trade.PositionClose(position.Ticket()); } } } //--- Если нажата кнопка BUTT_CLOSE_SELL2: Закрыть половину Sell с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Sell list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Рассчитываем закрываемый объём и закрываем половину позиции Sell по тикету if(engine.IsHedge()) trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0)); else trade.Buy(NormalizeLot(position.Symbol(),position.Volume()/2.0)); } } } //--- Если нажата кнопка BUTT_CLOSE_SELL_BY_BUY: Закрыть Sell с максимальной прибылью встречной Buy с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- Получаем список всех открытых позиций CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Sell list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- Получаем список всех открытых позиций CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- Выбираем позицию Sell с наибольшей прибылью COrder* position_sell=list_sell.At(index_sell); //--- Выбираем позицию Buy с наибольшей прибылью COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- Закрываем позицию Sell встречной позицией Buy trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket()); } } } //--- Если нажата кнопка BUTT_CLOSE_ALL: Закрыть все позиции, начиная от позиции с наименьшим профитом else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); if(list!=NULL) { //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- В цикле от позиции с наименьшей прибылью for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- закрываем каждую позицию по её тикету trade.PositionClose(position.Ticket()); } } } //--- Если нажата кнопка BUTT_DELETE_PENDING: Удалить первый отложенный ордер else if(button==EnumToString(BUTT_DELETE_PENDING)) { //--- Получаем список всех ордеров CArrayObj* list=engine.GetListMarketPendings(); if(list!=NULL) { //--- Сортируем список по времени установки list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); int total=list.Total(); //--- В цикле от позиции с наибольшим временем for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; //--- удаяем ордер по его тикету trade.OrderDelete(order.Ticket()); } } } //--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- Если программа запущена в тестере if(MQLInfoInteger(MQL_TESTER)) { //--- Эмулируем вывод средств TesterWithdrawal(withdrawal); } } //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету if(button==EnumToString(BUTT_SET_STOP_LOSS)) { SetStopLoss(); } //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету if(button==EnumToString(BUTT_SET_TAKE_PROFIT)) { SetTakeProfit(); } //--- Подождём 1/10 секунды Sleep(100); //--- "Отожмём" кнопку (если это не кнопка трейлинга) if(button!=EnumToString(BUTT_TRAILING_ALL)) ButtonState(button_name,false); //--- Если нажата кнопка BUTT_TRAILING_ALL else { //--- Поставим цвет активной кнопки ButtonState(button_name,true); trailing_on=true; } //--- перерисуем чарт ChartRedraw(); } //--- Вернём цвет неактивной кнопки (если это кнопка трейлинга) else if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,false); trailing_on=false; //--- перерисуем чарт ChartRedraw(); } } //+------------------------------------------------------------------+ //| Установка StopLoss всем ордерам и позициям | //+------------------------------------------------------------------+ void SetStopLoss(void) { if(stoploss_to_modify==0) return; //--- Установка StopLoss всем позициям, где его нету CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify); trade.PositionModify(position.Ticket(),sl,position.TakeProfit()); } //--- Установка StopLoss всем отложенным ордерам, где его нету list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),sl,order.TakeProfit(),trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+ //| Установка TakeProfit всем ордерам и позициям | //+------------------------------------------------------------------+ void SetTakeProfit(void) { if(takeprofit_to_modify==0) return; //--- Установка TakeProfit всем позициям, где его нету CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify); trade.PositionModify(position.Ticket(),position.StopLoss(),tp); } //--- Установка TakeProfit всем отложенным ордерам, где его нету list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),order.StopLoss(),tp,trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+ //| Трал стопа позиции с наибольшей прибылью | //+------------------------------------------------------------------+ void TrailingPositions(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Рассчитываем новый StopLoss double sl=NormalizeDouble(tick.bid-trailing_stop,Digits()); //--- Если цена и отложенный от неё уровень StopLevel выше нового StopLoss (соблюдена дистанция по StopLevel) if(tick.bid-stop_level>sl) { //--- Если новый уровень StopLoss выше, чем шаг трала, отложенный от текущего StopLoss if(buy.StopLoss()+trailing_step<sl) { //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss if(trailing_start==0 || buy.ProfitInPoints()>(int)trailing_start) trade.PositionModify(buy.Ticket(),sl,buy.TakeProfit()); } } } } //--- Выбираем из списка только позиции Sell CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Рассчитываем новый StopLoss double sl=NormalizeDouble(tick.ask+trailing_stop,Digits()); //--- Если цена и отложенный от неё уровень StopLevel ниже нового StopLoss (соблюдена дистанция по StopLevel) if(tick.ask+stop_level<sl) { //--- Если новый уровень StopLoss ниже, чем шаг трала, отложенный от текущего StopLoss или у позиции ещё не установлен StopLoss if(sell.StopLoss()-trailing_step>sl || sell.StopLoss()==0) { //--- Если тралим при любой прибыли или прибыль позиции в пунктах больше значения начала трейлинга - модифицируем StopLoss if(trailing_start==0 || sell.ProfitInPoints()>(int)trailing_start) trade.PositionModify(sell.Ticket(),sl,sell.TakeProfit()); } } } } } //+------------------------------------------------------------------+ //| Трал самых дальних отложенных ордеров | //+------------------------------------------------------------------+ void TrailingOrders(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Получаем список всех установленных ордеров CArrayObj* list=engine.GetListMarketPendings(); //--- Выбираем из списка только ордера в направлении Buy CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL); //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах) list_buy.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Получаем индекс ордера в направлении Buy с наибольшей дистанцией int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Если ордер установлен ниже цены (BuyLimit), и его необходимо "поднимать" за ценой if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT) { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.ask-trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel) if(price<tick.ask-stop_level) { //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price>buy.PriceOpen()+trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),buy.PriceStopLimit()); } } } //--- Если ордер установлен выше цены (BuyStop и BuyStopLimit), и его необходимо "опускать" за ценой else { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.ask+trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Ask (соблюдена дистанция по StopLevel) if(price>tick.ask+stop_level) { //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price<buy.PriceOpen()-trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(buy.PriceStopLimit()>0 ? price-distance_stoplimit*Point() : 0)); } } } } } //--- Выбираем из списка только ордера в направлении Sell CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL); //--- Сортируем список по дистанции от цены в пунктах (по прибыли в пунктах) list_sell.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Получаем индекс ордера в направлении Sell с наибольшей дистанцией int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Если ордер установлен выше цены (SellLimit), и его необходимо "опускать" за ценой if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT) { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.bid+trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- Если рассчитанная цена выше, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel) if(price>tick.bid+stop_level) { //--- Если рассчитанная цена ниже, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price<sell.PriceOpen()-trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),sell.PriceStopLimit()); } } } //--- Если ордер установлен ниже цены (SellStop и SellStopLimit), и его необходимо "поднимать" за ценой else { //--- Рассчитываем новую цену установки ордера и стоп-уровни от новой цены double price=NormalizeDouble(tick.bid-trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- Если рассчитанная цена ниже, чем дистанция StopLevel, отложенная от цены установки ордера Bid (соблюдена дистанция по StopLevel) if(price<tick.bid-stop_level) { //--- Если рассчитанная цена выше, чем шаг трала, отложенный от цены установки ордера - модифицируем цену установки ордера if(price>sell.PriceOpen()+trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(sell.PriceStopLimit()>0 ? price+distance_stoplimit*Point() : 0)); } } } } } } //+------------------------------------------------------------------+
Скомпилируем советник.
Зададим в его параметрах значения StopLoss in points и TaleProfit in points равными нулю — чтобы открывать позиции и устанавливать отложенные ордера изначально без стоп-уровней, зададим в параметрах StopLoss for modification (points) и TakeProfit for modification (points) значения 20 и 60 соответственно (значения по умолчанию) — эти уровни StopLoss и TakeProfit будут устанавливаться по нажатию кнопок.
Запустим советник в тестере и выставим отложенные ордера. Затем нажмём поочерёдно кнопки установки StopLoss и TakeProfit — уровни будут выставлены и в журнал будут выведены об этом записи. Затем включим трейлинг и понаблюдаем за ордерами — они перемещаются за ценой, и в журнал об этих событиях выводятся записи. У позиций, появившихся в результате срабатывания ордеров, будут тралиться уровни StopLoss, и об этих событиях в журнале будут появляться записи.
Неттинг:
Хедж:
Что дальше
В следующих статьях мы начнём вносить корректировки в библиотеку для совместимости с MQL4 и, естественно, продолжим развивать библиотеку — впереди ещё много интересного.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового
советника. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий вы можете высказаться в комментариях к статье.
Статьи этой серии:
Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Спред в тестере равен 1. И на ПК и на ноутбуке. Символ @Si.
А разве это может как-то влиять?
Я думал, что может быть как-нибудь проскальзывание (slippage) влияет и пробовал его менять. Но не помогает..
В одном из случаев цена явно не доходит до стоп-уровня
Артём.
Скрин во вложении.Т.е. проходит через уровни sl и tp будто их совсем нет.
Сегодня я попробовал ещё на одном компьютере - всё нормально, sl и tp срабатывают без проблем. Получается только на моём ПК эти проблемы. Я также попробовал на моём ПК, но на виртуальной машине - тоже всё нормально (везде один и тот же код).
Вобщем, ситуация непонятная...
Артём.
Скрин во вложении.Т.е. проходит через уровни sl и tp будто их совсем нет.
Сегодня я попробовал ещё на одном компьютере - всё нормально, sl и tp срабатывают без проблем. Получается только на моём ПК эти проблемы. Я также попробовал на моём ПК, но на виртуальной машине - тоже всё нормально (везде один и тот же код).
Вобщем, ситуация непонятная...
Добрый вечер! "Непонятки" ещё могут возникать, когда на персональном компьютере установлена 32-битная версия Windows.
С уважением, Владимир.
Артём.
Скрин во вложении.Т.е. проходит через уровни sl и tp будто их совсем нет.
Сегодня я попробовал ещё на одном компьютере - всё нормально, sl и tp срабатывают без проблем. Получается только на моём ПК эти проблемы. Я также попробовал на моём ПК, но на виртуальной машине - тоже всё нормально (везде один и тот же код).
Вобщем, ситуация непонятная...
А что пишется в журнале "Эксперты"?
Владимир, Артём.
На моём ПК установлена 64-битная версия Windows10, как и на других, на которых sl и tp срабатывают нормально. В журнале "Эксперты" ничего не пишется. Но если я в тестере это делаю, то как я понимаю, там ничего и не должно писаться.
Но что интересно, если включить trailing all то tp начинает срабатывать. Я предположил, что может как-нибудь влияет trade.PositionModify(...)? Но я пробовал в моём эксперте после открытия позиции сразу её модифаить (немного изменить sl и tp) - не помогло.
Может настройки Windows как-то влияют (ведь на других компьютерах работает нормально)?