Solicitudes síncronas y asíncronas

Antes de entrar en detalles, recordemos que cada programa MQL se ejecuta en su propio hilo, y por lo tanto el procesamiento paralelo asíncrono de transacciones (y otros eventos) sólo es posible debido a que otro programa MQL lo estaría haciendo. Al mismo tiempo, es necesario garantizar el intercambio de información entre programas. Ya conocemos un par de formas de hacerlo: variables globales del terminal y archivos. En la Parte 7 del libro exploraremos otras características como recursos gráficos y bases de datos.

De hecho, imagine que un Asesor Experto similar a TradeTransactions.mq5 se ejecuta en paralelo con el Asesor Experto de trading y guarda las transacciones recibidas (no necesariamente todos los campos, sino sólo los selectivos que afectan a la toma de decisiones) en variables globales. A continuación, el Asesor Experto podría comprobar las variables globales inmediatamente después de enviar la siguiente solicitud y leer los resultados de los mismos sin salir de la función actual. Además, no necesita su propio manejador OnTradeTransaction.

Sin embargo, no es fácil organizar la ejecución de un Asesor Experto de terceros. Desde el punto de vista técnico, esto podría hacerse creando un objeto gráfico y aplicando una plantilla con un Asesor Experto de monitor de transacciones predefinido. Pero hay una manera más fácil. La cuestión es que los eventos de OnTradeTransaction se traducen no sólo en Asesor Experto, sino también en indicadores. A su vez, un indicador es el tipo de programa MQL más fácil de lanzar: basta con llamar a iCustom.

Además, el uso del indicador ofrece otra ventaja: puede describir el búfer del indicador disponible desde programas externos a través de CopyBuffer, y disponer en él un ring buffer para almacenar las transacciones procedentes del terminal (resultados de solicitudes). Por lo tanto, no hay necesidad de complicarlo con variables globales.

¡Atención! El evento OnTradeTransaction no se genera para los indicadores en el probador, por lo que sólo puede comprobar el funcionamiento del par Asesor Experto-indicador en línea.

Llamemos a este indicador TradeTransactionRelay.mq5 y describamos un búfer en él. Podría hacerse invisible porque escribirá datos que no pueden ser renderizados, pero lo dejamos visible para probar el concepto.

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
   
double Buffer[];
   
void OnInit()
{
   SetIndexBuffer(0BufferINDICATOR_DATA);
}

El manejador OnCalculate está vacío.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
   return rates_total;
}

En el código, necesitamos un convertidor listo de double a ulong y viceversa, ya que las celdas del búfer pueden corromper valores ulong grandes si se escriben allí utilizando una conversión de tipos simple (véase Números reales).

#include <MQL5Book/ConverterT.mqh>
Converter<ulong,doublecnv;

Aquí está la función OnTradeTransaction.

#defineFIELD_NUM6// the most important fields in MqlTradeResult
   
void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      ArraySetAsSeries(Buffertrue);
      
      // store FIELD_NUM result fields into consecutive buffer cells
      const int offset = (int)((result.request_id * FIELD_NUM)
         % (Bars(_Symbol_Period) / FIELD_NUM * FIELD_NUM));
      Buffer[offset + 1] = result.retcode;
      Buffer[offset + 2] = cnv[result.deal];
      Buffer[offset + 3] = cnv[result.order];
      Buffer[offset + 4] = result.volume;
      Buffer[offset + 5] = result.price;
      // this assignment must come last,
      // because it is the result ready flag
      Buffer[offset + 0] = result.request_id;
   }
}

Hemos decidido conservar únicamente los seis campos más importantes de la estructura MqlTradeResult. Si lo desea, puede ampliar el mecanismo a toda la estructura, pero para transferir el campo de cadena comment necesitará un array de caracteres para la que tendrá que reservar bastantes elementos.

Así, cada resultado ocupa ahora seis celdas de búfer consecutivas. El índice de la primera celda de estas seis se determina en función del ID de la solicitud: este número simplemente se multiplica por 6. Como puede haber muchas solicitudes, la entrada funciona según el principio de un búfer anular, es decir, el índice resultante se normaliza dividiendo con resto ('%') por el tamaño del búfer indicador, que es el número de barras redondeado a 6. Cuando los números de solicitud superen el tamaño, el registro irá en círculo a partir de los elementos iniciales.

Dado que la numeración de las barras se ve afectada por la formación de nuevas barras, se recomienda poner el indicador en marcos temporales grandes, como D1. Entonces, sólo al principio del día es probable (aunque bastante improbable) la situación en que la numeración de las barras en el indicador cambiará directamente durante el procesamiento de la siguiente transacción, y luego los resultados registrados por el indicador no serán leídos por el Asesor Experto (puede que una transacción se pase por alto).

El indicador está listo. Ahora vamos a empezar a aplicar una nueva modificación de la prueba Asesor Experto OrderSendTransaction3.mq5 (¡hurra!, esta es su última versión). Vamos a describir la variable handle para el controlador del indicador y a crear el indicador en OnInit.

int handle = 0;
   
int OnInit()
{
   ...
   const static string indicator = "MQL5Book/p6/TradeTransactionRelay";
   handle = iCustom(_SymbolPERIOD_D1indicator);
   if(handle == INVALID_HANDLE)
   {
      Alert("Can't start indicator "indicator);
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

Para leer los resultados de la consulta desde el búfer del indicador, vamos a preparar una función auxiliar AwaitAsync. Como primer parámetro, recibe una referencia a la estructura MqlTradeRequestSync. Si tiene éxito, los resultados obtenidos del búfer del indicador con handle se escribirán en esta estructura. El identificador de la solicitud que nos interesa ya debería estar en la estructura anidada, en el campo result.request_id. Por supuesto, aquí debemos leer los datos según el mismo principio, es decir, en seis barras.

#define FIELD_NUM   6  // the most important fields in MqlTradeResult
#define TIMEOUT  1000  // 1 second
   
bool AwaitAsync(MqlTradeRequestSync &rconst int _handle)
{
   Converter<ulong,doublecnv;
   const int offset = (int)((r.result.request_id * FIELD_NUM)
      % (Bars(_Symbol_Period) / FIELD_NUM * FIELD_NUM));
   const uint start = GetTickCount();
   // wait for results or timeout
   while(!IsStopped() && GetTickCount() - start < TIMEOUT)
   {
      double array[];
      if((CopyBuffer(_handle0offsetFIELD_NUMarray)) == FIELD_NUM)
      {
         ArraySetAsSeries(arraytrue);
         // when request_id is found, fill other fields with results
         if((uint)MathRound(array[0]) == r.result.request_id)
         {
            r.result.retcode = (uint)MathRound(array[1]);
            r.result.deal = cnv[array[2]];
            r.result.order = cnv[array[3]];
            r.result.volume = array[4];
            r.result.price = array[5];
            PrintFormat("Got Req=%d at %d ms",
               r.result.request_idGetTickCount() - start);
            Print(TU::StringOf(r.result));
            return true;
         }
      }
   }
   Print("Timeout for: ");
   Print(TU::StringOf(r));
   return false;
}

Ahora que tenemos esta función, vamos a escribir un algoritmo de trading en un estilo asíncrono-síncrono: como una secuencia directa de pasos, cada uno de los cuales espera a que el anterior esté listo debido a las notificaciones del programa indicador paralelo mientras permanece dentro de una función.

void OnTimer()
{
   EventKillTimer();
   
   MqlTradeRequestSync::AsyncEnabled = true;
   
   MqlTradeRequestSync request;
   request.magic = Magic;
   request.deviation = Deviation;
   
   const double volume = Volume == 0 ?
      SymbolInfoDouble(_SymbolSYMBOL_VOLUME_MIN) : Volume;
   ...

Paso 1

   Print("Start trade");
   ResetLastError();
   if((bool)(Type == MARKET_BUY ? request.buy(volume) : request.sell(volume)))
   {
      Print("OK Open?");
   }
   
   if(!(AwaitAsync(requesthandle) && request.completed()))
   {
      Print("Failed Open");
      return;
   }
   ...

Paso 2

   Print("SL/TP modification");
   ...
   if(request.adjust(SLTP))
   {
      Print("OK Adjust?");
   }
   
   if(!(AwaitAsync(requesthandle) && request.completed()))
   {
      Print("Failed Adjust");
   }

Paso 3

   Print("Close down");
   if(request.close(request.result.position))
   {
      Print("OK Close?");
   }
   
   if(!(AwaitAsync(requesthandle) && request.completed()))
   {
      Print("Failed Close");
   }
   
   Print("Finish");
}

Tenga en cuenta que las llamadas al método completed ahora no se realizan después de enviar la solicitud, sino después de que la función AwaitAsync reciba el resultado.

Por lo demás, todo es muy similar a la primera versión de este algoritmo, pero ahora se basa en llamadas a funciones asíncronas y reacciona a eventos asíncronos.

Probablemente no parezca significativo en este ejemplo concreto de una cadena de manipulaciones en una única posición. Sin embargo, podemos utilizar la misma técnica para enviar y controlar un lote de órdenes. Y entonces los beneficios serán evidentes. En un momento demostraremos esto con la ayuda de un Asesor Experto de cuadrícula y al mismo tiempo compararemos el rendimiento de dos funciones: OrderSend y OrderSendAsync.

Pero ahora mismo, mientras completamos la serie de Asesores expertos OrderSendTransaction, vamos a ejecutar la última versión y a ver en el registro de la ejecución regular y lineal de todos los pasos.

Start trade

OK Open?

Got Req=1 at 62 ms

DONE, D=1282677007, #=1300045365, V=0.01, @ 1.10564, Bid=1.10564, Ask=1.10564, Order placed, Req=1

Waiting for position for deal D=1282677007

SL/TP modification

OK Adjust?

Got Req=2 at 63 ms

DONE, Order placed, Req=2

Close down

OK Close?

Got Req=3 at 78 ms

DONE, D=1282677008, #=1300045366, V=0.01, @ 1.10564, Bid=1.10564, Ask=1.10564, Order placed, Req=3

Finish

La sincronización con las demoras de la respuesta puede depender en gran medida del servidor, la hora del día y el símbolo. Por supuesto, parte del tiempo aquí no se dedica a una solicitud de operación con confirmación, sino a la ejecución de la función CopyBuffer. Según nuestras observaciones, no tarda más de 16 ms (dentro de un ciclo de un temporizador de sistema estándar, quienes lo deseen pueden perfilar programas que utilicen temporizadores de alta precisión GetMicrosecondCount).

Ignore la diferencia entre el estado (DONE) y la descripción de la cadena («Order placed»). El hecho es que el comentario (así como los campos ask/bid) permanece en la estructura desde el momento en que es enviado por la función OrderSendAsync, y el estado final en el campo retcode es escrito por nuestra función AwaitAsync. Para nosotros es importante que en la estructura con los resultados estén actualizados los números de los tickets (deal y order), el precio de ejercicio (price) y el volumen (volume).

Basándonos en el ejemplo anterior de OrderSendTransaction3.mq5, vamos a crear una nueva versión del Asesor Experto de cuadrícula PendingOrderGrid3.mq5 (la versión anterior se proporciona en la sección Funciones de lectura de propiedades de posición). Podrá establecer una cuadrícula completa de órdenes en modo síncrono o asíncrono, a elección del usuario. También detectaremos los tiempos de ajuste de la cuadrícula completa para comparar.

El modo se controla mediante la variable de entrada EnableAsyncSetup. Se asigna la variable handle para el manejador del indicador.

input bool EnableAsyncSetup = false;
   
int handle;

Durante la inicialización, en el caso del modo asíncrono, creamos una instancia del indicador TradeTransactionRelay.

int OnInit()
{
   ...
   if(EnableAsyncSetup)
   {
      const uint start = GetTickCount();
      const static string indicator = "MQL5Book/p6/TradeTransactionRelay";
      handle = iCustom(_SymbolPERIOD_D1indicator);
      if(handle == INVALID_HANDLE)
      {
         Alert("Can't start indicator "indicator);
         return INIT_FAILED;
      }
      PrintFormat("Started in %d ms"GetTickCount() - start);
   }
   ...
}

Para simplificar la codificación, hemos sustituido el array bidimensional request por una unidimensional en la función SetupGrid.

uint SetupGrid()
{
   ...                                  // prev:
   MqlTradeRequestSyncLog request[];    // MqlTradeRequestSyncLog request[][2];
   ArrayResize(requestGridSize * 2);  // ArrayResize(request, GridSize);
   ...
}

Más adelante en el bucle a través del array, en lugar de llamadas del tipo request[i][1], utilizamos el direccionamiento request[i * 2 + 1].

Esta pequeña transformación era necesaria por las siguientes razones. Dado que utilizamos este array de estructuras para las consultas al crear la cuadrícula, y necesitamos esperar todos los resultados, la función AwaitAsync debería ahora tomar como primer parámetro una referencia a un array. Un array unidimensional es más fácil de manejar.

Para cada solicitud, su desplazamiento en el búfer indicador se calcula en función de su request_id: todos los desplazamientos se colocan en el array offset. A medida que se reciben confirmaciones de solicitudes, los elementos correspondientes del array se marcan como procesados escribiendo en ellos el valor -1. El número de solicitudes ejecutadas se cuenta en la variable done. Cuando es igual al tamaño del array, la cuadrícula entera está lista.

bool AwaitAsync(MqlTradeRequestSyncLog &r[], const int _handle)
{
   Converter<ulong,doublecnv;
   int offset[];
   const int n = ArraySize(r);
   int done = 0;
   ArrayResize(offsetn);
   
   for(int i = 0i < n; ++i)
   {
      offset[i] = (int)((r[i].result.request_id * FIELD_NUM)
         % (Bars(_Symbol_Period) / FIELD_NUM * FIELD_NUM));
   }
   
   const uint start = GetTickCount();
   while(!IsStopped() && done < n && GetTickCount() - start < TIMEOUT)
   for(int i = 0i < n; ++i)
   {
      if(offset[i] == -1continue// skip empty elements
      double array[];
      if((CopyBuffer(_handle0offset[i], FIELD_NUMarray)) == FIELD_NUM)
      {
         ArraySetAsSeries(arraytrue);
         if((uint)MathRound(array[0]) == r[i].result.request_id)
         {
            r[i].result.retcode = (uint)MathRound(array[1]);
            r[i].result.deal = cnv[array[2]];
            r[i].result.order = cnv[array[3]];
            r[i].result.volume = array[4];
            r[i].result.price = array[5];
            PrintFormat("Got Req=%d at %d ms"r[i].result.request_id,
               GetTickCount() - start);
            Print(TU::StringOf(r[i].result));
            offset[i] = -1// mark processed
            done++;
         }
      }
   }
   return done == n;
}

Volviendo a la función SetupGrid, vamos a ver cómo se llama a AwaitAsync después del bucle de envío de solicitudes.

uint SetupGrid()
{
   ...
   const uint start = GetTickCount();
   for(int i = 0i < (int)GridSize / 2; ++i)
   {
      // calls of buyLimit/sellStopLimit/sellLimit/buyStopLimit
   }
   
   if(EnableAsyncSetup)
   {
      if(!AwaitAsync(requesthandle))
      {
         Print("Timeout");
         return TRADE_RETCODE_ERROR;
      }
   }
   
   PrintFormat("Done %d requests in %d ms (%d ms/request)",
      GridSize * 2GetTickCount() - start,
      (GetTickCount() - start) / (GridSize * 2));
   ...
}

Si se produce un tiempo de espera al configurar la cuadrícula (no todas las solicitudes recibirán confirmación dentro del tiempo asignado), devolveremos el código TRADE_RETCODE_ERROR, y el Asesor Experto intentará «deshacer» lo que consiguió crear.

Es importante tener en cuenta que el modo asíncrono sólo está pensado para configurar una cuadrícula completa cuando necesitamos enviar un lote de solicitudes. En caso contrario, se seguirá utilizando el modo síncrono. Por lo tanto, debemos poner la bandera MqlTradeRequestSync::AsyncEnabled a true antes del bucle de envío y volver a ponerla a false después. No obstante, preste atención a lo siguiente: pueden producirse errores dentro del bucle, debido a los cuales éste se termina prematuramente, devolviendo el último código del servidor. Por lo tanto, si colocamos un reset asíncrono después del bucle, no hay garantía de que se reinicie.

Para resolver este problema se añade una pequeña clase AsyncSwitcher al archivo MqlTradeSync.mqh. La clase controla la activación y desactivación del modo asíncrono desde su constructor y destructor. Esto se ajusta al concepto de gestión de recursos RAII que se expone en la sección Gestión de descriptores de archivos.

class AsyncSwitcher
{
public:
   AsyncSwitcher(const bool enabled = true)
   {
      MqlTradeRequestSync::AsyncEnabled = enabled;
   }
   ~AsyncSwitcher()
   {
      MqlTradeRequestSync::AsyncEnabled = false;
   }
};

Ahora, para la activación temporal segura del modo asíncrono, podemos simplemente describir el objeto local AsyncSwitcher en la función SetupGrid. El código volverá automáticamente al modo síncrono al salir de la función.

uint SetupGrid()
{
   ...
   AsyncSwitcher sync(EnableAsyncSetup);
   ...
   for(int i = 0i < (int)GridSize / 2; ++i)
   {
      ...
   }
   ...
}

El Asesor Experto está listo. Intentemos ejecutarlo dos veces: en modo síncrono y asíncrono para una cuadrícula suficientemente grande (10 niveles, paso de cuadrícula 200).

Para una cuadrícula de 10 niveles, obtendremos 20 consultas, por lo que a continuación se muestran algunos de los registros. En primer lugar, se ha utilizado un modo síncrono. Aclaremos que la inscripción sobre la disponibilidad de las solicitudes se muestra antes que los mensajes sobre las solicitudes porque estos últimos son generados por los destructores de la estructura cuando la función sale. La velocidad de procesamiento es de 51 ms por solicitud.

Start setup at 1.10379

Done 20 requests in 1030 ms (51 ms/request)

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978336, V=0.01, Request executed, Req=1

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978337, V=0.01, Request executed, Req=2

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978343, V=0.01, Request executed, Req=5

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» X=1.10200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978344, V=0.01, Request executed, Req=6

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978348, V=0.01, Request executed, Req=9

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» X=1.10000, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978350, V=0.01, Request executed, Req=10

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978339, V=0.01, Request executed, Req=3

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978340, V=0.01, Request executed, Req=4

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978345, V=0.01, Request executed, Req=7

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» X=1.10600, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978347, V=0.01, Request executed, Req=8

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978365, V=0.01, Request executed, Req=19

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» X=1.11200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978366, V=0.01, Request executed, Req=20

El centro de la cuadrícula coincidía con el precio de 1.10400. El sistema asigna números a las solicitudes en el orden en que se reciben, y su numeración en el array se corresponde con el orden en que cursamos las órdenes: desde el nivel base central, nos desviamos gradualmente hacia los lados. Por lo tanto, no se sorprenda de que después de un par de 1 y 2 (para el nivel 1.10200) viene 5 y 6 (1.10000), ya que 3 y 4 (1.10600) se enviaron antes.

En modo asíncrono, los destructores van precedidos de mensajes sobre la disponibilidad de determinadas solicitudes recibidas en AwaitAsync en tiempo real, y no necesariamente en el orden en que se enviaron las solicitudes (por ejemplo, las solicitudes 49 y 50 «adelantaron» a las 47 y 48).

Started in 16 ms
Start setup at 1.10356
Got Req=41 at 109 ms
DONE, #=1300979180, V=0.01, Order placed, Req=41
Got Req=42 at 109 ms
DONE, #=1300979181, V=0.01, Order placed, Req=42
Got Req=43 at 125 ms
DONE, #=1300979182, V=0.01, Order placed, Req=43
Got Req=44 at 140 ms
DONE, #=1300979183, V=0.01, Order placed, Req=44
Got Req=45 at 156 ms
DONE, #=1300979184, V=0.01, Order placed, Req=45
Got Req=46 at 172 ms
DONE, #=1300979185, V=0.01, Order placed, Req=46
Got Req=49 at 172 ms
DONE, #=1300979188, V=0.01, Order placed, Req=49
Got Req=50 at 172 ms
DONE, #=1300979189, V=0.01, Order placed, Req=50
Got Req=47 at 172 ms
DONE, #=1300979186, V=0.01, Order placed, Req=47
Got Req=48 at 172 ms
DONE, #=1300979187, V=0.01, Order placed, Req=48
Got Req=51 at 172 ms
DONE, #=1300979190, V=0.01, Order placed, Req=51
Got Req=52 at 203 ms
DONE, #=1300979191, V=0.01, Order placed, Req=52
Got Req=55 at 203 ms
DONE, #=1300979194, V=0.01, Order placed, Req=55
Got Req=56 at 203 ms
DONE, #=1300979195, V=0.01, Order placed, Req=56
Got Req=53 at 203 ms
DONE, #=1300979192, V=0.01, Order placed, Req=53
Got Req=54 at 203 ms
DONE, #=1300979193, V=0.01, Order placed, Req=54
Got Req=57 at 218 ms
DONE, #=1300979196, V=0.01, Order placed, Req=57
Got Req=58 at 218 ms
DONE, #=1300979198, V=0.01, Order placed, Req=58
Got Req=59 at 218 ms
DONE, #=1300979199, V=0.01, Order placed, Req=59
Got Req=60 at 218 ms
DONE, #=1300979200, V=0.01, Order placed, Req=60
Done 20 requests in 234 ms (11 ms/request)
...

Debido al hecho de que todas las solicitudes se ejecutaron en paralelo, el tiempo total de envío (234 ms) es sólo ligeramente superior al tiempo de una sola solicitud (aquí alrededor de 100 ms, pero usted tendrá su propio cronometraje). Como resultado, obtuvimos una velocidad de 11 ms por solicitud, que es 5 veces más rápida que con el método síncrono. Como las solicitudes se enviaron casi simultáneamente, no podemos saber el tiempo de ejecución de cada una, y los milisegundos indican la llegada del resultado de una solicitud concreta desde el momento del inicio general del envío en grupo.

Otros registros, como en el caso anterior, contienen todos los campos de consulta y resultado que se imprimen desde los destructores de estructuras. La línea «Order placed» no se modificó después de OrderSendAsync, ya que nuestro indicador auxiliar TradeTransactionRelay.mq5 no publica íntegramente la estructura MqlTradeResult del mensaje TRADE_TRANSACTION_REQUEST.

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979180, V=0.01, Order placed, Req=41

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979181, V=0.01, Order placed, Req=42

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979184, V=0.01, Order placed, Req=45

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» X=1.10200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979185, V=0.01, Order placed, Req=46

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979188, V=0.01, Order placed, Req=49

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» X=1.10000, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979189, V=0.01, Order placed, Req=50

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979182, V=0.01, Order placed, Req=43

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979183, V=0.01, Order placed, Req=44

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979186, V=0.01, Order placed, Req=47

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» X=1.10600, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979187, V=0.01, Order placed, Req=48

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979199, V=0.01, Order placed, Req=59

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» X=1.11200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979200, V=0.01, Order placed, Req=60

Hasta ahora, nuestro Asesor Experto de cuadrícula tenía un par de órdenes pendientes en cada nivel: Limit y Stop Limit. Para evitar esta duplicación, dejamos sólo las órdenes Limit. Esta será la versión final de PendingOrderGrid4.mq5, que también podrá ejecutarse en modo síncrono y asíncrono. No entraremos en detalle en el código fuente, sino que nos limitaremos a señalar las principales diferencias con respecto a la versión anterior.

En la función SetupGrid necesitamos un array de estructuras de tamaño igual a GridSize y no duplicado. El número de solicitudes también disminuirá 2 veces: los únicos métodos utilizados para ellas son buyLimit y sellLimit.

La función CheckGrid comprueba la integridad de la cuadrícula de una manera diferente. Anteriormente, la ausencia de una orden Stop Limit emparejada en el nivel en el que existe un límite se consideraba un error. Esto podía ocurrir cuando se activaba una orden Stop Limit en el servidor desde un nivel vecino. Sin embargo, este esquema no es capaz de restablecer la cuadrícula si se produce un fuerte movimiento bidireccional del precio (spike) en una barra: eliminará no sólo las órdenes Limit originales, sino también las nuevas generadas a partir de las Stop Limit. Ahora el algoritmo comprueba honestamente los niveles vacantes a ambos lados del precio actual y crea allí órdenes Limit utilizando RepairGridLevel. Esta función de ayuda coloca previamente órdenes Stop Limit.

Por último, el manejador OnTradeTransaction apareció en PendingOrderGrid4.mq5. La activación de una orden pendiente provocará la ejecución de una transacción (y un cambio en la configuración de la cuadrícula que debe corregirse), por lo que controlamos las transacciones por un símbolo y una magia determinados. Cuando se detecta una transacción, se llama instantáneamente a la función CheckGrid, además de que se sigue ejecutando al principio de cada barra.

void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &,
   const MqlTradeResult &)
{
   if(transaction.type == TRADE_TRANSACTION_DEAL_ADD)
   {
      if(transaction.symbol == _Symbol)
      {
         DealMonitor dm(transaction.deal); // select the deal
         if(dm.get(DEAL_MAGIC) == Magic)
         {
            CheckGrid();
         }
      }
   }
}

Cabe señalar que la presencia de OnTradeTransaction no es suficiente para escribir Asesores Expertos que sean resistentes a influencias externas imprevistas. Por supuesto, los eventos le permiten responder rápidamente a la situación, pero no tenemos ninguna garantía de que el Asesor Experto no se apague (o se desconecte) por una razón u otra durante algún tiempo y se salte tal o cual transacción. Por lo tanto, el manejador OnTradeTransaction sólo debería ayudar a acelerar los procesos que el programa puede realizar sin él. En concreto, para restablecer correctamente su estado tras el arranque.

Sin embargo, además del evento OnTradeTransaction, MQL5 proporciona otro evento más sencillo: OnTrade.