Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXIII): Clase comercial principal - control de parámetros permitidos
Contenido
- Notificación sonora de eventos comerciales
- Control de valores incorrectos, automatización de la selección y uso de parámetros de entrada de los métodos comerciales
- Simulación
- ¿Qué es lo próximo?
Continuamos desarrollando la clase comercial.
Ya hemos preparado métodos comerciales que trabajan con condiciones
"puras". Antes de enviar una orden comercial al servidor, ya comprobamos la posibilidad de enviarla, es decir, la ausencia de limitaciones
en el comercio tanto en el lado del servidor comercial como en el lado del terminal y el programa. Pero esto, por supuesto, no basta. También
debemos comprobar la corrección de los valores enviados por los argumentos a los métodos de envío de solicitudes al servidor. Asi, por
ejemplo, debemos comprobar si no se infringe la distancia de colocación de órdenes stop de los valores mínimos posibles establecidos para
el símbolo, según la distancia mínima de colocación de órdenes stop en puntos —
StopLevel.
Asimismo, vamos a comprobar la distancia mínima con la que queda prohibido cerrar una posición, eliminar una orden pendiente y modificar las órdenes stop de una posición y el precio de colocación de una orden pendiente: dicha distancia mínima está reglamentada por el valor del nivel de congelación en puntos establecido para el símbolo, FreezeLevel.
Las acciones que realizaremos por el momento en caso de que se infrinjan estos niveles, serán simplemente comunicar el error y
retornar
false desde el método comercial.
Hoy, además, implementaremos la
notificación sonora de eventos comerciales y reproduciremos los sonidos de envío exitoso de una solicitud al servidor y los sonidos del
error al comprobar los valores de la orden comercial o el error retornado por el servidor después de enviar la orden.
Ya en el siguiente artículo, comenzaremos a implementar el procesamiento de los valores erróneos en la orden comercial, así como de
los errores retornados por el servidor.
En primer lugar, vamos a trabajar con el objeto comercial básico de forma que tengamos la posibilidad de reproducir los sonidos
establecidos para cualquier evento comercial. Para que resulte más sencillo esteblecer los sonidos en el asesor de prueba (y no tener que
asignar su propio sonido a cada evento y símbolo), estableceremos los mismos sonidos estándar de error y éxito para todas acciones
comerciales de todos los símbolos.
Por ciero, podemos establecer cualquier sonido para cada evento comercial (también propio) para cada símbolo aparte. Es decir,
Podemos implementar la notificación sonora de cualquier evento comercial en cada símbolo con nuestro propio sonido, lo que nos permite
enseñar al programa a "hablar con voz propia" (registrada en el archivo necesario) sobre los eventos sucedidos.
Notificación sonora de eventos comerciales
En el artículo anterior, al introducir las mejoras en la clase comercial básica, creamos los métodos para reproducir los sonidos establecidos de diferentes eventos comerciales:
//--- Play the sound of (1) opening/placing a specified position/order type, //--- (2) closing/removal of a specified position/order type, (3) StopLoss modification for a specified position/order type, //--- (4) TakeProfit modification for a specified position/order type, (5) placement price modification for a specified order type void PlaySoundOpen(const int action); void PlaySoundClose(const int action); void PlaySoundModifySL(const int action); void PlaySoundModifyTP(const int action); void PlaySoundModifyPrice(const int action); //--- Play the error sound of (1) opening/placing a specified position/order type, //--- (2) closing/removal of a specified position/order type, (3) StopLoss modification for a specified position/order type, //--- (4) TakeProfit modification for a specified position/order type, (5) placement price modification for a specified order type void PlaySoundErrorOpen(const int action); void PlaySoundErrorClose(const int action); void PlaySoundErrorModifySL(const int action); void PlaySoundErrorModifyTP(const int action); void PlaySoundErrorModifyPrice(const int action);
Y estos métodos se ubicaban en la sección pública de la clase. Hoy, vamos a crear directamente solo dos métodos para reproducir el sonido de
éxito y el sonido de error. En lo que se refiere al método para el que precisamente implementaremos la notificación sonora, estos datos los
transmitiremos a los nuevos métodos. De esta forma, nos facilitaremos la escritura de la reproducción de sonidos para este u otro evento
comercial.
Vamos a reubicar los métodos anteriormente indicados en la sección privada de la clase y modificar un poco la implementación
de estos métodos:
//+------------------------------------------------------------------+ //| Play the sound of opening/placing | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundOpen(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.Buy.SoundOpen()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundOpen()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundOpen()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundOpen()); break; case ORDER_TYPE_SELL : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.Sell.SoundOpen()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundOpen()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundOpen()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundOpen()); break; default: break; } } //+------------------------------------------------------------------+ //| Play the sound of closing/removal of | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundClose(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.Buy.SoundClose()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundClose()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundClose()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundClose()); break; case ORDER_TYPE_SELL : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.Sell.SoundClose()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundClose()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundClose()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundClose()); break; default: break; } } //+------------------------------------------------------------------+ //| Play StopLoss modification sound of | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundModifySL(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.Buy.SoundModifySL()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundModifySL()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundModifySL()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundModifySL()); break; case ORDER_TYPE_SELL : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.Sell.SoundModifySL()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundModifySL()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundModifySL()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundModifySL()); break; default: break; } } //+------------------------------------------------------------------+ //| Play TakeProfit modification sound of | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundModifyTP(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.Buy.SoundModifyTP()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundModifyTP()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundModifyTP()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundModifyTP()); break; case ORDER_TYPE_SELL : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.Sell.SoundModifyTP()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundModifyTP()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundModifyTP()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundModifyTP()); break; default: break; } } //+------------------------------------------------------------------+ //| Play price modification sound | //| for a specified order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundModifyPrice(const int action) { switch(action) { case ORDER_TYPE_BUY_STOP : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundModifyPrice()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundModifyPrice()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundModifyPrice()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundModifyPrice()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundModifyPrice()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundModifyPrice()); break; default: break; } } //+------------------------------------------------------------------+ //| Play the error sound of opening/placing | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundErrorOpen(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.Buy.SoundErrorOpen()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorOpen()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorOpen()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorOpen()); break; case ORDER_TYPE_SELL : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.Sell.SoundErrorOpen()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundErrorOpen()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorOpen()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundOpen(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorOpen()); break; default: break; } } //+------------------------------------------------------------------+ //| Play the error sound of closing/removal of | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundErrorClose(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.Buy.SoundErrorClose()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorClose()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorClose()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorClose()); break; case ORDER_TYPE_SELL : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.Sell.SoundErrorClose()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundErrorClose()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorClose()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundClose(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorClose()); break; default: break; } } //+------------------------------------------------------------------+ //| Play StopLoss modification error sound of | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundErrorModifySL(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.Buy.SoundErrorModifySL()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorModifySL()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorModifySL()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorModifySL()); break; case ORDER_TYPE_SELL : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.Sell.SoundErrorModifySL()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundErrorModifySL()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorModifySL()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundModifySL(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorModifySL()); break; default: break; } } //+------------------------------------------------------------------+ //| Play TakeProfit modification error sound of | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundErrorModifyTP(const int action) { switch(action) { case ORDER_TYPE_BUY : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.Buy.SoundErrorModifyTP()); break; case ORDER_TYPE_BUY_STOP : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorModifyTP()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorModifyTP()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorModifyTP()); break; case ORDER_TYPE_SELL : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.Sell.SoundErrorModifyTP()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundErrorModifyTP()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorModifyTP()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundModifyTP(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorModifyTP()); break; default: break; } } //+------------------------------------------------------------------+ //| Play price modification error sound | //| for a specified order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundErrorModifyPrice(const int action) { switch(action) { case ORDER_TYPE_BUY_STOP : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.BuyStop.SoundErrorModifyPrice()); break; case ORDER_TYPE_BUY_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.BuyLimit.SoundErrorModifyPrice()); break; case ORDER_TYPE_BUY_STOP_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.BuyStopLimit.SoundErrorModifyPrice()); break; case ORDER_TYPE_SELL_STOP : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.SellStop.SoundErrorModifyPrice()); break; case ORDER_TYPE_SELL_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.SellLimit.SoundErrorModifyPrice()); break; case ORDER_TYPE_SELL_STOP_LIMIT : if(this.UseSoundModifyPrice(action)) CMessage::PlaySound(this.m_datas.SellStopLimit.SoundErrorModifyPrice()); break; default: break; } } //+------------------------------------------------------------------+
Aquí, hemos añadido la comprobación del permiso de reproducción del sonido de un evento concreto, el sonido solo se reproducirá en el caso de que exista la bandera de reproducción de sonidos para este evento.
Declaramos en la sección pública de la clase dos métodos, uno para reproducir el sonido de éxito y otro para reproducir el sonido de error:
//--- Play a sound of a specified trading event for a set position/order type void PlaySoundSuccess(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false); //--- Play an error sound of a specified trading event for a set position/order type void PlaySoundError(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false); //--- Set/return the flag of using sounds
Los implementamos fuera del cuerpo de la clase:
//+------------------------------------------------------------------+ //| Play a sound of a specified trading event | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundSuccess(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false) { if(!this.m_use_sound) return; switch((int)action) { //--- Open/set case ACTION_TYPE_BUY : case ACTION_TYPE_BUY_LIMIT : case ACTION_TYPE_BUY_STOP : case ACTION_TYPE_BUY_STOP_LIMIT : case ACTION_TYPE_SELL : case ACTION_TYPE_SELL_LIMIT : case ACTION_TYPE_SELL_STOP : case ACTION_TYPE_SELL_STOP_LIMIT : this.PlaySoundOpen(order); break; //--- Close/remove case ACTION_TYPE_CLOSE : case ACTION_TYPE_CLOSE_BY : this.PlaySoundClose(order); break; //--- Modification case ACTION_TYPE_MODIFY : if(sl) { this.PlaySoundModifySL(order); return; } if(tp) { this.PlaySoundModifyTP(order); return; } if(pr) { this.PlaySoundModifyPrice(order); return; } break; default: break; } } //+------------------------------------------------------------------+ //| Play an error sound of a specified trading event | //| a specified position/order type | //+------------------------------------------------------------------+ void CTradeObj::PlaySoundError(const ENUM_ACTION_TYPE action,const int order,bool sl=false,bool tp=false,bool pr=false) { if(!this.m_use_sound) return; switch((int)action) { //--- Open/set case ACTION_TYPE_BUY : case ACTION_TYPE_BUY_LIMIT : case ACTION_TYPE_BUY_STOP : case ACTION_TYPE_BUY_STOP_LIMIT : case ACTION_TYPE_SELL : case ACTION_TYPE_SELL_LIMIT : case ACTION_TYPE_SELL_STOP : case ACTION_TYPE_SELL_STOP_LIMIT : this.PlaySoundErrorOpen(order); break; //--- Close/remove case ACTION_TYPE_CLOSE : case ACTION_TYPE_CLOSE_BY : this.PlaySoundErrorClose(order); break; //--- Modification case ACTION_TYPE_MODIFY : if(sl) { this.PlaySoundErrorModifySL(order); return; } if(tp) { this.PlaySoundErrorModifyTP(order); return; } if(pr) { this.PlaySoundErrorModifyPrice(order); return; } break; default: break; } } //+------------------------------------------------------------------+
Transmitimos a los métodos el tipo de evento comercial, el
tipo de orden y las banderas de modificación del StopLoss,
el TakeProfit y los
precios de colocación de la orden.
Si no se ha activado la bandera
general de permiso de reproducción de sonidos para el objeto comercial,
salimos del método: la reproducción de todos los sonidos queda prohibida.
A continuación, dependiendo
del tipo de operación comercial, llamamos a los métodos de reproducción de los
sonidos correspondientes para la orden correspondiente.
Si el evento comercial es la modificación,
comprobamos de forma adicional las banderas que indican qué se
modifica precisamente. (Si se modifican varios parámetros a la vez, entonces se reproducirá el sonido solo para el primero de ellos)
Asimismo, en la clase se ha modificado el orden de secuencia de los argumentos en los métodos comerciales: ahora, los comentarios se deben indicar justo tras el número mágico, y ya después, el tamaño de la desviación. Se ha hecho de esta forma porque los comentarios se pueden establecer para diferentes órdenes con mayor frecuencia que el tamaño del desliazamiento. Precisamente por eso, el comentario ha cambiado su sitio con la desviación:
//--- Open a position bool OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX);
Esto se ha hecho con todos los métodos comerciales: todos los archivos se adjuntan al final del archivo.
En el método de establecimiento de los sonidos estándar, establecemos para todos los eventos comerciales la bandera que permite la reproducción de sonidos:
//+------------------------------------------------------------------+ //| Allow working with sounds and set standard sounds | //+------------------------------------------------------------------+ void CTradeObj::SetSoundsStandart(void) { this.SetUseSound(true); this.m_datas.Buy.UseSoundClose(true);
Hemos introducido en la clase algunos cambios adicionales para simplificar el proceso de compatibilidad con MQL4; no los vamos a ver aquí, el lector siempre podrá consultar los archivos adjuntos al final del artículo por sí mismo.
Ya hemos finalizado la mejora de la clase del objeto comercial básico.
Al enviar solicitudes comerciales desde el programa, necesitamos establecer la distancia de colocación de órdenes pendientes y establecer el tamaño de las órdenes stop. Para indicar estas dimensiones, podemos transmitir en los parámetros de la orden comercial el precio concreto en el que debe ser colocado la orden u orden stop, o bien transmitir la distancia en puntos con respecto al precio para la orden pendiente, o la distancia en puntos respecto al precio de apertura de la posición/colocación de la orden pendiente en la que se deberán ubicar las órdenes stop.
Para transmitir estos valores a los métodos de apertura de posición/colocación de órdenes, los métodos de colocación de niveles y los métodos de modificación de órdenes stop, podemos crear métodos sobrecargados a los que se transmiten los parámetros en la representación de precios de tipo real, así como métodos a los que se transmiten los valores de tipo entero de la distancia en puntos.
Pero esta opción no resulta muy adecuada: en primer lugar, tendremos que crear un mínimo de dos métodos iguales (a uno se le transmiten los
valores de tipo real, y al otro, los de tipo entero).
En segundo lugar, esta opción nos limita en cuanto a la combinación de los parámetros, ya que si transmitimos los valores de tipo real,
estos deberán ser de tipo real para cada uno de los parámetros: para el precio de colocación de las órdenes, para el precio de ubicación del
StopLoss y para el precio de ubicación del TakeProfit. Exactamente de la misma forma nos veremos limitados al transmitir a los métodos las
distancias en puntos, pues todas las magnitudes deberán ser transmitidas en valores de tipo entero.
O bien tendremos que crear multitud de métodos en los que se registrarán todas las combinaciones posibles de precios y distancias, lo
cual no resulta práctico en absoluto.
Por eso, vamos a decantarnos por otra opción: convertiremos todos los métodos comerciales en plantillas, y ya dentro de los métodos,
determinaremos los tipos de variables en los que se transmirán los valores de colocación de las órdenes y los valores de las órdenes stop. De
esta forma, podemos transmitir a los métodos los valores que necesitamos en cualquier combinación, por ejemplo, el precio de
establecimiento de una orden y la distancia en puntos con respecto al precio de colocación para las órdenes stop, o al contrario. Esto nos
ofrecerá una gran flexibilidadal al calcular los valores de colocación de las órdenes y órdenes stop.
Dentro de los métodos comerciales, todos los valores entrantes se transformarán en valores de precio, después de lo cual, los valores en
precios se enviarán a la orden comercial.
La comprobación de las limitaciones para la realización de operaciones comerciales
se efecturá en tres etapas:
- Primero se comprobarán las limitaciones para el comercio
- Después se comprobarán que los fondos sean suficientes para abrir las posiciones/colocar las órdenes
- Por último, se comprobarán los valores de los parámetros según los niveles StopLevel y FreezeLevel
Los dos primeros puntos ya los hemos creado; hoy nos encargaremos de añadir la comprobación según los niveles StopLevel y FreezeLevel. Lo
único que querríamos destacar es que las dos comprobaciones ya preparadas (la comprobación de las limitaciones para el comercio y la
comprobación de los fondos suficientes), en nuestro caso, se inician por turno directamente desde los métodos comerciales, lo cual
resulta normal, pero no práctico.
Por eso, hoy vamos a crear un método único para comprobar todas las limitaciones, que se iniciará desde los métodos comerciales,
mientras que en su interior se ejecutarán por turno las tres comprobaciones y se creará una lista con las limitaciones y errores
encontrados. En el caso de que estos existan, desde el método se mostrará en el diario la lista completa de errores y limitaciones, y también
se retornará la bandera de fracaso en la realización de las tres comprobaciones, o bien se retornará la bandera de éxito en la misma.
Dado que prácticamente todos los métodos están preparados y solo precisan de alguna mejora, no nos detendremos en ellos con detalle, sino que
nos limitaremos a realizar aclaraciones breves.
Control de valores incorrectos, automatización de la selección y uso de parámetros de entrada de los métodos comerciales
A los métodos comerciales de la clase CTrading se transmiten los valores de tipo real de los precios de colocación de las órdenes y órdenes stop. Vamos a añadir la posibilidad de transmitir además la distancia en puntos. Los tipos soportados de los parámetros que se podrán transmitir a las clases comerciales para indicar los precios o distancias son: double, long, ulong, int y uint. Los demás se percibirán como erróneos.
Vamos a añadir a la sección privada de la clase CTrading la bandera global de permiso de reproducción de sonidos de los eventos comerciales y la estructura de precios en la que se registrarán los valores de precio o distancia convertidos a valores de tipo real transmitidos a los métodos comerciales:
//+------------------------------------------------------------------+ //| Trading class | //+------------------------------------------------------------------+ class CTrading { private: 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 CArrayInt m_list_errors; // Error list bool m_is_trade_enable; // Flag enabling trading bool m_use_sound; // The flag of using sounds of the object trading events ENUM_LOG_LEVEL m_log_level; // Logging level //--- struct SDataPrices { double open; // Open price double limit; // Limit order price double sl; // StopLoss price double tp; // TakeProfit price }; SDataPrices m_req_price; // Trade request prices //--- Add the error code to the list
La bandera global de permiso de reproducción de sonidos actuará sobre todos los eventos comerciales, independientemente de qué sonidos hayan sido establecidos para ellos, y de si está permitido para cada uno de ellos reproducir el sonido establecido.
Para los métodos comerciales, necesitaremos obtener el objeto de orden según su ticket.
Añadimos la declaración
del método a la sección privada de la clase:
//--- Return an order object by ticket COrder *GetOrderObjByTicket(const ulong ticket); //--- Return the number of (1) all positions, (2) buy, (3) sell positions
Dado que los métodos para comprobar las limitaciones para el comercio y la suficiencia de fondos para la apertura de posiciones/colocación de órdenes funcionarán dentro del método general de comprobación de errores, los trasladaremos desde la sección pública de la clase a la privada, añadiendo también el método de plantilla de precios de la solicitud comercial, los métodos que retornan las banderas de permiso según los niveles StopLevel y FreezeLevel, y el método de comprobación del permiso de realización de operaciones según la distancia de colocación de órdenes stop y la distancia del nivel de congelación:
//--- Set trading request prices template <typename PR,typename SL,typename TP,typename PL> bool SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj); //--- Return the flag checking the permission to trade by (1) StopLoss, (2) TakeProfit distance, (3) order placement level by a StopLevel-based price bool CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj); bool CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj); bool CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj); //--- Return the flag checking if a distance from a price to (1) StopLoss, (2) TakeProfit, (3) order placement level by FreezeLevel is acceptable bool CheckStopLossByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double sl,const CSymbol *symbol_obj); bool CheckTakeProfitByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double tp,const CSymbol *symbol_obj); bool CheckPriceByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj); //--- Check trading limitations bool CheckTradeConstraints(const double volume, const ENUM_ACTION_TYPE action, const CSymbol *symbol_obj, const string source_method, double sl=0, double tp=0); //--- Check if the funds are sufficient bool CheckMoneyFree(const double volume,const double price,const ENUM_ORDER_TYPE order_type,const CSymbol *symbol_obj,const string source_method); //--- Check parameter values by StopLevel and FreezeLevel bool CheckLevels(const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, double price, double limit, double sl, double tp, const CSymbol *symbol_obj, const string source_method); public:
En la sección pública de la clase, declaramos el método de comprobación del
permiso de comercio y de comprobación de errores de la solicitud comercial, así como los
métodos de establecimiento y retorno de la bandera de permiso del uso de
sonidos:
public: //--- Constructor CTrading(); //--- Get the pointers to the lists (make sure to call the method in program's OnInit() since the symbol collection list is created there) void OnInit(CAccount *account,CSymbolsCollection *symbols,CMarketCollection *market,CHistoryCollection *history) { this.m_account=account; this.m_symbols=symbols; this.m_market=market; this.m_history=history; } //--- Return the error list CArrayInt *GetListErrors(void) { return &this.m_list_errors; } //--- Check for errors bool CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method, const double limit=0, double sl=0, double tp=0); //--- Set the following for symbol trading objects: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level void SetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetMagic(const ulong magic,const string symbol=NULL); void SetComment(const string comment,const string symbol=NULL); void SetDeviation(const ulong deviation,const string symbol=NULL); void SetVolume(const double volume=0,const string symbol=NULL); void SetExpiration(const datetime expiration=0,const string symbol=NULL); void SetAsyncMode(const bool mode=false,const string symbol=NULL); void SetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol=NULL); //--- Set standard sounds (1 symbol=NULL) for trading objects of all symbols, (2 symbol!=NULL) for a symbol trading object void SetSoundsStandart(const string symbol=NULL); //--- Set a sound for a specified order/position type and symbol //--- 'mode' specifies an event a sound is set for //--- (symbol=NULL) for trading objects of all symbols, //--- (symbol!=NULL) for a trading object of a specified symbol void SetSound(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,const string symbol=NULL); //--- Set/return the flag enabling sounds void SetUseSounds(const bool flag); bool IsUseSounds(void) const { return this.m_use_sound; }
Vamos a implementar los métodos descritos anteriormente fuera del cuerpo de la clase.
Método que retorna el objeto de orden según el ticket:
//+------------------------------------------------------------------+ //| Return an order object by ticket | //+------------------------------------------------------------------+ COrder *CTrading::GetOrderObjByTicket(const ulong ticket) { CArrayObj *list=this.m_market.GetList(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL); if(list==NULL || list.Total()==0) return NULL; return list.At(0); } //+------------------------------------------------------------------+
Transmitimos al método el ticket buscado, guardado en las
propiedades del objeto de orden.
Obtenemos la lista completa con todas las órdenes y posiciones activas y filtramos
la lista según
el ticket. Si
no existe un objeto de orden con este ticket, retornamos
NULL, de lo contrario, retornamos
el único objeto de orden de la lista.
Recordemos que los objetos de orden pueden ser tanto órdenes pendientes, como
posiciones.
Este método retorna el objeto, independientemente de que sea una orden pendiente o una posición.
Método de plantilla que calcula y registra en la estructura m_req_price los precios de la solicitud comercial:
//+------------------------------------------------------------------+ //| Set trading request prices | //+------------------------------------------------------------------+ template <typename PR,typename SL,typename TP,typename PL> bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj) { //--- Reset the prices and check the order type. If it is invalid, inform of that and return 'false' ::ZeroMemory(this.m_req_price); if(action>ORDER_TYPE_SELL_STOP_LIMIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(4003)); return false; } //--- Open/close price if(price>0) { //--- price parameter type (double) - normalize the price up to Digits(), since the price has been passed if(typename(price)=="double") this.m_req_price.open=::NormalizeDouble(price,symbol_obj.Digits()); //--- price parameter type (int) - the distance has been passed else if(typename(price)=="int" || typename(price)=="uint" || typename(price)=="long" || typename(price)=="ulong") { //--- Calculate the order price switch((int)action) { //--- Pending order case ORDER_TYPE_BUY_LIMIT : this.m_req_price.open=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : this.m_req_price.open=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_LIMIT : this.m_req_price.open=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : this.m_req_price.open=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits()); break; //--- Default - current position open prices default : this.m_req_price.open= ( this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits()) ); break; } } //--- unsupported price types - display the message and return 'false' else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE)); return false; } } //--- If no price is specified, use the current prices else { this.m_req_price.open= ( this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits()) ); } //--- StopLimit order price or distance if(limit>0) { //--- limit order price parameter type (double) - normalize the price up to Digits(), since the price has been passed if(typename(limit)=="double") this.m_req_price.limit=::NormalizeDouble(limit,symbol_obj.Digits()); //--- limit order price parameter type (int) - the distance has been passed else if(typename(limit)=="int" || typename(limit)=="uint" || typename(limit)=="long" || typename(limit)=="ulong") { //--- Calculate a limit order price if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY) this.m_req_price.limit=::NormalizeDouble(this.m_req_price.open-limit*symbol_obj.Point(),symbol_obj.Digits()); else this.m_req_price.limit=::NormalizeDouble(this.m_req_price.open+limit*symbol_obj.Point(),symbol_obj.Digits()); } //--- unsupported limit order price types - display the message and return 'false' else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE)); return false; } } //--- Order price stop order prices are calculated from double price_open= ( (action==ORDER_TYPE_BUY_STOP_LIMIT || action==ORDER_TYPE_SELL_STOP_LIMIT) && limit>0 ? this.m_req_price.limit : this.m_req_price.open ); //--- StopLoss if(sl>0) { //--- StopLoss parameter type (double) - normalize the price up to Digits(), since the price has been passed if(typename(sl)=="double") this.m_req_price.sl=::NormalizeDouble(sl,symbol_obj.Digits()); //--- StopLoss parameter type (int) - calculate the placement distance else if(typename(sl)=="int" || typename(sl)=="uint" || typename(sl)=="long" || typename(sl)=="ulong") { //--- Calculate the StopLoss price if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY) this.m_req_price.sl=::NormalizeDouble(price_open-sl*symbol_obj.Point(),symbol_obj.Digits()); else this.m_req_price.sl=::NormalizeDouble(price_open+sl*symbol_obj.Point(),symbol_obj.Digits()); } //--- unsupported StopLoss types - display the message and return 'false' else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE)); return false; } } //--- TakeProfit if(tp>0) { //--- TakeProfit parameter type (double) - normalize the price up to Digits(), since the price has been passed if(typename(tp)=="double") this.m_req_price.tp=::NormalizeDouble(tp,symbol_obj.Digits()); //--- TakeProfit parameter type (int) - calculate the placement distance else if(typename(tp)=="int" || typename(tp)=="uint" || typename(tp)=="long" || typename(tp)=="ulong") { if(this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY) this.m_req_price.tp=::NormalizeDouble(price_open+tp*symbol_obj.Point(),symbol_obj.Digits()); else this.m_req_price.tp=::NormalizeDouble(price_open-tp*symbol_obj.Point(),symbol_obj.Digits()); } //--- unsupported TakeProfit types - display the message and return 'false' else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE)); return false; } } //--- All prices are recorded return true; } //+------------------------------------------------------------------+
Independientemente de cómo hayan sido transmitidos los niveles de precio al metodo comercial, en precios o en distancia, este método registrará los precios
calculados en la estructura
m_req_price, que hemos declarado en la sección privada. Si hemos transmitido al método un valor double,
el precio será normalizado hasta el Digits() del símbolo cuyo puntero al objeto es transmitido al método. Si se han transmitido valores de
tipo entero, significa que se ha transmitido la distancia, por lo que el método calculará el precio normalizado según dicha distancia y lo
registrará en la estructura.
Todas las acciones se comentan en el código del método.
Métodos que retornan si la distancia de un StopLoss, un TakeProfit o el nivel de colocación de una orden respecto al StopLevel es correcta:
//+------------------------------------------------------------------+ //| Return the flag checking the validity of the distance | //| from the price to StopLoss by StopLevel | //+------------------------------------------------------------------+ bool CTrading::CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj) { double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point(); double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price); return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? sl<(pr-lv) : sl>(pr+lv)); } //+------------------------------------------------------------------+ //| Return the flag checking the validity of the distance | //| from the price to TakeProfit by StopLevel | //+------------------------------------------------------------------+ bool CTrading::CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj) { double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point(); double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price); return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? tp>(pr+lv) : tp<(pr-lv)); } //+------------------------------------------------------------------+ //| Return the flag checking the validity of the order distance | //| from the price to the placement level by StopLevel | //+------------------------------------------------------------------+ bool CTrading::CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj) { double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point(); double pr=(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.BidLast()); return ( order_type==ORDER_TYPE_SELL_STOP || order_type==ORDER_TYPE_SELL_STOP_LIMIT || order_type==ORDER_TYPE_BUY_LIMIT ? price<(pr-lv) : order_type==ORDER_TYPE_BUY_STOP || order_type==ORDER_TYPE_BUY_STOP_LIMIT || order_type==ORDER_TYPE_SELL_LIMIT ? price>(pr+lv) : true ); } //+------------------------------------------------------------------+
En los métodos, según el tipo de orden, se determina el precio a partir del cual debemos comprobar la distancia de colocación de la orden o de las órdenes stop, retornando true si la distancia superior al nivel mínimo de StopLevel. De lo contrario, retornamos false, lo que indicará que los valores del precio de colocación de la orden o de las órdenes stop no son correctos.
Métodos que retornan si la distancia de un StopLoss, un TakeProfit o el nivel de colocación de una orden respecto al FreezeLevel es correcta:
//+------------------------------------------------------------------+ //| Return the flag checking the validity of the | //| distance from the price to StopLoss by FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckStopLossByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double sl,const CSymbol *symbol_obj) { if(symbol_obj.TradeFreezeLevel()==0 || order_type>ORDER_TYPE_SELL) return true; double lv=symbol_obj.TradeFreezeLevel()*symbol_obj.Point(); double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : symbol_obj.Ask()); return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? sl<(pr-lv) : sl>(pr+lv)); } //+------------------------------------------------------------------+ //| Return the flag checking the distance validity of the | //| from the price to TakeProfit by FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckTakeProfitByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double tp,const CSymbol *symbol_obj) { if(symbol_obj.TradeFreezeLevel()==0 || order_type>ORDER_TYPE_SELL) return true; double lv=symbol_obj.TradeFreezeLevel()*symbol_obj.Point(); double pr=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : symbol_obj.Ask()); return(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? tp>(pr+lv) : tp<(pr-lv)); } //+------------------------------------------------------------------+ //| Return the flag checking the validity of the distance | //| from the price to the order price by FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckPriceByFreezeLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj) { if(symbol_obj.TradeFreezeLevel()==0 || order_type<ORDER_TYPE_BUY_LIMIT) return true; double lv=symbol_obj.TradeFreezeLevel()*symbol_obj.Point(); double pr=(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.BidLast()); return ( order_type==ORDER_TYPE_SELL_STOP || order_type==ORDER_TYPE_SELL_STOP_LIMIT || order_type==ORDER_TYPE_BUY_LIMIT ? price<(pr-lv) : order_type==ORDER_TYPE_BUY_STOP || order_type==ORDER_TYPE_BUY_STOP_LIMIT || order_type==ORDER_TYPE_SELL_LIMIT ? price>(pr+lv) : true ); } //+------------------------------------------------------------------+
Exactamente de la misma forma que al comprobar la distancia según el nivel StopLevel, aquí comprobamos la distancia desde el precio actual para el tipo
de orden hasta el precio de colocación de la orden o las órdenes stop.
Si el nivel de congelación para el símbolo ha sido establecido en 0, esto indicará la ausencia de nivel de congelación. Por eso, primero
comprobamos el valor cero de StopLevel, y luego retornamos
true si se confirma la ausencia del nivel de congelación de las operaciones comerciales.
A diferencia del nivel de congelación FreezeLevel, si el nivel mínimo de colocación de stops StopLevel tiene un valor cero, no indica la ausencia del mismo, sino que este nivel es flotante, por lo que deberemos controlarlo según la situación. Realizaremos esto en los próximos artículos, al implementar el procesamiento de los errores retornados por el servidor comercial.
Método para comprobar los valores de los parámetros según los niveles StopLevel y FreezeLevel
//+------------------------------------------------------------------+ //| Check parameter values by StopLevel and FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckLevels(const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, double price, double limit, double sl, double tp, const CSymbol *symbol_obj, const string source_method) { //--- the result of conducting all checks bool res=true; //--- StopLevel //--- If this is not a position closure/order removal if(action!=ACTION_TYPE_CLOSE && action!=ACTION_TYPE_CLOSE_BY) { //--- When placing a pending order if(action>ACTION_TYPE_SELL) { //--- If the placement distance in points is less than StopLevel if(!this.CheckPriceByStopLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_STOP_LEVEL); res &=false; } } //--- If StopLoss is present if(sl>0) { //--- If StopLoss distance in points from the open price is less than StopLevel double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); if(!this.CheckStopLossByStopLevel(order_type,price_open,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL); res &=false; } } //--- If TakeProfit is present if(tp>0) { double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); //--- If TakeProfit distance in points from the open price is less than StopLevel if(!this.CheckTakeProfitByStopLevel(order_type,price_open,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL); res &=false; } } } //--- FreezeLevel //--- If this is a position closure/order removal/modification if(action>ACTION_TYPE_SELL_STOP_LIMIT) { //--- If this is a position if(order_type<ORDER_TYPE_BUY_LIMIT) { //--- StopLoss modification if(sl>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckStopLossByFreezeLevel(order_type,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL); res &=false; } } //--- TakeProfit modification if(tp>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckTakeProfitByFreezeLevel(order_type,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL); res &=false; } } } //--- If this is a pending order else { //--- Placement price modification if(price>0) { //--- If the distance from the price to the order activation price is less than FreezeLevel if(!this.CheckPriceByFreezeLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL); res &=false; } } } } return res; } //+------------------------------------------------------------------+
Dependiendo del tipo de operación comercial realizada y del tipo de orden/posición, se comprueban los niveles de precio con respecto a los niveles StopLevel y FreezeLevel, y si los precios no son correctos, registramos el código del error en la lista de errores, añadiendo posteriormente false al resultado. Una vez finalizadas todas las comprobaciones, el resultado final se retorna al método que ha realizado la llamada.
Método general de comprobación de todas las limitaciones y errores:
//+------------------------------------------------------------------+ //| Check limitations and errors | //+------------------------------------------------------------------+ bool CTrading::CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method, const double limit=0, double sl=0, double tp=0) { //--- the result of conducting all checks bool res=true; //--- Clear the error list this.m_list_errors.Clear(); this.m_list_errors.Sort(); //--- Check trading limitations res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp); //--- Check the funds sufficiency for opening positions/placing orders if(action<ACTION_TYPE_CLOSE_BY) res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method); //--- Check parameter values by StopLevel and FreezeLevel res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method); //--- If there are limitations, display the header and the error list if(!res) { //--- Request was rejected before sending to the server due to: int total=this.m_list_errors.Total(); if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- For MQL5, first display the list header followed by the error list #ifdef __MQL5__ ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_REQUEST_REJECTED_DUE)); for(int i=0;i<total;i++) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); //--- For MQL4, the journal messages are displayed in the reverse order: the error list in the reverse loop is followed by the list header #else for(int i=total-1;i>WRONG_VALUE;i--) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_REQUEST_REJECTED_DUE)); #endif } } return res; } //+------------------------------------------------------------------+
En el método, llamamos secuencialmente el método para comprobar las
limitaciones en la realización de operaciones comerciales, el método
para comprobar si los fondos son suficientes para abrir una posición o
colocar una orden pendiente y el método para comprobar las distancias
mínimas de las órdenes stop de acuerdo con los niveles StopLevel y FreezeLevel.
El resultado del funcionamiento de cada uno de los métodos se añade al valor retornado desde el método.
En el caso de hallarse limitaciones o errores, se mostrará en el diario una lista completa con los errores encontrados.
Finalmente,
se retorna el resultado de todas las comprobaciones.
Método que establece la bandera de permiso de uso de sonidos para todos los objetos comerciales de todos los símbolos utilizados:
//+------------------------------------------------------------------+ //| Set the flag enabling sounds | //+------------------------------------------------------------------+ void CTrading::SetUseSounds(const bool flag) { //--- Set the flag enabling sounds this.m_use_sound=flag; //--- Get the symbol list CArrayObj *list=this.m_symbols.GetList(); if(list==NULL || list.Total()==0) return; //--- In a loop by the list of symbols int total=list.Total(); for(int i=0;i<total;i++) { //--- get the next symbol object CSymbol *symbol_obj=list.At(i); if(symbol_obj==NULL) continue; //--- get a symbol trading object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) continue; //--- set the flag enabling sounds for a trading object trade_obj.SetUseSound(flag); } } //+------------------------------------------------------------------+
En el método, establecemos directamente la bandera global de permiso de uso de sonidos
por parte de la clase comercial, y a continuación, en un ciclo por todos los símbolos utilizados, establecemos
banderas similares para cada objeto comercial de cada símbolo utilizado.
Dado que hemos decidido usar métodos comerciales de plantilla para poder transmitir en ellos los valores de los precios en forma de precio, o
bien en forma de distancia,
redefinimos los métodos comerciales definidos anteriormente en la sección pública de la clase y establecemos
los tipos de datos de plantilla para ciertos parámetros:
//--- Open (1) Buy, (2) Sell position template<typename SL,typename TP> bool OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX); template<typename SL,typename TP> bool OpenSell(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX); //--- Modify a position template<typename SL,typename TP> bool ModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE); //--- Close a position (1) fully, (2) partially, (3) by an opposite one bool ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX); bool ClosePositionPartially(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX); bool ClosePositionBy(const ulong ticket,const ulong ticket_by); //--- Set (1) BuyStop, (2) BuyLimit, (3) BuyStopLimit pending order template<typename PR,typename SL,typename TP> bool PlaceBuyStop(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename SL,typename TP> bool PlaceBuyLimit(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename PL,typename SL,typename TP> bool PlaceBuyStopLimit(const double volume, const string symbol, const PR price_stop, const PL price_limit, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); //--- Set (1) SellStop, (2) SellLimit, (3) SellStopLimit pending order template<typename PR,typename SL,typename TP> bool PlaceSellStop(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename SL,typename TP> bool PlaceSellLimit(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename PL,typename SL,typename TP> bool PlaceSellStopLimit(const double volume, const string symbol, const PR price_stop, const PL price_limit, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); //--- Modify a pending order template<typename PR,typename PL,typename SL,typename TP> bool ModifyOrder(const ulong ticket, const PR price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL limit=WRONG_VALUE, datetime expiration=WRONG_VALUE, ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE); //--- Remove a pending order bool DeleteOrder(const ulong ticket); };
Veamos la implementación del método de apertura de una posición Buy:
//+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { ENUM_ACTION_TYPE action=ACTION_TYPE_BUY; ENUM_ORDER_TYPE order=ORDER_TYPE_BUY; //--- Get a symbol object by a symbol name CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); if(symbol_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Update symbol quotes symbol_obj.RefreshRates(); //--- Set the prices if(!this.SetPrices(order,0,sl,tp,0,DFUN,symbol_obj)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ)); return false; } //--- In case of trading limitations, funds insufficiency, //--- StopLevel or FreezeLevel limitations, play the error sound and exit if(!this.CheckErrors(volume,symbol_obj.Ask(),action,ORDER_TYPE_BUY,symbol_obj,DFUN,0,this.m_req_price.sl,this.m_req_price.tp)) { if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order); return false; } //--- Send the request bool res=trade_obj.OpenPosition(POSITION_TYPE_BUY,volume,this.m_req_price.sl,this.m_req_price.tp,magic,comment,deviation); //--- If the request is successful, play the success sound set for a symbol trading object for this type of trading operation if(res) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order); } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order); } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
Como valores de los parámetros de StopLoss y TakeProfit, podemos transmitir al método un valor double, o bien un valor long, ulong, int, uint. El método de establecimiento de precios registra en la estructura de precios m_req_price los precios correctamente calculados y normalizados; dichos precios calculados luego son transmitidos al método comercial del objeto comercial del símbolo. El resto de acciones se describe en los comentarios al código, y seguro que el lector las comprenderá sin especial complicación. En cualquier caso, podrá expresar cualquier duda en los comentarios al artículo.
Los demás métodos comerciales se han creado de forma análoga, por lo que no vamos a analizarlos aquí, para ahorrar espacio. El lector siempre podrá estudiar por sí mismo con mayor detalle los archivos adjuntos al final del artículo.
Con esto, podemos dar por finalizada la mejora de la clase CTrading.
Ya hemos analizado los cambios
principales realizados en esta clase.
El resto de mejoras, pequeñas y poco significativas, no serán analizadas aquí, pues no son importantes a la hora de comprender la esencia
del artículo. El lector podrá consultar dichas mejoras en los archivos adjuntos al final.
Hemos añadido a Datas.mqh la constante del código de error, que omitimos y no añadimos en el artículo anterior:
//--- CTrading MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED, // Trade operations are not allowed in the terminal (the AutoTrading button is disabled) MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED, // EA is not allowed to trade (F7 --> Common --> Allow Automated Trading) MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED, // Trading is disabled for the current account MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED, // Trading on the trading server side is disabled for EAs on the current account MSG_LIB_TEXT_TERMINAL_NOT_CONNECTED, // No connection to the trade server MSG_LIB_TEXT_REQUEST_REJECTED_DUE, // Request was rejected before sending to the server due to: MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR, // Insufficient funds for performing a trade MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED, // Exceeded maximum allowed aggregate volume of orders and positions in one direction MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME, // Request volume is less than the minimum acceptable one MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME, // Request volume exceeds the maximum acceptable one MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED, // Close by is disabled MSG_LIB_TEXT_INVALID_VOLUME_STEP, // Request volume is not a multiple of the minimum lot change step gradation MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL, // Symbols of opposite positions are not equal MSG_LIB_TEXT_SL_LESS_STOP_LEVEL, // StopLoss in points is less than a value allowed by symbol's StopLevel parameter MSG_LIB_TEXT_TP_LESS_STOP_LEVEL, // TakeProfit in points is less than a value allowed by symbol's StopLevel parameter MSG_LIB_TEXT_PR_LESS_STOP_LEVEL, // Order distance in points is less than a value allowed by symbol's StopLevel parameter MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL, // The distance from the price to StopLoss is less than a value allowed by symbol's FreezeLevel parameter MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL, // The distance from the price to TakeProfit is less than a value allowed by symbol's FreezeLevel parameter MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL, // The distance from the price to an order activation level is less than a value allowed by symbol's FreezeLevel parameter MSG_LIB_TEXT_UNSUPPORTED_SL_TYPE, // Unsupported StopLoss parameter type (should be 'int' or 'double') MSG_LIB_TEXT_UNSUPPORTED_TP_TYPE, // Unsupported TakeProfit parameter type (should be 'int' or 'double') MSG_LIB_TEXT_UNSUPPORTED_PR_TYPE, // Unsupported price parameter type (should be 'int' or 'double') MSG_LIB_TEXT_UNSUPPORTED_PL_TYPE, // Unsupported limit order price parameter type (should be 'int' or 'double') MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ, // Unsupported price parameter type in a request };
Y el texto correspondiente a este código:
//--- CEngine {"С момента последнего запуска ЕА торговых событий не было","There have been no trade events since the last launch of EA"}, {"Не удалось получить описание последнего торгового события","Failed to get the description of the last trading event"}, {"Не удалось получить список открытых позиций","Failed to get open positions list"}, {"Не удалось получить список установленных ордеров","Failed to get pending orders list"}, {"Нет открытых позиций","No open positions"}, {"Нет установленных ордеров","No placed orders"}, {"В терминале нет разрешения на проведение торговых операций (отключена кнопка \"Авто-торговля\")","There is no permission to conduct trading operations in the terminal (the \"AutoTrading\" button is disabled)"}, {"Для советника нет разрешения на проведение торговых операций (F7 --> Общие --> \"Разрешить автоматическую торговлю\")","EA does not have permission to conduct trading operations (F7 --> Common --> \"Allow Automatic Trading\")"}, {"Для текущего счёта запрещена торговля","Trading is prohibited for the current account"}, {"Для советников на текущем счёте запрещена торговля на стороне торгового сервера","From the side of the trading server, trading for EA on the current account is prohibited"}, {"Нет связи с торговым сервером","No connection to the trading server"}, {"Запрос отклонён до отправки на сервер по причине:","The request was rejected before being sent to the server due to:"}, {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"}, {"Превышен максимальный совокупный объём ордеров и позиций в одном направлении","Exceeded the maximum total volume of orders and positions in one direction"}, {"Объём в запросе меньше минимально-допустимого","The volume in the request is less than the minimum allowable"}, {"Объём в запросе больше максимально-допустимого","The volume in the request is greater than the maximum allowable"}, {"Закрытие встречным запрещено","CloseBy orders is prohibited"}, {"Объём в запросе не кратен минимальной градации шага изменения лота","The volume in the request is not a multiple of the minimum gradation of the step for changing the lot"}, {"Символы встречных позиций не равны","Symbols of the two opposite positions are not equal"}, {"Размер StopLoss в пунктах меньше разрешённого параметром StopLevel символа","The StopLoss size in points is less than that allowed by the StopLevel parameter of the symbol"}, {"Размер TakeProfit в пунктах меньше разрешённого параметром StopLevel символа","The TakeProfit size in points is less than that allowed by the StopLevel parameter of the symbol"}, {"Дистанция установки ордера в пунктах меньше разрешённой параметром StopLevel символа","The distance to place an order in points is less than the symbol allowed by the StopLevel parameter"}, {"Дистанция от цены до StopLoss меньше разрешённой параметром FreezeLevel символа","The distance from the price to StopLoss is less than the symbol allowed by the FreezeLevel parameter"}, {"Дистанция от цены до TakeProfit меньше разрешённой параметром FreezeLevel символа","The distance from the price to TakeProfit is less than the symbol allowed by the FreezeLevel parameter"}, {"Дистанция от цены до цены срабатывания ордера меньше разрешённой параметром FreezeLevel символа","The distance from the price to the order triggering price is less than the symbol allowed by the FreezeLevel parameter"}, {"Неподдерживаемый тип параметра StopLoss (необходимо int или double)","Unsupported StopLoss parameter type (int or double required)"}, {"Неподдерживаемый тип параметра TakeProfit (необходимо int или double)","Unsupported TakeProfit parameter type (int or double required)"}, {"Неподдерживаемый тип параметра цены (необходимо int или double)","Unsupported price parameter type (int or double required)"}, {"Неподдерживаемый тип параметра цены limit-ордера (необходимо int или double)","Unsupported type of price parameter for limit order (int or double required)"}, {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"}, };
Los usuarios han informado en varias ocasiones sobre errores al obtener el último evento comercial. Lo que ocurre es que en el asesor de prueba de los artículos (que describe la obtención de los eventos comerciales), se ha organizado la obtención del hecho de un evento comercial ocurrido mediante la comparación del evento pasado con el valor del actual. Esto resultaba suficiente al simular el funcionamiento de la biblioteca en cuanto al seguimiento de eventos comerciales, dado que al escribir los artículos sobre los eventos comerciales, aún no se presuponía el uso de la versión incompleta de la biblioteca en nuestros programas. Pero resulta que la obtención del hecho de los eventos comerciales es algo requerido por los usuarios, y debemos saber cuál ha sido el último evento sucedido.
El método de obtención del evento comercial que hemos creado no siempre avisa sobre los eventos: si colocamos dos veces seguidas, por ejemplo, una orden pendiente, la segunda colocación no es monitoreada en el programa (en la biblioteca se monitorean todos los eventos), dado que el penúltimo y el último evento son iguales ("colocación de una orden pendiente"), pero las órdenes colocadas son distintas.
Por eso, vamos a corregir este comportamiento. Hoy simplemente vamos a crear una bandera que comunicará al programa que ha sucedido un cierto evento, y ya en el programa podremos ver de qué evento se trata. En el próximo artículo, terminaremos de implementar la obtención de eventos comerciales en el programa: crearemos una lista completa con todos los eventos sucedidos simultáneamente, y transmitiremos esta al programa. Así, en el programa podremos no solo conocer el hecho de un evento comercial sucedido, sino también ver todos los eventos comerciales ocurridos de forma simultánea, de la misma manera que sucede con los eventos de la cuenta y los eventos de la colección de símbolos.
Abrimos el archivo de la colección de eventos EventsCollection.mqh y escribimos las adiciones necesarias.
En el constructor de la clase reseteamos la bandera del último evento:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT) { this.m_list_trade_events.Clear(); this.m_list_trade_events.Sort(SORT_BY_EVENT_TIME_EVENT); this.m_list_trade_events.Type(COLLECTION_EVENTS_ID); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_chart_id=::ChartID(); this.m_is_event=false; ::ZeroMemory(this.m_tick); } //+------------------------------------------------------------------+
Al inicio del método Refresh(), también reseteamos la bandera del último evento:
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals, const double changed_volume) { //--- Exit if the lists are empty if(list_history==NULL || list_market==NULL) return; //--- this.m_is_event=false; //--- If the event is in the market environment if(is_market_event) {
En cada uno de los métodos de creación del evento comercial, en los bloques de adición del evento comercial a la lista, de envío
del evento al gráfico del programa de control y de establecimiento del valor del último evento:
//--- Add the event object if it is not in the list if(!this.IsPresentEventInList(event)) { this.m_list_trade_events.InsertSort(event); //--- Send a message about the event and set the value of the last trading event event.SendEvent(); this.m_trade_event=event.TradeEvent(); }
completamos el establecimiento de la bandera de evento comercial:
if(!this.IsPresentEventInList(event)) { this.m_list_trade_events.InsertSort(event); //--- Send a message about the event and set the value of the last trading event this.m_trade_event=event.TradeEvent(); this.m_is_event=true; event.SendEvent(); }
Para que resulte más cómodo buscar en todos los sitios donde debemos establecer la bandera, podemos usar la búsqueda Ctrl+F.
En el campo de búsqueda, introducimos "event.SendEvent();" sin comillas. Después, escribimos en cada lugar encontrado en el código
el establecimiento de la bandera de evento de la forma mostrada en el
listado anterior.
También cebemos escribir ciertos cambios en el archivo Engine.mqh.
En la sección pública de la clase CEngine, añadimos el método que retorna la bandera de evento comercial sucedido:
//--- Return the (1) hedge account, (2) working in the tester, (3) account event, (4) symbol event and (5) trading event flag bool IsHedge(void) const { return this.m_is_hedge; } bool IsTester(void) const { return this.m_is_tester; } bool IsAccountsEvent(void) const { return this.m_accounts.IsEvent(); } bool IsSymbolsEvent(void) const { return this.m_symbols.IsEvent(); } bool IsTradeEvent(void) const { return this.m_events.IsEvent(); }
Asimismo, añadimos al bloque que contiene los métodos de trabajo con
sonidos el método que establece la bandera de uso de sonidos:
//--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols void SetSoundsStandart(const string symbol=NULL) { this.m_trading.SetSoundsStandart(symbol); } //--- Set the flag of using sounds void SetUseSounds(const bool flag) { this.m_trading.SetUseSounds(flag); } //--- Set a sound for a specified order/position type and symbol. 'mode' specifies an event a sound is set for //--- (symbol=NULL) for trading objects of all symbols, (symbol!=NULL) for a trading object of a specified symbol void SetSound(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,const string symbol=NULL) { this.m_trading.SetSound(mode,action,sound,symbol); } //--- Play a sound by its description bool PlaySoundByDescription(const string sound_description);
El método simplemente llama al método homónimo de la clase CTrading, que hemos analizado más arriba.
Al inicio del método de comprobación de eventos comerciales, reseteamos
la bandera del evento comercial:
//+------------------------------------------------------------------+ //| Check trading events | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize trading events' flags this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; this.m_events.SetEvent(false); //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- First launch actions if(this.IsFirstStart()) { this.m_last_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the changes in the market status and account history this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(), this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewPositions(),this.m_history.NewDeals(), this.m_market.ChangedVolumeValue()); //--- Receive the last account trading event this.m_last_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Los métodos de trabajo con la clase comercial también han sufrido cambios: ahora también tienen parámetros de plantilla:
//--- Open (1) Buy, (2) Sell position template<typename SL,typename TP> bool OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, SL sl=0, TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX); template<typename SL,typename TP> bool OpenSell(const double volume, const string symbol, const ulong magic=ULONG_MAX, SL sl=0, TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX); //--- Modify a position template<typename SL,typename TP> bool ModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE); //--- Close a position (1) fully, (2) partially, (3) by an opposite one bool ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX); bool ClosePositionPartially(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX); bool ClosePositionBy(const ulong ticket,const ulong ticket_by); //--- Set (1) BuyStop, (2) BuyLimit, (3) BuyStopLimit pending order template<typename PR,typename SL,typename TP> bool PlaceBuyStop(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename SL,typename TP> bool PlaceBuyLimit(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename PL,typename SL,typename TP> bool PlaceBuyStopLimit(const double volume, const string symbol, const PR price_stop, const PL price_limit, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); //--- Set (1) SellStop, (2) SellLimit, (3) SellStopLimit pending order template<typename PR,typename SL,typename TP> bool PlaceSellStop(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename SL,typename TP> bool PlaceSellLimit(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); template<typename PR,typename PL,typename SL,typename TP> bool PlaceSellStopLimit(const double volume, const string symbol, const PR price_stop, const PL price_limit, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC); //--- Modify a pending order template<typename PR,typename SL,typename TP,typename PL> bool ModifyOrder(const ulong ticket, const PR price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL stoplimit=WRONG_VALUE, datetime expiration=WRONG_VALUE, ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE); //--- Remove a pending order bool DeleteOrder(const ulong ticket);
La implementación de los métodos comerciales también ha sido modificada de acuerdo con los datos de plantilla transmitidos a los métodos comerciales de la clase CTrading:
//+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CEngine::OpenBuy(const double volume,const string symbol,const ulong magic=ULONG_MAX,SL sl=0,TP tp=0,const string comment=NULL,const ulong deviation=ULONG_MAX) { return this.m_trading.OpenBuy(volume,symbol,magic,sl,tp,comment,deviation); } //+------------------------------------------------------------------+ //| Open a Sell position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CEngine::OpenSell(const double volume,const string symbol,const ulong magic=ULONG_MAX,SL sl=0,TP tp=0,const string comment=NULL,const ulong deviation=ULONG_MAX) { return this.m_trading.OpenSell(volume,symbol,magic,sl,tp,comment,deviation); } //+------------------------------------------------------------------+ //| Modify a position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CEngine::ModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE) { return this.m_trading.ModifyPosition(ticket,sl,tp); } //+------------------------------------------------------------------+ //| Close a position in full | //+------------------------------------------------------------------+ bool CEngine::ClosePosition(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX) { return this.m_trading.ClosePosition(ticket,comment,deviation); } //+------------------------------------------------------------------+ //| Close a position partially | //+------------------------------------------------------------------+ bool CEngine::ClosePositionPartially(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX) { return this.m_trading.ClosePositionPartially(ticket,volume,comment,deviation); } //+------------------------------------------------------------------+ //| Close a position by an opposite one | //+------------------------------------------------------------------+ bool CEngine::ClosePositionBy(const ulong ticket,const ulong ticket_by) { return this.m_trading.ClosePositionBy(ticket,ticket_by); } //+------------------------------------------------------------------+ //| Place BuyStop pending order | //+------------------------------------------------------------------+ template<typename PR,typename SL,typename TP> bool CEngine::PlaceBuyStop(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=WRONG_VALUE, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC) { return this.m_trading.PlaceBuyStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time); } //+------------------------------------------------------------------+ //| Place BuyLimit pending order | //+------------------------------------------------------------------+ template<typename PR,typename SL,typename TP> bool CEngine::PlaceBuyLimit(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=WRONG_VALUE, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC) { return this.m_trading.PlaceBuyLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time); } //+------------------------------------------------------------------+ //| Place BuyStopLimit pending order | //+------------------------------------------------------------------+ template<typename PR,typename PL,typename SL,typename TP> bool CEngine::PlaceBuyStopLimit(const double volume, const string symbol, const PR price_stop, const PL price_limit, const SL sl=0, const TP tp=0, const ulong magic=WRONG_VALUE, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC) { return this.m_trading.PlaceBuyStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time); } //+------------------------------------------------------------------+ //| Place SellStop pending order | //+------------------------------------------------------------------+ template<typename PR,typename SL,typename TP> bool CEngine::PlaceSellStop(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=WRONG_VALUE, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC) { return this.m_trading.PlaceSellStop(volume,symbol,price,sl,tp,magic,comment,expiration,type_time); } //+------------------------------------------------------------------+ //| Place SellLimit pending order | //+------------------------------------------------------------------+ template<typename PR,typename SL,typename TP> bool CEngine::PlaceSellLimit(const double volume, const string symbol, const PR price, const SL sl=0, const TP tp=0, const ulong magic=WRONG_VALUE, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC) { return this.m_trading.PlaceSellLimit(volume,symbol,price,sl,tp,magic,comment,expiration,type_time); } //+------------------------------------------------------------------+ //| Place SellStopLimit pending order | //+------------------------------------------------------------------+ template<typename PR,typename PL,typename SL,typename TP> bool CEngine::PlaceSellStopLimit(const double volume, const string symbol, const PR price_stop, const PL price_limit, const SL sl=0, const TP tp=0, const ulong magic=WRONG_VALUE, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC) { return this.m_trading.PlaceSellStopLimit(volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time); } //+------------------------------------------------------------------+ //| Modify a pending order | //+------------------------------------------------------------------+ template<typename PR,typename SL,typename TP,typename PL> bool CEngine::ModifyOrder(const ulong ticket, const PR price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL stoplimit=WRONG_VALUE, datetime expiration=WRONG_VALUE, ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE) { return this.m_trading.ModifyOrder(ticket,price,sl,tp,stoplimit,expiration,type_time); } //+------------------------------------------------------------------+ //| Remove a pending order | //+------------------------------------------------------------------+ bool CEngine::DeleteOrder(const ulong ticket) { return this.m_trading.DeleteOrder(ticket); } //+------------------------------------------------------------------+
Con esto, podemos dar por finalizadas la mejora de las clases comerciales y la corrección de la obtención de eventos comerciales.
Simulación
Para la simulación, vamos a tomar el asesor del artículo anterior y guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part23\ con el nuevo nombre TestDoEasyPart23.mq5.
Antes de proceder, pondremos un poco de orden; todas las acciones de inicialización de la biblioteca las trasladaremos a la función aparte OnInitDoEasy():
//+------------------------------------------------------------------+ //| Initializing DoEasy library | //+------------------------------------------------------------------+ void OnInitDoEasy() { //--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { int total=SymbolsTotal(false); string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов."; string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols."; string caption=TextByLanguage("Внимание!","Attention!"); string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; string message=TextByLanguage(ru,en); int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); int mb_res=MessageBox(message,caption,flags); switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; default: break; } } //--- Fill in the array of used symbols used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); //--- Set the type of the used symbol list in the symbol collection engine.SetUsedSymbols(array_used_symbols); //--- Displaying the selected mode of working with the symbol object collection Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal()); //--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); //--- Pass all existing collections to the trading class engine.TradingOnInit(); //--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(false); //--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); //--- Set the general flag of using sounds engine.SetUseSounds(InpUseSounds); //--- Set controlled values for symbols //--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=0) { //--- In a loop by the list, set the necessary values for tracked symbol properties //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program for(int i=0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) continue; //--- Set control of the symbol price increase by 100 points symbol.SetControlBidInc(100*symbol.Point()); //--- Set control of the symbol price decrease by 100 points symbol.SetControlBidDec(100*symbol.Point()); //--- Set control of the symbol spread increase by 40 points symbol.SetControlSpreadInc(40); //--- Set control of the symbol spread decrease by 40 points symbol.SetControlSpreadDec(40); //--- Set control of the current spread by the value of 40 points symbol.SetControlSpreadLevel(40); } } //--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) { //--- Set control of the profit increase to 10 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0); //--- Set control of the funds increase to 15 account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0); //--- Set profit control level to 20 account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0); } } //+------------------------------------------------------------------+
Todo el contenido de esta función antes estaba escrito en el manejador OnInit() del asesor. Ahora, después de trasladar las acciones de inicialización de la biblioteca a una función aparte (donde se puede escribir todo lo necesario para el funcionamiento del asesor), el manejador OnInit() resulta más aseado y visual:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining EA graphical objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Set trailing activation button status ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Check playing a standard sound by macro substitution and a custom sound by description engine.PlaySoundByDescription(SND_OK); Sleep(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2")); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
De la misma forma, ahora hemos implementado el trabajo con todos los eventos de la biblioteca en la función OnDoEasyEvent():
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; string event="::"+string(idx); //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=engine.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=engine.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- In case of a property value increase if(reason==BASE_EVENT_REASON_INC) { //--- Display an event in the journal Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); //--- if this is an equity increase if(idx==ACCOUNT_PROP_EQUITY) { //--- Get the list of all open positions CArrayObj* list_positions=engine.GetListMarketPosition(); //--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE); if(list_positions!=NULL) { //--- Sort the list by profit considering commission and swap list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the position index with the highest profit int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list_positions.At(index); if(position!=NULL) { //--- Get a ticket of a position with the highest profit and close the position by a ticket engine.ClosePosition(position.Ticket()); } } } } } //--- Other events are simply displayed in the journal if(reason==BASE_EVENT_REASON_DEC) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { Print(DFUN,engine.GetLastTradeEventDescription()); } } //+------------------------------------------------------------------+
Aquí, todo está dividido en bloques de acuerdo con el evento entratante. Por cierto, el trabajo con los eventos comerciales aquí se presenta mediante la muestra simple del nombre del último evento en el diario. Si resulta necesario procesar un evento concreto, aquí podemos saber de qué evento se trata y decidir cómo procesarlo: podemos hacerlo directamente aquí, o podemos definir la bandera para cada uno de los eventos comerciales, resetear/establecer las banderas de los eventos comerciales, y ya luego procesarlas en el lugar del programa que nos resulte más cómodo. Cada cual lo hace como cree conveniente.
Queremos destacar que la función OnDoEasyEvent() se llama desde el manejador del asesor OnChartEvent() al trabajar fuera
del simulador.
al trabajar en el simulador, desde OnTick() se llama la función EventsHandling():
//+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void EventsHandling(void) { //--- If a trading event is present if(engine.IsTradeEvent()) { long lparam=0; double dparam=0; string sparam=""; OnDoEasyEvent(CHARTEVENT_CUSTOM+engine.LastTradeEvent(),lparam,dparam,sparam); } //--- If there is an account event if(engine.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=engine.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(engine.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=engine.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
En la función se analizan las listas de los eventos correspondientes y se rellenan los parámetros del evento que después se envía a la función OnDoEasyEvent(). De esta forma, nuestro trabajo con los eventos estará organizado en la función OnDoEasyEvent(), independientemente de donde se haya iniciado el asesor, en el simulador o fuera del mismo. Si se ha iniciado en el simulador, el trabajo con los eventos tendrá lugar desde OnTick(), si se ha iniciado fuera, el trabajo con los eventos se realizará desde OnChartEvent().
De esta manera, la función OnTick() ahora ha adoptado el aspecto siguiente:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); // Working in the timer PressButtonsControl(); // Button pressing control EventsHandling(); // Working with events } //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing of pending orders } } //+------------------------------------------------------------------+
Para comprobar el funcionamiento de los métodos que controlan que los valores en los parámetros de las órdenes comerciales sean correctos,
tenemos que eliminar del asesor la corrección automática de los valores incorrectos.
En la función de procesamiento, eliminamos pulsando los botones PressButtonEvents() todo lo relacionado con la corrección de los
valores de los parámetros de las órdenes comerciales:
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { string comment=""; //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Open Buy position engine.OpenBuy(lot,Symbol(),magic_number,sl,tp); // No comment - the default comment is to be set } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Set BuyLimit order engine.PlaceBuyLimit(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order")); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- Set BuyStop order engine.PlaceBuyStop(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Get the correct BuyStop order placement price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Calculate BuyLimit order price relative to BuyStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit); //--- Set BuyStopLimit order engine.PlaceBuyStopLimit(lot,Symbol(),price_set_stop,price_set_limit,sl,tp,magic_number,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order")); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- Open Sell position engine.OpenSell(lot,Symbol(),magic_number,sl,tp); // No comment - the default comment is to be set } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- Set SellLimit order engine.PlaceSellLimit(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный SellLimit","Pending order SellLimit")); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- Set SellStop order engine.PlaceSellStop(lot,Symbol(),price_set,sl,tp,magic_number,TextByLanguage("Отложенный SellStop","Pending SellStop order")); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Get the correct SellStop order price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Calculate SellLimit order price relative to SellStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=stoploss;//CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss); double tp=takeprofit;//CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit); //--- Set SellStopLimit order engine.PlaceSellStopLimit(lot,Symbol(),price_set_stop,price_set_limit,sl,tp,magic_number,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order")); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY)) {
En esencia, debemos dejar solo la llamada de los métodos comerciales con los parámetros que han sido establecidos en los ajustes del asesor:
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { string comment=""; //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Open Buy position engine.OpenBuy(lot,Symbol(),magic_number,stoploss,takeprofit); // No comment - the default comment is to be set } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Set BuyLimit order engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order")); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Set BuyStop order engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Set BuyStopLimit order engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order")); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Open Sell position engine.OpenSell(lot,Symbol(),magic_number,stoploss,takeprofit); // No comment - the default comment is to be set } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Set SellLimit order engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный SellLimit","Pending SellLimit order")); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Set SellStop order engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный SellStop","Pending SellStop order")); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Set SellStopLimit order engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic_number,TextByLanguage("Отложенный SellStopLimit","Pending order SellStopLimit")); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY)) {
De esta forma, trasladamos a la biblioteca la tarea de controlar que los parámetros sean correctos, mientras que al asesor solo le daremos
las órdenes imprescindibles. Como es natural, en la biblioteca no está todo preparado, pero iremos añadiendo paulatinamente la
funcionalidad necesaria para trabajar con comodidad.
El lector podrá ver el código completo del experto en los archivos adjuntos al artículo.
Compilamos el asesor y lo iniciamos en el simulador, estableciendo preliminarmente en los parámetros un valor Lots
igual 10,
mientras que para los valores StopLoss in points y TakeProfit in points establecemos un valor
igual a 1 punto:
De esta manera, intentaremos abrir una posición con un lote no permitido, para que no haya fondos suficientes para su apertura, y trataremos de infringir el requisito sobre la distancia mínima de establecimiento de órdenes stop reglamentada por el parámetro StopLevel del símbolo:
El asesor ha mostrado en el diario dos errores "no hay fondos suficientes para realizar la operación comercial", y "el valor de StopLoss
infringe los requisitos del parámetro StopLevel del símbolo". Pero nosotros también hemos establecido para el TakeProfit un valor igual a
un punto. ¿Por qué no hemos obtenido error? Porque aquí no hay error:
el establecimiento de los niveles de TakeProfit y StopLoss
dentro del nivel mínimo de SYMBOL_TRADE_STOPS_LEVEL se realiza según las normas
los niveles de TakeProfit y StopLoss se deben comparar con el precio actual al que se puede realizar la operación en dirección contraria
- La compra se realiza al precio Ask: los niveles de TakeProfit y StopLoss se deben comparar con el precio actual de venta Bid.
- La venta se realiza al precio Bid: los niveles de TakeProfit y StopLoss se deben comparar con el precio actual de compra Ask.
La compra se realiza al precio Ask |
La venta se realiza al precio Bid |
---|---|
TakeProfit >= Bid StopLoss <= Bid |
TakeProfit <= Ask StopLoss >= Ask |
Dado que hemos pulsado el botón de apertura de una posición Buy, el precio de cierre para ella será Bid, y el precio de apertura, Ask. Los niveles de las órdenes stop los colocaremos a partir del precio de apertura, en este caso, Ask. Así, el TakeProfit ha caído en el nivel de Ask+1 punto, y el StopLoss, en el nivel Ask-1 punto. Aquí podemos comprender que el TakeProfit ha resultado por encima del Bid a partir del cual calculamos la distancia permitida, mientras que el StopLoss se encontraría dentro de los requisitos solo con un spread igual a cero. Y dado que el spread se encontraba en la zona de los diez puntos en el momento de la apertura, como es natural, hemos entrado en la limitación de los requisitos de distancia mínima para el StopLoss.
¿Qué es lo próximo?
En el próximo artículo, comenzaremos a implementar el procesamiento de los errores al enviar órdenes comerciales incorrectas y el
procesamiento de los errores retornados por el servidor.
Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del asesor de prueba. El lector podrá
descargar y poner a prueba todo por sí mismo.
Si tiene cualquier duda, observación o sugerencia, podrá formularla en los comentarios al artículo.
Artículos de esta serie:
Parte 1. Concepto, organización de datoss Parte 2. Colección de órdenes y transacciones históricas
Parte 3. Colección de órdenes y posiciones de mercado, organización de la búsqueda
Parte 4. Eventos comerciales. Concepto
Parte 5. Clases y concepto de los eventos comerciales. Envío de eventos al programa
Parte 6. Eventos en las cuentas de compensación
Parte 7. Eventos de activación de órdenes StopLimit, preparación de la funcionalidad para los eventos de modificación de órdenes y posiciones
Parte 8. Eventos de modificación de órdenes y posiciones
Parte 9. Compatibilidad con MQL4 - Preparando los datos
Parte 10. Compatibilidad con MQL4 - Eventos de apertura de posición y activación de órdenes pendientes
Parte 11. Compatibilidad con MQL4 - Eventos de cierre de posición
Parte 12. Implementando la clase de objeto "cuenta" y la colección de objetos de cuenta
Parte 13. Eventos del objeto "cuenta"
Parte 14. El objeto "Símbolo"
Parte 15. Colección de objetos de símbolo
Parte 16. Eventos de la colección de símbolos
Parte 17. Interactividad de los objetos de la biblioteca
Parte 18. Interactividad del objeto de cuenta con cualquier otro objeto de la biblioteca
Parte 19. Clase de mensajes de la biblioteca
Parte 20. Creación y guardado de los recursos del programa
Parte 21. Clases comerciales - El objeto comercial multiplataforma básico
Parte 22. Clases comerciales - Clase comercial principal, control de limitaciones
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/7286
- 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