Eventos de Trading en MetaTrader 5

MetaQuotes | 20 diciembre, 2013


Introducción

Todos los comandos para ejecutar operaciones de trading se pasan al servidor de trading desde el terminal de cliente MetaTrader 5 a través de solicitudes de envío. Cada solicitud se debe llenar correctamente de acuerdo con la operación solicitada; de lo contrario, no pasará la validación primaria y no se aceptará por el servidor para su procesamiento.

Las solicitudes aceptadas por el servidor de trading se almacenan en forma de órdenes que pueden ser pendientes o ejecutarse inmediatamente por el precio de mercado. Las órdenes se almacenan en el servidor hasta que se llenan o cancelan. El resultado de la ejecución de una orden es una transacción.

Una transacción cambia la posición de trading por un símbolo determinado; puede abrir, cerrar, incrementar, reducir o invertir la posición. Por tanto, una posición abierta siempre es el resultado de ejecutar una o más transacciones. Puede encontrar más información detallada en el artículo Órdenes, Posiciones, y Transacciones en MetaTrader 5.

Este artículo describe el concepto, términos y procesos que fluyen en el período desde el envío de una solicitud hasta su traslado al historial de trading tras su procesamiento.


Paso de Solicitud del Terminal de Cliente al Servidor de Trading

Para ejecutar una operación de trading, debe enviar una orden al sistema de trading. Siempre se envía una solicitud al servidor de trading cuando se presenta una orden del terminal de cliente. La estructura de una solicitud debe llenarse correctamente, independientemente de cómo lleve a cabo las operaciones de trading - manualmente o usando un programa MQL5.

Para ejecutar una operación de trading manualmente, debe abrir la ventana de diálogo para llenar una solicitud de trading pulsando la tecla F9. Cuando haga trading automáticamente a través de MQL5, las solicitudes se envían usando la función OrderSend(). Puesto que un gran número de solicitudes incorrectas pueden causar una sobrecarga del servidor de trading poco deseable, cada solicitud se debe revisar antes de enviarla usando la función OrderCheck(). El resultado de la revisión de una solicitud se coloca en una variable descrita por la estructura MqlTradeCheckresult.

Importante: La corrección de cada solicitud se revisa en el terminal de cliente antes de enviarla al servidor de trading. Solicitudes deliberadamente incorrectas (para comprar un millón de lotes o con un precio negativo) no pasan del terminal de cliente. Esto se hace para proteger los servidores de una masa de solicitudes incorrectas causadas por un error en un programa MQL5.

Una vez que la solicitud llega al servidor de trading, pasa la revisión primaria, comprobado...

Una solicitud incorrecta que no pase la revisión primaria en el servidor será rechazada. Siempre se informará al terminal de cliente de los resultados de la revisión de una solicitud, enviando una respuesta. La respuesta del servidor de trading puede tomarse de una variable del tipo MqlTradeResult, que se pasa como segundo parámetro en la función OrderSend() al enviar una solicitud.

Enviar Solicitudes de Trading al Servidor desde el Terminal de Cliente

Si una solicitud pasa la revisión primaria de corrección, se colocará en las solicitudes que aguardan para ser procesadas. Como resultado de procesar una solicitud, se crea una orden (comando para ejecutar una operación de trading) en la base del servidor de trading. No obstante, hay dos tipos de solicitudes que no resultan en la creación de una orden:

  1. una solicitud para cambiar una posición (cambiar su Stop Loss y/o Take Profit);
  2. una solicitud para modificar una orden pendiente (sus niveles de precio y fecha de caducidad).

El terminal de cliente recibe el mensaje de que la solicitud ha sido aceptada y colocada en el subsistema de trading de la plataforma MetaTrader 5. El servidor coloca la solicitud aceptada en la cola de solicitudes para su futuro procesamiento, que puede resultar en...

La duración de estancia de una solicitud en la cola del servidor tiene un límite de tres minutos. Una vez que el este período se ha excedido, la solicitud se quita de la cola de solicitudes.


Enviar Eventos de Trading del Servidor al Terminal de Cliente

El modelo de evento y las funciones de gestión de eventos se implementan en el lenguaje MQL5. Esto significa que en respuesta a cualquier evento predefinido, la ejecución del entorno MQL5 llama a la función apropiada: el controlador de eventos. Para el procesamiento de eventos de trading está la función predefinida OnTrade(). Dentro de ella se debe colocar el código para trabajar con órdenes, posiciones y transacciones. Esta función se llama solo para Asesores Expertos, no se usará en indicadores y scripts aún cuando usted añada en ellos una función con el mismo nombre y tipo.

Los eventos de trading se generan por el servidor en caso de...

Note que una operación puede provocar varios eventos. Por ejemplo, la activación de una orden pendiente lleva al desarrollo de dos eventos:

  1. la aparición de una transacción que se escribe en el historial de trading;
  2. el traslado de la orden pendiente de la lista de órdenes activas a la lista de órdenes del historial (la orden se ha trasladado al historial).

Otro ejemplo de eventos múltiples es la ejecución de varias transacciones en la base de una sola orden, en caso de que el volumen requerido no se pueda obtener de una sola oferta opuesta. El servidor de trading crea y envía los mensajes sobre cada evento al terminal de cliente. Por eso, la función OnTrade() se puede llamar varias veces para un evento aparentemente individual. Este es un ejemplo sencillo del procedimiento de una orden en el subsistema de trading de la plataforma MetaTrader 5.

Ejemplo: mientras una orden pendiente para la compra de 10 lotes de EURUSD aguarda su ejecución, aparecen ofertas contrarias para la venta de 1, 4 y 5 lotes. Esas tres solicitudes juntas suponen el volumen requerido de 10 lotes, de modo que se ejecutan una a una, si la política de ejecución permite la realización de operaciones de trading por partes.

Como resultado de la ejecución de 4 órdenes, el servidor realizará 3 transacciones de 1, 4 y 5 lotes en la base de ofertas contrarias existentes. ¿Cuántos eventos de trading se generarán en este caso? La primera solicitud opuesta para la venta de un lote llevará a la ejecución de la transacción de 1 lote. Este sería el primer evento de trading (transacción de 1 lote). Pero la orden pendiente para comprar 10 lotes también ha cambiado; ahora es la orden para comprar 9 lotes de EURUSD. El cambio de volumen de la orden pendiente sería el segundo evento de trading (cambio de volumen de una orden pendiente).

Generación de Eventos de Trading

Para la segunda transacción de 4 lotes, se generarán los otros dos evento de trading, y se enviará un mensaje sobre cada uno de ellos al terminal de cliente que inició la orden pendiente inicial para la compra de 10 lotes EURUSD.

La última transacción de 5 lotes generará tres eventos de trading:

  1. la transacción de 5 lotes;
  2. el cambio de volumen;
  3. el traslado de la orden al historial de órdenes.

Como resultado de la ejecución de la transacción, el terminal de cliente recibirá 7 eventos de trading uno tras otro (suponiendo que la conexión entre el terminal de cliente y el servidor de trading sea estable y no se pierdan mensajes). Esos mensajes se deben procesar en un Asesor Experto usando la función OnTrade().

Importante: Cada mensaje sobre un evento de trading puede aparecer como resultado de una o más solicitudes. Cada solicitud puede llevar a la generación de varios eventos de trading. No puede depender de la declaración "una solicitud - un evento de trading", puesto que el procesamiento de eventos puede desarrollarse en varias etapas, y cada operación podría cambiar el estado de órdenes, posiciones y el historial de trading.


Procesamiento de Órdenes con el Servidor de Trading

Todas las órdenes que aguardan su ejecución se trasladarán al historial al final, ya sea porque la condición para su ejecución se cumplió, o porque fueron canceladas. Hay muchas variantes de una cancelación de orden:

Independientemente de la razón por la que una orden activa pasa al historial, el mensaje sobre el cambio se envía al terminal de cliente. Los mensajes sobre el evento de trading no van a todos los terminales de cliente, sino a los que están conectados con la cuenta correspondiente.



Importante: El hecho de aceptar una solicitud por el servidor de trading no siempre lleva a la ejecución de la operación solicitada. Significa que la solicitud ha pasado la validación después de que fuera al servidor.

Por eso, la documentación de la función OrderSend() dice:

Valor de devolución

Si la revisión de una solicitud con la función OrderSend() devuelva "true", esto no es un símbolo de ejecución exitosa de una operación de trading. Para una descripción más detallada del resultado de ejecución de funciones, analice los campos de la estructura MqlTradeResult.


Actualización del Historial de Trading en el Terminal de Cliente

Los mensajes sobre los eventos de trading y cambios en el historial de trading vienen a través de canales separados. Al enviar una solicitud de compra usando la función OrderSend(), puede obtener el ticket de la orden, que se crea como resultado de la validación exitosa de la solicitud. Al mismo tiempo, puede que la orden no aparezca en el terminal de cliente, y que fallen los intentos de seleccionarla usando la función OrderSelect()


Todos los mensajes del servidor de trading llegan al terminal de cliente independientemente.

En la figura de arriba puede ver cómo el servidor de trading le comunica el ticket de la orden al programa MQL5, pero el mensaje sobre el evento de trading (la aparición de una nueva orden) no ha llegado todavía. El mensaje sobre el cambio de la lista de órdenes activas no ha llegado todavía.

Puede darse una situación en la que el mensaje sobre la aparición de una nueva orden llegue al programa cuando se realiza una transacción en su base, por tanto la orden ya no está en la lista de órdenes activas y ha pasado al historial. Esta es una situación real, puesto que la velocidad de procesamiento de solicitudes es mucho más rápida que la velocidad actual de entrega de mensajes a través de una red.


Gestión de Eventos de Trading

Todas las operaciones en el servidor y el envío de mensajes sobre eventos de trading se realizan asimétricamente. Solo hay un método seguro para descubrir qué es lo que ha cambiado exactamente en la cuenta de trading. Este método es la memorización del estado e historial de trading, y después la comparación con el nuevo estado.

El algoritmo para rastrear los eventos de trading en los Asesores Expertos es como se muestra a continuación:

  1. declarar los contadores de órdenes, posiciones y transacciones en el panorama global;
  2. determinar la profundidad del historial de trading que se solicitará para la caché del programa MQL5. Cuanto más historial carguemos en la caché, más recursos del terminal y del ordenador consumiremos;
  3. inicializar los contadores de órdenes, posiciones y transacciones en la función OnInit();
  4. determinar el controlador de funciones en las que se pedirá el historial de trading de la caché;
  5. Tras cargar el historial de trading, veremos también que le pasó a la cuenta de trading, comparando el estado memorizado con el actual.

Este es el algoritmo más sencillo, y permite descubrir si el número de posiciones abiertas (órdenes, transacciones) ha cambiado, y cuál fue la dirección del cambio. Si hay cambios, entonces podemos obtener más información detallada. Si el número de órdenes no ha cambiado pero las órdenes mismas han sido modificadas, necesita un enfoque diferente - una variante que no se trata en este artículo.

Los cambios del contador se pueden ver en las funciones OnTrade() y OnTick() de un Asesor Experto. 

Escribamos un programa de ejemplo paso a paso.

1. El contador de órdenes, transacciones y posiciones en el panorama global.

int          orders;            // number of active orders
int          positions;         // number of open positions
int          deals;             // number of deals in the trade history cache
int          history_orders;    // number of orders in the trade history cache
bool         started=false;     // flag of initialization of the counters


2. La profundidad del historial de trading a cargar en la caché se configura en la variable de entrada days ("días", que carga el historial de trading por el número de días especificado en esta variable).

input    int days=7;            // depth of the trade history in days

//--- set the limit of the trade history on the global scope
datetime     start;             // start date of the trade history in cache
datetime     end;               // end date of the trade history in cache


3. Inicialización de los contadores y los límites del historial de trading. Exponga la inicialización de los contadores con la función InitCounters() para una mejor legibilidad del código:

int OnInit()
  {
//---
   end=TimeCurrent();
   start=end-days*PeriodSeconds(PERIOD_D1);
   PrintFormat("Limits of the trade history to be loaded: start - %s, end - %s",
               TimeToString(start),TimeToString(end));
   InitCounters();
//---
   return(0);
  }

La función InitCounters() trata de cargar el historial de trading en la caché, y en caso de éxito, inicializa todos los contadores. Asimismo, si el historial se carga con éxito, el valor de la variable global "started" será "true", lo que indica que los contadores se han inicializado correctamente.

//+------------------------------------------------------------------+
//|  initialization of the counters of positions, orders and deals   |
//+------------------------------------------------------------------+
void InitCounters()
  {
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %s to %s to the cache. Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }
//--- get current values
   orders=OrdersTotal();
   positions=PositionsTotal();
   deals=HistoryDealsTotal();
   history_orders=HistoryOrdersTotal();
   started=true;
   Print("The counters of orders, positions and deals are successfully initialized");
  }


4. La revisión de los cambios en la cuenta de trading se ejecuta en los controladores OnTick() y OnTrade(). La variable "started" se revisa primero: si su valor es "true", se llama a la función SimpleTradeProcessor(). De lo contrario, se llama a la función de inicialización de los contadores InitCounters().

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }
//+------------------------------------------------------------------+
//| called when the Trade event occurs                               |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }

5. La función SimpleTradeProcessor() comprueba si el número de órdenes, transacciones y posiciones ha cambiado. Tras realizar todas las revisiones, llamamos la función CheckStartDateInTradeHistory(), que mueve el valor "start" más cerca al momento presente si es necesario.

//+------------------------------------------------------------------+
//| simple example of processing changes in trade and history        |
//+------------------------------------------------------------------+
void SimpleTradeProcessor()
  {
   end=TimeCurrent();
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %s to %s to the cache. Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }

//--- get current values
   int curr_orders=OrdersTotal();
   int curr_positions=PositionsTotal();
   int curr_deals=HistoryDealsTotal();
   int curr_history_orders=HistoryOrdersTotal();

//--- check whether the number of active orders has been changed
   if(curr_orders!=orders)
     {
      //--- number of active orders is changed
      PrintFormat("Number of orders has been changed. Previous number is %d, current number is %d",
                  orders,curr_orders);
     /*
       other actions connected with changes of orders
     */
      //--- update value
      orders=curr_orders;
     }

//--- change in the number of open positions
   if(curr_positions!=positions)
     {
      //--- number of open positions has been changed
      PrintFormat("Number of positions has been changed. Previous number is %d, current number is %d",
                  positions,curr_positions);
      /*
      other actions connected with changes of positions
      */
      //--- update value
      positions=curr_positions;
     }

//--- change in the number of deals in the trade history cache
   if(curr_deals!=deals)
     {
      //--- number of deals in the trade history cache has been changed
      PrintFormat("Number of deals has been changed. Previous number is %d, current number is %d",
                  deals,curr_deals);
      /*
       other actions connected with change of the number of deals
       */
      //--- update value
      deals=curr_deals;
     }

//--- change in the number of history orders in the trade history cache
   if(curr_history_orders!=history_orders)
     {
      //--- the number of history orders in the trade history cache has been changed
      PrintFormat("Number of orders in the history has been changed. Previous number is %d, current number is %d",
                  history_orders,curr_history_orders);
     /*
       other actions connected with change of the number of order in the trade history cache
      */
     //--- update value
     history_orders=curr_history_orders;
     }
//--- check whether it is necessary to change the limits of trade history to be requested in cache
   CheckStartDateInTradeHistory();
  }

La función CheckStartDateInTradeHistory() calcula la fecha de inicio de la solicitud del historial de trading para el momento actual (curr_start) y la compara con la variable "start". Si la diferencia entre ellas es mayor de 1 día, entonces "start" se corrige y los contadores de órdenes del historial y transacciones se actualizan.

//+------------------------------------------------------------------+
//|  Changing start date for the request of trade history            |
//+------------------------------------------------------------------+
void CheckStartDateInTradeHistory()
  {
//--- initial interval, as if we started working right now
   datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1);
//--- make sure that the start limit of the trade history period has not gone 
//--- more than 1 day over intended date
   if(curr_start-start>PeriodSeconds(PERIOD_D1))
     {
      //--- we need to correct the date of start of history loaded in the cache
      start=curr_start;
      PrintFormat("New start limit of the trade history to be loaded: start => %s",
                  TimeToString(start));

      //--- now load the trade history for the corrected interval again
      HistorySelect(start,end);

      //--- correct the counters of deals and orders in the history for further comparison
      history_orders=HistoryOrdersTotal();
      deals=HistoryDealsTotal();
     }
  }

El código completo del Asesor Experto DemoTradeEventProcessing.mq5 está adjunto con este artículo.


Conclusión

Todas las operaciones en la plataforma de trading online Meta Trader 5 se ejecutan de forma asíncrona, y los mensajes sobre todos los cambios en una cuenta de trading se envían de forma independiente unos de otros. Por tanto, no tiene sentido tratar de rastrear eventos independientes basándonos en la regla "una solicitud - un evento de trading". Si necesita determinar de forma precisa qué ha cambiado exactamente cuando ocurre un evento de trading, debe analizar todas sus transacciones, posiciones y órdenes en cada llamada del controlador OnTrade(), comparando su estado actual con el anterior.