Содержание
Реорганизация структуры библиотеки
В предыдущих статьях мы начали создавать большую кроссплатформенную библиотеку, целью которой является упростить написание программ
для платформ
MetaTrader 5
и MetaTrader 4. В четвёртой части мы протестировали отслеживание торговых
событий на счёте. В данной части повествования создадим классы торговых событий, поместим их в коллекцию событий, откуда они будут
отправляться в базовый объект библиотеки Engine и на график управляющей программы.
Но сначала подготовим почву для дальнейшего развития структуры библиотеки.
Так как у нас будет множество различных коллекций, и каждой коллекции будут соответствовать свои, присущие только этой коллекции объекты,
то кажется обоснованным хранить объекты для каждой коллекции в своей подпапке.
Для этого в корневом каталоге библиотеки DoEasy, в его подкаталоге Objects создадим две папки: Orders и Events.
В папку Orders переместим все ранее созданные нами классы из папки Objects, а папка Events нам нужна будет для хранения классов
объектов-событий, которые сегодня создадим.
Также переместим файл Select.mqh из папки Collections в папку Services так как планируется далее подключить к нему ещё один сервисный класс с
методами быстрого доступа к любым свойствам любых объектов из имеющихся и будущих коллекций, а значит — и место его как раз в папке сервисных
классов.
Так как после перемещения классов объектов-ордеров в новый каталог и файла класса CSelect, поменялись и относительные адреса требуемых
для их компиляции файлов, то пройдёмся по листингам перемещённых классов и заменим в них адреса подключаемых файлов:
В файле Order.mqh заменим путь подключения файла сервисных функций с
#include "..\Services\DELib.mqh"
на
#include "..\..\Services\DELib.mqh"
В файле HistoryCollection.mqh заменим пути
#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
на
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
В файле MarketCollection.mqh заменим пути
#include "Select.mqh"
#include "..\Objects\MarketOrder.mqh"
#include "..\Objects\MarketPending.mqh"
#include "..\Objects\MarketPosition.mqh"
на
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
Теперь всё должно компилироваться без ошибок.
Так как коллекций планируется много, то желательно иметь возможность как-то различать принадлежность списка коллекции, созданного на
основе
CArrayObj для идентификации этого списка.
Ведь в каждой коллекции есть метод, возвращающий указатель на полный список коллекции. И если где-либо будет метод, который получает
некий список некой коллекции, то внутри этого метода мы должны будем иметь возможность точной идентификации переданного в метод списка по
принадлежности к той или иной коллекции — во избежание передачи в метод дополнительного флага, указывающего на тип переданного в метод
списка.
К счастью, в стандартной библиотеке уже для этого всё предусмотрено — это виртуальный метод Type(),
возвращающий идентификатор объекта.
Так, для CObject возвращается
идентификатор 0, а для
CArrayObj — идентификатор
0x7778. Так как метод сделан виртуальным, то это даёт возможность потомкам этих классов иметь свой метод, возвращающий свой
идентификатор.
Сделаем так: все списки коллекций у нас созданы на базе класса CArrayObj, мы же создадим свой класс CListObj,
который будет потомком класса CArrayObj и в его виртуальном методе Type() будет возвращаться идентификатор этого списка. Сам же
идентификатор мы будем задавать константой в конструкторе класса. Таким образом мы продолжим обращаться к нашим коллекциям как к объекту
CArrayObj, но теперь каждый список будет иметь свой конкретный идентификатор.
Сначала пропишем нужные нам идентификаторы списков-коллекций
в файле Defines.mqh. И попутно добавим макрос
"описание функции с номером строки ошибки" для вывода отладочных сообщений с указанием строки, из которой отсылается это
сообщение — для локализации проблемного места в коде при отладке:
#define DFUN_ERR_LINE (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN (__FUNCTION__+": ")
#define COUNTRY_LANG ("Russian")
#define END_TIME (D'31.12.3000 23:59:59')
#define TIMER_FREQUENCY (16)
#define COLLECTION_PAUSE (250)
#define COLLECTION_COUNTER_STEP (16)
#define COLLECTION_COUNTER_ID (1)
#define COLLECTION_HISTORY_ID (0x7778+1)
#define COLLECTION_MARKET_ID (0x7778+2)
#define COLLECTION_EVENTS_ID (0x7778+3)
Теперь в папке Collections создадим класс CListObj в файле ListObj.mqh. Базовым классом для него будет класс CArrayObj:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include <Arrays\ArrayObj.mqh>
class CListObj : public CArrayObj
{
private:
int m_type;
public:
void Type(const int type) { this.m_type=type; }
virtual int Type(void) const { return(this.m_type); }
CListObj() { this.m_type=0x7778; }
};
Всё, что нам тут требуется — это объявить член класса, содержий тип списка,
добавить
метод установки типа списка и виртуальный
метод возврата типа списка.
В конструкторе класса зададим тип
списка, по умолчанию равным типу списка CArrajObj. Затем его можно будет переназначить из вызывающей программы при помощи метода
Type().
Теперь нам нужно все списки коллекций унаследовать от данного класса — тогда каждому списку мы сможем присвоить собственный идентификатор
списка, и по нему отслеживать принадлежность списка в любых методах, в которые будет передан список.
Откроем файл HistoryCollection.mqh и пропишем подключение класса
CListObj и унаследуем класс CHistoryCollection от класса CListObj.
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
class CHistoryCollection : public CListObj
{
В конструкторе класса зададим списку исторической коллекции его тип,
заданный нами в файле Defines.mqh как
COLLECTION_HISTORY_ID:
CHistoryCollection::CHistoryCollection(void) : m_index_deal(0),m_delta_deal(0),m_index_order(0),m_delta_order(0),m_is_trade_event(false)
{
this.m_list_all_orders.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN #else SORT_BY_ORDER_TIME_CLOSE #endif );
this.m_list_all_orders.Clear();
this.m_list_all_orders.Type(COLLECTION_HISTORY_ID);
}
Те же самые действия произведём и с классом CMarketCollection в файле MarketCollection.mqh:
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
class CMarketCollection : public CListObj
{
В конструкторе класса зададим списку рыночной коллекции его тип,
заданный нами в файле Defines.mqh как
COLLECTION_MARKET_ID:
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
{
this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
this.m_list_all_orders.Clear();
::ZeroMemory(this.m_struct_prev_market);
this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
this.m_list_all_orders.Type(COLLECTION_MARKET_ID);
}
Теперь списки коллекций имеют каждая свой идентификатор, который далее может нам облегчить опознание списков по их типам.
Так как мы будем добавлять новые коллекции для работы с новыми типами данных, а конкретно сегодня — коллекцию событий на счёте, то будем
использовать новые перечисления. И во избежания конфликта имён, нам необходимо
поменять имена некоторых ранее созданных макроподстановок:
#define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL)
#define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL)
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 = 10,
SORT_BY_ORDER_STATE = 11,
SORT_BY_ORDER_POSITION_ID = 12,
SORT_BY_ORDER_POSITION_BY_ID = 13,
SORT_BY_ORDER_DEAL_ORDER = 14,
SORT_BY_ORDER_DEAL_ENTRY = 15,
SORT_BY_ORDER_TIME_UPDATE = 16,
SORT_BY_ORDER_TIME_UPDATE_MSC = 17,
SORT_BY_ORDER_TICKET_FROM = 18,
SORT_BY_ORDER_TICKET_TO = 19,
SORT_BY_ORDER_PROFIT_PT = 20,
SORT_BY_ORDER_CLOSE_BY_SL = 21,
SORT_BY_ORDER_CLOSE_BY_TP = 22,
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,
SORT_BY_ORDER_TP = FIRST_ORD_DBL_PROP+3,
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,
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
};
Ну и поскольку мы сейчас правим файл Defines.mqh, то сразу же добавим в него все необходимые перечисления для классов событий и коллекции
событий счёта:
enum ENUM_EVENT_STATUS
{
EVENT_STATUS_MARKET_POSITION,
EVENT_STATUS_MARKET_PENDING,
EVENT_STATUS_HISTORY_PENDING,
EVENT_STATUS_HISTORY_POSITION,
EVENT_STATUS_BALANCE,
};
enum ENUM_EVENT_REASON
{
EVENT_REASON_ACTIVATED_PENDING = 0,
EVENT_REASON_ACTIVATED_PENDING_PARTIALLY = 1,
EVENT_REASON_CANCEL = 2,
EVENT_REASON_EXPIRED = 3,
EVENT_REASON_DONE = 4,
EVENT_REASON_DONE_PARTIALLY = 5,
EVENT_REASON_DONE_SL = 6,
EVENT_REASON_DONE_SL_PARTIALLY = 7,
EVENT_REASON_DONE_TP = 8,
EVENT_REASON_DONE_TP_PARTIALLY = 9,
EVENT_REASON_DONE_BY_POS = 10,
EVENT_REASON_DONE_PARTIALLY_BY_POS = 11,
EVENT_REASON_DONE_BY_POS_PARTIALLY = 12,
EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY = 13,
EVENT_REASON_BALANCE_REFILL = 14,
EVENT_REASON_BALANCE_WITHDRAWAL = 15,
EVENT_REASON_ACCOUNT_CREDIT = 16,
EVENT_REASON_ACCOUNT_CHARGE = 17,
EVENT_REASON_ACCOUNT_CORRECTION = 18,
EVENT_REASON_ACCOUNT_BONUS = 19,
EVENT_REASON_ACCOUNT_COMISSION = 20,
EVENT_REASON_ACCOUNT_COMISSION_DAILY = 21,
EVENT_REASON_ACCOUNT_COMISSION_MONTHLY = 22,
EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY = 23,
EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY = 24,
EVENT_REASON_ACCOUNT_INTEREST = 25,
EVENT_REASON_BUY_CANCELLED = 26,
EVENT_REASON_SELL_CANCELLED = 27,
EVENT_REASON_DIVIDENT = 28,
EVENT_REASON_DIVIDENT_FRANKED = 29,
EVENT_REASON_TAX = 30
};
#define REASON_EVENT_SHIFT (EVENT_REASON_ACCOUNT_CREDIT-3)
enum ENUM_EVENT_PROP_INTEGER
{
EVENT_PROP_TYPE_EVENT = 0,
EVENT_PROP_TIME_EVENT,
EVENT_PROP_STATUS_EVENT,
EVENT_PROP_REASON_EVENT,
EVENT_PROP_TYPE_DEAL_EVENT,
EVENT_PROP_TICKET_DEAL_EVENT,
EVENT_PROP_TYPE_ORDER_EVENT,
EVENT_PROP_TICKET_ORDER_EVENT,
EVENT_PROP_TIME_ORDER_POSITION,
EVENT_PROP_TYPE_ORDER_POSITION,
EVENT_PROP_TICKET_ORDER_POSITION,
EVENT_PROP_POSITION_ID,
EVENT_PROP_POSITION_BY_ID,
EVENT_PROP_MAGIC_ORDER,
};
#define EVENT_PROP_INTEGER_TOTAL (14)
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,
EVENT_PROP_PRICE_TP,
EVENT_PROP_VOLUME_INITIAL,
EVENT_PROP_VOLUME_EXECUTED,
EVENT_PROP_VOLUME_CURRENT,
EVENT_PROP_PROFIT
};
#define EVENT_PROP_DOUBLE_TOTAL (9)
enum ENUM_EVENT_PROP_STRING
{
EVENT_PROP_SYMBOL = (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL),
};
#define EVENT_PROP_STRING_TOTAL (1)
#define FIRST_EVN_DBL_PROP (EVENT_PROP_INTEGER_TOTAL)
#define FIRST_EVN_STR_PROP (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_EVENTS_MODE
{
SORT_BY_EVENT_TYPE_EVENT = 0,
SORT_BY_EVENT_TIME_EVENT = 1,
SORT_BY_EVENT_STATUS_EVENT = 2,
SORT_BY_EVENT_REASON_EVENT = 3,
SORT_BY_EVENT_TYPE_DEAL_EVENT = 4,
SORT_BY_EVENT_TICKET_DEAL_EVENT = 5,
SORT_BY_EVENT_TYPE_ORDER_EVENT = 6,
SORT_BY_EVENT_TYPE_ORDER_POSITION = 7,
SORT_BY_EVENT_TICKET_ORDER_EVENT = 8,
SORT_BY_EVENT_TICKET_ORDER_POSITION = 9,
SORT_BY_EVENT_POSITION_ID = 10,
SORT_BY_EVENT_POSITION_BY_ID = 11,
SORT_BY_EVENT_MAGIC_ORDER = 12,
SORT_BY_EVENT_TIME_ORDER_POSITION = 13,
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,
SORT_BY_EVENT_PRICE_TP = FIRST_EVN_DBL_PROP+4,
SORT_BY_EVENT_VOLUME_INITIAL = FIRST_EVN_DBL_PROP+5,
SORT_BY_EVENT_VOLUME = FIRST_EVN_DBL_PROP+6,
SORT_BY_EVENT_VOLUME_CURRENT = FIRST_EVN_DBL_PROP+7,
SORT_BY_EVENT_PROFIT = FIRST_EVN_DBL_PROP+8,
SORT_BY_EVENT_SYMBOL = FIRST_EVN_STR_PROP
};
Здесь перечислены все возможные статусы объектов-событий (по аналогии со статусами ордеров, рассмотренных нами в первой
статье), причины появления событий, все свойства событий и критерии сортировки событий для поиска по свойствам — всё это нам уже знакомо
из прошлых статей и расписано подробно. Для уточнения всегда можно
вернуться к началу и освежить в памяти информацию.
Помимо статуса события, дающего информацию о событии лишь общего характера, в причине события (ENUM_EVENT_REASON) уже содержится вся
уточняющая информация о происхождении конкретного события.
Например, если событие имеет статус рыночной позиции (EVENT_STATUS_MARKET_POSITION), то причина появления этого события указана в поле
объекта EVENT_PROP_REASON_EVENT, и может быть как срабатыванием отложенного ордера (EVENT_REASON_ACTIVATED_PENDING), так и
открытием позиции маркет-ордером (EVENT_REASON_DONE). При этом ещё и учитываются нюансы: так, если произошло частичное открытие
позиции (был исполнен не весь объём отложенного или маркет-ордера), то причиной события уже будут
EVENT_REASON_ACTIVATED_PENDING_PARTIALLY или EVENT_REASON_DONE_PARTIALLY, и т.п.
Таким образом, в объекте-событии у нас будет прописана вся информация как о самом событии, так и об ордере, срабатывание которого послужило
причиной появления данного события. Кроме того, в исторических событиях будет иметься информация о двух ордерах — о первом ордере
позиции, и о закрывающем ордере этой позиции.
Таким образом, имея данные об ордерах, сделках и самой позиции в объекте-событии, мы всегда сможем отследить всю цепочку происходящих
событий позиции за всю историю её существования — от открытия до закрытия.
Константы перечисления ENUM_EVENT_REASON расположены и пронумерованы таким образом, чтобы при статусе ордера события "сделка" тип сделки
попадал на значение перечисления
ENUM_DEAL_TYPE в случае, если тип сделки больше DEAL_TYPE_SELL. Таким образом мы попадаем на типы балансных
операций, и в причину события попадёт описание балансной операции при определении типа сделки в подготавливаемом для создания классе.
В макроподстановке #define REASON_EVENT_SHIFT
рассчитывается смещение, которое будем добавлять к типу сделки для указания на тип балансной операции в перечислении
ENUM_EVENT_REASON.
Для удобного вывода описаний ордеров, позиций и сделок, в файл DELib.mqh, находящийся в папке библиотеки Services добавим
функции, возвращающие описания
типов ордеров, позиций
и сделок, и функцию, возвращающую
наименование позиции в зависимости от типа открывающего её ордера:
string OrderTypeDescription(const ENUM_ORDER_TYPE type)
{
string pref=(#ifdef __MQL5__ "Market order" #else "Position" #endif );
return
(
type==ORDER_TYPE_BUY_LIMIT ? "Buy Limit" :
type==ORDER_TYPE_BUY_STOP ? "Buy Stop" :
type==ORDER_TYPE_SELL_LIMIT ? "Sell Limit" :
type==ORDER_TYPE_SELL_STOP ? "Sell Stop" :
#ifdef __MQL5__
type==ORDER_TYPE_BUY_STOP_LIMIT ? "Buy Stop Limit" :
type==ORDER_TYPE_SELL_STOP_LIMIT ? "Sell Stop Limit" :
type==ORDER_TYPE_CLOSE_BY ? TextByLanguage("Закрывающий ордер","Order for closing by") :
#else
type==ORDER_TYPE_BALANCE ? TextByLanguage("Балансовая операция","Balance operation") :
type==ORDER_TYPE_CREDIT ? TextByLanguage("Кредитная операция","Credit operation") :
#endif
type==ORDER_TYPE_BUY ? pref+" Buy" :
type==ORDER_TYPE_SELL ? pref+" Sell" :
TextByLanguage("Неизвестный тип ордера","Unknown order type")
);
}
string PositionTypeDescription(const ENUM_POSITION_TYPE type)
{
return
(
type==POSITION_TYPE_BUY ? "Buy" :
type==POSITION_TYPE_SELL ? "Sell" :
TextByLanguage("Неизвестный тип позиции","Unknown position type")
);
}
string DealTypeDescription(const ENUM_DEAL_TYPE type)
{
return
(
type==DEAL_TYPE_BUY ? TextByLanguage("Сделка на покупку","The deal to buy") :
type==DEAL_TYPE_SELL ? TextByLanguage("Сделка на продажу","The deal to sell") :
type==DEAL_TYPE_BALANCE ? TextByLanguage("Балансовая операция","Balance operation") :
type==DEAL_TYPE_CREDIT ? TextByLanguage("Начисление кредита","Credit") :
type==DEAL_TYPE_CHARGE ? TextByLanguage("Дополнительные сборы","Additional charge") :
type==DEAL_TYPE_CORRECTION ? TextByLanguage("Корректирующая запись","Correction") :
type==DEAL_TYPE_BONUS ? TextByLanguage("Перечисление бонусов","Bonus") :
type==DEAL_TYPE_COMMISSION ? TextByLanguage("Дополнительные комиссии","Additional comissions") :
type==DEAL_TYPE_COMMISSION_DAILY ? TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") :
type==DEAL_TYPE_COMMISSION_MONTHLY ? TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") :
type==DEAL_TYPE_COMMISSION_AGENT_DAILY ? TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") :
type==DEAL_TYPE_COMMISSION_AGENT_MONTHLY ? TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") :
type==DEAL_TYPE_INTEREST ? TextByLanguage("Начисления процентов на свободные средства","Agency commission charged at the end of the month") :
type==DEAL_TYPE_BUY_CANCELED ? TextByLanguage("Отмененная сделка покупки","Canceled buy transaction") :
type==DEAL_TYPE_SELL_CANCELED ? TextByLanguage("Отмененная сделка продажи","Canceled sell transaction") :
type==DEAL_DIVIDEND ? TextByLanguage("Начисление дивиденда","Dividend operations") :
type==DEAL_DIVIDEND_FRANKED ? TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
type==DEAL_TAX ? TextByLanguage("Начисление налога","Tax charges") :
TextByLanguage("Неизвестный тип сделки","Unknown deal type")
);
}
ENUM_POSITION_TYPE PositionTypeByOrderType(ENUM_ORDER_TYPE type_order)
{
if(
type_order==ORDER_TYPE_BUY ||
type_order==ORDER_TYPE_BUY_LIMIT ||
type_order==ORDER_TYPE_BUY_STOP
#ifdef __MQL5__ ||
type_order==ORDER_TYPE_BUY_STOP_LIMIT
#endif
) return POSITION_TYPE_BUY;
else if(
type_order==ORDER_TYPE_SELL ||
type_order==ORDER_TYPE_SELL_LIMIT ||
type_order==ORDER_TYPE_SELL_STOP
#ifdef __MQL5__ ||
type_order==ORDER_TYPE_SELL_STOP_LIMIT
#endif
) return POSITION_TYPE_SELL;
return WRONG_VALUE;
}
Забегая наперёд. При тестировании класса коллекции событий обнаружилась весьма неприятная проблема: при создании в терминале списков
ордеров и сделок при помощи
HistorySelect(), и последующего доступа к новым эдементам списков,
обнаружилось, что ордера попадают в список не в порядке свершения события, а по времени их установки. Поясню:
- откроем позицию,
- затем сразу же выставим отложенный ордер,
- затем закроем часть позиции
- дождёмся срабатывания отложенного ордера
По идее, и как ожидалось, порядок следования этих событий в истории должен был быть таким:
открытие позиции, установка
ордера, частичное закрытие, срабатывание ордера — всё в порядке проведения операций по времени. Но оказалось иначе, и порядок следования
этих событий в общей истории ордеров и сделок такой:
- открыта позиция
- установлен ордер
- сработал ордер
- частичное закрытие
Т.е., история ордеров и история сделок в терминале живут собственной жизнью и никак не коррелируют друг с другом, что тоже логично — два
списка — каждый со своей историей.
Класс же коллекции ордеров и сделок у нас сделан таким образом, что при изменении любого из списков — ордеров или сделок, считывается
последнее событие на счёте — чтобы не сканировать постоянно историю, что весьма накладно. Но в свете вышеописанного, и при проведении
торговых операций мы же не следим за последовательностью действий — поставил ордер, и ждёшь когда он сработает, открыл позицию, и
работаешь теперь только с ней. Только в такой ситуации все события будут располагаться в требуемом порядке для их безошибочного
отслеживания. Но это не правильно — нужно работать в любой последовательности, а программа должна уметь находить нужное событие и
точно на него указывать.
Исходя из вышеперечисленного, пришлось доработать класс-коллекцию исторических ордеров и событий. Теперь при появлении нового
события, и если оно не расположено в порядке следования появления его по последнему времени, то класс находит нужный ордер, создаёт
его объект и размещает в свой список последним — так, чтобы класс-коллекция событий всегда мог точно определять последнее
произошедшее событие.
Для реализации озвученного, добавим в класс-коллекцию исторических ордеров и сделок, в его приватную секцию три новых метода:
bool IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type);
ulong OrderSearch(const int start,ENUM_ORDER_TYPE &order_type);
bool CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type);
и за пределами тела класса — их реализацию.
Метод, возвращающий флаг наличия объекта-ордера в списке по его тикету и типу:
bool CHistoryCollection::IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type)
{
CArrayObj* list=dynamic_cast<CListObj*>(&this.m_list_all_orders);
list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,type,EQUAL);
list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,order_ticket,EQUAL);
return(list.Total()>0);
}
Создаём указатель на список при момощи динамического приведения типов (в
класс CSelect отправляем список CArrayObj, а списки-коллекции имеют тип CListObj — являются наследниками CArrayObj)
Оставляем в списке только ордера с
типом, переданным в метод входным параметром.
Оставляем в списке только
ордер с тикетом, переданным в метод входным параметром.
Если
такой ордер существует (список имеет размер больше нуля, возвращаем
true)
Метод, возвращающий тикет и тип ордера, расположенного не последним в списке терминала, но отсутствующий в списке-коллекции:
ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type)
{
ulong order_ticket=0;
for(int i=start-1;i>=0;i--)
{
ulong ticket=::HistoryOrderGetTicket(i);
if(ticket==0)
continue;
ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE);
if(this.IsPresentOrderInList(ticket,type))
continue;
order_ticket=ticket;
order_type=type;
}
return order_ticket;
}
В метод передаётся индекс последнего ордера в списке ордеров
терминала. Так как этот индекс указывает на ордер, который уже есть в коллекции, то
цикл поиска нужно начать с предыдущего ордера в списке (start-1).
В цикле от конца списка (ведь искомый ордер чаще будет нахоиться ближе к концу списка) ищем ордер с тикетом и типом, который отсутствует в
коллекции при помощи метода IsPresentOrderInList() —
если ордер есть в коллекции, то проверяем следующий. Как только
попадается ордер, отсутствующий в коллекции, то
записываем его тикет и тип
и возвращаем их в вызывающую программу — тикет результатом метода,
а тип —
в переменной по ссылке.
Так как теперь нужно создавать объекты-ордера в нескольких местах класса (при определении нового ордера и при нахождении "потерянного"),
то сделаем для этого отдельный
метод, создающий объект-ордер и помещающий его в список-коллекцию:
bool CHistoryCollection::CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type)
{
COrder* order=NULL;
if(order_type==ORDER_TYPE_BUY)
{
order=new CHistoryOrder(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_BUY_LIMIT)
{
order=new CHistoryPending(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_BUY_STOP)
{
order=new CHistoryPending(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_SELL)
{
order=new CHistoryOrder(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_SELL_LIMIT)
{
order=new CHistoryPending(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_SELL_STOP)
{
order=new CHistoryPending(order_ticket);
if(order==NULL)
return false;
}
#ifdef __MQL5__
else if(order_type==ORDER_TYPE_BUY_STOP_LIMIT)
{
order=new CHistoryPending(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_SELL_STOP_LIMIT)
{
order=new CHistoryPending(order_ticket);
if(order==NULL)
return false;
}
else if(order_type==ORDER_TYPE_CLOSE_BY)
{
order=new CHistoryOrder(order_ticket);
if(order==NULL)
return false;
}
#endif
if(this.m_list_all_orders.InsertSort(order))
return true;
else
{
delete order;
return false;
}
return false;
}
Здесь всё просто и наглядно: в метод передаются тикет и тип ордера, и в зависимости от типа ордера, создаётся новый объект-ордер. Если объект не
удалось создать, то сразу возвращается
false. При удачном создании объекта, он помещается в коллекцию и возвращается true.
Если не удалось поместить его в коллекцию — удаляется вновь созданный объект и возвращается
false.
Изменим метод класса-коллекции Refresh(), так как нужно обрабатывать факт "потери" нужного ордера:
void CHistoryCollection::Refresh(void)
{
#ifdef __MQL4__
int total=::OrdersHistoryTotal(),i=m_index_order;
for(; i<total; i++)
{
if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
{
CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
if(order==NULL) continue;
if(!this.m_list_all_orders.InsertSort(order))
{
::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
delete order;
}
}
else
{
CHistoryPending *order=new CHistoryPending(::OrderTicket());
if(order==NULL) continue;
if(!this.m_list_all_orders.InsertSort(order))this.m_list_all_orders.Type()
{
::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
delete order;
}
}
}
int delta_order=i-m_index_order;
this.m_index_order=i;
this.m_delta_order=delta_order;
this.m_is_trade_event=(this.m_delta_order!=0 ? true : false);
#else
if(!::HistorySelect(0,END_TIME)) return;
int total_orders=::HistoryOrdersTotal(),i=m_index_order;
for(; i<total_orders; i++)
{
ulong order_ticket=::HistoryOrderGetTicket(i);
if(order_ticket==0) continue;
ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
{
if(!this.IsPresentOrderInList(order_ticket,type))
{
if(!this.CreateNewOrder(order_ticket,type))
::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
}
else
{
ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
ulong ticket_lost=this.OrderSearch(i,type_lost);
if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
}
}
else
{
if(!this.IsPresentOrderInList(order_ticket,type))
{
if(!this.CreateNewOrder(order_ticket,type))
::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
}
else
{
ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
ulong ticket_lost=this.OrderSearch(i,type_lost);
if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
}
}
}
int delta_order=i-this.m_index_order;
this.m_index_order=i;
this.m_delta_order=delta_order;
int total_deals=::HistoryDealsTotal(),j=m_index_deal;
for(; j<total_deals; j++)
{
ulong deal_ticket=::HistoryDealGetTicket(j);
if(deal_ticket==0) continue;
CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
if(deal==NULL) continue;
if(!this.m_list_all_orders.InsertSort(deal))
{
::Print(DFUN,TextByLanguage("Не удалось добавить сделку в список","Could not add deal to the list"));
delete deal;
}
}
int delta_deal=j-this.m_index_deal;
this.m_index_deal=j;
this.m_delta_deal=delta_deal;
this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif
}
В методе изменён блок обработки новых ордеров для MQL5. Все
произведённые изменения описаны комментариями и
выделены в тексе листинга.
Для поиска одинаковых ордеров, в публичной секции класса COrder добавим определение метода:
bool IsEqual(COrder* compared_order) const;
И за пределами тела класса — его реализацию:
bool COrder::IsEqual(COrder *compared_order) const
{
int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false;
}
beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false;
}
beg=end; end+=ORDER_PROP_STRING_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false;
}
return true;
}
Метод проходит в цикле по всем свойствам текущего объекта-ордера и сравниваемого
ордера, переданного указателем в метод.
Как только встречается любое из свойств текущего ордера, не равное такому же свойству
сравниваемого ордера — возвращается
false — ордера не равны.
Классы событий
Подготовительный этап завершён. Приступим к созданию классов объектов-событий.
Поступим ровно так же, как и при создании классов-ордеров. Создадим базовый класс-событие и пять классов-наследников, описываемых их
статусами:
- событие открытия позиции,
- событие закрытия позиции,
- событие выставления отложенного ордера,
- событие удаления отложенного ордера,
- событие балансной операции
В ранее созданной нами папке Events в каталоге библиотеки Objects создадим новый класс CEvent, унаследованный от базового класса
CObject. Во вновь созданном шаблоне класса пропишем необходимые подключения файла сервисных функций, классов коллекций ордеров,
приватные и защищённые члены и методы класса:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#property strict
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
class CEvent : public CObject
{
private:
int m_event_code;
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;
long m_chart_id;
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){;}
void SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; }
void SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; }
void SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; }
long GetProperty(ENUM_EVENT_PROP_INTEGER property) const { return this.m_long_prop[property]; }
double GetProperty(ENUM_EVENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; }
string GetProperty(ENUM_EVENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; }
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property) { return true; }
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { return true; }
virtual bool SupportProperty(ENUM_EVENT_PROP_STRING property) { return true; }
void SetChartID(const long id) { this.m_chart_id=id; }
void SetTypeEvent(void);
ENUM_TRADE_EVENT TradeEvent(void) const { return this.m_trade_event; }
virtual void SendEvent(void) {;}
virtual int Compare(const CObject *node,const int mode=0) const;
bool IsEqual(CEvent* compared_event) const;
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); }
long TypeDeal(void) const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT); }
long TicketDeal(void) const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT); }
long TypeOrderEvent(void) const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT); }
long TypeOrderPosition(void) const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION); }
long TicketOrderEvent(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT); }
long TicketOrderPosition(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 TimePosition(void) const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION); }
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 VolumeInitial(void) const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL); }
double VolumeExecuted(void) const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED); }
double VolumeCurrent(void) const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT); }
string Symbol(void) const { return this.GetProperty(EVENT_PROP_SYMBOL); }
string GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
string GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
string GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
string StatusDescription(void) const;
string TypeEventDescription(void) const;
string TypeOrderDescription(void) const;
string TypeOrderBasedDescription(void) const;
string TypePositionDescription(void) const;
string ReasonDescription(void) const;
void Print(const bool full_prop=false);
virtual void PrintShort(void) {;}
};
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
{
this.m_long_prop[EVENT_PROP_STATUS_EVENT] = event_status;
this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] = (long)ticket;
this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
this.m_chart_id=::ChartID();
}
В конструктор передаются статус
события, код торгового события и тикет
ордера или сделки, которые стали причиной появления события.
Здесь практически всё то же самое, что и в защищённом конструкторе ранее
рассмотренного нами класса COrder в первой части описания библиотеки.
Отличием является то, что в защищённом конструкторе класса
заполняются только два свойства события — статус события и тикет ордера или сделки, послужившими причиной появления события. Тип события
выявляется и сохраняется в методе класса SetTypeEvent() исходя из переданного в конструктор кода события, а все остальные свойства
события выявляются из состояния ордеров и сделок, причастных к данному событию, и устанавливаются соответствующими методами класса
отдельно. Сделано так потому, что события будут выявляться в классе-коллекции событий, и там будет метод, устанавливающий все свойства
вновь создаваемому событию.
Код события (m_event_code) и его заполнение и
интерпретация нами были рассмотрены в
четвёртой части описания библиотеки. Мы его перенесли из класса CEngine сюда,
так как в базовом классе библиотеки он был лишь временно, и лишь для проверки концепции работы с событиями. Теперь он будет рассчитываться в
классе-коллекции событий и передаваться в конструктор этого класса при создании объекта-события.
Само же торговое событие (m_trade_event) будет
составляться методом расшифровки кода события в методе SetTypeEvent(), а этот метод расшифровки кода события мы также рассмотрели в
четвёртой статье.
Идентификатор
графика управляющей программы (
m_chart_id) нам необходим для отправки в него пользовательских сообщений о событиях.
Количество
знаков после запятой для валюты счёта (
m_digits_acc) нам необходимо для корректного вывода сообщений о событиях в журнал.
Методы сравнения свойств объекта-события Compare() и IsEqual()
достаточно просты и прозрачны. Метод Compare() нами рассматривался в первой части описания библиотеки, и он совершенно такой же, как и у объекта
COrder, метод IsEqual(), в отличии от первого метода, сравнивающего два объекта только по одному из свойств, сравнивает эти два объекта по
всем полям. Если все поля двух объектов одинаковы — каждое отдельное свойство текущего объекта равно соответствующему свойству
сравниваемого объекта, то оба объекта одинаковы. Метод проверяет в цикле все свойства двух объектов и при первой же встрече неравенства
свойств сразу возвращает
false — далее проверять бессмысленно — одно из свойств объекта уже не равно такому же
свойству сравниваемого объекта.
int CEvent::Compare(const CObject *node,const int mode=0) const
{
const CEvent *event_compared=node;
if(mode<EVENT_PROP_INTEGER_TOTAL)
{
long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
}
if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
{
double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
}
else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
{
string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
}
return 0;
}
bool CEvent::IsEqual(CEvent *compared_event) const
{
int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false;
}
beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false;
}
beg=end; end+=EVENT_PROP_STRING_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false;
}
return true;
}
Рассмотрим подробнее метод SetTypeEvent()
Здесь прямо в коде в комментариях прописаны все производящиеся необходимые проверки и действия:
void CEvent::SetTypeEvent(void)
{
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_POSITION_OPENED))
{
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))
{
if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
{
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;
}
else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
{
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);
}
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;
}
}
Здесь всё просто: в метод передаётся код события, и далее проверяются флаги кода события. При наличии в коде проверяемого флага
устанавливается соответствующее торговое событие. Так как флагов в коде события может быть несколько, то проверяются все возможные
флаги для данного события и из их комбинации определяется тип события. Далее тип события записывается в соответствующую переменную
класса и вносится в свойство объекта-события (EVENT_PROP_TYPE_EVENT).
Рассмотрим листинги остальных методов класса:
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_TIME_ORDER_POSITION ? TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(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_INITIAL ? TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl) :
property==EVENT_PROP_VOLUME_EXECUTED ? TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl) :
property==EVENT_PROP_VOLUME_CURRENT ? TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl) :
property==EVENT_PROP_PROFIT ? TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc) :
""
);
}
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
{
return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
}
string CEvent::StatusDescription(void) const
{
ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
return
(
status==EVENT_STATUS_MARKET_PENDING ? TextByLanguage("Установлен отложенный ордер","Pending order placed") :
status==EVENT_STATUS_MARKET_POSITION ? TextByLanguage("Открыта позиция","Position is open") :
status==EVENT_STATUS_HISTORY_PENDING ? TextByLanguage("Удален отложенный ордер","Pending order removed") :
status==EVENT_STATUS_HISTORY_POSITION ? TextByLanguage("Закрыта позиция","Position closed") :
status==EVENT_STATUS_BALANCE ? TextByLanguage("Балансная операция","Balance operation") :
""
);
}
string CEvent::TypeEventDescription(void) const
{
ENUM_TRADE_EVENT event=this.TypeEvent();
return
(
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 ? TextByLanguage("Разворот позиции","Position reversal") :
event==TRADE_EVENT_POSITION_VOLUME_ADD ? TextByLanguage("Добавлен объём к позиции","Added volume to position") :
TextByLanguage("Нет торгового события","No trade event")
);
}
string CEvent::TypeOrderDescription(void) const
{
ENUM_EVENT_STATUS status=this.Status();
return
(
status==EVENT_STATUS_MARKET_PENDING || status==EVENT_STATUS_HISTORY_PENDING ? OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT)) :
status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ? PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
status==EVENT_STATUS_BALANCE ? DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) : "Unknown"
);
}
string CEvent::TypeOrderBasedDescription(void) const
{
return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
}
string CEvent::TypePositionDescription(void) const
{
ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
return PositionTypeDescription(type);
}
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_CANCEL ? TextByLanguage("Отмена","Canceled") :
reason==EVENT_REASON_EXPIRED ? TextByLanguage("Истёк срок действия","Expired") :
reason==EVENT_REASON_DONE ? TextByLanguage("Запрос выполнен полностью","Request is fully executed") :
reason==EVENT_REASON_DONE_PARTIALLY ? TextByLanguage("Запрос выполнен частично","Request is partially executed") :
reason==EVENT_REASON_DONE_SL ? TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered") :
reason==EVENT_REASON_DONE_SL_PARTIALLY ? TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered") :
reason==EVENT_REASON_DONE_TP ? TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered") :
reason==EVENT_REASON_DONE_TP_PARTIALLY ? TextByLanguage("Частичное закрытие по TakeProfit","Partial 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)
);
}
void CEvent::Print(const bool full_prop=false)
{
::Print("============= ",TextByLanguage("Начало списка параметров события: \"","The beginning of the event parameter list: \""),this.StatusDescription(),"\" =============");
int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
if(!full_prop && !this.SupportProperty(prop)) continue;
::Print(this.GetPropertyDescription(prop));
}
::Print("------");
beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
if(!full_prop && !this.SupportProperty(prop)) continue;
::Print(this.GetPropertyDescription(prop));
}
::Print("------");
beg=end; end+=EVENT_PROP_STRING_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
if(!full_prop && !this.SupportProperty(prop)) continue;
::Print(this.GetPropertyDescription(prop));
}
::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of the parameter list: \""),this.StatusDescription(),"\" ==================\n");
}
Все эти методы — их логика — повторяют уже рассмотренные нами методы вывода информации об ордерах, поэтому не будем заострять на них
внимание, тут всё достаточно просто и наглядно.
Полный листинг класса-события:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#property strict
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
class CEvent : public CObject
{
private:
int m_event_code;
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;
long m_chart_id;
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){;}
void SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; }
void SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; }
void SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; }
long GetProperty(ENUM_EVENT_PROP_INTEGER property) const { return this.m_long_prop[property]; }
double GetProperty(ENUM_EVENT_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; }
string GetProperty(ENUM_EVENT_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; }
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property) { return true; }
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { return true; }
virtual bool SupportProperty(ENUM_EVENT_PROP_STRING property) { return true; }
void SetChartID(const long id) { this.m_chart_id=id; }
void SetTypeEvent(void);
ENUM_TRADE_EVENT TradeEvent(void) const { return this.m_trade_event; }
virtual void SendEvent(void) {;}
virtual int Compare(const CObject *node,const int mode=0) const;
bool IsEqual(CEvent* compared_event);
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); }
long TypeDeal(void) const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT); }
long TicketDeal(void) const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT); }
long TypeOrderEvent(void) const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT); }
long TypeOrderPosition(void) const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION); }
long TicketOrderEvent(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT); }
long TicketOrderPosition(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 TimePosition(void) const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION); }
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 VolumeInitial(void) const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL); }
double VolumeExecuted(void) const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED); }
double VolumeCurrent(void) const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT); }
string Symbol(void) const { return this.GetProperty(EVENT_PROP_SYMBOL); }
string GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
string GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
string GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
string StatusDescription(void) const;
string TypeEventDescription(void) const;
string TypeOrderDescription(void) const;
string TypeOrderBasedDescription(void) const;
string TypePositionDescription(void) const;
string ReasonDescription(void) const;
void Print(const bool full_prop=false);
virtual void PrintShort(void) {;}
};
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
{
this.m_long_prop[EVENT_PROP_STATUS_EVENT] = event_status;
this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] = (long)ticket;
this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
this.m_chart_id=::ChartID();
}
int CEvent::Compare(const CObject *node,const int mode=0) const
{
const CEvent *event_compared=node;
if(mode<EVENT_PROP_INTEGER_TOTAL)
{
long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
}
if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
{
double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
}
else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
{
string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
}
return 0;
}
bool CEvent::IsEqual(CEvent *compared_event)
{
int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false;
}
beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false;
}
beg=end; end+=EVENT_PROP_STRING_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false;
}
return true;
}
void CEvent::SetTypeEvent(void)
{
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_POSITION_OPENED))
{
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))
{
if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
{
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;
}
else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
{
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);
}
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;
}
}
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_TIME_ORDER_POSITION ? TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(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_INITIAL ? TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl) :
property==EVENT_PROP_VOLUME_EXECUTED ? TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl) :
property==EVENT_PROP_VOLUME_CURRENT ? TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl) :
property==EVENT_PROP_PROFIT ? TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc) :
""
);
}
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
{
return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
}
string CEvent::StatusDescription(void) const
{
ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
return
(
status==EVENT_STATUS_MARKET_PENDING ? TextByLanguage("Установлен отложенный ордер","Pending order placed") :
status==EVENT_STATUS_MARKET_POSITION ? TextByLanguage("Открыта позиция","Position is open") :
status==EVENT_STATUS_HISTORY_PENDING ? TextByLanguage("Удален отложенный ордер","Pending order removed") :
status==EVENT_STATUS_HISTORY_POSITION ? TextByLanguage("Закрыта позиция","Position closed") :
status==EVENT_STATUS_BALANCE ? TextByLanguage("Балансная операция","Balance operation") :
""
);
}
string CEvent::TypeEventDescription(void) const
{
ENUM_TRADE_EVENT event=this.TypeEvent();
return
(
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 ? TextByLanguage("Разворот позиции","Position reversal") :
event==TRADE_EVENT_POSITION_VOLUME_ADD ? TextByLanguage("Добавлен объём к позиции","Added volume to position") :
TextByLanguage("Нет торгового события","No trade event")
);
}
string CEvent::TypeOrderDescription(void) const
{
ENUM_EVENT_STATUS status=this.Status();
return
(
status==EVENT_STATUS_MARKET_PENDING || status==EVENT_STATUS_HISTORY_PENDING ? OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT)) :
status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ? PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
status==EVENT_STATUS_BALANCE ? DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) : "Unknown"
);
}
string CEvent::TypeOrderBasedDescription(void) const
{
return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
}
string CEvent::TypePositionDescription(void) const
{
ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
return PositionTypeDescription(type);
}
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_CANCEL ? TextByLanguage("Отмена","Canceled") :
reason==EVENT_REASON_EXPIRED ? TextByLanguage("Истёк срок действия","Expired") :
reason==EVENT_REASON_DONE ? TextByLanguage("Запрос выполнен полностью","Request is fully executed") :
reason==EVENT_REASON_DONE_PARTIALLY ? TextByLanguage("Запрос выполнен частично","Request is partially executed") :
reason==EVENT_REASON_DONE_SL ? TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered") :
reason==EVENT_REASON_DONE_SL_PARTIALLY ? TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered") :
reason==EVENT_REASON_DONE_TP ? TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered") :
reason==EVENT_REASON_DONE_TP_PARTIALLY ? TextByLanguage("Частичное закрытие по TakeProfit","Partial 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)
);
}
void CEvent::Print(const bool full_prop=false)
{
::Print("============= ",TextByLanguage("Начало списка параметров события: \"","The beginning of the event parameter list: \""),this.StatusDescription(),"\" =============");
int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
if(!full_prop && !this.SupportProperty(prop)) continue;
::Print(this.GetPropertyDescription(prop));
}
::Print("------");
beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
if(!full_prop && !this.SupportProperty(prop)) continue;
::Print(this.GetPropertyDescription(prop));
}
::Print("------");
beg=end; end+=EVENT_PROP_STRING_TOTAL;
for(int i=beg; i<end; i++)
{
ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
if(!full_prop && !this.SupportProperty(prop)) continue;
::Print(this.GetPropertyDescription(prop));
}
::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of the parameter list: \""),this.StatusDescription(),"\" ==================\n");
}
Класс абстрактного базового события готов. Теперь нам нужно создать пять классов-потомков, которые будут являться событием, указывающим
на его тип: установка отложенного ордера, удаление отложенного ордера, открытие позиции, закрытие позиции и баланстная операция.
Создадим класс-наследник, имеющий статус события "Установка отложенного ордера".
В папке библиотеки Events создадим новый файл класса CEventOrderPlased с именем EventOrderPlased.mqh с
базовым классом CEvent и пропишем в него необходимые подключения и методы:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Event.mqh"
class CEventOrderPlased : public CEvent
{
public:
CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property);
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
virtual void PrintShort(void);
virtual void SendEvent(void);
};
В конструктор класса передаём код события, тикет
ордера или сделки, послужившей причиной события, и в списке инициализации отправляем в родительский класс статус события "Установка
отложенного ордера" (EVENT_STATUS_MARKET_PENDING), код события и тикет ордера или сделки:
CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
Методы, возвращающие флаги поддержания объектом тех или иных свойств SupportProperty(), мы уже рассматривали в первой части описания
библиотеки — тут всё точно так же:
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
{
if(property==EVENT_PROP_TYPE_DEAL_EVENT ||
property==EVENT_PROP_TICKET_DEAL_EVENT ||
property==EVENT_PROP_TYPE_ORDER_POSITION ||
property==EVENT_PROP_TICKET_ORDER_POSITION ||
property==EVENT_PROP_POSITION_ID ||
property==EVENT_PROP_POSITION_BY_ID ||
property==EVENT_PROP_TIME_ORDER_POSITION
) return false;
return true;
}
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
{
if(property==EVENT_PROP_PRICE_CLOSE ||
property==EVENT_PROP_PROFIT
) return false;
return true;
}
В родительском объекте-событии CEvent есть метод Print(), который выводит полную информацию о всех поддерживаемых свойствах данного
объекта-события, и есть виртуальный метод PrintShort(), позволяющий в две строки вывести достаточную информацию о событии в журнал
терминала.
Реализация метода PrintShort() в каждом классе-наследнике базового объекта-события будет индивидуальна — ведь и события различаются по своему
происхождению:
void CEventOrderPlased::PrintShort(void)
{
string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
::Print(txt);
}
Здесь:
- Создаём заголовок сообщения, состоящий из описания типа события и времени события
- Если у ордера задан StopLoss, то создаём строку с его описанием, иначе — строка пустая
- Если у ордера задан TakeProfit, то создаём строку с его описанием, иначе — строка пустая
- Создаём строку с указанием объёма ордера
- Если у ордера задан магический номер, то создаём строку с его описанием, иначе — строка пустая
- Создаём строку с указанием типа ордера и его тикета
- Создаём строку с указанием цены установки ордера и символа, на котором установлен ордер
- Создаём полную строку из всех вышеперечисленных описаний
- Выводим созданную строку в журнал
Метод, отправляющий пользовательское событие на график достаточно прост:
void CEventOrderPlased::SendEvent(void)
{
this.PrintShort();
::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
}
Сначала выводится короткое сообщение о событии в журнал,
затем отправляется пользовательское событие
EventChartCustom() на график, указанный в
идентификаторе графика
m_chart_id в базовом классе-событии CEvent.
В
идентификатор события отправляем событие
m_trade_event,
в параметр типа long —
тикет ордера,
в параметр типа double —
цену установки ордера,
в параметр типа string —
символ ордера.
Хочу отметить, что в последующем будет создан класс для вывода сообщений, где можно будет задавать уровни сообщений, позволяющие
выводить только необходимую информацию в журнал. Сейчас — на этапе построения библиотеки — все сообщения выводятся по умолчанию.
Рассмотрим полные листинги остальных классов-событий.
Класс-событие "Удаление отложенного ордера":
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Event.mqh"
class CEventOrderRemoved : public CEvent
{
public:
CEventOrderRemoved(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_PENDING,event_code,ticket) {}
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property);
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
virtual void PrintShort(void);
virtual void SendEvent(void);
};
bool CEventOrderRemoved::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_TIME_ORDER_POSITION
) return false;
return true;
}
bool CEventOrderRemoved::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
{
return(property==EVENT_PROP_PROFIT ? false : true);
}
void CEventOrderRemoved::PrintShort(void)
{
string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
::Print(txt);
}
void CEventOrderRemoved::SendEvent(void)
{
this.PrintShort();
::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
}
Класс-событие "Открытие позиции":
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Event.mqh"
class CEventPositionOpen : public CEvent
{
public:
CEventPositionOpen(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_POSITION,event_code,ticket) {}
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property);
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
virtual void PrintShort(void);
virtual void SendEvent(void);
};
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
{
return(property==EVENT_PROP_POSITION_BY_ID ? false : true);
}
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
{
if(property==EVENT_PROP_PRICE_CLOSE ||
property==EVENT_PROP_PROFIT
) return false;
return true;
}
void CEventPositionOpen::PrintShort(void)
{
string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
string order=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? " #"+(string)this.TicketOrderPosition() : "");
string activated=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? TextByLanguage(" активацией ордера "," by ")+this.TypeOrderBasedDescription() : "");
string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
string type=this.TypePositionDescription()+" #"+(string)this.PositionID();
string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
string txt=head+this.Symbol()+" "+vol+" "+type+activated+order+price+sl+tp+magic;
::Print(txt);
}
void CEventPositionOpen::SendEvent(void)
{
this.PrintShort();
::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceOpen(),this.Symbol());
}
Класс-событие "Закрытие позиции":
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Event.mqh"
class CEventPositionClose : public CEvent
{
public:
CEventPositionClose(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_POSITION,event_code,ticket) {}
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property);
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
virtual void PrintShort(void);
virtual void SendEvent(void);
};
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
{
return true;
}
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
{
return true;
}
void CEventPositionClose::PrintShort(void)
{
string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
string opposite=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS) ? " by "+this.TypeOrderDescription()+" #"+(string)this.PositionByID() : "");
string vol=::DoubleToString(this.VolumeExecuted(),DigitsLots(this.Symbol()));
string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
string type=this.TypePositionDescription()+" #"+(string)this.PositionID()+opposite;
string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceClose(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
string profit=TextByLanguage(", профит: ",", profit: ")+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY);
string txt=head+this.Symbol()+" "+vol+" "+type+price+magic+profit;
::Print(txt);
}
void CEventPositionClose::SendEvent(void)
{
this.PrintShort();
::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceClose(),this.Symbol());
}
Класс-событие "Балансная операция":
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Event.mqh"
class CEventBalanceOperation : public CEvent
{
public:
CEventBalanceOperation(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_BALANCE,event_code,ticket) {}
virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property);
virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
virtual bool SupportProperty(ENUM_EVENT_PROP_STRING property);
virtual void PrintShort(void);
virtual void SendEvent(void);
};
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
{
if(property==EVENT_PROP_TYPE_ORDER_EVENT ||
property==EVENT_PROP_TYPE_ORDER_POSITION ||
property==EVENT_PROP_TICKET_ORDER_EVENT ||
property==EVENT_PROP_TICKET_ORDER_POSITION ||
property==EVENT_PROP_POSITION_ID ||
property==EVENT_PROP_POSITION_BY_ID ||
property==EVENT_PROP_POSITION_ID ||
property==EVENT_PROP_MAGIC_ORDER ||
property==EVENT_PROP_TIME_ORDER_POSITION
) return false;
return true;
}
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
{
return(property==EVENT_PROP_PROFIT ? true : false);
}
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_STRING property)
{
return false;
}
void CEventBalanceOperation::PrintShort(void)
{
string head="- "+this.StatusDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
::Print(head+this.TypeEventDescription()+": "+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY));
}
void CEventBalanceOperation::SendEvent(void)
{
this.PrintShort();
::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TypeEvent(),this.Profit(),::AccountInfoString(ACCOUNT_CURRENCY));
}
Как можно увидеть из листингов, классы различаются лишь по набору поддерживаемых свойств, по статусу, отправляемому в конструктор
родительского класса, и по методам PrintShort(), так как у каждого события есть свои особенности, которые необходимо отразить в журнале.
Всё это достаточно понятно из листингов методов, и их можно разобрать самостоятельно, поэтому не будем заострять на них внимание, а
перейдём к созданию класса-коллекции событий.
Коллекция торговых событий
В четвёртой части описания библиотеки нами было протестировано
определение событий на счёте и вывод их в журнал и в советник. Но мы могли отслеживать только последнее произошедшее событие, да и
организовано всё было в базовом классе библиотеки CEngine.
Правильным же решением будет всё вынести в отдельный класс и обрабатывать в нём все происходящие события.
Для этих целей мы создали объекты-события. Теперь необходимо сделать класс, обрабатывающий любое количество одновременно произошедших
событий. Ведь может быть ситуация, когда в одном цикле, например, удаляются или устанавливаются отложенные ордера, либо закрываются
сразу несколько позиций.
Тот принцип, который был уже нами протестирован, в таких ситуациях выдавал бы нам лишь самое последнее событие из нескольких, выполненных
за раз, событий. Это я считаю не верным. Поэтому сделаем класс, который будет сохранять в список-коллекцию событий все события, которые
произошли за один раз. И к тому же, в будущем возможно будет при помощи методов данных классов пройтись по истории счёта и воссоздать всё, что
происходило на нём с момента его открытия.
В папке DoEasy\Collections создадим новый файл класса CEventsCollection под именем EventsCollection.mqh.
Базовым классом нужно сделать класс
CListObj.
Сразу же заполним вновь созданный шаблон класса всеми необходимыми нам подключениями, членами и методами:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\EventBalanceOperation.mqh"
#include "..\Objects\Events\EventOrderPlaced.mqh"
#include "..\Objects\Events\EventOrderRemoved.mqh"
#include "..\Objects\Events\EventPositionOpen.mqh"
#include "..\Objects\Events\EventPositionClose.mqh"
class CEventsCollection : public CListObj
{
private:
CListObj m_list_events;
bool m_is_hedge;
long m_chart_id;
ENUM_TRADE_EVENT m_trade_event;
CEvent m_event_instance;
void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
CArrayObj* GetListMarketPendings(CArrayObj* list);
CArrayObj* GetListHistoryPendings(CArrayObj* list);
CArrayObj* GetListDeals(CArrayObj* list);
CArrayObj* GetListCloseByOrders(CArrayObj* list);
CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id);
COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
COrder* GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
bool IsPresentEventInList(CEvent* compared_event);
public:
CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
CArrayObj *GetList(void) { return &this.m_list_events; }
CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); }
CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); }
CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); }
void Refresh(CArrayObj* list_history,
CArrayObj* list_market,
const bool is_history_event,
const bool is_market_event,
const int new_history_orders,
const int new_market_pendings,
const int new_market_positions,
const int new_deals);
void SetChartID(const long id) { this.m_chart_id=id; }
ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; }
void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; }
CEventsCollection(void);
};
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_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();
}
В конструкторе класса в его списке инициализации сбрасываем торговое событие,
в теле конструктора очищаем список-коллекцию,
устанавливаем
его сортировку по времени события,
задаём идентификатор
списка-коллекции события,
устанавливаем флаг счёта с типом хедж и
задаём
идентификатор графика управляющей программы как текущий график.
Рассмотрим методы, необходимые для работы класса.
В приватной секции класса объявлены члены класса:
CListObj m_list_events;
bool m_is_hedge;
long m_chart_id;
ENUM_TRADE_EVENT m_trade_event;
CEvent m_event_instance;
Список событий m_list_events—
список на основе CListObj. В него будем добавлять и хранить события, происходящие на счёте с момента запуска программы. Из него же будем
получать необходимое количество множества событий, произошедших одним разом.
Флаг счёта с типом хедж m_is_hedgeнам
потребуется для хранения и получения типа счёта. От значения флага (от типа счёта) будет зависеть какой блок будет обрабатывать
происходящие события на счёте
Идентификатор графика управляющей программы m_chart_id—
идентификатор графика, на который будут отсылаться пользовательские события о происходящем на счёте. Этот идентификатор будет
отправляться в объекты-события, и уже из них будут отсылаться события на график. Идентификатор можно устанавливать из управляющей
программы методом, созданным для этой цели.
Торговое событие на счёте m_trade_event—
в данной переменной будем хранить последнее событие, произошедшее на счёте.
Объект-событие для поиска по свойству m_event_instance—
специальный объект-образец для внутреннего использования в методе, возвращающем список событий с заданными датами начала и конца
диапазона поиска. Точно такой же метод рассматривался нами в
третьей части описания библиотеки при обсуждении организации поиска в
списках по различным критериям.
Здесь же, в приватной секции, расположены необходимые для работы класса методы:
void CreateNewEvent(COrder* order,CArrayObj* list_history);
CArrayObj* GetListMarketPendings(CArrayObj* list);
CArrayObj* GetListHistoryOrders(CArrayObj* list);
CArrayObj* GetListHistoryPendings(CArrayObj* list);
CArrayObj* GetListDeals(CArrayObj* list);
CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
CArrayObj* GetListAllCloseByOrders(CArrayObj* list);
double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id);
COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
COrder* GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
bool IsPresentEventInList(CEvent* compared_event);
Метод CreateNewEvent(), создающий торговое событие в зависимости от статуса ордера, используется в основном методе класса Refresh(),
и его мы рассмотрим позже, при обсуждении метода Refresh()
Методы получения списков различных типов ордеров достаточно просты — принцип работы выбора по заданному свойству обсуждался в
третьей части описания библиотеки, и здесь можно лишь вкратце упомянуть, что некоторые методы состоят из нескольких итераций выбора
по нужным свойствам.
Метод получения списка рыночных отложенных ордеров:
CArrayObj* CEventsCollection::GetListMarketPendings(CArrayObj* list)
{
if(list.Type()!=COLLECTION_MARKET_ID)
{
Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection"));
return NULL;
}
CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
return list_orders;
}
Сначала проверяется тип переданного в метод списка, и если он не является списком рыночной коллекции, то выводится сообщение об ошибке и
возвращается пустой список.
Затем из переданного в метод списка выбираются ордера по статусу "Рыночный отложенный ордер" и возвращается полученный список.
Методы получения списков удалённых отложенных ордеров, сделок
и закрывающих ордеров, выставляемых при закрытии позиции
встречной:
CArrayObj* CEventsCollection::GetListHistoryPendings(CArrayObj* list)
{
if(list.Type()!=COLLECTION_HISTORY_ID)
{
Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
return NULL;
}
CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
return list_orders;
}
CArrayObj* CEventsCollection::GetListDeals(CArrayObj* list)
{
if(list.Type()!=COLLECTION_HISTORY_ID)
{
Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
return NULL;
}
CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
return list_deals;
}
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
{
if(list.Type()!=COLLECTION_HISTORY_ID)
{
Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
return NULL;
}
CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
return list_orders;
}
Здесь точно так же, как и при возврате списка действующих отложенных ордеров:
Проверяется тип списка, и если список не является списком исторической коллекции, то выводится сообщение и возвращается NULL.
Далее из переданного в
метод списка выбираются ордера по статусу "Удалённый отложенный ордер", "Сделка", или выбираются ордера по типу ORDER_TYPE_CLOSE_BY в
зависимости от метода, и возвращается полученный список.
Метод получения списка всех ордеров, принадлежащих позиции по её идентификатору:
CArrayObj* CEventsCollection::GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id)
{
CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
return list_orders;
}
Сначала из переданного в метод списка выбираем в отдельный список все объекты,
в которых есть указание на идентификатор позиции, переданный в метод его параметром,
Затем из
полученного списка удаляются все сделки, и окончательный
список возвращается в вызывающую программу. Результатом возврата метода может быть NULL, поэтому в вызывающей программе необходимо
проверять что нам вернул данный метод.
Метод получения списка всех сделок, принадлежащих позиции по её идентификатору:
CArrayObj* CEventsCollection::GetListAllDealsByPosID(CArrayObj *list,const ulong position_id)
{
if(list.Type()!=COLLECTION_HISTORY_ID)
{
Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
return NULL;
}
CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
return list_deals;
}
Сначала проверяется тип списка, и если он не является списком исторической коллекции, то выводится об этом сообщение и возвращается NULL.
Затем
из переданного в метод списка
выбираем в отдельный список все объекты, в которых есть указание на идентификатор
позиции, переданный в метод его параметром,
Затем в
полученном списке оставляются только сделки, и
окончательный список возвращается в вызывающую программу. Результатом возврата метода может быть NULL, поэтому в вызывающей программе
необходимо проверять что нам вернул данный метод.
Метод получения списка всех сделок на вход в рынок, принадлежащих позиции по её идентификатору:
CArrayObj* CEventsCollection::GetListAllDealsInByPosID(CArrayObj *list,const ulong position_id)
{
CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_IN,EQUAL);
return list_deals;
}
Сначала из переданного в метод списка выбираем в отдельный список все сделки, в
которых есть указание на идентификатор позиции, переданный в метод его параметром,
Затем в
полученном списке оставляются только сделки с типом DEAL_ENTRY_IN, и
окончательный список возвращается в вызывающую программу. Результатом возврата метода может быть NULL, поэтому в вызывающей программе
необходимо проверять что нам вернул данный метод.
Метод получения списка всех сделок на выход из рынка, принадлежащих позиции по её идентификатору:
CArrayObj* CEventsCollection::GetListAllDealsOutByPosID(CArrayObj *list,const ulong position_id)
{
CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_OUT,EQUAL);
return list_deals;
}
Сначала из переданного в метод списка выбираем в отдельный список все сделки, в
которых есть указание на идентификатор позиции, переданный в метод его параметром,
Затем в
полученном списке оставляются только сделки с типом DEAL_ENTRY_OUT, и
окончательный список возвращается в вызывающую программу. Результатом возврата метода может быть NULL, поэтому в вызывающей программе
необходимо проверять что имено нам вернул данный метод.
Метод, возвращающий суммарный объём всех сделок позиции на вход в рынок по её идентификатору:
double CEventsCollection::SummaryVolumeDealsInByPosID(CArrayObj *list,const ulong position_id)
{
double vol=0.0;
CArrayObj* list_in=this.GetListAllDealsInByPosID(list,position_id);
if(list_in==NULL)
return 0;
for(int i=0;i<list_in.Total();i++)
{
COrder* deal=list_in.At(i);
if(deal==NULL)
continue;
vol+=deal.Volume();
}
return vol;
}
Сначала получаем список всех сделок позиции на вход в рынок,
затем
в цикле складываем объёмы всех сделок. Результирующий объём
возвращаем в вызывающую программу. Если список, переданный в метод пустой, либо передан список не исторической коллекции, метод
возвращает ноль.
Метод, возвращающий суммарный объём всех сделок позиции на выход из рынка по её идентификатору:
double CEventsCollection::SummaryVolumeDealsOutByPosID(CArrayObj *list,const ulong position_id)
{
double vol=0.0;
CArrayObj* list_out=this.GetListAllDealsOutByPosID(list,position_id);
if(list_out!=NULL)
{
for(int i=0;i<list_out.Total();i++)
{
COrder* deal=list_out.At(i);
if(deal==NULL)
continue;
vol+=deal.Volume();
}
}
CArrayObj* list_by=this.GetListCloseByOrders(list);
if(list_by!=NULL)
{
for(int i=0;i<list_by.Total();i++)
{
COrder* order=list_by.At(i);
if(order==NULL)
continue;
if(order.PositionID()==position_id || order.PositionByID()==position_id)
{
vol+=order.Volume();
}
}
}
return vol;
}
Здесь учитывается тот факт, что если часть позиции, идентификатор которой передан в метод, участвовала в закрытии другой позиции (закрытие
встречной), либо часть этой позиции закрывалась встречной позицией, то в сделках этой позиции такой факт отражён не будет — он будет
отражён в последнем закрывающем ордере позиции, в поле его свойства ORDER_PROP_POSITION_BY_ID. Поэтому данный метод имеет два поиска
закрытых объёмов — по сделкам и по закрывающим ордерам.
Сначала получаем список всех сделок позиции на выход из рынка,
затем
в цикле складываем объёмы всех сделок.
Затем получаем
список всех закрывающих ордеров, имеющихся в историческом списке, и в цикле проверяем
принадлежность выделенного ордера к позиции, идентификатор которой передан в метод, и если выделенный ордер участвовал в
закрытии позиции, то
его объём добавляется к общему объёму.
Результирующий объём возвращаем в
вызывающую программу. Если список, переданный в метод пустой, либо передан список не исторической коллекции, метод возвращает ноль.
Метод, возвращающий первый (открывающий) ордер позиции по её идентификатору:
COrder* CEventsCollection::GetFirstOrderFromList(CArrayObj* list,const ulong position_id)
{
CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
if(list_orders==NULL || list_orders.Total()==0) return NULL;
list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list_orders.At(0);
return(order!=NULL ? order : NULL);
}
Сначала получаем список всех ордеров позиции. Полученный
список
сортируем по времени открытия и берём
из списка первый его элемент — он и будет первым ордером позиции. Полученный ордер возвращаем в вызывающую программу. Если списки
пустые, то метод возвращает NULL.
Метод, возвращающий последний ордер позиции по её идентификатору:
COrder* CEventsCollection::GetLastOrderFromList(CArrayObj* list,const ulong position_id)
{
CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
if(list_orders==NULL || list_orders.Total()==0) return NULL;
list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list_orders.At(list_orders.Total()-1);
return(order!=NULL ? order : NULL);
}
Сначала получаем список всех ордеров позиции. Полученный
список
сортируем по времени открытия и берём
из списка последний его элемент — он и будет последним ордером позиции. Полученный ордер возвращаем в вызывающую программу. Если
списки пустые, то метод возвращает NULL.
Метод, возвращающий последний закрывающий ордер позиции по её идентификатору (ордер с типом ORDER_TYPE_CLOSE_BY):
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
{
CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
if(list_orders==NULL || list_orders.Total()==0) return NULL;
list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list_orders.At(list_orders.Total()-1);
return(order!=NULL ? order : NULL);
}
Так как при закрытии встречной позицией возможны частичные закрытия, а также объёмы двух встречных позиций могут быть не равны, то вполне
вероятна ситуация, при которой закрывающий ордер может быть не один в составе ордеров позиции. Поэтому в методе организован поиск всех
таких ордеров и возврат последнего из них — именно последний ордер приводит к появлению события.
Сначала получаем список всех ордеров позиции. Затем из
полученного списка
получаем список, содержащий только закрывающие ордера (с типом ORDER_TYPE_CLOSE_BY).
Полученный таким образом список
сортируем по времени открытия и берём
из списка последний его элемент — он и будет последним закрывающим ордером позиции. Полученный ордер возвращаем в вызывающую
программу. Если списки пустые, то метод возвращает NULL.
При закрытии встречной позицией происходят ситуации, что библиотека видит два идентичных события: ведь закрываются же две позиции, и лишь
одна из них имеет в своём составе закрывающий ордер, плюс имеем две сделки. Поэтому, чтобы не попасть на ситуацию дублирования одного и того
же события в коллекции, нужно сначала проверить наличие точно такого же события в списке-коллекции событий, и если его там ещё нет, то
добавлять событие в список.
Метод, возвращающий ордер по тикету:
COrder* CEventsCollection::GetOrderByTicket(CArrayObj *list,const ulong order_ticket)
{
CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TICKET,order_ticket,EQUAL);
if(list_orders==NULL || list_orders.Total()==0) return NULL;
COrder* order=list_orders.At(0);
return(order!=NULL ? order : NULL);
}
Сначала создаём список только ордеров, затем фильтруем
список по тикету, переданному параметром метода. В итоге возвращаем
либо
NULL (при отсутствии ордера с таким тикетом), либо номер тикета.
Для проверки наличия события в списке используется метод, возвразающий факт наличия события в списке:
bool CEventsCollection::IsPresentEventInList(CEvent *compared_event)
{
int total=this.m_list_events.Total();
if(total==0)
return false;
for(int i=total-1;i>=0;i--)
{
CEvent* event=this.m_list_events.At(i);
if(event==NULL)
continue;
if(event.IsEqual(compared_event))
return true;
}
return false;
}
В метод передаётся указатель на сравниваемый объёкт-событие.
Если список коллекции пуст, то сразу возвращается false — нет
такого события в списке. Далее в цикле по списку коллекции событий
берётся очередное событие из списка и сравнивается
с переданным в метод событием при помощи метода IsEqual() абстрактного события CEvent. Если
метод возвращает true, значит есть такой объект-событие в списке-коллекции событий —
возвращаем true. После завершения цикла, и в случае, если мы дошли до последней строки метода — значит нет события в списке, и
возвращается false.
В публичной секции класса объявим методы:
public:
CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
CArrayObj *GetList(void) { return &this.m_list_events; }
CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); }
CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); }
CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); }
void Refresh(CArrayObj* list_history,
CArrayObj* list_market,
const bool is_history_event,
const bool is_market_event,
const int new_history_orders,
const int new_market_pendings,
const int new_market_positions,
const int new_deals);
void SetChartID(const long id) { this.m_chart_id=id; }
ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; }
CEventsCollection(void);
Методы получения полного списка, списков по диапазону дат, по выбранным целочисленным, вещественным и строковым свойствам рассматривались
нами в
третьей части описания библиотеки, и здесь мы ограничимся листингом этих
методов для самостоятельного разбора.
Метод получения списка событий в заданном диапазоне дат:
CArrayObj *CEventsCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
{
CArrayObj *list=new CArrayObj();
if(list==NULL)
{
::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
return NULL;
}
datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
if(begin_time>end_time) begin=0;
list.FreeMode(false);
ListStorage.Add(list);
this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,begin);
int index_begin=this.m_list_events.SearchGreatOrEqual(&m_event_instance);
if(index_begin==WRONG_VALUE)
return list;
this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,end);
int index_end=this.m_list_events.SearchLessOrEqual(&m_event_instance);
if(index_end==WRONG_VALUE)
return list;
for(int i=index_begin; i<=index_end; i++)
list.Add(this.m_list_events.At(i));
return list;
}
Основным методом, который будет вызываться из базового объекта библиотеки при наступлении любого из событий, будет метод Refresh().
В данный момент реализована работа этого метода на хеджевых счетах для MQL5.
В метод передаются указатели на списки коллекций рыночных и исторических ордеров, сделок и позиций, и данные о количестве вновь
появившихся или удалённых ордеров, открытых или закрытых позициях и новых сделках.
В зависимости от того, в каком списке произошли изменения, в цикле по количеству новых ордеров/позиций/сделок берётся необходимое
количество ордеров или сделок и для каждого из них вызывается метод создания события CreateNewEvent() и занесения события в
список-коллекцию.
Таким образом для любого из произошедших событий будет вызван метод создания нового события, и оно будет помещено в список-коллекцию, а
вызывающая программа будет оповещена обо всех событиях при помощи отправки пользовательского сообщения на график вызывающей
программы.
К тому же в классе заполняется значением последнего произошедшего события переменная-член класса m_trade_event,
и есть публичный метод
GetLastTradeEvent(), возвращающий значение последнего торгового события, и метод, сбрасывающий последнее торговое событие (по
аналогии с GetLastError() и ResetLastError()).
В дополнение, есть методы, возвращающие список-коллекцию событий как полностью, так и по диапазону времени и по заданным критериям. А
вызывающая программа всегда будет знать, что на счёте произошло событие — одно, или сразу несколько одной пачкой, и при этом можно
запросить список всех этих событий в требуемом количестве и обработать в соответствии с заложенной в программу логикой.
Рассмотрим листинги методов Refresh() и CreateNewEvent().
Метод обновления списка коллекции событий:
void CEventsCollection::Refresh(CArrayObj* list_history,
CArrayObj* list_market,
const bool is_history_event,
const bool is_market_event,
const int new_history_orders,
const int new_market_pendings,
const int new_market_positions,
const int new_deals)
{
if(list_history==NULL || list_market==NULL)
return;
if(this.m_is_hedge)
{
if(is_market_event)
{
if(new_market_pendings>0)
{
CArrayObj* list=this.GetListMarketPendings(list_market);
if(list!=NULL)
{
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
int total=list.Total(), n=new_market_pendings;
for(int i=total-1; i>=0 && n>0; i--,n--)
{
COrder* order=list.At(i);
if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
this.CreateNewEvent(order,list_history,list_market);
}
}
}
}
if(is_history_event)
{
if(new_history_orders>0)
{
CArrayObj* list=this.GetListHistoryPendings(list_history);
if(list!=NULL)
{
list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
int total=list.Total(), n=new_history_orders;
for(int i=total-1; i>=0 && n>0; i--,n--)
{
COrder* order=list.At(i);
if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING)
this.CreateNewEvent(order,list_history,list_market);
}
}
}
if(new_deals>0)
{
CArrayObj* list=this.GetListDeals(list_history);
if(list!=NULL)
{
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
int total=list.Total(), n=new_deals;
for(int i=total-1; i>=0 && n>0; i--,n--)
{
COrder* order=list.At(i);
if(order!=NULL)
this.CreateNewEvent(order,list_history,list_market);
}
}
}
}
}
else
{
}
}
Чтобы не останавливаться на описании простого метода, прямо в его листинге описаны все необходимые условия и действия при выполнении этих
условий — тут, мне кажется, всё достаточно прозрачно. Одно замечание — пока реализована обработка событий на хеджевом счёте.
Рассмотрим метод создания нового события:
void CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market)
{
int trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
ENUM_ORDER_STATUS status=order.Status();
if(status==ORDER_STATUS_MARKET_PENDING)
{
trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED;
CEvent* event=new CEventOrderPlased(trade_event_code,order.Ticket());
if(event!=NULL)
{
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent());
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());
event.SetProperty(EVENT_PROP_PROFIT,order.Profit());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
if(status==ORDER_STATUS_HISTORY_PENDING)
{
trade_event_code=TRADE_EVENT_FLAG_ORDER_REMOVED;
CEvent* event=new CEventOrderRemoved(trade_event_code,order.Ticket());
if(event!=NULL)
{
ENUM_EVENT_REASON reason=
(
order.State()==ORDER_STATE_CANCELED ? EVENT_REASON_CANCEL :
order.State()==ORDER_STATE_EXPIRED ? EVENT_REASON_EXPIRED : EVENT_REASON_DONE
);
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeCloseMSC());
event.SetProperty(EVENT_PROP_REASON_EVENT,reason);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent());
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());
event.SetProperty(EVENT_PROP_PROFIT,order.Profit());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
if(status==ORDER_STATUS_MARKET_POSITION)
{
trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
CEvent* event=new CEventPositionOpen(trade_event_code,order.Ticket());
if(event!=NULL)
{
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpen());
event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());
event.SetProperty(EVENT_PROP_PROFIT,order.Profit());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
if(status==ORDER_STATUS_DEAL)
{
if((ENUM_DEAL_TYPE)order.TypeOrder()>DEAL_TYPE_SELL)
{
trade_event_code=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
CEvent* event=new CEventBalanceOperation(trade_event_code,order.Ticket());
if(event!=NULL)
{
ENUM_EVENT_REASON reason=
(
(ENUM_DEAL_TYPE)order.TypeOrder()==DEAL_TYPE_BALANCE ? (order.Profit()>0 ? EVENT_REASON_BALANCE_REFILL : EVENT_REASON_BALANCE_WITHDRAWAL) :
(ENUM_EVENT_REASON)(order.TypeOrder()+REASON_EVENT_SHIFT)
);
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_REASON_EVENT,reason);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());
event.SetProperty(EVENT_PROP_PROFIT,order.Profit());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
else
{
if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
{
trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
int reason=EVENT_REASON_DONE;
double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
ulong order_ticket=order.GetProperty(ORDER_PROP_DEAL_ORDER);
COrder* order_first=this.GetOrderByTicket(list_history,order_ticket);
COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
if(order_last==NULL)
order_last=order_first;
if(order_first!=NULL)
{
if(this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume())
{
trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
reason=EVENT_REASON_DONE_PARTIALLY;
}
if(order_first.TypeOrder()>ORDER_TYPE_SELL && order_first.TypeOrder()<ORDER_TYPE_CLOSE_BY)
{
trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
reason=
(this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume() ?
EVENT_REASON_ACTIVATED_PENDING_PARTIALLY :
EVENT_REASON_ACTIVATED_PENDING
);
}
CEvent* event=new CEventPositionOpen(trade_event_code,order.PositionID());
if(event!=NULL)
{
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_REASON_EVENT,reason);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order_first.Volume());
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,volume_in);
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order_first.Volume()-volume_in);
event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
}
else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
{
trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
int reason=EVENT_REASON_DONE;
COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
if(order_first!=NULL && order_last!=NULL)
{
double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());
int dgl=(int)DigitsLots(order.Symbol());
double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
if(volume_current>0)
{
trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
}
if(order_last.VolumeCurrent()>0)
{
reason=EVENT_REASON_DONE_PARTIALLY;
}
if(order_last.IsCloseByStopLoss())
{
trade_event_code+=TRADE_EVENT_FLAG_SL;
reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_SL_PARTIALLY : EVENT_REASON_DONE_SL);
}
else if(order_last.IsCloseByTakeProfit())
{
trade_event_code+=TRADE_EVENT_FLAG_TP;
reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_TP_PARTIALLY : EVENT_REASON_DONE_TP);
}
CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
if(event!=NULL)
{
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_REASON_EVENT,reason);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,volume_in);
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_in-volume_out);
event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
}
else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
{
trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
int reason=EVENT_REASON_DONE_BY_POS;
COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
COrder* order_close=this.GetCloseByOrderFromList(list_history,order.PositionID());
if(order_first!=NULL && order_close!=NULL)
{
trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());
int dgl=(int)DigitsLots(order.Symbol());
double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
double volume_opp_in=this.SummaryVolumeDealsInByPosID(list_history,order_close.PositionByID());
double volume_opp_out=this.SummaryVolumeDealsOutByPosID(list_history,order_close.PositionByID());
double volume_opp_current=::NormalizeDouble(volume_opp_in-volume_opp_out,dgl);
if(volume_current>0 || order_close.VolumeCurrent()>0)
{
trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
reason=(volume_opp_current>0 ? EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY : EVENT_REASON_DONE_PARTIALLY_BY_POS);
}
else
{
if(volume_opp_current>0)
{
reason=EVENT_REASON_DONE_BY_POS_PARTIALLY;
}
}
CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
if(event!=NULL)
{
event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());
event.SetProperty(EVENT_PROP_REASON_EVENT,reason);
event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());
event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_close.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_close.Ticket());
event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());
event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());
event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());
event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());
event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_close.PositionByID());
event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());
event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());
event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());
event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());
event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());
event.SetProperty(EVENT_PROP_VOLUME_INITIAL,::NormalizeDouble(volume_in,dgl));
event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());
event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_current);
event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());
event.SetProperty(EVENT_PROP_SYMBOL,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;
}
}
}
}
else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_INOUT)
{
Print(DFUN,"Разворот позиции");
order.Print();
}
}
}
}
Метод получился достаточно объёмным. Поэтому все описания необходимых в нём проверок и соответствующих им действий прописаны прямо в
листинге.
Вся суть работы данного метода сводится к тому, что проверяется статус переданного в метод ордера, и в зависимости от того, какой это
ордер — либо отложенный установленный, либо отложенный удалённый, либо это сделка — проверяются все необходимые составляющие
произошедшего события, создаётся новое событие, заполняется данными, соответствующими типу ордера и события, событие помещается
в коллекцию событий, и наконец — отправляется сообщение об этом событии на график управляющей программы и заполняется переменная,
хранящая тип последнего произошедшего события.
Класс коллекции событий готов. Теперь необходимо подключить его к базовому объекту библиотеки.
Теперь, после создания класса-коллекции событий, некоторые вещи, что мы делали в
четвёртой части в классе базового объекта CEngine для отслеживания событий, теперь нам не нужны, и стоит переработать базовый
объект.
- Удалим из листинга приватную переменную-член класса m_trade_event_code, хранящую код состояния торгового события.
- Удалим приватные методы:
- метод расшифровки кода события SetTradeEvent(),
- метод, возвращающий наличие флага в торговом событии IsTradeEventFlag(),
- методы работы с хеджевой и неттинговой коллекциями WorkWithHedgeCollections() и WorkWithNettoCollections()
- и метод возврата кода торгового события TradeEventCode()
Добавим в тело класса подключение файла класса коллекции торговых событий,
объявим объект-коллекцию событий, в приватную секцию класса
добавим
метод работы с событиями TradeEventsControl(), в публичной
секции изменим название метода GetListHistoryDeals() на
GetListDeals() — сделки всегда расположены в
исторической коллекции, и явно указывать на коллекцию в названии метода считаю избыточным. Изменим реализацию метода сброса
последнего торгового события: так как теперь последнее событие получаем из класса-коллекции событий, и внутри этого класса есть
метод сброса последнего события, то в данном классе, в методе
ResetLastTradeEvent() нам просто необходимо вызвать
одноимённый метод из класса-коллекции событий.
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
class CEngine : public CObject
{
private:
CHistoryCollection m_history;
CMarketCollection m_market;
CEventsCollection m_events;
CArrayObj m_list_counters;
bool m_first_start;
bool m_is_hedge;
bool m_is_market_trade_event;
bool m_is_history_trade_event;
ENUM_TRADE_EVENT m_acc_trade_event;
int CounterIndex(const int id) const;
bool IsFirstStart(void);
void TradeEventsControl(void);
COrder* GetLastMarketPending(void);
COrder* GetLastMarketOrder(void);
COrder* GetLastPosition(void);
COrder* GetPosition(const ulong ticket);
COrder* GetLastHistoryPending(void);
COrder* GetLastHistoryOrder(void);
COrder* GetHistoryOrder(const ulong ticket);
COrder* GetFirstOrderPosition(const ulong position_id);
COrder* GetLastOrderPosition(const ulong position_id);
COrder* GetLastDeal(void);
public:
CArrayObj* GetListMarketPosition(void);
CArrayObj* GetListMarketPendings(void);
CArrayObj* GetListMarketOrders(void);
CArrayObj* GetListHistoryOrders(void);
CArrayObj* GetListHistoryPendings(void);
CArrayObj* GetListDeals(void);
CArrayObj* GetListAllOrdersByPosID(const ulong position_id);
void ResetLastTradeEvent(void) { this.m_events.ResetLastTradeEvent(); }
ENUM_TRADE_EVENT LastTradeEvent(void) const { return this.m_acc_trade_event; }
bool IsHedge(void) const { return this.m_is_hedge; }
void CreateCounter(const int id,const ulong frequency,const ulong pause);
void OnTimer(void);
CEngine();
~CEngine();
};
В конструкторе класса CEngine добавим обработку результата создания милисекундного таймера, и если он не был создан, то выведем об этом
сообщение в журнал. Далее мы создадим класс обработки некоторых ошибок, и будем выставлять флаги, которые будет видеть программа,
работающая на основе библиотеки, и обрабатывать ошибочные ситуации.
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
{
::ResetLastError();
if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
this.m_list_counters.Sort();
this.m_list_counters.Clear();
this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
}
В таймере класса просто будем вызывать метод TradeEventsControl() после
завершения паузы таймера коллекций ордеров, сделок и позиций.
void CEngine::OnTimer(void)
{
int index=this.CounterIndex(COLLECTION_COUNTER_ID);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL && counter.IsTimeDone())
{
this.TradeEventsControl();
}
}
}
Доработаем метод, возвращающий исторический ордер по тикету. Так как у нас в списке исторической коллекции могут находиться как отложенные
ордера, так и сработавшие маркет-ордера и закрывающие ордера при закрытии встречной позицией. Поэтому необходимо учитывать все типы
ордеров.
Для этого сначала производится поиск ордера по тикету в списке маркетных и закрывающих
ордеров, и если список оказался пустым, то ищется удалённый отложенный ордер с таким тикетом. Если и в этом списке не оказалось
ордера, то возвращается NULL. Иначе — возвращается первый элемент списка, в котором был найден ордер. Если и ордер не удалось получить из
списка, то возвращается NULL.
COrder* CEngine::GetHistoryOrder(const ulong ticket)
{
CArrayObj* list=this.GetListHistoryOrders();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
if(list==NULL || list.Total()==0)
{
list=this.GetListHistoryPendings();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
if(list==NULL) return NULL;
}
COrder* order=list.At(0);
return(order!=NULL ? order : NULL);
}
Напишем реализацию метода работы с событиями счёта TradeEventsControl():
void CEngine::TradeEventsControl(void)
{
this.m_is_market_trade_event=false;
this.m_is_history_trade_event=false;
this.m_market.Refresh();
this.m_history.Refresh();
if(this.IsFirstStart())
{
this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
return;
}
this.m_is_market_trade_event=this.m_market.IsTradeEvent();
this.m_is_history_trade_event=this.m_history.IsTradeEvent();
if(this.m_is_history_trade_event || this.m_is_market_trade_event)
{
this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
this.m_is_history_trade_event,this.m_is_market_trade_event,
this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
this.m_market.NewMarketOrders(),this.m_history.NewDeals());
this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
}
}
Метод оказался куда лаконичнее, чем его предшественник WorkWithHedgeCollections() из четвёртой части описания библиотеки.
Метод простой, и не требует пояснений, тем более, что все комментарии в коде прописаны — можно легко понять всю его незамысловатую логику.
Приведём полный листинг обновлённого класса CEngine:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
class CEngine : public CObject
{
private:
CHistoryCollection m_history;
CMarketCollection m_market;
CEventsCollection m_events;
CArrayObj m_list_counters;
bool m_first_start;
bool m_is_hedge;
bool m_is_market_trade_event;
bool m_is_history_trade_event;
ENUM_TRADE_EVENT m_acc_trade_event;
int CounterIndex(const int id) const;
bool IsFirstStart(void);
void TradeEventsControl(void);
COrder* GetLastMarketPending(void);
COrder* GetLastMarketOrder(void);
COrder* GetLastPosition(void);
COrder* GetPosition(const ulong ticket);
COrder* GetLastHistoryPending(void);
COrder* GetLastHistoryOrder(void);
COrder* GetHistoryOrder(const ulong ticket);
COrder* GetFirstOrderPosition(const ulong position_id);
COrder* GetLastOrderPosition(const ulong position_id);
COrder* GetLastDeal(void);
public:
CArrayObj* GetListMarketPosition(void);
CArrayObj* GetListMarketPendings(void);
CArrayObj* GetListMarketOrders(void);
CArrayObj* GetListHistoryOrders(void);
CArrayObj* GetListHistoryPendings(void);
CArrayObj* GetListDeals(void);
CArrayObj* GetListAllOrdersByPosID(const ulong position_id);
void ResetLastTradeEvent(void) { this.m_events.ResetLastTradeEvent(); }
ENUM_TRADE_EVENT LastTradeEvent(void) const { return this.m_acc_trade_event; }
bool IsHedge(void) const { return this.m_is_hedge; }
void CreateCounter(const int id,const ulong frequency,const ulong pause);
void OnTimer(void);
CEngine();
~CEngine();
};
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
{
::ResetLastError();
if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
this.m_list_counters.Sort();
this.m_list_counters.Clear();
this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
}
CEngine::~CEngine()
{
::EventKillTimer();
}
void CEngine::OnTimer(void)
{
int index=this.CounterIndex(COLLECTION_COUNTER_ID);
if(index>WRONG_VALUE)
{
CTimerCounter* counter=this.m_list_counters.At(index);
if(counter!=NULL && counter.IsTimeDone())
{
this.TradeEventsControl();
}
}
}
void CEngine::CreateCounter(const int id,const ulong step,const ulong pause)
{
if(this.CounterIndex(id)>WRONG_VALUE)
{
::Print(TextByLanguage("Ошибка. Уже создан счётчик с идентификатором ","Error. Already created a counter with id "),(string)id);
return;
}
m_list_counters.Sort();
CTimerCounter* counter=new CTimerCounter(id);
if(counter==NULL)
::Print(TextByLanguage("Не удалось создать счётчик таймера ","Failed to create timer counter "),(string)id);
counter.SetParams(step,pause);
if(this.m_list_counters.Search(counter)==WRONG_VALUE)
this.m_list_counters.Add(counter);
else
{
string t1=TextByLanguage("Ошибка. Счётчик с идентификатором ","Error. Counter with ID ")+(string)id;
string t2=TextByLanguage(", шагом ",", step ")+(string)step;
string t3=TextByLanguage(" и паузой "," and pause ")+(string)pause;
::Print(t1+t2+t3+TextByLanguage(" уже существует"," already exists"));
delete counter;
}
}
int CEngine::CounterIndex(const int id) const
{
int total=this.m_list_counters.Total();
for(int i=0;i<total;i++)
{
CTimerCounter* counter=this.m_list_counters.At(i);
if(counter==NULL) continue;
if(counter.Type()==id)
return i;
}
return WRONG_VALUE;
}
bool CEngine::IsFirstStart(void)
{
if(this.m_first_start)
{
this.m_first_start=false;
return true;
}
return false;
}
void CEngine::TradeEventsControl(void)
{
this.m_is_market_trade_event=false;
this.m_is_history_trade_event=false;
this.m_market.Refresh();
this.m_history.Refresh();
if(this.IsFirstStart())
{
this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
return;
}
this.m_is_market_trade_event=this.m_market.IsTradeEvent();
this.m_is_history_trade_event=this.m_history.IsTradeEvent();
if(this.m_is_history_trade_event || this.m_is_market_trade_event)
{
this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
this.m_is_history_trade_event,this.m_is_market_trade_event,
this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
this.m_market.NewMarketOrders(),this.m_history.NewDeals());
this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
}
}
CArrayObj* CEngine::GetListMarketPosition(void)
{
CArrayObj* list=this.m_market.GetList();
list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
return list;
}
CArrayObj* CEngine::GetListMarketPendings(void)
{
CArrayObj* list=this.m_market.GetList();
list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
return list;
}
CArrayObj* CEngine::GetListMarketOrders(void)
{
CArrayObj* list=this.m_market.GetList();
list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
return list;
}
CArrayObj* CEngine::GetListHistoryOrders(void)
{
CArrayObj* list=this.m_history.GetList();
list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
return list;
}
CArrayObj* CEngine::GetListHistoryPendings(void)
{
CArrayObj* list=this.m_history.GetList();
list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
return list;
}
CArrayObj* CEngine::GetListDeals(void)
{
CArrayObj* list=this.m_history.GetList();
list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
return list;
}
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
{
CArrayObj* list=this.GetListHistoryOrders();
list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
return list;
}
COrder* CEngine::GetLastPosition(void)
{
CArrayObj* list=this.GetListMarketPosition();
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetPosition(const ulong ticket)
{
CArrayObj* list=this.GetListMarketPosition();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TICKET);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetLastDeal(void)
{
CArrayObj* list=this.GetListDeals();
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetLastMarketPending(void)
{
CArrayObj* list=this.GetListMarketPendings();
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetLastHistoryPending(void)
{
CArrayObj* list=this.GetListHistoryPendings();
if(list==NULL) return NULL;
list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetLastMarketOrder(void)
{
CArrayObj* list=this.GetListMarketOrders();
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetLastHistoryOrder(void)
{
CArrayObj* list=this.GetListHistoryOrders();
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetHistoryOrder(const ulong ticket)
{
CArrayObj* list=this.GetListHistoryOrders();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
if(list==NULL || list.Total()==0)
{
list=this.GetListHistoryPendings();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
if(list==NULL) return NULL;
}
COrder* order=list.At(0);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
{
CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN);
COrder* order=list.At(0);
return(order!=NULL ? order : NULL);
}
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
{
CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
if(list==NULL) return NULL;
list.Sort(SORT_BY_ORDER_TIME_OPEN);
COrder* order=list.At(list.Total()-1);
return(order!=NULL ? order : NULL);
}
Тест определения, обработки и получения событий
Теперь у нас всё готово для работы с событиями, и можно подготовить советник для проведения тестирования определения событий, их обработки и
отправки в управляющую программу.
В каталоге расположения терминала\MQL5\Experts\TestDoEasy создадим папку Part05 и скопируем в неё советник из прошлой части
TestDoEasyPart04.mq5 под новым именем:
TestDoEasyPart05.mq5
В его обработчике событий OnChartEvent() внесём изменения для получения
пользовательских событий:
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);
}
}
Здесь: если идентификатор события больше или равен идентификатору
пользовательского события, то получим код события,
передаваемый из библиотеки потомками класса CEvent. Так как при отправке пользовательского события функцией
EventChartCustom(), указанного в параметре функции custom_event_id
(в него мы записываем наше событие) к значению нашего события добавляется значение константы CHARTEVENT_CUSTOM (его значение равно 1000) из
перечисления
ENUM_CHART_EVENT, то для получения обратно
значения нашего события, нужно просто из идентификатора события (id) вычесть значение CHARTEVENT_CUSTOM. Далее мы просто выведем
данные о событии в журнал терминала.
Распечатаны будут: идентификатор как есть, описание события в виде наименования значения перечисления ENUM_TRADE_EVENT, значение lparam, где
хранится тикет ордера или позиции, значение dparam, где хранится цена, на которой произошло событие, и значение sparam — символ ордера или
позиции участвующих в событии, либо наименование валюты счёта — если событие является балансной операцией.
Например, так:
2019.04.06 03:19:54.442 OnChartEvent: id=1001, event=TRADE_EVENT_PENDING_ORDER_PLASED, lparam=375419507, dparam=1.14562, sparam=EURUSD
Так же необходимо в советнике подправить лот, рассчитываемый для частичного закрытия — в прошлых версиях тестовых советников была ошибка —
при расчёте лота, бралось значение невыполненного объёма позиции (VolumeCurrent()) — оно в тестере при открытии позиции всегда равно
нулю, так как тестер не моделирует частичные открытия. Соответственно и бралось минимальное значение лота для закрытия, так как функция
расчёта лота всегда корректировала ноль к минимально-допустимому значению лота.
Найдём в коде строки, где происходит расчёт лота для частичного закрытия, и заменим VolumeCurrent() на Volume():
trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
Всего два места в коде — закрытие половины позиции Buy и закрытие половины позиции Sell.
Так же добавим во входные параметры советника смещение кнопок по осям X и Y
— для удобства расположения набора кнопок на графике визуального тестера (мне потребовалось сместить кнопки правее, чтобы в визуализаторе
видеть тикеты ордеров и позиций, которые могли быть скрыты кнопками):
input ulong InpMagic = 123;
input double InpLots = 0.1;
input uint InpStopLoss = 50;
input uint InpTakeProfit = 50;
input uint InpDistance = 50;
input uint InpDistanceSL = 50;
input uint InpSlippage = 0;
input double InpWithdrawal = 10;
input uint InpButtShiftX = 40;
input uint InpButtShiftY = 10;
Чуть изменим код функции создания кнопок:
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)+2*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-3) x=cx;
y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? 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;
}
и вызов этой функции в обработчике OnInit() советника:
if(!CreateButtons(InpButtShiftX,InpButtShiftY))
return INIT_FAILED;
Полный код тестового советника:
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link "https://mql5.com/ru/users/artmedia70"
#property version "1.00"
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>
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
};
#define TOTAL_BUTT (17)
struct SDataButt
{
string name;
string text;
};
input ulong InpMagic = 123;
input double InpLots = 0.1;
input uint InpStopLoss = 50;
input uint InpTakeProfit = 50;
input uint InpDistance = 50;
input uint InpDistanceSL = 50;
input uint InpSlippage = 0;
input double InpWithdrawal = 10;
input uint InpButtShiftX = 40;
input uint InpButtShiftY = 10;
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;
int OnInit()
{
if(!engine.IsHedge())
{
Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
return INIT_FAILED;
}
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;
if(!CreateButtons(InpButtShiftX,InpButtShiftY))
return INIT_FAILED;
trade.SetDeviationInPoints(slippage);
trade.SetExpertMagicNumber(magic_number);
trade.SetTypeFillingBySymbol(Symbol());
trade.SetMarginMode();
trade.LogLevel(LOG_LEVEL_NO);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectsDeleteAll(0,prefix);
Comment("");
}
void OnTick()
{
static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
if(MQLInfoInteger(MQL_TESTER))
{
engine.OnTimer();
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);
}
}
if(engine.LastTradeEvent()!=last_event)
{
Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent()));
last_event=engine.LastTradeEvent();
}
}
void OnTimer()
{
if(!MQLInfoInteger(MQL_TESTER))
engine.OnTimer();
}
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 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)+2*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-3) x=cx;
y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? 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);
}
string EnumToButtText(const ENUM_BUTTONS member)
{
string txt=StringSubstr(EnumToString(member),5);
StringToLower(txt);
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))
{
if(button==EnumToString(BUTT_BUY))
{
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
}
else if(button==EnumToString(BUTT_BUY_LIMIT))
{
double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
}
else if(button==EnumToString(BUTT_BUY_STOP))
{
double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
trade.BuyStop(lot,price_set,Symbol(),sl,tp);
}
else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
{
double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
}
else if(button==EnumToString(BUTT_SELL))
{
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
trade.Sell(lot,Symbol(),0,sl,tp);
}
else if(button==EnumToString(BUTT_SELL_LIMIT))
{
double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
trade.SellLimit(lot,price_set,Symbol(),sl,tp);
}
else if(button==EnumToString(BUTT_SELL_STOP))
{
double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
trade.SellStop(lot,price_set,Symbol(),sl,tp);
}
else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
{
double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
}
else if(button==EnumToString(BUTT_CLOSE_BUY))
{
CArrayObj* list=engine.GetListMarketPosition();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
list.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
if(index>WRONG_VALUE)
{
COrder* position=list.At(index);
if(position!=NULL)
{
trade.PositionClose(position.Ticket());
}
}
}
else if(button==EnumToString(BUTT_CLOSE_BUY2))
{
CArrayObj* list=engine.GetListMarketPosition();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
list.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
if(index>WRONG_VALUE)
{
COrder* position=list.At(index);
if(position!=NULL)
{
trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
}
}
}
else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
{
CArrayObj* list_buy=engine.GetListMarketPosition();
list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
CArrayObj* list_sell=engine.GetListMarketPosition();
list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
{
COrder* position_buy=list_buy.At(index_buy);
COrder* position_sell=list_sell.At(index_sell);
if(position_buy!=NULL && position_sell!=NULL)
{
trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
}
}
}
else if(button==EnumToString(BUTT_CLOSE_SELL))
{
CArrayObj* list=engine.GetListMarketPosition();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
list.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
if(index>WRONG_VALUE)
{
COrder* position=list.At(index);
if(position!=NULL)
{
trade.PositionClose(position.Ticket());
}
}
}
else if(button==EnumToString(BUTT_CLOSE_SELL2))
{
CArrayObj* list=engine.GetListMarketPosition();
list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
list.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
if(index>WRONG_VALUE)
{
COrder* position=list.At(index);
if(position!=NULL)
{
trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
}
}
}
else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
{
CArrayObj* list_sell=engine.GetListMarketPosition();
list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
CArrayObj* list_buy=engine.GetListMarketPosition();
list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
{
COrder* position_sell=list_sell.At(index_sell);
COrder* position_buy=list_buy.At(index_buy);
if(position_sell!=NULL && position_buy!=NULL)
{
trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
}
}
}
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());
}
}
}
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());
}
}
}
if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
{
if(MQLInfoInteger(MQL_TESTER))
{
TesterWithdrawal(withdrawal);
}
}
Sleep(100);
ButtonState(button_name,false);
ChartRedraw();
}
}
Теперь можно скомпилировать советник и запустить его в тестере. При нажатии на кнопки, в журнал тестера будут выводиться краткие двухстрочные
сообщения о происходящих на счёте событиях.

Записи же из обработчика событий советника в журнал выводиться не будут — они работают не в тестере. Если на демо-счёте покликать по кнопкам
советника, то в журнал терминала будут выводиться три строки: две строки от метода вывода коротких сообщений класса CEvent и одна строка —
из обработчика OnChartEvent() советника.
Пример вывода в журнал советника при установке и удалении
отложенного ордера:
- Pending order placed: 2019.04.05 23:19:55.248 -
EURUSD 0.10 Sell Limit #375419507 at price 1.14562
OnChartEvent: id=1001, event=TRADE_EVENT_PENDING_ORDER_PLASED, lparam=375419507, dparam=1.14562, sparam=EURUSD
- Pending order removed: 2019.04.05 23:19:55.248 -
EURUSD 0.10 Sell Limit #375419507 at price 1.14562
OnChartEvent: id=1002, event=TRADE_EVENT_PENDING_ORDER_REMOVED, lparam=375419507, dparam=1.14562, sparam=EURUSD
Что дальше
В следующей статье начнём добавлять функционал для работы на неттинговых счетах MetaTrader 5.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестовых советников. Их можно скачать и протестировать всё
самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.
К содержанию
Статьи этой серии:
Часть 1. Концепция, организация данных.
Часть
2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и
позиций, организация поиска.
Часть 4. Торговые события. Концепция.