Otras clases en la biblioteca DoEasy (Parte 66): Clases de Colección de Señales MQL5.com

Artyom Trishkin | 12 mayo, 2021

Contenido


Concepto

En el último artículo, creamos una clase de objeto de señal que constituye una señal formada a partir de un conjunto transmitido en el servicio de Señales de MQL5.com.
En esta ocasión, vamos a crear una clase de colección de las señales disponibles en la base de señales, que se pueden obtener utilizando la función SignalBaseSelect(), especificando el índice de la señal necesaria.
La colección nos permitirá guardar todas las señales disponibles en la base de datos, en una lista que será de utilidad para realizar la búsqueda y la clasificación. Podremos encontrar y retornar listas de señales según propiedades diversas, por ejemplo, listas con solo señales gratuitas, o solo con señales de pago; también podremos clasificar las listas según un parámetro, por ejemplo, la rentabilidad de señal, u obtener directamente el índice de una señal en la lista con un parámetro igual, mayor o menor al valor indicado. Conociendo de antemano el nombre de la señal necesaria, podremos encontrarla rápidamente en la colección para seguir trabajando con ella. En la clase de colección, organizamos la oportunidad de suscribirnos a una señal seleccionada en la colección o darnos de baja de una señal a la que ya nos hemos suscrito en la cuenta actual.

Además de trabajar con los objetos del servicio Señales de MQL5.com, modificaremos la clase del objeto de instantánea de profundidad de mercado, añadiéndole propiedades adicionales que nos permitirán calcular por separado los volúmenes de las órdenes de compra y de venta al crear el objeto de instantánea de profundidad de mercado. Esto nos evitará realizar cálculos adicionales al trabajar con la profundidad de mercado, pues conoceremos de inmediato los volúmenes totales de cada instantánea de la profundidad de mercado, tanto de compra como de venta, lo cual nos permitirá no recurrir a la búsqueda adicional de órdenes Buy y Sell en la profundidad de mercado, con la posterior suma de sus volúmenes al crear estrategias usando la profundidad de mercado y sus volúmenes.


Mejorando las clases de la biblioteca

Como siempre, primero añadiremos todos los mensajes nuevos de la biblioteca al archivo \MQL5\Include\DoEasy\Data.mqh.
En primer lugar, añadimos los índices de los nuevos mensajes:

//--- CMarketBookSnapshot
   MSG_MBOOK_SNAP_TEXT_SNAPSHOT,                      // DOM snapshot
   MSG_MBOOK_SNAP_VOLUME_BUY,                         // Buy volume
   MSG_MBOOK_SNAP_VOLUME_SELL,                        // Sell volume
   
//--- CMBookSeries
   MSG_MBOOK_SERIES_TEXT_MBOOKSERIES,                 // DOM snapshot series
   MSG_MBOOK_SERIES_ERR_ADD_TO_LIST,                  // Error. Failed to add DOM snapshot series to the list

...

//--- CMQLSignal
   MSG_SIGNAL_MQL5_TEXT_SIGNAL,                       // Signal
   MSG_SIGNAL_MQL5_TEXT_SIGNAL_MQL5,                  // MQL5.com Signals service signal
   MSG_SIGNAL_MQL5_TRADE_MODE,                        // Account type
   MSG_SIGNAL_MQL5_DATE_PUBLISHED,                    // Publication date
   MSG_SIGNAL_MQL5_DATE_STARTED,                      // Monitoring start date
   MSG_SIGNAL_MQL5_DATE_UPDATED,                      // Date of the latest update of the trading statistics 
   MSG_SIGNAL_MQL5_ID,                                // ID
   MSG_SIGNAL_MQL5_LEVERAGE,                          // Trading account leverage
   MSG_SIGNAL_MQL5_PIPS,                              // Trading result in pips
   MSG_SIGNAL_MQL5_RATING,                            // Position in the signal rating
   MSG_SIGNAL_MQL5_SUBSCRIBERS,                       // Number of subscribers
   MSG_SIGNAL_MQL5_TRADES,                            // Number of trades
   MSG_SIGNAL_MQL5_SUBSCRIPTION_STATUS,               // Status of account subscription to a signal
   MSG_SIGNAL_MQL5_EQUITY,                            // Account equity
   MSG_SIGNAL_MQL5_GAIN,                              // Account growth in %
   MSG_SIGNAL_MQL5_MAX_DRAWDOWN,                      // Maximum drawdown
   MSG_SIGNAL_MQL5_PRICE,                             // Signal subscription price
   MSG_SIGNAL_MQL5_ROI,                               // Signal ROI (Return on Investment) in %
   MSG_SIGNAL_MQL5_AUTHOR_LOGIN,                      // Author login
   MSG_SIGNAL_MQL5_BROKER,                            // Broker (company) name
   MSG_SIGNAL_MQL5_BROKER_SERVER,                     // Broker server
   MSG_SIGNAL_MQL5_NAME,                              // Name
   MSG_SIGNAL_MQL5_CURRENCY,                          // Account currency
   MSG_SIGNAL_MQL5_TEXT_GAIN,                         // Growth
   MSG_SIGNAL_MQL5_TEXT_DRAWDOWN,                     // Drawdown
   MSG_SIGNAL_MQL5_TEXT_SUBSCRIBERS,                  // Subscribers
   
//--- CMQLSignalsCollection
   MSG_MQLSIG_COLLECTION_TEXT_MQL5_SIGNAL_COLLECTION, // Collection of MQL5.com Signals service signals
   MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_PAID,           // Paid signals
   MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_FREE,           // Free signals
   MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_NEW,            // New signal added to collection
   MSG_MQLSIG_COLLECTION_ERR_FAILED_GET_SIGNAL,       // Failed to receive signal from collection
   MSG_SIGNAL_INFO_PARAMETERS,                        // Signal copying parameters
   MSG_SIGNAL_INFO_EQUITY_LIMIT,                      // Percentage for converting deal volume
   MSG_SIGNAL_INFO_SLIPPAGE,                          // Market order slippage when synchronizing positions and copying deals
   MSG_SIGNAL_INFO_VOLUME_PERCENT,                    // Limitation on signal equity
   MSG_SIGNAL_INFO_CONFIRMATIONS_DISABLED,            // Enable synchronization without confirmation dialog
   MSG_SIGNAL_INFO_COPY_SLTP,                         // Copy Stop Loss and Take Profit
   MSG_SIGNAL_INFO_DEPOSIT_PERCENT,                   // Limit by deposit
   MSG_SIGNAL_INFO_ID,                                // Signal ID
   MSG_SIGNAL_INFO_SUBSCRIPTION_ENABLED,              // Enable copying deals by subscription
   MSG_SIGNAL_INFO_TERMS_AGREE,                       // Agree to the terms of use of the Signals service
   MSG_SIGNAL_INFO_NAME,                              // Signal name
   MSG_SIGNAL_INFO_SIGNALS_PERMISSION,                // Allow using signals for program
   MSG_SIGNAL_INFO_TEXT_SIGNAL_SUBSCRIBED,            // Subscribed to signal
   MSG_SIGNAL_INFO_TEXT_SIGNAL_UNSUBSCRIBED,          // Unsubscribed from signal
   MSG_SIGNAL_INFO_ERR_SIGNAL_NOT_ALLOWED,            // Signal service disabled for program
   MSG_SIGNAL_INFO_TEXT_CHECK_SETTINGS,               // Please check program settings (Common-->Allow modification of Signals settings)
   
  };
//+------------------------------------------------------------------+

Después, añadimos los textos de los mensajes que se corresponden con los índices recién añadidos:

//--- CMarketBookSnapshot
   {"Снимок стакана цен","Depth of Market Snapshot"},
   {"Объём на покупку","Buy Volume"},
   {"Объём на продажу","Sell Volume"},
   
//--- CMBookSeries
   {"Серия снимков стакана цен","Series of shots of the Depth of Market"},
   {"Ошибка. Не удалось добавить серию снимков стакана цен в список","Error. Failed to add a shots series of the Depth of Market to the list"},
   
//--- CMBookSeriesCollection
   {"Коллекция серий снимков стакана цен","Collection of series of the Depth of Market shot"},   
   
//--- CMQLSignal
   {"Сигнал","Signal"},
   {"Сигнал сервиса сигналов mql5.com","Signal from mql5.com signal service"},
   {"Тип счета","Account type"},
   {"Дата публикации","Publication date"},
   {"Дата начала мониторинга","Monitoring starting date"},
   {"Дата последнего обновления торговой статистики","The date of the last update of the signal's trading statistics"},
   {"ID","ID"},
   {"Плечо торгового счета","Account leverage"},
   {"Результат торговли в пипсах","Profit in pips"},
   {"Позиция в рейтинге сигналов","Position in rating"},
   {"Количество подписчиков","Number of subscribers"},
   {"Количество трейдов","Number of trades"},
   {"Состояние подписки счёта на этот сигнал","Account subscription status for this signal"},
   {"Средства на счете","Account equity"},
   {"Прирост счета в процентах","Account gain"},
   {"Максимальная просадка","Account maximum drawdown"},
   {"Цена подписки на сигнал","Signal subscription price"},
   {"Значение ROI (Return on Investment) сигнала в %","Return on Investment (%)"},
   {"Логин автора","Author login"},
   {"Наименование брокера (компании)","Broker name (company)"},
   {"Сервер брокера","Broker server"},
   {"Имя","Name"},
   {"Валюта счета","Base currency"},
   {"Прирост","Gain"},
   {"Просадка","Drawdown"},
   {"Подписчиков","Subscribers"},
   
//--- CMQLSignalsCollection
   {"Коллекция сигналов сервиса сигналов mql5.com","Collection of signals from the mql5.com signal service"},
   {"Платных сигналов","Paid signals"},
   {"Бесплатных сигналов","Free signals"},
   {"Новый сигнал добавлен в коллекцию","New signal added to collection"},
   {"Не удалось получить сигнал из коллекции","Failed to get signal from collection"},
   {"Параметры копирования сигнала","Signal copying parameters"},
   {"Процент для конвертации объема сделки","Equity limit"},
   {"Проскальзывание, с которым выставляются рыночные ордера при синхронизации позиций и копировании сделок","Slippage (used when placing market orders in synchronization of positions and copying of trades)"},
   {"Ограничение по средствам для сигнала","Maximum percent of deposit used"},
   {"Разрешение синхронизации без показа диалога подтверждения","Allow synchronization without confirmation dialog"},
   {"Копирование Stop Loss и Take Profit","Copy Stop Loss and Take Profit"},
   {"Ограничение по депозиту","Deposit percent"},
   {"Идентификатор сигнала","Signal ID"},
   {"Разрешение на копирование сделок по подписке","Permission to copy trades by subscription"},
   {"Согласие с условиями использования сервиса \"Сигналы\"","Agree to the terms of use of the \"Signals\" service"},
   {"Имя сигнала","Signal name"},
   {"Разрешение на работу с сигналами для программы","Permission to work with signals for the program"},
   {"Осуществлена подписка на сигнал","Signal subscribed"},
   {"Осуществлена отписка от сигнала","Signal unsubscribed"},
   {"Работа с сервисом сигналов для программы не разрешена","Work with the \"Signals\" service is not allowed for the program"},
   {
    "Пожалуйста, проверьте настройки программы (Общие --> Разрешить изменение настроек Сигналов)",
    "Please check the program settings (Common --> Allow modification of Signals settings)"
   },
   
  };
//+---------------------------------------------------------------------+

Con frecuencia, al enviar mensajes al diario, especialmente los mensajes de depuración, al principio del mensaje indicamos el nombre del método desde el que se ha enviado este mensaje. En el artículo 19, ya creamos la clase de mensaje de biblioteca. Pero hasta ahora no lo hemos utilizado de forma muy activa, solo indicamos los índices de los mensajes que deben imprimirse en el diario usando la función estándar Print(). Como pronto iniciaremos una nueva sección de la biblioteca para trabajar con gráficos, vamos a comenzar a operar paulatinamente con esta clase para mostrar los mensajes de la biblioteca. En esta ocasión, le añadiremos una sobrecarga del método ToLog(), de forma que podamos transmitir adicionalmente al método el mensaje "fuente", decir, el método de clase o función de programa desde el que se ha llamado a este método. Así, dispondremos de dos variantes del método ToLog(), lo cual nos permitirá mostrar mensajes con y sin indicación sobre su función o método original.

Abrimos el archivo \MQL5\Include\DoEasy\Services\Message.mqh y le añadimos la declaración del método sobrecargado:

//--- (1,2) display a message in the journal by ID, (3) to e-mail, (4) to a mobile device
   static void       ToLog(const int msg_id,const bool code=false);
   static void       ToLog(const string source,const int msg_id,const bool code=false);
   static bool       ToMail(const string message,const string subject=NULL);
   static bool       Push(const string message);
//--- (1) send a file to FTP, (2) return an error code
   static bool       ToFTP(const string filename,const string ftp_path=NULL);
   static int        GetError(void)                { return CMessage::m_global_error;  }

Lo implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Display a message in the journal by a message ID                 |
//+------------------------------------------------------------------+
void CMessage::ToLog(const int msg_id,const bool code=false)
  {
   CMessage::GetTextByID(msg_id);
   ::Print(m_text,(!code || msg_id>ERR_USER_ERROR_FIRST-1 ? "" : " "+CMessage::Retcode(msg_id)));
  }
//+------------------------------------------------------------------+
//| Display a message in the journal by a message ID                 |
//+------------------------------------------------------------------+
void CMessage::ToLog(const string source,const int msg_id,const bool code=false)
  {
   CMessage::GetTextByID(msg_id);
   ::Print(source,m_text,(!code || msg_id>ERR_USER_ERROR_FIRST-1 ? "" : " "+CMessage::Retcode(msg_id)));
  }
//+------------------------------------------------------------------+

Como podemos ver, a diferencia de la primera forma de llamada del método, en su segunda forma, añadimos un parámetro de entrada más, en el que se transmitirá el nombre del método (o función) desde el cuál se ha llamado al método ToLog(), y que será registrado antes del mensaje.

Volveremos a esta clase en artículos posteriores para realizar mejoras al transmitir todas las clases de la biblioteca para mostrar mensajes con la ayuda de esta clase.

Vamos a mejorar la clase CMBookSnapshot en el archivo \MQL5\Include\DoEasy\Objects\Book\MarketBookSnapshot.mqh.

En la sección privada de la clase, añadimos las variables de miembro de clase para guardar los volúmenes acumulados de la instantánea de profundidad de mercado de compra y venta:

//+------------------------------------------------------------------+
//| "DOM snapshot" class                                             |
//+------------------------------------------------------------------+
class CMBookSnapshot : public CBaseObj
  {
private:
   string            m_symbol;                  // Symbol
   long              m_time;                    // Snapshot time
   int               m_digits;                  // Symbol's Digits
   long              m_volume_buy;              // DOM buy volume
   long              m_volume_sell;             // DOM sell volume
   double            m_volume_buy_real;         // DOM buy volume with an increased accuracy
   double            m_volume_sell_real;        // DOM sell volume with an increased accuracy
   CArrayObj         m_list;                    // List of DOM order objects
public:

En la sección con los métodos para el acceso simplificado a las propiedades de la instantánea del objeto de profundidad de mercado de la sección pública de la clase, añadimos los métodos que retornan estas propiedades de clase recién añadidas, así como los métodos para mostrar su descripción:

//+----------------------------------------------------------------------+ 
//|Methods of a simplified access to the DOM snapshot object properties  |
//+----------------------------------------------------------------------+
//--- Set (1) a symbol, (2) a DOM snapshot time and (3) the specified time for all DOM orders
   void              SetSymbol(const string symbol)   { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); }
   void              SetTime(const long time_msc)     { this.m_time=time_msc; }
   void              SetTimeToOrders(const long time_msc);
//--- Return (1) a DOM symbol, (2) symbol's Digits and (3) a snapshot time
//--- (4) buy and (5) sell DOM snapshot volume
//--- with increased accuracy for (6) buying and (7) selling,
   string            Symbol(void)               const { return this.m_symbol;             }
   int               Digits(void)               const { return this.m_digits;             }
   long              Time(void)                 const { return this.m_time;               }
   long              VolumeBuy(void)            const { return this.m_volume_buy;         }
   long              VolumeSell(void)           const { return this.m_volume_sell;        }
   double            VolumeBuyReal(void)        const { return this.m_volume_buy_real;    }
   double            VolumeSellReal(void)       const { return this.m_volume_sell_real;   }
   
//--- Return the description of DOM (1) buy and (2) sell volume
   string            VolumeBuyDescription(void);
   string            VolumeSellDescription(void);
   
  };
//+------------------------------------------------------------------+

Al crear un nuevo objeto de instantánea de profundidad de mercado, examinaremos en un ciclo todas sus órdenes, crearemos los objetos de estas órdenes y las añadiremos a la lista. Ahora, debemos considerar los tipos de órdenes en el constructor de clases y sumar inmediatamente al valor de las variables que guardan el volumen total de órdenes de compra y venta, el volumen de la orden actual, dependiendo de su tipo. Por consiguiente, cada variable guardará en última instancia el volumen total de las órdenes de compra o venta inmediatamente después de crear el objeto de instantánea de profundidad de mercado.

Vamos a añadir estas mejoras al constructor paramétrico de la clase:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CMBookSnapshot::CMBookSnapshot(const string symbol,const long time,MqlBookInfo &book_array[]) : m_time(time)
  {
   //--- Set a symbol
   this.SetSymbol(symbol);
   //--- Clear the list
   this.m_list.Clear();
   //--- In the loop by the structure array
   int total=::ArraySize(book_array);
   this.m_volume_buy=this.m_volume_sell=0;
   this.m_volume_buy_real=this.m_volume_sell_real=0;
   for(int i=0;i<total;i++)
     {
      //--- Create order objects of the current DOM snapshot depending on the order type
      CMarketBookOrd *mbook_ord=NULL;
      switch(book_array[i].type)
        {
         case BOOK_TYPE_BUY         : mbook_ord=new CMarketBookBuy(this.m_symbol,book_array[i]);         break;
         case BOOK_TYPE_SELL        : mbook_ord=new CMarketBookSell(this.m_symbol,book_array[i]);        break;
         case BOOK_TYPE_BUY_MARKET  : mbook_ord=new CMarketBookBuyMarket(this.m_symbol,book_array[i]);   break;
         case BOOK_TYPE_SELL_MARKET : mbook_ord=new CMarketBookSellMarket(this.m_symbol,book_array[i]);  break;
         default: break;
        }
      if(mbook_ord==NULL)
         continue;
      //--- Set the DOM snapshot time for the order
      mbook_ord.SetTime(this.m_time);

      //--- Set the sorted list flag for the list (by the price value) and add the current order object to it
      //--- If failed to add the object to the DOM order list, remove the order object
      this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE);
      if(!this.m_list.InsertSort(mbook_ord))
         delete mbook_ord;
      //--- If the order object is successfully added to the DOM order list, supplement the total snapshot volumes
      else
        {
         switch(mbook_ord.TypeOrd())
           {
            case BOOK_TYPE_BUY         : 
              this.m_volume_buy+=mbook_ord.Volume(); 
              this.m_volume_buy_real+=mbook_ord.VolumeReal();
              break;
            case BOOK_TYPE_SELL        : 
              this.m_volume_sell+=mbook_ord.Volume(); 
              this.m_volume_sell_real+=mbook_ord.VolumeReal();
              break;
            case BOOK_TYPE_BUY_MARKET  : 
              this.m_volume_buy+=mbook_ord.Volume(); 
              this.m_volume_buy_real+=mbook_ord.VolumeReal();
              break;
            case BOOK_TYPE_SELL_MARKET : 
              this.m_volume_buy+=mbook_ord.Volume(); 
              this.m_volume_buy_real+=mbook_ord.VolumeReal();
              break;
            default: break;
           }
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, en primer lugar, inicializamos las variables que almacenan los volúmenes totales de todas las órdenes de la profundidad de mercado de compra y venta. A continuación, en el cuerpo del ciclo para todas las órdenes de la profundidad de mercado, dependiendo del tipo de orden, añadimos el volumen de la orden actual a las variables que almacenan los volúmenes totales, y que corresponden al tipo de orden. Por consiguiente, al final del ciclo, para todas las órdenes de la instantánea de profundidad de mercado, se guardarán en todas las variables los volúmenes acumulados de compra y venta.

En los métodos encargados de generar en el diario una descripción breve del objeto, así como todas las propiedades del mismo, añadimos la muestra de los volúmenes totales de compra y venta:

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CMBookSnapshot::PrintShort(void)
  {
   string vol_buy="Buy vol: "+(this.VolumeBuyReal()>0 ? ::DoubleToString(this.VolumeBuyReal(),2) : (string)this.VolumeBuy());
   string vol_sell="Sell vol: "+(this.VolumeSellReal()>0 ? ::DoubleToString(this.VolumeSellReal(),2) : (string)this.VolumeSell());   
   ::Print(this.Header()," ",vol_buy,", ",vol_sell," ("+TimeMSCtoString(this.m_time),")");
  }
//+------------------------------------------------------------------+
//| Display object properties in the journal                         |
//+------------------------------------------------------------------+
void CMBookSnapshot::Print(void)
  {
   string vol_buy=CMessage::Text(MSG_MBOOK_SNAP_VOLUME_BUY)+": "+(this.VolumeBuyReal()>0 ? ::DoubleToString(this.VolumeBuyReal(),2) : (string)this.VolumeBuy());
   string vol_sell=CMessage::Text(MSG_MBOOK_SNAP_VOLUME_SELL)+": "+(this.VolumeSellReal()>0 ? ::DoubleToString(this.VolumeSellReal(),2) : (string)this.VolumeSell());
   ::Print(this.Header(),": ",vol_buy,", ",vol_sell," ("+TimeMSCtoString(this.m_time),"):");
   this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE);
   for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--)
     {
      CMarketBookOrd *ord=this.m_list.At(i);
      if(ord==NULL)
         continue;
      ::Print("- ",ord.Header());
     }
  }
//+------------------------------------------------------------------+

Fuera del cuerpo de la clase, escribiremos la implementación de dos nuevos métodos que retornarán las descripciones de los volúmenes de la profundidad de mercado de compra y venta:

//+------------------------------------------------------------------+
//| Return the DOM buy volume description                            |
//+------------------------------------------------------------------+
string CMBookSnapshot::VolumeBuyDescription(void)
  {
   return(CMessage::Text(MSG_MBOOK_SNAP_VOLUME_BUY)+": "+(this.VolumeBuyReal()>0 ? ::DoubleToString(this.VolumeBuyReal(),2) : (string)this.VolumeBuy()));
  }
//+------------------------------------------------------------------+
//| Return the DOM sell volume description                           |
//+------------------------------------------------------------------+
string CMBookSnapshot::VolumeSellDescription(void)
  {
   return(CMessage::Text(MSG_MBOOK_SNAP_VOLUME_SELL)+": "+(this.VolumeSellReal()>0 ? ::DoubleToString(this.VolumeSellReal(),2) : (string)this.VolumeSell()));
  }
//+------------------------------------------------------------------+

En ambos métodos, se verificará con mayor precisión el valor del volumen, y si este es mayor que cero, se retornará el encabezado + este valor de volumen (de tipo real); de lo contrario, se retornará un valor entero.

En la clase de colección de series de instantáneas de las profundidades de mercado CMBookSeriesCollection, en la sección pública del archivo \MQL5\Include\DoEasy\Collections\BookSeriesCollection.mqh,
añadimos los métodos que retornan las listas según los criterios indicados de las propiedades de los objetos de la lista:

public:
//--- Return (1) itself and (2) the DOM series collection list
   CMBookSeriesCollection *GetObject(void)                              { return &this;               }
   CArrayObj              *GetList(void)                                { return &this.m_list;        }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj              *GetList(ENUM_MBOOK_ORD_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByMBookProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_MBOOK_ORD_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByMBookProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_MBOOK_ORD_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByMBookProperty(this.GetList(),property,value,mode);  }
//--- Return the number of DOM series in the list
   int                     DataTotal(void)                        const { return this.m_list.Total();    }
//--- Return the pointer to the DOM series object (1) by symbol and (2) by index in the list

En la clase del objeto de orden de profundidad de mercado CMQLSignal, en el archivo \MQL5\Include\DoEasy\Objects\MQLSignalBase\MQLSignal.mqh, completamos el método PrintShort() con un valor de entrada en forma de bandera que indica la necesidad de mostrar un guión antes de la descripción del objeto:

//--- Display the description of object properties in the journal (full_prop=true - all properties, false - supported ones only)
   void              Print(const bool full_prop=false);
//--- Display a short description of the object in the journal
   virtual void      PrintShort(const bool dash=false);
//--- Return the object short name
   virtual string    Header(const bool shrt=false);

E introducimos las correcciones en el cuerpo de la clase:

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CMQLSignal::PrintShort(const bool dash=false)
  {
   ::Print
     (
      (dash ? "- " : ""),this.Header(true),
      " \"",this.Name(),"\". ",
      CMessage::Text(MSG_SIGNAL_MQL5_AUTHOR_LOGIN),": ",this.AuthorLogin(),
      ", ID ",this.ID(),
      ", ",CMessage::Text(MSG_SIGNAL_MQL5_TEXT_GAIN),": ",::DoubleToString(this.Gain(),2),
      ", ",CMessage::Text(MSG_SIGNAL_MQL5_TEXT_DRAWDOWN),": ",::DoubleToString(this.MaxDrawdown(),2),
      ", ",CMessage::Text(MSG_LIB_TEXT_REQUEST_PRICE),": ",::DoubleToString(this.Price(),2),
      ", ",CMessage::Text(MSG_SIGNAL_MQL5_TEXT_SUBSCRIBERS),": ",this.Subscribers()
     );
  }
//+------------------------------------------------------------------+

Aquí, dependiendo del valor transmitido, se mostrará un guión delante de la descripción del objeto, o bien este no se mostrará, y al final de la descripción se añadirá un valor sobre el número de suscriptores a la señal.

Al final del cuerpo de la clase, escribiremos un nuevo método que realiza la suscripción a la señal descrita por este objeto:

//--- Return the account type name
   string            TradeModeDescription(void);
   
//--- Subscribe to a signal
   bool              Subscribe(void)                        { return ::SignalSubscribe(this.ID()); }
   
  };
//+------------------------------------------------------------------+

En el constructor de la clase, corregimos la inicialización de la variable que almacena el estado de la suscripción a la señal:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CMQLSignal::CMQLSignal(const long signal_id)
  {
   this.m_long_prop[SIGNAL_MQL5_PROP_ID]                             = signal_id;
   this.m_long_prop[SIGNAL_MQL5_PROP_SUBSCRIPTION_STATUS]            = (::SignalInfoGetInteger(SIGNAL_INFO_ID)==signal_id);
   this.m_long_prop[SIGNAL_MQL5_PROP_TRADE_MODE]                     = ::SignalBaseGetInteger(SIGNAL_BASE_TRADE_MODE);
   this.m_long_prop[SIGNAL_MQL5_PROP_DATE_PUBLISHED]                 = ::SignalBaseGetInteger(SIGNAL_BASE_DATE_PUBLISHED);
   this.m_long_prop[SIGNAL_MQL5_PROP_DATE_STARTED]                   = ::SignalBaseGetInteger(SIGNAL_BASE_DATE_STARTED);
   this.m_long_prop[SIGNAL_MQL5_PROP_DATE_UPDATED]                   = ::SignalBaseGetInteger(SIGNAL_BASE_DATE_UPDATED);
   this.m_long_prop[SIGNAL_MQL5_PROP_LEVERAGE]                       = ::SignalBaseGetInteger(SIGNAL_BASE_LEVERAGE);
   this.m_long_prop[SIGNAL_MQL5_PROP_PIPS]                           = ::SignalBaseGetInteger(SIGNAL_BASE_PIPS);
   this.m_long_prop[SIGNAL_MQL5_PROP_RATING]                         = ::SignalBaseGetInteger(SIGNAL_BASE_RATING);
   this.m_long_prop[SIGNAL_MQL5_PROP_SUBSCRIBERS]                    = ::SignalBaseGetInteger(SIGNAL_BASE_SUBSCRIBERS);
   this.m_long_prop[SIGNAL_MQL5_PROP_TRADES]                         = ::SignalBaseGetInteger(SIGNAL_BASE_TRADES);
   
   this.m_double_prop[this.IndexProp(SIGNAL_MQL5_PROP_BALANCE)]      = ::SignalBaseGetDouble(SIGNAL_BASE_BALANCE);
   this.m_double_prop[this.IndexProp(SIGNAL_MQL5_PROP_EQUITY)]       = ::SignalBaseGetDouble(SIGNAL_BASE_EQUITY);
   this.m_double_prop[this.IndexProp(SIGNAL_MQL5_PROP_GAIN)]         = ::SignalBaseGetDouble(SIGNAL_BASE_GAIN);
   this.m_double_prop[this.IndexProp(SIGNAL_MQL5_PROP_MAX_DRAWDOWN)] = ::SignalBaseGetDouble(SIGNAL_BASE_MAX_DRAWDOWN);
   this.m_double_prop[this.IndexProp(SIGNAL_MQL5_PROP_PRICE)]        = ::SignalBaseGetDouble(SIGNAL_BASE_PRICE);
   this.m_double_prop[this.IndexProp(SIGNAL_MQL5_PROP_ROI)]          = ::SignalBaseGetDouble(SIGNAL_BASE_ROI);
   
   this.m_string_prop[this.IndexProp(SIGNAL_MQL5_PROP_AUTHOR_LOGIN)] = ::SignalBaseGetString(SIGNAL_BASE_AUTHOR_LOGIN);
   this.m_string_prop[this.IndexProp(SIGNAL_MQL5_PROP_BROKER)]       = ::SignalBaseGetString(SIGNAL_BASE_BROKER);
   this.m_string_prop[this.IndexProp(SIGNAL_MQL5_PROP_BROKER_SERVER)]= ::SignalBaseGetString(SIGNAL_BASE_BROKER_SERVER);
   this.m_string_prop[this.IndexProp(SIGNAL_MQL5_PROP_NAME)]         = ::SignalBaseGetString(SIGNAL_BASE_NAME);
   this.m_string_prop[this.IndexProp(SIGNAL_MQL5_PROP_CURRENCY)]     = ::SignalBaseGetString(SIGNAL_BASE_CURRENCY);
  }
//+------------------------------------------------------------------+

Anteriormente, esta se inicializaba con el valor false. Ahora, la inicializaremos con el resultado de la compración entre el identificador del objeto señal y el identificador de la señal suscrita actual. Si existe una suscripción a cualquier señal, esta señal estará "actualmente suscrita" y tendrá un identificador de señal perteneciente a la base de datos de Señales de MQL5.com que usamos para la comparación. Si son iguales, significará que existe una suscripción a esta señal; el resultado de la comparación será true, de lo contrario, false.

Como hoy estamos creando una nueva colección, necesitaremos definir nuestro propio identificador para ella. En el archivo \MQL5\Include\DoEasy\Defines.mqh añadimos el indentificador de la colección de señales del servicio Señales de MQL5.com:

//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Tick series collection list ID
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // DOM series collection list ID
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // MQL5 signals collection list ID
//--- Data parameters for file operations

Para trabajar plenamente con la colección de señales del servicio Señales de MQL5.com, necesitaremos crear los métodos necesarios para buscar y clasificar los objetos de señal según las propiedades. Para cada colección, crearemos nuestros propios métodos de búsqueda y clasificación. Todos resultan idénticos entre sí y ya los hemos descrito con detalle en el tercer artículo de descripción de la biblioteca.

En el archivo de la clase CSelect (encargado de realizar la búsqueda y clasificación), en \MQL5\Include\DoEasy\Services\Select.mqh,
incluimos el archivo de la clase de objeto de señal mql5 y declaramos los nuevos métodos para trabajar con la colección de objetos de señal:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
#include "..\Objects\Indicators\IndicatorDE.mqh"
#include "..\Objects\Indicators\DataInd.mqh"
#include "..\Objects\Ticks\DataTick.mqh"
#include "..\Objects\Book\MarketBookOrd.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Methods of working with MQL5 signal data                         |
//+------------------------------------------------------------------+
   //--- Return the list of MQL5 signals with one of (1) integer, (2) real and (3) string properties meeting a specified criterion
   static CArrayObj *ByMQLSignalProperty(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByMQLSignalProperty(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByMQLSignalProperty(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Return the MQL5 signal index in the list with the maximum value of the (1) integer, (2) real and (3) string properties
   static int        FindMQLSignalMax(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_INTEGER property);
   static int        FindMQLSignalMax(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_DOUBLE property);
   static int        FindMQLSignalMax(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_STRING property);
   //--- Return the MQL5 signal in the list with the minimum value of (1) integer, (2) real and (3) string property
   static int        FindMQLSignalMin(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_INTEGER property);
   static int        FindMQLSignalMin(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_DOUBLE property);
   static int        FindMQLSignalMin(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

Los implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Methods of working with MQL5 signal data                         |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Return the list of MQL5 signals with one integer                 |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByMQLSignalProperty(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of MQL5 signals with one real                    |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByMQLSignalProperty(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CMQLSignal *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of MQL5 signals with one string                  |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByMQLSignalProperty(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CMQLSignal *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the MQL5 signal index in the list                         |
//| with the maximum integer property value                          |
//+------------------------------------------------------------------+
int CSelect::FindMQLSignalMax(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CMQLSignal *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      long obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the MQL5 signal index in the list                         |
//| with the maximum real property value                             |
//+------------------------------------------------------------------+
int CSelect::FindMQLSignalMax(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CMQLSignal *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      double obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the MQL5 signal index in the list                         |
//| with the maximum string property value                           |
//+------------------------------------------------------------------+
int CSelect::FindMQLSignalMax(CArrayObj *list_source,ENUM_SIGNAL_MQL5_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CMQLSignal *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      string obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the MQL5 signal index in the list                         |
//| with the minimum integer property value                          |
//+------------------------------------------------------------------+
int CSelect::FindMQLSignalMin(CArrayObj* list_source,ENUM_SIGNAL_MQL5_PROP_INTEGER property)
  {
   int index=0;
   CMQLSignal *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      long obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the MQL5 signal index in the list                         |
//| with the minimum real property value                             |
//+------------------------------------------------------------------+
int CSelect::FindMQLSignalMin(CArrayObj* list_source,ENUM_SIGNAL_MQL5_PROP_DOUBLE property)
  {
   int index=0;
   CMQLSignal *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      double obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the MQL5 signal index in the list                         |
//| with the minimum string property value                           |
//+------------------------------------------------------------------+
int CSelect::FindMQLSignalMin(CArrayObj* list_source,ENUM_SIGNAL_MQL5_PROP_STRING property)
  {
   int index=0;
   CMQLSignal *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMQLSignal *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      string obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

Como hemos mencionado más de una vez, ya hemos analizado todos estos métodos, que son idénticos para cada una de las clases de objetos de la biblioteca, por lo que el lector podrá familiarizarse con las explicaciones detalladas sobre su funcionamiento en el artículo №3.

Ya estamos listos para crear la clase de colección de objetos de señal del servicio Señales de MQL5.com.

Clase de colección de objetos de señal mql5

En el directorio de la biblioteca \MQL5\Include\DoEasy\Collections\, creamos la nueva clase CMQLSignalsCollection en el archivo MQLSignalsCollection.mqh.

En el archivo de la clase, incluimos todos los archivos de clase necesarios para su funcionamiento:

//+------------------------------------------------------------------+
//|                                         MQLSignalsCollection.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
//+------------------------------------------------------------------+

La clase debe heredarse del objeto básico de todos los objetos de la biblioteca:

//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CMQLSignalsCollection : public CBaseObj
  {
  }

Veamos primero el cuerpo completo de la clase; luego analizaremos los métodos que la componen:

//+------------------------------------------------------------------+
//|                                         MQLSignalsCollection.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CMQLSignalsCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // List of MQL5 signal objects
   int                     m_signals_base_total;                     // Number of signals in the MQL5 signal database
//--- Subscribe to a signal
   bool                    Subscribe(const long signal_id);
//--- Set the flag allowing synchronization without confirmation dialog
   bool                    CurrentSetConfirmationsDisableFlag(const bool flag);
//--- Set the flag of copying Stop Loss and Take Profit
   bool                    CurrentSetSLTPCopyFlag(const bool flag);
//--- Set the flag allowing the copying of signals by subscription
   bool                    CurrentSetSubscriptionEnabledFlag(const bool flag);
public:
//--- Return (1) itself and (2) the DOM series collection list
   CMQLSignalsCollection  *GetObject(void)                              { return &this;                  }
   CArrayObj              *GetList(void)                                { return &this.m_list;           }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj              *GetList(ENUM_SIGNAL_MQL5_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByMQLSignalProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_SIGNAL_MQL5_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByMQLSignalProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_SIGNAL_MQL5_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByMQLSignalProperty(this.GetList(),property,value,mode);  }
//--- Return the number of MQL5 signal objects in the list
   int                     DataTotal(void)                        const { return this.m_list.Total();    }
//--- Return the pointer to the MQL5 signal object (1) by ID, (2) by name and (3) by index in the list
   CMQLSignal             *GetMQLSignal(const long id);
   CMQLSignal             *GetMQLSignal(const string name);
   CMQLSignal             *GetMQLSignal(const int index)                { return this.m_list.At(index);  }

//--- Create the collection list of MQL5 signal objects
   bool                    CreateCollection(void);
//--- Update the collection list of MQL5 signal objects
   void                    Refresh(const bool messages=true);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(void);
   void                    PrintShort(const bool list=false,const bool paid=true,const bool free=true);
   
//--- Constructor
                           CMQLSignalsCollection();
//--- Subscribe to a signal by (1) ID and (2) signal name
   bool                    SubscribeByID(const long signal_id);
   bool                    SubscribeByName(const string signal_name);
   
//+----------------------------------------------------------------------------+
//| Methods of working with the current signal the subscription is active for  |
//+----------------------------------------------------------------------------+
//--- Return the flag allowing working with the signal service
   bool                    ProgramIsAllowed(void)                 { return (bool)::MQLInfoInteger(MQL_SIGNALS_ALLOWED); }
//--- Unsubscribe from the current signal
   bool                    CurrentUnsubscribe(void);

//--- Set the percentage for converting deal volume
   bool                    CurrentSetEquityLimit(const double value);
//--- Set the market order slippage used when synchronizing positions and copying deals
   bool                    CurrentSetSlippage(const double value);
//--- Set deposit limitations (in %)
   bool                    CurrentSetDepositPercent(const int value);

//--- Return the percentage for converting deal volume
   double                  CurrentEquityLimit(void)               { return ::SignalInfoGetDouble(SIGNAL_INFO_EQUITY_LIMIT);                  }
//--- Return the market order slippage used when synchronizing positions and copying deals
   double                  CurrentSlippage(void)                  { return ::SignalInfoGetDouble(SIGNAL_INFO_SLIPPAGE);                      }
//--- Return the flag allowing synchronization without confirmation dialog 
   bool                    CurrentConfirmationsDisableFlag(void)  { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_CONFIRMATIONS_DISABLED); }
//--- Return the flag of copying Stop Loss and Take Profit
   bool                    CurrentSLTPCopyFlag(void)              { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_COPY_SLTP);              }
//--- Return deposit limitations (in %)
   int                     CurrentDepositPercent(void)            { return (int)::SignalInfoGetInteger(SIGNAL_INFO_DEPOSIT_PERCENT);         }
//--- Return the flag allowing the copying of signals by subscription
   bool                    CurrentSubscriptionEnabledFlag(void)   { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_SUBSCRIPTION_ENABLED);   }
//--- Return the limitation by funds for a signal
   double                  CurrentVolumePercent(void)             { return ::SignalInfoGetDouble(SIGNAL_INFO_VOLUME_PERCENT);                }
//--- Return the signal ID
   long                    CurrentID(void)                        { return ::SignalInfoGetInteger(SIGNAL_INFO_ID);                           }
//--- Return the flag of agreeing to the terms of use of the Signals service
   bool                    CurrentTermsAgreeFlag(void)            { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_TERMS_AGREE);            }
//--- Return the signal name
   string                  CurrentName(void)                      { return ::SignalInfoGetString(SIGNAL_INFO_NAME);                          }

//--- Enable synchronization without the confirmation dialog
   bool                    CurrentSetConfirmationsDisableON(void) { return this.CurrentSetConfirmationsDisableFlag(true);                    }
//--- Disable synchronization without the confirmation dialog
   bool                    CurrentSetConfirmationsDisableOFF(void){ return this.CurrentSetConfirmationsDisableFlag(false);                   }
//--- Enable copying Stop Loss and Take Profit
   bool                    CurrentSetSLTPCopyON(void)             { return this.CurrentSetSLTPCopyFlag(true);                                }
//--- Disable copying Stop Loss and Take Profit
   bool                    CurrentSetSLTPCopyOFF(void)            { return this.CurrentSetSLTPCopyFlag(false);                               }
//--- Enable copying deals by subscription
   bool                    CurrentSetSubscriptionEnableON(void)   { return this.CurrentSetSubscriptionEnabledFlag(true);                     }
//--- Disable copying deals by subscription
   bool                    CurrentSetSubscriptionEnableOFF(void)  { return this.CurrentSetSubscriptionEnabledFlag(false);                    }

//--- Return the description of enabling working with signals for the launched program
   string                  ProgramIsAllowedDescription(void);
//--- Return the percentage description for converting the deal volume
   string                  CurrentEquityLimitDescription(void);
//--- Return the description of the market order slippage used when synchronizing positions and copying deals
   string                  CurrentSlippageDescription(void);
//--- Return the description of the limitation by funds for a signal
   string                  CurrentVolumePercentDescription(void);
//--- Return the description of the flag allowing synchronization without confirmation dialog
   string                  CurrentConfirmationsDisableFlagDescription(void);
//--- Return the description of the flag of copying Stop Loss and Take Profit
   string                  CurrentSLTPCopyFlagDescription(void);
//--- Return the description of the deposit limitations (in %)
   string                  CurrentDepositPercentDescription(void);
//--- Return the description of the flag allowing the copying of signals by subscription
   string                  CurrentSubscriptionEnabledFlagDescription(void);
//--- Return the description of the signal ID
   string                  CurrentIDDescription(void);
//--- Return the description of the flag of agreeing to the terms of use of the Signals service
   string                  CurrentTermsAgreeFlagDescription(void);
//--- Return the description of the signal name
   string                  CurrentNameDescription(void);
//--- Display the parameters of signal copying settings in the journal
   void                    CurrentSubscriptionParameters(void);
//---
  };
//+------------------------------------------------------------------+

La sección privada de la clase contiene el objeto de lista en el que guardaremos los objetos de señal mql5 y las variables y métodos auxiliares.

La sección pública de la clase contiene los métodos estándar para todos los objetos de la biblioteca necesarios para trabajar con la lista de colección de objetos, así como dos métodos para suscribirse a una señal seleccionada según su nombre e identificador. También en la sección pública de la clase, se ubican los métodos para trabajar con la señal suscrita actual.

Vamos a analizar la implementación de algunos métodos.

En el constructor de clase, limpiamos lista de colección, establecemos para ella la bandera de lista clasificada, asignamos a la lista el identificador de la colección de objetos de señal mql5, escribimos el número total de señales en la base de datos de Señales de MQL5.com y llamamos al método de creación de la colección.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMQLSignalsCollection::CMQLSignalsCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_MQL5_SIGNALS_ID);
   this.m_signals_base_total=::SignalBaseTotal();
   this.CreateCollection();
  }
//+------------------------------------------------------------------+

Dado que no actualizaremos y controlaremos automáticamente la lista de señales usando la biblioteca, en principio, nos bastará con crear solo un método para actualizar la lista, en el que se leerán todas las señales disponibles en la base de datos, registrando también estas en la lista de colección. El propio usuario deberá llamar al método de actualización Refresh() antes de obtener cualquier dato de la colección y, si así lo desea, conseguir una lista reciente con las señales de la base de datos de Señales de MQL5.com. No obstante, también dispondremos de un método para crear una colección, solo que como compatibilidad con el conjunto de métodos típicos de colección de biblioteca. En el método en sí, simplemente borraremos la lista y llamaremos al método de actualización de la colección. Después de realizar la primera llamada al método Refresh() desde el método de creación de la colección, rellenaremos la lista de colección, pudiendo así trabajar con esta lista posteriormente. Si necesitamos actualizar la lista de colección en busca de posibles nuevas señales, solo necesitamos llamar al método Refresh() antes de acceder a la lista de colecciones.

Método de creación de la colección:

//+------------------------------------------------------------------+
//| Create the collection list of MQL5 signal objects                |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CreateCollection(void)
  {
   this.m_list.Clear();
   this.Refresh(false);
   if(m_list.Total()>0)
     {
      ::Print(CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_MQL5_SIGNAL_COLLECTION)," ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED_OK));
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Aquí, limpiamos la lista de colección de señales, rellenamos la lista con las señales de la base de datos de Señales de MQL5.com y, si el número de señales en la lista de colección es superior a cero (es decir, la lista ha sido rellenada), mostramos un mensaje sobre la creación exitosa de la lista de colección y retornamos true.
De lo contrario, retornamos false.

Método de actualización de la lista de colección:

//+------------------------------------------------------------------+
//| Update the collection list of MQL5 signal objects                |
//+------------------------------------------------------------------+
void CMQLSignalsCollection::Refresh(const bool messages=true)
  {
   this.m_signals_base_total=::SignalBaseTotal();
   //--- loop through all signals in the signal database
   for(int i=0;i<this.m_signals_base_total;i++) 
     { 
      //--- Select a signal from the signal database by the loop index
      if(!::SignalBaseSelect(i))
         continue;
      //--- Get the current signal ID and
      //--- create a new MQL5 signal object based on it
      long id=::SignalBaseGetInteger(SIGNAL_BASE_ID);
      CMQLSignal *signal=new CMQLSignal(id);
      if(signal==NULL)
         continue;
      //--- Set the sorting flag for the list by signal ID and,
      //--- if such a signal is already present in the collection list,
      //--- remove the created object and go to the next loop iteration
      m_list.Sort(SORT_BY_SIGNAL_MQL5_ID);
      if(this.m_list.Search(signal)!=WRONG_VALUE)
        {
         delete signal;
         continue;
        }
      //--- If failed to add a new signal object to the collection list,
      //--- remove the created object and go to the next loop iteration
      if(!this.m_list.InsertSort(signal))
        {
         delete signal;
         continue;
        }
      //--- If an MQL5 signal object is successfully added to the collection
      //--- and the new object message flag is set in the parameters passed to the method,
      //--- display a message about a newly found signal
      else if(messages)
        {
         ::Print(DFUN,CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_NEW),":");
         signal.PrintShort(true);
        }
     } 
  }
//+------------------------------------------------------------------+

Describimos con detalle la lógica del método en los comentarios al código. En resumen: transmitimos al método una bandera para informar sobre una nueva señal encontrada. Como el método no limpia la lista de colección, solo podemos añadirle una señal recién encontrada. Si establecemos la bandera de mensaje, tras realizarse la adición exitosa de un nuevo objeto de señal a la lista, en el diario se mostrará un mensaje sobre la nueva señal encontrada.

Por el momento, este es el método más simple sin implementación de la actualización de los parámetros de las señales existentes: podremos actualizar estos de forma independiente en nuestros programas accediendo al objeto de señal según su identificador y estableciendo los nuevos valores de sus propiedades. Más tarde, añadiremos la actualización automática de los parámetros de las señales según el tiempo, y si la clase de colección de Señales de MQL5.com tiene demanda, crearemos para ella el envío de eventos sobre nuevas señales y sobre los cambios acaecidos en los parámetros de las señales monitoreadas.

Método que retorna el puntero a un objeto de señal mql5 según el identificador de la señal:

//+------------------------------------------------------------------+
//| Return the pointer to an MQL5 signal object by an ID             |
//+------------------------------------------------------------------+
CMQLSignal *CMQLSignalsCollection::GetMQLSignal(const long id)
  {
   CArrayObj *list=GetList(SIGNAL_MQL5_PROP_ID,id,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Obtenemos la lista de objetos de señal mql5 según el identificador de la señal y retornamos el único objeto de la lista obtenida, o bien NULL.

Método que retorna el puntero a un objeto de señal mql5 según el nombre de la señal:

//+------------------------------------------------------------------+
//| Return the pointer to an MQL5 signal object by a name            |
//+------------------------------------------------------------------+
CMQLSignal *CMQLSignalsCollection::GetMQLSignal(const string name)
  {
   CArrayObj *list=GetList(SIGNAL_MQL5_PROP_NAME,name,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Obtenemos la lista de objetos de señal mql5 según el nombre de la señal y retornamos el único objeto de la lista obtenida, o bien NULL.

Método que muestra en el diario la descripción completa de la colección:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CMQLSignalsCollection::Print(void)
  {
   ::Print(CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_MQL5_SIGNAL_COLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CMQLSignal *signal=this.m_list.At(i);
      if(signal==NULL)
         continue;
      signal.Print();
     }
  }
//+------------------------------------------------------------------+

Primero, mostramos el encabezado, y luego en un ciclo por la lista de colección, obtenemos el siguiente objeto de señal mql5 y mostramos su descripción completa.

Método que muestra en el diario la descripción breve de la colección:

//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CMQLSignalsCollection::PrintShort(const bool list=false,const bool paid=true,const bool free=true)
  {
   //--- Display the header in the journal
   ::Print(CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_MQL5_SIGNAL_COLLECTION),":");
   //--- If the list is full, display short descriptions of all signals in the collection 
   //--- according to the flags indicating the necessity to display paid and free signals
   if(list)
      for(int i=0;i<this.m_list.Total();i++)
        {
         CMQLSignal *signal=this.m_list.At(i);
         if(signal==NULL || (signal.Price()>0 && !paid) || (signal.Price()==0 && !free))
            continue;
         signal.PrintShort(true);
        }
   //--- If not the signal list
   else
     {
      //--- Sort the list by signal price
      this.m_list.Sort(SORT_BY_SIGNAL_MQL5_PRICE);
      //--- Get the list of free signals and their number
      CArrayObj *list_free=this.GetList(SIGNAL_MQL5_PROP_PRICE,0,EQUAL);
      int num_free=(list_free==NULL ? 0 : list_free.Total());
      //--- Sort the list by signal price
      this.m_list.Sort(SORT_BY_SIGNAL_MQL5_PRICE);
      //--- Get the list of paid signals and their number
      CArrayObj *list_paid=this.GetList(SIGNAL_MQL5_PROP_PRICE,0,MORE);
      int num_paid=(list_paid==NULL ? 0 : list_paid.Total());
      //--- Display the number of free and paid signals in the collection in the journal
      ::Print
        (
         "- ",CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_FREE),": ",(string)num_free,
         ", ",CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_PAID),": ",(string)num_paid
        );
     }
  }
//+------------------------------------------------------------------+

En este método, dependiendo de las banderas transmitidas, se imprimen diferentes mensajes y listas en el diario.

Primero, imprimimos el encabezado, luego, si hemos establecido la bandera de la lista, imprimimos en el diario las descripciones breves de las señales en la colección. En este caso, se tienen en cuenta las banderas de las señales de pago y gratuitas. Dependiendo de su estado, en el diario pueden mostrarse todas las señales, o bien solo las de pago, o solo las gratuitas. Si necesitamos mostrar la descripción no como una lista, después del título, se mostrará el número total de señales gratuitas y de pago en la lista de colección.

Métodos para suscribirse a una señal (método privado) y darse de baja de una señal existente (método público):

//+------------------------------------------------------------------+
//| Subscribe to a signal                                            |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::Subscribe(const long signal_id)
  {
//--- If working with signals is disabled for a program,
//--- display the appropriate message and the recommendation to check the program settings
   if(!this.ProgramIsAllowed())
     {
      ::Print(DFUN,CMessage::Text(MSG_SIGNAL_INFO_ERR_SIGNAL_NOT_ALLOWED));
      ::Print(DFUN,CMessage::Text(MSG_SIGNAL_INFO_TEXT_CHECK_SETTINGS));
      return false;
     }
//--- If failed to subscribe to a signal, display the error message and return 'false'
   ::ResetLastError();
   if(!::SignalSubscribe(signal_id))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
//--- Subscription successful. Display the successful signal subscription message and return 'true'
   ::Print(CMessage::Text(MSG_SIGNAL_INFO_TEXT_SIGNAL_SUBSCRIBED)," ID ",(string)this.CurrentID()," \"",CurrentName(),"\"");
   return true;
  }
//+------------------------------------------------------------------+
//| Unsubscribe from a subscribed signal                             |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentUnsubscribe(void)
  {
//--- If working with signals is disabled for a program,
//--- display the appropriate message and the recommendation to check the program settings
   if(!this.ProgramIsAllowed())
     {
      ::Print(DFUN,CMessage::Text(MSG_SIGNAL_INFO_ERR_SIGNAL_NOT_ALLOWED));
      ::Print(DFUN,CMessage::Text(MSG_SIGNAL_INFO_TEXT_CHECK_SETTINGS));
      return false;
     }
//--- Remember an ID and a name of the current signal
   ::ResetLastError();
   long id=this.CurrentID();
   string name=this.CurrentName();
//--- If the ID is zero (no subscription), return 'true'
   if(id==0)
      return true;
//--- If failed to unsubscribe from a signal, display the error message and return 'false'
   if(!::SignalUnsubscribe())
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
//--- Unsubscribed from a signal successfully. Display the message about successfully unsubscribing from a signal and return 'true'
   ::Print(CMessage::Text(MSG_SIGNAL_INFO_TEXT_SIGNAL_UNSUBSCRIBED)," ID ",(string)id," \"",name,"\"");
   return true;
  }
//+------------------------------------------------------------------+

La lógica de funcionamiento de los métodos está ampliamente comentada en la lista de métodos.

Método público que realiza la suscripción a una señal según el identificador de la señal:

//+------------------------------------------------------------------+
//| Subscribe to a signal by ID                                      |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::SubscribeByID(const long signal_id)
  {
   CMQLSignal *signal=GetMQLSignal(signal_id);
   if(signal==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_MQLSIG_COLLECTION_ERR_FAILED_GET_SIGNAL),": ",signal_id);
      return false;
     }
   return this.Subscribe(signal.ID());
  }
//+------------------------------------------------------------------+

Aquí, obtenemos el puntero a un objeto de señal mql5 en la lista de colección según el identificador transmitido al método y retornamos el resultado del funcionamiento del método privado de suscripción a una señal que hemos analizado anteriormente.

Método público que realiza la suscripción a una señal según el nombre de la señal:

//+------------------------------------------------------------------+
//| Subscribe to a signal by signal name                             |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::SubscribeByName(const string signal_name)
  {
   CMQLSignal *signal=GetMQLSignal(signal_name);
   if(signal==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_MQLSIG_COLLECTION_ERR_FAILED_GET_SIGNAL),": \"",signal_name,"\"");
      return false;
     }
   return this.Subscribe(signal.ID());
  }
//+------------------------------------------------------------------+

Aquí, obtenemos el puntero a un objeto de señal mql5 en la lista de colección según el nombre transmitido al método (deberemos conocer el nombre de antemano) y retornamos el resultado del funcionamiento del método privado de suscripción a una señal que hemos analizado anteriormente.

Métodos para establecer los valores de copiado de las señales comerciales:

//+------------------------------------------------------------------+
//| Set the percentage for converting a deal volume                  |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetEquityLimit(const double value) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetDouble(SIGNAL_INFO_EQUITY_LIMIT,value))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Define the slippage used to set                                  |
//| market orders when synchronizing positions and copying deals     |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetSlippage(const double value) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetDouble(SIGNAL_INFO_SLIPPAGE,value))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set the flag enabling synchronization                            |
//| without confirmation dialog                                      |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetConfirmationsDisableFlag(const bool flag) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_CONFIRMATIONS_DISABLED,flag))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set the flag of copying Stop Loss and Take Profit                |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetSLTPCopyFlag(const bool flag) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_COPY_SLTP,flag))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set deposit limitations (in %)                                   |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetDepositPercent(const int value) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_DEPOSIT_PERCENT,value))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set the flag allowing the copying of signals by subscription     |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetSubscriptionEnabledFlag(const bool flag) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_SUBSCRIPTION_ENABLED,flag))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Aquí, en todos los métodos se usan las funciones de establecimiento de valores SignalInfoSetDouble() y SignalInfoSetInteger(). Si el valor se establece sin éxito, los métodos mostrarán la descripción del error y retornarán false. Si el valor se establece correctamente, los métodos retornarán true.

Métodos que retornan las descripciones de los parámetros de ajuste para el copiado de señales comerciales:

//+------------------------------------------------------------------+
//| Return the description of the permission to work with signals    |
//| for the currently launched program                               |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::ProgramIsAllowedDescription(void)
  {
   return
     (
      CMessage::Text(MSG_SIGNAL_INFO_SIGNALS_PERMISSION)+": "+
      (this.ProgramIsAllowed() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
     );
  }
//+------------------------------------------------------------------+
//| Return the percentage description for converting the deal volume |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentEquityLimitDescription(void) 
  {
   return CMessage::Text(MSG_SIGNAL_INFO_EQUITY_LIMIT)+": "+::DoubleToString(this.CurrentEquityLimit(),2)+"%";
  }
//+------------------------------------------------------------------+
//| Return the description of the slippage used to set               |
//| market orders when synchronizing positions and copying deals     |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentSlippageDescription(void) 
  {
   return CMessage::Text(MSG_SIGNAL_INFO_SLIPPAGE)+": "+CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+" * "+::DoubleToString(this.CurrentSlippage(),2);
  }
//+------------------------------------------------------------------+
//| Return the description of the limitation by funds for a signal   |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentVolumePercentDescription(void)
  {
   return CMessage::Text(MSG_SIGNAL_INFO_VOLUME_PERCENT)+": "+::DoubleToString(this.CurrentVolumePercent(),2)+"%";
  }
//+------------------------------------------------------------------+
//| Return the description of the flag enabling synchronization      |
//| without confirmation dialog                                      |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentConfirmationsDisableFlagDescription(void) 
  {
   return
     (
      CMessage::Text(MSG_SIGNAL_INFO_CONFIRMATIONS_DISABLED)+": "+
      (this.CurrentConfirmationsDisableFlag() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
     );
  }
//+----------------------------------------------------------------------------+
//| Return the description of the flag of copying Stop Loss and Take Profit    |
//+----------------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentSLTPCopyFlagDescription(void) 
  {
   return
     (
      CMessage::Text(MSG_SIGNAL_INFO_COPY_SLTP)+": "+
      (this.CurrentSLTPCopyFlag() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the deposit limitations (in %)         |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentDepositPercentDescription(void) 
  {
   return CMessage::Text(MSG_SIGNAL_INFO_DEPOSIT_PERCENT)+": "+(string)this.CurrentDepositPercent()+"%";
  }
//+------------------------------------------------------------------+
//| Return the description of the flag enabling                      |
//| deal copying by subscription                                     |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentSubscriptionEnabledFlagDescription(void) 
  {
   return
     (
      CMessage::Text(MSG_SIGNAL_INFO_SUBSCRIPTION_ENABLED)+": "+
      (this.CurrentSubscriptionEnabledFlag() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
     );
  }
//+------------------------------------------------------------------+
//| Return the signal ID description                                 |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentIDDescription(void)
  {
   return CMessage::Text(MSG_SIGNAL_INFO_ID)+": "+(this.CurrentID()>0 ? (string)this.CurrentID() : CMessage::Text(MSG_LIB_PROP_EMPTY));
  }
//+------------------------------------------------------------------+
//| Return the description of the flag indicating                    |
//| consent to the terms of use of the Signals service               |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentTermsAgreeFlagDescription(void)
  {
   return
     (
      CMessage::Text(MSG_SIGNAL_INFO_TERMS_AGREE)+": "+
      (this.CurrentTermsAgreeFlag() ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the signal name                        |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentNameDescription(void)
  {
   return CMessage::Text(MSG_SIGNAL_INFO_NAME)+": "+(this.CurrentName()!="" ? this.CurrentName() : CMessage::Text(MSG_LIB_PROP_EMPTY));
  }
//+------------------------------------------------------------------+

En cada método, se crea y retorna una línea con el encabezado de la descripción del parámetro y su valor actual.

Método que muestra en el diario los parámetros de ajuste para el copiado de señales comerciales:

//+------------------------------------------------------------------+
//| Display the parameters of signal copying settings in the journal |
//+------------------------------------------------------------------+
void CMQLSignalsCollection::CurrentSubscriptionParameters(void)
  {
   ::Print("============= ",CMessage::Text(MSG_SIGNAL_INFO_PARAMETERS)," =============");
   ::Print(this.ProgramIsAllowedDescription());
   ::Print(this.CurrentTermsAgreeFlagDescription());
   ::Print(this.CurrentSubscriptionEnabledFlagDescription());
   ::Print(this.CurrentConfirmationsDisableFlagDescription());
   ::Print(this.CurrentSLTPCopyFlagDescription());
   ::Print(this.CurrentSlippageDescription());
   ::Print(this.CurrentEquityLimitDescription());
   ::Print(this.CurrentDepositPercentDescription());
   ::Print(this.CurrentVolumePercentDescription());
   ::Print(this.CurrentIDDescription());
   ::Print(this.CurrentNameDescription());
   ::Print("");
  }  
//+------------------------------------------------------------------+

Primero, se muestra el encabezado y luego, uno por uno, se imprimen todos los parámetros de ajuste para copiar las señales comerciales retornadas por los métodos correspondientes, descritos anteriormente.

Con esto, podemos dar por finalizada la creación de la clase de colección de objetos de señal mql5.
En el futuro, puede que volvamos a trabajar en su mejora, pero, por ahora, no tenemos forma de conocer su relevancia, así que la dejaremos como está.

Para vincular la clase de colección de señales comerciales con el "mundo exterior", necesitaremos añadir los métodos necesarios para trabajar con ella en la clase del objeto principal de la biblioteca CEngine, en el archivo \MQL5\Include\DoEasy\Engine.mqh.

Conectamos el archivo de la clase de colección de señales comerciales al archivo de la clase del objeto CEngine y declaramos el objeto de clase de colección de señales mql5:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "Collections\IndicatorsCollection.mqh"
#include "Collections\TickSeriesCollection.mqh"
#include "Collections\BookSeriesCollection.mqh"
#include "Collections\MQLSignalsCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_time_series;                  // Timeseries collection
   CBuffersCollection   m_buffers;                       // Collection of indicator buffers
   CIndicatorsCollection m_indicators;                   // Indicator collection
   CTickSeriesCollection m_tick_series;                  // Collection of tick series
   CMBookSeriesCollection m_book_series;                 // Collection of DOM series
   CMQLSignalsCollection m_signals_mql5;                 // Collection of MQL5.com Signals service signals
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CPause               m_pause;                         // Pause object
   CArrayObj            m_list_counters;                 // List of timer counters

En la sección pública de la clase, declaramos e implementamos los nuevos métodos para trabajar con la clase de colección de instantáneas de la profundidad de mercado y los métodos para trabajar con la colección de señales comerciales:

//--- Update the DOM series of a specified symbol
   void                 MBookSeriesRefresh(const string symbol,const long time_msc)    { this.m_book_series.Refresh(symbol,time_msc);        }

//--- Return (1) the DOM series of a specified symbol, the DOM (2) by index and (3) by time in milliseconds
   CMBookSeries        *GetMBookSeries(const string symbol)                            { return this.m_book_series.GetMBookseries(symbol);   }
   CMBookSnapshot      *GetMBook(const string symbol,const int index)                  { return this.m_book_series.GetMBook(symbol,index);   }
   CMBookSnapshot      *GetMBook(const string symbol,const long time_msc)              { return this.m_book_series.GetMBook(symbol,time_msc);}
   
//--- Return the volume of a (1) buy and (2) sell DOM specified by symbol and index
   long                 MBookVolumeBuy(const string symbol,const int index);
   long                 MBookVolumeSell(const string symbol,const int index);
//--- Return the increased precision volume of a (1) buy and (2) sell DOM specified by symbol and index
   double               MBookVolumeBuyReal(const string symbol,const int index);
   double               MBookVolumeSellReal(const string symbol,const int index);
//--- Return the volume of a (1) buy and (2) sell DOM specified by symbol and time in milliseconds
   long                 MBookVolumeBuy(const string symbol,const long time_msc);
   long                 MBookVolumeSell(const string symbol,const long time_msc);
//--- Return the increased precision volume of a (1) buy and (2) sell DOM specified by symbol and time in milliseconds
   double               MBookVolumeBuyReal(const string symbol,const long time_msc);
   double               MBookVolumeSellReal(const string symbol,const long time_msc);
   

//--- Return (1) the collection of mql5.com Signals service signals and (2) the list of signals from the mql5.com Signals service signal collection
   CMQLSignalsCollection *GetSignalsMQL5Collection(void)                               { return &this.m_signals_mql5;                        }
   CArrayObj           *GetListSignalsMQL5(void)                                       { return this.m_signals_mql5.GetList();               }
//--- Return the list of (1) paid and (2) free signals
   CArrayObj           *GetListSignalsMQL5Paid(void)                    { return this.m_signals_mql5.GetList(SIGNAL_MQL5_PROP_PRICE,0,MORE); }
   CArrayObj           *GetListSignalsMQL5Free(void)                    { return this.m_signals_mql5.GetList(SIGNAL_MQL5_PROP_PRICE,0,EQUAL);}

//--- (1) Create and (2) update the collection of mql5.com Signals service signals
   bool                 SignalsMQL5Create(void)                                        { return this.m_signals_mql5.CreateCollection();      } 
   void                 SignalsMQL5Refresh(void)                                       { this.m_signals_mql5.Refresh();                      }
//--- Subscribe to a signal by (1) ID and (2) signal name
   bool                 SignalsMQL5Subscribe(const long signal_id)                     { return this.m_signals_mql5.SubscribeByID(signal_id);}
   bool                 SignalsMQL5Subscribe(const string signal_name)                 { return this.m_signals_mql5.SubscribeByName(signal_name);}
//--- Unsubscribe from the current signal
   bool                 SignalsMQL5Unsubscribe(void)                                   { return this.m_signals_mql5.CurrentUnsubscribe();    }
//--- Return (1) ID and (2) the name of the current signal subscription is performed to
   long                 SignalsMQL5CurrentID(void)                                     { return this.m_signals_mql5.CurrentID();             }
   string               SignalsMQL5CurrentName(void)                                   { return this.m_signals_mql5.CurrentName();           }
     
//--- Set the percentage for converting deal volume
   bool                 SignalsMQL5CurrentSetEquityLimit(const double value)  { return this.m_signals_mql5.CurrentSetEquityLimit(value);     }
//--- Set the market order slippage used when synchronizing positions and copying deals
   bool                 SignalsMQL5CurrentSetSlippage(const double value)     { return this.m_signals_mql5.CurrentSetSlippage(value);        }
//--- Set deposit limitations (in %)
   bool                 SignalsMQL5CurrentSetDepositPercent(const int value)  { return this.m_signals_mql5.CurrentSetDepositPercent(value);  }
//--- Enable synchronization without the confirmation dialog
   bool                 SignalsMQL5CurrentSetConfirmationsDisableON(void)     { return this.m_signals_mql5.CurrentSetConfirmationsDisableON();}
//--- Disable synchronization without the confirmation dialog
   bool                 SignalsMQL5CurrentSetConfirmationsDisableOFF(void)    { return this.m_signals_mql5.CurrentSetConfirmationsDisableOFF();}
//--- Enable copying Stop Loss and Take Profit
   bool                 SignalsMQL5CurrentSetSLTPCopyON(void)                 { return this.m_signals_mql5.CurrentSetSLTPCopyON();           }
//--- Disable copying Stop Loss and Take Profit
   bool                 SignalsMQL5CurrentSetSLTPCopyOFF(void)                { return this.m_signals_mql5.CurrentSetSLTPCopyOFF();          }
//--- Enable copying deals by subscription
   bool                 SignalsMQL5CurrentSetSubscriptionEnableON(void)       { return this.m_signals_mql5.CurrentSetSubscriptionEnableON(); }
//--- Disable copying deals by subscription
   bool                 SignalsMQL5CurrentSetSubscriptionEnableOFF(void)      { return this.m_signals_mql5.CurrentSetSubscriptionEnableOFF();}
   
//--- Display (1) the complete, (2) short collection description in the journal and (3) parameters of the signal copying settings
   void                 SignalsMQL5Print(void)                                         { m_signals_mql5.Print();                             }
   void                 SignalsMQL5PrintShort(const bool list=false,const bool paid=true,const bool free=true)
                                                                                       { m_signals_mql5.PrintShort(list,paid,free);          }
   void                 SignalsMQL5CurrentSubscriptionParameters(void)                 { this.m_signals_mql5.CurrentSubscriptionParameters();}

//--- Return (1) the buffer collection and (2) the buffer list from the collection 

Los métodos implementados retornan el resultado de la llamada a los métodos homónimos de las colecciones correspondientes.

Vamos a analizar la implementación de los métodos que retornan los volúmenes establecidos de las instantáneas indicadas de las profundidades de mercado:

//+------------------------------------------------------------------+
//| Return the buy volume of a DOM                                   |
//| specified by symbol and index                                    |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeBuy(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeBuy() : 0);
  }
//+------------------------------------------------------------------+
//| Return the sell volume of a DOM                                  |
//| specified by symbol and index                                    |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeSell(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeSell() : 0);
  }
//+------------------------------------------------------------------+
//| Return the extended accuracy buy volume                          |
//| of a DOM specified by symbol and index                           |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeBuyReal(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeBuyReal() : 0);
  }
//+------------------------------------------------------------------+
//| Return the extended accuracy sell volume                         |
//| of a DOM specified by symbol and index                           |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeSellReal(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeSellReal() : 0);
  }
//+------------------------------------------------------------------+
//| Return the buy volume of a DOM                                   |
//| specified by symbol and time in milliseconds                     |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeBuy(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeBuy() : 0);
  }
//+------------------------------------------------------------------+
//| Return the sell volume of a DOM                                  |
//| specified by symbol and time in milliseconds                     |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeSell(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeSell() : 0);
  }
//+------------------------------------------------------------------+
//| Return the extended accuracy buy volume                          |
//| of a DOM specified by symbol and time in milliseconds            |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeBuyReal(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeBuyReal() : 0);
  }
//+------------------------------------------------------------------+
//| Return the extended accuracy sell volume                         |
//| of a DOM specified by symbol and time in milliseconds            |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeSellReal(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeSellReal() : 0);
  }
//+------------------------------------------------------------------+

Aquí, todo resulta sencillo. La lógica de todos los métodos es idéntica: primero, obtenemos de la lista de colección el objeto de instantánea de la profundidad de mercado según el símbolo y el índice o el tiempo en milisegundos con la ayuda de los métodos GetMBook() implementados previamente. A continuación, retornamos el volumen de la profundidad de mercado correspondiente al método desde el objeto de instantánea obtenido de la profundidad de mercado, o cero, si no hemos podido obtener el objeto.

Por hoy, estos son todos los cambios y mejoras.

Simulación

Vamos a poner a prueba la creación de la colección de Señales de MQL5.com. Realizaremos la simulación así:
Primero, obtendremos una lista completa de la base de datos de las Señales; luego, mostraremos una lista que contenga solo las señales gratuitas, y después, encontraremos la señal más rentable en esta lista y nos suscribiremos a ella. Si la suscripción tiene éxito, mostraremos los parámetros de la señal suscrita, así como los parámetros de copiado de señales comerciales establecidos al suscribirse a la señal. En el siguiente tick, nos daremos de baja de la señal suscrita.

Para la prueba, tomaremos el asesor del artículo anterior
y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part66\ con el nuevo nombre TestDoEasyPart66.mq5.

En la lista de parámetros de entrada del asesor, añadiremos el parámetro de ajuste que permite elegir si queremos trabajar con el servicio de Señales de MQL5.com en el asesor:

//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)

sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 

input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)

sinput   ENUM_INPUT_YES_NO InpUseBook           =  INPUT_YES;                       // Use Depth of Market
sinput   ENUM_INPUT_YES_NO InpUseMqlSignals     =  INPUT_YES;                       // Use signal service
sinput   ENUM_INPUT_YES_NO InpUseSounds         =  INPUT_YES;                       // Use sounds

//--- global variables

En el último artículo, hicimos todas las comprobaciones para trabajar con las señales en el manejador OnInit() del asesor. En esta ocasión, trabajaremos en OnTick().
Por ello, eliminaremos el anterior bloque de código de prueba, ya innecesario, del manejador OnInit():

   CArrayObj *list=new CArrayObj();
   if(list!=NULL)
     {
      //--- request the total number of signals in the signal database 
      int total=SignalBaseTotal(); 
      //--- loop through all signals 
      for(int i=0;i<total;i++) 
        { 
         //--- select a signal for further operation 
         if(!SignalBaseSelect(i))
            continue;
         long id=SignalBaseGetInteger(SIGNAL_BASE_ID);
         CMQLSignal *signal=new CMQLSignal(id);
         if(signal==NULL)
            continue;
         if(!list.Add(signal))
           {
            delete signal;
            continue;
           }
        } 
      //--- display all profitable free signals with a non-zero number of subscribers
      Print("");
      static bool done=false;
      for(int i=0;i<list.Total();i++)
        {
         CMQLSignal *signal=list.At(i);
         if(signal==NULL)
            continue;
         if(signal.Price()>0 || signal.Subscribers()==0)
            continue;
         //--- The very first suitable signal is fully displayed in the journal
         if(!done)
           {
            signal.Print();
            done=true;
           }
         //--- Short descriptions are displayed for the rest
         else
            signal.PrintShort();
        }
      delete list;
     }
     
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

En el manejador OnTick(), escribiremos un nuevo bloque de código de prueba, en el que se cumplirán todas las condiciones de prueba que hemos indicado al inicio de esta sección:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Remove EA graphical objects by an object name prefix
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Deinitialize library
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Handle the NewTick event in the library
   engine.OnTick(rates_data);

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      engine.EventsHandling();      // Working with events
     }

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
   
//--- Search for available signals in the database and check the ability to subscribe to a signal by its name
   static bool done=false;
   //--- If the first launch and working with signals is enabled in EA custom settings
   if(InpUseMqlSignals && !done)
     {
      //--- Display the list of all free signals in the journal
      Print("");
      engine.GetSignalsMQL5Collection().PrintShort(true,false,true);
      //--- Get the list of free signals
      CArrayObj *list=engine.GetListSignalsMQL5Free();
      //--- If the list is obtained
      if(list!=NULL)
        {
         //--- Find a signal with the maximum growth in % in the list
         int index_max_gain=CSelect::FindMQLSignalMax(list,SIGNAL_MQL5_PROP_GAIN);
         CMQLSignal *signal_max_gain=list.At(index_max_gain);
         //--- If the signal is found
         if(signal_max_gain!=NULL)
           {
            //--- Display the full signal description in the journal
            signal_max_gain.Print();
            //--- If managed to subscribe to a signal
            if(engine.SignalsMQL5Subscribe(signal_max_gain.ID()))
              {
               //--- Set subscription parameters
               //--- Enable copying deals by subscription
               engine.SignalsMQL5CurrentSetSubscriptionEnableON();
               //--- Set synchronization without the confirmation dialog
               engine.SignalsMQL5CurrentSetConfirmationsDisableOFF();
               //--- Set copying Stop Loss and Take Profit
               engine.SignalsMQL5CurrentSetSLTPCopyON();
               //--- Set the market order slippage used when synchronizing positions and copying deals
               engine.SignalsMQL5CurrentSetSlippage(2);
               //--- Set the percentage for converting deal volume
               engine.SignalsMQL5CurrentSetEquityLimit(50);
               //--- Set deposit limitations (in %)
               engine.SignalsMQL5CurrentSetDepositPercent(70);
               //--- Display subscription parameters in the journal
               engine.SignalsMQL5CurrentSubscriptionParameters();
              }
           }
        }
      done=true;
      return;
     }
   //--- If a signal subscription is active,  unsubscribe
   if(engine.SignalsMQL5CurrentID()>0)
     {
      engine.SignalsMQL5Unsubscribe();
     }
//---
  }
//+------------------------------------------------------------------+

Ya hemos comentado con suficiente detalle el nuevo bloque de código al completo. Si el lector tiene alguna duda al respecto, podrá plantearla en los comentarios al artículo.

En la función de inicialización de la biblioteca OnInitDoEasy(), escribimos el bloque de código en el que se creará la lista de colección de señales comerciales y se establecerá la bandera para permitir el copiado de señales comerciales según la suscripción:

//--- Create tick series of all used symbols
   engine.TickSeriesCreateAll();

//--- Check created tick series - display descriptions of all created tick series in the journal
   engine.GetTickSeriesCollection().Print();

//--- Check created DOM series - display descriptions of all created DOM series in the journal
   engine.GetMBookSeriesCollection().Print();
   
//--- Create the collection of mql5.com Signals service signals
//--- If working with signals is enabled and the signal collection is created
   if(InpUseMqlSignals && engine.SignalsMQL5Create())
     {
      //--- Enable copying deals by subscription
      engine.SignalsMQL5CurrentSetSubscriptionEnableON();
      //--- Check created MQL5 signal objects of the Signals service - display the short collection description in the journal
      engine.SignalsMQL5PrintShort();
     }
//--- If working with signals is not enabled or failed to create the signal collection, 
//--- disable copying deals by subscription
   else
      engine.SignalsMQL5CurrentSetSubscriptionEnableOFF();

//--- Create resource text files

A continuación, compilamos el asesor y lo iniciamos en el gráfico del símbolo, configurando previamente en los ajustes el trabajo con el símbolo/marco temporal actual y estableciendo la bandera que indica si vamos a trabajar con las señales comerciales del servicio Señales de MQL5.com:


Debemos asegurarnos de marcar en la pestaña "General" de la ventana de ajustes del asesor la casilla "Permitir el cambio de los ajustes de las señales":


Si nos saltamos este paso, el asesor no podrá trabajar con el servicio de Señales de MQL5.com.

Después de iniciar el asesor, en el diario se mostrará un mensaje sobre la creación exitosa de la colección de señales, así como una breve descripción de la misma:

Collection of MQL5.com Signals service signals created successfully
Collection of MQL5.com Signals service signals:
- Free signals: 195, Paid signals: 805

A continuación, se mostrará una lista completa de las señales gratuitas. Dado que son muchas, pondremos solo una parte como ejemplo:

Collection of MQL5.com Signals service signals:
- Signal "GBPUSD EXPERT 23233". Author login: mbt_trader, ID 919099, Growth: 3.30, Drawdown: 11.92, Price: 0.00, Subscribers: 0
- Signal "Willian". Author login: Desg, ID 917396, Growth: 12.69, Drawdown: 15.50, Price: 0.00, Subscribers: 0
- Signal "VahidVHZ1366". Author login: 39085485, ID 921427, Growth: 34.36, Drawdown: 12.84, Price: 0.00, Subscribers: 0
- Signal "Vikings". Author login: Myxx, ID 921040, Growth: 7.05, Drawdown: 2.22, Price: 0.00, Subscribers: 2
- Signal "VantageFX Sunphone Dragon". Author login: sunphone, ID 916421, Growth: 537.89, Drawdown: 39.06, Price: 0.00, Subscribers: 21
- Signal "Forex money maker free". Author login: Yggdrasills, ID 916328, Growth: 44.66, Drawdown: 61.15, Price: 0.00, Subscribers: 0
...
...
...
- Signal "Nine Pairs ST". Author login: ebi.pilehvar, ID 935603, Growth: 25.92, Drawdown: 26.41, Price: 0.00, Subscribers: 2
- Signal "FBS140". Author login: mohammeeeedali, ID 949720, Growth: 42.14, Drawdown: 23.11, Price: 0.00, Subscribers: 2
- Signal "StopTheFourthAddition". Author login: pinheirodps, ID 934990, Growth: 41.78, Drawdown: 28.03, Price: 0.00, Subscribers: 2
- Signal "The art of Forex". Author login: Myxx, ID 801685, Growth: 196.39, Drawdown: 40.95, Price: 0.00, Subscribers: 59
- Signal "Bongsanmaskdance1803". Author login: kim25801863, ID 936062, Growth: 12.53, Drawdown: 10.31, Price: 0.00, Subscribers: 0
- Signal "Prospector Scalper EA". Author login: robots4forex, ID 435626, Growth: 334.76, Drawdown: 43.93, Price: 0.00, Subscribers: 215
- Signal "ADS MT5". Author login: vluxus, ID 478235, Growth: 295.68, Drawdown: 40.26, Price: 0.00, Subscribers: 92

A continuación, se mostrará una descripción completa de la señal encontrada con el incremento máximo en tanto por ciento y un mensaje sobre la suscripción exitosa a la misma:

============= Beginning of parameter list (Signal from the MQL5.com Signal service) =============
Account type: Demo
Publication date: 2020.07.02 16:29
Monitoring start date: 2020.07.02 16:29
Date of the latest update of the trading statistics: 2021.03.07 15:11
ID: 784584
Trading account leverage: 33
Trading result in pips: -19248988
Position in the Rating of Signals: 872
Number of subscribers: 6
Number of trades: 1825
Status of account subscription to a signal: No
------
Account balance: 12061.98
Account equity: 12590.32
Account growth in %: 1115.93
Maximum drawdown: 70.62
Signal subscription price: 0.00
Signal ROI (Return on Investment) in %: 1169.19
------
Author login: "tradewai.com"
Broker (company) name: "MetaQuotes Software Corp."
Broker server: "MetaQuotes-Demo"
Name: "Tradewai"
Account currency: "USD"
============= End of parameter list (Signal from the MQL5.com Signal service) =============

Subscribed to signal ID 784584 "Tradewai"

Después, se mostrarán los parámetros de la suscripción:

============= Signal copying parameters =============
Allow using signals for program: Yes
Agree to the terms of use of the Signals service: Yes
Enable copying deals by subscription: Yes
Enable synchronization without confirmation dialog: No
Copying Stop Loss and Take Profit: Yes
Market order slippage when synchronizing positions and copying deals: Spread * 2.00
Percentage for converting deal volume: 50.00%
Limit by deposit: 70%
Limitation on signal equity: 7.00%
Signal ID: 784584
Signal name: Tradewai

y cuando llegue el siguiente tick, recibiremos un mensaje sobre la cancelación exitosa de la suscripción a la señal.

Unsubscribed from the signal ID 784584 "Tradewai"


¿Qué es lo próximo?

En el próximo artículo, comenzaremos a desarrollar la funcionalidad de la biblioteca para trabajar con los gráficos de los símbolos.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Queríamos señalar que la simulación del trabajo con las señales en el asesor experto de prueba supone precisamente una prueba para trabajar con señales y su uso no resulta apropiado en la forma en que se implementa en el asesor experto adjunto. Tenga en cuenta que este es solo un ejemplo sin ninguna carga útil.
El presente ejemplo en el asesor experto solo ofrece una idea de la dirección general de las acciones al implementar nuestros propios desarrollos basados ​​en la biblioteca y sus clases para trabajar con el servicio de Señales de MQL5.com.

Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

Trabajando con los precios en la biblioteca DoEasy (Parte 62): Actualización de las series de tick en tiempo real, preparando para trabajar con la Profundidad del mercado
Trabajando con los precios en la biblioteca DoEasy (Parte 63): Profundidad del mercado, clase de orden abstracta de la Profundidad del mercado
Trabajando con los precios en la biblioteca DoEasy (Parte 64): Profundidad del mercado, clases del objeto de instantánea y del objeto de serie de instantáneas del DOM
Trabajando con los precios y Señales en la biblioteca DoEasy (Parte 65): Colección de la profundidad de mercado y clase para trabajar con las Señales MQL5.com