Evento OnTradeTransaction

Los Asesores Expertos y los indicadores pueden recibir notificaciones sobre eventos de trading si su código contiene una función de procesamiento especial OnTradeTransaction.

void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request, const MqlTradeResult &result)

El primer parámetro es la estructura MqlTradeTransaction descrita en la sección anterior. El segundo y tercer parámetro son estructuras MqlTradeRequest y MqlTradeResult, que se han presentado anteriormente en las secciones correspondientes.

La estructura MqlTradeTransaction que describe la transacción de trading se rellena de forma diferente en función del tipo de transacción especificado en el campo type. Por ejemplo, para las transacciones del tipo TRADE_TRANSACTION_REQUEST, todos los demás campos no son importantes, y para obtener información adicional, es necesario analizar el segundo y tercer parámetro de la función (request y result). Por el contrario, para todos los demás tipos de transacciones, los dos últimos parámetros de la función deben ignorarse.

En el caso de TRADE_TRANSACTION_REQUEST, el campo request_id de la variable result contiene un identificador (a través del número de serie), con el que la operación request queda registrada en el terminal. Este número no tiene nada que ver con los tickets de orden y transacción, ni con los identificadores de posición. En cada sesión con el terminal, la numeración empieza por el principio (1). La presencia de un identificador de solicitud permite asociar la acción realizada (llamada a las funciones OrderSend o OrderSendAsync) con el resultado de esta acción pasado a OnTradeTransaction. Veremos ejemplos más adelante.

Para las operaciones de trading relacionadas con órdenes activas (TRADE_TRANSACTION_ORDER_ADD, TRADE_TRANSACTION_ORDER_UPDATE y TRADE_TRANSACTION_ORDER_DELETE) y el historial de órdenes (TRADE_TRANSACTION_HISTORY_ADD, TRADE_TRANSACTION_HISTORY_UPDATE, TRADE_TRANSACTION_HISTORY_DELETE), se rellenan los siguientes campos en la estructura MqlTradeTransaction:

  • orden: ticket de la orden
  • symbol: nombre del instrumento financiero de la orden
  • type: tipo de operación de trading
  • order_type: tipo de orden
  • orders_state: estado actual de la orden
  • time_type: tipo de vencimiento de la orden
  • time_expiration: hora de vencimiento de la orden (para órdenes con tipos de vencimiento ORDER_TIME_SPECIFIED y ORDER_TIME_SPECIFIED_DAY)
  • price: precio de la orden especificada por el cliente/programa
  • price_trigger: precio stop para activar una orden Stop Limit (sólo para ORDER_TYPE_BUY_STOP_LIMIT y ORDER_TYPE_SELL_STOP_LIMIT)
  • price_sl: Stop Loss precio de la orden (cumplimentado si se especifica en la orden)
  • price_tp: Take Profit precio de la orden (cumplimentado si se especifica en la orden)
  • volume: volumen actual de la orden (no ejecutada), el volumen inicial de la orden se puede encontrar en el historial de órdenes
  • position: ticket de una posición abierta, modificada o cerrada
  • position_by: ticket de posición opuesta (sólo para órdenes que se van a cerrar con posición opuesta)

Para operaciones de trading relacionadas con transacciones (TRADE_TRANSACTION_DEAL_ADD, TRADE_TRANSACTION_DEAL_UPDATE y TRADE_TRANSACTION_DEAL_DELETE), se rellenan los siguientes campos en la estructura MqlTradeTransaction:

  • deal: ticket de transacción
  • order: ticket de orden en base al cual se realizó la transacción
  • symbol: nombre del instrumento financiero de la transacción
  • type: tipo de operación de trading
  • deal_type: tipo de transacción
  • precio: precio de transacción
  • price_sl: Stop Loss precio (rellenado si se especifica en la orden en base a la cual se realizó la transacción)
  • price_tp: Take Profit precio (rellenado si se especifica en la orden en base a la cual se realizó la transacción)
  • volume: volumen de transacción
  • position: ticket de una posición abierta, modificada o cerrada
  • position_by: ticket de posición opuesta (para cerrar transacciones con posición opuesta)

Para las operaciones de trading relacionadas con cambios de posición (TRADE_TRANSACTION_POSITION), se rellenan los siguientes campos en la estructura MqlTradeTransaction:

  • symbol: nombre del instrumento financiero de la posición
  • type: tipo de operación de trading
  • deal_type: tipo de posición (DEAL_TYPE_BUY o DEAL_TYPE_SELL)
  • price: precio medio ponderado de apertura de la posición
  • price_sl: precio Stop Loss
  • price_tp: precio Take Profit
  • volume: volumen de posiciones en lotes
  • position: ticket de posición

No toda la información disponible sobre órdenes, transacciones y posiciones (por ejemplo, un comentario) se transmite en la descripción de una operación de trading. Para obtener más información, utilice las funciones correspondientes: OrderGet, HistoryOrderGet, HistoryDealGet y PositionGet.

Una solicitud de trading enviada desde el terminal manualmente o a través de las funciones de trading OrderSend/OrderSendAsync puede generar varias transacciones de trading consecutivas en el servidor de trading. Al mismo tiempo, el orden en que las notificaciones sobre estas operaciones llegan al terminal no está garantizado, por lo que no puede construir su algoritmo de trading esperando unas operaciones después de otras.

Los eventos de trading se procesan de forma asíncrona, es decir, con retraso (en el tiempo) respecto al momento de generación. Cada evento de trading se envía a la cola del programa MQL, y el programa los recoge secuencialmente en el orden de la cola.

Cuando un Asesor Experto está procesando transacciones de trading dentro del procesador OnTradeTransaction, el terminal continúa aceptando transacciones de trading entrantes. Por lo tanto, el estado de la cuenta de trading puede cambiar mientras OnTradeTransaction está en funcionamiento. En el futuro, el programa será notificado de todos estos eventos en el orden en que aparezcan.

La longitud de la cola de transacciones es de 1024 elementos. Si OnTradeTransaction procesa la siguiente transacción durante demasiado tiempo, las transacciones antiguas de la cola pueden ser desbancadas por las más recientes.

Debido al funcionamiento paralelo multihilo del terminal con objetos de trading, en el momento en que se llama al manejador OnTradeTransaction, todas las entidades mencionadas en él, incluyendo órdenes, transacciones y posiciones, pueden estar ya en un estado diferente al especificado en las propiedades de la transacción. Para obtener su estado actual, debe seleccionarlos en el entorno actual o en el historial y solicitar sus propiedades utilizando las funciones MQL5 adecuadas.

Comencemos con un ejemplo sencillo de Asesor Experto TradeTransactions.mq5, que registra todos los eventos de trading de OnTradeTransaction. Su único parámetro DetailedLog permite utilizar opcionalmente las clases OrderMonitor, DealMonitor, PositionMonitor para mostrar todas las propiedades. Por defecto, el Asesor Experto muestra sólo el contenido de los campos rellenados de las estructuras MqlTradeTransaction, MqlTradeRequest y MqlTradeResult, que llegan al manejador en forma de parámetros; al mismo tiempo, request y result se procesan sólo para las transacciones TRADE_TRANSACTION_REQUEST.

input bool DetailedLog = false// DetailedLog ('true' shows order/deal/position details)
   
void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   static ulong count = 0;
   PrintFormat(">>>% 6d", ++count);
   Print(TU::StringOf(transaction));
   
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      Print(TU::StringOf(request));
      Print(TU::StringOf(result));
   }
   
   if(DetailedLog)
   {
      if(transaction.order != 0)
      {
         OrderMonitor m(transaction.order);
         m.print();
      }
      if(transaction.deal != 0)
      {
         DealMonitor m(transaction.deal);
         m.print();
      }
      if(transaction.position != 0)
      {
         PositionMonitor m(transaction.position);
         m.print();
      }
   }
}

Vamos a ejecutarlo en el gráfico EURUSD y a realizar varias acciones manualmente, y las entradas correspondientes aparecerán en el registro (por la pureza del experimento, se supone que nadie ni nada más realiza operaciones en la cuenta de trading; en concreto, que no hay otros Asesores Expertos en ejecución).

Vamos a abrir una posición larga con un lote mínimo.

>>> 1

TRADE_TRANSACTION_ORDER_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10947, V=0.01

>>> 2

TRADE_TRANSACTION_DEAL_ADD, D=1279627746(DEAL_TYPE_BUY), »

» #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296991463

>>> 3

TRADE_TRANSACTION_ORDER_DELETE, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10947, P=1296991463

>>> 4

TRADE_TRANSACTION_HISTORY_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10947, P=1296991463

>>> 5

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10947, #=1296991463

DONE, D=1279627746, #=1296991463, V=0.01, @ 1.10947, Bid=1.10947, Ask=1.10947, Req=7

Venderemos el doble del lote mínimo.

>>> 6

TRADE_TRANSACTION_ORDER_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10964, V=0.02

>>> 7

TRADE_TRANSACTION_DEAL_ADD, D=1279628463(DEAL_TYPE_SELL), »

» #=1296992157(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.02, P=1296992157

>>> 8

TRADE_TRANSACTION_ORDER_DELETE, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 9

TRADE_TRANSACTION_HISTORY_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 10

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.02, ORDER_FILLING_FOK, @ 1.10964, #=1296992157

DONE, D=1279628463, #=1296992157, V=0.02, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=8

Vamos a realizar la operación de cierre del contador.

>>> 11

TRADE_TRANSACTION_ORDER_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10964, V=0.01, P=1296991463, b=1296992157

>>> 12

TRADE_TRANSACTION_DEAL_ADD, D=1279628878(DEAL_TYPE_SELL), »

» #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296991463

>>> 13

TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10947, P=1296991463

>>> 14

TRADE_TRANSACTION_DEAL_ADD, D=1279628879(DEAL_TYPE_BUY), »

» #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296992157

>>> 15

TRADE_TRANSACTION_ORDER_DELETE, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296991463, b=1296992157

>>> 16

TRADE_TRANSACTION_HISTORY_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296991463, b=1296992157

>>> 17

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_CLOSE_BY, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, #=1296992548, »

» P=1296991463, b=1296992157

DONE, D=1279628878, #=1296992548, V=0.01, @ 1.10964, Bid=1.10961, Ask=1.10965, Req=9

Seguimos teniendo una posición corta del lote mínimo. Vamos a cerrarla.

>>> 18

TRADE_TRANSACTION_ORDER_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10964, V=0.01, P=1296992157

>>> 19

TRADE_TRANSACTION_ORDER_DELETE, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 20

TRADE_TRANSACTION_HISTORY_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 21

TRADE_TRANSACTION_DEAL_ADD, D=1279639132(DEAL_TYPE_BUY), »

» #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296992157

>>> 22

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10964, #=1297002683, »

» P=1296992157

DONE, D=1279639132, #=1297002683, V=0.01, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=10

Si lo desea, puede activar la opción DetailedLog para registrar todas las propiedades de los objetos de trading en el momento de procesar el evento. En un registro detallado se pueden observar discrepancias entre el estado de los objetos almacenados en la estructura de la transacción (en el momento de su inicio) y el estado actual. Por ejemplo, al añadir una orden para cerrar una posición (opuesta o normal), se especifica un ticket en la transacción, según el cual el objeto monitor ya no podrá leer nada, puesto que la posición se ha borrado. Como resultado, veremos líneas como ésta en el registro:

TRADE_TRANSACTION_ORDER_ADD, #=1297777749(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10953, V=0.01, P=1297774881, b=1297776850

...

Error: PositionSelectByTicket(1297774881) failed: TRADE_POSITION_NOT_FOUND

Vamos a reiniciar el Asesor Experto TradeTransaction.mq5 para restablecer los eventos registrados para la próxima prueba. Esta vez utilizaremos la configuración por defecto (sin detalles).

Ahora vamos a intentar realizar acciones de trading programáticamente en el nuevo Asesor Experto OrderSendTransaction1.mq5, y al mismo tiempo describir nuestro manejador OnTradeTransaction en él (igual que en el ejemplo anterior).

Este Asesor Experto le permite seleccionar el volumen y la dirección de la operación: si lo deja a cero, se utiliza por defecto el lote mínimo del símbolo actual. También en los parámetros hay una distancia a los niveles de protección en puntos. Se entra en el mercado con los parámetros especificados, hay una pausa de 5 segundos entre el ajuste de Stop Loss y Take Profit, y a continuación se cierra la posición, de manera que el usuario pueda intervenir (por ejemplo, editar Stop Loss manualmente), aunque esto no es necesario, puesto que ya nos hemos asegurado de que las operaciones manuales sean interceptadas por el programa.

enum ENUM_ORDER_TYPE_MARKET
{
   MARKET_BUY = ORDER_TYPE_BUY,    // ORDER_TYPE_BUY
   MARKET_SELL = ORDER_TYPE_SELL   // ORDER_TYPE_SELL
};
   
input ENUM_ORDER_TYPE_MARKET Type;
input double Volume;               // Volume (0 - minimal lot)
input uint Distance2SLTP = 1000;

La estrategia se lanza una vez, para lo cual se utiliza un temporizador de 1 segundo, que se desactiva en su propio manejador.

int OnInit()
{
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}
   
void OnTimer()
{
   EventKillTimer();
   ...

Todas las acciones se realizan a través de una estructura MqlTradeRequestSync ya familiar con características avanzadas (MqlTradeSync.mqh): inicialización implícita de campos con valores correctos, métodos buy/sell para órdenes de mercado, adjust para niveles de protección y close para cerrar la posición.

Paso 1:

   MqlTradeRequestSync request;
   
   const double volume = Volume == 0 ?
      SymbolInfoDouble(_SymbolSYMBOL_VOLUME_MIN) : Volume;
   
   Print("Start trade");
   const ulong order = (Type == MARKET_BUY ? request.buy(volume) : request.sell(volume));
   if(order == 0 || !request.completed())
   {
      Print("Failed Open");
      return;
   }
   
   Print("OK Open");

Paso 2:

   Sleep(5000); // wait 5 seconds (user can edit position)
   Print("SL/TP modification");
   const double price = PositionGetDouble(POSITION_PRICE_OPEN);
   const double point = SymbolInfoDouble(_SymbolSYMBOL_POINT);
   TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
   const double SL = dir.negative(priceDistance2SLTP * point);
   const double TP = dir.positive(priceDistance2SLTP * point);
   if(request.adjust(SLTP) && request.completed())
   {
      Print("OK Adjust");
   }
   else
   {
      Print("Failed Adjust");
   }

Paso 3:

   Sleep(5000); // wait another 5 seconds
   Print("Close down");
   if(request.close(request.result.position) && request.completed())
   {
      Print("Finish");
   }
   else
   {
      Print("Failed Close");
   }
}

Las esperas intermedias no sólo permiten tener tiempo para considerar el proceso, sino que también demuestran un aspecto importante de la programación MQL5, que es el monohilo. Mientras nuestro Asesor Experto de trading se encuentra dentro de OnTimer, los eventos de trading generados por el terminal se acumulan en su cola y serán reenviados al manejador interno de OnTradeTransaction en un estilo diferido, sólo después de la salida de OnTimer.

Al mismo tiempo, el Asesor Experto de TradeTransactions que se ejecuta en paralelo no está ocupado con ningún cálculo y recibirá los eventos de trading lo más rápido posible.

El resultado de la ejecución de dos Asesores Expertos se presenta en el siguiente registro con temporización (por brevedad, OrderSendTransaction1 se etiqueta como OS1, y Trade Transactions como TTs).

19:09:08.078 OS1 Start trade

19:09:08.109 TTs >>> 1

19:09:08.125 TTs TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »

EURUSD, @ 1.10913, V=0.01

19:09:08.125 TTs >>> 2

19:09:08.125 TTs TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), »

#=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10913, V=0.01, »

P=1298021794

19:09:08.125 TTs >>> 3

19:09:08.125 TTs TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:08.125 TTs >>> 4

19:09:08.125 TTs TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:08.125 TTs >>> 5

19:09:08.125 TTs TRADE_TRANSACTION_REQUEST

19:09:08.125 TTs TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, »

D=10, #=1298021794, M=1234567890

19:09:08.125 TTs DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, »

Req=9

19:09:08.125 OS1 Waiting for position for deal D=1280661362

19:09:08.125 OS1 OK Open

19:09:13.133 OS1 SL/TP modification

19:09:13.164 TTs >>> 6

19:09:13.164 TTs TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, »

P=1298021794

19:09:13.164 OS1 OK Adjust

19:09:13.164 TTs >>> 7

19:09:13.164 TTs TRADE_TRANSACTION_REQUEST

19:09:13.164 TTs TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09913, »

TP=1.11913, D=10, P=1298021794, M=1234567890

19:09:13.164 TTs DONE, Req=10

19:09:18.171 OS1 Close down

19:09:18.187 OS1 Finish

19:09:18.218 TTs >>> 8

19:09:18.218 TTs TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), »

EURUSD, @ 1.10901, V=0.01, P=1298021794

19:09:18.218 TTs >>> 9

19:09:18.218 TTs TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), »

#=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, »

SL=1.09913, TP=1.11913, V=0.01, P=1298021794

19:09:18.218 TTs >>> 10

19:09:18.218 TTs TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 TTs >>> 11

19:09:18.218 TTs TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 TTs >>> 12

19:09:18.218 TTs TRADE_TRANSACTION_REQUEST

19:09:18.218 TTs TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, »

D=10, #=1298022443, P=1298021794, M=1234567890

19:09:18.218 TTs DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, »

Req=11

19:09:18.218 OS1 >>> 1

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »

EURUSD, @ 1.10913, V=0.01

19:09:18.218 OS1 >>> 2

19:09:18.218 OS1 TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), »

#=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

@ 1.10913, V=0.01, P=1298021794

19:09:18.218 OS1 >>> 3

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:18.218 OS1 >>> 4

19:09:18.218 OS1 TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:18.218 OS1 >>> 5

19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST

19:09:18.218 OS1 TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, »

D=10, #=1298021794, M=1234567890

19:09:18.218 OS1 DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, »

Req=9

19:09:18.218 OS1 >>> 6

19:09:18.218 OS1 TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, »

P=1298021794

19:09:18.218 OS1 >>> 7

19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST

19:09:18.218 OS1 TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, »

SL=1.09913, TP=1.11913, D=10, P=1298021794, M=1234567890

19:09:18.218 OS1 DONE, Req=10

19:09:18.218 OS1 >>> 8

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), »

EURUSD, @ 1.10901, V=0.01, P=1298021794

19:09:18.218 OS1 >>> 9

19:09:18.218 OS1 TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), »

#=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, »

SL=1.09913, TP=1.11913, V=0.01, P=1298021794

19:09:18.218 OS1 >>> 10

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 OS1 >>> 11

19:09:18.218 OS1 TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 OS1 >>> 12

19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST

19:09:18.218 OS1 TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, »

D=10, #=1298022443, P=1298021794, M=1234567890

19:09:18.218 OS1 DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, »

Req=11

La numeración de los eventos en los programas es la misma (siempre que se inicien limpiamente, según lo recomendado). Observe que el mismo evento se imprime primero desde TTs inmediatamente después de que se ejecute la solicitud, y la segunda vez sólo al final de la prueba, donde, de hecho, todos los eventos se emiten desde la cola a OS1.

Si eliminamos los retrasos artificiales, el script, por supuesto, se ejecutará más rápido, pero aún así el manejador OnTradeTransaction recibirá notificaciones (múltiples veces) después de los tres pasos, no después de cada solicitud respectiva. ¿Hasta qué punto esto es crítico?

Ahora los ejemplos utilizan nuestra modificación de la estructura MqlTradeRequestSync, utilizando a propósito la opción sincrónica OrderSend, que también implementa un método universal completed que comprueba si la solicitud se ha completado con éxito. Con este control, podemos establecer niveles de protección para una posición, ya que sabemos cómo esperar a que aparezca su ticket. En el marco de este concepto síncrono (adoptado por comodidad), no necesitamos analizar los resultados de las consultas en OnTradeTransaction, pero esto no siempre es así.

Cuando un Asesor Experto necesita enviar muchas solicitudes a la vez, como en el caso del ejemplo con el establecimiento de una cuadrícula de órdenes PendingOrderGrid2.mq5 abordado en la sección sobre propiedades de posiciones, esperar a que cada posición u orden esté «lista» puede reducir el rendimiento global del Asesor Experto. En tales casos se recomienda utilizar la función OrderSendAsync. Pero si tiene éxito, sólo rellena el campo request_id en MqlTradeResult, con el que luego hay que hacer un seguimiento de la aparición de órdenes, transacciones y posiciones en OnTradeTransaction.

Uno de los trucos más obvios pero no especialmente elegantes para implementar este esquema es almacenar los identificadores de las solicitudes o estructuras enteras de las solicitudes que se envían en un array, en el contexto global. Estos identificadores pueden buscarse seguidamente en las transacciones entrantes en OnTradeTransaction, los tickets pueden encontrarse en el parámetro MqlTradeResult y pueden adoptarse además otras medidas. Como resultado, la lógica de trading se separa en diferentes funciones. Por ejemplo, en el contexto del último Asesor Experto OrderSendTransaction1.mq5, esta "diversificación" radica en el hecho de que después de enviar la primera orden, los fragmentos de código deben ser transferidos a OnTradeTransaction y comprobados para lo siguiente:

  • tipo de transacción en MqlTradeTransaction (transaction type);
  • tipo de solicitud en MqlTradeRequest (request action);
  • id de solicitud en MqlTradeResult (result.request_id);

Todo ello debe completarse con una lógica aplicada específica (por ejemplo, la comprobación de la existencia de una posición), que proporciona la ramificación por estados de la estrategia de trading. Un poco más adelante haremos una modificación similar del Asesor Experto OrderSendTransaction bajo un número diferente para mostrar visualmente la cantidad de código fuente adicional. Y luego ofreceremos una forma de organizar el programa de forma más lineal, pero sin abandonar los eventos transaccionales.

Por ahora señalamos únicamente que el desarrollador debe elegir si construye un algoritmo en torno a OnTradeTransaction o sin él. En muchos casos, cuando no es necesario el envío masivo de órdenes, es posible permanecer en el paradigma de programación síncrona. Sin embargo, OnTradeTransaction es la forma más práctica de controlar la activación de órdenes pendientes y niveles de protección, así como otros eventos generados por el servidor. Tras una pequeña preparación presentaremos dos ejemplos relevantes: la modificación final del Asesor Experto de cuadrícula y la implementación de la popular configuración de dos órdenes OCO (One Cancels Other) (véase la sección On Trade).

Una alternativa a la aplicación de OnTradeTransaction consiste en el análisis periódico del entorno de trading, es decir, en recordar el número de órdenes y posiciones y buscar cambios entre ellas. Este enfoque es adecuado para estrategias basadas en calendarios o que permiten ciertos retrasos.

Insistimos una vez más en que el uso de OnTradeTransaction no significa que el programa deba pasar necesariamente de OrderSend a OrderSendAsync: Puede utilizar cualquiera de las dos variedades o ambas. Recordemos que la función OrderSend tampoco es del todo síncrona, ya que devuelve, en el mejor de los casos, el ticket de la orden y la transacción, pero no la posición. Pronto podremos medir el tiempo de ejecución de un lote de órdenes dentro de la misma estrategia de cuadrícula utilizando ambas variantes de la función: OrderSend y OrderSendAsync.

Para unificar el desarrollo de programas síncronos y asíncronos sería genial admitir OrderSendAsync en nuestra estructura MqlTradeRequestSync (a pesar de su nombre), lo cual puede hacerse con sólo un par de correcciones. En primer lugar, debe sustituir todas las llamadas OrderSend existentes por su propio método orderSend, y en él cambiar la llamada a OrderSend o OrderSendAsync en función de una bandera.

struct MqlTradeRequestSyncpublic MqlTradeRequest
{
   ...
   static bool AsyncEnabled;
   ...
private:
   bool orderSend(const MqlTradeRequest &reqMqlTradeResult &res)
   {
      return AsyncEnabled ? ::OrderSendAsync(reqres) : ::OrderSend(reqres);
   }
};

Estableciendo la variable pública AsyncEnabled en true o false, puede cambiar de un modo a otro; por ejemplo, en el fragmento de código en el que se envían las órdenes en masa.

En segundo lugar, aquellos métodos de la estructura que devuelvan un ticket (por ejemplo, para entrar en el mercado) deberán devolver el campo request_id en lugar de order. Por ejemplo, dentro de los métodos _pending y _market teníamos el siguiente operador:

if(OrderSend(thisresult)) return result.order;

Ahora se sustituye por:

if(orderSend(thisresult)) return result.order ? result.order :
   (result.retcode == TRADE_RETCODE_PLACED ? result.request_id : 0);

Por supuesto, cuando el modo asíncrono está activado, ya no podemos utilizar el método completed para esperar a que los resultados de la consulta estén listos inmediatamente después de su envío. Pero este método es, básicamente, opcional: puede abandonarlo incluso cuando trabaje a través de OrderSend.

Así pues, teniendo en cuenta la nueva modificación del archivo MqlTradeSync.mqh, vamos a crear OrderSendTransaction2.mq5.

Este Asesor Experto enviará la solicitud inicial como antes desde OnTimer, mientras establece niveles de protección y cierra una posición en OnTradeTransaction paso a paso. Aunque esta vez no tendremos un retraso artificial entre las etapas, la secuencia de estados en sí es estándar para muchos Asesores Expertos: se abrió una posición, se modificó, se cerró (si se cumplen ciertas condiciones de mercado, que aquí se dejan entre bastidores).

Dos variables globales le permitirán realizar un seguimiento del estado: RequestID con el id de la última solicitud enviada (cuyo resultado esperamos) y Position Ticket con un ticket de posición abierta. Cuando allí la posición no ha aparecido todavía, o ya no existe, la entrada es igual a 0.

uint RequestID = 0;
ulong PositionTicket = 0;

El modo asíncrono está activado en el manejador OnInit.

int OnInit()
{
   ...
   MqlTradeRequestSync::AsyncEnabled = true;
   ...
}

La función OnTimer es ahora mucho más corta.

void OnTimer()
{
   ...
   // send a request TRADE_ACTION_DEAL (asynchronously!)
   const ulong order = (Type == MARKET_BUY ? request.buy(volume) : request.sell(volume));
   if(order// in asynchronous mode this is now request_id
   {
      Print("OK Open?");
      RequestID = request.result.request_id// same as order
   }
   else
   {
      Print("Failed Open");
   }
}

Una vez completada con éxito la solicitud, sólo obtenemos request_id y lo almacenamos en la variable RequestID. La impresión de estado contiene ahora un signo de interrogación, como «¿OK Open?», porque aún no se conoce el resultado real.

OnTradeTransaction se complicó considerablemente debido a la verificación de los resultados y la ejecución de las órdenes de trading posteriores según las condiciones. Vamos a verlo poco a poco.

En este caso, toda la lógica de trading se ha trasladado a la rama de operaciones de tipo TRADE_TRANSACTION_REQUEST. Por supuesto, el desarrollador puede utilizar otros tipos si lo desea, pero utilizamos este porque contiene información en forma de una estructura MqlTradeResult familiar, es decir, este tipo representa una finalización retrasada de una llamada asíncrona OrderSendAsync.

void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   static ulong count = 0;
   PrintFormat(">>>% 6d", ++count);
   Print(TU::StringOf(transaction));
   
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      Print(TU::StringOf(request));
      Print(TU::StringOf(result));
      
      ...
      // here is the whole algorithm
   }
}

Sólo deberían interesarnos las solicitudes con el ID que esperamos. Así que la siguiente sentencia será if anidada. En su bloque, describimos el objeto MqlTradeRequestSync con antelación, ya que será necesario enviar solicitudes de trading periódicas de acuerdo con el plan.

      if(result.request_id == RequestID)
      {
         MqlTradeRequestSync next;
         next.magic = Magic;
         next.deviation = Deviation;
         ...
      }

Sólo tenemos dos tipos de solicitud en funcionamiento, así que añadimos para ellos una if anidada más.

         if(request.action == TRADE_ACTION_DEAL)
         {
            ... // here is the reaction to opening and closing a position
         }
         else if(request.action == TRADE_ACTION_SLTP)
         {
            ... // here is the reaction to setting SLTP for an open position
         }

Hay que tener en cuenta que TRADE_ACTION_DEAL se utiliza tanto para abrir como para cerrar una posición, por lo que se requiere un if más, en el que distinguiremos entre estos dos estados dependiendo del valor de la variable PositionTicket.

            if(PositionTicket == 0)
            {
               ... // there is no position, so this is an opening notification 
            }
            else
            {
               ... // there is a position, so this is a closure
            }

En la estrategia de trading analizada no hay incrementos de posición (para la compensación) ni posiciones múltiples (para la cobertura), por lo que esta parte es sencilla desde el punto de vista lógico. Los Asesores Expertos reales requerirán estimaciones mucho más diferentes de los estados intermedios.

En el caso de una notificación de apertura de posición, el bloque de código tiene el siguiente aspecto:

            if(PositionTicket == 0)
            {
               // trying to get results from the transaction: select an order by ticket
               if(!HistoryOrderSelect(result.order))
               {
                  Print("Can't select order in history");
                  RequestID = 0;
                  return;
               }
               // get position ID and ticket
               const ulong posid = HistoryOrderGetInteger(result.orderORDER_POSITION_ID);
               PositionTicket = TU::PositionSelectById(posid);
               ...

Para simplificar, hemos omitido aquí la comprobación de errores y recotizaciones. Puede ver un ejemplo de su manejo en el código fuente adjunto. Recordemos que todas estas comprobaciones ya se han implementado en los métodos de la estructura MqlTradeRequestSync, pero sólo funcionan en modo síncrono, por lo que tenemos que repetirlas explícitamente.

El siguiente fragmento de código para establecer los niveles de protección no ha cambiado mucho.

            if(PositionTicket == 0)
            {
               ...
               const double price = PositionGetDouble(POSITION_PRICE_OPEN);
               const double point = SymbolInfoDouble(_SymbolSYMBOL_POINT);
               TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
               const double SL = dir.negative(priceDistance2SLTP * point);
               const double TP = dir.positive(priceDistance2SLTP * point);
               // sending TRADE_ACTION_SLTP request (asynchronously!)
               if(next.adjust(PositionTicketSLTP))
               {
                  Print("OK Adjust?");
                  RequestID = next.result.request_id;
               }
               else
               {
                  Print("Failed Adjust");
                  RequestID = 0;
               }
            }

La única diferencia aquí es: rellenamos la variable RequestID con el ID de la nueva solicitud TRADE_ACTION_SLTP.

Recibir una notificación sobre una transacción con un PositionTicket distinto de cero implica que la posición se ha cerrado.

            if(PositionTicket == 0)
            {
               ... // see above
            }
            else
            {
               if(!PositionSelectByTicket(PositionTicket))
               {
                  Print("Finish");
                  RequestID = 0;
                  PositionTicket = 0;
               }
            }

En caso de borrado con éxito, la posición no puede seleccionarse utilizando PositionSelectByTicket, por lo que reiniciamos RequestID y PositionTicket. A continuación, el Asesor Experto vuelve a su estado inicial y está listo para realizar el siguiente ciclo de compra/venta-modificación-cierre.

Nos queda considerar el envío de una solicitud de cierre de la posición. En nuestra estrategia simplificada al mínimo, esto ocurre inmediatamente después de modificar con éxito los niveles de protección.

         if(request.action == TRADE_ACTION_DEAL)
         {
            ... // see above
         }
         else if(request.action == TRADE_ACTION_SLTP)
         {
            // send a TRADE_ACTION_DEAL request to close (asynchronously!)
            if(next.close(PositionTicket))
            {
               Print("OK Close?");
               RequestID = next.result.request_id;
            }
            else
            {
               PrintFormat("Failed Close %lld"PositionTicket);
            }
         }

Esa es toda la función OnTradeTransaction. El Asesor Experto está listo.

Vamos a ejecutar OrderSendTransaction2.mq5 con la configuración por defecto. He aquí un registro de ejemplo:

Start trade

OK Open?

>>> 1

TRADE_TRANSACTION_ORDER_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10640, V=0.01

>>> 2

TRADE_TRANSACTION_DEAL_ADD, D=1282135720(DEAL_TYPE_BUY), »

» #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10640, V=0.01, P=1299508203

>>> 3

TRADE_TRANSACTION_ORDER_DELETE, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10640, P=1299508203

>>> 4

TRADE_TRANSACTION_HISTORY_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10640, P=1299508203

>>> 5

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10640, D=10, »

» #=1299508203, M=1234567890

DONE, D=1282135720, #=1299508203, V=0.01, @ 1.1064, Bid=1.1064, Ask=1.1064, Req=7

OK Adjust?

>>> 6

TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10640, SL=1.09640, TP=1.11640, V=0.01, P=1299508203

>>> 7

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09640, TP=1.11640, »

» D=10, P=1299508203, M=1234567890

DONE, Req=8

OK Close?

>>> 8

TRADE_TRANSACTION_ORDER_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10638, V=0.01, P=1299508203

>>> 9

TRADE_TRANSACTION_ORDER_DELETE, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10638, P=1299508203

>>> 10

TRADE_TRANSACTION_HISTORY_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10638, P=1299508203

>>> 11

TRADE_TRANSACTION_DEAL_ADD, D=1282135730(DEAL_TYPE_SELL), »

» #=1299508215(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10638, »

» SL=1.09640, TP=1.11640, V=0.01, P=1299508203

>>> 12

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10638, D=10, »

» #=1299508215, P=1299508203, M=1234567890

DONE, D=1282135730, #=1299508215, V=0.01, @ 1.10638, Bid=1.10638, Ask=1.10638, Req=9

Finish

La lógica de trading está funcionando como se esperaba, y los eventos de transacción llegan estrictamente después de que se envíe cada orden siguiente. Si ahora ejecutamos nuestro nuevo Asesor Experto y el interceptor de transacciones TradeTransactions.mq5 en paralelo, los mensajes de registro de dos Asesores Expertos aparecerán de forma sincronizada.

Sin embargo, pasar de la primera versión OrderSendTransaction1.mq5 directa a una segunda versión OrderSendTransaction2.mq5 asíncrona requería un código bastante más sofisticado. La pregunta que se plantea es: ¿es posible combinar de algún modo los principios de descripción secuencial de la lógica de trading (transparencia del código) y procesamiento paralelo (velocidad)?

En teoría, esto es posible, pero requerirá en algún momento dedicar tiempo a trabajar en la creación de algún tipo de mecanismo auxiliar.