Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXIII): Clase comercial principal - control de parámetros permitidos

15 enero 2020, 08:58
Artyom Trishkin
0
350

Contenido

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.

Si la distancia en la orden comercial es inferior a la mínima, no tendrá sentido enviar dicha orden al servidor, ya que este la rechazará. Y para no sobrecargar el servidor con órdenes que sabemos de antemano incorrectas, debemos comprobar por nosotros mismos que el tamaño de las órdenes stop sea correcto, y también retornar el error antes de enviar la orden al servidor, en el caso de que se infrinja la distancia mínima.

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.

Volver al contenido

Artículos de esta serie:

Parte 1. Concepto, organización de datos
s 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 Software Corp.
Artículo original: https://www.mql5.com/ru/articles/7286

Archivos adjuntos |
MQL5.zip (3589.61 KB)
MQL4.zip (3589.62 KB)
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXII): Clases comerciales - Clase comercial principal, control de limitaciones Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXII): Clases comerciales - Clase comercial principal, control de limitaciones

En el artículo, comenzaremos a crear la clase comercial principal de la biblioteca, equipándola con la primera versión de la funcionalidad para la comprobacion primaria de los permisos de realización de operaciones comerciales. Asimismo, ampliaremos un poco las posibilidades y el contenido de la clase comercial básica.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXI): Clases comerciales - El objeto comercial multiplataforma básico Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXI): Clases comerciales - El objeto comercial multiplataforma básico

En este artículo vamos a comenzar un nuevo apartado de la biblioteca, las clases comerciales, y también vamos a analizar la creación de un objeto comercial básico único para las plataformas MetaTrader 5 y MetaTrader 4. Dicho objeto comercial, al enviar una solicitud al servidor, presupondrá que los parámetros de la solicitud comercial que se le han transmitido han sido verificados y son correctos.

Optimización móvil continua (Parte 1): Mecanismo de trabajo con los informes de optimización Optimización móvil continua (Parte 1): Mecanismo de trabajo con los informes de optimización

La primera parte del artículo está dedicada a la creación de una herramienta para trabajar con los informes de optimización y su importación desde el terminal, así como a los procesos de filtrado y clasificación de los datos obtenidos. MetaTrader 5 permite descargar informes sobre las pasadas de optimización, pero querríamos tener la posibilidad de añadir al informe nuestros propios datos.

Una rápida inmersión en MQL5 Una rápida inmersión en MQL5

¿Ha decidido estudiar el lenguaje de programación de estrategias de trading MQL5 pero no sabe nada sobre él? Hemos intentado describir el terminal de MQL5 y Meta Trader 5 desde el punto de vista de una persona iniciada y para ello hemos escrito este corto artículo introductorio. En este artículo encontrará una breve descripción de las posibilidades de este lenguaje, así como algunos consejos sobre cómo trabajar con MetaEditor 5 y el terminal.