Colocar una orden pendiente

En Tipos de órdenes hemos considerado, en teoría, todas las opciones de colocación de órdenes pendientes que admite la plataforma. Desde un punto de vista práctico, las órdenes se crean utilizando las funciones OrderSend/OrderSendAsync, para las que la estructura de solicitud MqlTradeRequest se rellena previamente de acuerdo con reglas especiales. En concreto, el campo action debe contener el valor TRADE_ACTION_PENDING de la enumeración ENUM_TRADE_REQUEST_ACTIONS. Teniendo esto en cuenta, los siguientes campos son obligatorios:

  • action
  • symbol
  • volume
  • price
  • type (el valor por defecto 0 corresponde a ORDER_TYPE_BUY)
  • type_filling (por defecto 0 corresponde a ORDER_FILLING_FOK)
  • type_time (el valor por defecto 0 corresponde a ORDER_TIME_GTC)
  • expiration (por defecto 0, no se utiliza para ORDER_TIME_GTC)

Si los valores predeterminados cero son adecuados para la tarea, pueden omitirse algunos de los cuatro últimos campos.

El campo stoplimit es obligatorio sólo para las órdenes de los tipos ORDER_TYPE_BUY_STOP_LIMIT y ORDER_TYPE_SELL_STOP_LIMIT.

Los siguientes campos son opcionales:

  • sl
  • tp
  • magic
  • comment

Los valores cero en sl y tp indican la ausencia de niveles protectores.

Añadamos los métodos para comprobar valores y rellenar campos en nuestras estructuras en el archivo MqlTradeSync.mqh. El principio de formación de todos los tipos de órdenes es el mismo, así que analicemos un par de casos especiales de colocación de órdenes de compra y venta limitadas. El resto de tipos sólo se diferenciarán en el valor del tipo de campo. Los métodos públicos con un conjunto completo de campos obligatorios, así como los niveles de protección, se denominan según los tipos: buyLimit y sellLimit.

   ulong buyLimit(const string nameconst double lotconst double p,
      const double stop = 0const double take = 0,
      ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0)
   {
      type = ORDER_TYPE_BUY_LIMIT;
      return _pending(namelotpstoptakedurationuntil);
   }
   
   ulong sellLimit(const string nameconst double lotconst double p,
      const double stop = 0const double take = 0,
      ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0)
   {
      type = ORDER_TYPE_SELL_LIMIT;
      return _pending(namelotpstoptakedurationuntil);
   }

Dado que la estructura contiene el campo symbol que se inicializa opcionalmente en el constructor, existen métodos similares sin el parámetro name: llaman a los métodos anteriores pasando symbol como primer parámetro. Así, para crear una orden con el mínimo esfuerzo, escriba lo siguiente:

MqlTradeRequestSync request// by default uses the current chart symbol
request.buyLimit(volumeprice);

La parte general del código para comprobar los valores pasados, normalizarlos, guardarlos en campos de estructura y crear una orden pendiente se ha trasladado al método de ayuda _pending. Devuelve el ticket de orden en caso de éxito, o 0 en caso de fallo.

   ulong _pending(const string nameconst double lotconst double p,
      const double stop = 0const double take = 0,
      ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0,
      const double origin = 0)
   {
      action = TRADE_ACTION_PENDING;
      if(!setSymbol(name)) return 0;
      if(!setVolumePrices(lotpstoptakeorigin)) return 0;
      if(!setExpiration(durationuntil)) return 0;
      if((SymbolInfoInteger(nameSYMBOL_ORDER_MODE) & (1 << (type / 2))) == 0)
      {
         Print(StringFormat("pending orders %s not allowed for %s",
            EnumToString(type), name));
         return 0;
      }
      ZeroMemory(result);
      if(OrderSend(thisresult)) return result.order;
      return 0;
   }

Ya sabemos cómo rellenar el campo action y cómo llamar a los métodos setSymbol y setVolumePrices desde operaciones de trading anteriores.

El operador multilínea if garantiza que la operación que se está preparando está presente entre las operaciones de símbolos permitidas especificadas en la propiedad SYMBOL_ORDER_MODE. La división de tipo entero type que divide por la mitad y desplaza el valor resultante en 1, establece el bit correcto en la máscara de tipos de orden permitidos. Esto se debe a la combinación de constantes en la enumeración ENUM_ORDER_TYPE y la propiedad SYMBOL_ORDER_MODE. Por ejemplo, ORDER_TYPE_BUY_STOP y ORDER_TYPE_SELL_STOP tienen los valores 4 y 5, que divididos por 2 dan ambos 2 (sin decimales). La operación 1 << 2 tiene un resultado 4 igual a SYMBOL_ORDER_STOP.

Una característica especial de las órdenes pendientes es el tratamiento de la fecha de vencimiento. El método setExpiration se ocupa de ello. En este método hay que asegurarse de que el modo de vencimiento especificado ENUM_ORDER_TYPE_TIME de duration se permite para el símbolo y la fecha y hora en until se rellenan correctamente.

   bool setExpiration(ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0)
   {
      const int modes = (int)SymbolInfoInteger(symbolSYMBOL_EXPIRATION_MODE);
      if(((1 << duration) & modes) != 0)
      {
         type_time = duration;
         if((duration == ORDER_TIME_SPECIFIED || duration == ORDER_TIME_SPECIFIED_DAY)
            && until == 0)
         {
            Print(StringFormat("datetime is 0, "
               "but it's required for order expiration mode %s",
               EnumToString(duration)));
            return false;
         }
         if(until > 0 && until <= TimeTradeServer())
         {
            Print(StringFormat("expiration datetime %s is in past, server time is %s",
               TimeToString(until), TimeToString(TimeTradeServer())));
            return false;
         }
         expiration = until;
      }
      else
      {
         Print(StringFormat("order expiration mode %s is not allowed for %s",
            EnumToString(duration), symbol));
         return false;
      }
      return true;
   }

La máscara de bits de los modos permitidos está disponible en la propiedad SYMBOL_EXPIRATION_MODE. La combinación de bits en la máscara y las constantes ENUM_ORDER_TYPE_TIME es tal que basta con evaluar la expresión 1 << duration y superponerla a la máscara: un valor distinto de cero indica la presencia del modo.

Para los modos ORDER_TIME_SPECIFIED y ORDER_TIME_SPECIFIED_DAY, el campo expiration con el valor específico datetime no puede estar vacío. Además, la fecha y hora especificadas no pueden estar en el pasado.

Dado que el método _pending presentado anteriormente envía una solicitud al servidor utilizando OrderSend al final, nuestro programa debe asegurarse de que la orden con el ticket recibido fue realmente creada (esto es especialmente importante para las órdenes Limit que pueden ser emitidas a un sistema de trading externo). Por lo tanto, en el método completed, que se utiliza para el control de «bloqueo» del resultado, añadiremos una rama para la operación TRADE_ACTION_PENDING.

   bool completed()
   {
      // old processing code
      // TRADE_ACTION_DEAL
      // TRADE_ACTION_SLTP
      // TRADE_ACTION_CLOSE_BY
      ...
      else if(action == TRADE_ACTION_PENDING)
      {
         return result.placed(timeout);
      }
      ...
      return false;
   }

En la estructura MqlTradeResultSync, añadimos el método placed.

   bool placed(const ulong msc = 1000)
   {
      if(retcode != TRADE_RETCODE_DONE
         && retcode != TRADE_RETCODE_DONE_PARTIAL)
      {
         return false;
      }
      
      if(!wait(orderExistmsc))
      {
         Print("Waiting for order: #" + (string)order);
         return false;
      }
      return true;
   }

Su tarea principal es esperar a que aparezca la orden utilizando la espera en la función orderExist: ya se ha utilizado en la primera fase de verificación de apertura de posición.

Para probar la nueva funcionalidad, vamos a implementar el Asesor Experto PendingOrderSend.mq5. Ello permite seleccionar el tipo de orden pendiente y todos sus atributos mediante variables de entrada, tras lo cual se ejecuta una solicitud de confirmación.

enum ENUM_ORDER_TYPE_PENDING
{                                                        // UI interface strings
   PENDING_BUY_STOP = ORDER_TYPE_BUY_STOP,               // ORDER_TYPE_BUY_STOP
   PENDING_SELL_STOP = ORDER_TYPE_SELL_STOP,             // ORDER_TYPE_SELL_STOP
   PENDING_BUY_LIMIT = ORDER_TYPE_BUY_LIMIT,             // ORDER_TYPE_BUY_LIMIT
   PENDING_SELL_LIMIT = ORDER_TYPE_SELL_LIMIT,           // ORDER_TYPE_SELL_LIMIT
   PENDING_BUY_STOP_LIMIT = ORDER_TYPE_BUY_STOP_LIMIT,   // ORDER_TYPE_BUY_STOP_LIMIT
   PENDING_SELL_STOP_LIMIT = ORDER_TYPE_SELL_STOP_LIMIT// ORDER_TYPE_SELL_STOP_LIMIT
};
 
input string Symbol;             // Symbol (empty = current _Symbol)
input double Volume;             // Volume (0 = minimal lot)
input ENUM_ORDER_TYPE_PENDING Type = PENDING_BUY_STOP;
input int Distance2SLTP = 0;     // Distance to SL/TP in points (0 = no)
input ENUM_ORDER_TYPE_TIME Expiration = ORDER_TIME_GTC;
input datetime Until = 0;
input ulong Magic = 1234567890;
input string Comment;

El Asesor Experto creará una nueva orden cada vez que se inicie o se cambien los parámetros. La eliminación de orden automática no se ofrece todavía. Hablaremos de este tipo de operación más adelante. A este respecto, no olvide borrar las órdenes manualmente.

La colocación de una orden única se realiza, como en algunos ejemplos anteriores, basándose en un temporizador (por lo tanto, primero debe asegurarse de que el mercado esté abierto).

void OnTimer()
{
   // execute once and wait for the user to change the settings
   EventKillTimer();
   
   const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
   if(PlaceOrder((ENUM_ORDER_TYPE)TypesymbolVolume,
      Distance2SLTPExpirationUntilMagicComment))
   {
      Alert("Pending order placed - remove it manually, please");
   }
}

La función PlaceOrder acepta todos los ajustes como parámetros, envía una solicitud y devuelve un indicador de éxito (ticket distinto de cero). Las órdenes de todos los tipos admitidos se ofrecen con distancias preestablecidas desde el precio actual que se calculan como parte del rango diario de cotizaciones.

ulong PlaceOrder(const ENUM_ORDER_TYPE type,
   const string symbolconst double lot,
   const int sltpENUM_ORDER_TYPE_TIME expirationdatetime until,
   const ulong magic = 0const string comment = NULL)
{
   static double coefficients[] = // indexed by order type
   {
      0  ,   // ORDER_TYPE_BUY - not used
      0  ,   // ORDER_TYPE_SELL - not used
     -0.5,   // ORDER_TYPE_BUY_LIMIT - slightly below the price
     +0.5,   // ORDER_TYPE_SELL_LIMIT - slightly above the price
     +1.0,   // ORDER_TYPE_BUY_STOP - far above the price
     -1.0,   // ORDER_TYPE_SELL_STOP - far below the price
     +0.7,   // ORDER_TYPE_BUY_STOP_LIMIT - average above the price 
     -0.7,   // ORDER_TYPE_SELL_STOP_LIMIT - average below the price
      0  ,   // ORDER_TYPE_CLOSE_BY - not used
   };
   ...

Por ejemplo, el coeficiente de -0.5 para ORDER_TYPE_BUY_LIMIT significa que la orden se colocará por debajo del precio actual en la mitad del rango diario (rebote dentro del rango), y el coeficiente de +1.0 para ORDER_TYPE_BUY_STOP significa que la orden se situará en el límite superior del rango (ruptura).

El rango diario propiamente dicho se calcula del siguiente modo:

   const double range = iHigh(symbolPERIOD_D11) - iLow(symbolPERIOD_D11);
   Print("Autodetected daily range: ", (float)range);
   ...

A continuación se indican los valores de punto y volumen que serán necesarios.

   const double volume = lot == 0 ? SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN) : lot;
   const double point = SymbolInfoDouble(symbolSYMBOL_POINT);

El nivel de precios para colocar una orden se calcula en la variable price a partir de los coeficientes dados del rango total.

   const double price = TU::GetCurrentPrice(typesymbol) + range * coefficients[type];

El campo stoplimit sólo debe rellenarse para las órdenes *_STOP_LIMIT. Sus valores se almacenan en la variable origin.

   const bool stopLimit =
      type == ORDER_TYPE_BUY_STOP_LIMIT ||
      type == ORDER_TYPE_SELL_STOP_LIMIT;
   const double origin = stopLimit ? TU::GetCurrentPrice(typesymbol) : 0;

Cuando se activan estos dos tipos de órdenes se coloca una nueva orden pendiente al precio actual. De hecho, en este escenario, el precio se mueve desde el valor actual hasta el nivel price, donde se activa la orden, y por lo tanto el precio «actual anterior» se convierte en el nivel de rebote correcto indicado por una orden Limit. A continuación ilustraremos esta situación.

Los niveles de protección se determinan utilizando el objeto TU::TradeDirection. Para las órdenes Stop-Limit, calculamos a partir de origin.

   TU::TradeDirection dir(type);
   const double stop = sltp == 0 ? 0 :
      dir.negative(stopLimit ? origin : pricesltp * point);
   const double take = sltp == 0 ? 0 :
      dir.positive(stopLimit ? origin : pricesltp * point);

A continuación, se describe la estructura y se rellenan los campos opcionales.

   MqlTradeRequestSync request(symbol);
   
   request.magic = magic;
   request.comment = comment;
   // request.type_filling = SYMBOL_FILLING_FOK;

Aquí puede seleccionar el modo de relleno. Por defecto, MqlTradeRequestSync selecciona automáticamente el primero de los modos permitidos, ENUM_ORDER_TYPE_FILLING.

Dependiendo del tipo de orden elegido por el usuario, llamamos a uno u otro método de trading.

   ResetLastError();
   // fill in and check the required fields, send the request
   ulong order = 0;
   switch(type)
   {
   case ORDER_TYPE_BUY_STOP:
      order = request.buyStop(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_SELL_STOP:
      order = request.sellStop(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_BUY_LIMIT:
      order = request.buyLimit(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_SELL_LIMIT:
      order = request.sellLimit(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_BUY_STOP_LIMIT:
      order = request.buyStopLimit(volumepriceoriginstoptakeexpirationuntil);
      break;
   case ORDER_TYPE_SELL_STOP_LIMIT:
      order = request.sellStopLimit(volumepriceoriginstoptakeexpirationuntil);
      break;
   }
   ...

Si el ticket se ha recibido, esperamos a que aparezca en el entorno de trading del terminal.

   if(order != 0)
   {
      Print("OK order sent: #="order);
      if(request.completed()) // expect result (order confirmation)
      {
         Print("OK order placed");
      }
   }
   Print(TU::StringOf(request));
   Print(TU::StringOf(request.result));
   return order;
}

Vamos a ejecutar el Asesor Experto en el gráfico EURUSD con la configuración predeterminada y, además, seleccionamos la distancia a los niveles de protección de 1000 puntos. Veremos las siguientes entradas en el registro (suponiendo que la configuración por defecto coincide con los permisos para EURUSD de su cuenta).

Autodetected daily range: 0.01413
OK order sent: #=1282106395
OK order placed
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.11248, SL=1.10248, TP=1.12248, ORDER_TIME_GTC, M=1234567890
DONE, #=1282106395, V=0.01, Request executed, Req=91
Alert: Pending order placed - remove it manually, please

Este es el aspecto que tiene en el gráfico:

Orden ORDER_TYPE_BUY_STOP pendiente

Orden ORDER_TYPE_BUY_STOP pendiente

Eliminemos la orden manualmente y cambiemos el tipo de orden a ORDER_TYPE_BUY_STOP_LIMIT. El resultado es una imagen más compleja:

Orden ORDER_TYPE_BUY_STOP_LIMIT pendiente

Orden ORDER_TYPE_BUY_STOP_LIMIT pendiente

El precio en el que se encuentra el par superior de líneas discontinuas es el precio de activación de la orden, como resultado de lo cual se colocará una orden ORDER_TYPE_BUY_LIMIT en el nivel de precios actual, con los valores Stop Loss y Take Profit marcados con líneas rojas. El nivel Take Profit de la futura orden ORDER_TYPE_BUY_LIMIT coincide prácticamente con el nivel de activación de la orden preliminar recién creada ORDER_TYPE_BUY_STOP_LIMIT.

Como ejemplo adicional para el autoaprendizaje, se incluye con el libro un Asesor Experto AllPendingsOrderSend.mq5; el Asesor Experto establece 6 órdenes pendientes a la vez: una de cada tipo.

Órdenes pendientes de todo tipo

Órdenes pendientes de todo tipo

Como resultado de ejecutarlo con la configuración predeterminada, puede obtener entradas de registro como las siguientes:

Autodetected daily range: 0.01413
OK order placed: #=1282032135
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.08824, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032135, V=0.01, Request executed, Req=73
OK order placed: #=1282032136
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.10238, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032136, V=0.01, Request executed, Req=74
OK order placed: #=1282032138
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.10944, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032138, V=0.01, Request executed, Req=75
OK order placed: #=1282032141
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.08118, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032141, V=0.01, Request executed, Req=76
OK order placed: #=1282032142
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.10520, X=1.09531, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032142, V=0.01, Request executed, Req=77
OK order placed: #=1282032144
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.08542, X=1.09531, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032144, V=0.01, Request executed, Req=78
Alert: 6 pending orders placed - remove them manually, please