Eventos en MetaTrader 4
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.
- Apertura de posición
- "Posición de mercado"
- Buy
- Sell
- Orden pendiente
- Buy Limit
- Sell Limit
- Buy Stop
- Sell Stop
- "Posición de mercado"
- Orden de disparo
- Buy Limit
- Sell Limit
- Buy Stop
- Sell Stop
- 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
- Buy
- Orden pendiente (eliminación)
- Buy Limit
- Vencimiento
- Manual
- Sell Limit
- Hora de vencimiento
- Manual
- Buy Stop
- Vencimiento
- Manual
- Sell Stop
- Vencimiento
- Manual
- Buy Limit
- "Posición de mercado"
- Modificación de la posición
- "Posición de mercado"
- Buy
- Stop Loss
- Take Profit
- Sell
- Stop Loss
- Take Profit
- Buy
- 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
- Buy Limit
- "Posición de mercado"
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:
- Orden de disparo
- Buy Limit
- Sell Limit
- Buy Stop
- Sell Stop
- Cierre de posición
- "Posición de mercado"
- Buy
- Stop Loss
- Take Profit
- Sell
- Stop Loss
- Take Profit
- Buy
- Orden pendiente (vencimiento)
- Buy Limit
- Sell Limit
- Buy Stop
- Sell Stop
- "Posición de mercado"
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
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso