English Русский 中文 Deutsch 日本語 Português
Eventos en MetaTrader 4

Eventos en MetaTrader 4

MetaTrader 4Ejemplos | 28 enero 2016, 12:32
3 117 0
Andrey Khatimlianskii
Andrey Khatimlianskii

Introducción

En este artículo vamos a tratar el seguimiento programado de eventos en el Terminal Cliente MetaTrader 4, tales como la apertura, el cierre y la modificación de órdenes. Se dirige a los usuarios que tienen unos conocimientos básicos en programación MQL 4 y ya saben manejar el terminal.


¿Qué son los eventos y por qué hay que registrarlos?

Para implementar algunas estrategias de trading no basta solo con saber si tal o cual Asesor Experto ha abierto una posición determinada. A veces hay que "capturar" el momento de apertura/cierre/modificación de una posición, o saber cuando se dispara una orden pendiente. MQL4 no incorpora funciones que solucionan este problema, pero hay muchas opciones a nuestra disposición para crear tal herramienta. Esto es lo que vamos a hacer.


Definición de evento

¿Cómo podemos saber que un evento ha ocurrido? Es más, ¿qué es un evento? Al intentar responder estas preguntas llegaremos a esta conclusión: un evento es un cambio de estado de la orden. Por lo que a nosotros respecta, esto significa por ejemplo cambiar la cantidad de posiciones abiertas, o el nivel de Stop Loss de una posición.

¿Cómo podemos detectar que un evento está ocurriendo en un momento determinado? Esto es sencillo. Para ello hay que memorizar el valor a seguir, en nuestro caso la cantidad de posiciones, y a continuación, por ejemplo, compararla en el siguiente tick con el nuevo valor. Escribamos un sencillo Asesor Experto que informe sobre los cambios que ocurren en la cantidad de posiciones.

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // Si esta es la primera vez que se lanza el experto, no sabemos la cantidad de órdenes del tick anterior.
    // Así que simplemente la recordamos, dejamos constancia que el primer lanzamiento ha ocurrido, y salimos.
    if ( first )
    {
        pre_OrdersTotal = _OrdersTotal;
        first = false;
        return(0);
    }
 
    // Comparar la cantidad de posiciones del tick anterior con la cantidad actual
    // Si ha cambiado, mostramos un mensaje
    if ( _OrdersTotal > pre_OrdersTotal ) 
        Alert( "¡La cantidad de posiciones se ha incrementado! Había - ", pre_OrdersTotal, 
                                                         ", ahora hay - ", _OrdersTotal );
 
    if ( _OrdersTotal < pre_OrdersTotal )
        Alert( "¡La cantidad de posiciones ha disminuido! Había - ", pre_OrdersTotal, 
                                                         ", ahora hay - ", _OrdersTotal );
 
    // Memorizamos la cantidad de posiciones
    pre_OrdersTotal = _OrdersTotal;
 
return(0);
}

Cabe señalar algunas características especiales de este Asesor Experto:

  • Las variables first y pre_OrdersTotal se declaran static. Por consiguiente, sus valores no se establecen a cero cuando salimos de la función start(). Las variables globales, declaradas por encima de las funciones, son una alternativa a las variables estáticas. Pero hay que utilizarlas con cuidado porque si se declaran muchas entonces los nombres pueden colisionar por error, causando conflictos. Así que declararemos todas las variables en el cuerpo de la función.
  • El Experto informará de los cambios producidos en las cantidades, tanto en las posiciones abiertas como en las órdenes pendientes (function OrdersTotal() devuelve la cantidad total).
  • El Experto no informará del disparo de órdenes pendientes porque, en este caso, el valor de OrdersTotal() no cambiará.
  • Por otro lado, no detectará ningún cambio en la cantidad de órdenes durante el primer lanzamiento porque todavía no existe ese dato en el tick previo.
  • El mensaje aparecerá solo cuando entre un nuevo tick en el símbolo, en el gráfico donde el Asesor Experto esté trabajando. El Experto no tiene más eventos de lanzamiento.

El último problema se puede solucionar poniendo el cuerpo de la función start en un bucle. Así, la comprobación no se llevará a cabo en cada tick, sino que se ejecutará en los intervalos de tiempo determinados:

int start()
{
    static bool first = true;
    static int pre_OrdersTotal = 0;
    int _OrdersTotal = OrdersTotal();
 
    // Si este es le primer lanzamiento del Asesor Experto, no podemos saber la cantidad de órdenes del tick previo.
    // Así que simplemente memorizaremos este dato, comprobaremos que el primer lanzamiento se ha producido, y saldremos.
    if ( first )
    {
        pre_OrdersTotal = _OrdersTotal;
        first = false;
        return(0);
    }
 
    while ( !IsStopped() )
    {
        _OrdersTotal = OrdersTotal();
 
        // Comparamos la cantidad de posiciones del tick anterior con la cantidad actual.
        // Si ha cambiado mostramos el mensaje
        if ( _OrdersTotal > pre_OrdersTotal ) 
            Alert( "¡La cantidad de posiciones se ha incrementado! Había - ", pre_OrdersTotal, 
                                                             ", ahora hay - ", _OrdersTotal );
 
        if ( _OrdersTotal < pre_OrdersTotal )
            Alert( "¡La cantidad de posiciones ha disminuido! Había - ", pre_OrdersTotal, 
                                                             ", ahora hay - ", _OrdersTotal );
 
        // Memorizamos la cantidad de posiciones
        pre_OrdersTotal = _OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}

En la versión anterior, el mensaje que informa de los cambios sobre la cantidad de posiciones aparecerá inmediatamente, ¡puede comprobarlo usted mismo!


Criterio de filtrado de mensajes

Al igual que en la implementación actual, nuestro Asesor Experto informará sobre las nuevas posiciones que aparecen en los símbolos. Pero lo más habitual es que solamente necesitemos información de los cambios producidos en el número de órdenes del símbolo actual. Además, las órdenes gestionadas por un Asesor Experto suelen venir definidas con un MagicNumber. Vamos a filtrar pues los eventos de acuerdo a estos dos criterios, es decir, informaremos de los cambios producidos en el número de órdenes solo para el símbolo actual, dado un MagicNumber determinado.

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;
            }
            // Contamos la cantidad de órdenes del símbolo actual, con el MagicNumber especificado
            if ( OrderMagicNumber() == MagicNumber && 
                  OrderSymbol() == Symbol() ) now_OrdersTotal ++;
        }
 
        // Si este no es el primer lanzamiento del Asesor Experto, mostramos los datos
        if ( !first )
        {
            // Comparamos la cantidad de posiciones del tick anterior con la cantidad actual
            // Si ha cambiado, mostramos el mensaje
            if ( now_OrdersTotal > pre_OrdersTotal ) 
                Alert( Symbol(), ": cantidad de posiciones con MagicNumber ", MagicNumber,
                       " incrementado! había - ", pre_OrdersTotal, ", ahora hay - ", now_OrdersTotal );
 
            if ( now_OrdersTotal < pre_OrdersTotal )
                Alert( Symbol(), ": cantidad de posiciones con MagicNumber ", MagicNumber,
                         " disminuido! Había - ", pre_OrdersTotal, ", ahora hay - ", now_OrdersTotal );
        }
        else
        {
            first = false;
        }
        //---- Recordamos la cantidad de posiciones
        pre_OrdersTotal = now_OrdersTotal;
        
        Sleep(100);
    }
 
return(0);
}


Refinamiento

Por supuesto que calcular la cantidad total de órdenes es muy bueno, pero a veces hace falta una información adicional más detallada; por ejemplo, "¿Se trataba de una orden de compra o de venta?", "¿Se disparó alguna orden pendiente?", "¿La posición se cerró manualmente, o la cerró algún StopLoss o TakeProfit?". Hagamos una lista completa de los eventos que se tienen que seguir y clasifiquémosla en grupos.

  1. Apertura de posición
    • "Posición de mercado"
      • Buy
      • Sell
    • Orden pendiente
      • Buy Limit
      • Sell Limit
      • Buy Stop
      • Sell Stop
  2. Orden de disparo
    • Buy Limit
    • Sell Limit
    • Buy Stop
    • Sell Stop
  3. Cierre de posición
    • "Posición de mercado"
      • Buy
        • Stop Loss
        • Take Profit
        • Manual (ni Stop Loss ni Take Profit)
      • Sell
        • Stop Loss
        • Take Profit
        • Manual
    • Orden pendiente (eliminación)
      • Buy Limit
        • Vencimiento
        • Manual
      • Sell Limit
        • Hora de vencimiento
        • Manual
      • Buy Stop
        • Vencimiento
        • Manual
      • Sell Stop
        • Vencimiento
        • Manual
  4. Modificación de la posición
    • "Posición de mercado"
      • Buy
        • Stop Loss
        • Take Profit
      • Sell
        • Stop Loss
        • Take Profit
    • Orden pendiente
      • Buy Limit
        • Precio de apertura
        • Stop Loss
        • Take Profit
        • Vencimiento
      • Sell Limit
        • Precio de apertura
        • Stop Loss
        • Take Profit
        • Vencimiento
      • Buy Stop
        • Precio de apertura
        • Stop Loss
        • Take Profit
        • Vencimiento
      • Sell Stop
        • Precio de apertura
        • Stop Loss
        • Take Profit
        • Vencimiento

Antes de implementar el algoritmo, comprobemos primero si todos los eventos listados arriba son realmente necesarios. Si tenemos que crear un Asesor Experto que nos informe de todos los cambios producidos en todas las posiciones, la respuesta es que sí; entonces hay que tener en cuenta todos los eventos. Pero nuestro objetivo es más modesto: queremos que nuestro Asesor Experto entienda mejor lo que sucede en las posiciones donde trabaja. En este caso la lista se hace significativamente más pequeña: apertura de posición, colocación de órdenes pendientes, se pueden eliminar de la lista tanto los ítems de modificación y las posiciones de cierre manual. El mismo Asesor Experto genera estos eventos (no pueden ocurrir sin el Experto). En consecuencia tenemos lo siguiente:

  1. Orden de disparo
    • Buy Limit
    • Sell Limit
    • Buy Stop
    • Sell Stop
  2. Cierre de posición
    • "Posición de mercado"
      • Buy
        • Stop Loss
        • Take Profit
      • Sell
        • Stop Loss
        • Take Profit
    • Orden pendiente (vencimiento)
      • Buy Limit
      • Sell Limit
      • Buy Stop
      • Sell Stop

Ahora, la lista es mucho más manejable y podemos comenzar a escribir algo de código. Es importante señalar que el método de cierre de posiciones (SL, TP) se puede definir de varias maneras:

  • Si el número total de posiciones ha disminuido, buscamos en el historial la posición cerrada más recientemente y, por medio de sus parámetros, detectamos cómo se ha cerrado, o
  • memorizamos los tickets de todas las posiciones abiertas y luego buscamos la posición "desaparecida" mediante su ticket, en el historial.

La implementación del primer esquema es más fácil de implementar, pero puede proporcionar datos erróneos. Si se cierran dos posiciones en el mismo tick, una de forma manual y la otra con un Stop Loss, el Asesor Experto generará dos eventos idénticos con la hora de cierre más reciente. Si la última posición se cierra manualmente, los dos eventos serán considerados como un cierre manual. Entonces el EA no sabrá que una de las posiciones se ha cerrado con un Stop Loss.

Así pues, para evitar tales problemas simplificaremos el código al máximo.

extern int MagicNumber = 0;
 
// abrir el array de posiciones como en el tick anterior
int pre_OrdersArray[][2]; // [cantidad de posiciones][ticket #, tipo de posición]
 
int start()
{
    // bandera del primer lanzamiento
    static bool first = true;
    // código del último error
    int _GetLastError = 0;
    // cantidad total de posiciones
    int _OrdersTotal = 0;
    // la cantidad de posiciones que satisfacen los criterios (símbolo actual y MagicNumber),
    // como en el tick actual
    int now_OrdersTotal = 0;
    // la cantidad de posiciones que satisfacen los criterios (símbolo actual y MagicNumber especificado),
    // como en el tick anterior
    static int pre_OrdersTotal = 0;
    // abrir el array de posiciones como en el tick actual
    int now_OrdersArray[][2]; // [# en la lista][ticket #, tipo de posición]
    // el número actual de la posición en el array now_OrdersArray (para la búsqueda)
    int now_CurOrder = 0;
    // el número actual de la posición en el array pre_OrdersArray (para la búsqueda)
    int pre_CurOrder = 0;
 
    // array que almacena la cantidad de posiciones cerradas de cada tipo
    int now_ClosedOrdersArray[6][3]; // [tipo de orden][tipo de cierre]
    // array que almacena la cantidad de órdenes pendientes disparadas
    int now_OpenedPendingOrders[4]; // [tipo de orden] (solo hay 4 tipos de órdenes pendientes en total)
 
    // banderas temporales
    bool OrderClosed = true, PendingOrderOpened = false;
    // variables temporales
    int ticket = 0, type = -1, close_type = -1;
 
 
    //+------------------------------------------------------------------+
    //| Bucle infinito
    //+------------------------------------------------------------------+
    while ( !IsStopped() )
    {
        // memorizamos la cantidad total de posiciones
        _OrdersTotal = OrdersTotal();
        // cambiamos el tamaño del array de posiciones abiertas para la cantidad actual
        ArrayResize( now_OrdersArray, _OrdersTotal );
        // establecemos el array a cero
        ArrayInitialize( now_OrdersArray, 0.0 );
        // establecemos a cero la cantidad de posiciones que satisfacen los criterios
        now_OrdersTotal = 0;
 
        // establecemos a cero los arrays de posiciones cerradas y de órdenes disparadas
        ArrayInitialize( now_ClosedOrdersArray, 0.0 );
        ArrayInitialize( now_OpenedPendingOrders, 0.0 );
 
        //+------------------------------------------------------------------+
        //| Buscamos en todas las posiciones y escribimos en el array solo las   
        //| que satisfacen los criterios
        //+------------------------------------------------------------------+
        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;
            }
            // Contamos las órdenes del símbolo actual con el MagicNumber especificado
            if ( OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() )
            {
                now_OrdersArray[now_OrdersTotal][0] = OrderTicket();
                now_OrdersArray[now_OrdersTotal][1] = OrderType();
                now_OrdersTotal ++;
            }
        }
        // cambiamos el tamaño del array de posiciones abiertas según la cantidad de posiciones que satisfacen los criterios
        ArrayResize( now_OrdersArray, now_OrdersTotal );
 
        //+------------------------------------------------------------------+
        //| Buscamos en la lista de posiciones del tick anterior y contamos
        //| cuántas posiciones se han cerrado y cuántas órdenes pendientes se han disparado
        //+------------------------------------------------------------------+
        for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ )
        {
            // memorizamos el ticket y el tipo de orden
            ticket = pre_OrdersArray[pre_CurOrder][0];
            type   = pre_OrdersArray[pre_CurOrder][1];
            // suponemos que, si se trata de una posición, se ha cerrado
            OrderClosed = true;
            // suponemos que, si se trata de una orden pendiente, no se ha disparado
            PendingOrderOpened = false;
 
            // buscamos en todas las posiciones de la lista actual de posiciones abiertas
            for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
            {
                // si una posición con este ticket está en la lista,
                if ( ticket == now_OrdersArray[now_CurOrder][0] )
                {
                    // la posición no se ha cerrado (la orden no ha sido cancelada)
                    OrderClosed = false;
 
                    // si el tipo ha cambiado,
                    if ( type != now_OrdersArray[now_CurOrder][1] )
                    {
                        // entonces es una orden pendiente que se ha disparado
                        PendingOrderOpened = true;
                    }
                    break;
                }
            }
            // si la posición no se ha cerrado (la orden no se ha cancelado),
            if ( OrderClosed )
            {
                // la seleccionamos
                if ( !OrderSelect( ticket, SELECT_BY_TICKET ) )
                {
                    _GetLastError = GetLastError();
                    Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - Error #", _GetLastError );
                    continue;
                }
                // y comprobamos CÓMO se ha cerrado la posición (la orden ha sido cancelada):
                if ( type < 2 )
                {
                    // Buy y Sell: 0 - manualmente, 1 - por SL, 2 - por TP
                    close_type = 0;
                    if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1;
                    if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2;
                }
                else
                {
                    // Órdenes pendientes: 0 - manualmente, 1 - vencimiento
                    close_type = 0;
                    if ( StringFind( OrderComment(), "vencimiento" ) >= 0 ) close_type = 1;
                }
                
                // y escribimos en el array de órdenes cerradas que la orden de tipo 'type' 
                // se ha cancelado como close_type
                now_ClosedOrdersArray[type][close_type] ++;
                continue;
            }
            // si se ha disparado una orden pendiente,
            if ( PendingOrderOpened )
            {
                // escribimos en el array de órdenes disparadas que la orden de tipo 'type' se ha disparado
                now_OpenedPendingOrders[type-2] ++;
                continue;
            }
        }
 
        //+------------------------------------------------------------------+
        //| Una vez recopilada toda la información necesaria, la mostramos
        //+------------------------------------------------------------------+
        // si no es el primer lanzamiento del Asesor Experto
        if ( !first )
        {
            // rastreamos todos los elementos del array de órdenes pendientes disparadas
            for ( type = 2; type < 6; type ++ )
            {
                // si el elemento no está vacío (se ha disparado una orden del tipo), mostramos la información
                if ( now_OpenedPendingOrders[type-2] > 0 )
                    Alert( Symbol(), ": disparo ", _OrderType_str( type ), ", orden." );
            }
 
            // buscamos en todos los elementos del array de posiciones cerradas
            for ( type = 0; type < 6; type ++ )
            {
                for ( close_type = 0; close_type < 3; close_type ++ )
                {
                    // si el elemento no está vacío (la posición se ha cerrado), mostramos la información
                    if ( now_ClosedOrdersArray[type][close_type] > 0 ) CloseAlert( type, close_type );
                }
            }
        }
        else
        {
            first = false;
        }
 
        //---- guardamos el array de posiciones actual en el array de posiciones anterior
        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 = " por StopLoss!"; break;
            case 2: action = " por TakeProfit!"; break;
            default: action = " manualmente!"; break;
        }
        Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-posición cerrada", action );
    }
    else
    {
        switch ( alert_close_type )
        {
            case 1: action = " por vencimiento!"; break;
            default: action = " manualmente!"; break;
        }
        Alert( Symbol(), ": ", _OrderType_str( alert_type ), "-orden cancelada", action );
    }
}
// devuelve OrderType como texto
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("Tipo de orden desconocido");
    }
}


Integración y utilización en Asesores Expertos

Para manejar convenientemente la "trampa del evento", vamos a poner el código en el archivo Events.mq4 para incluirlo en los EAs con la directiva #include. Para ello:

  • encapsule el código en una función que pueda llamarse después desde un Asesor Experto;
  • borre las variables globales MagicNumber y añada un parámetro de función mágica. Esto equivale a lo anterior, procedemos así para no bloquear la lista de variables externas del Experto;
  • añada una variable global a cada evento. Esto mejorará la usabilidad (también hay que establecer a cero el valor de estas variables en la función start);
  • elimine el bucle infinito – ahora el muestreo se hará entre llamadas de funciones, es decir, llamando a una función obtendremos una lista de cambios comparada con la llamada anterior a la función.
  • elimine los Alerts, si es necesario se pueden añadir al Experto;
  • refine su código de acuerdo a todo lo anterior.

Esto es lo que obtenemos como resultado:

// array de posiciones abiertas como en el tick anterior
int pre_OrdersArray[][2]; // [cantidad de posiciones][ticket #, tipo de posiciones]
 
// variables de eventos
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 )
{
    // bandera del primer lanzamiento
    static bool first = true;
    // el último código de error
    int _GetLastError = 0;
    // cantidad total de posiciones
    int _OrdersTotal = OrdersTotal();
    // la cantidad de posiciones satisfizo los criterios (el símbolo actual y el MagicNumber especificado),
    // como en el tick actual
    int now_OrdersTotal = 0;
    // la cantidad de posiciones satisfizo los criterios como en el tick anterior
    static int pre_OrdersTotal = 0;
    // array de posiciones abiertas como en el tick actual
    int now_OrdersArray[][2]; // [# en la lista][ticket #, tipo de posición]
    // el número actual de la posición en el array now_OrdersArray (para la búsqueda)
    int now_CurOrder = 0;
    // el número actual de la posición en el array pre_OrdersArray (para la búsqueda)
    int pre_CurOrder = 0;
 
    // array que almacena la cantidad de posiciones cerradas de cada tipo
    int now_ClosedOrdersArray[6][3]; // [tipo de orden][tipo de cierre]
    // array que almacena la cantidad de órdenes pendientes disparadas
    int now_OpenedPendingOrders[4]; // [tipo de orden]
 
    // banderas temporales
    bool OrderClosed = true, PendingOrderOpened = false;
    // variables temporales
    int ticket = 0, type = -1, close_type = -1;
 
    // establecemos a cero las variables de los eventos
    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;
 
    // cambiamos el tamaño del array de posiciones abiertas para la cantidad actual
    ArrayResize( now_OrdersArray, MathMax( _OrdersTotal, 1 ) );
    // establecemos el array a cero
    ArrayInitialize( now_OrdersArray, 0.0 );
 
    // establecemos a cero los arrays de las posiciones cerradas y de las posiciones disparadas
    ArrayInitialize( now_ClosedOrdersArray, 0.0 );
    ArrayInitialize( now_OpenedPendingOrders, 0.0 );
 
    //+------------------------------------------------------------------+
    //| Buscamos en todas las posiciones y escribimos en el array solo las
    //| que satisfacen los criterios
    //+------------------------------------------------------------------+
    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;
        }
        // Contamos la cantidad de órdenes del símbolo actual con el MagicNumber especificado
        if ( OrderMagicNumber() == magic && OrderSymbol() == Symbol() )
        {
            now_OrdersArray[now_OrdersTotal][0] = OrderTicket();
            now_OrdersArray[now_OrdersTotal][1] = OrderType();
            now_OrdersTotal ++;
        }
    }
    // cambiamos el tamaño del array de posiciones abiertas por la cantidad de posiciones que satisfacen los criterios
    ArrayResize( now_OrdersArray, MathMax( now_OrdersTotal, 1 ) );
 
    //+-------------------------------------------------------------------------------------------------+
    //| Buscamos en la lista de posiciones del tick anterior y contamos cuántas posiciones se han cerrado
    //| y cuántas órdenes pendientes se han disparado
    //+-------------------------------------------------------------------------------------------------+
    for ( pre_CurOrder = 0; pre_CurOrder < pre_OrdersTotal; pre_CurOrder ++ )
    {
        // memorizamos el número de ticket y el tipo de orden
        ticket = pre_OrdersArray[pre_CurOrder][0];
        type   = pre_OrdersArray[pre_CurOrder][1];
        // asumimos que, si se trata de una posición, esta se ha cerrado
        OrderClosed = true;
        // asumimos que, si se trata de una orden pendiente, esta no se ha disparado
        PendingOrderOpened = false;
 
        // buscamos en todas las posiciones de la lista actual de posiciones abiertas
        for ( now_CurOrder = 0; now_CurOrder < now_OrdersTotal; now_CurOrder ++ )
        {
            // si hay una posición con dicho número de ticket en la lista,
            if ( ticket == now_OrdersArray[now_CurOrder][0] )
            {
                // significa que la posición no se ha cerrado (la orden no se ha cancelado)
                OrderClosed = false;
 
                // si el tipo ha cambiado,
                if ( type != now_OrdersArray[now_CurOrder][1] )
                {
                    // significa que se trataba de una orden pendiente que se disparó
                    PendingOrderOpened = true;
                }
                break;
            }
        }
        // si la posición se ha cerrado (la orden se ha cancelado),
        if ( OrderClosed )
        {
            // la seleccionamos
            if ( !OrderSelect( ticket, SELECT_BY_TICKET ) )
            {
                _GetLastError = GetLastError();
                Print( "OrderSelect( ", ticket, ", SELECT_BY_TICKET ) - Error #", _GetLastError );
                continue;
            }
            // y comprobamos CÓMO se ha cerrado la posición (la orden ha sido cancelada):
            if ( type < 2 )
            {
                // Buy y Sell: 0 - manualmente, 1 - por SL, 2 - por TP
                close_type = 0;
                if ( StringFind( OrderComment(), "[sl]" ) >= 0 ) close_type = 1;
                if ( StringFind( OrderComment(), "[tp]" ) >= 0 ) close_type = 2;
            }
            else
            {
                // Órdenes pendientes: 0 - manualmente, 1 - vencimiento
                close_type = 0;
                if ( StringFind( OrderComment(), "vencimiento" ) >= 0 ) close_type = 1;
            }
            
            // y escribimos en el array de órdenes cerradas que la orden de tipo 'type' 
            // fue cerrada por close_type
            now_ClosedOrdersArray[type][close_type] ++;
            continue;
        }
        // si se ha disparado una orden pendiente,
        if ( PendingOrderOpened )
        {
            // escribimos en el array de órdenes disparadas que la orden de tipo 'type' se ha disparado
            now_OpenedPendingOrders[type-2] ++;
            continue;
        }
    }
 
    //+--------------------------------------------------------------------------------------------------+
    //| Se ha recopilado la información necesaria - asignamos los valores correspondientes a las variables de los eventos
    //+--------------------------------------------------------------------------------------------------+
    // si no es el primer lanzamiento del Asesor Experto
    if ( !first )
    {
        // rastreamos todos los elementos del array de órdenes pendientes disparadas
        for ( type = 2; type < 6; type ++ )
        {
            // si el elemento no está vacío (la orden del tipo no se ha disparado), cambiamos el valor de la variable
            if ( now_OpenedPendingOrders[type-2] > 0 )
                SetOpenEvent( type );
        }
 
        // buscamos en todos los elementos del array de posiciones cerradas
        for ( type = 0; type < 6; type ++ )
        {
            for ( close_type = 0; close_type < 3; close_type ++ )
            {
                // si el elemento no está vacío (la posición se ha cerrado), cambiamos el valor de la variable
                if ( now_ClosedOrdersArray[type][close_type] > 0 )
                    SetCloseEvent( type, close_type );
            }
        }
    }
    else
    {
        first = false;
    }
 
    //---- guardamos el array de posiciones actual en el array de posiciones anterior
    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);
        }
    }
}


Ahora se puede hacer un seguimiento de los eventos desde cualquier Asesor Experto. Tan solo hay que activar la librería. A continuación mostramos un Asesor Experto de ejemplo (EventsExpert.mq4):

extern int MagicNumber = 0;
 
#include <Events.mq4>
 
int start()
{
    CheckEvents( MagicNumber );
 
    if ( eventBuyClosed_SL > 0 )
        Alert( Symbol(), ": Posición Buy cerrada por StopLoss!" );
 
    if ( eventBuyClosed_TP > 0 )
        Alert( Symbol(), ": Posición Buy cerrada por TakeProfit!" );
 
    if ( eventBuyLimitOpened > 0 || eventBuyStopOpened > 0 || 
          eventSellLimitOpened > 0 || eventSellStopOpened > 0 )
        Alert( Symbol(), ": orden pendiente disparada!" );
return(0);
}


6. Conclusión

En este artículo hemos expuesto varias formas de programar el seguimiento de los eventos en MetaTrader 4, con el lenguaje de programación MQL4. Hemos dividido los eventos en tres grupos, y los hemos filtrado de acuerdo a los criterios predefinidos. También hemos creado una librería que permite seguir fácilmente algunos eventos en cualquier Asesor Experto.
La función CheckEvents() se puede ampliar, o utilizar como plantilla, para seguir otros eventos aparte de los expuestos en este artículo.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1399

Archivos adjuntos |
Events.mq4 (8.62 KB)
EventsExpert.mq4 (0.93 KB)
Cómo evaluar los resultados de los Asesores Expertos Cómo evaluar los resultados de los Asesores Expertos
El presente artículo explica el funcionamiento del Informe de pruebas de MetaTrader 4, mostrando los cálculos realizados.
Cómo funcionan las órdenes en los programas complejos Cómo funcionan las órdenes en los programas complejos
En este artículo vamos a explicar los principios generales que rigen el funcionamiento de las órdenes en programas extensos y complejos.
Recetas MQL5 - Escribiendo nuestra propia profundidad de mercado Recetas MQL5 - Escribiendo nuestra propia profundidad de mercado
Este artículo enseñará a los lectores a trabajar de forma programática con la profundidad de mercado, también describirá el principio de funcionamiento de la clase CMarketBook, que ampliará de forma orgánica la biblioteca estándar de clases MQL5 y proporcionará métodos cómodos para trabajar con la profundidad del mercado.
Trabajando con archivos. Un ejemplo de visualización de eventos importantes del mercado Trabajando con archivos. Un ejemplo de visualización de eventos importantes del mercado
Este artículo explica cómo se puede trabajar de forma más productiva con MQL4 en los mercados FOREX.