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)
Artyom Trishkin | 7 febrero, 2020
Contenido
- Concepto
- El número mágico como repositorio de datos
- La clase de solicitud pendiente, primera implementación de las solicitudes
- Simulación
- ¿Qué es lo próximo?
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 restablecer 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 restablecer 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, restableciendo con ello la conexión con el servidor comercial:
En cuanto se restablezca, 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 restablecer 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.
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