Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXVI): Trabajando con las solicitudes comerciales pendientes - primera implementación (apertura de posiciones)

7 febrero 2020, 12:55
Artyom Trishkin
0
476

Contenido


Concepto

En artículos anteriores, ya enunciamos el concepto de solicitud comercial pendiente.
Hoy vamos a analizar en qué consiste dicho concepto, cómo nos ayudará y qué nos aportará, además de proceder a la implementación de las solicitudes pendientes.

Al obtener y procesar los errores del servidor comercial, a veces debemos esperar cierto tiempo y repetir la solicitud. Para ejecutar la espera, en el caso más sencillo, podemos simplemente esperar la ejecución de la función Sleep() con el número requerido de milisegundos, y después repetir la solicitud. En muchos programas, esto resulta más que suficiente. Pero aquí tenemos un momento negativo: durante el tiempo de espera, nuestro programa se detiene y aguarda la finalización de la pausa, y ya después continúa con su lógica. Todo ello sucede en el método comercial, y todas las demás funciones del programan dejan de estar disponibles.
Para evitar este momento desagradable, podemos crear una copia de la solicitud comercial cuyo envío al servidor ha provocado el error que ha requerido de una nueva solicitud después de la espera, y luego ubicar esta solicitud en la lista de solicitudes comerciales y salir tranquilamente del método comercial. Con ello, también evitamos que el programa se quede "colgado" en el estado de espera dentro del método comercial y le damos la posibilidad de seguir trabajando según la lógica implementada. En este caso, además, la clase comercial analiza constantemente la lista de solicitudes pendientes, y cuando llega el momento de ejecutar la solicitud (ha finalizado su tiempo de espera), la biblioteca llama por sí misma al método comercial necesario con la solicitud requerida. A continuación, todo tiene lugar en la secuencia anterior: si otra vez obtenemos error, salimos de nuevo del método comercial hasta que llegue el momento de una nueva solicitud comercial. Si la solicitud se ha ejecutado sin errores y ha sido colocada en la cola del servidor, esta solicitud es eliminada de la lista de solicitudes comerciales.

Se trata de una de las opciones de uso de las solicitudes pendientes que nos dan la posibilidad de no interrumpir la ejecución del programa durante el tiempo de espera, especialmente si este es prlongado.
Otra variante de uso de las solicitudes pendientes es la implementación de órdenes StopLimit en MQL4. Porque, ¿qué es una orden StopLimit? Es una orden pendiente doble que contiene dos precios: el precio de la orden Stop y el precio de la orden Limit. La orden pendiente límite se establece cuando el precio alcanza el nivel establecido para la orden Stop. De esta forma, con la ayuda de una solicitud pendiente, resulta fácil implementar la lógica de funcionamiento de la orden StopLimit: solo tenemos que crear una solicitud pendiente para la colocación de la orden Limit, mientras que el momento de establecimiento de dicha orden será el precio al que se debe enviar la solicitud, que registraremos en los parámetros de la solicitud pendiente. En cuanto el precio alcance el valor establecido, se enviará una solicitud al servidor para que coloque una orden Limit. Esta lógica repite completamente la lógica de funcionamiento de las órdenes StopLimit.

Otra variante de aplicación de las solicitudes pendientes se puede considerar el envío automático de solicitudes comerciales para la apertura de posiciones de mercado cuando el precio alcance el nivel establecido, o a una hora determinada, o bien al cumplirse los dos factores.
En general, aquí disponemos de mucha libertad en cuanto a la automatización del envío de solicitudes comerciales según las condiciones establecidas.

El número mágico como repositorio de datos

Al crear una nueva solicitud pendiente, debemos marcarla de alguna forma, para que el programa pueda saber que esta orden concreta ha sido colocada de acuerdo con una solicitud comercial específica, es decir, debemos identificar y vincular la orden o posición de forma unívoca con una solicitud pendiente concreta. Esta vinculación, además, no deberá perderse en situaciones que se salgan de lo normal.
Tras analizar largamente diferentes posibilidades respecto a la organización de esta vinculación, hemos llegado a una única conclusión: necesitamos registrar el identificador de la solicitud comercial en el valor del número mágico de la orden/posición. Se trata del único parámetro que permanecerá inalterable, y que además se encuentra inicialmente en la orden. Todos los demás métodos serán o bien poco fiables (guardado en los comentarios a la orden/posición), o bien requerirán mucho esfuerzo en cuanto a la creación y el acompañamiento (guardado en los archivos). Tampoco vamos a considerar las variables globales del terminal, dado que estas igualmente podrían no tener tiempo de registrarse en el portador en situaciones excepcionales; por eso tampoco ofrecen fiabilidad completa en cuanto a la actualidad de la información.

A nuestro entender, esto solo deja una salida: guardar los datos en el número mágico. Y aquí nos viene a la cabeza otra cuestión importante: anteriormente añadimos a los objetos de orden un identificador que permite agrupar las órdenes/posiciones en un grupo común, lo cual puede resultar útil, por ejemplo, para diferentes asesores de cuadrícula. Este identificador podemos añadirlo al objeto de orden ya solo después de que haya aparecido físicamente en el servidor. Así, no se extraviará en situaciones excepcionales. Claro que ya habíamos planeado inicialmente el guardado posterior de todas las colecciones junto con sus objetos en el disco duro, pero aún queda bastante para llegar ahí. Lo que sí podemos hacer ahora es añadir al número mágico de la orden, además del identificador de solicitud pendiente, los identificadores del grupo.
Así, podremos guardar en el valor del número mágico varios identificadores al mismo tiempo:

  • el identificador del número mágico (el que se establece en los parámetros de entrada del asesor)
  • el identificador del primer grupo (con números de subgrupo de 0 a 15, el cero indica que no hay pertenencia a ningún grupo)
  • el identificador del segundo grupo (con números de subgrupo de 0 a 15, el cero indica que no hay pertenencia a ningún grupo)
  • el identificador de solicitud pendiente (con números posibles de 0 a 255, el cero indica que no hay identificador)

De esta forma, tendremos la posibilidad de establecer el número del primer y el segundo grupo de órdenes. En cada grupo de órdenes pueden existir hasta quince subgrupos. ¿Y qué nos da esto? Su pongamos que tenemos 20 órdenes/posiciones que queremos reunir según un cierto criterio en un subgrupo. Les asignaremos en el primer grupo el número de subgrupo 1. Y también tenemos 20 órdenes/posiciones que queremos reunir según un cierto criterio en otro subgrupo de este primer grupo. Les asignaremos el número de subgrupo 2. Y al tercer conjunto de veinte órdenes/posiciones le asignaremos el subgrupo 3. Ahora tenemos tres subgrupos de órdenes/posiciones que podemos procesar fácilmente al mismo tiempo con nuestro manejador para cada uno de los subgrupos del primer grupo. Y si necesitamos procesar/analizar de forma adicional dos de tres grupos, pero ya con otro manejador completamente distinto y sin perder por ello la pertenencia a los grupos ya existentes, podremos definirlos en un segundo grupo (de hasta quince subgrupos). Esto nos ofrece una flexibilidad mayor al combinar órdenes/posiciones en diferentes grupos (en comparación con un solo grupo), aunque sea usando más números de subgrupos.

Bien, podemos ver que hemos planeado mucho, pero, ¿dónde está la trampa? La trampa se encuentra en lo siguiente: el tamaño del valor de tipo entero en el que se guarda el número mágico para MQL4 es de solo 4 bytes (int). Por eso, aquí tendremos que sacrificar la magnitud del valor del número mágico que podemos establecer en nuestros programas. Para MQL5, el tamaño para el número mágico se establece con el tipo ulong, y ahí podemos establecer más cifras y guardar bastante más información. Pero todo depende de la compatibilidad... Y esto significa que tendremos que sacrificar algo, concretamente, deberemos sacrificar el tamaño del valor del número mágico, que solo tendrá dos bytes (ushort), mientras que los dos bytes liberados se usarán para los identificadores de los dos grupos (uchar) y el identificador de la orden pendiente (uchar).

En el recuadro se muestra la estructura del número mágico y la ubicación de los datos en el valor uint del número mágico:

-------------------------------------------------------------------------
| bit   |31           24|23   20|19   16|15            8|7             0|
-------------------------------------------------------------------------
| byte  |       3       |       2       |       1       |       0       |
-------------------------------------------------------------------------
| type  |     uchar     |     uchar     |            ushort             |
-------------------------------------------------------------------------
| descr |  request id   |  id2  |  id1  |             magic             |
-------------------------------------------------------------------------

Como podemos ver en el recuadro,

  • el valor del número mágico ocupa ahora un tamaño de dos bytes, y se guarda en los dos bytes menores 0 y 1 de la cifra uint (bits 0 — 15), lo que se corresponde con el tipo ushort. Precisamente este tipo de número mágico deberemos utilizar en nuestros programas con valores posibles de 0 a 65535.
  • El siguiente por orden creciente, el byte 2 de la cifra uint, sirve para guardar los dos identificadores de los grupos, y tiene un tamaño uchar (bits 16 — 23).
    El identificador del primer grupo se guarda en los cuatro bits menores de la cifra uchar (bits 16 — 19), y
    el identificador del segundo grupo se guarda en los cuatro bits mayores de esta cifra uchar (bits 20 — 23).
    De esta manera, podemos guardar dos grupos en un valor uchar de un solo byte. La cantidad de números en cada uno de dichos grupos puede ir desde cero (no hay grupo) hasta 15 (el valor máximo que podemos guardar en cuatro bits). 
  • En el tercer y último byte de la cifra uint, guardaremos el valor uchar del identificador de la solicitud pendiente (bits 24 — 31), que puede tener un valor desde cero (no hay identificador) hasta 255. Esto significa que podemos tener simultáneamente hasta 255 solicitudes pendientes activas.

Para organizar el guardado de datos en el valor de la propiedad "número mágico" de la orden, vamos a mejorar la clase de orden abstracta y las clases de eventos.
Pero antes de ello, añadimos al archivo Defines.mqh los nuevos valores de tipo entero del objeto de orden abstracta:

//+------------------------------------------------------------------+
//| Order, deal, position integer properties                         |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order ticket
   ORDER_PROP_MAGIC,                                        // Real order magic number
   ORDER_PROP_TIME_OPEN,                                    // Open time in milliseconds (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_EXP,                                     // Order expiration date (for pending orders)
   ORDER_PROP_STATUS,                                       // Order status (from the ENUM_ORDER_STATUS enumeration)
   ORDER_PROP_TYPE,                                         // Order/deal type
   ORDER_PROP_REASON,                                       // Deal/order/position reason or source
   ORDER_PROP_STATE,                                        // Order status (from the ENUM_ORDER_STATE enumeration)
   ORDER_PROP_POSITION_ID,                                  // Position ID
   ORDER_PROP_POSITION_BY_ID,                               // Opposite position ID
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Ticket of the order that triggered a deal
   ORDER_PROP_DEAL_ENTRY,                                   // Deal direction – IN, OUT or IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Position change time in milliseconds
   ORDER_PROP_TICKET_FROM,                                  // Parent order ticket
   ORDER_PROP_TICKET_TO,                                    // Derived order ticket
   ORDER_PROP_PROFIT_PT,                                    // Profit in points
   ORDER_PROP_CLOSE_BY_SL,                                  // Flag of closing by StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Flag of closing by TakeProfit
   ORDER_PROP_MAGIC_ID,                                     // Order's "magic number" ID
   ORDER_PROP_GROUP_ID1,                                    // First order/position group ID
   ORDER_PROP_GROUP_ID2,                                    // Second order/position group ID
   ORDER_PROP_PEND_REQ_ID,                                  // Pending request ID
   ORDER_PROP_DIRECTION,                                    // Type by direction (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (24)                    // Total number of integer properties
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Number of order properties not used in sorting
//+------------------------------------------------------------------+

Estas propiedades guardarán los identificadores mencionados anteriormente, que se contendrán en el valor del número mágico. Dado que hemos añadido tres nuevas propiedades, y hemos modificado una, en la macrosustitución que indica el número total de propiedades de tipo entero de la orden, cambiaremos su cantidad de 21 a 24.

Asimismo, añadiremos a la enumeración de posibles criterios de clasificación de órdenes y transacciones la clasificación según estas propiedades:

//+------------------------------------------------------------------+
//| Possible criteria of sorting orders and deals                    |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP)
enum ENUM_SORT_ORDERS_MODE
  {
//--- Sort by integer properties
   SORT_BY_ORDER_TICKET = 0,                                // Sort by an order ticket
   SORT_BY_ORDER_MAGIC,                                     // Sort by an order magic number
   SORT_BY_ORDER_TIME_OPEN,                                 // Sort by an order open time in milliseconds
   SORT_BY_ORDER_TIME_CLOSE,                                // Sort by an order close time in milliseconds
   SORT_BY_ORDER_TIME_EXP,                                  // Sort by an order expiration date
   SORT_BY_ORDER_STATUS,                                    // Sort by an order status (market order/pending order/deal/balance and credit operation)
   SORT_BY_ORDER_TYPE,                                      // Sort by an order type
   SORT_BY_ORDER_REASON,                                    // Sort by a deal/order/position reason/source
   SORT_BY_ORDER_STATE,                                     // Sort by an order status
   SORT_BY_ORDER_POSITION_ID,                               // Sort by a position ID
   SORT_BY_ORDER_POSITION_BY_ID,                            // Sort by an opposite position ID
   SORT_BY_ORDER_DEAL_ORDER,                                // Sort by the order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY,                                // Sort by a deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE,                               // Sort by position change time in seconds
   SORT_BY_ORDER_TICKET_FROM,                               // Sort by a parent order ticket
   SORT_BY_ORDER_TICKET_TO,                                 // Sort by a derived order ticket
   SORT_BY_ORDER_PROFIT_PT,                                 // Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL,                               // Sort by the flag of closing an order by StopLoss
   SORT_BY_ORDER_CLOSE_BY_TP,                               // Sort by the flag of closing an order by TakeProfit
   SORT_BY_ORDER_MAGIC_ID,                                  // Sort by an order/position "magic number" ID
   SORT_BY_ORDER_GROUP_ID1,                                 // Sort by the first order/position group ID
   SORT_BY_ORDER_GROUP_ID2,                                 // Sort by the second order/position group ID
   SORT_BY_ORDER_PEND_REQ_ID,                               // Sort by a pending request ID
   SORT_BY_ORDER_DIRECTION,                                 // Sort by direction (Buy, Sell)
//--- Sort by real properties
   SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP,           // Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE,                               // Sort by close price
   SORT_BY_ORDER_SL,                                        // Sort by StopLoss price
   SORT_BY_ORDER_TP,                                        // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT,                                    // Sort by profit
   SORT_BY_ORDER_COMMISSION,                                // Sort by commission
   SORT_BY_ORDER_SWAP,                                      // Sort by swap
   SORT_BY_ORDER_VOLUME,                                    // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT,                            // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL,                               // Sort by profit+commission+swap
   SORT_BY_ORDER_PRICE_STOP_LIMIT,                          // Sort by Limit order when StopLimit order is activated
//--- Sort by string properties
   SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP,               // Sort by symbol
   SORT_BY_ORDER_COMMENT,                                   // Sort by comment
   SORT_BY_ORDER_COMMENT_EXT,                               // Sort by custom comment
   SORT_BY_ORDER_EXT_ID                                     // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+

Para mostrar correctamente las propiedades de la orden en el diario,
añadiremos en el archivo Datas.mqh los índices de las nuevas propiedades y los mensajes que corresponden a los índices:

   MSG_ORD_PROFIT_PT,                                 // Profit in points
   MSG_ORD_MAGIC_ID,                                  // Magic number ID
   MSG_ORD_GROUP_ID1,                                 // First group ID
   MSG_ORD_GROUP_ID2,                                 // Second group ID
   MSG_ORD_PEND_REQ_ID,                               // Pending request ID
   MSG_ORD_PRICE_OPEN,                                // Open price

   {"Прибыль в пунктах","Profit in points"},
   {"Идентификатор магического номера","Magic number's identifier"},
   {"Идентификатор первой группы","First group's identifier"},
   {"Идентификатор второй группы","Second group's identifier"},
   {"Идентификатор отложенного запроса","Pending request's identifier"},
   {"Цена открытия","Price open"},

Vamos a escribir en la clase de orden abstracta en el archivo Order.mqh las mejoras necesarias.

En la sección privada de la clase, añadimos cuatro métodos encargados de extraer (y retornar) del valor de la propiedad de la orden "número mágico" el identificador del número mágico (el número mágico establecido en los ajustes del programa), los identificadores del grupo 1 y el grupo 2, y el identificador de la solicitud pendiente:

//+------------------------------------------------------------------+
//| Abstract order class                                             |
//+------------------------------------------------------------------+
class COrder : public CObject
  {
private:
   ulong             m_ticket;                                    // Selected order/deal ticket (MQL5)
   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];       // Integer properties
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];      // Real properties
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];      // String properties

//--- Return the index of the array the order's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_ORDER_PROP_DOUBLE property)      const { return(int)property-ORDER_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_ORDER_PROP_STRING property)      const { return(int)property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL; }

//--- Data location in the magic number int value
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(void)                                const { return ushort(this.Magic() & 0xFFFF);                                 }
   uchar             GetGroupID1(void)                               const { return uchar(this.Magic()>>16) & 0x0F;                                }
   uchar             GetGroupID2(void)                               const { return uchar((this.Magic()>>16) & 0xF0)>>4;                           }
   uchar             GetPendReqID(void)                              const { return uchar(this.Magic()>>24) & 0xFF;                                }

public:
//--- Default constructor
                     COrder(void){;}

Para retornar el valor ushort del número mágico (establecido en los ajustes) desde el valor uint del número mágico de la orden, basta con superponer la máscara (0xFFFF), que deja inalterados en el valor uint solo los dos bytes menores, mientras que los dos bytes mayores de la cifra uint se rellenan con ceros. Por cierto, al transformar el tipo uint en el tipo ushort, los dos bytes mayores se descartan automáticamente.

Para extraer el identificador del primer grupo, debemos primero desplazar 16 bits a la derecha el valor de la propiedad del número mágico (para que el valor uchar de los identificadores de los grupos se encuentre en el byte cero de la cifra uint), y después superponer sobre la cifra obtenida la máscara 0x0F, que dejará solo los cuatro bits menores del valor obtenido tras el desplazamiento. La transformación del tipo uint al tipo uchar descartará todos los bytes mayores de la cifra, dejando solo el menor, sobre el que está colocada la máscara. De esta forma, obtendremos un valor de cuatro bits entre 0 y 15.

La extracción del identificador del segundo grupo se diferencia un tanto de la extracción del identificador del primer grupo, dado que aquí, el valor que necesitamos se encuentra en los cuatro bits mayores del valor uchar. Por eso, primero haremos exactamente lo mismo que al extraer el identificador del primer grupo: desplazaremos 16 bits a la derecha el valor de la propiedad del número mágico (para que el valor uchar de los identificadores de los grupos se encuentre en el byte cero de la cifra uint), y después superpondremos sobre la cifra obtenida la máscara 0xF0, que dejará solo los cuatro bits mayores del valor obtenido tras el desplazamiento. A continuación, desplazaremos el valor obtenido otros cuatro bits a la derecha, para convertir los bits mayores, que guardan el número del identificador, en valores de 0 a 15.

Para extraer el identificador de la solicitud pendiente, necesitaremos desplazar 24 bits a la derecha el bit mayor de la cifra uint, para que este valor uchar de un solo byte se encuentre en el byte cero de la cifra uint, y después superponer sobre él la máscara 0xFF (lo cual no es obligatorio, dado que al transformar la cifra uint en el tipo uchar quedará de todas formas solo el bit menor).

En el bloque con los métodos de acceso simplificado a las propiedades del objeto de orden abstracta, añadimos los métodos que retornan las cuatro nuevas propiedades:

//+------------------------------------------------------------------+
//| Methods of a simplified access to the order object properties    |
//+------------------------------------------------------------------+
//--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason,
//--- (6) position ID, (7) opposite position ID, (8) first group ID, (9) second group ID,
//--- (10) pending request ID, (11) magic number ID, (12) type, (13) flag of closing by StopLoss,
//--- (14) flag of closing by TakeProfit (15) open time, (16) close time,
//--- (17) order expiration date, (18) state, (19) status, (20) type by direction
   long              Ticket(void)                                       const { return this.GetProperty(ORDER_PROP_TICKET);                     }
   long              TicketFrom(void)                                   const { return this.GetProperty(ORDER_PROP_TICKET_FROM);                }
   long              TicketTo(void)                                     const { return this.GetProperty(ORDER_PROP_TICKET_TO);                  }
   long              Magic(void)                                        const { return this.GetProperty(ORDER_PROP_MAGIC);                      }
   long              Reason(void)                                       const { return this.GetProperty(ORDER_PROP_REASON);                     }
   long              PositionID(void)                                   const { return this.GetProperty(ORDER_PROP_POSITION_ID);                }
   long              PositionByID(void)                                 const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID);             }
   long              MagicID(void)                                      const { return this.GetProperty(ORDER_PROP_MAGIC_ID);                   }
   long              GroupID1(void)                                     const { return this.GetProperty(ORDER_PROP_GROUP_ID1);                  }
   long              GroupID2(void)                                     const { return this.GetProperty(ORDER_PROP_GROUP_ID2);                  }
   long              PendReqID(void)                                    const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID);                }
   long              TypeOrder(void)                                    const { return this.GetProperty(ORDER_PROP_TYPE);                       }
   bool              IsCloseByStopLoss(void)                            const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL);          }
   bool              IsCloseByTakeProfit(void)                          const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP);          }
   long              TimeOpen(void)                                     const { return this.GetProperty(ORDER_PROP_TIME_OPEN);                  }
   long              TimeClose(void)                                    const { return this.GetProperty(ORDER_PROP_TIME_CLOSE);                 }
   datetime          TimeExpiration(void)                               const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP);         }
   ENUM_ORDER_STATE  State(void)                                        const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE);    }
   ENUM_ORDER_STATUS Status(void)                                       const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);  }
   ENUM_ORDER_TYPE   TypeByDirection(void)                              const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }

Y ahí mismo, añadimos los tres métodos para establecer las nuevas propiedades en las propiedadesd de orden abstracta:

//--- Get the full order profit
   double            ProfitFull(void)                                   const { return this.Profit()+this.Comission()+this.Swap();              }
//--- Get order profit in points
   int               ProfitInPoints(void) const;
//--- Set (1) the first group ID, (2) the second group ID, (3) the pending request ID, (4) custom comment
   void              SetGroupID1(const long group_id)                         { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id);                }
   void              SetGroupID2(const long group_id)                         { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id);                }
   void              SetPendReqID(const long req_id)                          { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id);                }
   void              SetCommentExt(const string comment_ext)                  { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext);           }
   
//+------------------------------------------------------------------+

En el constructor cerrado de la clase, rellenamos los nuevos campos para las propiedades de la orden con los valores de los identificadores usando los métodos analizados anteriormente:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Save integer properties
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = this.OrderOpenTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = this.OrderCloseTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = this.PositionTimeUpdateMSC();
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- Save additional integer properties
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_MAGIC_ID]                             = this.GetMagicID();
   this.m_long_prop[ORDER_PROP_GROUP_ID1]                            = this.GetGroupID1();
   this.m_long_prop[ORDER_PROP_GROUP_ID2]                            = this.GetGroupID2();
   this.m_long_prop[ORDER_PROP_PEND_REQ_ID]                          = this.GetPendReqID();
   
//--- Save additional real properties
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
   
//--- Save additional string properties
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)]        = "";
  }
//+------------------------------------------------------------------+

Añadimos al método que retorna la descripción de las propiedades de tipo entero la muestra de la descripción de todas las nuevas propiedades añadidas de la orden abstracta:

//+------------------------------------------------------------------+
//| Return description of an order's integer property                |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- General properties
      property==ORDER_PROP_MAGIC             ?  CMessage::Text(MSG_ORD_MAGIC)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  CMessage::Text(MSG_ORD_TICKET)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  CMessage::Text(MSG_ORD_TICKET_FROM)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  CMessage::Text(MSG_ORD_TICKET_TO)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  CMessage::Text(MSG_ORD_TIME_EXP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          (this.GetProperty(property)==0     ?  CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET)  :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription()   :
      property==ORDER_PROP_DIRECTION         ?  CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  CMessage::Text(MSG_ORD_REASON)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  CMessage::Text(MSG_ORD_POSITION_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER_TICKET ?  CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  CMessage::Text(MSG_ORD_DEAL_ENTRY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  CMessage::Text(MSG_ORD_POSITION_BY_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  CMessage::Text(MSG_ORD_TIME_OPEN)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  CMessage::Text(MSG_ORD_TIME_CLOSE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  CMessage::Text(MSG_ORD_TIME_UPDATE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0")
         )  :
      property==ORDER_PROP_STATE             ?  CMessage::Text(MSG_ORD_STATE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Additional property
      property==ORDER_PROP_STATUS            ?  CMessage::Text(MSG_ORD_STATUS)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  (
                                                 this.Status()==ORDER_STATUS_MARKET_PENDING ? 
                                                 CMessage::Text(MSG_ORD_DISTANCE_PT)  : 
                                                 CMessage::Text(MSG_ORD_PROFIT_PT)
                                                )+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==ORDER_PROP_MAGIC_ID          ?  CMessage::Text(MSG_ORD_MAGIC_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_GROUP_ID1         ?  CMessage::Text(MSG_ORD_GROUP_ID1)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_GROUP_ID2         ?  CMessage::Text(MSG_ORD_GROUP_ID2)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_PEND_REQ_ID       ?  CMessage::Text(MSG_ORD_PEND_REQ_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Ya hemos terminado con la clase de orden abstracta. Ahora, debemos introducir algunos cambios en las clases de eventos.

En la clase de evento abstracto, en la sección protegida de la clase en el archivo Event.mqh, añadimos los métodos que retornan los nuevos identificadores:

protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // Trading event
   bool              m_is_hedge;                                     // Hedge account flag
   long              m_chart_id;                                     // Control program chart ID
   int               m_digits;                                       // Symbol's Digits()
   int               m_digits_acc;                                   // Number of decimal places for the account currency
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Event integer properties
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Event real properties
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // Event string properties
//--- return the flag presence in the trading event
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(void)                          const { return ushort(this.Magic() & 0xFFFF);                           }
   uchar             GetGroupID1(void)                         const { return uchar(this.Magic()>>16) & 0x0F;                          }
   uchar             GetGroupID2(void)                         const { return uchar((this.Magic()>>16) & 0xF0)>>4;                     }
   uchar             GetPendReqID(void)                        const { return uchar(this.Magic()>>24) & 0xFF;                          }
//--- Protected parametric constructor
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);

Los métodos son similares a los métodos de clase de la orden abstracta analizados anteriormente.

Ahora, en las cinco clases herederas de la clase de evento abstracto, en los archivos EventModify.mqh, EventOrderPlaced.mqh, EventOrderRemoved.mqh, EventPositionClose.mqh y EventPositionOpen.mqh, en sus métodos de descripción breve del evento, en lugar de la línea

//+------------------------------------------------------------------+
//| Create and return a short event message                          |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {

//--- (1) header, (2) magic number
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic() : "");
   string text="";

añadimos para cada clase estas líneas:

//+------------------------------------------------------------------+
//| Create and return a short event message                          |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {
//--- (1) header, (2) magic number
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic_id=((this.GetPendReqID()>0 || this.GetGroupID1()>0 || this.GetGroupID2()>0) ? " ("+(string)this.GetMagicID()+")" : "");
   string group_id1=(this.GetGroupID1()>0 ? ", G1: "+(string)this.GetGroupID1() : "");
   string group_id2=(this.GetGroupID2()>0 ? ", G2: "+(string)this.GetGroupID2() : "");
   string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : "");
   string text="";

Como guardamos multitud de datos en el mismo valor del número mágico, al mostrar este número en el diario se reflejará otro valor totalmente distinto, no el establecido en los ajustes del programa. Esto se debe que el valor del número mágico se guarda solo en los dos bytes menores, mientras que en los dos mayores se guardan los identificadores de los grupos y la solicitud pendiente. Por eso, si en el valor del número mágico de la orden se añaden los identificadores, o aunque sea uno de ellos, a los valores mostrados en el diario se le añadirán las descripciones de cada identificador por separado.

Ya hemos hecho todos los cambios necesarios para guardar los datos en el valor del número mágico. Ahora, vamos a ocuparnos propiamente de la clase de la solicitud pendiente y de la primera implementación de la creación de solicitudes pendientes al darse errores de apertura de posiciones.

La clase de solicitud pendiente, primera implementación de las solicitudes

En el archivo de la clase comercial Trading.mqh, justo antes del cuerpo de la clase comercial CTrading, añadimos una nueva clase que describe el objeto de solicitud comercial:

//+------------------------------------------------------------------+
//| Pending request object class                                     |
//+------------------------------------------------------------------+
class CPendingReq : public CObject
  {
private:
   MqlTradeRequest      m_request;                       // Trade request structure
   uchar                m_id;                            // Trading request ID
   int                  m_retcode;                       // Result a request is based on
   double               m_price_create;                  // Price at the moment of a request generation
   ulong                m_time_create;                   // Request generation time
   ulong                m_time_activate;                 // Next attempt activation time
   ulong                m_waiting_msc;                   // Waiting time between requests
   uchar                m_current_attempt;               // Current attempt index
   uchar                m_total_attempts;                // Number of attempts
//--- Copy trading request data
   void                 CopyRequest(const MqlTradeRequest &request)  { this.m_request=request;        }
//--- Compare CPendingReq objects by IDs
   virtual int          Compare(const CObject *node,const int mode=0) const;

public:
//--- Return (1) the request structure, (2) the price at the moemnt of the request generation,
//--- (3) request generation time, (4) current attempt time,
//--- (5) waiting time between requests, (6) current attempt index,
//--- (7) number of attempts, (8) request ID
   MqlTradeRequest      MqlRequest(void)                    const { return this.m_request;            }
   double               PriceCreate(void)                   const { return this.m_price_create;       }
   ulong                TimeCreate(void)                    const { return this.m_time_create;        }
   ulong                TimeActivate(void)                  const { return this.m_time_activate;      }
   ulong                WaitingMSC(void)                    const { return this.m_waiting_msc;        }
   uchar                CurrentAttempt(void)                const { return this.m_current_attempt;    }
   uchar                TotalAttempts(void)                 const { return this.m_total_attempts;     }
   uchar                ID(void)                            const { return this.m_id;                 }
//--- Set (1) the price when creating a request, (2) request creation time,
//--- (3) current attempt time, (4) waiting time between requests,
//--- (5) current attempt index, (6) number of attempts, (7) request ID
   void                 SetPriceCreate(const double price)        { this.m_price_create=price;        }
   void                 SetTimeCreate(const ulong time)           { this.m_time_create=time;          }
   void                 SetTimeActivate(const ulong time)         { this.m_time_activate=time;        }
   void                 SetWaitingMSC(const ulong miliseconds)    { this.m_waiting_msc=miliseconds;   }
   void                 SetCurrentAttempt(const uchar number)     { this.m_current_attempt=number;    }
   void                 SetTotalAttempts(const uchar number)      { this.m_total_attempts=number;     }
   void                 SetID(const uchar id)                     { this.m_id=id;                     }
//--- Constructors
                        CPendingReq(void){;}
                        CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode);
  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price),
                                                                                                                                m_time_create(time),
                                                                                                                                m_id(id),
                                                                                                                                m_retcode(retcode)
  {
   this.CopyRequest(request);
  }
//+------------------------------------------------------------------+
//| Compare CPendingReq objects by IDs                               |
//+------------------------------------------------------------------+
int CPendingReq::Compare(const CObject *node,const int mode=0) const
  {
   const CPendingReq *compared_req=node;
   return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0);
   return 0;
  }
//+------------------------------------------------------------------+


A nuestro parecer, esta clase es tan sencilla que no tiene sentido describir lo comentado en la misma: todo se puede comprender a partir de los nombres de los métodos y las variables de miembro de clase. No obstante, sí que merece la pena hablar sobre el funcionamiento del objeto, así como de los métodos y la funcionalidad de la clase comercial relacionados con el mismo.

Al obtener un error del servidor, queremos crear una solicitud repetida al servidor y salir del método comercial. A continuación, la solicitud nuevamente creada (una vez finalizado su tiempo de espera), se envía de nuevo al servidor. En caso de obtener nuevamente error, de acuerdo con la lógica, deberíamos crear la solicitud pendiente, pero ya ha sido creada al obtener el primer error del servidor. Por eso, en el número mágico de la solicitud comercial recibida, se comprueba la presencia del identificador de la solicitud pendiente, y si este se encuentra ahí, significará que esta solicitud ya ha sido creada anteriormente (en este momento se está enviando el siguiente intento al servidor), así que no hay necesidad de crear una nueva solicitud. Si en el número mágico de la solicitud comercial no hay identificador, crearemos una nueva solicitud pendiente con el primer identificador libre de menor valor y saldremos del método comercial para liberar el programa para otras acciones. 
En el temporizador de la clase comercial, se monitorea constantemente la lista de solicitudes comerciales, y si el tiempo de espera de la próxima solicitud ha finalizado, llamaremos desde el temporizador el método comercial correspondiente a la solicitud. Al comprobar cada nueva solicitud de la lista de solicitudes pendientes, se comprueba la presencia de la posición u orden correspondiente a la solicitud en la lista de órdenes y posiciones de mercado, y si existe la posición u orden con el identificador actual, significará que la solicitud pendiente ha realizado su función, y será eliminada de la lista de solicitudes.

Resumidamente, la lógica es esta. Vamos a implementarla.

La clase ya la hemos incluido en el archivo Trading.mqh, ahora, en su sección privada,
declaramos el método para buscar y retornar el primer menor identificador no utilizado de la solicitud pendiente:

//--- Look for the first free pending request ID
   int                  GetFreeID(void);
                                    
public:
//--- Constructor
                        CTrading();

Vamos a realizar la implementación fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Look for the first free pending request ID                       |
//+------------------------------------------------------------------+
int CTrading::GetFreeID(void)
  {
   int id=WRONG_VALUE;
   CPendingReq *element=new CPendingReq();
   if(element==NULL)
      return 0;
   for(int i=1;i<256;i++)
     {
      element.SetID((uchar)i);
      this.m_list_request.Sort();
      if(this.m_list_request.Search(element)==WRONG_VALUE)
        {
         id=i;
         break;
        }
     }
   delete element;
   return id;
  }
//+------------------------------------------------------------------+

En total, podemos tener 255 solicitudes pendientes autónomas. Cada una de las solicitudes tiene sus propiedades y su tiempo de espera entre intentos comerciales, y, a partir de ello, un periodo de existencia propio para el objeto de solicitud pendiente. En relación con ello, podría darse la situación en la que para un identificador de solicitud ya se usa un número (por ejemplo, 255), mientras que el identificador con el número 0 o 1, o cualquiera de los menores, ya ha sido liberado, por lo que se lo podría utilizar para las nuevas solicitudes comerciales. Este método se usa precisamente para buscar el menor número liberado para un identificador.

Primero se crea un objeto temporal de clase de solicitud pendiente y un identificador con el valor -1. Este valor nos comunicará que no hay identificadores libres: los 255 están ocupados, mientras que el valor 0 se retornará al darse el error de creación del objeto temporal. A continuación, en un ciclo por los posibles valores de los números de los indicadores, del 1 al 255, comprobamos en la lista de solicitudes pendientes la presencia de un objeto de solicitud con un identificador igual al valor actual del índice del ciclo. Para ello, primero asignamos al objeto temporal un identificador igual al número del índice del ciclo; luego asignamos a la lista la bandera de lista clasificada, y simplemente buscamos en la lista un objeto de solicitud con ese identificador, es decir, igual al objeto temporal al que se le ha asignado el índice de ciclo como identificador. Si no encontramos tal objeto en la lista, asignamos al valor retornado del método la magnitud del índice del ciclo e interrumpimos el mismo.
Al finalizar el ciclo, eliminamos el objeto de solicitud temporal y retornamos el valor del identificador, que puede ser o bien -1, o bien de 1 a 255.

En la sección pública de la clase, declaramos el método para crear la solicitud pendiente, y añadimos los métodos para establecer y retornar los valores de los indicadores en/desde el valor de la propiedad de la orden/posición "número mágico":

//--- Create a pending request
   bool                 CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj);

//--- Data location in the magic number int value
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------

//--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value
   void              SetGroupID1(const uchar group,uint &magic)            { magic &=0xFFF0FFFF; magic |= uint(ConvToXX(group,0)<<16);    }
   void              SetGroupID2(const uchar group,uint &magic)            { magic &=0xFF0FFFFF; magic |= uint(ConvToXX(group,1)<<16);    }
   void              SetPendReqID(const uchar id,uint &magic)              { magic &=0x00FFFFFF; magic |= (uint)id<<24;                   }
//--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones)
   uchar             ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));}
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(const uint magic)                    const { return ushort(magic & 0xFFFF);                               }
   uchar             GetGroupID1(const uint magic)                   const { return uchar(magic>>16) & 0x0F;                              }
   uchar             GetGroupID2(const uint magic)                   const { return uchar((magic>>16) & 0xF0)>>4;                         }
   uchar             GetPendReqID(const uint magic)                  const { return uchar(magic>>24) & 0xFF;                              }

Los métodos de retorno de los valores ya los hemos analizado anteriormente, aquí son idénticos. Vamos a analizar los métodos para establecer los diferentes identificadores.

Dado que los dos identificadores de los grupos se guardan en un solo byte, y el valor numérico del identificador puede estar solo entre 0 y 15 (4 bytes), para establecer el identificador del segundo grupo, necesitaremos desplazar su valor 4 bits a la izquierda, guardándolo así en los cuatro bits mayores de la cifra de un solo byte. Para ello, hemos creado el método ConvToXX(), que, dependiendo del índice del grupo (0 o 1), o bien desplaza la cifra transmitida (de 0 a 15) 4 bits a la izquierda (segundo grupo, índice 1), o bien no la desplaza (primer grupo, índice 0)

Para establecer el valor del identificador del primer grupo, primero necesitamos poner a cero los cuatro bits menores del byte en el que vamos a guardar el valor del identificador. Esto se puede hacer superponiendo al valor del número mágico una máscara en la que para cada medio byte (4 bits) se use el valor F.
Es decir, en aquellos bits en los que el valor se debe dejar sin cambio, superponemos un valor hexadecimal de la cifra decimal 15 (F), y en aquellos bits que no se deben borrar, superponemos cero. De esta forma, la máscara superpuesta al valor del número mágico será así: 0x FFF0FFFF.
Aquí:

  • FFFF — dejamos el identificador del número mágico (el número mágico establecido en los ajustes del asesor),
  • F0 — borramos (0) los cuatro bits menores en el byte que guarda los identificadores de los grupos; los mayores los dejamos (F), ahí se guarda el identificador del segundo grupo,
  • FF — dejamos el valor del identificador de la solicitud pendiente

A continuación, colocamos en el byte preparado para el guardado de los identificadores de los grupos el número del grupo obtenido del método ConvToXX() con el índice 0 y desplazado 16 bits a la izquierda, para que la cifra obtenida se encuentre en el byte necesario en el que se guardan los identificadores de los grupos.

Para establecer el valor del identificador del segundo grupo, ponemos a cero los cuatro bits mayores del byte en el que vamos a guardar el valor del identificador. Lo hacemos superponiendo al valor del número mágico la máscara 0x FF0FFFFF.
Aquí:

  • FFFF — dejamos el identificador del número mágico (el número mágico establecido en los ajustes del asesor),
  • 0F — borramos (0) los cuatro bits mayores en el byte que guarda los identificadores de los grupos; los menores los dejamos (F), ahí se guarda el identificador del primer grupo,
  • FF — dejamos el valor del identificador de la solicitud pendiente

A continuación, colocamos en el byte preparado para el guardado de los identificadores de los grupos el número del grupo obtenido del método ConvToXX() con el índice 1 y desplazado 16 bits a la izquierda, para que la cifra obtenida se encuentre en el byte necesario en el que se guardan los identificadores de los grupos.

Para establecer el valor del identificador de la solicitud pendiente, ponemos a cero el valor del byte en el que vamos a guardar el valor del identificador. Lo hacemos superponiendo al valor del número mágico la máscara 0x 00FFFFFF.
Aquí:

  • FFFF — dejamos el identificador del número mágico (el número mágico establecido en los ajustes del asesor),
  • FF — dejamos los valores de los identificadores de los grupos,
  • 00 — borramos el valor del identificador de la solicitud pendiente

A continuación, en el byte preparado para guardar el identificador de la solicitud pendiente, colocamos el valor uchar del identificador desplazado 24 bits a la izquierda para que la cifra obtenida se encuentre en el byte requerido en el que se guarda el identificador de la solicitud pendiente.

Implementamos el método para crear el objeto de solicitud pendiente fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Create a pending request                                         |
//+------------------------------------------------------------------+
bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj)
  {
   //--- Create a new pending request object
   CPendingReq *req_obj=new CPendingReq(id,symbol_obj.Bid(),symbol_obj.Time(),request,retcode);
   if(req_obj==NULL)
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
      return false;
     }
   //--- If failed to add the request to the list, display the appropriate message,
   //--- remove the created object and return 'false'
   if(!this.m_list_request.Add(req_obj))
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
      delete req_obj;
      return false;
     }
   //--- Filled in the fields of a successfully created object by the values passed to the method
   req_obj.SetTimeActivate(symbol_obj.Time()+wait);
   req_obj.SetWaitingMSC(wait);
   req_obj.SetCurrentAttempt(0);
   req_obj.SetTotalAttempts(attempts);
   return true;
  }
//+------------------------------------------------------------------+

El méotodo es sencillo: se crea un nuevo objeto de solicitud, se añade a la lista de solicitudes pendientes, se rellenan los campos del objeto con los valores transmitidos al método (el tiempo de activación de la solicitud se calcula como el tiempo de creación de la solicitud + tiempo de espera) y se retorna true. En caso de error, se retorna false.

En el temporizador de la clase que creamos en el artículo anterior, escribimos la lógica del trabajo con solicitudes pendientes:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CTrading::OnTimer(void)
  {
   //--- In a loop by the list of pending requests
   int total=this.m_list_request.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      //--- receive the next request object
      CPendingReq *req_obj=this.m_list_request.At(i);
      if(req_obj==NULL)
         continue;
      //--- if the current attempt exceeds the defined number of trading attempts,
      //--- remove the current request object and move on to the next one
      if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX)
        {
         this.m_list_request.Delete(i);
         continue;
        }
      //--- get the request structure and the symbol object a trading operation should be performed for
      MqlTradeRequest request=req_obj.MqlRequest();
      CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol);
      if(symbol_obj==NULL || !symbol_obj.RefreshRates())
         continue;
      
      //--- Set the request activation time in the request object
      req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1));
      
      //--- If the current time is less than the request activation time,
      //--- this is not the request time - move on to the next request in the list
      if(symbol_obj.Time()<req_obj.TimeActivate())
         continue;
      
      //--- Set the attempt number in the request object
      req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1));
      
      //--- Get the pending request ID
      uchar id=this.GetPendReqID((uint)request.magic);
      
      //--- Get the list of orders/positions containing the order/position with the pending request ID
      CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL);
      if(::CheckPointer(list)==POINTER_INVALID)
         continue;
      //--- Depending on the type of action performed in the trading request 
      switch(request.action)
        {
         //--- Open a position
         case TRADE_ACTION_DEAL :
            //--- if there is no position/order with the obtained pending request ID (the list is empty), send a trading request
            if(list.Total()==0)
              {
               this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation);
              }
            //--- if a position/order with the obtained pending request ID is already present (the list is empty), the request has been handled and should be removed 
            else
               this.m_list_request.Delete(i);
            break;
         //---
         default:
            break;
        }
     }
  }
//+------------------------------------------------------------------+

La lógica de trabajo se describe detalladamente en los comentarios al código, y a buen seguro no requerirá de explicaciones. Lo único que podemos destacar es el cálculo del tiempo de activación de la siguiente solicitud comercial. El tiempo se calcula como "el tiempo de creación del objeto de solicitud" + el tiempo de espera en milisegundos * el número del siguiente intento. De esta forma, vinculamos la hora de la solicitud a la hora de creación de la primera solicitud y al número del intento: cuanto mayor sea el número del intento, más tiempo deberá pasar desde la creación del objeto hasta su activación. Y este tiempo se deberá indicar de forma discreta: al alcanzar los 10 segundos, el primer intento deberá suceder tras 10 segundos, el segundo, tras 20, la tercero, tras 30, etcétera. De esta forma, el intervalo entre los nuevos intentos comerciales nunca será inferior al tiempo de espera establecido entre ellos.

En el método que retorna el modo de procesamiento de errores, trasladamos el código de error de ausencia de conexión con el servidor comercial al bloque que retorna el modo de procesamiento "espera". Antes, este código se procesaba como "crear una solicitud comercial pendiente":

//+------------------------------------------------------------------+
//| Return the error handling method                                 |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code)
  {
   switch(result_code)
     {
   #ifdef __MQL4__
      //--- Malfunctional trade operation
      case 9   :
      //--- Account disabled
      case 64  :
      //--- Invalid account number
      case 65  :  return ERROR_CODE_PROCESSING_METHOD_DISABLE;
      
      //--- No error but result is unknown
      case 1   :
      //--- General error
      case 2   :
      //--- Old client terminal version
      case 5   :
      //--- Not enough rights
      case 7   :
      //--- Market closed
      case 132 :
      //--- Trading disabled
      case 133 :
      //--- Order is locked and being processed
      case 139 :
      //--- Buy only
      case 140 :
      //--- The number of open and pending orders has reached the limit set by the broker
      case 148 :
      //--- Attempt to open an opposite order if hedging is disabled
      case 149 :
      //--- Attempt to close a position on a symbol contradicts the FIFO rule
      case 150 :  return ERROR_CODE_PROCESSING_METHOD_EXIT;
      
      //--- Invalid trading request parameters
      case 3   :
      //--- Invalid price
      case 129 :
      //--- Invalid stop levels
      case 130 :
      //--- Invalid volume
      case 131 :
      //--- Not enough money to perform the operation
      case 134 :
      //--- Expirations are denied by broker
      case 147 :  return ERROR_CODE_PROCESSING_METHOD_CORRECT;
      
      //--- Trade server is busy
      case 4   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- No connection to the trade server
      case 6   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Too frequent requests
      case 8   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- No price
      case 136 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Broker is busy
      case 137 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Too many requests
      case 141 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Modification denied because the order is too close to market
      case 145 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Trade context is busy
      case 146 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      
      //--- Trade timeout
      case 128 :
      //--- Price has changed
      case 135 :
      //--- New prices
      case 138 :  return ERROR_CODE_PROCESSING_METHOD_REFRESH;

   //--- MQL5
   #else
      //--- Auto trading disabled by the server
      case 10026  :  return ERROR_CODE_PROCESSING_METHOD_DISABLE;
      
      //--- Request canceled by a trader
      case 10007  :
      //--- Request expired
      case 10012  :
      //--- Trading disabled
      case 10017 :
      //--- Market closed
      case 10018  :
      //--- Order status changed
      case 10023  :
      //--- Request unchanged
      case 10025  :
      //--- Request blocked for handling
      case 10028  :
      //--- Transaction is allowed for live accounts only
      case 10032  :
      //--- The maximum number of pending orders is reached
      case 10033  :
      //--- Reached the maximum order and position volume for this symbol
      case 10034  :
      //--- Invalid or prohibited order type
      case 10035  :
      //--- Position with the specified ID already closed
      case 10036  :
      //--- A close order is already present for a specified position
      case 10039  :
      //--- The maximum number of open positions is reached
      case 10040  :
      //--- Request to activate a pending order is rejected, the order is canceled
      case 10041  :
      //--- Request is rejected, because the rule "Only long positions are allowed" is set for the symbol
      case 10042  :
      //--- Request is rejected, because the rule "Only short positions are allowed" is set for the symbol
      case 10043  :
      //--- Request is rejected, because the rule "Only closing of existing positions is allowed" is set for the symbol
      case 10044  :
      //--- Request is rejected, because the rule "Only closing of existing positions by FIFO rule is allowed" is set for the symbol
      case 10045  :  return ERROR_CODE_PROCESSING_METHOD_EXIT;

      //--- Requote
      case 10004  :
      //--- Request rejected
      case 10006  :
      //--- Prices changed
      case 10020  :  return ERROR_CODE_PROCESSING_METHOD_REFRESH;

      //--- Invalid request
      case 10013  :
      //--- Invalid request volume
      case 10014  :
      //--- Invalid request price
      case 10015  :
      //--- Invalid request stop levels
      case 10016  :
      //--- Insufficient funds for request execution
      case 10019  :
      //--- Invalid order expiration in a request
      case 10022  :
      //--- The specified type of order execution by balance is not supported
      case 10030  :
      //--- Closed volume exceeds the current position volume
      case 10038  :  return ERROR_CODE_PROCESSING_METHOD_CORRECT;

      //--- No quotes to handle the request
      case 10021  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT;
      //--- Too frequent requests
      case 10024  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- An order or a position is frozen
      case 10029  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT;
      //--- No connection to the trade server
      case 10031  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)20000;   // ERROR_CODE_PROCESSING_METHOD_WAIT;

      //--- Request handling error
      case 10011  :
      //--- Auto trading disabled by the client terminal
      case 10027  :  return ERROR_CODE_PROCESSING_METHOD_PENDING;

      //--- Order placed
      case 10008  :
      //--- Request executed
      case 10009  :
      //--- Request executed partially
      case 10010  :
   #endif 
      //--- "OK"
      default:
        break;
     }
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

¿Por qué es así? En primer lugar, para la simulación de la creación de solicitudes pendientes con espera, retornamos una espera de 20 segundos entre solicitudes; en segundo lugar, porque así resulta más cómodo hacer varios intentos comerciales con espera de conexión con el servidor comercial. En cualquier caso, se trata de una primera variante de prueba para procesar solicitudes comerciales: posteriormente la mejoraremos y modificaremos.

Dado que hoy estamos poniendo a prueba únicamente el concepto, vamos a crear una solicitud comercial solo para abrir posiciones, y solo al obtener un error del servidor comercial. Al comprobar que las solicitudes comerciales sean correctas, no vamos a crear por el momento solicitudes pendientes, dejaremos la espera dentro del método de apertura de posiciones.

Añadimos al método de apertura de una posición el bloque de creación de solicitudes pendientes:

//+------------------------------------------------------------------+
//| Open a position                                                  |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type,
                            const double volume,
                            const string symbol,
                            const ulong magic=ULONG_MAX,
                            const SL sl=0,
                            const TP tp=0,
                            const string comment=NULL,
                            const ulong deviation=ULONG_MAX)
  {
//--- Set the trading request result as 'true' and the error flag as "no errors"
   bool res=true;
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type;
   ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type;
//--- Get a symbol object by a symbol name. If failed to get
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
//--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false'
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- get a trading object from a symbol object
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
//--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false'
   if(trade_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Set the prices
//--- If failed to set - write the "internal error" flag, set the error code in the return structure,
//--- display the message in the journal and return 'false'
   if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj))
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      trade_obj.SetResultRetcode(10021);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));   // No quotes to process the request
      return false;
     }

//--- Write the volume to the request structure
   this.m_request.volume=volume;
//--- Get the method of handling errors from the CheckErrors() method while checking for errors in the request parameters
   double pr=(type==POSITION_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid());
   ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,pr,action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp);
//--- In case of trading limitations, funds insufficiency,
//--- if there are limitations by StopLevel or FreezeLevel ...
   if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
     {
      //--- If trading is completely disabled, set the error code to the return structure,
      //--- display a journal message, play the error sound and exit
      if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
        {
         trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
         trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- If the check result is "abort trading operation" - set the last error code to the return structure,
      //--- display a journal message, play the error sound and exit
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal
      if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
         //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned)
         ::Sleep(method);
         //--- after waiting, update all data
         symbol_obj.Refresh();
        }
      //--- If the check result is "create a pending request", do nothing temporarily
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
        }
     }
   
//--- In the loop by the number of attempts
   for(int i=0;i<this.m_total_try;i++)
     {                
      //--- Send the request
      res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);
      //--- If the request is executed successfully or the asynchronous order sending mode is set, play the success sound
      //--- set for a symbol trading object for this type of trading operation and return 'true'
      if(res || trade_obj.IsAsyncMode())
        {
         if(this.IsUseSounds())
            trade_obj.PlaySoundSuccess(action,order_type);
         return true;
        }
      //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         
         //--- Get the error handling method
         method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
         //--- If "Disable trading for the EA" is received as a result of sending a request, enable the disabling flag and end the attempt loop
         if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
           {
            this.SetTradingDisableFlag(true);
            break;
           }
         //--- If "Exit the trading method" is received as a result of sending a request, end the attempt loop
         if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
           {
            break;
           }
         //--- If "Correct the parameters and repeat" is received as a result of sending a request -
         //--- correct the parameters and start the next iteration
         if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT)
           {
            this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj);
            continue;
           }
         //--- If "Update data and repeat" is received as a result of sending a request -
         //--- update data and start the next iteration
         if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
           {
            symbol_obj.Refresh();
            continue;
           }
         //--- If "Wait and repeat" or "Create a pending request" is received as a result of sending a request,
         //--- create a pending request and end the loop
         if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH)
           {
            //--- If the trading request magic number, has no pending request ID
            if(this.GetPendReqID((uint)magic)==0)
              {
               //--- Waiting time in milliseconds:
               //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value,
               //--- for the "Create a pending request" handling method - till there is a zero waiting time
               ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : 0);
               //--- Look for the least of the possible IDs. If failed to find
               //--- or in case of an error while updating the current symbol data, return 'false'
               int id=this.GetFreeID();
               if(id<1 || !symbol_obj.RefreshRates())
                  return false;
               //--- Write the request ID to the magic number, while a symbol name is set in the request structure
               //--- Set position and trading operation types (the remaining structure fields are already filled in)
               uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic);
               this.SetPendReqID((uchar)id,mn);
               this.m_request.magic=mn;
               this.m_request.symbol=symbol_obj.Name();
               this.m_request.action=TRADE_ACTION_DEAL;
               this.m_request.type=order_type;
               //--- Pass the number of trading attempts minus one to the pending request,
               //--- since there already has been one failed attempt
               uchar attempts=(this.m_total_try-1 < 1 ? 1 : this.m_total_try-1);
               this.CreatePendingRequest((uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj);
               break;
              }
           }
        }
     }
//--- Return the result of sending a trading request in a symbol trading object
   return res;
  }
//+------------------------------------------------------------------+

Aquí hemos descrito todo con detalle en los comentarios al código, así que esperamos que el lector pueda analizar la información por su cuenta. En cualquier caso, es posible debatir cualquier duda en los comentarios al artículo.

Vamos a introducir algunas mejoras en el archivo Engine.mqh de la clase del objeto principal de la biblioteca CEngine.

En el bloque para los métodos de establecimiento de los objetos comerciales, escribimos el método para establecer el número de intentos comerciales:

//--- Set the following for the trading classes:
//--- (1) correct filling policy, (2) filling policy,
//--- (3) correct order expiration type, (4) order expiration type,
//--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date,
//--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts
   void                 TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetMagic(const uint magic,const string symbol_name=NULL);
   void                 TradingSetComment(const string comment,const string symbol_name=NULL);
   void                 TradingSetDeviation(const ulong deviation,const string symbol_name=NULL);
   void                 TradingSetVolume(const double volume=0,const string symbol_name=NULL);
   void                 TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL);
   void                 TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL);
   void                 TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL);
   void                 TradingSetTotalTry(const uchar attempts)                          { this.m_trading.SetTotalTry(attempts);               }
   
//--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols

El método simplemente llama al método homónimo de la clase comercial.

En la sección privada de la clase, escribimos el método para convertir los valores de los identificadores de los grupos en un valor uchar,
mientras que declaramos en la sección pública el método para crear y retornar el valor del número mágico compuesto:

//--- Constructor/destructor
                        CEngine();
                       ~CEngine();

private:
//--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones)
   uchar                ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));   }

public:
//--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

  };
//+------------------------------------------------------------------+

El método de conversión lo hemos analizado más arriba. El método para crear el número mágico compuesto sirve para combinar los valores del número mágico, el número del primer y el segundo grupo y el identificador de la solicitud comercial en un único número mágico asignado a la orden al enviarla al servidor.
Lo implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Create and return the composite magic number                     |
//| from the specified magic number value,                           |
//| first and seconf group IDs and                                   |
//| the pending request ID                                           |
//+------------------------------------------------------------------+
uint CEngine::SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0)
  {
   uint magic=magic_id;
   this.m_trading.SetGroupID1(group_id1,magic);
   this.m_trading.SetGroupID2(group_id2,magic);
   this.m_trading.SetPendReqID(pending_req_id,magic);
   return magic;
  }
//+------------------------------------------------------------------+

Transmitimos todos los identificadores al método y, con ayuda de los métodos anteriormente analizados en la clase comercial para el establecimiento de los identificadores, añadimos aquellos al valor del número mágico que es precisamente devueto desde el método.

En general, esto es todo lo que deberíamos hacer en el marco del presente artículo para comprobar el concepto propuesto.

Para poner a prueba la creación y procesar la solicitud pendiente, deberemos modelar una situación errónea que requiera una solicitud repetida tras la espera. Si el lector recuerda, ya implementamos el procesamiento del error "no existe conexión con el servidor comercial" precisamente como una espera de 20 segundos. Por defecto, hemos establecido cinco intentos comerciales. Esto significa que simplemente debemos iniciar el asesor, desconectar internet (interrumpir la conexión con el servidor) e intentar abrir una posición (botón Buy o Sell en el panel comercial del asesor de prueba). Después de obtener el error, tendremos 20 * 5 = 100 segundos para conectar de nuevo internet y mirar cómo el asesor procesa la solicitud pendiente creada. Al finalizar los 100 segundos, es decir, el tiempo requerido para los cinco intentos repetidos, la solicitud pendiente deberá eliminarse automáticamente de la lista de solicitudes (ya después de reestablecer la conexión con el servidor, dado que la hora se puede obtener solo después de recuperar la conexión). Por el momento, no hemos implementado esta posibilidad, ya que, en primer lugar, estamos ante una simple prueba del funcionamiento de las solicitudes pendientes, y en segundo lugar, la funcionalidad se desarrollará y requerirá que introduzcamos cambios: solo entonces se implementarán las otras posibilidades pensadas. Es decir, tras reestablecer la conexión con el servidor comercial, el asesor comenzará en cualquier caso a enviar las solicitudes comerciales inscritas en el objeto de solicitud. Tras el primer intento comercial, la posición deberá ser abierta, y el objeto de solicitud pendiente deberá ser eliminado de la lista de solicitudes.

Además de la solicitud pendiente, hemos implementado el guardado de varios identificadores en el valor del número mágico. Para poner a prueba el establecimiento de estos identificadores en el número mágico de la solicitud enviada, realizamos una selección aleatoria de números del primer y segundo subgrupo en los grupos 1 y 2, y luego registramos su propiedad de orden "número mágico". Al abrir una posición, veremos en el diario tanto el valor real del número mágico de la posición abierta o la orden colocada, como el identificador del número mágico establecido en los ajustes (entre paréntesis tras el verdadero valor del número mágico), así como los identificadores de los subgrupos en el primer y segundo grupo (se designan como G1 y G2)

Simulación

Para poner a prueba las solicitudes pendientes, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part26\ con el nuevo nombre TestDoEasyPart26.mq5.

En los parámetros de entrada del asesor, cambiamos el tipo para el número mágico de ulong a ushort , ahora, el tamaño máximo del número mágico no podrá superar los dos bytes (65535). Asimismo, añadimos otra variable más, el número de intentos comerciales:

//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  40;   // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;   // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds
//--- global variables

En las variables globales, cambiamos el tipo de la variable magic_number de ulong a ushort, y añadimos dos variables para guardar los valores de los grupos:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
bool           trailing_on;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         used_symbols;
string         array_used_symbols[];
bool           testing;
uchar          group1;
uchar          group2;
//+------------------------------------------------------------------+

En el manejador OnInit(), inicializamos las variables de los grupos y establecemos el estado inicial para generar números pseudoaleatorios:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   
//--- Initialize random group numbers
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Initialize DoEasy library
   OnInitDoEasy();

En la función de inicialización de la biblioteca, establecemos el número mágico por defecto para todos los objetos comerciales y el número de intentos comerciales:

//+------------------------------------------------------------------+
//| Initializing DoEasy library                                      |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Check if working with the full list is selected
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Fill in the array of used symbols
   used_symbols=InpUsedSymbols;
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols);

//--- Set the type of the used symbol list in the symbol collection
   engine.SetUsedSymbols(array_used_symbols);
//--- Displaying the selected mode of working with the symbol object collection
   Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal());
   
//--- Create resource text files
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Pass all existing collections to the trading class
   engine.TradingOnInit();

//--- Set the default magic number for all used symbols
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Set synchronous passing of orders for all used symbols
   engine.TradingSetAsyncMode(false);
//--- Set the number of trading attempts in case of an error
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Set standard sounds for trading objects of all used symbols
   engine.SetSoundsStandart();
//--- Set the general flag of using sounds
   engine.SetUseSounds(InpUseSounds);
//--- Set the spread multiplier for symbol trading objects in the symbol collection
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Set controlled values for symbols
   //--- Get the list of all collection symbols
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- In a loop by the list, set the necessary values for tracked symbol properties
      //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" 
      //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program
      /*
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Set control of the symbol price increase by 100 points
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Set control of the symbol price decrease by 100 points
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Set control of the symbol spread increase by 40 points
         symbol.SetControlSpreadInc(400);
         //--- Set control of the symbol spread decrease by 40 points
         symbol.SetControlSpreadDec(400);
         //--- Set control of the current spread by the value of 40 points
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Set controlled values for the current account
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Set control of the profit increase to 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Set control of the funds increase to 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Set profit control level to 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
  }
//+------------------------------------------------------------------+

Para poner a prueba el número mágico con valores aleatorios para los identificadores de los grupos, introducimos la variable booleana comp_magic igual a true, que indica el uso del número mágico compuesto en las funciones de apertura de posiciones o el establecimiento de órdenes pendientes. En lugar de usar la variable magic_number, introducimos la nueva variable magic, que guarda el valor del número mágico dependiendo del valor de la variable comp_magic.
Al establecer el valor magic (el número mágico constante establecido en los ajustes o el número mágico compuesto, que consta del número mágico establecido + los valores aleatorios de los identificadores de los grupos 1 y 2), comprobaremos la presencia de comp_magic, y si es igual a true, usaremos un número mágico compuesto, y si es igual a false
, el establecido en los ajustes.

Introducimos las correcciones en la función de procesamiento de la pulsación en el panel del asesor PressButtonEvents():

//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   bool comp_magic=true;   // Temporary variable selecting the composite magic number with random group IDs
   string comment="";
   //--- Convert button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Random group 1 and 2 numbers within the range of 0 - 15
   group1=(uchar)Rand();
   group2=(uchar)Rand();
   uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number);
   //--- If the button is pressed
   if(ButtonState(button_name))
     {
      //--- If the BUTT_BUY button is pressed: Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Open Buy position
         engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit);   // No comment - the default comment is to be set
        }
      //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Set BuyLimit order
         engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order"));
        }
      //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Set BuyStop order
         engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order"));
        }
      //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Set BuyStopLimit order
         engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order"));
        }
      //--- If the BUTT_SELL button is pressed: Open Sell position
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Open Sell position
         engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit);  // No comment - the default comment is to be set
        }
      //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Set SellLimit order
         engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order"));
        }
      //--- If the BUTT_SELL_STOP button is pressed: Set SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Set SellStop order
         engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order"));
        }
      //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Set SellStopLimit order
         engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order"));
        }
      //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit
      else if(button==EnumToString(BUTT_CLOSE_BUY))

Vamos a sustituir en todas las líneas de llamada de los métodos comerciales de la biblioteca la variable magic_number por la variable magic.

Para asignar un valor aleatorio a los identificadores de los grupos, les añadiremos el valor retornado por la función Rand(), donde ya se han establecido por defecto los valores mínimo y máximo del intervalo en el que la función retornará el número pseudoaleatorio:

//+------------------------------------------------------------------+
//| A random value within the range                                  |
//+------------------------------------------------------------------+
uint Rand(const uint min=0, const uint max=15)
  {
   return (rand() % (max+1-min))+min;
  }
//+------------------------------------------------------------------+

Compilamos e iniciamos el asesor. Desconectamos internet y esperamos a que aparezca este icono en la esquina inferior derecha:



Después de desconectar internet y pulsar el botón Sell, el servidor comercial retornará error, y en el diario se mostrarán las entradas:

2019.11.26 15:34:48.661 CTrading::OpenPosition<uint,uint>: Invalid request:
2019.11.26 15:34:48.661 No connection with the trade server
2019.11.26 15:34:48.661 Correction of trade request parameters ...
2019.11.26 15:34:48.661 Trading attempt #1. Error: No connection with the trade server

Al obtener este error, la biblioteca crea una solicitud pendiente con los parámetros existentes en el intento fallido de apertura de una posición corta.
En la solicitud pendiente también está registrado el número de intentos y un tiempo de espera de 20 segundos.

A continuación, conectamos internet, reestableciendo con ello la conexión con el servidor comercial:


En cuanto se reestablezca, la biblioteca comenzará a procesar la solicitud pendiente, enviando esta al servidor. Como resultado, tendremos una posición abierta con las siguientes entradas en el diario:

2019.11.26 15:35:00.853 CTrading::OpenPosition<double,double>: Invalid request:
2019.11.26 15:35:00.853 Trading is prohibited for the current account
2019.11.26 15:35:00.853 Correction of trade request parameters ...
2019.11.26 15:35:00.853 Trading operation aborted
2019.11.26 15:35:01.192 CTrading::OpenPosition<double,double>: Invalid request:
2019.11.26 15:35:01.192 Trading is prohibited for the current account
2019.11.26 15:35:01.192 Correction of trade request parameters ...
2019.11.26 15:35:01.192 Trading operation aborted
2019.11.26 15:35:01.942 - Position is open: 2019.11.26 10:35:01.660 -
2019.11.26 15:35:01.942 EURUSD Opened 0.10 Sell #486405595 [0.10 Market-order Sell #486405595] at price 1.10126, sl 1.10285, tp 1.09985, Magic number 17629307 (123), G1: 13
2019.11.26 15:35:01.942 OnDoEasyEvent: Position is open

Como podemos ver por el diario, después de reestablecer la conexión con el servidor comercial, el permiso de comercio para la cuenta actual no se ha activado de inmediato.
Pero, aun así, la solicitud comercial ha cumplido su parte
...

En el propio diario, podemos ver el número mágico real 17629307, tras este, entre paréntesis, vemos el número mágico establecido en los ajustes del asesor (123), además de la entrada G1: 13, que nos indica que el identificador del nuevo grupo es igual a 13; en cuanto al identificador del segundo grupo, no lo hay, ya que su valor es igual a cero, por eso no se ha mostrado una segunda entrada con el identificador del segunndo grupo G2: XX

Por favor, tenga en cuenta:

¡Insistimos en que los resultados del trabajo con la clase comercial con solicitudes pendientes que hemos descrito en el presente artículo no se deben utilizar en ningún caso en proyectos propios para el comercio real!
Este artículo, así como sus materiales y resultados, constituyen solo una comprobación del concepto de las solicitudes pendientes y, en el presente estadio, no suponen de forma alguna un producto definitivo a usar en cuentas reales,
sino exclusivamente en el modo demo o el simulador.

¿Qué es lo próximo?

En los siguientes artículos, continuaremos desarrollando la clase de solicitud pendiente.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. El lector podrá descargar y poner a prueba todo por sí mismo.
Si tiene cualquier duda, observación o sugerencia, podrá formularla en los comentarios al artículo.

Volver al contenido

Artículos de esta serie:

Parte 1. Concepto, organización de datos
sParte 2. Colección de órdenes y transacciones históricas
Parte 3. Colección de órdenes y posiciones de mercado, organización de la búsqueda
Parte 4. Eventos comerciales. Concepto
Parte 5. Clases y concepto de los eventos comerciales. Envío de eventos al programa
Parte 6. Eventos en las cuentas de compensación
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para los eventos de modificación de órdenes y posiciones
Parte 8. Eventos de modificación de órdenes y posiciones
Parte 9. Compatibilidad con MQL4 - Preparando los datos
Parte 10. Compatibilidad con MQL4 - Eventos de apertura de posición y activación de órdenes pendientes
Parte 11. Compatibilidad con MQL4 - Eventos de cierre de posiciones
Parte 12. Implementando la clase de objeto "cuenta" y la colección de objetos de cuenta
Parte 13. Eventos del objeto "cuenta"
Parte 14. El objeto "Símbolo"
Parte 15. Colección de objetos de símbolo
Parte 16. Eventos de la colección de símbolos
Parte 17. Interactividad de los objetos de la biblioteca
Parte 18. Interactividad del objeto de cuenta con cualquier otro objeto de la biblioteca
Parte 19. Clase de mensajes de la biblioteca
Parte 20. Creación y guardado de los recursos del programa
Parte 21. Clases comerciales - El objeto comercial multiplataforma básico
Parte 22. Clases comerciales - Clase comercial principal, control de limitaciones
Parte 23. Clase comercial principal - Control de parámetros permitidos
Parte 24. Clase comercial principal - corrección automática de parámetros erróneos
Parte 25. Procesando los errores retornados por el servidor comercial

Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/7394

Archivos adjuntos |
MQL5.zip (3609.2 KB)
MQL4.zip (3609.19 KB)
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXV): Procesando los errores retornados por el servidor comercial Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXV): Procesando los errores retornados por el servidor comercial

Después de enviar una orden comercial al servidor, no deberíamos pensar que "ya está todo hecho", ya que ahora tendremos que comprobar los códigos de error, o bien la ausencia de los mismos. En el presente artículo, vamos a implementar el procesamiento de los errores retornados por el servidor comercial, preparando asimismo la base para crear solicitudes comerciales pendientes.

Trabajando con las funciones de red, o MySQL sin DLL: Parte I - el conector Trabajando con las funciones de red, o MySQL sin DLL: Parte I - el conector

Hace relativamente poco, aparecieron en MetaTrader 5 las funciones de red. Este hecho abre un amplio abanico de posibilidades para los programadores que desarrollan productos para el Mercado, ya que ahora es posible implementar aquello que antes no se podía conseguir sin bibliotecas dinámicas. En este artículo, nos familiarizaremos con ellas usando como ejemplo la escritura de un conector MySQL.

Implementando OLAP en la negociación (Parte 3): analizando las cotizaciones con el fin de desarrollar las estrategias comerciales Implementando OLAP en la negociación (Parte 3): analizando las cotizaciones con el fin de desarrollar las estrategias comerciales

En este artículo, continuaremos analizando la tecnología OLAP en aplicación al trading, ampliando la funcionalidad representada en dos artículos anteriores. Esta vez, al análisis operativo se le someterán las cotizaciones. Mostraremos cómo se hacen y se comprueban las hipótesis sobre las estrategias comerciales a base de los indicadores agregados del historial. Además, presentaremos los Asesores Expertos para analizar las regularidades barra por barra y el trading adaptativo.

Monitoreo multidivisas de las señales comerciales (Parte 2): Implementando la parte visual de la aplicación Monitoreo multidivisas de las señales comerciales (Parte 2): Implementando la parte visual de la aplicación

En el artículo anterior, creamos la plantilla de la aplicación en la que se basará todo nuestro trabajo siguiente. Ahora, pasaremos paso a paso a su desarrollo, es decir, diseñaremos la parte visual de la aplicación y configuraremos las interacciones básicas entre los controles de la interfaz.