English 中文 Español Deutsch 日本語 Português
События в МetaТrader 4

События в МetaТrader 4

MetaTrader 4Примеры | 29 мая 2006, 15:25
11 502 7
Andrey Khatimlianskii
Andrey Khatimlianskii

Введение

Статья посвящена программному отслеживанию событий в терминале МetaТrader 4, таких как открытие, закрытие и модификация ордеров, и рассчитана на пользователя, обладающего базовыми навыками работы с терминалом и программирования на MQL4.



1. Что такое события и зачем их отслеживать?

Для реализации некоторых стратегий не достаточно знать, есть ли позиция, открытая экспертом. Иногда нужно "словить" именно момент открытия, закрытия или модификации позиции, или срабатывания отложенного ордера.

В MQL4 нет встроенных функций, самостоятельно решающих эту задачу, но есть всё, чтобы создать такой инструмент. Этим мы и займёмся.

2. Принцип определения события

Как понять, что событие произошло? Что такое, вообще, событие? Попытавшись ответить на эти вопросы, мы придём к примерно следующему выводу: событие – это изменение состояния ордера или открытой позиции. В контексте нашей задачи это изменение, например, количества открытых позиций или уровня Стоп Лосс позиции.

Как определить, что событие произошло именно сейчас? Очень просто. Для этого надо запомнить отслеживаемое значение (в нашем примере – количество позиций), а в следующий момент времени, например на следующем тике, сравнить его с вновь полученным значением. Создадим простого эксперта, который будет информировать нас об изменении количества позиций.

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // Если это первый запуск эксперта, мы не знаем количество ордеров на предыдущем тике.
    // Поэтому просто запоминаем его, делаем пометку, что первый запуск уже был, и выходим.
    if ( first )
    {
        pre_OrdersTotal = _OrdersTotal;
        first = false;
        return(0);
    }
 
    // Сравниваем количество позиций на предидущем тике с текущим количеством
    // Если оно изменилось, выводим сообщение
    if ( _OrdersTotal > pre_OrdersTotal ) 
        Alert( "Количество позиций увеличилось! Было - ", pre_OrdersTotal, 
                                                         ", стало - ", _OrdersTotal );
 
    if ( _OrdersTotal < pre_OrdersTotal )
        Alert( "Количество позиций уменьшилось! Было - ", pre_OrdersTotal, 
                                                         ", стало - ", _OrdersTotal );
 
    // Запоминаем количество позиций
    pre_OrdersTotal = _OrdersTotal;
 
return(0);
}

Следует отметить несколько особенностей этого эксперта:

  • Переменные first и pre_OrdersTotal объявлены как static. Таким образом, их значения не обнуляются при выходе из функции start(). Альтернативой статическим переменным могут быть глобальные переменные (объявленные вне функций), но большое их количество может вызвать путаницу с именами (по ошибке можно объявить одноименную переменную внутри функции, тогда могут возникнуть конфликты). Поэтому мы будем объявлять все переменные в теле функции.
  • Советник будет сообщать об изменении количества как открытых позиций, так и отложенных ордеров (функция OrdersTotal() возвращает их общее количество).
  • Советник не будет сообщать о срабатывании отложенного ордера, так как в этом случае значение OrdersTotal() не изменится
  • При первом запуске советник не сможет определить изменение количества ордеров, так как он не знает, сколько их было на предыдущем тике.
  • Сообщение появится только с поступлением нового тика по тому торговому инструменту, на графике которого работает эксперт. Других событий запуска у эксперта нет.

Последнюю проблему можно решить, поместив тело функции старт в цикл. Таким образом, проверка будет происходить не каждый тик, а с заданным интервалом времени:

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // Если это первый запуск эксперта, мы не знаем количество ордеров на предыдущем тике.
    // Поэтому просто запоминаем его, делаем пометку, что первый запуск уже был, и выходим.
    if ( first )
    {
        pre_OrdersTotal = _OrdersTotal;
        first = false;
        return(0);
    }
 
    while ( !IsStopped() )
    {
        _OrdersTotal = OrdersTotal();
 
        // Сравниваем количество позиций на предидущем тике с текущим количеством
        // Если оно изменилось, выводим сообщение
        if ( _OrdersTotal > pre_OrdersTotal ) 
            Alert( "Количество позиций увеличилось! Было - ", pre_OrdersTotal, 
                                                             ", стало - ", _OrdersTotal );
 
        if ( _OrdersTotal < pre_OrdersTotal )
            Alert( "Количество позиций уменьшилось! Было - ", pre_OrdersTotal, 
                                                             ", стало - ", _OrdersTotal );
 
        // Запоминаем количество позиций
        pre_OrdersTotal = _OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}

В этой версии сообщение об изменении количества позиций появляется моментально – можете проверить.

3. Фильтрация событий: критерии

В текущей реализации наш эксперт будет сообщать нам о появлении позиций по всем торговым инструментам. Но, чаще всего, информация необходима только об ордерах по текущему инструменту. Кроме того, ордера, принадлежащие эксперту, чаще всего помечаются "магическим числом" – MagicNumber-ом. Давайте "отфильтруем" события по этим двум критериям, то есть будем сообщать об изменении количества ордеров и позиций только с заданным MagicNumber и только по текущему инструменту.

extern int MagicNumber = 0;
 
int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = 0, now_OrdersTotal = 0, _GetLastError = 0;
 
    while ( !IsStopped() )
    {
        _OrdersTotal = OrdersTotal();
        now_OrdersTotal = 0;
 
        for ( int z = _OrdersTotal - 1; z >= 0; z -- )
        {
            if ( !OrderSelect( z, SELECT_BY_POS ) )
            {
                _GetLastError = GetLastError();
                Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - Error #", _GetLastError );
                continue;
            }
            // Считаем количество ордеров по текущему символу и с заданным MagicNumber
            if ( OrderMagicNumber() == MagicNumber && 
                  OrderSymbol() == Symbol() ) now_OrdersTotal ++;
        }
 
        // Выводим информацию только если это не первый запуск эксперта
        if ( !first )
        {
            // Сравниваем количество позиций на предидущем тике с текущим количеством
            // Если оно изменилось, выводим сообщение
            if ( now_OrdersTotal > pre_OrdersTotal ) 
                Alert( Symbol(), ": количество позиций с MagicNumber ", MagicNumber,
                       " увеличилось! Было - ", pre_OrdersTotal, ", стало - ", now_OrdersTotal );
 
            if ( now_OrdersTotal < pre_OrdersTotal )
                Alert( Symbol(), ": количество позиций с MagicNumber ", MagicNumber,
                         " уменьшилось! Было - ", pre_OrdersTotal, ", стало - ", now_OrdersTotal );
        }
        else
        {
            first = false;
        }
        //---- Запоминаем количество позиций
        pre_OrdersTotal = now_OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}


4. Конкретизация

Общее количество ордеров это, конечно, хорошо, но чаще всего необходима более конкретная информация – например, "была открыта бай или селл позиция?", "сработал ли отложенный ордер?", "позиция закрылась по Стоп Лоссу, по Тейк Профиту, или вручную?". Давайте постараемся составить наиболее полный список событий, которые надо отследить, и разделим их по группам.

  1. Открытие позиции
    • "Рыночная позиция"
      • Бай
      • Селл
    • Отложенный ордер
      • Бай-лимит
      • Селл-лимит
      • Бай-стоп
      • Селл-стоп
  2. Срабатывание ордера
    • Бай-лимит
    • Селл-лимит
    • Бай-стоп
    • Селл-стоп
  3. Закрытие позиции
    • "Рыночная позиция"
      • Бай
        • Стоп Лосс
        • Тейк Профит
        • Вручную (не Стоп Лосс и не Тейк Профит)
      • Селл
        • Стоп Лосс
        • Тейк Профит
        • Вручную
    • Отложенный ордер (удаление)
      • Бай-лимит
        • Время истечения
        • Вручную
      • Селл-лимит
        • Время истечения
        • Вручную
      • Бай-стоп
        • Время истечения
        • Вручную
      • Селл-стоп
        • Время истечения
        • Вручную
  4. Модификация позиции
    • "Рыночная позиция"
      • Бай
        • Стоп Лосс
        • Тейк Профит
      • Селл
        • Стоп Лосс
        • Тейк Профит
    • Отложенный ордер
      • Бай-лимит
        • Цена открытия
        • Стоп Лосс
        • Тейк Профит
        • Время истечения
      • Селл-лимит
        • Цена открытия
        • Стоп Лосс
        • Тейк Профит
        • Время истечения
      • Бай-стоп
        • Цена открытия
        • Стоп Лосс
        • Тейк Профит
        • Время истечения
      • Селл-стоп
        • Цена открытия
        • Стоп Лосс
        • Тейк Профит
        • Время истечения

Перед реализацией алгоритма, давайте подумаем – а все ли перечисленные события нужны? Если поставить себе цель написать эксперта, информирующего (или пишущего отчёт) обо всех изменениях во всех позициях, то ответ – да, все эти события должны быть учтены. Но наша цель скромнее – помочь торгующему эксперту понять, что происходит с позициями, с которыми он работает. А в этом случае список можно значительно сократить: открытие позиций, установку отложенных ордеров, весь блок модификации и все "ручные" закрытия позиций можно убрать – это события, которые генерирует сам советник (без его участия они не могут произойти). Итак, что у нас получится:

  1. Срабатывание ордера
    • Бай-лимит
    • Селл-лимит
    • Бай-стоп
    • Селл-стоп
  2. Закрытие позиции
    • "Рыночная позиция"
      • Бай
        • Стоп Лосс
        • Тейк Профит
      • Селл
        • Стоп Лосс
        • Тейк Профит
    • Отложенный ордер (время истечения)
      • Бай-лимит
      • Селл-лимит
      • Бай-стоп
      • Селл-стоп

В таком виде список намного менее "грозный", и мы можем приступать к написанию кода. Сделаю только оговорку, что для определения способа закрытия позиции (СЛ, ТП) есть несколько вариантов:

  • При уменьшении общего количества позиций, искать в истории позицию с максимальным временем закрытия, и по её параметрам определять, как она была закрыта;
  • Запоминать тикеты всех открытых позиций, а потом искать "пропавшую" позицию в истории по тикету.

Первый вариант проще в реализации, но может дать неправильную информацию – если на одном тике закроются две позиции, одна "вручную", а вторая по Стоп Лоссу, эксперт, найдя позицию с максимальным временем закрытия, сгенерирует 2 одинаковых события (если последняя позиция была закрыта вручную, оба события будут именно о "ручном" закрытии). И тогда эксперт не узнает, что одна из его позиций только что была закрыта по Стоп Лоссу.

Давайте не будем создавать себе проблемы, и сразу напишем максимально грамотный код.

extern int MagicNumber = 0;
 
// массив открытых позиций состоянием на предыдущий тик
int pre_OrdersArray[][2]; // [количество позиций][№ тикета, тип позиции]
 
int start()
{
    // флаг первого запуска
    static bool first = true;
    // код последней ошибки
    int _GetLastError = 0;
    // общее количество позиций
    int _OrdersTotal = 0;
    // кол-во позиций, соответствующих критериям (текущий инструмент и заданный MagicNumber),
    // состоянием на текущий тик
    int now_OrdersTotal = 0;
    // кол-во позиций, соответствующих критериям (текущий инструмент и заданный MagicNumber),
    // состоянием на предыдущий тик
    static int pre_OrdersTotal = 0;
    // массив открытых позиций состоянием на текущий тик
    int now_OrdersArray[][2]; // [№ в списке][№ тикета, тип позиции]
    // текущий номер позиции в массиве now_OrdersArray (для перебора)
    int now_CurOrder = 0;
    // текущий номер позиции в массиве pre_OrdersArray (для перебора)
    int pre_CurOrder = 0;
 
    // массив для хранения количества закрытых позиций каждого типа
    int now_ClosedOrdersArray[6][3]; // [тип ордера][тип закрытия]
    // массив для хранения количества сработавших отложенных ордеров
    int now_OpenedPendingOrders[4]; // [тип ордера] (всего есть 4 типа отложенных ордеров)
 
    // временные флаги
    bool OrderClosed = true, PendingOrderOpened = false;
    // временные переменные
    int ticket = 0, type = -1, close_type = -1;
 
 
    //+------------------------------------------------------------------+
    //| Бесконечный цикл
    //+------------------------------------------------------------------+
    while ( !IsStopped() )
    {
        // запоминаем общее количество позиций
        _OrdersTotal = OrdersTotal();
        // изменяем размер массива открытых позиций под текущее кол-во
        ArrayResize( now_OrdersArray, _OrdersTotal );
        // обнуляем массив
        ArrayInitialize( now_OrdersArray, 0.0 );
        // обнуляем количество позиций, соответствующих критериям
        now_OrdersTotal = 0;
 
        // обнуляем массивы закрытых позиций и сработавших ордеров
        ArrayInitialize( now_ClosedOrdersArray, 0.0 );
        ArrayInitialize( now_OpenedPendingOrders, 0.0 );
 
        //+------------------------------------------------------------------+
        //| Перебираем все позиции и записываем в массив только те, которые
        //| соответствуют критериям
        //+------------------------------------------------------------------+
        for ( int z = _OrdersTotal - 1; z >= 0; z -- )
        {
            if ( !OrderSelect( z, SELECT_BY_POS ) )
            {
                _GetLastError = GetLastError();
                Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - Error #", _GetLastError );
                continue;
            }
            // Считаем количество ордеров по текущему символу и с заданным MagicNumber
            if ( OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() )
            {
                now_OrdersArray[now_OrdersTotal][0] = OrderTicket();
                now_OrdersArray[now_OrdersTotal][1] = OrderType();
                now_OrdersTotal ++;
            }
        }
        // изменяем размер массива открытых позиций под кол-во позиций, соответствующих критериям
        ArrayResize( now_OrdersArray, now_OrdersTotal );
 
        //+------------------------------------------------------------------+
        //| Перебираем список позиций предыдущего тика, и считаем сколько закрылось позиций и
        //| сработало отложенных ордеров
        //+------------------------------------------------------------------+
        for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ )
        {
            // запоминаем тикет и тип ордера
            ticket = pre_OrdersArray[pre_CurOrder][0];
            type   = pre_OrdersArray[pre_CurOrder][1];
            // предпологаем, что если это позиция, то она закрылась
            OrderClosed = true;
            // предполагаем, что если это был отложенный ордер, то он не сработал
            PendingOrderOpened = false;
 
            // перебираем все позиции из текущего списка открытых позиций
            for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
            {
                // если позиция с таким тикетом есть в списке,
                if ( ticket == now_OrdersArray[now_CurOrder][0] )
                {
                    // значит позиция не была закрыта (ордер не был удалён)
                    OrderClosed = false;
 
                    // если её тип поменялся,
                    if ( type != now_OrdersArray[now_CurOrder][1] )
                    {
                        // значит это был отложенный ордер, и он сработал
                        PendingOrderOpened = true;
                    }
                    break;
                }
            }
            // если была закрыта позиция (удалён ордер),
            if ( OrderClosed )
            {
                // выбираем её
                if ( !OrderSelect( ticket, SELECT_BY_TICKET ) )
                {
                    _GetLastError = GetLastError();
                    Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - Error #", _GetLastError );
                    continue;
                }
                // и определяем, КАК закрылась позиция (удалился ордер):
                if ( type < 2 )
                {
                    // Бай и Селл: 0 - вручную, 1 - СЛ, 2 - ТП
                    close_type = 0;
                    if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1;
                    if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2;
                }
                else
                {
                    // Отложенные ордера: 0 - вручную, 1 - время истечения
                    close_type = 0;
                    if ( StringFind( OrderComment(), "expiration" ) >= 0 ) close_type = 1;
                }
                
                // и записываем в массив закрытых ордеров, что ордер с типом type 
                // закрылся при обстоятельствах close_type
                now_ClosedOrdersArray[type][close_type] ++;
                continue;
            }
            // если сработал отложенный ордер,
            if ( PendingOrderOpened )
            {
                // записываем в массив сработавших ордеров, что ордер с типом type сработал
                now_OpenedPendingOrders[type-2] ++;
                continue;
            }
        }
 
        //+------------------------------------------------------------------+
        //| Всю необходимую информацию собрали - выводим информацию
        //+------------------------------------------------------------------+
        // если это не первый запуск эксперта
        if ( !first )
        {
            // перебираем все элементы массива срабатывания отложенных ордеров
            for ( type = 2; type < 6; type ++ )
            {
                // и если элемент не пустой (ордер такого типа сработал), выводим информацию
                if ( now_OpenedPendingOrders[type-2] > 0 )
                    Alert( Symbol(), ": сработал ", _OrderType_str( type ), "-ордер!" );
            }
 
            // перебираем все элементы массива закрытых позиций
            for ( type = 0; type < 6; type ++ )
            {
                for ( close_type = 0; close_type < 3; close_type ++ )
                {
                    // и если элемент не пустой (была закрыта позиция), выводим информацию
                    if ( now_ClosedOrdersArray[type][close_type] > 0 ) CloseAlert( type, close_type );
                }
            }
        }
        else
        {
            first = false;
        }
 
        //---- сохраняем массив текущих позиций в массив предыдущих позиций
        ArrayResize( pre_OrdersArray, now_OrdersTotal );
        for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
        {
            pre_OrdersArray[now_CurOrder][0] = now_OrdersArray[now_CurOrder][0];
            pre_OrdersArray[now_CurOrder][1] = now_OrdersArray[now_CurOrder][1];
        }
        pre_OrdersTotal = now_OrdersTotal;
 
        Sleep(100);
    }
return(0);
}
void CloseAlert( int alert_type, int alert_close_type )
{
    string action = "";
    if ( alert_type < 2 )
    {
        switch ( alert_close_type )
        {
            case 1: action = " по СтопЛоссу!"; break;
            case 2: action = " по ТейкПрофиту!"; break;
            default: action = " вручную!"; break;
        }
        Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-позиция закрыта", action );
    }
    else
    {
        switch ( alert_close_type )
        {
            case 1: action = " по времени истечения!"; break;
            default: action = " вручную!"; break;
        }
        Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-ордер удалён", action );
    }
}
// возвращает OrderType в виде текста
string _OrderType_str( int _OrderType )
{
    switch ( _OrderType )
    {
        case OP_BUY:          return("Buy");
        case OP_SELL:         return("Sell");
        case OP_BUYLIMIT:     return("BuyLimit");
        case OP_BUYSTOP:      return("BuyStop");
        case OP_SELLLIMIT:    return("SellLimit");
        case OP_SELLSTOP:     return("SellStop");
        default:              return("UnknownOrderType");
    }
}


5. Интеграция в экспертов и использование

Для удобства использования "уловителя событий" из любого эксперта, "вынесем" код в файл Events.mq4, чтоб потом просто включать его в экспертов директивой #include. Для этого:

  • Оформим код в виде функции, которую в последствии и будем вызывать из экспертов
  • Уберём внешнюю переменную MagicNumber и добавим параметр функции magic (они будут играть одинаковую роль, делаем мы это просто чтоб не "засорять" список внешних переменных эксперта)
  • Добавим по одной глобальной переменной для каждого события – это сделает их использование максимально комфортным (также не забудем вставить обнуление этих переменных в начало функции)
  • Уберём бесконечный цикл – теперь "замеры" будут проводиться между вызовами функции (то есть, вызывая функцию, мы будем просто получать список изменений по сравнению с предыдущим вызовом функции)
  • Уберём Алерты, при необходимости их можно будет добавить в эксперта
  • Подчистим код в связи со всем вышеперечисленным

Вот, что у нас должно получиться:

// массив открытых позиций состоянием на предыдущий тик
int pre_OrdersArray[][2]; // [количество позиций][№ тикета, тип позиции]
 
// переменные событий
int eventBuyClosed_SL  = 0, eventBuyClosed_TP  = 0;
int eventSellClosed_SL = 0, eventSellClosed_TP = 0;
int eventBuyLimitDeleted_Exp  = 0, eventBuyStopDeleted_Exp  = 0;
int eventSellLimitDeleted_Exp = 0, eventSellStopDeleted_Exp = 0;
int eventBuyLimitOpened  = 0, eventBuyStopOpened  = 0;
int eventSellLimitOpened = 0, eventSellStopOpened = 0;
 
void CheckEvents( int magic = 0 )
{
    // флаг первого запуска
    static bool first = true;
    // код последней ошибки
    int _GetLastError = 0;
    // общее количество позиций
    int _OrdersTotal = OrdersTotal();
    // кол-во позиций, соответствующих критериям (текущий инструмент и заданный MagicNumber),
    // состоянием на текущий тик
    int now_OrdersTotal = 0;
    // кол-во позиций, соответствующих критериям, состоянием на предыдущий тик
    static int pre_OrdersTotal = 0;
    // массив открытых позиций состоянием на текущий тик
    int now_OrdersArray[][2]; // [№ в списке][№ тикета, тип позиции]
    // текущий номер позиции в массиве now_OrdersArray (для перебора)
    int now_CurOrder = 0;
    // текущий номер позиции в массиве pre_OrdersArray (для перебора)
    int pre_CurOrder = 0;
 
    // массив для хранения количества закрытых позиций каждого типа
    int now_ClosedOrdersArray[6][3]; // [тип ордера][тип закрытия]
    // массив для хранения количества сработавших отложенных ордеров
    int now_OpenedPendingOrders[4]; // [тип ордера]
 
    // временные флаги
    bool OrderClosed = true, PendingOrderOpened = false;
    // временные переменные
    int ticket = 0, type = -1, close_type = -1;
 
    //обнуляем переменные событий
    eventBuyClosed_SL  = 0; eventBuyClosed_TP  = 0;
    eventSellClosed_SL = 0; eventSellClosed_TP = 0;
    eventBuyLimitDeleted_Exp  = 0; eventBuyStopDeleted_Exp  = 0;
    eventSellLimitDeleted_Exp = 0; eventSellStopDeleted_Exp = 0;
    eventBuyLimitOpened  = 0; eventBuyStopOpened  = 0;
    eventSellLimitOpened = 0; eventSellStopOpened = 0;
 
    // изменяем размер массива открытых позиций под текущее кол-во
    ArrayResize( now_OrdersArray, MathMax( _OrdersTotal, 1 ) );
    // обнуляем массив
    ArrayInitialize( now_OrdersArray, 0.0 );
 
    // обнуляем массивы закрытых позиций и сработавших ордеров
    ArrayInitialize( now_ClosedOrdersArray, 0.0 );
    ArrayInitialize( now_OpenedPendingOrders, 0.0 );
 
    //+------------------------------------------------------------------+
    //| Перебираем все позиции и записываем в массив только те, которые
    //| соответствуют критериям
    //+------------------------------------------------------------------+
    for ( int z = _OrdersTotal - 1; z >= 0; z -- )
    {
        if ( !OrderSelect( z, SELECT_BY_POS ) )
        {
            _GetLastError = GetLastError();
            Print( "OrderSelect( ", z, ", SELECT_BY_POS ) - Error #", _GetLastError );
            continue;
        }
        // Считаем количество ордеров по текущему символу и с заданным MagicNumber
        if ( OrderMagicNumber() == magic && OrderSymbol() == Symbol() )
        {
            now_OrdersArray[now_OrdersTotal][0] = OrderTicket();
            now_OrdersArray[now_OrdersTotal][1] = OrderType();
            now_OrdersTotal ++;
        }
    }
    // изменяем размер массива открытых позиций под кол-во позиций, соответствующих критериям
    ArrayResize( now_OrdersArray, MathMax( now_OrdersTotal, 1 ) );
 
    //+------------------------------------------------------------------+
    //| Перебираем список позиций предыдущего тика, и считаем сколько закрылось позиций и
    //| сработало отложенных ордеров
    //+------------------------------------------------------------------+
    for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ )
    {
        // запоминаем тикет и тип ордера
        ticket = pre_OrdersArray[pre_CurOrder][0];
        type   = pre_OrdersArray[pre_CurOrder][1];
        // предпологаем, что если это позиция, то она закрылась
        OrderClosed = true;
        // предполагаем, что если это был отложенный ордер, то он не сработал
        PendingOrderOpened = false;
 
        // перебираем все позиции из текущего списка открытых позиций
        for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
        {
            // если позиция с таким тикетом есть в списке,
            if ( ticket == now_OrdersArray[now_CurOrder][0] )
            {
                // значит позиция не была закрыта (ордер не был удалён)
                OrderClosed = false;
 
                // если её тип поменялся,
                if ( type != now_OrdersArray[now_CurOrder][1] )
                {
                    // значит это был отложенный ордер, и он сработал
                    PendingOrderOpened = true;
                }
                break;
            }
        }
        // если была закрыта позиция (удалён ордер),
        if ( OrderClosed )
        {
            // выбираем её
            if ( !OrderSelect( ticket, SELECT_BY_TICKET ) )
            {
                _GetLastError = GetLastError();
                Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - Error #", _GetLastError );
                continue;
            }
            // и определяем, КАК закрылась позиция (удалился ордер):
            if ( type < 2 )
            {
                // Бай и Селл: 0 - вручную, 1 - СЛ, 2 - ТП
                close_type = 0;
                if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1;
                if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2;
            }
            else
            {
                // Отложенные ордера: 0 - вручную, 1 - время истечения
                close_type = 0;
                if ( StringFind( OrderComment(), "expiration" ) >= 0 ) close_type = 1;
            }
            
            // и записываем в массив закрытых ордеров, что ордер с типом type 
            // закрылся при обстоятельствах close_type
            now_ClosedOrdersArray[type][close_type] ++;
            continue;
        }
        // если сработал отложенный ордер,
        if ( PendingOrderOpened )
        {
            // записываем в массив сработавших ордеров, что ордер с типом type сработал
            now_OpenedPendingOrders[type-2] ++;
            continue;
        }
    }
 
    //+------------------------------------------------------------------+
    //| Всю необходимую информацию собрали - назначаем переменным событий нужные значения
    //+------------------------------------------------------------------+
    // если это не первый запуск эксперта
    if ( !first )
    {
        // перебираем все элементы массива срабатывания отложенных ордеров
        for ( type = 2; type < 6; type ++ )
        {
            // и если элемент не пустой (ордер такого типа сработал), меняем значение переменной
            if ( now_OpenedPendingOrders[type-2] > 0 )
                SetOpenEvent( type );
        }
 
        // перебираем все элементы массива закрытых позиций
        for ( type = 0; type < 6; type ++ )
        {
            for ( close_type = 0; close_type < 3; close_type ++ )
            {
                // и если элемент не пустой (была закрыта позиция), меняем значение переменной
                if ( now_ClosedOrdersArray[type][close_type] > 0 )
                    SetCloseEvent( type, close_type );
            }
        }
    }
    else
    {
        first = false;
    }
 
    //---- сохраняем массив текущих позиций в массив предыдущих позиций
    ArrayResize( pre_OrdersArray, MathMax( now_OrdersTotal, 1 ) );
    for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
    {
        pre_OrdersArray[now_CurOrder][0] = now_OrdersArray[now_CurOrder][0];
        pre_OrdersArray[now_CurOrder][1] = now_OrdersArray[now_CurOrder][1];
    }
    pre_OrdersTotal = now_OrdersTotal;
}
void SetOpenEvent( int SetOpenEvent_type )
{
    switch ( SetOpenEvent_type )
    {
        case OP_BUYLIMIT: eventBuyLimitOpened ++; return(0);
        case OP_BUYSTOP: eventBuyStopOpened ++; return(0);
        case OP_SELLLIMIT: eventSellLimitOpened ++; return(0);
        case OP_SELLSTOP: eventSellStopOpened ++; return(0);
    }
}
void SetCloseEvent( int SetCloseEvent_type, int SetCloseEvent_close_type )
{
    switch ( SetCloseEvent_type )
    {
        case OP_BUY:
        {
            if ( SetCloseEvent_close_type == 1 ) eventBuyClosed_SL ++;
            if ( SetCloseEvent_close_type == 2 ) eventBuyClosed_TP ++;
            return(0);
        }
        case OP_SELL:
        {
            if ( SetCloseEvent_close_type == 1 ) eventSellClosed_SL ++;
            if ( SetCloseEvent_close_type == 2 ) eventSellClosed_TP ++;
            return(0);
        }
        case OP_BUYLIMIT:
        {
            if ( SetCloseEvent_close_type == 1 ) eventBuyLimitDeleted_Exp ++;
            return(0);
        }
        case OP_BUYSTOP:
        {
            if ( SetCloseEvent_close_type == 1 ) eventBuyStopDeleted_Exp ++;
            return(0);
        }
        case OP_SELLLIMIT:
        {
            if ( SetCloseEvent_close_type == 1 ) eventSellLimitDeleted_Exp ++;
            return(0);
        }
        case OP_SELLSTOP:

        {
            if ( SetCloseEvent_close_type == 1 ) eventSellStopDeleted_Exp ++;
            return(0);
        }
    }
}

Теперь отслеживание событий можно использовать из любого эксперта, просто подлючив библиотеку. Ниже представлен пример такого эксперта (EventsExpert.mq4):

extern int MagicNumber = 0;
 
#include <Events.mq4>
 
int start()
{
    CheckEvents( MagicNumber );
 
    if ( eventBuyClosed_SL > 0 )
        Alert( Symbol(), ": Buy-позиция закрыта по СтопЛоссу!" );
 
    if ( eventBuyClosed_TP > 0 )
        Alert( Symbol(), ": Buy-позиция закрыта по ТейкПрофиту!" );
 
    if ( eventBuyLimitOpened > 0 || eventBuyStopOpened > 0 || 
          eventSellLimitOpened > 0 || eventSellStopOpened > 0 )
        Alert( Symbol(), ": сработал отложенный ордер!" );
return(0);
}



6. Заключение

В этой статье мы рассмотрели способы программного отслеживания событий в МetaТrader 4 с использованием средств языка MQL4, разделили их на группы и отфильтровали по заданным критериям. Также была создана библиотека, позволяющая без дополнительных усилий отслеживать некоторые события из любого эксперта.

Функция CheckEvents() может быть доработана (или служить шаблоном) для отслеживания других, не рассмотренных в данной статье, событий.
Прикрепленные файлы |
Events.mq4 (8.62 KB)
EventsExpert.mq4 (0.93 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
Nikolai Karetnikov
Nikolai Karetnikov | 19 июн. 2020 в 22:21

Хорошая штука! Пригодилась, спасибо!

IrishDance
IrishDance | 14 мар. 2022 в 13:47
Andrey Khatimlianskii #:


Здравствуйте, Андрей!

Спасибо за статью про события в МТ4 https://www.mql5.com/ru/articles/1399 ! Понимаю, что для вас это давно пройденная вещь, но если будет время то не могли бы вы добавить в библиотеку определение событий по модификации отложенных стоповых ордеров: цены открытия, стоплоссов, тейкпрофитов. 

Заранее огромное спасибо!

Andrey Khatimlianskii
Andrey Khatimlianskii | 14 мар. 2022 в 18:45
IrishDance #:

Здравствуйте, Андрей!

Спасибо за статью про события в МТ4 https://www.mql5.com/ru/articles/1399 ! Понимаю, что для вас это давно пройденная вещь, но если будет время то не могли бы вы добавить в библиотеку определение событий по модификации отложенных стоповых ордеров: цены открытия, стоплоссов, тейкпрофитов. 

Заранее огромное спасибо!

Добрый вечер!

Действительно, не вижу ни какого смысла развивать этот древний код.

Но зачем вам отслеживать модификации? Их производит не ваш советник?

И почему не переходите на МТ5?

IrishDance
IrishDance | 15 мар. 2022 в 06:44
Andrey Khatimlianskii #:

Добрый вечер!

Действительно, не вижу ни какого смысла развивать этот древний код.

Но зачем вам отслеживать модификации? Их производит не ваш советник?

И почему не переходите на МТ5?

Здравствуйте! Я торгую в полуавтоматическом режиме и планирую отправлять сигналы в телеграмм-канал, хочу максимально автоматизировать отправку информации, но пока не знаю как отправлять инфу об изменениях стопов и тейков, которые делаю вручную. Т.е. можно писать "от руки" сообщения в телегу но будет лучше это автоматизировать. На МТ5 пока не было необходимости переходить, может быть, в перспективе.

Я не профи кодер, могу написать чтото простое. Сначала думал, здесь нужно использовать OnChartEvent , который для меня темный лес, но почитал статьи на эту тему и выяснил, что нужно сравнивать прошлое и текущее значение а с этим я, наверное, справлюсь.

Andrey Khatimlianskii
Andrey Khatimlianskii | 15 мар. 2022 в 12:08
IrishDance #:

Здравствуйте! Я торгую в полуавтоматическом режиме и планирую отправлять сигналы в телеграмм-канал, хочу максимально автоматизировать отправку информации, но пока не знаю как отправлять инфу об изменениях стопов и тейков, которые делаю вручную. Т.е. можно писать "от руки" сообщения в телегу но будет лучше это автоматизировать. На МТ5 пока не было необходимости переходить, может быть, в перспективе.

Я не профи кодер, могу написать чтото простое. Сначала думал, здесь нужно использовать OnChartEvent , который для меня темный лес, но почитал статьи на эту тему и выяснил, что нужно сравнивать прошлое и текущее значение а с этим я, наверное, справлюсь.

Да, просто храните значения стопов для всех открытых позиций, и при проверке сравнивайте эти значения с актуальными.

Самостоятельная оценка результатов тестирования эксперта Самостоятельная оценка результатов тестирования эксперта
В статье представлены формулы и порядок расчета данных, отображаемых в отчете тестера.
MagicNumber - "магический" идентификатор ордера MagicNumber - "магический" идентификатор ордера
Статья посвящена бесконфликтной торговле нескольких экспертов на одном терминале МТ 4. Она научит эксперта управлять только "своими" ордерами, не модифицируя и не закрывая "чужие" (открытые вручную или другими экспертами) позиции. Статья рассчитана на пользователя, обладающего базовыми навыками работы с терминалом и программирования на MQL 4.
Работа с файлами. Пример визуализация важных рыночных событий Работа с файлами. Пример визуализация важных рыночных событий
Статья рассматривает перспективу использования MQL4 для более продуктивной работы на рынке ФОРЕКС.
Пауза между торговыми операциями Пауза между торговыми операциями
Статья посвящена организации паузы между торговыми операциями при работе нескольких экспертов на одном терминале МТ 4 и рассчитана на пользователя, обладающего базовыми навыками работы с терминалом и программирования на MQL 4.