Eventos no MetaTrader 4

Andrey Khatimlianskii | 18 fevereiro, 2016


Introdução

O artigo trata do monitoramento programado de eventos no terminal do cliente do MetaTrader 4, como a abertura/fechamento/modificação de pedidos, e é destinado a um usuário que possua habilidades básicas no trabalho com o terminal e em programação em MQL4.


O que são eventos, e por que eles devem ser monitorados?

Para realizar a implementação de algumas estratégias, não basta simplesmente saber se há uma posição aberta pelo Expert Advisor. Às vezes é preciso "capturar" o momento de abertura/fechamento/modificação de uma posição ou de ativação de um pedido pendente. Não há funções integradas no MQL4 que possam resolver este problema de forma independente, mas há tudo que é necessário para criar uma ferramenta do tipo. É isso o que faremos.

Princípio de definição de evento

Como é possível saber se um evento ocorreu? O que é, em linhas gerais, um evento? Após tentar responder a essas perguntas, nós chegamos à seguinte conclusão: um evento é uma mudança de estado de um pedido/uma posição aberta. Para os fins da nossa tarefa, isso consiste, por exemplo, na alteração da quantidade de posições abertas ou no nível de Stop Loss de uma posição.


Como é possível detectar se um evento está ocorrendo no momento? É muito simples. Para fazer isso, é necessário memorizar o valor a ser monitorado (neste caso, a quantidade de posições) e, então, no próximo tick, por exemplo, compará-lo ao novo valor obtido. Vamos escrever um Expert Advisor simples capaz de nos informar a respeito de mudanças na quantidade de posições.

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // If this is the first launch of the Expert, we don't know the amount of orders on the previous tick.
    // So just remeber it, mark that the first launch has been happened, and exit.
    if ( first )
    {
        pre_OrdersTotal = _OrdersTotal;
        first = false;
        return(0);
    }
 
    // Compare the amount of positions on the previous tick with the current amount
    // If it has been changed, display message
    if ( _OrdersTotal > pre_OrdersTotal ) 
        Alert( "The amount of positions increased! There were - ", pre_OrdersTotal, 
                                                         ", there are now - ", _OrdersTotal );
 
    if ( _OrdersTotal < pre_OrdersTotal )
        Alert( "The amount of positions decreased! There were - ", pre_OrdersTotal, 
                                                         ", there are now - ", _OrdersTotal );
 
    // Memorize the amount of positions
    pre_OrdersTotal = _OrdersTotal;
 
return(0);
}

É necessário destacar algumas características especiais deste Expert Advisor:

  • As variáveis primeira e pre_OrdersTotal são declaradas estáticas. Portanto, os seus valores não são "zeroizados" ao deixar a função start(). Uma alternativa às variáveis estáticas são as variáveis globais (declaradas mais que funções), mas uma grande quantidade delas pode causar confusão de nomes (pode-se acidentalmente declarar uma variável de mesmo nome dentro da função, o que pode causar conflitos). Então nós iremos declarar todas as variáveis no corpo da função.
  • O Expert irá nos informar a respeito de mudanças na quantidade de posições abertas e de pedidos pendentes (a função OrdersTotal() fornece a sua quantidade total).
  • O Expert não irá nos informar a respeito do acionamento de um pedido pendente, pois, neste caso, o valor do OrdersTotal() não será alterado.
  • O Expert não será capaz de detectar mudanças na quantidade de pedidos na primeira inicialização, pois ele não 'sabe' quantos eles eram no tick anterior.
  • A mensagem irá aparecer apenas quando um novo tick chegar para o símbolo, no gráfico a partir do qual o Expert está trabalhando. O Expert não possui nenhum outro evento de inicialização.

O último problema pode ser resolvido colocando-se o corpo da função de início em um ciclo. Portanto, a verificação não irá ocorrer a cada tick, mas a certos intervalos de tempo:

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // If it is the first launch of the Expert Advisor, we do not know the amount of orders on the previous tick.
    // So we will just memorize it, check that the first launch has already happened, and exit.
    if ( first )
    {
        pre_OrdersTotal = _OrdersTotal;
        first = false;
        return(0);
    }
 
    while ( !IsStopped() )
    {
        _OrdersTotal = OrdersTotal();
 
        // Compare the amount of positions on the previous tick to the current amount.
        // If it has been changed, display he message
        if ( _OrdersTotal > pre_OrdersTotal ) 
            Alert( "The amount of positions increased! There were - ", pre_OrdersTotal, 
                                                             ", there are now - ", _OrdersTotal );
 
        if ( _OrdersTotal < pre_OrdersTotal )
            Alert( "The amount of positions decreased! There were - ", pre_OrdersTotal, 
                                                             ", there are now - ", _OrdersTotal );
 
        // Memorize the amount of positions
        pre_OrdersTotal = _OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}


Na versão acima, a mensagem a respeito de mudanças na quantidade de posição irá aparecer imediatamente: Você pode verificar!

Filtragem de eventos: Critérios

Assim como na sua realização atual, o nosso Expert Advisor irá nos informar a respeito de novas posições surgidas em todos os símbolos. Mas é mais comum que nós precisemos apenas de informações a respeito de mudanças em quantidades de pedidos no símbolo atual. Além disso, pedidos gerenciados por um Expert Advisor são comumente marcados com um MagicNumber. Vamos filtrar os eventos usando esses dois critérios, ou seja, nós iremos informar a respeito de mudanças nas quantidades de pedidos apenas em relação ao símbolo atual, e apenas em relação ao MagicNumber fornecido.

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;
            }
            // Count the amount of orders by the current symbol and with the specified MagicNumber
            if ( OrderMagicNumber() == MagicNumber && 
                  OrderSymbol() == Symbol() ) now_OrdersTotal ++;
        }
 
        // Display data only if this is not the first launch of the Expert
        if ( !first )
        {
            // Compare the amount of positions on the prevous tick with the current amount
            // If it has changed, display the message
            if ( now_OrdersTotal > pre_OrdersTotal ) 
                Alert( Symbol(), ": amount of positions having MagicNumber ", MagicNumber,
                       " increased! there were - ", pre_OrdersTotal, ", there are now - ", now_OrdersTotal );
 
            if ( now_OrdersTotal < pre_OrdersTotal )
                Alert( Symbol(), ": amount of positions having MagicNumber ", MagicNumber,
                         " decreased! There were - ", pre_OrdersTotal, ", there are now - ", now_OrdersTotal );
        }
        else
        {
            first = false;
        }
        //---- Remember the amount of positions
        pre_OrdersTotal = now_OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}

Refinamento

Encontrar a quantidade total de pedidos é, claro, muito bom. Mas às vezes algumas informações mais detalhadas são necessárias. Por exemplo, "uma posição de compra ou de venda foi aberta?", "um pedido pendente foi acionado?", "a posição foi fechada por Stop Loss, por Take Profit, ou manualmente?". Vamos tentar criar a lista de eventos a serem monitorados, de forma tão completa quanto possível, e dividi-la em grupos.

  1. Abertura de uma posição
    • "Posição de mercado"
      • Compra
      • Venda
    • Pedido pendente
      • Buy Limit
      • Sell Limit
      • Buy Stop
      • Sell Stop
  2. Acionamento de pedido
    • Buy Limit
    • Sell Limit
    • Buy Stop
    • Sell Stop
  3. Fechamento de uma posição
    • "Posição de mercado"
      • Compra
        • Stop Loss
        • Take Profit
        • Manualmente (nem por Stop Loss e nem por Take Profit)
      • Venda
        • Stop Loss
        • Take Profit
        • Manualmente
    • Pedido pendente (deleção)
      • Buy Limit
        • Expiração
        • Manualmente
      • Sell Limit
        • Tempo de expiração
        • Manualmente
      • Buy Stop
        • Expiração
        • Manualmente
      • Sell Stop
        • Expiração
        • Manualmente
  4. Modificação de uma posição
    • "Posição de mercado"
      • Compra
        • Stop Loss
        • Take Profit
      • Venda
        • Stop Loss
        • Take Profit
    • Pedido pendente
      • Buy Limit
        • Preço aberto
        • Stop Loss
        • Take Profit
        • Expiração
      • Sell Limit
        • Preço aberto
        • Stop Loss
        • Take Profit
        • Expiração
      • Buy Stop
        • Preço aberto
        • Stop Loss
        • Take Profit
        • Expiração
      • Sell Stop
        • Preço aberto
        • Stop Loss
        • Take Profit
        • Expiração

Antes de realizarmos o algoritmo, vamos verificar se todos os eventos supracitados são realmente necessários. Se nós vamos criar um Expert Advisor para nos informar ou reportar a respeito de todas as mudanças em todas as posições, a resposta é sim, todos esses eventos devem ser levados em consideração. Mas o nosso objetivo é mais humilde: Nós queremos auxiliar o Expert Advisor realizando transações a 'entender' o que acontece com as posições com as quais ele está trabalhando. Nesse caso, a lista pode ser significativamente mais curta: a abertura de posições, a feitura de pedidos pendentes, todos os itens de modificação e as posições de fechamento manual podem ser removidas da lista. Esses eventos são gerados pelo próprio Expert (elas não podem ocorrer sem o Expert). Portanto, é isso que nós teremos:

  1. Acionamento de pedido
    • Buy Limit
    • Sell Limit
    • Buy Stop
    • Sell Stop
  2. Fechamento de posição
    • "Posição de mercado"
      • Compra
        • Stop Loss
        • Take Profit
      • Venda
        • Stop Loss
        • Take Profit
    • Pedido pendente (expiração)
      • Buy Limit
      • Sell Limit
      • Buy Stop
      • Sell Stop

Deste modo, a lista fica bem menos intimidadora, e nós podemos começar a escrever o código. Devemos fazer apenas uma pequena observação: há vários modos de definir o método de fechamento de posições (SL, TP):

  • Caso a quantidade total de posições for reduzida, busque no histórico a posição mais recentemente fechada e determine, a partir dos seus parâmetros, a forma com que ela foi fechada, ou
  • memorize os bilhetes de todas as posições abertas e então busque pela posição 'desaparecida' através do seu bilhete no histórico.

O primeiro modo possui uma implementação mais simples, mas pode resultar em dados errados. Caso duas posições forem fechadas durante o mesmo tick, um manualmente e um por Stop Loss, o Expert irá gerar 2 eventos idênticos ao encontrar a posição com o tempo de fechamento mais recente (caso a última posição tiver sido fechada manualmente, ambos eventos serão considerados como fechados manualmente). O EA não irá 'saber' que uma das posições foi fechada por Stop Loss.

Portanto, de modo a evitar esse tipo de problema, vamos escrever um código tão literal quanto o possível.

extern int MagicNumber = 0;
 
// open positions array as on the previous tick
int pre_OrdersArray[][2]; // [amount of positions][ticket #, position type]
 
int start()
{
    // first launch flag
    static bool first = true;
    // last error code
    int _GetLastError = 0;
    // total amount of positions
    int _OrdersTotal = 0;
    // the amount of positions that meet the criteria (the current symbol and the MagicNumber),
    // as on the current tick
    int now_OrdersTotal = 0;
    // the amount of positions that meet the criteria (the current symbol and the specified MagicNumber),
    // as on the previous tick
    static int pre_OrdersTotal = 0;
    // open positions array as on the current tick
    int now_OrdersArray[][2]; // [# in the list][ticket #, position type]
    // the current number of the position in the array now_OrdersArray (for search)
    int now_CurOrder = 0;
    // the current number of the position in the array pre_OrdersArray (for search)
    int pre_CurOrder = 0;
 
    // array for storing the amount of closed positions of each type
    int now_ClosedOrdersArray[6][3]; // [order type][closing type]
    // array for storing the amount of triggered pending orders
    int now_OpenedPendingOrders[4]; // [order type] (there are only 4 types of pending orders totally)
 
    // temporary flags
    bool OrderClosed = true, PendingOrderOpened = false;
    // temporary variables
    int ticket = 0, type = -1, close_type = -1;
 
 
    //+------------------------------------------------------------------+
    //| Infinite loop
    //+------------------------------------------------------------------+
    while ( !IsStopped() )
    {
        // memorize the total amount of positions
        _OrdersTotal = OrdersTotal();
        // change the open positions array size for the current amount
        ArrayResize( now_OrdersArray, _OrdersTotal );
        // zeroize the array
        ArrayInitialize( now_OrdersArray, 0.0 );
        // zeroize the amount of positions met the criteria
        now_OrdersTotal = 0;
 
        // zeroize the arrays of closed positions and triggered orders
        ArrayInitialize( now_ClosedOrdersArray, 0.0 );
        ArrayInitialize( now_OpenedPendingOrders, 0.0 );
 
        //+------------------------------------------------------------------+
        //| Search in all positions and write only those in the array that   
        //| meet the criteria
        //+------------------------------------------------------------------+
        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;
            }
            // Count orders for the current symbol and with the specified MagicNumber
            if ( OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() )
            {
                now_OrdersArray[now_OrdersTotal][0] = OrderTicket();
                now_OrdersArray[now_OrdersTotal][1] = OrderType();
                now_OrdersTotal ++;
            }
        }
        // change the open positions array size for the amount of positions met the criteria
        ArrayResize( now_OrdersArray, now_OrdersTotal );
 
        //+------------------------------------------------------------------+
        //| Search in the list of positions on the previous tick and count
        //| how many positions have been closed and pending orders triggered
        //+------------------------------------------------------------------+
        for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ )
        {
            // memorize the ticket and the order type
            ticket = pre_OrdersArray[pre_CurOrder][0];
            type   = pre_OrdersArray[pre_CurOrder][1];
            // suppose that, if it is a position, it has been closed
            OrderClosed = true;
            // suppose that, if it is a pending order, it has not triggered
            PendingOrderOpened = false;
 
            // search in all positions from the current list of open positions
            for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
            {
                // if a position with this ticket is in the list,
                if ( ticket == now_OrdersArray[now_CurOrder][0] )
                {
                    // the position has not been closed (the order has not been cancelled)
                    OrderClosed = false;
 
                    // if its type has changed,
                    if ( type != now_OrdersArray[now_CurOrder][1] )
                    {
                        // it is a pending order that has triggered
                        PendingOrderOpened = true;
                    }
                    break;
                }
            }
            // if a position has not been closed (the order has not been cancelled),
            if ( OrderClosed )
            {
                // select it
                if ( !OrderSelect( ticket, SELECT_BY_TICKET ) )
                {
                    _GetLastError = GetLastError();
                    Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - Error #", _GetLastError );
                    continue;
                }
                // and check HOW the position has been closed (the order has been cancelled):
                if ( type < 2 )
                {
                    // Buy and Sell: 0 - manually, 1 - by SL, 2 - by TP
                    close_type = 0;
                    if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1;
                    if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2;
                }
                else
                {
                    // Pending orders: 0 - manually, 1 - expiration
                    close_type = 0;
                    if ( StringFind( OrderComment(), "expiration" ) >= 0 ) close_type = 1;
                }
                
                // and write in the closed orders array that the order of type 'type' 
                // was cancelled as close_type
                now_ClosedOrdersArray[type][close_type] ++;
                continue;
            }
            // if a pending order has triggered,
            if ( PendingOrderOpened )
            {
                // write in the triggered orders array that the order of type 'type' has triggered
                now_OpenedPendingOrders[type-2] ++;
                continue;
            }
        }
 
        //+------------------------------------------------------------------+
        //| Collected all necessary information - display it
        //+------------------------------------------------------------------+
        // if it is not the first launch of the Expert Advisor
        if ( !first )
        {
            // search in all elements of the triggered pending orders array
            for ( type = 2; type < 6; type ++ )
            {
                // if the element is not empty (an order of the type has triggered), display information
                if ( now_OpenedPendingOrders[type-2] > 0 )
                    Alert( Symbol(), ": triggered ", _OrderType_str( type ), " order!" );
            }
 
            // search in all elements of the closed positions array
            for ( type = 0; type < 6; type ++ )
            {
                for ( close_type = 0; close_type < 3; close_type ++ )
                {
                    // if the element is not empty (the position has been closed), display information
                    if ( now_ClosedOrdersArray[type][close_type] > 0 ) CloseAlert( type, close_type );
                }
            }
        }
        else
        {
            first = false;
        }
 
        //---- save the current positions array in the previous positions array
        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 = " by StopLoss!"; break;
            case 2: action = " by TakeProfit!"; break;
            default: action = " manually!"; break;
        }
        Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-position closed", action );
    }
    else
    {
        switch ( alert_close_type )
        {
            case 1: action = " by expiration!"; break;
            default: action = " manually!"; break;
        }
        Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-order cancelled", action );
    }
}
// returns OrderType as a text
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");
    }
}

Integração em Expert Advisors e utilização

Para usar convenientemente a 'retenção de eventos' a partir de qualquer Expert Advisor, vamos localizar o código no arquivo Events.mq4 apenas para incluí-lo nos EAs com o diretório #include. Para fazer isso:

  • forme o código como uma função a ser chamada a partir dos Expert Advisors futuramente;
  • remova a variável global MagicNumber e adicione o parâmetro da função "magic" (eles irão desempenhar a mesma função, nós fazemos isso apenas para não bloquear a lista das variáveis externas do Expert);
  • adicione uma variável global por evento - isso irá tornar o seu uso mais confortável (nós também devemos inserir a "zeroização" dessas variáveis no início da função);
  • remova o ciclo infinito - agora a 'amostragem' será feita entre as chamadas de função (ou seja, ao chamar uma função, nós receberemos apenas uma lista de alterações em comparação com a chamada anterior da função);
  • remova os alertas, eles podem ser adicionados ao Expert, se necessário;
  • refine o código de acordo com as diretrizes acima.

Isto é o que devemos obter como resultado:

// array of open positions as it was on the previous tick
int pre_OrdersArray[][2]; // [amount of positions][ticket #, positions type]
 
// variables of events
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 )
{
    // flag of the first launch
    static bool first = true;
    // the last error code
    int _GetLastError = 0;
    // total amount of positions
    int _OrdersTotal = OrdersTotal();
    // the amount of positions met the criteria (the current symbol and the specified MagicNumber),
    // as it is on the current tick
    int now_OrdersTotal = 0;
    // the amount of positions met the criteria as on the previous tick
    static int pre_OrdersTotal = 0;
    // array of open positions as of the current tick
    int now_OrdersArray[][2]; // [# in the list][ticket #, position type]
    // the current number of the position in array now_OrdersArray (for searching)
    int now_CurOrder = 0;
    // the current number of the position in array pre_OrdersArray (for searching)
    int pre_CurOrder = 0;
 
    // array for storing the amount of closed positions of each type
    int now_ClosedOrdersArray[6][3]; // [order type][closing type]
    // array for storing the amount of triggered pending orders
    int now_OpenedPendingOrders[4]; // [order type]
 
    // temporary flags
    bool OrderClosed = true, PendingOrderOpened = false;
    // temporary variables
    int ticket = 0, type = -1, close_type = -1;
 
    //zeroize the variables of events
    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;
 
    // change the open positions array size for the current amount
    ArrayResize( now_OrdersArray, MathMax( _OrdersTotal, 1 ) );
    // zeroize the array
    ArrayInitialize( now_OrdersArray, 0.0 );
 
    // zeroize arrays of closed positions and triggered orders
    ArrayInitialize( now_ClosedOrdersArray, 0.0 );
    ArrayInitialize( now_OpenedPendingOrders, 0.0 );
 
    //+------------------------------------------------------------------+
    //| Search in all positions and write in the array only those
    //| meeting the criteria
    //+------------------------------------------------------------------+
    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;
        }
        // Count the amount of orders on the current symbol with the specified MagicNumber
        if ( OrderMagicNumber() == magic && OrderSymbol() == Symbol() )
        {
            now_OrdersArray[now_OrdersTotal][0] = OrderTicket();
            now_OrdersArray[now_OrdersTotal][1] = OrderType();
            now_OrdersTotal ++;
        }
    }
    // change the open positions array size for the amount of positions meeting the criteria
    ArrayResize( now_OrdersArray, MathMax( now_OrdersTotal, 1 ) );
 
    //+-------------------------------------------------------------------------------------------------+
    //| Search in the list of the previous tick positions and count how many positions have been closed
    //| and pending orders triggered
    //+-------------------------------------------------------------------------------------------------+
    for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ )
    {
        // memorize the ticket number and the order type
        ticket = pre_OrdersArray[pre_CurOrder][0];
        type   = pre_OrdersArray[pre_CurOrder][1];
        // assume that, if it is a position, it has been closed
        OrderClosed = true;
        // assume that, if it is a pending order, it has not triggered
        PendingOrderOpened = false;
 
        // search in all positions from the current list of open positions
        for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
        {
            // if there is a position with such a ticket number in the list,
            if ( ticket == now_OrdersArray[now_CurOrder][0] )
            {
                // it means that the position has not been closed (the order has not been cancelled)
                OrderClosed = false;
 
                // if its type has changed,
                if ( type != now_OrdersArray[now_CurOrder][1] )
                {
                    // it means that it was a pending order and it triggered
                    PendingOrderOpened = true;
                }
                break;
            }
        }
        // if a position has been closed (an order has been cancelled),
        if ( OrderClosed )
        {
            // select it
            if ( !OrderSelect( ticket, SELECT_BY_TICKET ) )
            {
                _GetLastError = GetLastError();
                Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - Error #", _GetLastError );
                continue;
            }
            // and check HOW the position has been closed (the order has been cancelled):
            if ( type < 2 )
            {
                // Buy and Sell: 0 - manually, 1 - by SL, 2 - by TP
                close_type = 0;
                if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1;
                if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2;
            }
            else
            {
                // Pending orders: 0 - manually, 1 - expiration
                close_type = 0;
                if ( StringFind( OrderComment(), "expiration" ) >= 0 ) close_type = 1;
            }
            
            // and write in the closed orders array that the order of the type 'type' 
            // was closed by close_type
            now_ClosedOrdersArray[type][close_type] ++;
            continue;
        }
        // if a pending order has triggered,
        if ( PendingOrderOpened )
        {
            // write in the triggered orders array that the order of type 'type' triggered
            now_OpenedPendingOrders[type-2] ++;
            continue;
        }
    }
 
    //+--------------------------------------------------------------------------------------------------+
    //| All necessary information has been collected - assign necessary values to the variables of events
    //+--------------------------------------------------------------------------------------------------+
    // if it is not the first launch of the Expert Advisor
    if ( !first )
    {
        // search in all elements of the triggered pending orders array
        for ( type = 2; type < 6; type ++ )
        {
            // if the element is not empty (an order of the type has not triggered), change the variable value
            if ( now_OpenedPendingOrders[type-2] > 0 )
                SetOpenEvent( type );
        }
 
        // search in all elements of the closed positions array
        for ( type = 0; type < 6; type ++ )
        {
            for ( close_type = 0; close_type < 3; close_type ++ )
            {
                // if the element is not empty (a position has been closed), change the variable value
                if ( now_ClosedOrdersArray[type][close_type] > 0 )
                    SetCloseEvent( type, close_type );
            }
        }
    }
    else
    {
        first = false;
    }
 
    //---- save the current positions array in the previous positions array
    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);
        }
    }
}


Agora os eventos podem ser monitorados a partir de qualquer Expert Advisor: basta habilitar uma biblioteca. Um exemplo de um Expert Advisor do tipo (EventsExpert.mq4) é apresentado abaixo:

extern int MagicNumber = 0;
 
#include <Events.mq4>
 
int start()
{
    CheckEvents( MagicNumber );
 
    if ( eventBuyClosed_SL > 0 )
        Alert( Symbol(), ": Buy position was closed by StopLoss!" );
 
    if ( eventBuyClosed_TP > 0 )
        Alert( Symbol(), ": Buy position was closed by TakeProfit!" );
 
    if ( eventBuyLimitOpened > 0 || eventBuyStopOpened > 0 || 
          eventSellLimitOpened > 0 || eventSellStopOpened > 0 )
        Alert( Symbol(), ": pending order triggered!" );
return(0);
}

6. Conclusão

Neste artigo nós consideramos as formas de monitoramento programado de eventos no MetaTrader 4 através do MQL4. Nós dividimos todos os eventos em 3 grupos e os filtramos de acordo com os critérios pré-definidos. Nós também criamos uma biblioteca que permite o monitoramento fácil de alguns eventos a partir de qualquer Expert Advisor.
A função CheckEvents() pode ser completada (ou usada como um modelo) para monitorar outros eventos que não tenham sido considerados no artigo.