Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXX): Solicitudes comerciales pendientes - Control de los objetos de solicitudes
Contenido
- Concepto
- Objeto de pausa
- Optimizamos ligeramente el código
- Clase de gestión de objetos de solicitudes pendientes
- Simulación
- ¿Qué es lo próximo?
Concepto
Desde el artículo 26, hemos ido creado y poniendo paulatinamente a prueba el concepto de trabajo con solicitudes comerciales pendientes. En el anterior artículo, creamos las clases de los objetos de solicitudes pendientes que se corresponden con el concepto general de los objetos de la biblioteca. En el presente artículo, nos ocuparemos de la clase que permite controlar los objetos de solicitudes pendientes.
Inicialmente, queríamos crear una clase independiente para controlar las solicitudes pendientes, en la que se concentraran todos los métodos para trabajar con ellas. Pero, finalmente, ha resultado que la clase comercial principal CTrading de la biblioteca y la nueva clase creada para la gestión de solicitudes pendientes están tan estrechamente relacionadas, que es más sencillo crear una nueva clase para controlar los objetos de solicitudes pendientes a través de un heredero de la comercial principal.
Todo el trabajo en cuanto al control de los objetos de solicitudes pendientes se implementa en el temporizador de la clase, por eso, haremos virtual el temporizador de la clase comercial principal, y, por consiguiente, también será virtual el temporizador de la clase de control de solicitudes pendientes. Entonces, escribiremos todo lo relacionado con el temporizador de la clase comercial principal en su temporizador, mientras que todo lo que debe funcionar en la clase de control de los objetos de solicitudes pendientes se escribirá en el temporizador de su clase.
Aparte de la clase de control de objetos de solicitudes pendientes, vamos a crear una pequeña clase para organizar la pausa, para no utilizar en el futuro la función Sleep(), que detiene la ejecución de un programa durante el tiempo de retraso. El objeto de pausa nos permitirá no depender de los ticks, para que también sea posible simular el código que requiere de algún tipo de espera. La pausa se controlará desde el temporizador.
Tras introducir el guardado de algunos datos en la propiedad de la orden "Número mágico", hemos tenido que repartir por diferentes clases (en las que se requería la obtención de estos datos) métodos idénticos que retornan los identificadores del número mágico y de los grupos anotados en el valor del número mágico de la orden. Sin embargo, hemos olvidado injustamente que antes habíamos creado el objeto básico de todos los objetos de la biblioteca, a partir del cual podemos heredar objetos nuevamente creados, que en este caso obtendrán la funcionalidad de eventos del objeto básico, así como algunos métodos que son idénticos en cuanto a su principio y misión, y que se repetirán de una clase a otra. Precisamente a este vamos a trasladar el guardado y la obtención de datos en el número mágico, así como la gestión de la muestra de mensajes en el diario, es decir, la bandera que muestra el nivel de logueo de cada clase en la que se muestran los diferentes mensajes. Resulta más lógico heredar todas estas clases del objeto básico y obtener los métodos necesarios, que escribir lo mismo cada vez en cada nueva clase.
Partiendo de lo anteriormente descrito, hoy vamos a crear la clase de objeto de pausa (que usaremos en los próximos artículos). Asimismo, optimizaremos ligeramente el código preparado para trasladar los métodos repetidos de diferentes clases a la clase del objeto básico, y crearemos una clase para gestionar solicitudes comerciales. Y, naturalmente, mejoraremos las clases de los objetos de solicitudes pendientes: tendremos que escribir en ellos propiedades adicionales que nos permitirán comparar los estados de dos objetos interrelacionados, a saber, el estado actual de las propiedades de la orden y el estado de las propiedades del objeto de solicitud. Esto es necesario para registrar la finalización del funcionamiento de una solicitud pendiente, y así eliminarla a tiempo de la lista de solicitudes activas que se encuentran a la espera de que llegue el momento del envío de la solicitud al servidor.
Bien, como ya es costumbre, primero vamos a escribir en el archivo Datas.mqh los índices de los nuevos mensejes:
MSG_LIB_TEXT_PEND_REQUEST_STATUS_REMOVE, // Pending request to delete a pending order MSG_LIB_TEXT_PEND_REQUEST_STATUS_MODIFY, // Pending request to modify pending order parameters MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_VOLUME, // Actual volume MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_PRICE, // Actual order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_STOPLIMIT, // Actual StopLimit order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_SL, // Actual StopLoss order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TP, // Actual TakeProfit order price MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TYPE_FILLING, // Actual order filling type MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TYPE_TIME, // Actual order expiration type MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_EXPIRATION, // Actual order lifetime }; //+------------------------------------------------------------------+
y los textos que corresponden a estos nuevos mensajes:
{"Отложенный запрос на удаление отложенного ордера","Pending request to remove pending order"}, {"Отложенный запрос на модификацию параметров отложенного ордера","Pending request to modify pending order parameters"}, {"Фактический объем","Actual volume"}, {"Фактическая цена установки ордера","Actual order placement price"}, {"Фактическая цена установки StopLimit-ордера","Actual StopLimit order placement price"}, {"Фактическая цена установки StopLoss-ордера","Actual StopLoss order placement price"}, {"Фактическая цена установки TakeProfit-ордера","Actual TakeProfit order placement price"}, {"Фактический тип заливки ордера","Actual order filling type"}, {"Фактический тип экспирации ордера","Actual of order expiration type"}, {"Фактическое время жизни ордера","Actual of order lifetime"}, }; //+---------------------------------------------------------------------+
Objeto de pausa
Vamos a crear en la carpeta de las clases de servicios y las funciones de la biblioteca \MQL5\Include\DoEasy\Services\ la nueva claseCPause en el archivo Pause.mqh, escribiendo de inmediato todo lo necesario en ella:
//+------------------------------------------------------------------+ //| Pause.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include "DELib.mqh" //+------------------------------------------------------------------+ //| Pause class | //+------------------------------------------------------------------+ class CPause { private: ulong m_start; // Countdown start ulong m_time_begin; // Pause countdown start time ulong m_wait_msc; // Pause in milliseconds public: //--- Set the new (1) countdown start time and (2) pause in milliseconds void SetTimeBegin(const ulong time) { this.m_time_begin=time; this.m_start=::GetTickCount(); } void SetWaitingMSC(const ulong pause) { this.m_wait_msc=pause; } //--- Return (1) the time passed from the countdown start in milliseconds, (2) waiting completion flag //--- (3) pause countdown start time, (4) pause in milliseconds ulong Passed(void) const { return ::GetTickCount()-this.m_start; } bool IsCompleted(void) const { return this.Passed()>this.m_wait_msc; } ulong TimeBegin(void) const { return this.m_time_begin; } ulong TimeWait(void) const { return this.m_wait_msc; } //--- Return the description (1) of the time passed till the countdown starts in milliseconds, //--- (2) pause countdown start time, (3) pause in milliseconds string PassedDescription(void) const { return ::TimeToString(this.Passed()/1000,TIME_SECONDS); } string TimeBeginDescription(void) const { return ::TimeMSCtoString(this.m_time_begin); } string WaitingMSCDescription(void) const { return (string)this.m_wait_msc; } string WaitingSECDescription(void) const { return ::TimeToString(this.m_wait_msc/1000,TIME_SECONDS); } //--- Constructor CPause(void) : m_start(::GetTickCount()){;} }; //+------------------------------------------------------------------+
Incluimos directamente en el archivo de la clase el archivo de funciones de servicio DELib.mqh, en el que se encuentra la función de muestra de la hora en milisegundos: la necesitaremos para el método auxiliar de la clase que muestra en el diario la hora de inicio de la pausa.
En la sección privada de la clase, declaramos las tres variables de miembro de clase que necesitamos para calcular la pausa:
- La variable m_start sirve para indicar la cifra de control en el momento en que se crea el objeto de pausa, o para el grabado en la variable de una nueva cifra de control para el comienzo de una nueva cuenta en el objeto de pausa anteriormente creado.
- La variable m_time_begin guarda la hora establecida para el inicio de la cuenta de la pausa, y solo sirve para mostrar mensajes informativos.
- La variable m_wait_msc contiene el número de milisegundos de la pausa: una vez transcurrido este tiempo, se considerará que la pausa ha finalizado.
Los métodos de la clase, en nuestra opinión, no requieren de explicación.
- El método SetTimeBegin(), que establece la nueva hora de comienzo de la nueva cuenta, primero graba en la variable m_time_begin la hora que se la ha transmitido, y después establece un nuevo número para el comienzo de la cuenta en la variable m_start. El resultado retornado por la función GetTickCount() será el número de control del comienzo de cualquier cuenta en la clase.
- El método Passed(), que retorna el número de milisegundos de pausa transcurridos, en esencia, retorna la diferencia en milisegundos entre la medición actual de la hora de control y el número establecido en m_start al iniciar la cuenta de la pausa.
Los demás métodos de la clase son bastante fáciles de comprender, por lo que no requieren de aclaraciones.
Por el momento, no vamos a utilizar esta clase en ninguna parte, pero sí que la usaremos en posteriores desarrollos de la biblioteca.
Para que la nueva clase de pausa esté disponible en cualquier punto de la biblioteca y en los programas que funcionen sobre la base de la biblioteca,
vamos a incluir el archivo de clase CPause en el archivo de funciones de servicio de la biblioteca DELib.mqh:
//+------------------------------------------------------------------+ //| DELib.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property strict // Es necesario para mql4 //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "Message.mqh" #include "TimerCounter.mqh" #include "Pause.mqh" //+------------------------------------------------------------------+
Optimizamos ligeramente el código
Bien, ya hemos decidido que es hora de trasladar los métodos que se repiten de clase a clase al objeto básico de todos los objetos de la biblioteca, y heredar estas clases del objeto básico (si aún no se ha hecho esto).
En primer lugar, vamos a escribir las nuevas variables y métodos en la clase del objeto básico de todos los objetos de la biblioteca:
//+------------------------------------------------------------------+ //| Base object class for all library objects | //+------------------------------------------------------------------+ #define CONTROLS_TOTAL (10) class CBaseObj : public CObject { private: int m_long_prop_total; int m_double_prop_total; //--- Fill in the object property array template<typename T> bool FillPropertySettings(const int index,T &array[][CONTROLS_TOTAL],T &array_prev[][CONTROLS_TOTAL],int &event_id); protected: CArrayObj m_list_events_base; // Object base event list CArrayObj m_list_events; // Object event list ENUM_LOG_LEVEL m_log_level; // Logging level MqlTick m_tick; // Tick structure for receiving quote data double m_hash_sum; // Object data hash sum double m_hash_sum_prev; // Object data hash sum during the previous check int m_digits_currency; // Number of decimal places in an account currency int m_global_error; // Global error code long m_chart_id; // Control program chart ID bool m_is_event; // Object event flag int m_event_code; // Object event code int m_event_id; // Event ID (equal to the object property value) string m_name; // Object name string m_folder_name; // Name of the folder storing CBaseObj descendant objects bool m_first_start; // First launch flag int m_type; // Object type (corresponds to the collection IDs) //--- Data for storing, controlling and returning tracked properties: //--- [Property index][0] Controlled property increase value //--- [Property index][1] Controlled property decrease value //--- [Property index][2] Controlled property value level //--- [Property index][3] Property value //--- [Property index][4] Property value change //--- [Property index][5] Flag of a property change exceeding the increase value //--- [Property index][6] Flag of a property change exceeding the decrease value //--- [Property index][7] Flag of a property increase exceeding the control level //--- [Property index][8] Flag of a property decrease being less than the control level //--- [Property index][9] Flag of a property value being equal to the control level long m_long_prop_event[][CONTROLS_TOTAL]; // The array for storing object's integer properties values and controlled property change values double m_double_prop_event[][CONTROLS_TOTAL]; // The array for storing object's real properties values and controlled property change values long m_long_prop_event_prev[][CONTROLS_TOTAL]; // The array for storing object's controlled integer properties values during the previous check double m_double_prop_event_prev[][CONTROLS_TOTAL]; // The array for storing object's controlled real properties values during the previous check //--- Return (1) time in milliseconds, (2) milliseconds from the MqlTick time value long TickTime(void) const { return #ifdef __MQL5__ this.m_tick.time_msc #else this.m_tick.time*1000 #endif ; } ushort MSCfromTime(const long time_msc) const { return #ifdef __MQL5__ ushort(this.TickTime()%1000) #else 0 #endif ; } //--- return the flag of the event code presence in the event object bool IsPresentEventFlag(const int change_code) const { return (this.m_event_code & change_code)==change_code; } //--- Return the number of decimal places of the account currency int DigitsCurrency(void) const { return this.m_digits_currency; } //--- Returns the number of decimal places in the 'double' value int GetDigits(const double value) const; //--- Set the size of the array of controlled (1) integer and (2) real object properties bool SetControlDataArraySizeLong(const int size); bool SetControlDataArraySizeDouble(const int size); //--- Check the array size of object properties bool CheckControlDataArraySize(bool check_long=true); //--- Check the list of object property changes and create an event void CheckEvents(void); //--- (1) Pack a 'ushort' number to a passed 'long' number long UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value); protected: //--- (1) convert a 'ushort' value to a specified 'long' number byte long UshortToByte(const ushort value,const uchar to_byte) const; public: //--- Set the value of the pbject property controlled (1) increase, (2) decrease, (3) control level template<typename T> void SetControlledValueINC(const int property,const T value); template<typename T> void SetControlledValueDEC(const int property,const T value); template<typename T> void SetControlledValueLEVEL(const int property,const T value); //--- (1) Set, (2) return the error logging level void SetLogLevel(const ENUM_LOG_LEVEL level) { this.m_log_level=level; } ENUM_LOG_LEVEL GetLogLevel(void) const { return this.m_log_level; } //--- Return the set value of the controlled (1) integer and (2) real object properties increase long GetControlledLongValueINC(const int property) const { return this.m_long_prop_event[property][0]; } double GetControlledDoubleValueINC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][0]; } //--- Return the set value of the controlled (1) integer and (2) real object properties decrease long GetControlledLongValueDEC(const int property) const { return this.m_long_prop_event[property][1]; } double GetControlledDoubleValueDEC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][1]; } //--- Return the specified control level of object's (1) integer and (2) real properties long GetControlledLongValueLEVEL(const int property) const { return this.m_long_prop_event[property][2]; } double GetControlledDoubleValueLEVEL(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][2]; } //--- Return the current value of the object (1) integer and (2) real property long GetPropLongValue(const int property) const { return this.m_long_prop_event[property][3]; } double GetPropDoubleValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][3]; } //--- Return the change value of the controlled (1) integer and (2) real object property long GetPropLongChangedValue(const int property) const { return this.m_long_prop_event[property][4]; } double GetPropDoubleChangedValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][4]; } //--- Return the flag of an (1) integer and (2) real property value change exceeding the increase value long GetPropLongFlagINC(const int property) const { return this.m_long_prop_event[property][5]; } double GetPropDoubleFlagINC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][5]; } //--- Return the flag of an (1) integer and (2) real property value change exceeding the decrease value long GetPropLongFlagDEC(const int property) const { return this.m_long_prop_event[property][6]; } double GetPropDoubleFlagDEC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][6]; } //--- Return the flag of an (1) integer and (2) real property value increase exceeding the control level long GetPropLongFlagMORE(const int property) const { return this.m_long_prop_event[property][7]; } double GetPropDoubleFlagMORE(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][7]; } //--- Return the flag of an (1) integer and (2) real property value decrease being less than the control level long GetPropLongFlagLESS(const int property) const { return this.m_long_prop_event[property][8]; } double GetPropDoubleFlagLESS(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][8]; } //--- Return the flag of an (1) integer and (2) real property being equal to the control level long GetPropLongFlagEQUAL(const int property) const { return this.m_long_prop_event[property][9]; } double GetPropDoubleFlagEQUAL(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][9]; } //--- Reset the variables of (1) tracked and (2) controlled object data (can be reset in the descendants) void ResetChangesParams(void); virtual void ResetControlsParams(void); //--- Add the (1) object event and (2) the object event reason to the list bool EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam); bool EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value); //--- Set/return the occurred event flag to the object data void SetEvent(const bool flag) { this.m_is_event=flag; } bool IsEvent(void) const { return this.m_is_event; } //--- Return (1) the list of events, (2) the object event code and (3) the global error code CArrayObj *GetListEvents(void) { return &this.m_list_events; } int GetEventCode(void) const { return this.m_event_code; } int GetError(void) const { return this.m_global_error; } //--- Return (1) an event object and (2) a base event by its number in the list CEventBaseObj *GetEvent(const int shift=WRONG_VALUE,const bool check_out=true); CBaseEvent *GetEventBase(const int index); //--- Return the number of (1) object events int GetEventsTotal(void) const { return this.m_list_events.Total(); } //--- (1) Set and (2) return the chart ID of the control program void SetChartID(const long id) { this.m_chart_id=id; } long GetChartID(void) const { return this.m_chart_id; } //--- (1) Set the sub-folder name, (2) return the folder name for storing descendant object files void SetSubFolderName(const string name) { this.m_folder_name=DIRECTORY+name; } string GetFolderName(void) const { return this.m_folder_name; } //--- Return the object name string GetName(void) const { return this.m_name; } //--- Update the object data to search for changes (Calling from the descendants: CBaseObj::Refresh()) virtual void Refresh(void); //--- Return an object type virtual int Type(void) const { return this.m_type; } //--- Return an object event description string EventDescription(const int property, const ENUM_BASE_EVENT_REASON reason, const int source, const string value, const string property_descr, const int digits); //--- 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(this.ConvToXX(group,0)<<16); } void SetGroupID2(const uchar group,uint &magic) { magic &=0xFF0FFFFF; magic |= uint(this.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; } //--- Constructor CBaseObj(); }; //+------------------------------------------------------------------+
Variable protegida (disponible para los herederos, pero no para los programas), que guarda los valores del nivel de logueo para cada uno de los objetos herederos del objeto básico que requieren de la muestra de información en el diario. En dicha variable se escribe el nivel de logueo de la enumeración ENUM_LOG_LEVEL anotado en Defines.mqh:
//+------------------------------------------------------------------+ //| Logging level | //+------------------------------------------------------------------+ enum ENUM_LOG_LEVEL { LOG_LEVEL_NO_MSG, // Trading logging disabled LOG_LEVEL_ERROR_MSG, // Only trading errors LOG_LEVEL_ALL_MSG // Full logging }; //+------------------------------------------------------------------+
El establecimiento y el retorno de los valores de esta variable se realiza mediante los métodos públicos SetLogLevel() y GetLogLevel(), respectivamente.
También se han hecho métodos públicos los métodos de establecimiento y retorno de los valores de los diferentes identificadores guardados en el número mágico de la orden/posición.
Ya hemos analizado anteriormente todos estos métodos, por lo que no tiene sentido examinarlos de nuevo.
El objeto comercial básico del símbolo se encuentra en el archivo \MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh.
Vamos a introducir algunas mejoras necesarias:
incluimos en el mismo el archivo del objeto de la biblioteca y convertimos la clase CBaseObj en su clase padre:
//+------------------------------------------------------------------+ //| TradeObj.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include "..\..\Services\DELib.mqh" #include "..\..\Objects\BaseObj.mqh" //+------------------------------------------------------------------+ //| Trading object class | //+------------------------------------------------------------------+ class CTradeObj : public CBaseObj {
Tras introducir estas correcciones, la clase del objeto comercial del símbolo se ha convertido en heredera del objeto básico de todos los objetos de la biblioteca. Ahora, tenemos que eliminar de esta clase todas las variables y métodos que ya existen en la clase padre CBaseObj.
Eliminamos de la sección privada de la clase las variables sobrantes que ya exiten en la clase padre:
SActions m_datas; MqlTick m_tick; // Tick structure for receiving prices MqlTradeRequest m_request; // Trade request structure MqlTradeResult m_result; // trade request execution result ENUM_SYMBOL_CHART_MODE m_chart_mode; // Price type for constructing bars ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; // Margin calculation mode ENUM_ORDER_TYPE_FILLING m_type_filling; // Filling policy ENUM_ORDER_TYPE_TIME m_type_time; // Order type per expiration int m_symbol_expiration_flags; // Flags of order expiration modes for a trading object symbol ulong m_magic; // Magic number string m_symbol; // Symbol string m_comment; // Comment ulong m_deviation; // Slippage in points double m_volume; // Volume datetime m_expiration; // Order expiration time (for ORDER_TIME_SPECIFIED type order) bool m_async_mode; // Flag of asynchronous sending of a trade request ENUM_LOG_LEVEL m_log_level; // Logging level int m_stop_limit; // Distance of placing a StopLimit order in points bool m_use_sound; // The flag of using sounds of the object trading events uint m_multiplier; // The spread multiplier to adjust levels relative to StopLevel
Eliminamos de la sección pública de la clase los métodos de establecimiento y retorno del nivel de logueo:
//--- (1) Return the margin calculation mode, (2) hedge account flag ENUM_ACCOUNT_MARGIN_MODE GetMarginMode(void) const { return this.m_margin_mode; } bool IsHedge(void) const { return this.GetMarginMode()==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; } //--- (1) Set, (2) return the error logging level void SetLogLevel(const ENUM_LOG_LEVEL level) { this.m_log_level=level; } ENUM_LOG_LEVEL GetLogLevel(void) const { return this.m_log_level; } //--- (1) Set, (2) return the filling policy void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type) { this.m_type_filling=type; } ENUM_ORDER_TYPE_FILLING GetTypeFilling(void) const { return this.m_type_filling; }
También eliminamos de la lista de inicialización del constructor de la clase la inicialización del nivel de logueo:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeObj::CTradeObj(void) : m_magic(0), m_deviation(5), m_stop_limit(0), m_expiration(0), m_async_mode(false), m_type_filling(ORDER_FILLING_FOK), m_type_time(ORDER_TIME_GTC), m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"), m_log_level(LOG_LEVEL_ERROR_MSG)
Y escribimos la inicialización del nivel de logueo en el cuerpo del método:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeObj::CTradeObj(void) : m_magic(0), m_deviation(5), m_stop_limit(0), m_expiration(0), m_async_mode(false), m_type_filling(ORDER_FILLING_FOK), m_type_time(ORDER_TIME_GTC), m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy") { //--- Margin calculation mode this.m_margin_mode= ( #ifdef __MQL5__ (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else /* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ); //--- Spread multiplier this.m_multiplier=1; //--- Set default sounds and flags of using sounds this.m_use_sound=false; this.m_log_level=LOG_LEVEL_ERROR_MSG; this.InitSounds(); } //+------------------------------------------------------------------+
El objeto de la orden abstracta básica se encuentra en el archivo \MQL5\Include\DoEasy\Objects\Orders\Order.mqh.
Vamos a introducir algunas mejoras necesarias:
incluimos en el mismo el archivo del objeto de la biblioteca y convertimos la clase CBaseObj en su clase padre:
//+------------------------------------------------------------------+ //| Order.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include <Object.mqh> #include "..\..\Services\DELib.mqh" #include "..\..\Objects\BaseObj.mqh" //+------------------------------------------------------------------+ //| Clase de orden abstracta | //+------------------------------------------------------------------+ class COrder : public CBaseObj {
Eliminamos de la sección de la clase los métodos de obtención de los identicadores anotados en el número mágico de la orden:
(vamos a dejar el recuadro con el recordatorio sobre la ubicación de los valores de los identificadores en el valor del número mágico)
//+------------------------------------------------------------------+ //| Clase de orden abstracta | //+------------------------------------------------------------------+ class COrder : public CObject { private: ulong m_ticket; // Ticket de la orden/transacción seleccionada (MQL5) long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Propiedades de tipo entero double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Propiedades de tipo real string m_string_prop[ORDER_PROP_STRING_TOTAL]; // Propiedades de tipo string //--- 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:
En el constructor cerrado de la clase, completamos la indicación del número mágico en los métodos de obtención de los indicadores:
//+------------------------------------------------------------------+ //| Constructor paramétrico Closed | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Guardando las propiedades de tipo entero 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_FILLING] = this.OrderTypeFilling(); this.m_long_prop[ORDER_PROP_TYPE_TIME] = this.OrderTypeTime(); 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(); //--- Guardando las propiedades de tipo real 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(); //--- Guardando las propiedades de tipo string 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(); //--- Guardando las propiedades adicionales de tipo entero 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((uint)this.GetProperty(ORDER_PROP_MAGIC)); this.m_long_prop[ORDER_PROP_GROUP_ID1] = this.GetGroupID1((uint)this.GetProperty(ORDER_PROP_MAGIC)); this.m_long_prop[ORDER_PROP_GROUP_ID2] = this.GetGroupID2((uint)this.GetProperty(ORDER_PROP_MAGIC)); this.m_long_prop[ORDER_PROP_PEND_REQ_ID] = this.GetPendReqID((uint)this.GetProperty(ORDER_PROP_MAGIC)); //--- Guardando las propiedades adicionales de tipo real 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)] = ""; } //+------------------------------------------------------------------+
La clase comercial principal se encuentra en el archivo \MQL5\Include\DoEasy\Trading.mqh.
Vamos a introducir las mejoras necesarias. Dado que el método ahora es el padre de la futura clase de gestión de solicitudes pendientes,
vamos a trasladar los punteros a los objetos de colección y la lista de punteros a las solicitudes pendientes desde la sección privada a la protegida:
//+------------------------------------------------------------------+ //| Trading class | //+------------------------------------------------------------------+ class CTrading : public CBaseObj { protected: CAccount *m_account; // Pointer to the current account object CSymbolsCollection *m_symbols; // Pointer to the symbol collection list CMarketCollection *m_market; // Pointer to the list of the collection of market orders and positions CHistoryCollection *m_history; // Pointer to the list of the collection of historical orders and deals CEventsCollection *m_events; // Pointer to the event collection list CArrayObj m_list_request; // List of pending requests private:
Eliminamos de la sección privada la variable para guardar los niveles de logueo:
private: CArrayInt m_list_errors; // Error list bool m_is_trade_disable; // Flag disabling trading bool m_use_sound; // The flag of using sounds of the object trading events uchar m_total_try; // Number of trading attempts ENUM_LOG_LEVEL m_log_level; // Logging level MqlTradeRequest m_request; // Trading request prices ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; // Flags of error source in a trading method ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Behavior when handling error
Añadimos a la sección pública de la clase el método que retorna por completo el objeto de la clase comercial, y convertimos el temporizador de la clase en virtual:
public: //--- Return itself CTrading *GetObject(void) { return &this; } //--- Constructor CTrading(); //--- Timer virtual void OnTimer(void); //--- Get the pointers to the lists (make sure to call the method in program's OnInit() since the symbol collection list is created there)
En el método para crear una solicitud pendiente, añadimos la tranmisión del objeto de orden al método y eliminamos los métodos para trabajar con los identificadores anotados en el valor del número mágico de la orden:
//--- Create a pending request bool CreatePendingRequest(const ENUM_PEND_REQ_STATUS status, const uchar id, const uchar attempts, const ulong wait, const MqlTradeRequest &request, const int retcode, CSymbol *symbol_obj, COrder *order); //--- 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; } }; //+------------------------------------------------------------------+
Eliminamos todo de la implementación del temporizador de la clase, dejándola vacía:
//+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CTrading::OnTimer(void) { } //+------------------------------------------------------------------+
En este lugar, escribiremos el código en el caso de que necesitemos en el futuro procesar algo solamente en el temporizador de esta clase, que es la clase padre para otras clases comerciales (en esta implementación, para la futura clase de gestión de solicitudes pendientes).
En el método de comprobación de las limitaciones para el comercio CheckTradeConstraints(), en el bloque encargado de comprobar la corrección del volumen al realizar operaciones comerciales que requieran el valor del lote (la condición ha sido completada), comprobaremos que el volumen sea correcto solo si el valor del volumen transmitido como parámetro de entrada es superior a cero:
//--- If closing or not removing/modifying if(action_type<ACTION_TYPE_CLOSE_BY || action_type==ACTION_TYPE_CLOSE) { //--- In case of close-only, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_CLOSEONLY); return false; } //--- Check the minimum volume if(volume>0) { if(volume<symbol_obj.LotsMin()) { //--- The volume in a request is less than the minimum allowed one. //--- add the error code to the list this.m_error_reason_flags |=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the maximum volume else if(volume>symbol_obj.LotsMax()) { //--- The volume in the request exceeds the maximum acceptable one. //--- add the error code to the list this.m_error_reason_flags |=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the minimum volume gradation double step=symbol_obj.LotsStep(); if(fabs((int)round(volume/step)*step-volume)>0.0000001) { //--- The volume in the request is not a multiple of the minimum gradation of the lot change step //--- add the error code to the list this.m_error_reason_flags |=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_INVALID_VOLUME_STEP); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } } //--- When opening a position
A continuación:
En todos los métodos comerciales, allí donde se requiere la creación de un objeto de solicitud pendiente, transmitimos al método el objeto de orden. Y donde no haya un objeto semejante (en los métodos de apertura de posición y establecimiento de órdenes pendientes), transmitimos al método NULL.
En el método de apertura de posiciones, transmitimos NULL como último parámetro:
//--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal, //--- create a pending request and return 'false' (OpenPosition) if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH) { //--- If the trading request magic number, has no pending request ID if(this.GetPendReqID((uint)magic)==0) { //--- Play the error sound if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); //--- set the last error code to the return structure int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { if(code==MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED || code==MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED) code=10027; trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } //--- Waiting time in milliseconds: //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value, ulong wait=method; //--- 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 pending request object ID to the magic number and fill in the remaining unfilled fields of the trading request structure uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic); this.SetPendReqID((uchar)id,mn); this.m_request.magic=mn; this.m_request.action=TRADE_ACTION_DEAL; this.m_request.symbol=symbol_obj.Name(); this.m_request.type=order_type; //--- Set the number of trading attempts and create a pending request uchar attempts=(this.m_total_try < 1 ? 1 : this.m_total_try); this.CreatePendingRequest(PEND_REQ_STATUS_OPEN,(uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj,NULL); } return false; }
Y en el método para establecer una orden pendiente, transmitimos exactamente de la misma forma NULL como objeto de orden al método de creación de una solicitud pendiente.
Por otra parte, en los métodos que trabajan con órdenes y posiciones ya existentes, transmitimos el objeto de orden al método de creación de una solicitud pendiente, por ejemplo,
transmitimos un objeto de orden en el método de modificación de órdenes stop de una posición existente:
//--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal, //--- create a pending request and return 'false' (ModifyPosition) if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH) { //--- If the pending request object with the position ticket is not present in the list if(this.GetIndexPendingRequestByPosition(ticket)==WRONG_VALUE) { //--- Play the error sound if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type,(sl<0 ? false : true),(tp<0 ? false : true)); //--- set the last error code to the return structure int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { if(code==MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED || code==MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED) code=10027; trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } //--- Waiting time in milliseconds: //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value, ulong wait=method; //--- 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 a type of a conducted operation, as well as a symbol and a ticket of a modified position to the request structure this.m_request.action=TRADE_ACTION_SLTP; this.m_request.symbol=symbol_obj.Name(); this.m_request.position=ticket; this.m_request.type=order_type; this.m_request.volume=order.Volume(); //--- Set the number of trading attempts and create a pending request uchar attempts=(this.m_total_try < 1 ? 1 : this.m_total_try); this.CreatePendingRequest(PEND_REQ_STATUS_SLTP,(uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj,order); } return false; }
Y en los demás métodos en los que se conoce una orden o posición, transmitimos el objeto de orden al método para crear una solicitud pendiente.
Al realizar la simulación, hemos notado que en el método de cierre de posiciones ClosePosition() a veces se transmitía un volumen incorrecto para la posición cerrada al darse el cierre parcial: simplemente se enviaba cero como volumen, lo que provocaba la omisión de su comprobación. Y, debido a ello, nunca se corregía en ninguna parte. Para corregir esta situación, ya hemos añadido más arriba al método de comprobación de permiso de comercio la comprobación del volumen al cerrar una posición; además, ahora le añadiremos el registro del volumen correcto al crear una solicitud pendiente y el envío del mismo al método de comprobación.
Para ello, añadimos al método una línea:
//--- Write a deviation and a comment to the request structure this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation); this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.volume=(volume==WRONG_VALUE || volume>order.Volume() ? order.Volume() : symbol_obj.NormalizedLot(volume)); //--- If there are trading and volume limitations //--- there are limitations on FreezeLevel - play the error sound and exit ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(volume,0,action,order_type,symbol_obj,trade_obj,DFUN,0,0,0,ticket); if(method!=ERROR_CODE_PROCESSING_METHOD_OK) {
Aquí, anotamos el volumen en la estructura de la solicitud comercial de la forma siguiente: si se ha transmitido al método el valor -1 o el valor de volumen transmitido al método es superior al volumen de la posición cerrada, anotaremos en la estructura el volumen completo de la posición (cierre completo). De lo contrario, anotaremos en la estructura el volumen normalizado transmitido al método (puede darse incorrecto, por ejemplo, al dividir un volumen de 0.05 por 2 con un cierre parcial de posición, al método llegará 0.025, lo que provocará un error)
En el método de modificación de órdenes pendientes ModifyOrder()
añadimos adicionalmente a la estructura de la solicitud comercial el identificador del número mágico y el volumen de la orden:
//--- Write the magic number, volume, filling type, as well as expiration date and type to the request structure this.m_request.magic=order.GetMagicID((uint)order.Magic()); this.m_request.volume=order.Volume(); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : order.TypeFilling()); this.m_request.expiration=(expiration>WRONG_VALUE ? expiration : order.TimeExpiration()); this.m_request.type_time=(type_time>WRONG_VALUE ? type_time : order.TypeTime()); //--- If there are trading limitations, //--- StopLevel or FreezeLevel limitations, play the error sound and exit ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(0,this.m_request.price,action,order_type,symbol_obj,trade_obj,DFUN,this.m_request.stoplimit,this.m_request.sl,this.m_request.tp,ticket); if(method!=ERROR_CODE_PROCESSING_METHOD_OK) {
Para el comercio real, esto no nos ofrece nada concreto, pues la orden se modifica según el ticket, pero para las solicitudes pendientes, esto posibilitará la muestra correcta en el diario de las entradas sobre la orden modificada.
El método para crear una solicitud pendiente ha sufrido algunos cambios:
//+------------------------------------------------------------------+ //| Create a pending request | //+------------------------------------------------------------------+ bool CTrading::CreatePendingRequest(const ENUM_PEND_REQ_STATUS status, const uchar id, const uchar attempts, const ulong wait, const MqlTradeRequest &request, const int retcode, CSymbol *symbol_obj, COrder *order) { //--- Create a new pending request object depending on a request status CPendRequest *req_obj=NULL; switch(status) { case PEND_REQ_STATUS_OPEN : req_obj=new CPendReqOpen(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); break; case PEND_REQ_STATUS_CLOSE : req_obj=new CPendReqClose(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); break; case PEND_REQ_STATUS_SLTP : req_obj=new CPendReqSLTP(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); break; case PEND_REQ_STATUS_PLACE : req_obj=new CPendReqPlace(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); break; case PEND_REQ_STATUS_REMOVE : req_obj=new CPendReqRemove(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); break; case PEND_REQ_STATUS_MODIFY : req_obj=new CPendReqModify(id,symbol_obj.BidLast(),symbol_obj.Time(),request,retcode); break; default: req_obj=NULL; break; } 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; } //--- Fill in the properties 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); if(order!=NULL) { req_obj.SetActualVolume(order.Volume()); req_obj.SetActualPrice(order.PriceOpen()); req_obj.SetActualStopLimit(order.PriceStopLimit()); req_obj.SetActualSL(order.StopLoss()); req_obj.SetActualTP(order.TakeProfit()); req_obj.SetActualTypeFilling(order.TypeFilling()); req_obj.SetActualTypeTime(order.TypeTime()); req_obj.SetActualExpiration(order.TimeExpiration()); } else { req_obj.SetActualVolume(request.volume); req_obj.SetActualPrice(request.price); req_obj.SetActualStopLimit(request.stoplimit); req_obj.SetActualSL(request.sl); req_obj.SetActualTP(request.tp); req_obj.SetActualTypeFilling(request.type_filling); req_obj.SetActualTypeTime(request.type_time); req_obj.SetActualExpiration(request.expiration); } //--- Display a brief description of a created pending request if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CREATED)," #",req_obj.ID(),":"); req_obj.PrintShort(); } //--- successful return true; } //+------------------------------------------------------------------+
Adelantándonos un poco (ya que ahora vamos a corregir la clase comercial principal), debemos decir que, cuando vayamos a mejorar el objeto de solicitud pendiente, introduciremos los métodos de establecimiento de sus valores factuales, lo que nos dará la posibilidad de comparar el estado del objeto, viendo si ha cambiado (para ser más concretos, si se ha activado ya esta solicitud); para ello, compararemos el estado de las propiedades de la orden con los valores escritos en las propiedades del objeto de solicitud pendiente. Si son completamente iguales, consideraremos que la solicitud pendiente ha cumplido su función.
Precisamente en el método de creación de solicitudes comerciales pendientes comprobamos qué se ha transmitido como objeto de orden. Si se ha transmitido algo distinto a NULL (el objeto de orden existe), estableceremos en el objeto de solicitud pendiente los parámetros iniciales del objeto de orden. De lo contrario, los parámetros se establecerán a partir de la estructura de solicitud pendiente.
Estos son todos los cambios de la clase comercial principal.
Ahora, vamos a ocuparnos de mejorar la clase del objeto de solicitud pendiente.
Podemos ver que han aparecido nuevas propiedades del objeto de solicitud pendiente. Las grabaremos en el archivo Defines.mqh.
Añadimos las nuevas propiedades a las propiedades de tipo entero de la solicitud pendiente, y cambiamos el número de propiedades de tipo entero de 19 a 22:
//+------------------------------------------------------------------+ //| Integer properties of a pending trading request | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_PROP_INTEGER { PEND_REQ_PROP_STATUS = 0, // Trading request status (from the ENUM_PEND_REQ_STATUS enumeration) PEND_REQ_PROP_TYPE, // Trading request type (from the ENUM_PEND_REQ_TYPE enumeration) PEND_REQ_PROP_ID, // Trading request ID PEND_REQ_PROP_RETCODE, // Result a request is based on PEND_REQ_PROP_TIME_CREATE, // Request creation time PEND_REQ_PROP_TIME_ACTIVATE, // Next attempt activation time PEND_REQ_PROP_WAITING, // Waiting time between requests PEND_REQ_PROP_CURRENT_ATTEMPT, // Current attempt index PEND_REQ_PROP_TOTAL, // Number of attempts PEND_REQ_PROP_ACTUAL_TYPE_FILLING, // Actual order filling type PEND_REQ_PROP_ACTUAL_TYPE_TIME, // Actual order expiration type PEND_REQ_PROP_ACTUAL_EXPIRATION, // Actual order lifetime //--- MqlTradeRequest PEND_REQ_PROP_MQL_REQ_ACTION, // Type of a performed action in the request structure PEND_REQ_PROP_MQL_REQ_TYPE, // Order type in the request structure PEND_REQ_PROP_MQL_REQ_MAGIC, // EA stamp (magic number ID) in the request structure PEND_REQ_PROP_MQL_REQ_ORDER, // Order ticket in the request structure PEND_REQ_PROP_MQL_REQ_POSITION, // Position ticket in the request structure PEND_REQ_PROP_MQL_REQ_POSITION_BY, // Opposite position ticket in the request structure PEND_REQ_PROP_MQL_REQ_DEVIATION, // Maximum acceptable deviation from a requested price in the request structure PEND_REQ_PROP_MQL_REQ_EXPIRATION, // Order expiration time (for ORDER_TIME_SPECIFIED type orders) in the request structure PEND_REQ_PROP_MQL_REQ_TYPE_FILLING, // Order filling type in the request structure PEND_REQ_PROP_MQL_REQ_TYPE_TIME, // Order lifetime type in the request structure }; #define PEND_REQ_PROP_INTEGER_TOTAL (22) // Total number of integer event properties #define PEND_REQ_PROP_INTEGER_SKIP (0) // Number of request properties not used in sorting //+------------------------------------------------------------------+
Añadimos las nuevas propiedades a la lista de propiedades de tipo real y cambiamos su número de 6 a 11:
//+------------------------------------------------------------------+ //| Real properties of a pending trading request | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_PROP_DOUBLE { PEND_REQ_PROP_PRICE_CREATE = PEND_REQ_PROP_INTEGER_TOTAL,// Price at the moment of a request generation PEND_REQ_PROP_ACTUAL_VOLUME, // Actual volume PEND_REQ_PROP_ACTUAL_PRICE, // Actual order price PEND_REQ_PROP_ACTUAL_STOPLIMIT, // Actual stoplimit order price PEND_REQ_PROP_ACTUAL_SL, // Actual stoploss order price PEND_REQ_PROP_ACTUAL_TP, // Actual takeprofit order price //--- MqlTradeRequest PEND_REQ_PROP_MQL_REQ_VOLUME, // Requested volume of a deal in lots in the request structure PEND_REQ_PROP_MQL_REQ_PRICE, // Price in the request structure PEND_REQ_PROP_MQL_REQ_STOPLIMIT, // StopLimit level in the request structure PEND_REQ_PROP_MQL_REQ_SL, // Stop Loss level in the request structure PEND_REQ_PROP_MQL_REQ_TP, // Take Profit level in the request structure }; #define PEND_REQ_PROP_DOUBLE_TOTAL (11) // Total number of event's real properties #define PEND_REQ_PROP_DOUBLE_SKIP (0) // Number of order properties not used in sorting //+------------------------------------------------------------------+
Asimismo, añadimos la clasificación según los nuevos criterios a la lista de posibles criterios de clasificación:
//+------------------------------------------------------------------+ //| Possible pending request sorting criteria | //+------------------------------------------------------------------+ #define FIRST_PREQ_DBL_PROP (PEND_REQ_PROP_INTEGER_TOTAL-PEND_REQ_PROP_INTEGER_SKIP) #define FIRST_PREQ_STR_PROP (PEND_REQ_PROP_INTEGER_TOTAL-PEND_REQ_PROP_INTEGER_SKIP+PEND_REQ_PROP_DOUBLE_TOTAL-PEND_REQ_PROP_DOUBLE_SKIP) enum ENUM_SORT_PEND_REQ_MODE { //--- Sort by integer properties SORT_BY_PEND_REQ_STATUS = 0, // Sort by a trading request status (from the ENUM_PEND_REQ_STATUS enumeration) SORT_BY_PEND_REQ_TYPE, // Sort by a trading request type (from the ENUM_PEND_REQ_TYPE enumeration) SORT_BY_PEND_REQ_ID, // Sort by a trading request ID SORT_BY_PEND_REQ_RETCODE, // Sort by a result a request is based on SORT_BY_PEND_REQ_TIME_CREATE, // Sort by a request generation time SORT_BY_PEND_REQ_TIME_ACTIVATE, // Sort by next attempt activation time SORT_BY_PEND_REQ_WAITING, // Sort by a waiting time between requests SORT_BY_PEND_REQ_CURENT, // Sort by the current attempt index SORT_BY_PEND_REQ_TOTAL, // Sort by a number of attempts SORT_BY_PEND_REQ_ACTUAL_TYPE_FILLING, // Sort by actual order filling type SORT_BY_PEND_REQ_ACTUAL_TYPE_TIME, // Sort by actual order expiration type SORT_BY_PEND_REQ_ACTUAL_EXPIRATION, // Sort by actual order lifetime //--- MqlTradeRequest SORT_BY_PEND_REQ_MQL_REQ_ACTION, // Sort by a type of a performed action in the request structure SORT_BY_PEND_REQ_MQL_REQ_TYPE, // Sort by an order type in the request structure SORT_BY_PEND_REQ_MQL_REQ_MAGIC, // Sort by an EA stamp (magic number ID) in the request structure SORT_BY_PEND_REQ_MQL_REQ_ORDER, // Sort by an order ticket in the request structure SORT_BY_PEND_REQ_MQL_REQ_POSITION, // Sort by a position ticket in the request structure SORT_BY_PEND_REQ_MQL_REQ_POSITION_BY, // Sort by an opposite position ticket in the request structure SORT_BY_PEND_REQ_MQL_REQ_DEVIATION, // Sort by a maximum acceptable deviation from a requested price in the request structure SORT_BY_PEND_REQ_MQL_REQ_EXPIRATION, // Sort by an order expiration time (for ORDER_TIME_SPECIFIED type orders) in the request structure SORT_BY_PEND_REQ_MQL_REQ_TYPE_FILLING, // Sort by an order filling type in the request structure SORT_BY_PEND_REQ_MQL_REQ_TYPE_TIME, // Sort by order lifetime type in the request structure //--- Sort by real properties SORT_BY_PEND_REQ_PRICE_CREATE = FIRST_PREQ_DBL_PROP, // Sort by a price at the moment of a request generation SORT_BY_PEND_REQ_ACTUAL_VOLUME, // Sort by initial volume SORT_BY_PEND_REQ_ACTUAL_PRICE, // Sort by actual order price SORT_BY_PEND_REQ_ACTUAL_STOPLIMIT, // Sort by actual stoplimit order price SORT_BY_PEND_REQ_ACTUAL_SL, // Sort by actual stoploss order price SORT_BY_PEND_REQ_ACTUAL_TP, // Sort by actual takeprofit order price //--- MqlTradeRequest SORT_BY_PEND_REQ_MQL_REQ_VOLUME, // Sort by a requested volume of a deal in lots in the request structure SORT_BY_PEND_REQ_MQL_REQ_PRICE, // Sort by a price in the request structure SORT_BY_PEND_REQ_MQL_REQ_STOPLIMIT, // Sort by StopLimit order level in the request structure SORT_BY_PEND_REQ_MQL_REQ_SL, // Sort by StopLoss order level in the request structure SORT_BY_PEND_REQ_MQL_REQ_TP, // Sort by TakeProfit order level in the request structure //--- Sort by string properties //--- MqlTradeRequest SORT_BY_PEND_REQ_MQL_SYMBOL = FIRST_PREQ_STR_PROP, // Sort by a trading instrument name in the request structure SORT_BY_PEND_REQ_MQL_COMMENT // Sort by an order comment in the request structure }; //+------------------------------------------------------------------+
Ahora, pasemos a los objetos.
Abrimos el archivo de la clase del objeto básico de la solicitud pendiente abstracta \MQL5\Include\DoEasy\Objects\PendRequest\PendRequest.mqh e introducimos todos los cambios necesarios.
Declaramos el objeto de clase de pausa en la sección privada de la clase, y declaramos en la sección protegida dos métodos sobrecargados que comparan el valor de las propiedades del objeto con el valor transmitido al método y el conjunto de métodos que retornan las banderas de finalización del funcionamiento de la solicitud pendiente al cambiar cada uno de los parámetros de la orden/posición:
//+------------------------------------------------------------------+ //| Abstract pending trading request class | //+------------------------------------------------------------------+ class CPendRequest : public CObject { private: MqlTradeRequest m_request; // Trade request structure CPause m_pause; // Pause class object //--- Copy trading request data void CopyRequest(const MqlTradeRequest &request); //--- Return the index of the array the request (1) double and (2) string properties are actually located at int IndexProp(ENUM_PEND_REQ_PROP_DOUBLE property)const { return(int)property-PEND_REQ_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_PEND_REQ_PROP_STRING property)const { return(int)property-PEND_REQ_PROP_INTEGER_TOTAL-PEND_REQ_PROP_DOUBLE_TOTAL; } protected: int m_digits; // Number of decimal places in a quote int m_digits_lot; // Number of decimal places in the symbol lot value bool m_is_hedge; // Hedging account flag long m_long_prop[PEND_REQ_PROP_INTEGER_TOTAL]; // Request integer properties double m_double_prop[PEND_REQ_PROP_DOUBLE_TOTAL]; // Request real properties string m_string_prop[PEND_REQ_PROP_STRING_TOTAL]; // Request string properties //--- Constructor paramétrico protegido CPendRequest(const ENUM_PEND_REQ_STATUS status, const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode); //--- Return (1) the magic number specified in the settings, (2) hedging account flag, (3) flag indicating the real property is equal to the value ushort GetMagicID(void) const { return ushort(this.GetProperty(PEND_REQ_PROP_MQL_REQ_MAGIC) & 0xFFFF);} bool IsHedge(void) const { return this.m_is_hedge; } bool IsEqualByMode(const int mode,const double value) const; bool IsEqualByMode(const int mode,const long value) const; //--- Return the flags indicating the pending request has completed changing each of the order/position parameters bool IsCompletedVolume(void) const; bool IsCompletedPrice(void) const; bool IsCompletedStopLimit(void) const; bool IsCompletedStopLoss(void) const; bool IsCompletedTakeProfit(void) const; bool IsCompletedTypeFilling(void) const; bool IsCompletedTypeTime(void) const; bool IsCompletedExpiration(void) const; public:
En la sección pública de la clase, declaramos el método virtual que retorna la bandera de finalización del funcionamiento de una solicitud pendiente, el método que retorna por completo el actual objeto de solicitud, y los métodos para establecer y retornar los parámetros del objeto de pausa:
public: //--- Constructor por defecto CPendRequest(){;} //--- Set request (1) integer, (2) real and (3) string properties void SetProperty(ENUM_PEND_REQ_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_PEND_REQ_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_PEND_REQ_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string request properties from the properties array long GetProperty(ENUM_PEND_REQ_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_PEND_REQ_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_PEND_REQ_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return the flag of the request supporting the property virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property) { return true; } //--- Return the flag indicating the pending request has completed its work virtual bool IsCompleted(void) const { return false;} //--- Return itself CPendRequest *GetObject(void) { return &this;} //--- Compare CPendRequest objects by a specified property (to sort the lists by a specified request object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CPendRequest objects by all properties (to search for equal request objects) bool IsEqual(CPendRequest* compared_obj); //--- Return (1) the elapsed number of milliseconds, (2) waiting time completion from the pause object, //--- time of the (3) pause countdown start in milliseconds and (4) waiting time ulong PausePassed(void) const { return this.m_pause.Passed(); } bool PauseIsCompleted(void) const { return this.m_pause.IsCompleted(); } ulong PauseTimeBegin(void) const { return this.m_pause.TimeBegin(); } ulong PauseTimeWait(void) const { return this.m_pause.TimeWait(); } //--- Return the description (1) of an elapsed number of pause waiting seconds, //--- (2) pause countdown start time, as well as waiting time (3) in milliseconds and (4) in seconds string PausePassedDescription(void) const { return this.m_pause.PassedDescription(); } string PauseTimeBeginDescription(void) const { return this.m_pause.TimeBeginDescription(); } string PauseWaitingMSCDescription(void) const { return this.m_pause.WaitingMSCDescription(); } string PauseWaitingSecDescription(void) const { return this.m_pause.WaitingSECDescription(); } //+------------------------------------------------------------------+
Aquí (en el objeto padre), el método virtual IsCompleted() siempre retorna false, y será implementado individualmente para cada objeto heredero. Los métodos de trabajo con el objeto de pausa simplemente llaman los métodos correspondientes de este objeto analizados al principio artículo.
Añadimos al bloque de métodos de acceso simplificado a las propiedades del objeto de solicitud pendiente los métodos que retornan las propiedades factuales de la orden:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the request object properties | //+------------------------------------------------------------------+ //--- Return (1) request structure, (2) status, (3) type, (4) price at the moment of the request generation, //--- (5) request generation time, (6) next attempt activation time, //--- (7) waiting time between requests, (8) current attempt index, //--- (9) number of attempts, (10) request ID //--- (11) result a request is based on, //--- (12) order ticket, (13) position ticket, (14) trading operation type MqlTradeRequest MqlRequest(void) const { return this.m_request; } ENUM_PEND_REQ_STATUS Status(void) const { return (ENUM_PEND_REQ_STATUS)this.GetProperty(PEND_REQ_PROP_STATUS); } ENUM_PEND_REQ_TYPE TypeRequest(void) const { return (ENUM_PEND_REQ_TYPE)this.GetProperty(PEND_REQ_PROP_TYPE); } double PriceCreate(void) const { return this.GetProperty(PEND_REQ_PROP_PRICE_CREATE); } ulong TimeCreate(void) const { return this.GetProperty(PEND_REQ_PROP_TIME_CREATE); } ulong TimeActivate(void) const { return this.GetProperty(PEND_REQ_PROP_TIME_ACTIVATE); } ulong WaitingMSC(void) const { return this.GetProperty(PEND_REQ_PROP_WAITING); } uchar CurrentAttempt(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_CURRENT_ATTEMPT); } uchar TotalAttempts(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_TOTAL); } uchar ID(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_ID); } int Retcode(void) const { return (int)this.GetProperty(PEND_REQ_PROP_RETCODE); } ulong Order(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_ORDER); } ulong Position(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_POSITION); } ENUM_TRADE_REQUEST_ACTIONS Action(void) const { return (ENUM_TRADE_REQUEST_ACTIONS)this.GetProperty(PEND_REQ_PROP_MQL_REQ_ACTION); } //--- Return the actual (1) volume, (2) order, (3) limit order, //--- (4) stoploss order and (5) takeprofit order prices, (6) order filling type, //--- (7) order expiration type and (8) order lifetime double ActualVolume(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_VOLUME); } double ActualPrice(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_PRICE); } double ActualStopLimit(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT); } double ActualSL(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_SL); } double ActualTP(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_TP); } ENUM_ORDER_TYPE_FILLING ActualTypeFilling(void) const { return (ENUM_ORDER_TYPE_FILLING)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING); } ENUM_ORDER_TYPE_TIME ActualTypeTime(void) const { return (ENUM_ORDER_TYPE_TIME)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME); } datetime ActualExpiration(void) const { return (datetime)this.GetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION); }
Cada vez que comprobemos el objeto de solicitud pendiente, en sus propiedades se anotarán las propiedades correspondientes de la orden/posición para cuyo funcionamiento se ha creado el objeto de solicitud. Estos métodos permiten obtener del objeto de solicitud las propiedades actuales de la orden que le corresponde.
En este bloque, mejoramos el método de establecimiento de la hora de creación de la solicitud pendiente y el método de establecimiento de su tiempo de espera:
//--- 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) ID, //--- (8) order ticket, (9) position ticket void SetPriceCreate(const double price) { this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price); } void SetTimeCreate(const ulong time) { this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time); this.m_pause.SetTimeBegin(time); } void SetTimeActivate(const ulong time) { this.SetProperty(PEND_REQ_PROP_TIME_ACTIVATE,time); } void SetWaitingMSC(const ulong miliseconds) { this.SetProperty(PEND_REQ_PROP_WAITING,miliseconds); this.m_pause.SetWaitingMSC(miliseconds); }
Ahora, estos métodos establecerán estos valores también para el objeto de solicitud pendiente, al mismo tiempo que establecen para el objeto de solicitud pendiente los valores transmitidos.
En este mismo bloque, escribimos los métodos para establecer el objeto de solicitud de las propiedades factuales del objeto de orden:
//--- Set the actual (1) volume, (2) order, (3) limit order, //--- (4) stoploss order and (5) takeprofit order prices, (6) order filling type, //--- (7) order expiration type and (8) order lifetime void SetActualVolume(const double volume) { this.SetProperty(PEND_REQ_PROP_ACTUAL_VOLUME,volume); } void SetActualPrice(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_PRICE,price); } void SetActualStopLimit(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT,price); } void SetActualSL(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_SL,price); } void SetActualTP(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TP,price); } void SetActualTypeFilling(const ENUM_ORDER_TYPE_FILLING type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING,type); } void SetActualTypeTime(const ENUM_ORDER_TYPE_TIME type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME,type); } void SetActualExpiration(const datetime expiration) { this.SetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION,expiration); } //+------------------------------------------------------------------+
En el bloque para la muestra de las propiedades del objeto, añadimos la definición de los métodos que retornan las descripciones de las propiedades factuales de la orden, y al final del todo, añadimos un método virtual que retorna el "encabezado" (descripción breve del objeto) que describe el objeto de solicitud pendiente:
//+------------------------------------------------------------------+ //| Descriptions of request object properties | //+------------------------------------------------------------------+ //--- Get description of a request (1) integer, (2) real and (3) string property string GetPropertyDescription(ENUM_PEND_REQ_PROP_INTEGER property); string GetPropertyDescription(ENUM_PEND_REQ_PROP_DOUBLE property); string GetPropertyDescription(ENUM_PEND_REQ_PROP_STRING property); //--- Return the names of pending request object parameters string StatusDescription(void) const; string TypeRequestDescription(void) const; string IDDescription(void) const; string RetcodeDescription(void) const; string TimeCreateDescription(void) const; string TimeActivateDescription(void) const; string TimeWaitingDescription(void) const; string CurrentAttemptDescription(void) const; string TotalAttemptsDescription(void) const; string PriceCreateDescription(void) const; string TypeFillingActualDescription(void) const; string TypeTimeActualDescription(void) const; string ExpirationActualDescription(void) const; string VolumeActualDescription(void) const; string PriceActualDescription(void) const; string StopLimitActualDescription(void) const; string StopLossActualDescription(void) const; string TakeProfitActualDescription(void) const; //--- Return the names of trading request structures parameters in the request object string MqlReqActionDescription(void) const; string MqlReqMagicDescription(void) const; string MqlReqOrderDescription(void) const; string MqlReqSymbolDescription(void) const; string MqlReqVolumeDescription(void) const; string MqlReqPriceDescription(void) const; string MqlReqStopLimitDescription(void) const; string MqlReqStopLossDescription(void) const; string MqlReqTakeProfitDescription(void) const; string MqlReqDeviationDescription(void) const; string MqlReqTypeOrderDescription(void) const; string MqlReqTypeFillingDescription(void) const; string MqlReqTypeTimeDescription(void) const; string MqlReqExpirationDescription(void) const; string MqlReqCommentDescription(void) const; string MqlReqPositionDescription(void) const; string MqlReqPositionByDescription(void) const; //--- Display (1) description of request properties (full_prop=true - all properties, false - only supported ones), //--- (2) short message about the request, (3) short request name (2 and 3 - implementation in the class descendants) void Print(const bool full_prop=false); virtual void PrintShort(void){;} virtual string Header(void){return NULL;} }; //+------------------------------------------------------------------+
Veremos los métodos de descripción de las propiedades factuales del objeto de orden un poco más tarde. El método virtual para describir el encabezado de la solicitud comercial retorna aquí NULL, y será implementado en los objetos herederos.
Añadimos al constructor de clase la inicialización del objeto de pausa con los valores de la hora de creación del objeto de solicitud y su tiempo de espera:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPendRequest::CPendRequest(const ENUM_PEND_REQ_STATUS status, const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) { this.CopyRequest(request); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_digits=(int)::SymbolInfoInteger(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL),SYMBOL_DIGITS); int dg=(int)DigitsLots(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL)); this.m_digits_lot=(dg==0 ? 1 : dg); this.SetProperty(PEND_REQ_PROP_STATUS,status); this.SetProperty(PEND_REQ_PROP_ID,id); this.SetProperty(PEND_REQ_PROP_RETCODE,retcode); this.SetProperty(PEND_REQ_PROP_TYPE,this.GetProperty(PEND_REQ_PROP_RETCODE)>0 ? PEND_REQ_TYPE_ERROR : PEND_REQ_TYPE_REQUEST); this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time); this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price); this.m_pause.SetTimeBegin(this.GetProperty(PEND_REQ_PROP_TIME_CREATE)); this.m_pause.SetWaitingMSC(this.GetProperty(PEND_REQ_PROP_WAITING)); } //+------------------------------------------------------------------+
De esta forma, justo al crear un nuevo objeto de solicitud pendiente, se añadirá a su objeto de pausa la hora de comienzo de la espera y la duración de la pausa.
Fuera del cuerpo de la clase, implementamos los métodos que retornan las banderas de igualdad de las propiedades de tipo entero y real del objeto de solicitud pendiente y el valor transmitido a los métodos:
//+---------------------------------------------------------------------+ //| Return the flag indicating the real value is equal to the passed one| //+---------------------------------------------------------------------+ bool CPendRequest::IsEqualByMode(const int mode,const double value) const { CPendRequest *req=new CPendRequest(); if(req==NULL) return false; req.SetProperty((ENUM_PEND_REQ_PROP_DOUBLE)mode,value); bool res=!this.Compare(req,mode); delete req; return res; } //+------------------------------------------------------------------------+ //| Return the flag indicating the integer value is equal to the passed one| //+------------------------------------------------------------------------+ bool CPendRequest::IsEqualByMode(const int mode,const long value) const { CPendRequest *req=new CPendRequest(); if(req==NULL) return false; req.SetProperty((ENUM_PEND_REQ_PROP_INTEGER)mode,value); bool res=!this.Compare(req,mode); delete req; return res; } //+------------------------------------------------------------------+
Transmitimos al método el modo usado para comparar la propiedad del objeto y el valor transmitido al método.
El modo supone la indicación de uno de los valores de propiedad del objeto pertenecientes a la lista de sus propiedades de tipo real (ENUM_PEND_REQ_PROP_DOUBLE) o entero (ENUM_PEND_REQ_PROP_INTEGER). Precisamente según esta propiedad se realizará la comparación con el valor transmitido al método.
Creamos en el método un nuevo objeto de solicitud pendiente temporal y establecemos para su propiedad correspondiente el valor transmitido al método.
El método Compare() del objeto básico CObject de la biblioteca estándar retorna 0, lo que indica igualdad. Pero el método es virtual, y se implmenta en los herederos de la clase. Aquí, el método Compare() se ha implementado de tal forma que retorna 1 si el valor del objeto actual es superior al mismo valor del objeto comparado, -1 si el valor del objeto actual es inferior al del comparado, y 0 si estos son iguales.
De esta forma, si los valores comparados son iguales, en la variable res se registrará false.
Pero nosotros anotamos allí la negación lógica del resultado de la comprobación. De esta manera, si el método de comparación retorna 0, entonces !false será igual a true. Y si es 1 o -1, entonces !true será igual a false.
Después de registrar el resultado de la comparación, eliminamos el objeto temporal y retornamos el resultado de la comparación.
Métodos que retornan las banderas de comparación de los valores factuales de las propiedades de la orden y del objeto de solicitud según sus tipos:
//+------------------------------------------------------------------+ //| Return the flag of a successful volume change | //+------------------------------------------------------------------+ bool CPendRequest::IsCompletedVolume(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_VOLUME,this.GetProperty(PEND_REQ_PROP_MQL_REQ_VOLUME)); } //+------------------------------------------------------------------+ //| Return the flag of a successful price modification | //+------------------------------------------------------------------+ bool CPendRequest::IsCompletedPrice(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_PRICE,this.GetProperty(PEND_REQ_PROP_MQL_REQ_PRICE)); } //+-------------------------------------------------------------------+ //| Return the flag of a successful StopLimit order price modification| //+-------------------------------------------------------------------+ bool CPendRequest::IsCompletedStopLimit(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_STOPLIMIT,this.GetProperty(PEND_REQ_PROP_MQL_REQ_STOPLIMIT)); } //+------------------------------------------------------------------+ //| Return the flag of a successful StopLoss order modification | //+------------------------------------------------------------------+ bool CPendRequest::IsCompletedStopLoss(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_SL,this.GetProperty(PEND_REQ_PROP_MQL_REQ_SL)); } //+------------------------------------------------------------------+ //| Return the flag of a successful TakeProfit order modification | //+------------------------------------------------------------------+ bool CPendRequest::IsCompletedTakeProfit(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_TP,this.GetProperty(PEND_REQ_PROP_MQL_REQ_TP)); } //+------------------------------------------------------------------+ //| Return the flag of a successful order filling type modification | //+------------------------------------------------------------------+ bool CPendRequest::IsCompletedTypeFilling(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_TYPE_FILLING,this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE_FILLING)); } //+-------------------------------------------------------------------+ //| Return the flag of a successful order expiration type modification| //+-------------------------------------------------------------------+ bool CPendRequest::IsCompletedTypeTime(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_TYPE_TIME,this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE_TIME)); } //+------------------------------------------------------------------+ //| Return the flag of a successful order lifetime modification | //+------------------------------------------------------------------+ bool CPendRequest::IsCompletedExpiration(void) const { return this.IsEqualByMode(PEND_REQ_PROP_ACTUAL_EXPIRATION,this.GetProperty(PEND_REQ_PROP_MQL_REQ_EXPIRATION)); } //+------------------------------------------------------------------+
Los métodos retornan el resultado de la comparación de las dos propiedades correspondientes (la factual y la anotada en el objeto), con la ayuda del método de comparación que acabamos de analizar.
Añadimos las nuevas propiedades a la implementación de los métodos que retornan las propiedades del objeto:
//+------------------------------------------------------------------+ //| Return the description of a request integer property | //+------------------------------------------------------------------+ string CPendRequest::GetPropertyDescription(ENUM_PEND_REQ_PROP_INTEGER property) { return ( property==PEND_REQ_PROP_STATUS ? this.StatusDescription() : property==PEND_REQ_PROP_TYPE ? this.TypeRequestDescription() : property==PEND_REQ_PROP_ID ? this.IDDescription() : property==PEND_REQ_PROP_RETCODE ? this.RetcodeDescription() : property==PEND_REQ_PROP_TIME_CREATE ? this.TimeCreateDescription() : property==PEND_REQ_PROP_TIME_ACTIVATE ? this.TimeActivateDescription() : property==PEND_REQ_PROP_WAITING ? this.TimeWaitingDescription() : property==PEND_REQ_PROP_CURRENT_ATTEMPT ? this.CurrentAttemptDescription() : property==PEND_REQ_PROP_TOTAL ? this.TotalAttemptsDescription() : property==PEND_REQ_PROP_ACTUAL_TYPE_FILLING ? this.TypeFillingActualDescription() : property==PEND_REQ_PROP_ACTUAL_TYPE_TIME ? this.TypeTimeActualDescription() : property==PEND_REQ_PROP_ACTUAL_EXPIRATION ? this.ExpirationActualDescription() : //--- MqlTradeRequest property==PEND_REQ_PROP_MQL_REQ_ACTION ? this.MqlReqActionDescription() : property==PEND_REQ_PROP_MQL_REQ_TYPE ? this.MqlReqTypeOrderDescription() : property==PEND_REQ_PROP_MQL_REQ_MAGIC ? this.MqlReqMagicDescription() : property==PEND_REQ_PROP_MQL_REQ_ORDER ? this.MqlReqOrderDescription() : property==PEND_REQ_PROP_MQL_REQ_POSITION ? this.MqlReqPositionDescription() : property==PEND_REQ_PROP_MQL_REQ_POSITION_BY ? this.MqlReqPositionByDescription() : property==PEND_REQ_PROP_MQL_REQ_DEVIATION ? this.MqlReqDeviationDescription() : property==PEND_REQ_PROP_MQL_REQ_EXPIRATION ? this.MqlReqExpirationDescription() : property==PEND_REQ_PROP_MQL_REQ_TYPE_FILLING ? this.MqlReqTypeFillingDescription() : property==PEND_REQ_PROP_MQL_REQ_TYPE_TIME ? this.MqlReqTypeTimeDescription() : ::EnumToString(property) ); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the description of the request real property | //+------------------------------------------------------------------+ string CPendRequest::GetPropertyDescription(ENUM_PEND_REQ_PROP_DOUBLE property) { return ( property==PEND_REQ_PROP_PRICE_CREATE ? this.PriceCreateDescription() : property==PEND_REQ_PROP_ACTUAL_VOLUME ? this.VolumeActualDescription() : property==PEND_REQ_PROP_ACTUAL_PRICE ? this.PriceActualDescription() : property==PEND_REQ_PROP_ACTUAL_STOPLIMIT ? this.StopLimitActualDescription() : property==PEND_REQ_PROP_ACTUAL_SL ? this.StopLossActualDescription() : property==PEND_REQ_PROP_ACTUAL_TP ? this.TakeProfitActualDescription() : //--- MqlTradeRequest property==PEND_REQ_PROP_MQL_REQ_VOLUME ? this.MqlReqVolumeDescription() : property==PEND_REQ_PROP_MQL_REQ_PRICE ? this.MqlReqPriceDescription() : property==PEND_REQ_PROP_MQL_REQ_STOPLIMIT ? this.MqlReqStopLimitDescription() : property==PEND_REQ_PROP_MQL_REQ_SL ? this.MqlReqStopLossDescription() : property==PEND_REQ_PROP_MQL_REQ_TP ? this.MqlReqTakeProfitDescription() : ::EnumToString(property) ); } //+------------------------------------------------------------------+
En el método se comprueba la propiedad de objeto transmitida al mismo, y se retorna su descripción.
Los métodos que retornan la descripción de las propiedades factuales de la orden están registrados en el objeto de solicitud:
//+------------------------------------------------------------------+ //| Return the description of the actual order filling mode | //+------------------------------------------------------------------+ string CPendRequest::TypeFillingActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TYPE_FILLING)+": "+OrderTypeFillingDescription((ENUM_ORDER_TYPE_FILLING)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING)); } //+------------------------------------------------------------------+ //| Return the actual order lifetime type | //+------------------------------------------------------------------+ string CPendRequest::TypeTimeActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TYPE_TIME)+": "+OrderTypeTimeDescription((ENUM_ORDER_TYPE_TIME)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME)); } //+------------------------------------------------------------------+ //| Return the description of the actual order lifetime | //+------------------------------------------------------------------+ string CPendRequest::ExpirationActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_EXPIRATION)+": "+ (this.GetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION)>0 ? ::TimeToString(this.GetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION)) : CMessage::Text(MSG_LIB_PROP_NOT_SET)); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the description of the actual order volume | //+------------------------------------------------------------------+ string CPendRequest::VolumeActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_VOLUME)+": "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_ACTUAL_VOLUME),this.m_digits_lot); } //+------------------------------------------------------------------+ //| Return the description of the actual order price | //+------------------------------------------------------------------+ string CPendRequest::PriceActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_PRICE)+": "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_ACTUAL_PRICE),this.m_digits); } //+------------------------------------------------------------------+ //| Return the description of the actual StopLimit order price | //+------------------------------------------------------------------+ string CPendRequest::StopLimitActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_STOPLIMIT)+": "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT),this.m_digits); } //+------------------------------------------------------------------+ //| Return the description of the actual StopLoss order price | //+------------------------------------------------------------------+ string CPendRequest::StopLossActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_SL)+": "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_ACTUAL_SL),this.m_digits); } //+------------------------------------------------------------------+ //| Return the description of the actual TakeProfit order price | //+------------------------------------------------------------------+ string CPendRequest::TakeProfitActualDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_TP)+": "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_ACTUAL_TP),this.m_digits); } //+------------------------------------------------------------------+
En los métodos simplemente se compara y se retorna el texto de los mensajes que se corresponde con la propiedad retornada por el método.
Estos son todos los cambios del objeto básico de la solicitud pendiente abstracta.
Ahora, vamos a introducir los cambios en las clases herederas del objeto de solicitud básico.
Abrimos el archivo de la clase del objeto de solicitud pendiente para la apertura de posiciones \MQL5\Include\DoEasy\Objects\PendRequest\PendReqOpen.mqh e introducimos las correcciones.
Añadimos la definición del método virtual que retorna la denominación breve de la solicitud:
//+------------------------------------------------------------------+ //| Pending request for opening a position | //+------------------------------------------------------------------+ class CPendReqOpen : public CPendRequest { public: //--- Constructor CPendReqOpen(const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) : CPendRequest(PEND_REQ_STATUS_OPEN,id,price,time,request,retcode) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property); //--- Display a brief message with request data and (2) a short request name in the journal virtual void PrintShort(void); virtual string Header(void); }; //+------------------------------------------------------------------+
Y su implementación:
//+------------------------------------------------------------------+ //| Return the short request name | //+------------------------------------------------------------------+ string CPendReqOpen::Header(void) { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_OPEN)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Clase de solicitud pendiente para la modificación de órdenes stop de una posición (archivo PendReqSLTP.mqh):
//+------------------------------------------------------------------+ //| Pending request to modify position stop orders | //+------------------------------------------------------------------+ class CPendReqSLTP : public CPendRequest { public: //--- Constructor CPendReqSLTP(const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) : CPendRequest(PEND_REQ_STATUS_SLTP,id,price,time,request,retcode) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property); //--- Return the flag indicating the pending request has completed its work virtual bool IsCompleted(void) const; //--- Display a brief message with request data and (2) a short request name in the journal virtual void PrintShort(void); virtual string Header(void); }; //+------------------------------------------------------------------+
Añadimos las nuevas propiedades a los métodos que retornan la bandera de soporte de algunas de sus propiedades por parte del objeto:
//+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CPendReqSLTP::SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property) { if(property==PEND_REQ_PROP_MQL_REQ_POSITION_BY || property==PEND_REQ_PROP_MQL_REQ_ORDER || property==PEND_REQ_PROP_MQL_REQ_EXPIRATION || property==PEND_REQ_PROP_MQL_REQ_DEVIATION || property==PEND_REQ_PROP_MQL_REQ_TYPE_FILLING || property==PEND_REQ_PROP_MQL_REQ_TYPE_TIME || property==PEND_REQ_PROP_ACTUAL_EXPIRATION || property==PEND_REQ_PROP_ACTUAL_TYPE_TIME || property==PEND_REQ_PROP_ACTUAL_TYPE_FILLING ) return false; return true; } //+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CPendReqSLTP::SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property) { if(property==PEND_REQ_PROP_PRICE_CREATE || property==PEND_REQ_PROP_ACTUAL_SL || property==PEND_REQ_PROP_ACTUAL_TP || property==PEND_REQ_PROP_MQL_REQ_SL || property==PEND_REQ_PROP_MQL_REQ_TP ) return true; return false; } //+------------------------------------------------------------------+
Método virtual que retorna la bandera de finalización del funcionamiento de una solicitud pendiente:
//+----------------------------------------------------------------------+ //| Return the flag indicating the pending request has completed its work| //+----------------------------------------------------------------------+ bool CPendReqSLTP::IsCompleted(void) const { bool res=true; res &= this.IsCompletedStopLoss(); res &= this.IsCompletedTakeProfit(); return res; } //+------------------------------------------------------------------+
Aquí: añadimos a la variable res los resultados de la comparación de la finalización de la modificación de StopLoss y TakeProfit. Si aunque sea una sola de las comprobaciones retorna false, el resultado general será false. Retornamos el resultado total desde el método.
Método que retorna la denominación breve de la solicitud:
//+------------------------------------------------------------------+ //| Return the short request name | //+------------------------------------------------------------------+ string CPendReqSLTP::Header(void) { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_SLTP)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Clase de solicitud pendiente de cierre de posición (archivo PendReqClose.mqh):
//+------------------------------------------------------------------+ //| Pending request to close a position | //+------------------------------------------------------------------+ class CPendReqClose : public CPendRequest { public: //--- Constructor CPendReqClose(const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) : CPendRequest(PEND_REQ_STATUS_CLOSE,id,price,time,request,retcode) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property); //--- Return the flag indicating the pending request has completed its work virtual bool IsCompleted(void) const; //--- Display a brief message with request data and (2) a short request name in the journal virtual void PrintShort(void); virtual string Header(void); }; //+------------------------------------------------------------------+
Método que retorna la bandera de finalización del funcionamiento de una solicitud:
//+----------------------------------------------------------------------+ //| Return the flag indicating the pending request has completed its work| //+----------------------------------------------------------------------+ bool CPendReqClose::IsCompleted(void) const { return this.IsCompletedVolume(); } //+------------------------------------------------------------------+
Método que retorna la denominación breve de la solicitud:
//+------------------------------------------------------------------+ //| Return the short request name | //+------------------------------------------------------------------+ string CPendReqClose::Header(void) { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_CLOSE)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Clase de solicitud pendiente para la colocación de órdenes pendientes (archivo PendReqPlace.mqh):
//+------------------------------------------------------------------+ //| Pending request to place a pending order | //+------------------------------------------------------------------+ class CPendReqPlace : public CPendRequest { public: //--- Constructor CPendReqPlace(const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) : CPendRequest(PEND_REQ_STATUS_PLACE,id,price,time,request,retcode) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property); //--- Display a brief message with request data and (2) a short request name in the journal virtual void PrintShort(void); virtual string Header(void); }; //+------------------------------------------------------------------+
Método que retorna la denominación breve de la solicitud:
//+------------------------------------------------------------------+ //| Return the short request name | //+------------------------------------------------------------------+ string CPendReqPlace::Header(void) { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_PLACE)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Clase de solicitud pendiente para modificar los parámetros de órdenes pendientes (archivo PendReqModify.mqh):
//+------------------------------------------------------------------+ //| Pending request to modify pending order parameters | //+------------------------------------------------------------------+ class CPendReqModify : public CPendRequest { public: //--- Constructor CPendReqModify(const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) : CPendRequest(PEND_REQ_STATUS_MODIFY,id,price,time,request,retcode) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property); //--- Return the flag indicating the pending request has completed its work virtual bool IsCompleted(void) const; //--- Display a brief message with request data and (2) a short request name in the journal virtual void PrintShort(void); virtual string Header(void); }; //+------------------------------------------------------------------+
Método que retorna la bandera de finalización del funcionamiento de una solicitud:
//+----------------------------------------------------------------------+ //| Return the flag indicating the pending request has completed its work| //+----------------------------------------------------------------------+ bool CPendReqModify::IsCompleted(void) const { bool res=true; res &= this.IsCompletedPrice(); res &= this.IsCompletedStopLimit(); res &= this.IsCompletedStopLoss(); res &= this.IsCompletedTakeProfit(); res &= this.IsCompletedTypeFilling(); res &= this.IsCompletedTypeTime(); res &= this.IsCompletedExpiration(); return res; } //+------------------------------------------------------------------+
Método que retorna la denominación breve de la solicitud:
//+------------------------------------------------------------------+ //| Return the short request name | //+------------------------------------------------------------------+ string CPendReqModify::Header(void) { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_MODIFY)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Clase de solicitud pendiente para la eliminación de una orden pendiente (archivo PendReqRemove.mqh):
//+------------------------------------------------------------------+ //| Pending request to remove a pending order | //+------------------------------------------------------------------+ class CPendReqRemove : public CPendRequest { public: //--- Constructor CPendReqRemove(const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) : CPendRequest(PEND_REQ_STATUS_REMOVE,id,price,time,request,retcode) {} //--- Supported deal properties (1) real, (2) integer virtual bool SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_DOUBLE property); virtual bool SupportProperty(ENUM_PEND_REQ_PROP_STRING property); //--- Display a brief message with request data and (2) a short request name in the journal virtual void PrintShort(void); virtual string Header(void); }; //+------------------------------------------------------------------+
Método que retorna la denominación breve de la solicitud:
//+------------------------------------------------------------------+ //| Return the short request name | //+------------------------------------------------------------------+ string CPendReqRemove::Header(void) { return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_REMOVE)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Como podemos ver por los listados de las clases y sus métodos correspondientes, aquí todo es bastante simple: cada método usa solo las propiedades del objeto de solicitud pendiente abstracta que el son inherentes. Luego, en la denominación breve de la solicitud, se crea un texto a partir de los mensajes de texto que le corresponden a la solicitud.
Con esto, hemos finalizado la mejora de las clases herederas de la clase básica del objeto de solicitud comercial pendiente abstracta.
Clase de gestión de objetos de solicitudes pendientes
Ahora, vamos a ocuparnos finalmente de la clase de control de los objetos de solicitudes pendientes.
Como ya hemos escrito anteriormente, heredaremos esta clase de la clase comercial CTrading, dado que en las clases CTrading y CPendRequest, así como en la nueva clase CTradingControl, todos los datos y métodos están estrechamente relacionados. La clase en la presente implementación no será grande, y trabajará con el temporizador. El código completo de este será trasladado desde el código del temporizador de la clase CTrading con algunas pequeñas mejoras.
Creamos la nueva clase CTradingControl en el archivo \MQL5\Include\DoEasy\TradingControl.mqh. Al crear esta clase, indicaremos como clase básica CTrading, sin olvidarnos de incluir el archivo Trading.mqh en la clase después de crear la misma:
//+------------------------------------------------------------------+ //| PendReqControl.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include "Trading.mqh" //+------------------------------------------------------------------+ //| Class for managing pending trading requests | //+------------------------------------------------------------------+ class CTradingControl : public CTrading { private: //--- Set actual order/position data to a pending request object void SetActualProperties(CPendRequest *req_obj,const COrder *order); public: //--- Return itself CTradingControl *GetObject(void) { return &this; } //--- Timer virtual void OnTimer(void); //--- Constructor CTradingControl(); }; //+------------------------------------------------------------------+
Qué podemos ver aquí:
la clase tiene el método privado SetActualProperties(), que establece en el objeto de solicitud pendiente los datos reales de la orden/posición.
En la sección pública, el método GetObject() retorna el puntero al objeto de control de solicitudes pendientes al completo, mientras que el método virtual OnTimer() actúa como temporizador de la clase de control de solicitudes pendientes, donde precisamente va a funcionar todo.
En el constructor de la clase, limpiamos la lista de solicitudes pendientes y le asignamos la bandera de lista clasificada:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradingControl::CTradingControl() { this.m_list_request.Clear(); this.m_list_request.Sort(); } //+------------------------------------------------------------------+
Esta lista pertenece a la clase CTrading, dado que es precisamente en la clase comercial donde se crean las solicitudes comerciales pendientes.
Implementamos fuera del cuerpo de la clase el método para establecer los datos reales de la orden en el objeto de solicitud:
//+------------------------------------------------------------------+ //| Set order/position data to a pending request object | //+------------------------------------------------------------------+ void CTradingControl::SetActualProperties(CPendRequest *req_obj,const COrder *order) { req_obj.SetActualExpiration(order.TimeExpiration()); req_obj.SetActualPrice(order.PriceOpen()); req_obj.SetActualSL(order.StopLoss()); req_obj.SetActualStopLimit(order.PriceStopLimit()); req_obj.SetActualTP(order.TakeProfit()); req_obj.SetActualTypeFilling(order.TypeFilling()); req_obj.SetActualTypeTime(order.TypeTime()); req_obj.SetActualVolume(order.Volume()); } //+------------------------------------------------------------------+
Transmitimos al método el puntero al objeto de solicitud pendiente y el puntero al objeto de orden. A continuación, con la ayuda de los métodos correspondientes que ya hemos analizado antes, establecemos en el objeto de solicitud los valores correspondientes de las propiedades de la orden.
En el temporizador de la clase, hemos organizado el seguimiento de todos los objetos de solicitud disponibles en la lista de solicitudes pendientes. En él se comprueba el tiempo de vida útil de cada solicitud pendiente, y si este ha expirado, la solicitud se eliminará. Si la solicitud ya ha sido procesada, es decir, ya existe el evento comercial que le corresponde en la historia de la cuenta, o existe una orden/posición con un identificador de solicitud pendiente en el número mágico que se corresponda con el identificador de la solicitud pendiente, dichas solicitudes se considerarán completamente procesadas, y serán eliminadas de la lista.
Si la solicitud aún no ha sido procesada y ha llegado su hora de activación, se envirá al servidor la orden comercial registrada en la solicitud pendiente.
En el método, hemos organizado la comprobación sobre la realización de operaciones comerciales por parte del terminal: el botón "Trading automático" y la casilla en los ajustes del asesor "Permitir trading automático". Si una solicitud comercial pendiente ha sido creada de acuerdo con el código de error 10027 (se prohíbe el trading automático por parte del terminal), al solucionar el usuario la causa del error (activando el botón "Trading automático" o estableciendo en los ajustes del asesor la casilla "Permitir trading automático"), esta solicitud pendiente será procesada de inmediato, estableciéndose para ello la nueva hora de activación de la solcitud pendiente de forma que se dé en el siguiente tick, puesto que el usuario ha corregido el error, y ya no es necesario esperar, sino enviar directamente la orden al servidor para evitar recotizaciones posteriores.
Como el método es bastante voluminoso, hemos intentado describir con detalle en los comentarios al código todas las acciones realizadas dentro del temporizador de la clase de control de solicitudes pendientes. Esperamos que la información resulte comprensible para el usuario.
Temporizador de la clase de control de solicitudes pendientes:
//+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CTradingControl::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 CPendRequest *req_obj=this.m_list_request.At(i); if(req_obj==NULL) 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 flag disabling trading in the terminal by two properties simultaneously //--- (the AutoTrading button in the terminal and the Allow Automated Trading option in the EA settings) //--- If any of the two properties is 'false', the flag is 'false' as well bool terminal_trade_allowed=::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED); terminal_trade_allowed &=::MQLInfoInteger(MQL_TRADE_ALLOWED); //--- If a request object is based on the error code if(req_obj.TypeRequest()==PEND_REQ_TYPE_ERROR) { //--- if the error has been caused by trading disabled on the terminal side and has been eliminated if(req_obj.Retcode()==10027 && terminal_trade_allowed) { //--- if the current attempt has not exceeded the defined number of trading attempts yet if(req_obj.CurrentAttempt()<req_obj.TotalAttempts()+1) { //--- Set the request creation time equal to its creation time minus waiting time, i.e. send the request immediately //--- Also, decrease the number of a successful attempt since during the next attempt, its number is increased, and if this is the last attempt, //--- it is not executed. However, this is related to fixing the error cause by a user, which means we need to give more time for the last attempt req_obj.SetTimeCreate(req_obj.TimeCreate()-req_obj.WaitingMSC()); req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()>0 ? req_obj.CurrentAttempt()-1 : 0)); } } } //--- if the current attempt exceeds the defined number of trading attempts, //--- or the current time exceeds the waiting time of all 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 || (long)symbol_obj.Time()>long(req_obj.TimeCreate()+req_obj.WaitingMSC()*req_obj.TotalAttempts())) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_DELETED)); this.m_list_request.Delete(i); continue; } //--- If this is a position opening or placing a pending order if((req_obj.Action()==TRADE_ACTION_DEAL && req_obj.Position()==0) || req_obj.Action()==TRADE_ACTION_PENDING) { //--- 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; //--- If the order/position is present, the request is handled: remove it and proceed to the next if(list.Total()>0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(i); continue; } } //--- Otherwise: full and partial position closure, removing an order, modifying order parameters and position stop orders else { CArrayObj *list=NULL; //--- if this is a position closure, including a closure by an opposite one if((req_obj.Action()==TRADE_ACTION_DEAL && req_obj.Position()>0) || req_obj.Action()==TRADE_ACTION_CLOSE_BY) { //--- Get a position with the necessary ticket from the list of open positions list=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Position(),EQUAL); if(::CheckPointer(list)==POINTER_INVALID) continue; //--- If the market has no such position - the request is handled: remove it and proceed to the next one if(list.Total()==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(i); continue; } //--- Otherwise, if the position still exists, this is a partial closure else { //--- Get the list of all account trading events list=this.m_events.GetList(); if(list==NULL) continue; //--- In the loop from the end of the account trading event list int events_total=list.Total(); for(int j=events_total-1; j>WRONG_VALUE; j--) { //--- get the next trading event CEvent *event=list.At(j); if(event==NULL) continue; //--- If this event is a partial closure or there was a partial closure when closing by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL || event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { //--- If a position ticket in a trading event coincides with the ticket in a pending trading request if(event.PositionID()==req_obj.Position()) { //--- Get a position object from the list of market positions CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_POSITION_ID,req_obj.Position(),EQUAL); if(list_orders==NULL || list_orders.Total()==0) break; COrder *order=list_orders.At(list_orders.Total()-1); if(order==NULL) break; //--- Set actual position data to the pending request object this.SetActualProperties(req_obj,order); //--- If (executed request volume + unexecuted request volume) is equal to the requested volume in a pending request - //--- the request is handled: remove it and break the loop by the list of account trading events if(req_obj.GetProperty(PEND_REQ_PROP_MQL_REQ_VOLUME)==event.VolumeOrderExecuted()+event.VolumeOrderCurrent()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(i); break; } } } } //--- If a handled pending request object was removed by the trading event list in the loop, move on to the next one if(::CheckPointer(req_obj)==POINTER_INVALID) continue; } } //--- If this is a modification of position stop orders if(req_obj.Action()==TRADE_ACTION_SLTP) { //--- Get the list of all account trading events list=this.m_events.GetList(); if(list==NULL) continue; //--- In the loop from the end of the account trading event list int events_total=list.Total(); for(int j=events_total-1; j>WRONG_VALUE; j--) { //--- get the next trading event CEvent *event=list.At(j); if(event==NULL) continue; //--- If this is a change of the position's stop orders if(event.TypeEvent()>TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT) { //--- If a position ticket in a trading event coincides with the ticket in a pending trading request if(event.PositionID()==req_obj.Position()) { //--- Get a position object from the list of market positions CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_POSITION_ID,req_obj.Position(),EQUAL); if(list_orders==NULL || list_orders.Total()==0) break; COrder *order=list_orders.At(list_orders.Total()-1); if(order==NULL) break; //--- Set actual position data to the pending request object this.SetActualProperties(req_obj,order); //--- If all modifications have worked out - //--- the request is handled: remove it and break the loop by the list of account trading events if(req_obj.IsCompleted()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(i); break; } } } } //--- If a handled pending request object was removed by the trading event list in the loop, move on to the next one if(::CheckPointer(req_obj)==POINTER_INVALID) continue; } //--- If this is a pending order removal if(req_obj.Action()==TRADE_ACTION_REMOVE) { //--- Get the list of removed pending orders from the historical list list=this.m_history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) continue; //--- Leave a single order with the necessary ticket in the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,req_obj.Order(),EQUAL); //--- If the order is present, the request is handled: remove it and proceed to the next if(list.Total()>0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(i); continue; } } //--- If this is a pending order modification if(req_obj.Action()==TRADE_ACTION_MODIFY) { //--- Get the list of all account trading events list=this.m_events.GetList(); if(list==NULL) continue; //--- In the loop from the end of the account trading event list int events_total=list.Total(); for(int j=events_total-1; j>WRONG_VALUE; j--) { //--- get the next trading event CEvent *event=list.At(j); if(event==NULL) continue; //--- If this event involves any change of modified pending order parameters if(event.TypeEvent()>TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER && event.TypeEvent()<TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT) { //--- If an order ticket in a trading event coincides with the ticket in a pending trading request if(event.TicketOrderEvent()==req_obj.Order()) { //--- Get an order object from the list CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Order(),EQUAL); if(list_orders==NULL || list_orders.Total()==0) break; COrder *order=list_orders.At(0); if(order==NULL) break; //--- Set actual order data to the pending request object this.SetActualProperties(req_obj,order); //--- If all modifications have worked out - //--- the request is handled: remove it and break the loop by the list of account trading events if(req_obj.IsCompleted()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(i); break; } } } } } } //--- Exit if the pending request object has been removed after checking its operation if(::CheckPointer(req_obj)==POINTER_INVALID) return; //--- 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((long)symbol_obj.Time()<(long)req_obj.TimeActivate()) continue; //--- Set the attempt number in the request object req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1)); //--- Display the number of a trading attempt in the journal if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_RE_TRY_N)+(string)req_obj.CurrentAttempt()+":"); req_obj.PrintShort(); } //--- Depending on the type of action performed in the trading request switch(request.action) { //--- Opening/closing a position case TRADE_ACTION_DEAL : //--- If no ticket is present in the request structure - this is opening a position if(request.position==0) this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation,request.type_filling); //--- If the ticket is present in the request structure - this is a position closure else this.ClosePosition(request.position,request.volume,request.comment,request.deviation); break; //--- Modify StopLoss/TakeProfit position case TRADE_ACTION_SLTP : this.ModifyPosition(request.position,request.sl,request.tp); break; //--- Close by an opposite one case TRADE_ACTION_CLOSE_BY : this.ClosePositionBy(request.position,request.position_by); break; //--- //--- Place a pending order case TRADE_ACTION_PENDING : this.PlaceOrder(request.type,request.volume,request.symbol,request.price,request.stoplimit,request.sl,request.tp,request.magic,request.comment,request.expiration,request.type_time,request.type_filling); break; //--- Modify a pending order case TRADE_ACTION_MODIFY : this.ModifyOrder(request.order,request.price,request.sl,request.tp,request.stoplimit,request.expiration,request.type_time,request.type_filling); break; //--- Remove a pending order case TRADE_ACTION_REMOVE : this.DeleteOrder(request.order); break; //--- default: break; } } } //+------------------------------------------------------------------+
Por el momento, esto es todo lo que debemos hacer en la clase de control de solicitudes comerciales pendientes. Si hay algo que no haya quedado claro en el funcionamiento del temporizador de la clase, el lector podrá formular las preguntas pertinentes en los comentarios al artículo.
Vamos a introducir algunos cambios en la clase del objeto básico de la biblioteca CEngine.
Dado que ahora, en lugar de la clase comercial, vamos a usar su heredera, la clase de control de solicitudes pendientes,
en vez de incluir en el listado el archivo de la clase comercial:
//+------------------------------------------------------------------+ //| Engine.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include "Services\TimerCounter.mqh" #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Collections\AccountsCollection.mqh" #include "Collections\SymbolsCollection.mqh" #include "Collections\ResourceCollection.mqh" #include "Trading.mqh" //+------------------------------------------------------------------+
vamos a incluir el archivo de la clase de control de solicitudes comerciales pendientes:
//+------------------------------------------------------------------+ //| Engine.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/es/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/es/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Archivos de inclusión | //+------------------------------------------------------------------+ #include "Services\TimerCounter.mqh" #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Collections\AccountsCollection.mqh" #include "Collections\SymbolsCollection.mqh" #include "Collections\ResourceCollection.mqh" #include "TradingControl.mqh" //+------------------------------------------------------------------+
Y en lugar del objeto de la clase comercial
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CSymbolsCollection m_symbols; // Symbol collection CResourceCollection m_resource; // Resource list CTrading m_trading; // Trading class object CArrayObj m_list_counters; // List of timer counters
usaremos el objeto de la clase de control de solicitudes comerciales:
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CSymbolsCollection m_symbols; // Symbol collection CResourceCollection m_resource; // Resource list CTradingControl m_trading; // Trading management object CArrayObj m_list_counters; // List of timer counters
Por hoy, estos son todos los cambios que realizaremos en la biblioteca.
Simulación
Para poner a prueba la nueva versión de la biblioteca, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part30\ con el nuevo nombre TestDoEasyPart30.mq5.
Compilamos el asesor y lo iniciamos en el gráfico de la cuenta demo.
Tenemos que comprobar cómo funcionan los objetos de solicitudes pendientes cuando el botón de Trading automático en el terminal está desactivado.
En los ajustes del asesor, establecemos la distancia de colocación del StopLoss y el TakeProfit con el valor 0.
De ello se encargan dos parámetros ajustables:
- StopLoss in points
- TakeProfit in points.
A continuación, hacemos lo siguiente:
primero, desactivamos el comercio automático con el botón "Trading automático" en el terminal, y luego intentamos enviar una solicitud comercial, por ejemplo, para colocar una orden pendiente SellLimit, usando el botón del panel comercial del asesor.
Lógicamente, obtendremos el error de prohibición del comercio por parte del terminal,
y además, se creará una solicitud pendiente.
A continuación, pulsamos de inmediato el botón "Trading automático" para corregir el error.
En teoría, se debería activar de inmediato la solicitud comercial recién creada, que enviará una solicitud comercial al servidor.
Después de su ejecución,
el objeto de solicitud será eliminado como ejecutado:
2019.12.27 04:16:28.894 automated trading is disabled 2019.12.27 04:16:33.177 CTrading::PlaceOrder<uint,int,uint,uint>: Invalid request: 2019.12.27 04:16:33.177 There is no permission to conduct trading operations in the terminal (the "AutoTrading" button is disabled) 2019.12.27 04:16:33.177 Correction of trade request parameters ... 2019.12.27 04:16:33.178 Pending request created #1: 2019.12.27 04:16:33.178 Pending request to place a pending order: 2019.12.27 04:16:33.178 - GBPUSD 0.10 Pending order Sell Limit, Price 1.30088 2019.12.27 04:16:33.178 - Pending request ID: #1, Created 2019.12.26 23:16:30.003, Attempts 5, Wait 00:00:20, End 2019.12.26 23:18:10.003 2019.12.27 04:16:33.178 2019.12.27 04:16:37.397 automated trading is enabled 2019.12.27 04:16:37.472 Retry trading attempt #1: 2019.12.27 04:16:37.472 Pending request to place a pending order: 2019.12.27 04:16:37.472 - GBPUSD 0.10 Pending order Sell Limit, Price 1.30088 2019.12.27 04:16:37.472 - Pending request ID: #1, Created 2019.12.26 23:16:10.003, Attempts 5, Wait 00:00:20, End 2019.12.26 23:17:50.003 2019.12.27 04:16:37.472 2019.12.27 04:16:37.977 - Pending order placed: 2019.12.26 23:16:38.325 - 2019.12.27 04:16:37.977 GBPUSD Placed 0.10 Pending order Sell Limit #500442708 at price 1.30088, Magic number 26148987 (123), G1: 15, G2: 8, ID: 1 2019.12.27 04:16:37.979 OnDoEasyEvent: Pending order placed 2019.12.27 04:16:38.024 Pending request to place a pending order, ID #1: Deleted due completed
Ahora, necesitaremos comprobar la posibilidad de modificar las órdenes stop de la orden pendiente que se acaba de colocar (se ha colocado sin ellas).
Una vez más, desactivamos el comercio automático y pulsamos el botón "Set StopLoss" en el panel comercial del asesor de prueba.
Obtendremos el error de prohibición de realización de operaciones comerciales por parte del terminal,
y además se creará una solicitud pendiente de cambio de parámetros de la orden pendiente.
Después del nuevo intento comercial, que ya enviará la solicitud pendiente,
permitiremos el comercio automático al terminal.
Para la orden pendiente se colocará un StopLoss,
mientras que la propia solicitud pendiente será eliminada como ejecutada:
2019.12.27 04:24:11.671 automated trading is disabled 2019.12.27 04:24:16.653 CTrading::ModifyOrder<int,int,double,int>: Invalid request: 2019.12.27 04:24:16.653 There is no permission to conduct trading operations in the terminal (the "AutoTrading" button is disabled) 2019.12.27 04:24:16.653 Correction of trade request parameters ... 2019.12.27 04:24:16.653 Pending request created #1: 2019.12.27 04:24:16.653 Pending request to modify pending order parameters: 2019.12.27 04:24:16.653 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208 2019.12.27 04:24:16.653 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:24:16.653 - Order expiration type: Good till cancel order 2019.12.27 04:24:16.653 - Pending request ID: #1, Created 2019.12.26 23:24:00.270, Attempts 5, Wait 00:00:20, End 2019.12.26 23:25:40.270 2019.12.27 04:24:16.653 2019.12.27 04:24:25.803 Retry trading attempt #1: 2019.12.27 04:24:25.803 Pending request to modify pending order parameters: 2019.12.27 04:24:25.803 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208 2019.12.27 04:24:25.803 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:24:25.803 - Order expiration type: Good till cancel order 2019.12.27 04:24:25.803 - Pending request ID: #1, Created 2019.12.26 23:24:00.270, Attempts 5, Wait 00:00:20, End 2019.12.26 23:25:40.270 2019.12.27 04:24:25.803 2019.12.27 04:24:29.770 automated trading is enabled 2019.12.27 04:24:30.022 Retry trading attempt #1: 2019.12.27 04:24:30.022 Pending request to modify pending order parameters: 2019.12.27 04:24:30.022 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208 2019.12.27 04:24:30.022 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:24:30.022 - Order expiration type: Good till cancel order 2019.12.27 04:24:30.022 - Pending request ID: #1, Created 2019.12.26 23:23:40.270, Attempts 5, Wait 00:00:20, End 2019.12.26 23:25:20.270 2019.12.27 04:24:30.022 2019.12.27 04:24:30.405 - Modified order StopLoss: 2019.12.26 23:16:38.325 - 2019.12.27 04:24:30.405 GBPUSD Pending order Sell Limit #500442708: Modified order StopLoss: [0.00000 --> 1.30208], Magic number 26148987 (123), G1: 15, G2: 8, ID: 1 2019.12.27 04:24:30.405 OnDoEasyEvent: Modified order StopLoss 2019.12.27 04:24:30.601 Pending request to modify pending order parameters, ID #1: Deleted due completed
Aquí podemos ver que después de permitir el comercio, la solicitud repetida del objeto de solicitud pendiente tiene el mismo número que en el primer intento de la solicitud pendiente repetida, es decir, la lógica de adición de un nuevo intento cuando el usuario elimina la causa del error (el permiso del comercio automático en el terminal) ha funcionado correctamente.
Ahora, vamos a hecer lo mismo, pero con la colocación del TakeProfit.
Y de nuevo todo ha funcionado (aquí, tras el segundo intento comercial realizado por el objeto de solicitud pendiente),
el TakeProfit ha sido colocado por el objeto de solicitud pendiente, y esta solicitud ha sido eliminada:
2019.12.27 04:32:46.843 automated trading is disabled 2019.12.27 04:32:50.810 CTrading::ModifyOrder<int,int,int,double>: Invalid request: 2019.12.27 04:32:50.810 There is no permission to conduct trading operations in the terminal (the "AutoTrading" button is disabled) 2019.12.27 04:32:50.810 Correction of trade request parameters ... 2019.12.27 04:32:50.810 Pending request created #1: 2019.12.27 04:32:50.810 Pending request to modify pending order parameters: 2019.12.27 04:32:50.810 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208, TakeProfit order level 1.29888 2019.12.27 04:32:50.810 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:32:50.810 - Order expiration type: Good till cancel order 2019.12.27 04:32:50.810 - Pending request ID: #1, Created 2019.12.26 23:32:47.943, Attempts 5, Wait 00:00:20, End 2019.12.26 23:34:27.943 2019.12.27 04:32:50.810 2019.12.27 04:33:08.782 Retry trading attempt #1: 2019.12.27 04:33:08.782 Pending request to modify pending order parameters: 2019.12.27 04:33:08.782 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208, TakeProfit order level 1.29888 2019.12.27 04:33:08.782 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:33:08.782 - Order expiration type: Good till cancel order 2019.12.27 04:33:08.782 - Pending request ID: #1, Created 2019.12.26 23:32:47.943, Attempts 5, Wait 00:00:20, End 2019.12.26 23:34:27.943 2019.12.27 04:33:08.782 2019.12.27 04:33:29.984 Retry trading attempt #2: 2019.12.27 04:33:29.984 Pending request to modify pending order parameters: 2019.12.27 04:33:29.984 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208, TakeProfit order level 1.29888 2019.12.27 04:33:29.984 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:33:29.984 - Order expiration type: Good till cancel order 2019.12.27 04:33:29.984 - Pending request ID: #1, Created 2019.12.26 23:32:47.943, Attempts 5, Wait 00:00:20, End 2019.12.26 23:34:27.943 2019.12.27 04:33:29.984 2019.12.27 04:33:31.999 automated trading is enabled 2019.12.27 04:33:32.250 Retry trading attempt #2: 2019.12.27 04:33:32.250 Pending request to modify pending order parameters: 2019.12.27 04:33:32.250 - GBPUSD 0.10 Pending order Sell Limit #500442708, Price 1.30088, StopLoss order level 1.30208, TakeProfit order level 1.29888 2019.12.27 04:33:32.250 - Order execution type: The order is executed at an available volume, unfulfilled remains in the market (Return) 2019.12.27 04:33:32.250 - Order expiration type: Good till cancel order 2019.12.27 04:33:32.250 - Pending request ID: #1, Created 2019.12.26 23:32:27.943, Attempts 5, Wait 00:00:20, End 2019.12.26 23:34:07.943 2019.12.27 04:33:32.250 2019.12.27 04:33:32.352 - Modified order TakeProfit: 2019.12.26 23:24:26.509 - 2019.12.27 04:33:32.352 GBPUSD Pending order Sell Limit #500442708: Modified order TakeProfit: [0.00000 --> 1.29888], Magic number 26148987 (123), G1: 15, G2: 8, ID: 1 2019.12.27 04:33:32.352 OnDoEasyEvent: Modified order TakeProfit 2019.12.27 04:33:32.754 Pending request to modify pending order parameters, ID #1: Deleted due completed
Gracias a las pruebas realizadas, vemos que ahora podemos hacer diferentes operaciones comerciales con la misma orden o posición(operaciones comerciales con el mismo ticket). En las anteriores versiones, solo podíamos realizar una operación comercial con un ticket, y a continuación, el objeto de solicitud pendiente siempre consideraba que ya había funcionado. Ahora, hemos logrado corregir ese comportamiento.
Las demás operaciones comerciales al trabajar con solicitudes pendientes las comprobaremos y depuraremos paulatinamente en el posterior desarrollo de la biblioteca. Dicho proceso requiere de una simulación escrupulosa y prolongada para detectar y corregir las diferentes situaciones imprevistas.
¿Qué es lo próximo?
En el próximo artículo, continuaremos desarrollando el concepto de las solicitudes comerciales pendientes.
Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
Artículos de esta serie:
Parte 1: Concepto y organización de datosParte 2: Colecciones de las ó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 colección de eventos comerciales. Envío de eventos al programa.
Parte 6. Eventos en la cuenta con compensación
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para el registro de 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 y 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
Parte 26. Trabajando con las solicitudes comerciales pendientes - primera implementación (apertura de posiciones)
Parte 27. Trabajando con las solicitudes comerciales pendientes - Colocación de órdenes pendientes
Parte 28. Trabajando con las solicitudes comerciales pendientes - Cierre, eliminación y modificación
Parte 29. Solicitudes comerciales pendientes - Clases de objetos de solicitudes
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/7481
- 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