Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte VII): Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para los eventos de modificación de órdenes y posiciones
Contenido
Concepto
En las anteriores partes de la descripción de la biblioteca multiplataforma para MetaTrader
5 y MetaTrader 4, preparamos el instrumental necesario para crear funciones de caso de usuario que ofrezcan acceso rápido desde sus
programas a cualquier dato de cualquier orden y posición en las cuentas de cobertura y compensación. Se trata de funciones para monitorear
los eventos relacionados con las órdenes y posiciones: la colocación, eliminación y activación de órdenes pendientes, la apertura y
cierre de posiciones.
Pero, por el momento, se ha quedado sin implementar la funcionalidad para monitorear la activación de las órdenes StopLimit colocadas y
la modificación de órdenes y posiciones de mercado.
En este artículo, implementaremos el seguimiento del evento de activación de una orden StopLimit, cuyo resultado será la
colocación de una orden Limit.
La biblioteca monitoreará estos eventos y enviará al programa los mensajes necesarios para su posterior uso en el mismo.
Implementación
Al simular los momentos de activación de las órdenes StopLimit colocadas, hemos notado que este evento no se representa de ninguna forma en
la historia de la cuenta en el terminal, y no podremos simplemente recibirlo de la historia de la cuenta "as is". Por eso, deberemos
monitorear el estado de las órdenes existentes hasta el momento en que cambie este estado (en nuestro caso, el cambio del tipo de orden
colocada con el mismo ticket).
Vamos a enfocar el seguimiento de la activación de órdenes StopLimit desde un punto de vista práctico: aparte de crear una nueva funcionalidad,
la haremos apropiada para el seguimiento de los demás eventos de cambio en las órdenes y posiciones existentes (cambio del precio de
colocación de órdenes pendientes existentes, así como los niveles de StopLoss y TakeProfit de estos mismos niveles de la posición
abierta).
La lógica de la funcionalidad a preparar será la siguiente:
Tenemos acceso a la lista completa de todas las órdenes y posiciones activas en la cuenta. De esta lista también podremos obtener el estado actual
de cada una de las propiedades de estos objetos. Para monitorear los cambios de las propiedades controladas, necesitaremos tener una lista
adicional, en la que se registrará el estado "pasado" de las propiedades del objeto, que inicialmente será igual al actual.
Al comparar las propiedades de los objetos de estas dos listas, en cuanto veamos una diferencia en cualquiera de las propiedades
controladas, entenderemos que el cambio es un hecho, por lo que crearemos de inmediato un objeto modificado, en el que registraremos tanto
la propiedad pasada como la nueva, es decir, la cambiada, colocando acto seguido este objeto en la nueva lista: "la lista de objetos
modificados".
Esta nueva lista la procesaremos en lo sucesivo en la clase que monitorea los eventos en la cuenta.
Claro que
podemos enviar el evento en cuanto observemos algún cambio en las propiedades del objeto, pero entonces podríamos encontrarnos con la
situación en la que varios objetos han cambiado en un solo tick. Y si procesamos los cambios directamente, podremos procesar solo el cambio
del último objeto del paquete. Esto no nos conviene en absoluto. Por ello, en esta etapa crearemos una lista con todos los objetos cambiados y
comprobaremos el tamaño de esta lista en la clase de procesamiento de eventos. Y ya en ella, procesaremos en el ciclo cada objeto modificado
de la lista de objetos cambiados. De esta forma, no omitiremos ni uno solo de los eventos de cambio de las propiedades de las órdenes y
posiciones acontecidos al mismo tiempo en la cuenta.
En la tercera parte de la descripción de la biblioteca, al crear la colección de órdenes y posiciones de mercado, lo hiciemos considerando que para monitorear el estado actual de las órdenes y posiciones, necesitábamos actualizar la lista y guardar la suma hash actual y pasada, calculada como el ticket + la hora de cambio de la posición en milisegundos y el volumen. Pero, para monitorear el cambio en los estados de las propiedades de las órdenes y posiciones, estos datos para el cálculo de la suma hash no serán suficientes.
- Para registrar el cambio de precio de la colocación de una orden, deberemos tener en cuenta este precio
- Para registrar el cambio de precio del StopLoss y TakeProfit, deberemos tener en cuenta también estos precios.
Estos significa que vamos a añadir los tres precios a la suma hash, pero transformando cada uno en una cifra ulong de siete dígitos: solo
tenemos que quitar la coma y aumentar en un solo orden la capacidad del número (para registrar las cotizaciones de seis dígitos). Por
ejemplo, si el precio es igual a 1.12345, la cifra de la suma hash será 1123450.
Vamos a terminar con la teoría y proceder a la implementación.
Añadimos al archivo Defines.mqh las enumeraciones con las banderas de las posibles variantes de cambio de las propiedades de
las órdenes y posiciones, así como las propias variantes que vamos a monitorear:
//+------------------------------------------------------------------+ //| List of flags of possible order and position change options | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE_FLAGS { CHANGE_TYPE_FLAG_NO_CHANGE = 0, // No changes CHANGE_TYPE_FLAG_TYPE = 1, // Order type change CHANGE_TYPE_FLAG_PRICE = 2, // Price change CHANGE_TYPE_FLAG_STOP = 4, // StopLoss change CHANGE_TYPE_FLAG_TAKE = 8, // TakeProfit change CHANGE_TYPE_FLAG_ORDER = 16 // Order properties change flag }; //+------------------------------------------------------------------+ //| Possible order and position change options | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE { CHANGE_TYPE_NO_CHANGE, // No changes CHANGE_TYPE_ORDER_TYPE, // Order type change CHANGE_TYPE_ORDER_PRICE, // Order price change CHANGE_TYPE_ORDER_PRICE_STOP_LOSS, // Order and StopLoss price change CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT, // Order and TakeProfit price change CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Order, StopLoss and TakeProfit price change CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT, // StopLoss and TakeProfit change CHANGE_TYPE_ORDER_STOP_LOSS, // Order's StopLoss change CHANGE_TYPE_ORDER_TAKE_PROFIT, // Order's TakeProfit change CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT, // Change position's StopLoss and TakeProfit CHANGE_TYPE_POSITION_STOP_LOSS, // Change position's StopLoss CHANGE_TYPE_POSITION_TAKE_PROFIT, // Change position's TakeProfit }; //+------------------------------------------------------------------+
Con respecto a las banderas de las posibles variantes de cambio de las propiedades de las órdenes y posiciones:
- la bandera de cambio del tipo de orden se establecerá al
activarse una orden StopLimit,
- la bandera de cambio del precio se establecerá al
modificarse el precio de colocación de una orden pendiente,
- la bandera de cambio stoploss y takeprofit
se entiende,
- la bandera de orden sirve para identificar el cambio de
propiedades de la orden (no de la posición)
En la enumeración de las posibles variantes de modificación de las órdenes y
posiciones se han reunido todas las variantes que vamos a monitorear en el futuro. Hoy vamos a implementar solo el seguimiento del evento
de activación de una orden StopLimit (CHANGE_TYPE_ORDER_TYPE).
Añadimos a la enumeración de la lista de posibles eventos comerciales en la cuenta ENUM_TRADE_EVENT los ocho nuevos eventos que se enviarán al programa al darse su identificación:
//+------------------------------------------------------------------+ //| List of possible trading events on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT = 0, // No trading event TRADE_EVENT_PENDING_ORDER_PLASED, // Pending order placed TRADE_EVENT_PENDING_ORDER_REMOVED, // Pending order removed //--- enumeration members matching the ENUM_DEAL_TYPE enumeration members //--- (constant order below should not be changed, no constants should be added/deleted) TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT, // Accruing credit (3) TRADE_EVENT_ACCOUNT_CHARGE, // Additional charges TRADE_EVENT_ACCOUNT_CORRECTION, // Correcting entry TRADE_EVENT_ACCOUNT_BONUS, // Accruing bonuses TRADE_EVENT_ACCOUNT_COMISSION, // Additional commissions TRADE_EVENT_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month TRADE_EVENT_ACCOUNT_INTEREST, // Accrued interest on free funds TRADE_EVENT_BUY_CANCELLED, // Canceled buy deal TRADE_EVENT_SELL_CANCELLED, // Canceled sell deal TRADE_EVENT_DIVIDENT, // Accruing dividends TRADE_EVENT_DIVIDENT_FRANKED, // Accruing franked dividends TRADE_EVENT_TAX = DEAL_TAX, // Tax //--- constants related to the DEAL_TYPE_BALANCE deal type from the DEAL_TYPE_BALANCE enumeration TRADE_EVENT_ACCOUNT_BALANCE_REFILL = DEAL_TAX+1, // Replenishing account balance TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+2, // Withdrawing funds from an account //--- Remaining possible trading events //--- (constant order below can be changed, constants can be added/deleted) TRADE_EVENT_PENDING_ORDER_ACTIVATED = DEAL_TAX+3, // Pending order activated by price TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL, // Pending order partially activated by price TRADE_EVENT_POSITION_OPENED, // Position opened TRADE_EVENT_POSITION_OPENED_PARTIAL, // Position opened partially TRADE_EVENT_POSITION_CLOSED, // Position closed TRADE_EVENT_POSITION_CLOSED_BY_POS, // Position closed partially TRADE_EVENT_POSITION_CLOSED_BY_SL, // Position closed by StopLoss TRADE_EVENT_POSITION_CLOSED_BY_TP, // Position closed by TakeProfit TRADE_EVENT_POSITION_REVERSED_BY_MARKET, // Position reversal by a new deal (netting) TRADE_EVENT_POSITION_REVERSED_BY_PENDING, // Position reversal by activating a pending order (netting) TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL, // Position reversal by partial market order execution (netting) TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL, // Position reversal by partial pending order activation (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET, // Added volume to a position by a new deal (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL, // Added volume to a position by partial activation of an order (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING, // Added volume to a position by activating a pending order (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL, // Added volume to a position by partial activation of a pending order (netting) TRADE_EVENT_POSITION_CLOSED_PARTIAL, // Position closed partially TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS, // Position closed partially by an opposite one TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL, // Position closed partially by StopLoss TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP, // Position closed partially by TakeProfit TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER, // StopLimit order activation TRADE_EVENT_MODIFY_ORDER_PRICE, // Changing order price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS, // Changing order and StopLoss price TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT, // Changing order and TakeProfit price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Changing order, StopLoss and TakeProfit price TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT, // Changing order's StopLoss and TakeProfit price TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, // Changing position StopLoss TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, // Changing position TakeProfit };
Y finalmente, añadimos a la lista de enumeraciones de los motivos de los eventos ENUM_EVENT_REASON una nueva constante que describe la activación de una orden StopLimit:
//+------------------------------------------------------------------+ //| Event reason | //+------------------------------------------------------------------+ enum ENUM_EVENT_REASON { EVENT_REASON_REVERSE, // Position reversal (netting) EVENT_REASON_REVERSE_PARTIALLY, // Position reversal by partial request execution (netting) EVENT_REASON_REVERSE_BY_PENDING, // Position reversal by pending order activation (netting) EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY, // Position reversal in case of a pending order partial execution (netting) //--- All constants related to a position reversal should be located in the above list EVENT_REASON_ACTIVATED_PENDING, // Pending order activation EVENT_REASON_ACTIVATED_PENDING_PARTIALLY, // Pending order partial activation EVENT_REASON_STOPLIMIT_TRIGGERED, // StopLimit order activation EVENT_REASON_CANCEL, // Cancelation EVENT_REASON_EXPIRED, // Order expiration EVENT_REASON_DONE, // Request executed in full EVENT_REASON_DONE_PARTIALLY, // Request executed partially EVENT_REASON_VOLUME_ADD, // Add volume to a position (netting) EVENT_REASON_VOLUME_ADD_PARTIALLY, // Add volume to a position by a partial request execution (netting) EVENT_REASON_VOLUME_ADD_BY_PENDING, // Add volume to a position when a pending order is activated (netting) EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY, // Add volume to a position when a pending order is partially executed (netting) EVENT_REASON_DONE_SL, // Closing by StopLoss EVENT_REASON_DONE_SL_PARTIALLY, // Partial closing by StopLoss EVENT_REASON_DONE_TP, // Closing by TakeProfit EVENT_REASON_DONE_TP_PARTIALLY, // Partial closing by TakeProfit EVENT_REASON_DONE_BY_POS, // Closing by an opposite position EVENT_REASON_DONE_PARTIALLY_BY_POS, // Partial closing by an opposite position EVENT_REASON_DONE_BY_POS_PARTIALLY, // Closing an opposite position by a partial volume EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY, // Partial closing of an opposite position by a partial volume //--- Constants related to DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration EVENT_REASON_BALANCE_REFILL, // Refilling the balance EVENT_REASON_BALANCE_WITHDRAWAL, // Withdrawing funds from the account //--- List of constants is relevant to TRADE_EVENT_ACCOUNT_CREDIT from the ENUM_TRADE_EVENT enumeration and shifted to +13 relative to ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3) EVENT_REASON_ACCOUNT_CREDIT, // Accruing credit EVENT_REASON_ACCOUNT_CHARGE, // Additional charges EVENT_REASON_ACCOUNT_CORRECTION, // Correcting entry EVENT_REASON_ACCOUNT_BONUS, // Accruing bonuses EVENT_REASON_ACCOUNT_COMISSION, // Additional commissions EVENT_REASON_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month EVENT_REASON_ACCOUNT_INTEREST, // Accruing interest on free funds EVENT_REASON_BUY_CANCELLED, // Canceled buy deal EVENT_REASON_SELL_CANCELLED, // Canceled sell deal EVENT_REASON_DIVIDENT, // Accruing dividends EVENT_REASON_DIVIDENT_FRANKED, // Accruing franked dividends EVENT_REASON_TAX // Tax }; #define REASON_EVENT_SHIFT (EVENT_REASON_ACCOUNT_CREDIT-3)
Ya hemos realizado todos los cambios en el archivo Defines.mqh.
Dado que antes acordamos que vamos a crear y guardar la lista de órdenes de control, en esta lista deberemos guardar los objetos con el conjunto mínimo suficiente de propiedades para determinar el momento de cambio de uno de los objetos de orden y posición de mercado.
Vamos a crear la clase objeto de control de la orden.
Creamos en la carpeta de la biblioteca Collections la nueva clase con el nombre de archivo OrderControl.mqh. Hacemos clase básica a la clase de la biblioteca estándar CObject e incluimos los archivos necesarios para el funcionamiento de la clase:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: public: COrderControl(); ~COrderControl(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::COrderControl() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::~COrderControl() { } //+------------------------------------------------------------------+
Declaramos directamente todas las variables y métodos necesarios en la sección privada de la clase:
private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Return the order parameters change type void CalculateChangedType(void);
Todas las variables-miembro de clase tienen descripciones comprensibles. Vamos a realizar ciertas aclaraciones sobre la variable que guarda la estructura del tick: al activarse una orden StopLimit, necesitaremos registrar la hora de activación. Dado que necesitamos registrarla en milisegundos, y TimeCurrent() retorna la hora sin milisegundos, para obtener la hora en milisegundos del último tick en el que se ha activado la orden, usaremosla función estándar SymbolInfoTick(), que rellena la estructura del tick con datos entre los cuales figura la hora del tcik en milisegundos.
El código de cambio de la orden se compondrá de las banderas que
describimos en la enumeración ENUM_CHANGE_TYPE_FLAGS, y dependerá de los cambios sucedidos en las propiedades de la orden. El método
privado CalculateChangedType() se encargará de comprobar y crear el código de cambio de la orden. Lo veremos más abajo.
En la sección pública de la clase, ubicaremos los métodos de obtención y registro de los datos sobre el estado pasado y actual de las
propiedades de la orden de control,
el método que establece el tipo del cambio sucedido en las propiedades de la orden,
el método que establece el nuevo estado de la orden modificada,
el método que retorna el tipo del cambio sucedido y el
método que comprueba el cambio en las propiedades de la orden, que además establece y retorna el tipo de cambio sucedido. Este método
se llamará desde la clase de colección de las órdenes y posiciones de mercado, para determinar el hecho del cambio de las órdenes y posiciones
activas.
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Order and position control class | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Calculate the order parameters change type void CalculateChangedType(void); public: //--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss, //--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Set (1) change type, (2) new current status void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Check and set order parameters change flags and return the change type ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price, //--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Return the change type ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Constructor COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+
Vamos a transmitir al constructor de la clase el identificador de la posición, el ticket, el número mágico y el símbolo de la orden/posición. Reseateamos en su lista de inicialización las banderas de cambio de la orden y el tipo de cambio sucedido; asimismo, registramos de inmediato los datos obtenidos en los parámetros transmitidos sobre la orden/posición en las variables-miembros de clase correspondientes.
Implementamos los métodos declarados fuera del cuerpo de la clase.
Método privado que calcula el tipo de cambio de los
parámetros de la orden/posición:
//+------------------------------------------------------------------+ //| Calculate order parameters change type | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- If the order flag is set this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- If StopLimit order is activated this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- If an order price is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- If StopLoss and TakeProfit are modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- If StopLoss modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Only order price is modified CHANGE_TYPE_ORDER_PRICE ) : //--- Price is not modified //--- If StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- If StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ) : //--- Position //--- If position's StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- If position's TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- If position's StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+
Este método registra en la varible-miembro de clase m_changed_type el tipo de cambio sucedido desde la enumeración
ENUM_CHANGE_TYPE (que hemos declarado anteriormente), dependiendo de la presencia de banderas entre los componentes de la variable
m_change_code.
Todas las acciones de comprobación de banderas están descritas en los comentarios a las líneas del listado del
método, y deberían ser comprensibles.
Un método privado se encarga de la comprobación de la presencia de banderas en la variable m_change_code
bool IsPresentChangeFlag(const int change_flag const { return (this.m_change_code & change_flag)==change_flag }
La bandera comprobada se transmite al método. Luego se comprueba su presencia en m_change_code bit a bit con
una operación Y, y se retorna el resultado booleano de la comparación (la operación bit a bit entre el valor del código y el valor de la
bandera) con el valor de la bandera comprobada.
Método que establece el nuevo estado actual de las propiedades de la orden/posición:
//+------------------------------------------------------------------+ //| Set the new relevant status | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- New type this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- New price this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- New StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- New TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- New time this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
Transmitimos al método el puntero a la orden/posición en la que ha sucedido
el cambio de una de sus propiedades.
Dado que después de determinar que se ha sucedido un cambio en una de las propiedades de la
orden o posición, debemos comprobar este nuevo estado para comprobaciones posteriores, el método simplemente
guarda en primer lugar su estado actual de la propiedad como el estado pasado, y
después
registra en su estado actual el valor de esta propiedad de la orden transmitida al método.
Al
guardar la hora del evento sucedido, usamos la función estándar SymbolInfoTick()
para obtener la hora del ticket en milisegundos.
Método principal llamado desde la clase CMarketCollection, y encargado de determinar los cambios sucedidos:
//+------------------------------------------------------------------+ //| Check and set order parameters change flags | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+
El método recibe el puntero a la orden/posición comprobada y se inicializa el código de cambio. Si se ha transmitido un objeto vacío de la orden comparada, o si su ticket no es igual al ticket de la orden de control actual, retornamos el código de ausencia de cambios.
A continuación, comprobamos si hay alguna falta de coincidencia en las propiedades monitoreadas de la orden de control y la comprobada, y
si se detecta alguna, añadimos al código de cambio una bandera que describa dicho cambio.
Acto seguido, calculamos respecto al código de cambio completamente formado el
tipo de cambio en el método CalculateChangedType() y lo retornamos al
programa que ha realizado la llamada mediante el método GetChangeType().
Aquí tenemos el listado completo de la orden de control:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Order and position control class | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Calculate the order parameters change type void CalculateChangedType(void); public: //--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss, //--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Set (1) change type, (2) new current status void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Check and set order parameters change flags and return the change type ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price, //--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Return the change type ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Constructor COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+ //| Check and set the order parameters change flags | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+ //| Calculate the order parameters change type | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- If the order flag is set this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- If StopLimit order is activated this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- If an order price is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- If StopLoss and TakeProfit are modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- If StopLoss modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Only order price is modified CHANGE_TYPE_ORDER_PRICE ) : //--- Price is not modified //--- If StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- If StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ) : //--- Position //--- If position's StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- If position's TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- If position's StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+ //| Set the new relevant status | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- New type this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- New price this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- New StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- New TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- New time this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
Vamos a mejorar la clase de colección de órdenes y posiciones de mercado CMarketCollection.
Necesitamos monitorear los cambios de las propiedades que hayan sucedido en las órdenes o posiciones activas. Dado que precisamente en esta clase obtenemos todas las órdenes y posiciones de mercado, lo lógico será comprobar su modificación en ella misma.
Vamos a incluir el archivo de clase de la orden de control.
En la sección privada de la clase, declaramos
la lista para guardar las órdenes y posiciones de control y la
lista para guardar las órdenes y posiciones modificadas, la
variable-miembro de clase para guardar el tipo de cambio de la orden y la
variable para guardar el coeficiente para el cálculo de la conversión del valor del precio al valor de la suma hash.
Y
declaramos los métodos privados:
un método para convertir las propiedades de una orden en la cifra de la suma
hash, un método que añade una orden o posición a la lista de órdenes
pendientes y posiciones en la cuenta, un método que crea y añade una orden
de control a la lista de órdenes de control, así como un método que crea y
añade las órdenes cambiadas a la lista de órdenes modificadas, un método
para eliminar de la lista de órdenes de control una orden según el ticket y el identificador de la posición, un
método que retorna el índice de una orden de control en la lista de órdenes de control según el ticket y el identificador de la posición,
y
un manejador de eventos de cambio de una orden/posición existente.
En la sección pública de la clase, declaramos un método que retorna la
lista creada de órdenes modificadas.
//+------------------------------------------------------------------+ //| MarketCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\MarketOrder.mqh" #include "..\Objects\Orders\MarketPending.mqh" #include "..\Objects\Orders\MarketPosition.mqh" #include "OrderControl.mqh" //+------------------------------------------------------------------+ //| Collection of market orders and positions | //+------------------------------------------------------------------+ class CMarketCollection : public CListObj { private: struct MqlDataCollection { ulong hash_sum_acc; // Hash sum of all orders and positions on the account int total_market; // Number of market orders on the account int total_pending; // Number of pending orders on the account int total_positions; // Number of positions on the account double total_volumes; // Total volume of orders and positions on the account }; MqlDataCollection m_struct_curr_market; // Current data on market orders and positions on the account MqlDataCollection m_struct_prev_market; // Previous data on market orders and positions on the account CListObj m_list_all_orders; // List of pending orders and positions on the account CArrayObj m_list_control; // List of control orders CArrayObj m_list_changed; // List of changed orders COrder m_order_instance; // Order object for searching by property ENUM_CHANGE_TYPE m_change_type; // Order change type bool m_is_trade_event; // Trading event flag bool m_is_change_volume; // Total volume change flag double m_change_volume_value; // Total volume change value ulong m_k_pow; // Ratio for converting the price into a hash sum int m_new_market_orders; // Number of new market orders int m_new_positions; // Number of new positions int m_new_pendings; // Number of new pending orders //--- Save the current values of the account data status as previous ones void SavePrevValues(void) { this.m_struct_prev_market=this.m_struct_curr_market; } //--- Convert order data into a hash sum value ulong ConvertToHS(COrder* order) const; //--- Add an order or a position to the list of pending orders and positions on an account and sets the data on market orders and positions on the account bool AddToListMarket(COrder* order); //--- (1) Create and add a control order to the list of control orders, (2) a control order to the list of changed control orders bool AddToListControl(COrder* order); bool AddToListChanges(COrderControl* order_control); //--- Remove an order by a ticket or a position ID from the list of control orders bool DeleteOrderFromListControl(const ulong ticket,const ulong id); //--- Return the control order index in the list by a position ticket and ID int IndexControlOrder(const ulong ticket,const ulong id); //--- Handler of an existing order/position change event void OnChangeEvent(COrder* order,const int index); public: //--- Return the list of (1) all pending orders and open positions, (2) modified orders and positions CArrayObj* GetList(void) { return &this.m_list_all_orders; } CArrayObj* GetListChanges(void) { return &this.m_list_changed; } //--- Return the list of orders and positions with an open time from begin_time to end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj* GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } //--- Return the number of (1) new market order, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume int NewMarketOrders(void) const { return this.m_new_market_orders; } int NewPendingOrders(void) const { return this.m_new_pendings; } int NewPositions(void) const { return this.m_new_positions; } bool IsTradeEvent(void) const { return this.m_is_trade_event; } double ChangedVolumeValue(void) const { return this.m_change_volume_value; } //--- Constructor CMarketCollection(void); //--- Update the list of pending orders and positions void Refresh(void); }; //+------------------------------------------------------------------+
En el constructor de la clase, añadimos la limpieza y clasificación de dos listas: la
lista de órdenes de control y la lista de órdenes modificadas,
así como
el cálculo del coeficiente para el cálculo de la suma hash:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0) { this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN); this.m_list_all_orders.Clear(); ::ZeroMemory(this.m_struct_prev_market); this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE; this.m_list_all_orders.Type(COLLECTION_MARKET_ID); this.m_list_control.Clear(); this.m_list_control.Sort(); this.m_list_changed.Clear(); this.m_list_changed.Sort(); this.m_k_pow=(ulong)pow(10,6); } //+------------------------------------------------------------------+
Método para la conversión de las propiedades de una orden para el cálculo de la suma hash:
//+---------------------------------------------------------------------+ //| Convert the order price and its type into a number for the hash sum | //+---------------------------------------------------------------------+ ulong CMarketCollection::ConvertToHS(COrder *order) const { if(order==NULL) return 0; ulong price=ulong(order.PriceOpen()*this.m_k_pow); ulong stop=ulong(order.StopLoss()*this.m_k_pow); ulong take=ulong(order.TakeProfit()*this.m_k_pow); ulong type=order.TypeOrder(); ulong ticket=order.Ticket(); return price+stop+take+type+ticket; } //+------------------------------------------------------------------+
Transmitimos al método el puntero a la orden cuyos datos se
debe transformar en una cifra. A conitnuación, las propiedades double de la orden se convierten en una cifra para la suma hash mediante
una multiplicación simple por el coeficiente anteriormente calculado en el
constructor de clase; todos los valores de las propiedades se suman y se
retornan como una cifra ulong.
Los cambios se relacionan con la adición de objetos a la lista de órdenes y posiciones: ahora estas líneas similares se ubican en un mismo método, AddToListMarket(). Después de añadir un objeto de orden a la lista de órdenes y posiciones, comprobamos la presencia de esta misma orden en la lista de órdenes de control, y si no se encuentra allí, se crea un objeto de orden de control y se añade a la lista de órdenes de control con la ayuda del método AddToListControl(). Si la comprobación de la presencia de una orden de control muestra que esta existe, se llama el método de comparación de las propiedades de la orden actual con las propiedades de OnChangeEvent() de control.
En el listado del método, en los comentarios a las líneas, se describen todas las acciones realizadas; estas también han
sido resaltadas en el texto.
//+------------------------------------------------------------------+ //| Update the list of orders | //+------------------------------------------------------------------+ void CMarketCollection::Refresh(void) { ::ZeroMemory(this.m_struct_curr_market); this.m_is_trade_event=false; this.m_is_change_volume=false; this.m_new_pendings=0; this.m_new_positions=0; this.m_change_volume_value=0; this.m_list_all_orders.Clear(); #ifdef __MQL4__ int total=::OrdersTotal(); for(int i=0; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS)) continue; long ticket=::OrderTicket(); //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket); ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL) { CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Add a position object to the list of market orders and positions if(!this.AddToListMarket(position)) continue; //--- If there is no order in the list of control orders and positions, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Add a pending order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; //--- If there is no order in the list of control orders and positions, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } //--- MQ5 #else //--- Positions int total_positions=::PositionsTotal(); for(int i=0; i<total_positions; i++) { ulong ticket=::PositionGetTicket(i); if(ticket==0) continue; CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Add a position object to the list of market orders and positions if(!this.AddToListMarket(position)) continue; //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket,position.PositionID()); //--- If the order is not present in the list of control orders, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(position)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add control position "),position.TypeDescription()," #",position.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties else if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } //--- Orders int total_orders=::OrdersTotal(); for(int i=0; i<total_orders; i++) { ulong ticket=::OrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE); //--- Market order if(type<ORDER_TYPE_BUY_LIMIT) { CMarketOrder *order=new CMarketOrder(ticket); if(order==NULL) continue; //--- Add a market order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; } //--- Pending order else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Add a pending order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket,order.PositionID()); //--- If the order is not present in the control order list, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already in the control order list, check it for changed properties else if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } #endif //--- First launch if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE) { this.SavePrevValues(); } //--- If the hash sum of all orders and positions changed if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc) { this.m_new_market_orders=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market; this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending; this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions; this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4); this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false); this.m_is_trade_event=true; this.SavePrevValues(); } } //+------------------------------------------------------------------+
Método para añadir una orden o posición a la lista de órdenes y posiciones de mercado de la colección:
//+--------------------------------------------------------------------------------+ //| Add an order or a position to the list of orders and positions on the account | //+--------------------------------------------------------------------------------+ bool CMarketCollection::AddToListMarket(COrder *order) { if(order==NULL) return false; ENUM_ORDER_STATUS status=order.Status(); if(this.m_list_all_orders.InsertSort(order)) { if(status==ORDER_STATUS_MARKET_POSITION) { this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE_MSC)+this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_positions++; return true; } if(status==ORDER_STATUS_MARKET_PENDING) { this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_pending++; return true; } } else { ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to list")); delete order; } return false; } //+------------------------------------------------------------------+
Transmitimos al método el puntero a la colección de órdenes añadida a la lista. Después de añadir una orden a la lista de colección, dependiendo del estado de la orden, modificamos los datos de la estructura que guarda el estado actual de las órdenes y posiciones de mercado para la posterior comprobación y determinación del cambio en el número de las órdenes y posiciones.
- Si se trata de una posición, añadimos a la suma hash total la hora de cambio de la posición y el número calculado para la suma hash; asimismo, aumentamos el valor del número total de posiciones.
- Si se trata de una orden pendiente, añadimos a la suma hash total el número calculado para la suma hash y aumentamos el valor del número total de órdenes pendientes.
Método que crea una orden de control y la añade a la lista de órdenes de control:
//+------------------------------------------------------------------+ //| Create and add an order to the list of control orders | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListControl(COrder *order) { if(order==NULL) return false; COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol()); if(order_control==NULL) return false; order_control.SetTime(order.TimeOpenMSC()); order_control.SetTimePrev(order.TimeOpenMSC()); order_control.SetVolume(order.Volume()); order_control.SetTime(order.TimeOpenMSC()); order_control.SetTypeOrder(order.TypeOrder()); order_control.SetTypeOrderPrev(order.TypeOrder()); order_control.SetPrice(order.PriceOpen()); order_control.SetPricePrev(order.PriceOpen()); order_control.SetStopLoss(order.StopLoss()); order_control.SetStopLossPrev(order.StopLoss()); order_control.SetTakeProfit(order.TakeProfit()); order_control.SetTakeProfitPrev(order.TakeProfit()); if(!this.m_list_control.Add(order_control)) { delete order_control; return false; } return true; } //+------------------------------------------------------------------+
Transmitimos al método el puntero a una orden o posición de mercado. Si
se ha tansmitido un objeto no válido, retornaremos
false.
A continuación, creamos
una nueva orden de control a cuyo constructor transmitimos de inmediato el identificador de la posición, el ticket, el número
mágico y el símbolo del objeto de orden transmitido al método; después
rellenamos todos los datos del mismo que necesitamos para identificar la modificación de la
orden o posición.
Si no hemos logrado añadir la orden de control a
la lista de órdenes de control, la orden será eliminada y se retornará false.
Dado que añadimos constantemente a la lista de órdenes y posiciones de control las órdenes y posiciones que van apareciendo, esta lista podría
aumenta significativamente tras un largo trabajo. Y es que las órdenes y posiciones no viven eternamente, y sus copias de control no deben
guardarse constantemente en la lista: las órdenes y posiciones ya no se encuentran en el mercado, y la orden de control continúa existiendo
en la lista y ocupando memoria. Así que, para eliminar las órdenes de control innecesarias de la lista, vamos a usar
un método que elimina órdenes de control de la lista de órdenes de control según el ticket y el identificador de la posición
DeleteOrderFromListControl().
Deebmos notar que el método ha sido declarado, pero implementado. Lo implementaremos después de que esté totalmente lista la funcionalidad completa para el seguimiento de la modificación de órdenes y posiciones.
Método que retorna el índice de una orden de control en la lista de órdenes de control según su ticket y tipo:
//+------------------------------------------------------------------+ //| Return an order index by a ticket in the list of control orders | //+------------------------------------------------------------------+ int CMarketCollection::IndexControlOrder(const ulong ticket,const ulong id) { int total=this.m_list_control.Total(); for(int i=0;i<total;i++) { COrderControl* order=this.m_list_control.At(i); if(order==NULL) continue; if(order.PositionID()==id && order.Ticket()==ticket) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
Transmitimos al método el ticket de la orden/posición y el identificador de la posición.
Buscamos
en el ciclo por todas las órdenes de control de la lista la
orden de control con el mismo ticket e identificador y retornamos su índice
en la lista de órdenes de control. Si la orden no ha sido encontrada, se
retornará -1.
Método-manejador del evento de cambio de una orden/posición existente:
//+------------------------------------------------------------------+ //| Handler of changing an existing order/position | //+------------------------------------------------------------------+ void CMarketCollection::OnChangeEvent(COrder* order,const int index) { COrderControl* order_control=this.m_list_control.At(index); if(order_control!=NULL) { this.m_change_type=order_control.ChangeControl(order); ENUM_CHANGE_TYPE change_type=(order.Status()==ORDER_STATUS_MARKET_POSITION ? CHANGE_TYPE_ORDER_TAKE_PROFIT : CHANGE_TYPE_NO_CHANGE); if(this.m_change_type>change_type) { order_control.SetNewState(order); if(!this.AddToListChanges(order_control)) { ::Print(DFUN,TextByLanguage("Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to list of modified orders")); } } } } //+------------------------------------------------------------------+
El método recibe el puntero a la orden comprobada y el
índice de la orden de control que le corresponde en la lista de órdenes de control.
Obtenemos
la orden de control de la lista según su índice y comprobamos
la presencia de cambios en las propiedades de la orden de control que se correspondan con las propiedades de la orden de control comprobada
con la ayuda del método ChangeControl(). Transmitimos al método el
puntero a la orden de control, y si existen diferencias, el método retornará el tipo de cambio, que se registrará en la
variable-miembro de clase
m_change_type.
A continuación, revisamos el estado de la orden
comprobada y establecemos un valor superado el cual se considerará que ha sucedido un cambio. Para una
posición, este valor deberá ser superior al valor de la constante
CHANGE_TYPE_ORDER_TAKE_PROFIT de la enumeración ENUM_CHANGE_TYPE, dado que todos los valores que son iguales o menores al valor
de esta constante pertenecen solo a las órdenes pendientes. Para
una orden pendiente, el valor deberá ser superior al valor de la constante
CHANGE_TYPE_NO_CHANGE.
Si el valor de la constante m_change_type obtenido
es superior al establecido, significará que existe una modificación, y será necesario primero guardar
el estado actual de la orden de control para la posterior comprobación, y ubicar
una copia de la orden de control en la lista de órdenes cambiadas para el posterior procesamiento de esta lista en la clase
CEventsCollection.
Método que crea una orden de control modificada y la añade a la lista de órdenes modificadas:
//+------------------------------------------------------------------+ //| Create and add a control order to the list of changed orders | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListChanges(COrderControl* order_control) { if(order_control==NULL) return false; COrderControl* order_changed=new COrderControl(order_control.PositionID(),order_control.Ticket(),order_control.Magic(),order_control.Symbol()); if(order_changed==NULL) return false; order_changed.SetTime(order_control.Time()); order_changed.SetTimePrev(order_control.TimePrev()); order_changed.SetVolume(order_control.Volume()); order_changed.SetTypeOrder(order_control.TypeOrder()); order_changed.SetTypeOrderPrev(order_control.TypeOrderPrev()); order_changed.SetPrice(order_control.Price()); order_changed.SetPricePrev(order_control.PricePrev()); order_changed.SetStopLoss(order_control.StopLoss()); order_changed.SetStopLossPrev(order_control.StopLossPrev()); order_changed.SetTakeProfit(order_control.TakeProfit()); order_changed.SetTakeProfitPrev(order_control.TakeProfitPrev()); order_changed.SetChangedType(order_control.GetChangeType()); if(!this.m_list_changed.Add(order_changed)) { delete order_changed; return false; } return true; } //+------------------------------------------------------------------+
El método recibe el puntero a la orden que ha sido modificada,
y cuya copia se debe ubicar en la lista de órdenes y posiciones de control modificadas.
A continuación, se crea una nueva orden de control. Al
crear la misma, se le asignan de inmediato el mismo identificador de posición, ticket, número mágico y símbolo de la orden de control
modificada.
Acto seguido, las propiedades de la orden de control modificada
simplemente se copian en las propiedades de la nuevamente creada copiando elemento por elemento..
Y, finalmente, colocamos
la copia nuevamente creada de la orden de control modificada en la lista de órdenes modificadas.
Si
no hemos logrado colocar la orden nuevamente creada en la lista, el
objeto de orden nuevamente creado será eliminado y se retornará
false.
Con esto, ya hemos finalizado los cambios necesarios de la clase CMarketCollection, ahora ha llegado el turno de la clase CEventsCollection.
En la clase de colección de eventos CEventsCollection, debemos añadir el procesamiento de la situación en la que una lista
de órdenes modificadas creada en la clase de colección de órdenes y posiciones de mercado no se encuentra vacía, lo que indica que en ella
existen órdenes y posiciones modificadas que deben ser procesadas, es decir, tenemos que crear un nuevo evento y enviar un mensaje sobre
ello al programa que ha realizado la llamada.
Añadimos al método ya existente en la sección privada de la clase la definición de dos métodos: un nuevo
método sobrecargado para la creación de un nuevo evento y un método-manejador del
evento de cambio de una orden/posición existente; asimismo, añadimos al método Refresh() en sus parámetros la
transmisión de la lista de órdenes cambiadas al método:
//+------------------------------------------------------------------+ //| EventsCollection.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\EventBalanceOperation.mqh" #include "..\Objects\Events\EventOrderPlaced.mqh" #include "..\Objects\Events\EventOrderRemoved.mqh" #include "..\Objects\Events\EventPositionOpen.mqh" #include "..\Objects\Events\EventPositionClose.mqh" //+------------------------------------------------------------------+ //| Collection of account events | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // List of events bool m_is_hedge; // Hedge account flag long m_chart_id; // Control program chart ID int m_trade_event_code; // Trading event code ENUM_TRADE_EVENT m_trade_event; // Account trading event CEvent m_event_instance; // Event object for searching by property //--- Create a trading event depending on the (1) order status and (2) change type void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market); void CreateNewEvent(COrderControl* order); //--- Create an event for a (1) hedging account, (2) netting account void NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); void NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); //--- Select and return the list of market pending orders CArrayObj* GetListMarketPendings(CArrayObj* list); //--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders CArrayObj* GetListHistoryPendings(CArrayObj* list); CArrayObj* GetListDeals(CArrayObj* list); CArrayObj* GetListCloseByOrders(CArrayObj* list); //--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID, //--- (3) all market entry deals by position ID, (4) all market exit deals by position ID, //--- (5) all position reversal deals by position ID CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id); double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the (1) first, (2) last and (3) closing order from the list of all position orders, //--- (4) an order by ticket, (5) market position by ID, //--- (6) the last and (7) penultimate InOut deal by position ID COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket); COrder* GetPositionByID(CArrayObj* list,const ulong position_id); //--- Return the flag of the event object presence in the event list bool IsPresentEventInList(CEvent* compared_event); //--- Handler of an existing order/position change void OnChangeEvent(CArrayObj* list_changes,CArrayObj* list_history,CArrayObj* list_market,const int index); public: //--- Select events from the collection with time within the range from begin_time to end_time CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the full event collection list "as is" CArrayObj *GetList(void) { return &this.m_list_events; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } //--- Update the list of events void Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals); //--- Set the control program chart ID void SetChartID(const long id) { this.m_chart_id=id; } //--- Return the last trading event on the account ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } //--- Reset the last trading event void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- Constructor CEventsCollection(void); }; //+------------------------------------------------------------------+
Vamos a implementar los nuevos métodos fuera del cuerpo de la clase.
Método sobrecargado de creación del evento de modificación de una orden o posición:
//+------------------------------------------------------------------+ //| Create a trading event depending on the order change type | //+------------------------------------------------------------------+ void CEventsCollection::CreateNewEvent(COrderControl* order) { CEvent* event=NULL; //--- Pending StopLimit order placed if(order.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED; event=new CEventOrderPlased(this.m_trade_event_code,order.Ticket()); } //--- if(event!=NULL) { event.SetProperty(EVENT_PROP_TIME_EVENT,order.Time()); // Event time event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_STOPLIMIT_TRIGGERED); // Event reason (from the ENUM_EVENT_REASON enumeration) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrderPrev()); // Type of the order that triggered an event event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Ticket of the order that triggered an event event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder()); // Event order type event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Event order ticket event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder()); // Position first order type event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Position first order ticket event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID()); // Position ID event.SetProperty(EVENT_PROP_POSITION_BY_ID,0); // Opposite position ID event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Opposite position magic number event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrderPrev()); // Position order type before changing the direction event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Position order ticket before changing direction event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder()); // Current position order type event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket()); // Current position order ticket event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic()); // Order magic number event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev()); // First position order time event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev()); // Event price event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price()); // Order open price event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price()); // Order close price event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // StopLoss order price event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // TakeProfit order price event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Requested order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,0); // Executed order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.Volume()); // Remaining (unexecuted) order volume event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,0); // Executed position volume event.SetProperty(EVENT_PROP_PROFIT,0); // Profit event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Order symbol event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Opposite position symbol //--- Set the control program chart ID, decode the event code and set the event type event.SetChartID(this.m_chart_id); event.SetTypeEvent(); //--- Add the event object if it is not in the list if(!this.IsPresentEventInList(event)) { this.m_list_events.InsertSort(event); //--- Send a message about the event and set the value of the last trading event event.SendEvent(); this.m_trade_event=event.TradeEvent(); } //--- If the event is already present in the list, remove a new event object and display a debugging message else { ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list.")); delete event; } } } //+------------------------------------------------------------------+
Ya analizamos el método de creación de un nuevo evento en la quinta parte de la descripción de la biblioteca, al
crear la colección de eventos.
Este método se ha construido de forma prácticamente idéntica. La única diferencia reside en el
tipo de orden
cuyo puntero se transmite al método.
Al inicio del método, se
comprueba el tipo de cambio que ha sucedido a la orden y, de acuerdo con el
tipo de cambio, se establece el código de cambio en la variable-miembro de clase
m_trade_event_code.
A continuación, se crea el evento
correspondiente al tipo de cambioy se rellenan sus propiedades de acuerdo con el tipo de cambio; después, el
evento se ubica en la lista de eventos y se envía al programa de gestión.
Método mejorado de actualización de la lista de eventos:
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) { //--- Exit if the lists are empty if(list_history==NULL || list_market==NULL) return; //--- If the event is in the market environment if(is_market_event) { //--- if the order properties were changed int total_changes=list_changes.Total(); if(total_changes>0) { for(int i=total_changes-1;i>=0;i--) { this.OnChangeEvent(list_changes,i); } } //--- if the number of placed pending orders increased if(new_market_pendings>0) { //--- Receive the list of the newly placed pending orders CArrayObj* list=this.GetListMarketPendings(list_market); if(list!=NULL) { //--- Sort the new list by order placement time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_market_pendings; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list, if this is a pending order, set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING) this.CreateNewEvent(order,list_history,list_market); } } } } //--- If the event is in the account history if(is_history_event) { //--- If the number of historical orders increased if(new_history_orders>0) { //--- Receive the list of removed pending orders only CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { //--- Sort the new list by order removal time list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list. If this is a removed pending order without a position ID, //--- this is an order removal - set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } //--- If the number of deals increased if(new_deals>0) { //--- Receive the list of deals only CArrayObj* list=this.GetListDeals(list_history); if(list!=NULL) { //--- Sort the new list by deal time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_deals; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive a deal from the list and set a trading event COrder* order=list.At(i); if(order!=NULL) this.CreateNewEvent(order,list_history,list_market); } } } } } //+------------------------------------------------------------------+
Este método también fue analizado en la quinta parte de la descripción de la biblioteca, al crear la colección de eventos. La principal diferencia respecto al método analizado es la adición de un bloque de código para el procesamiento de eventos de modificación, en el caso de que el tamaño de la lista de órdenes modificadas sea distinto a cero: tomamos de la lista en el ciclo cada orden modificada y la procesamos en el Método-manejador del evento de modificación de una orden:
//+------------------------------------------------------------------+ //| The handler of an existing order/position change event | //+------------------------------------------------------------------+ void CEventsCollection::OnChangeEvent(CArrayObj* list_changes,const int index) { COrderControl* order_changed=list_changes.Detach(index); if(order_changed!=NULL) { if(order_changed.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.CreateNewEvent(order_changed); } delete order_changed; } } //+------------------------------------------------------------------+
Al procesar la lista de órdenes modificadas, no solo necesitamos obtener la orden modificada de la lista, sino también, al finalizar el
procesamiento de la orden de turno, eliminar tanto el propio objeto de orden, como el puntero al mismo de la lista, para no procesar varias
veces el mismo evento.
Por fortuna, ya se ha previsto tal posibilidad en la Biblioteca estándar, al trabajar con una
matriz dinámica de punteros a los objetos CArrayObj: el método Detach(),
que
obtiene un elemento de la posición indicada, eliminándolo además de la matriz. Es decir, obtenemos
el puntero a un objeto guardado en la matriz según un índice,
y simultáneamente eliminamos este puntero de la matriz.
Si el tipo de cambio es igual a CHANGE_TYPE_ORDER_TYPE (cambio
del tipo de orden— activación de una orden StopLimit pendiente y su transformación en una orden Limit), creamos
un nuevo evento , la activación de una orden StopLimit. Cuando finaliza el procesamiento de un objeto según el
puntero obtenido con la ayuda del método Detach(), solo tenemos (pues ya no lo necesitamos) que eliminar
el propio puntero.
Con ello, ya hemos finalizado la mejora de la clase CEventsCollection.
Para que todos los cambios entren en vigor, será necesario obtener (en el método TradeEventsControl() de la clase CEngine del objeto principal de la biblioteca) la lista de órdenes modificadas de la clase de colección de órdenes y posiciones de mercado, luego registrar su tamaño y, al llamar el método de actualización de eventos Refresh() de la clase de colección de eventos, comprobar adicionalmente el tamaño de la lista de órdenes modificadas, y transmitir al método Refresh() de la colección de eventos la lista de órdenes modificadas para su procesamiento:
//+------------------------------------------------------------------+ //| Check trading events | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flag this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- First launch actions if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the changes in the market status and account history this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes, this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewMarketOrders(),this.m_history.NewDeals()); //--- Receive the last account trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Dado que la activación de una orden StopLimit provoca la colocación de una orden Limit, vamos a "calificar" este evento como la colocación de una orden pendiente, mientras que el motivo será la activación de una orden StopLimit EVENT_REASON_STOPLIMIT_TRIGGERED, cuya constante ya hemos establecido en el archivo Defines.mqh, en la enumeración ENUM_EVENT_REASON.
Para mostrar este evento en el diario y enviarlo al programa de gestión, vamos a mejorar la clase EventOrderPlased:
Solo tenemos que añadir el procesamiento del motivo del evento
EVENT_REASON_STOPLIMIT_TRIGGERED.
//+------------------------------------------------------------------+ //| EventOrderPlased.mqh | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Event.mqh" //+------------------------------------------------------------------+ //| Placing a pending order event | //+------------------------------------------------------------------+ class CEventOrderPlased : public CEvent { public: //--- Constructor CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {} //--- Supported order properties (1) real, (2) integer virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property); virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property); //--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart virtual void PrintShort(void); virtual void SendEvent(void); }; //+------------------------------------------------------------------+ //| Return 'true' if the event supports the passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property) { if(property==EVENT_PROP_TYPE_DEAL_EVENT || property==EVENT_PROP_TICKET_DEAL_EVENT || property==EVENT_PROP_TYPE_ORDER_POSITION || property==EVENT_PROP_TICKET_ORDER_POSITION || property==EVENT_PROP_POSITION_ID || property==EVENT_PROP_POSITION_BY_ID || property==EVENT_PROP_TIME_ORDER_POSITION ) return false; return true; } //+------------------------------------------------------------------+ //| Return 'true' if the event supports the passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { if(property==EVENT_PROP_PRICE_CLOSE || property==EVENT_PROP_PROFIT ) return false; return true; } //+------------------------------------------------------------------+ //| Display a brief message about the event in the journal | //+------------------------------------------------------------------+ void CEventOrderPlased::PrintShort(void) { int digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),digits) : ""); string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),digits) : ""); string vol=::DoubleToString(this.VolumeOrderInitial(),DigitsLots(this.Symbol())); string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : ""); string type=this.TypeOrderFirstDescription()+" #"+(string)this.TicketOrderEvent(); string event=TextByLanguage(" Установлен "," Placed "); string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),digits); string txt=head+this.Symbol()+event+vol+" "+type+price+sl+tp+magic; //--- If StopLimit order is activated if(this.Reason()==EVENT_REASON_STOPLIMIT_TRIGGERED) { head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimeEvent())+" -\n"; event=TextByLanguage(" Сработал "," Triggered "); type= ( OrderTypeDescription(this.TypeOrderPosPrevious())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" по цене "," at price ")+DoubleToString(this.PriceEvent(),digits)+" -->\n"+ vol+" "+OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" на цену "," on price ")+DoubleToString(this.PriceOpen(),digits) ); txt=head+this.Symbol()+event+"("+TimeMSCtoString(this.TimePosition())+") "+vol+" "+type+sl+tp+magic; } ::Print(txt); } //+------------------------------------------------------------------+ //| Send the event to the chart | //+------------------------------------------------------------------+ void CEventOrderPlased::SendEvent(void) { this.PrintShort(); ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol()); } //+------------------------------------------------------------------+
Aquí todo resulta muy fácil de entender, así que no vamos a detenernos en la explicación de acciones sencillas.
Con esto, hemos finalizado la mejora de la biblioteca para monitorear la activación de órdenes StopLimit.
Prueba
Para comprobar las mejoras realizadas, vamos a usar el asesor del artículo anterior. Solo tenemos que cambiar el nombre de TestDoEasyPart06.mq5 de la carpeta \MQL5\Experts\TestDoEasy\Part06 a TestDoEasyPart07.mq5 y guardarlo en la nueva subcarpeta \MQL5\Experts\TestDoEasy\ Part07.
Lo compilamos, lo iniciamos en el simulador, colocamos una StopLimit y esperamos a que se active:
¿Qué es lo próximo?
No resulta complicado adivinar que la funcionalidad implementada en el presente artículo incluye la posibilidad de añadir rápidamente el
seguimiento de otros eventos, como la modificación de las propiedades de las órdenes pendientes: su precio de colocación y los niveles de
StopLoss y TakeProfit, así como la modificación de los niveles de StopLoss y TakeProfit de las posiciones. Precisamente de ello nos
ocuparemos en el próximo artículo.
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.
Parte
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.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/6482
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso