Создание пользовательских событий
Часто случается так, что блоки кода, будь то функции или методы классов, в результате какой-либо работы, вызывают другие функции или методы. Таким образом эти функции становятся жестко связанными друг с другом.
Чем больше таких связок, тем становится всё труднее вносить последующие изменения в программу. Особенно это касается создания автоматических торговых систем для терминала MetaTrader, которые часто изменяются и совершенствуются после проведения тестирования или оптимизации в тестере стратегий, а потом и при работе в реальном времени.
Чтобы сделать части кода независимыми друг от друга, нужно использовать систему "событие-слушатель". Когда одна функция создает событие, а любые другие функции его прослушивают.
Главное преимущество такого подхода в том, что функция, транслирующая событие и функции его прослушивающие, вообще ничего не знают друг о друге. Они никак не связаны друг с другом, и мы можем изменять код, удалять и создавать новые функции, не ломая логику программы. Что позволит избежать многих неявных ошибок, которые могут незаметно изменить, например, прибыльную торговую стратегию на убыточную.
Далее мы создадим систему создания и прослушивания событий, которая одинаково работает в MQL 4 и 5 версий.
Начнем с создания идентификаторов событий. Их нужно задать на глобальном уровне, как константы или перечисление констант.
// Константы
const int TRADE_EVENT_POSITION_CLOSED = 1001;
const int TRADE_EVENT_POSITION_OPENED = 1002;
// Или перечисление констант
enum ENUM_TRADE_EVENT
{
TRADE_EVENT_POSITION_CLOSED=1001,
TRADE_EVENT_POSITION_OPENED, // автоматически становится равно 1002
};
Используя перечисление, мы группируем пользовательские события, как бы отделяя их от других событий. Можно даже создать несколько таких групп событий, но нужно учитывать, что у всех событий должен быть разный идентификатор. И чтобы не возникало путаницы, лучше держать все события в одном перечислении.
Итак, мы определили два пользовательских события: TRADE_EVENT_POSITION_OPENED - событие открытия рыночной позиции и TRADE_EVENT_POSITION_CLOSED - событие закрытия рыночной позиции. То есть, после успешного открытия позиции мы будем вызывать событие её открытия и при закрытии позиции, соответственно будем вызывать событие её закрытия.
Следующим шагом мы создадим функцию, которая будет создавать нужное событие.
//+------------------------------------------------------------------+
//| Создает событие графика |
//+------------------------------------------------------------------+
void CreateEvent(const ushort event_id, const int position_type, const double volume)
{
//---
// Переменные ниже созданы для примера,
// чтобы показать какие параметры принимает функция EventChartCustom
// и как можно передавать любые данные в событиях
long lparam = long(position_type);
double dparam = volume;
string sparam = Symbol();
// Функция языка MQL, создает пользовательское событие графика
EventChartCustom(0, event_id, lparam, dparam, sparam);
//---
}
Самое главное в функции CreateEvent() выше, это использование встроенной функции языка MQL - EventChartCustom()
.
Эта функция принимает следующие параметры:
bool EventChartCustom(
long chart_id, // идентификатор графика-получателя события
ushort custom_event_id, // идентификатор события
long lparam, // параметр типа long
double dparam, // параметр типа double
string sparam // строковый параметр события
);
В параметрах lparam
, dparam
и sparam
, мы можем передавать любые данные, предназначенные для слушателей.
Идентификатор события нужен, чтобы отличать события друг от друга. Именно для этого мы создали в начале перечисление с нашими событиями, которые и являются идентификаторами.
Идентификатор графика-получателя события позволяет отправлять события слушателям на любых других графиках, если известны ID этих графиков. Если этот параметр равен нулю, то события отправляются на текущий график.
Теперь мы можем создавать события в любом месте программы. Например, после успешного открытия позиции на покупку, мы отправим соответствующее событие так:
CreateEvent(TRADE_EVENT_POSITION_OPENED, ORDER_TYPE_BUY, 0.01);
Далее создадим функцию-слушатель. Она будет принимать те же параметры: lparam
, dparam
и sparam
. Для примера мы создадим отдельную функцию только для отслеживания события открытия позиции.
На ваше усмотрение, вы можете создать функцию, которая будет отслеживать оба события: открытие и закрытие позиции, для этого нужно будет добавить еще одной входной параметр - идентификатор полученного пользовательского события.
//+------------------------------------------------------------------+
//| Прослушивает событие открытия позиции |
//+------------------------------------------------------------------+
void ListenEventOpen(const long lparam, const double dparam, const string sparam)
{
//---
printf("Открыт ордер: Тип: %s, Объем: %.2f, Символ: %s", EnumToString(ENUM_ORDER_TYPE(lparam)), dparam, sparam);
//---
}
В результате, после вызова функции CreateEvent(), с параметрами в примере выше, функция ListenEventOpen() выведет во вкладку "Эксперты" торгового терминала, следующее сообщение: Открыт ордер: Тип: ORDER_TYPE_BUY, Объем: 0.01, Символ: EURUSD.
Но, это еще не всё. На данный момент, как мы и хотели, мы создали две независимые функции: отправителя и слушателя событий, которые ничего не знают друг о друге. И на данный момент мы можем отправить событие, но не можем его прослушать.
В программе на языке MQL, все события графика отправляются в функцию OnChartEvent(), которая объявляется глобально, наравне с такими функциями как OnInit() и OnTick(). В этой функции мы и проложим "линию связи" между отправителями и получателями событий.
Это будет единственное место в коде, который может состоять из десятков файлов, где функции будут общаться между собой. При этом функции не свяжутся между собой напрямую и не станут зависимы друг от друга, как было описано в начале статьи.
Вот как это работает:
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, // идентификатор события
const long &lparam, // параметр события типа long
const double &dparam, // параметр события типа double
const string &sparam) // параметр события типа string
{
//---
// Если id-события больше, чем константа пользовательского события,
// значит это пользовательское событие, а не встроенное.
if(id > CHARTEVENT_CUSTOM)
{
// Здесь уже по пользовательскому id-события, мы определяем что произошло именно оно
if(id == (CHARTEVENT_CUSTOM + TRADE_EVENT_POSITION_OPENED))
{
// Далее вызывается функция или функции обрабатывающие данное событие
ListenEventOpen(lparam, dparam, sparam);
}
else if(id == (CHARTEVENT_CUSTOM + TRADE_EVENT_POSITION_CLOSED))
{
ListenEventClose(lparam, dparam, sparam);
}
}
//---
}
На этом система отправления и прослушивания событий готова. В параметрах lparam
, dparam
и sparam
мы можем передавать практически любые данные между функциями. При этом функции останутся независимыми, что уменьшит появление ошибок в программе и сохранит код чистым и понятным для последующего сопровождения.