Una pausa entre operaciones
Andrey Khatimlianskii | 19 enero, 2016
1. Tiempo transcurrido entre la ejecución de las operaciones
En el terminal MetaTrader 3 se podían ejecutar dos operaciones en un intervalo de 10 segundos. Al desarrollar МТ4, MetaQuotes Software Corporation satisfizo las peticiones de los traders y eliminó esta limitación. Efectivamente, hay situaciones donde es aceptable ejecutar varias operaciones una después de la otra, moviendo los niveles de StopLoss de algunas posiciones, borrando órdenes pendientes, etc. Pero algunos traders utilizaron esta capacidad de manera equivocada, implementando asesores expertos "letales" que abrían posiciones una detrás de la otra, sin descanso. Esto derivó en el bloqueo de cuentas, o, por lo menos, la reacción de los brokers no fue positiva porque no veían con buenos ojos esta práctica.
Este artículo no es para tales desarrolladores de asesores expertos. Se dirige a los programadores que quieren hacer trading de forma amigable, tanto para ellos mismos como para sus brokers.
2. ¿Uno o más expertos? ¿Cuál es la diferencia?
Si usted lanza un terminal con un experto trabajando en el mismo, entonces la gestión de las pausas entre las operaciones es sencillo. Basta con crear una variable global (es decir, una variable declarada a nivel global, no confundir con las Variables Globales del terminal) que almacene la hora de la última operación. Y, por supuesto, antes de ejecutar una operación tiene que comprobar si el tiempo transcurrido tras el último intento de colocar una posición es suficiente.
Lo anterior tiene este aspecto:
datetime LastTradeTime = 0; int start() { // comprobar si deberíamos entrar al mercado ... // calcular los niveles de StopLoss y TakeProfit, y el tamaño del lote ... // comprobar si ha transcurrido tiempo suficiente tras la última operación if(LocalTime() - LastTradeTime < 10) { Comment("¡Han pasado menos de 10 segundos tras la última operación!", " ¡El experto no colocará ninguna orden!"); return(-1); } // abrir una posición if(OrderSend(...) < 0) { Alert( "Error de apertura de posición # ", GetLastError() ); return(-2); } // almacenar la hora de la última transacción LastTradeTime = LocalTime(); return(0); }
Este ejemplo funciona para un experto trabajando en un terminal. Si uno o varios expertos más se lanzan a la vez, no mantendrán la pausa de 10 segundos. No pueden saber si otro de los expertos está haciendo trading. Cada uno tiene su propia variable LastTradeTime. Para salir de esta situación, debe crear una Variable Global y almacenar en ella la hora de la operación. Aquí hacemos referencia a una Variable Global del terminal, todos los asesores expertos podrán acceder a ella.
3. La función _PauseBeforeTrade()
Como el código que hace la pausa es el mismo en todos los asesores, es conveniente encapsularlo en una función. Esto mejorará la usabilidad del código y contribuirá a reducir el número de líneas totales del asesor experto.
Antes de escribir el código vamos a definir la tarea de forma más específica, lo que nos ayudará a ahorrar tiempo y esfuerzo. La función tiene que hacer lo siguiente:
- comprobar si una variable global ha sido creada y, en caso negativo, crearla. Puede parecer más lógico hacerlo en la función init(), pero entonces existe el riesgo de que el usuario la borre, en cuyo caso desaparecería la pausa entre las operaciones de los asesores expertos. Así que lo pondremos en la función que se tiene que crear;
- almacenar la hora actual en la variable global para que los otros asesores expertos puedan mantener la pausa;
- comprobar si ha transcurrido tiempo suficiente después de la última operación. Por cuestiones de usabilidad, también hay que añadir una variable externa que establezca la duración de la pausa. Su valor se puede cambiar en cada experto, de forma separada;
- información de visualización del proceso, así como de los errores ocurridos en el mismo;
- devolver valores diferentes dependiendo de los resultados de rendimiento.
Si la función detecta que no ha pasado tiempo suficiente después de la última operación, espera. La función Sleep() proporciona la espera y la comprobación de IsStopped(). Es decir, si usted elimina el experto del gráfico mientras está "durmiendo", no se colgará o se detendrá forzosamente.
Pero para describirlo mejor, vamos a mostrar información sobre el tiempo que hay que esperar, cada segundo del "sueño".
Este es el resultado:
extern int PauseBeforeTrade = 10; // pausa entre operaciones (en segundos) ///////////////////////////////////////////////////////////////////////////////// // int _PauseBeforeTrade() // // La función establece la hora local para la variable global LastTradeTime. // Si la hora local en el momento del lanzamiento es menor que LastTradeTime + // PauseBeforeTrade, la función esperará. // Si no hay ninguna variable global LastTradeTime, la función la creará. // Códigos de retorno: // 1 - ejecución satisfactoria // -1 - el usuario interrumpió el asesor experto (el experto se eliminó del gráfico, // se cerró el terminal, se cambió el símbolo o el periodo del gráfico, etc.) ///////////////////////////////////////////////////////////////////////////////// int _PauseBeforeTrade() { // no hay motivo para mantener la pausa durante las pruebas, tan solo se termina la función if(IsTesting()) return(1); int _GetLastError = 0; int _LastTradeTime, RealPauseBeforeTrade; //+------------------------------------------------------------------+ //| Comprobar si la variable global existe; si no, crearla | //+------------------------------------------------------------------+ while(true) { // si el usuario interrumpió el experto, dejar de trabajar if(IsStopped()) { Print("¡El usuario paró el asesor experto!"); return(-1); } // comprobar si existe la variable global // en caso afirmativo, salir del bucle if(GlobalVariableCheck("LastTradeTime")) break; else // si GlobalVariableCheck devuelve FALSE, significa que la variable global no existe, o bien // ha ocurrido un error en la comprobación { _GetLastError = GetLastError(); // si se trata de un error, mostrar la información, esperar 0.1 segundos, y comprobar // de nuevo if(_GetLastError != 0) { Print("_PauseBeforeTrade()-GlobalVariableCheck(\"LastTradeTime\")-Error #", _GetLastError ); Sleep(100); continue; } } // si no hay ningún error, significa que no hay ninguna variable global, intentamos crearla // si GlobalVariableSet > 0, significa que la variable global se ha creado correctamente. // Terminar la función if(GlobalVariableSet("LastTradeTime", LocalTime() ) > 0) return(1); else // si GlobalVariableSet devuelve un valor <= 0 significa que ha ocurrido un error durante la creación // de la variable { _GetLastError = GetLastError(); // mostrar la información, esperar 0.1 sec, y reiniciar el intento if(_GetLastError != 0) { Print("_PauseBeforeTrade()-GlobalVariableSet(\"LastTradeTime\", ", LocalTime(), ") - Error #", _GetLastError ); Sleep(100); continue; } } } //+--------------------------------------------------------------------------------+ //| Si el rendimiento de la función alcanza este punto significa que | //| la variable global existe. | //| Esperar a que LocalTime() se convierta en > LastTradeTime + PauseBeforeTrade | //+--------------------------------------------------------------------------------+ while(true) { // si el usuario detuvo el experto, paramos if(IsStopped()) { Print("¡El usuario paró el asesor experto!"); return(-1); } // obtener el valor de la variable global _LastTradeTime = GlobalVariableGet("LastTradeTime"); // si ocurre un error, se visualiza la información, esperamos 0.1 segundos, // e intentamos otra vez _GetLastError = GetLastError(); if(_GetLastError != 0) { Print("_PauseBeforeTrade()-GlobalVariableGet(\"LastTradeTime\")-Error #", _GetLastError ); continue; } // contamos los segundos transcurridos desde la última operación RealPauseBeforeTrade = LocalTime() - _LastTradeTime; // si han pasado menos de PauseBeforeTrade segundos if(RealPauseBeforeTrade < PauseBeforeTrade) { // mostramos la información, esperamos un segundo, y volvemos a comprobar Comment("Pausa entre transacciones. Tiempo restante: ", PauseBeforeTrade - RealPauseBeforeTrade, " segundos" ); Sleep(1000); continue; } // si el tiempo transcurrido es mayor que PauseBeforeTrade segundos, paramos el bucle else break; } //+--------------------------------------------------------------------------------+ //| Si el rendimiento de la función alcanza este punto significa que | //| la variable existe y la hora local supera LastTradeTime + PauseBeforeTrade | //| Establecemos el valor de la hora local para la variable global LastTradeTime | //+--------------------------------------------------------------------------------+ while(true) { // si el usuario detuvo el experto, paramos if(IsStopped()) { Print("¡El usuario paró el asesor experto!"); return(-1); } // Establecemos el valor de la hora local para la variable LastTradeTime. // Si se ejecuta correctamente salimos if(GlobalVariableSet( "LastTradeTime", LocalTime() ) > 0) { Comment(""); return(1); } else // si GlobalVariableSet devuelve un valor <= 0 significa que ha habido un error { _GetLastError = GetLastError(); // mostramos la información, esperamos 0.1 segundos y volvemos a intentarlo if(_GetLastError != 0) { Print("_PauseBeforeTrade()-GlobalVariableSet(\"LastTradeTime\", ", LocalTime(), " ) - Error #", _GetLastError ); Sleep(100); continue; } } } }
4. Integración y utilización de los Asesores Expertos
Para probar la función, hemos creado un experto de diagnóstico que tuvo que operar manteniendo las pausas entre las transacciones. La función _PauseBeforeTrade() se puso de manera preliminar en el archivo PauseBeforeTrade.mq4, incluido en el experto con la directiva #include.
¡Atención! Este asesor experto está pensado exclusivamente para probar la función. No lo utilice en un entorno real.
#include <PauseBeforeTrade.mq4> int ticket = 0; int start() { // si no hay ninguna posición abierta por este experto if(ticket <= 0) { // mantenemos una pausa entre las transacciones; si ocurre un error, salimos if(_PauseBeforeTrade() < 0) return(-1); // actualizamos la información del mercado RefreshRates(); // se intenta abrir una posición ticket = OrderSend(Symbol(), OP_BUY, 0.1, Ask, 5, 0.0, 0.0, "PauseTest", 123, 0, Lime); if(ticket < 0) Alert("Error OrderSend № ", GetLastError()); } // si hay alguna posición abierta por este experto else { // mantenemos la pausa entre las operaciones; si ocurre algún error salimos if(_PauseBeforeTrade() < 0) return(-1); // actualizamos la información del mercado RefreshRates(); // e intentamos cerrar la posición if (!OrderClose( ticket, 0.1, Bid, 5, Lime )) Alert("Error al cerrar la orden № ", GetLastError()); else ticket = 0; } return(0); }
Después de lo anterior, adjuntamos un experto al gráfico EURUSD-M1, y otro completamente idéntico al gráfico GBPUSD-M1. El resultado no necesitó una larga espera, sino que ambos expertos comenzaron a operar manteniendo la pausa de 10 segundos entre las operaciones:
5. Posibles problemas
Cuando hay varios expertos trabajando con una variable global pueden ocurrir errores. Para evitarlos tenemos que delimitar el acceso a la variable. El artículo titulado "Cómo manejar el Error 146, "Trade context busy"" describe detalladamente el funcionamiento de un algoritmo de "delimitación". Este es el algoritmo que vamos a utilizar.
La versión final del asesor experto es la siguiente:
#include <PauseBeforeTrade.mq4> #include <TradeContext.mq4> int ticket = 0; int start() { // si no hay ninguna posición abierta por este experto if(ticket <= 0) { // esperamos a que la operación esté libre y la ocupamos; si ocurre un error, // salimos) if(TradeIsBusy() < 0) return(-1); // mantenemos la pausa entre las operaciones if(_PauseBeforeTrade() < 0) { // si ocurre un error, liberamos la operación y salimos TradeIsNotBusy(); return(-1); } // actualizamos la información del mercado RefreshRates(); // se intenta abrir una posición ticket = OrderSend(Symbol(), OP_BUY, 0.1, Ask, 5, 0.0, 0.0, "PauseTest", 123, 0, Lime); if (ticket < 0) Alert("Error OrderSend № ", GetLastError()); // liberamos la operación TradeIsNotBusy(); } // si hay alguna posición abierta por este experto else { // esperamos a que la operación esté libre y la ocupamos; si ocurre un error, // salimos) if(TradeIsBusy() < 0) return(-1); // mantenemos la pausa entre las operaciones if(_PauseBeforeTrade() < 0) { // si ocurre un error liberamos la operación y salimos TradeIsNotBusy(); return(-1); } // actualizamos la información del mercado RefreshRates(); // e intentamos cerrar la posición if(!OrderClose( ticket, 0.1, Bid, 5, Lime)) Alert("Error al cerrar la orden № ", GetLastError()); else ticket = 0; // liberamos la operación TradeIsNotBusy(); } return(0); }