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:

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);
 }