Прочие классы в библиотеке DoEasy (Часть 66): Класс-коллекция Сигналов MQL5.com

Artyom Trishkin | 12 марта, 2021

Содержание


Концепция

В прошлой статье мы создали класс объекта-сигнала, представляющего собой один сигнал из множества, транслируемых в сервисе Сигналы MQL5.com.
Сегодня создадим класс-коллекцию сигналов, доступных в базе сигналов, и которые можно получить при помощи функции SignalBaseSelect(), указав индекс нужного нам сигнала.
Коллекция будет позволять хранить все имеющиеся в базе сигналы в удобном для поиска и сортировки списке. Мы сможем находить и возвращать списки сигналов по их различным свойствам, например, сможем получить списки только бесплатных, или наоборот — только платных сигналов, отсортировать их по одному из параметров, например, по прибыльности сигнала, либо сразу получить индекс сигнала в списке с параметром равным, больше, или меньше заданного значения. Зная заранее имя нужного нам сигнала, мы сможем быстро найти его в коллекции для дальнейшей с ним работы.
В классе-коллекции организуем возможность подписаться на выбранный в коллекции сигнал, либо отписаться от сигнала, на который уже осуществлена подписка на текущем аккаунте.

Помимо работы с объектами сервиса Сигналов MQL5.com доработаем класс объекта-снимка стакана цен — добавим в него дополнительные свойства, позволяющие сразу же при создании объекта-снимка стакана цен рассчитать по отдельности объёмы заявок на покупку и на продажу. Это избавит конечного пользователя от проведения дополнительных расчётов при работе со стаканом цен — мы сразу же будем знать совокупные объёмы каждого снимка стакана цен — как на покупку, так и на продажу, что позволит не прибегать к дополнительному поиску заявок Buy и Sell в стакане цен с последующим суммированием их объёмов при создании стратегий с использованием стакана цен и его объёмов.


Доработка классов библиотеки

Как уже сложилось, сразу же добавим все новые сообщения библиотеки в файл \MQL5\Include\DoEasy\Data.mqh.
Сначала впишем индексы новых сообщений:

//--- CMarketBookSnapshot
   MSG_MBOOK_SNAP_TEXT_SNAPSHOT,                      // Снимок стакана цен
   MSG_MBOOK_SNAP_VOLUME_BUY,                         // Объём на покупку
   MSG_MBOOK_SNAP_VOLUME_SELL,                        // Объём на продажу
   
//--- CMBookSeries
   MSG_MBOOK_SERIES_TEXT_MBOOKSERIES,                 // Серия снимков стакана цен
   MSG_MBOOK_SERIES_ERR_ADD_TO_LIST,                  // Ошибка. Не удалось добавить серию снимков стакана цен в список

...

//--- CMQLSignal
   MSG_SIGNAL_MQL5_TEXT_SIGNAL,                       // Сигнал
   MSG_SIGNAL_MQL5_TEXT_SIGNAL_MQL5,                  // Сигнал сервиса сигналов mql5.com
   MSG_SIGNAL_MQL5_TRADE_MODE,                        // Тип счета
   MSG_SIGNAL_MQL5_DATE_PUBLISHED,                    // Дата публикации
   MSG_SIGNAL_MQL5_DATE_STARTED,                      // Дата начала мониторинга
   MSG_SIGNAL_MQL5_DATE_UPDATED,                      // Дата последнего обновления торговой статистики
   MSG_SIGNAL_MQL5_ID,                                // ID
   MSG_SIGNAL_MQL5_LEVERAGE,                          // Плечо торгового счета
   MSG_SIGNAL_MQL5_PIPS,                              // Результат торговли в пипсах
   MSG_SIGNAL_MQL5_RATING,                            // Позиция в рейтинге сигналов
   MSG_SIGNAL_MQL5_SUBSCRIBERS,                       // Количество подписчиков
   MSG_SIGNAL_MQL5_TRADES,                            // Количество трейдов
   MSG_SIGNAL_MQL5_SUBSCRIPTION_STATUS,               // Состояние подписки счёта на этот сигнал
   MSG_SIGNAL_MQL5_EQUITY,                            // Средства на счете
   MSG_SIGNAL_MQL5_GAIN,                              // Прирост счета в процентах
   MSG_SIGNAL_MQL5_MAX_DRAWDOWN,                      // Максимальная просадка
   MSG_SIGNAL_MQL5_PRICE,                             // Цена подписки на сигнал
   MSG_SIGNAL_MQL5_ROI,                               // Значение ROI (Return on Investment) сигнала в %
   MSG_SIGNAL_MQL5_AUTHOR_LOGIN,                      // Логин автора
   MSG_SIGNAL_MQL5_BROKER,                            // Наименование брокера (компании)
   MSG_SIGNAL_MQL5_BROKER_SERVER,                     // Сервер брокера
   MSG_SIGNAL_MQL5_NAME,                              // Имя
   MSG_SIGNAL_MQL5_CURRENCY,                          // Валюта счета
   MSG_SIGNAL_MQL5_TEXT_GAIN,                         // Прирост
   MSG_SIGNAL_MQL5_TEXT_DRAWDOWN,                     // Просадка
   MSG_SIGNAL_MQL5_TEXT_SUBSCRIBERS,                  // Подписчиков
   
//--- CMQLSignalsCollection
   MSG_MQLSIG_COLLECTION_TEXT_MQL5_SIGNAL_COLLECTION, // Коллекция сигналов сервиса сигналов mql5.com
   MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_PAID,           // Платных сигналов
   MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_FREE,           // Бесплатных сигналов
   MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_NEW,            // Новый сигнал добавлен в коллекцию
   MSG_MQLSIG_COLLECTION_ERR_FAILED_GET_SIGNAL,       // Не удалось получить сигнал из коллекции
   MSG_SIGNAL_INFO_PARAMETERS,                        // Параметры копирования сигнала
   MSG_SIGNAL_INFO_EQUITY_LIMIT,                      // Процент для конвертации объема сделки
   MSG_SIGNAL_INFO_SLIPPAGE,                          // Проскальзывание, с которым выставляются рыночные ордера при синхронизации позиций и копировании сделок
   MSG_SIGNAL_INFO_VOLUME_PERCENT,                    // Ограничение по средствам для сигнала
   MSG_SIGNAL_INFO_CONFIRMATIONS_DISABLED,            // Разрешение синхронизации без показа диалога подтверждения
   MSG_SIGNAL_INFO_COPY_SLTP,                         // Копирование Stop Loss и Take Profit
   MSG_SIGNAL_INFO_DEPOSIT_PERCENT,                   // Ограничение по депозиту
   MSG_SIGNAL_INFO_ID,                                // Идентификатор сигнала
   MSG_SIGNAL_INFO_SUBSCRIPTION_ENABLED,              // Разрешение на копирование сделок по подписке
   MSG_SIGNAL_INFO_TERMS_AGREE,                       // Согласие с условиями использования сервиса "Сигналы"
   MSG_SIGNAL_INFO_NAME,                              // Имя сигнала
   MSG_SIGNAL_INFO_SIGNALS_PERMISSION,                // Разрешение на работу с сигналами для программы
   MSG_SIGNAL_INFO_TEXT_SIGNAL_SUBSCRIBED,            // Осуществлена подписка на сигнал
   MSG_SIGNAL_INFO_TEXT_SIGNAL_UNSUBSCRIBED,          // Осуществлена отписка от сигнала
   MSG_SIGNAL_INFO_ERR_SIGNAL_NOT_ALLOWED,            // Работа с сервисом сигналов для программы не разрешена
   MSG_SIGNAL_INFO_TEXT_CHECK_SETTINGS,               // Пожалуйста, проверьте настройки программы (Общие-->Разрешить изменение настроек Сигналов)
   
  };
//+------------------------------------------------------------------+

Затем впишем тексты сообщений, соответствующие вновь добавленным индексам:

//--- 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)"
   },
   
  };
//+---------------------------------------------------------------------+

Часто при выводе сообщений в журнал, особенно отладочных, в начале сообщения мы указываем название метода, из которого было послано это сообщение.  В статье 19 нами был создан класс сообщений библиотеки. Но пока мы не очень активно его используем — только указываем индексы сообщений, которые необходимо вывести в журнал при помощи стандартной функции Print(). Так как в скором времени мы начнём новый раздел библиотеки по работе с графикой, то постепенно будем переходить на работу с этим классом для вывода сообщений из библиотеки. Сегодня добавим в него перегрузку метода ToLog(), чтобы можно было дополнительно передать в метод "источник" сообщения — тот метод класса или функцию программы, из которой был вызван этот метод. Таким образом, у нас будут два варианта метода ToLog(), позволяющие выводить сообщения с указанием его исходной функции или метода и без такого указания.

Откроем файл \MQL5\Include\DoEasy\Services\Message.mqh и допишем в него объявление перегруженного метода:

//--- (1,2) выводит сообщение в журнал по идентификатору, (3) на e-mail, (4) на мобильное устройство
   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) отправляет файл на FTP, (2) возвращает код ошибки
   static bool       ToFTP(const string filename,const string ftp_path=NULL);
   static int        GetError(void)                { return CMessage::m_global_error;  }

За пределами тела класса напишем его реализацию:

//+------------------------------------------------------------------+
//| Выводит сообщение в журнал по идентификатору сообщения           |
//+------------------------------------------------------------------+
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)));
  }
//+------------------------------------------------------------------+
//| Выводит сообщение в журнал по идентификатору сообщения           |
//+------------------------------------------------------------------+
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)));
  }
//+------------------------------------------------------------------+

Как видим, в отличии от первой формы вызова метода во второй его форме добавлен ещё один входной параметр, в котором и будет передаваться наименование метода или функции, из которых был вызван метод ToLog(), и которое будет выводиться в журнал перед сообщением.

К данному классу мы ещё вернёмся в последующих статьях для внесения в него доработок при переводе всех классов библиотеки на вывод сообщений посредством этого класса.

Доработаем класс CMBookSnapshot в файле \MQL5\Include\DoEasy\Objects\Book\MarketBookSnapshot.mqh.

В приватной секции класса впишем переменные-члены класса для хранения совокупных объёмов снимка стакана цен на покупку и продажу:

//+------------------------------------------------------------------+
//| Класс "Снимок стакана цен"                                       |
//+------------------------------------------------------------------+
class CMBookSnapshot : public CBaseObj
  {
private:
   string            m_symbol;                  // Символ
   long              m_time;                    // Время снимка
   int               m_digits;                  // Digits символа
   long              m_volume_buy;              // Объём стакана на покупку
   long              m_volume_sell;             // Объём стакана на продажу
   double            m_volume_buy_real;         // Объём стакана на покупку с повышенной точностью
   double            m_volume_sell_real;        // Объём стакана на продажу с повышенной точностью
   CArrayObj         m_list;                    // Список объектов-заявок стакана цен
public:

В разделе методов упрощённого доступа к свойствам объекта-снимка стакана цен публичной секции класса впишем методы, возвращающие эти вновь добавленные свойства класса, и методы для вывода их описания:

//+------------------------------------------------------------------+ 
//|Методы упрощённого доступа к свойствам объекта-снимка стакана цен |
//+------------------------------------------------------------------+
//--- Устанавливает (1) символ, (2) время снимка стакана цен, (3) указанное время всем заявкам в стакане
   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);
//--- Возвращает (1) символ стакана цен, (2) Digits символа, (3) время снимка
//--- объём снимка стакана цен (4) на покупку, (5) на продажу,
//--- с повышенной точностью (6) на покупку, (7) на продажу,
   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;   }
   
//--- Возвращает описание объёма стакана (1) на покупку, (2) на продажу
   string            VolumeBuyDescription(void);
   string            VolumeSellDescription(void);
   
  };
//+------------------------------------------------------------------+

При создании нового объекта-снимка стакана цен мы просматриваем в цикле все его заявки, создаём объекты этих заявок и складываем их в список. Сейчас нам нужно в конструкторе класса учитывать типы заявок и сразу же прибавлять к значению переменных, хранящих совокупный объём заявок на покупку и продажу, объём текущей заявки в зависимости от её типа. Таким образом, каждая переменная в итоге будет хранить общий объём заявок либо на покупку, либо на продажу сразу же при создании объекта-снимка стакана цен.

Впишем эти доработки в параметрический конструктор класса:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CMBookSnapshot::CMBookSnapshot(const string symbol,const long time,MqlBookInfo &book_array[]) : m_time(time)
  {
   //--- Устанавливаем символ
   this.SetSymbol(symbol);
   //--- Очищаем список
   this.m_list.Clear();
   //--- В цикле по массиву структур
   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++)
     {
      //--- Создаём объекты-заявки текущего снимка стакана цен в зависимости от типа заявки
      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;
      //--- Устанавливаем заявке время снимка стакана цен
      mbook_ord.SetTime(this.m_time);

      //--- Ставим списку флаг сортированного списка (по значению цены) и добавляем в него текущий объект-заявку
      //--- Если не удалось добавить объект в список заявок стакана цен - удаляем объект-заявку
      this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE);
      if(!this.m_list.InsertSort(mbook_ord))
         delete mbook_ord;
      //--- Если объект-заявка успешно добавлен в список заявок снимка стакана - дополняем общие объёмы снимка
      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;
           }
        }
     }
  }
//+------------------------------------------------------------------+

Здесь: в первую очередь инициализируем переменные, хранящие общие объёмы всех заявок снимка стакана цен на покупку и продажу. Затем — уже в теле цикла по всем заявкам стакана, в зависимости от типа заявки, добавляем к переменным, хранящим общие объёмы, и соответствующие типу заявки, объём текущей заявки. Таким образом, у нас по окончании цикла по всем заявкам снимка стакана цен во всех переменных будут храниться совокупные объёмы на покупку и на продажу.

В методы, выводящие в журнал краткое описание объекта и все свойства объекта, добавим вывод общих объёмов на покупку и продажу:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта                        |
//+------------------------------------------------------------------+
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),")");
  }
//+------------------------------------------------------------------+
//| Выводит в журнал свойства объекта                                |
//+------------------------------------------------------------------+
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());
     }
  }
//+------------------------------------------------------------------+

За пределами тела класса напишем реализацию двух новых методов, возвращающих описания объёмов стакана на покупку и на продажу:

//+------------------------------------------------------------------+
//| Возвращает описание объёма стакана на покупку                    |
//+------------------------------------------------------------------+
string CMBookSnapshot::VolumeBuyDescription(void)
  {
   return(CMessage::Text(MSG_MBOOK_SNAP_VOLUME_BUY)+": "+(this.VolumeBuyReal()>0 ? ::DoubleToString(this.VolumeBuyReal(),2) : (string)this.VolumeBuy()));
  }
//+------------------------------------------------------------------+
//| Возвращает описание объёма стакана на продажу                    |
//+------------------------------------------------------------------+
string CMBookSnapshot::VolumeSellDescription(void)
  {
   return(CMessage::Text(MSG_MBOOK_SNAP_VOLUME_SELL)+": "+(this.VolumeSellReal()>0 ? ::DoubleToString(this.VolumeSellReal(),2) : (string)this.VolumeSell()));
  }
//+------------------------------------------------------------------+

В обоих методах проверяется значение объёма с повышенной точностью и, если значение больше нуля, то возвращается заголовок + это значение объёма (вещественное), иначе — целочисленное значение.

В класс-коллекцию серий снимков стаканов цен CMBookSeriesCollection в файле \MQL5\Include\DoEasy\Collections\BookSeriesCollection.mqh,
в его публичную секцию добавим методы, возвращающие списки по указанным критериям свойств объектов списка:

public:
//--- Возвращает (1) себя, (2) список-коллекцию серий стаканов цен
   CMBookSeriesCollection *GetObject(void)                              { return &this;               }
   CArrayObj              *GetList(void)                                { return &this.m_list;        }
   //--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   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);  }
//--- Возвращает количество серий стаканов цен в списке
   int                     DataTotal(void)                        const { return this.m_list.Total();    }
//--- Возвращает указатель на объект серий стаканов цен (1) по символу, (2) по индексу в списке

В классе объекта-заявки стакана цен CMQLSignal в файле \MQL5\Include\DoEasy\Objects\MQLSignalBase\MQLSignal.mqh дополним метод PrintShort() входным значением-флагом, указывающим на необходимость вывода дефиса перед описанием объекта:

//--- Выводит в журнал описание свойств объекта (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал краткое описание объекта
   virtual void      PrintShort(const bool dash=false);
//--- Возвращает краткое наименование объекта
   virtual string    Header(const bool shrt=false);

И внесём правки в тело метода:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта                        |
//+------------------------------------------------------------------+
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()
     );
  }
//+------------------------------------------------------------------+

Здесь: в зависимости от переданного значения перед описанием объекта выводится дефис либо не выводится, и в самом конце описания добавлено значение о количестве подписчиков на сигнал.

В самом конце тела класса впишем новый метод, осуществляющий подписку на сигнал, описываемый данным объектом:

//--- Возвращает наименование типа счёта
   string            TradeModeDescription(void);
   
//--- Осуществляет подписку на сигнал
   bool              Subscribe(void)                        { return ::SignalSubscribe(this.ID()); }
   
  };
//+------------------------------------------------------------------+

В конструкторе класса поправим инициализацию переменной, хранящей статус подписки на сигнал:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

Ранее она инициализировалась значением false. Теперь же мы будем её инициализировать результатом сравнения идентификатора объекта-сигнала с идентификатором текущего подписанного сигнала. Если на какой-либо сигнал есть подписка, то этот сигнал будет "текущим подписанным", и он будет иметь идентификатор сигнала из базы Сигналов MQL5.com, которые мы и сравниваем. Если они равны, то значит на этот сигнал оформлена подписка — результат сравнения будет равен true, иначе — false.

Так как сегодня создаём новую коллекцию, то нам необходимо определить для неё свой идентификатор. В файле \MQL5\Include\DoEasy\Defines.mqh впишем идентификатор коллекции сигналов сервиса Сигналы MQL5.com:

//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID          (0x777A)                   // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID           (0x777B)                   // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID           (0x777C)                   // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Идентификатор списка коллекции аккаунтов
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Идентификатор списка коллекции символов
#define COLLECTION_SERIES_ID           (0x777F)                   // Идентификатор списка коллекции таймсерий
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Идентификатор списка коллекции индикаторных буферов
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Идентификатор списка коллекции индикаторов
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Идентификатор списка коллекции индикаторных данных
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Идентификатор списка коллекции тиковых серий
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // Идентификатор списка коллекции серий стаканов цен
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // Идентификатор списка коллекции mql5-сигналов
//--- Параметры данных для файловых операций

Для полноценной работы с коллекцией сигналов сервиса Сигналы MQL5.com нам нужно создать методы для поиска и сортировки по свойствам объектов-сигналов. Для каждой коллекции мы создаём свои методы поиска и сортировки. Все они идентичны друг другу и описывались нами подробно в третьей статье описания библиотеки.

В файле класса CSelect для поиска и сортировки, находящегося в расположении \MQL5\Include\DoEasy\Services\Select.mqh,
подключим файл класса объекта-mql5-сигнала и объявим новые методы для работы с коллекцией объектов-сигналов:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Методы работы с данными mql5-сигналов                            |
//+------------------------------------------------------------------+
   //--- Возвращает список mql5-сигналов, где одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   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);
   //--- Возвращает индекс mql5-сигнала в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   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);
   //--- Возвращает индекс mql5-сигнала в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   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);
//---
  };
//+------------------------------------------------------------------+

За пределами тела класса напишем их реализацию:

//+------------------------------------------------------------------+
//| Методы работы с данными mql5-сигналов                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список mql5-сигналов, где одно из целочисленных       |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает список mql5-сигналов, где одно из вещественных        |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает список mql5-сигналов, где одно из строковых           |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс mql5-сигнала в списке                          |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс mql5-сигнала в списке                          |
//| с максимальным значением вещественного свойства                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс mql5-сигнала в списке                          |
//| с максимальным значением строкового свойства                     |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс mql5-сигнала в списке                          |
//| с минимальным значением целочисленного свойства                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс mql5-сигнала в списке                          |
//| с минимальным значением вещественного свойства                   |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс mql5-сигнала в списке                          |
//| с минимальным значением строкового свойства                      |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Как уже не раз упоминалось, все эти методы, идентичные для каждого из классов объектов библиотеки, рассматривались нами не раз, и можно ещё раз ознакомиться с подробными пояснениями их работы в статье №3.

У нас всё готово для создания класса-коллекции объектов-сигналов сервиса Сигналы MQL5.com.

Класс-коллекция объектов-mql5-сигналов

В каталоге библиотеки\MQL5\Include\DoEasy\Collections\ создадим новый класс CMQLSignalsCollection в файле MQLSignalsCollection.mqh.

К файлу класса подключим все файлы классов, необходимых для его работы:

//+------------------------------------------------------------------+
//|                                         MQLSignalsCollection.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
//+------------------------------------------------------------------+

Класс должен быть унаследован от базового объекта всех объектов библиотеки:

//+------------------------------------------------------------------+
//| Коллекция объектов-mql5-сигналов                                 |
//+------------------------------------------------------------------+
class CMQLSignalsCollection : public CBaseObj
  {
  }

Рассмотрим тело класса целиком, а затем разберём составляющие его методы:

//+------------------------------------------------------------------+
//|                                         MQLSignalsCollection.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
//+------------------------------------------------------------------+
//| Коллекция объектов-mql5-сигналов                                 |
//+------------------------------------------------------------------+
class CMQLSignalsCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // Список объектов-mql5-сигналов
   int                     m_signals_base_total;                     // Количество сигналов в базе сигналов mql5
//--- Осуществляет подписку на сигнал
   bool                    Subscribe(const long signal_id);
//--- Устанавливает флаг разрешения синхронизации без показа диалога подтверждения
   bool                    CurrentSetConfirmationsDisableFlag(const bool flag);
//--- Устанавливает флаг копирования Stop Loss и Take Profit
   bool                    CurrentSetSLTPCopyFlag(const bool flag);
//--- Устанавливает флаг разрешения на копирование сделок по подписке
   bool                    CurrentSetSubscriptionEnabledFlag(const bool flag);
public:
//--- Возвращает (1) себя, (2) список-коллекцию серий стаканов цен
   CMQLSignalsCollection  *GetObject(void)                              { return &this;                  }
   CArrayObj              *GetList(void)                                { return &this.m_list;           }
   //--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   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);  }
//--- Возвращает количество объектов-mql5-сигналов в списке
   int                     DataTotal(void)                        const { return this.m_list.Total();    }
//--- Возвращает указатель на объект-mql5-сигнал (1) по идентификатору, (2) по имени, (3) по индексу в списке
   CMQLSignal             *GetMQLSignal(const long id);
   CMQLSignal             *GetMQLSignal(const string name);
   CMQLSignal             *GetMQLSignal(const int index)                { return this.m_list.At(index);  }

//--- Создаёт список-коллекцию объектов-mql5-сигналов
   bool                    CreateCollection(void);
//--- Обновляет список-коллекцию объектов-mql5-сигналов
   void                    Refresh(const bool messages=true);

//--- Выводит в журнал (1) полное, (2) краткое описание коллекции
   void                    Print(void);
   void                    PrintShort(const bool list=false,const bool paid=true,const bool free=true);
   
//--- Конструктор
                           CMQLSignalsCollection();
//--- Осуществляет подписку на сигнал по (1) идентификатору, (2) имени сигнала
   bool                    SubscribeByID(const long signal_id);
   bool                    SubscribeByName(const string signal_name);
   
//+------------------------------------------------------------------+
//| Методы работы с текущим сигналом, на который оформлена подписка  |
//+------------------------------------------------------------------+
//--- Возвращает флаг разрешения работы с сервисом сигналов
   bool                    ProgramIsAllowed(void)                 { return (bool)::MQLInfoInteger(MQL_SIGNALS_ALLOWED); }
//--- Осуществляет отписку от текущего подписанного сигнала
   bool                    CurrentUnsubscribe(void);

//--- Устанавливает процент для конвертации объема сделки
   bool                    CurrentSetEquityLimit(const double value);
//--- Устанавливает величину проскальзывания, с которой выставляются рыночные ордера при синхронизации позиций и копировании сделок
   bool                    CurrentSetSlippage(const double value);
//--- Устанавливает ограничения по депозиту (в %)
   bool                    CurrentSetDepositPercent(const int value);

//--- Возвращает процент для конвертации объема сделки
   double                  CurrentEquityLimit(void)               { return ::SignalInfoGetDouble(SIGNAL_INFO_EQUITY_LIMIT);                  }
//--- Возвращает величину проскальзывания, с которой выставляются рыночные ордера при синхронизации позиций и копировании сделок
   double                  CurrentSlippage(void)                  { return ::SignalInfoGetDouble(SIGNAL_INFO_SLIPPAGE);                      }
//--- Возвращает флаг разрешения синхронизации без показа диалога подтверждения 
   bool                    CurrentConfirmationsDisableFlag(void)  { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_CONFIRMATIONS_DISABLED); }
//--- Возвращает флаг копирования Stop Loss и Take Profit
   bool                    CurrentSLTPCopyFlag(void)              { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_COPY_SLTP);              }
//--- Возвращает ограничения по депозиту (в %)
   int                     CurrentDepositPercent(void)            { return (int)::SignalInfoGetInteger(SIGNAL_INFO_DEPOSIT_PERCENT);         }
//--- Возвращает флаг разрешения на копирование сделок по подписке
   bool                    CurrentSubscriptionEnabledFlag(void)   { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_SUBSCRIPTION_ENABLED);   }
//--- Возвращает значение ограничения по средствам для сигнала
   double                  CurrentVolumePercent(void)             { return ::SignalInfoGetDouble(SIGNAL_INFO_VOLUME_PERCENT);                }
//--- Возвращает id сигнала
   long                    CurrentID(void)                        { return ::SignalInfoGetInteger(SIGNAL_INFO_ID);                           }
//--- Возвращает флаг согласия с условиями использования сервиса "Сигналы"
   bool                    CurrentTermsAgreeFlag(void)            { return (bool)::SignalInfoGetInteger(SIGNAL_INFO_TERMS_AGREE);            }
//--- Возвращает имя сигнала
   string                  CurrentName(void)                      { return ::SignalInfoGetString(SIGNAL_INFO_NAME);                          }

//--- Устанавливает разрешение синхронизации без показа диалога подтверждения
   bool                    CurrentSetConfirmationsDisableON(void) { return this.CurrentSetConfirmationsDisableFlag(true);                    }
//--- Устанавливает запрет синхронизации без показа диалога подтверждения
   bool                    CurrentSetConfirmationsDisableOFF(void){ return this.CurrentSetConfirmationsDisableFlag(false);                   }
//--- Устанавливает разрешение копирования Stop Loss и Take Profit
   bool                    CurrentSetSLTPCopyON(void)             { return this.CurrentSetSLTPCopyFlag(true);                                }
//--- Устанавливает запрет копирования Stop Loss и Take Profit
   bool                    CurrentSetSLTPCopyOFF(void)            { return this.CurrentSetSLTPCopyFlag(false);                               }
//--- Устанавливает разрешение на копирование сделок по подписке
   bool                    CurrentSetSubscriptionEnableON(void)   { return this.CurrentSetSubscriptionEnabledFlag(true);                     }
//--- Устанавливает запрет на копирование сделок по подписке
   bool                    CurrentSetSubscriptionEnableOFF(void)  { return this.CurrentSetSubscriptionEnabledFlag(false);                    }

//--- Возвращает описание разрешения работы с сигналами для данной запущенной программы
   string                  ProgramIsAllowedDescription(void);
//--- Возвращает описание процента для конвертации объема сделки
   string                  CurrentEquityLimitDescription(void);
//--- Возвращает описание проскальзывания, с которой выставляются рыночные ордера при синхронизации позиций и копировании сделок
   string                  CurrentSlippageDescription(void);
//--- Возвращает описание значения ограничения по средствам для сигнала
   string                  CurrentVolumePercentDescription(void);
//--- Возвращает описание флага разрешения синхронизации без показа диалога подтверждения
   string                  CurrentConfirmationsDisableFlagDescription(void);
//--- Возвращает описание флага копирования Stop Loss и Take Profit
   string                  CurrentSLTPCopyFlagDescription(void);
//--- Возвращает описание ограничения по депозиту (в %)
   string                  CurrentDepositPercentDescription(void);
//--- Возвращает описание флага разрешения на копирование сделок по подписке
   string                  CurrentSubscriptionEnabledFlagDescription(void);
//--- Возвращает описание id сигнала
   string                  CurrentIDDescription(void);
//--- Возвращает описание флага согласия с условиями использования сервиса "Сигналы"
   string                  CurrentTermsAgreeFlagDescription(void);
//--- Возвращает описание имени сигнала
   string                  CurrentNameDescription(void);
//--- Выводит в журнал параметры настроек копирования сигналов
   void                    CurrentSubscriptionParameters(void);
//---
  };
//+------------------------------------------------------------------+

В приватной секции класса расположен объект списка, в котором будем хранить объекты-mql5-сигналы, и вспомогательные переменные и методы.

В публичной секции класса расположены стандартные для всех объектов библиотеки методы работы со списком-коллекцией объектов и два метода для подписки на выбранный сигнал по его идентификатору и имени. Также в публичной секции класса расположены методы для работы с текущим сигналом, на который оформлена подписка.

Разберём реализацию некоторых методов.

В конструкторе класса очищаем список-коллекцию, устанавливаем для него флаг сортированного списка, устанавливаем списку идентификатор коллекции объектов-mql5-сигналов, записываем общее количество сигналов в базе Сигналов MQL5.com и вызываем метод создания коллекции.

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
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();
  }
//+------------------------------------------------------------------+

Так как список сигналов мы не будем автоматически обновлять и контролировать средствами библиотеки, то нам в принципе достаточно создать только метод обновления списка, в котором будут считываться все имеющиеся в базе сигналы и записываться в список-коллекцию. Пользователю самому необходимо будет вызывать метод обновления Refresh() перед получением каких-либо данных из коллекции и при желании иметь свежий список сигналов из базы Сигналов MQL5.com. Но метод создания коллекции у нас тоже будет — лишь в качестве совместимости с набором типичных методов коллекций библиотеки. В самом же методе просто будет очищаться список и вызываться метод обновления коллекции. После первого вызова метода Refresh() из метода создания коллекции список-коллекция будет заполнен, и далее можно уже работать с этим списком. При необходимости обновить список-коллекцию в поисках возможных новых сигналов нужно просто вызвать метод Refresh() перед обращением к списку-коллекции.

Метод создания коллекции:

//+------------------------------------------------------------------+
//| Создаёт список-коллекцию объектов-mql5-сигналов                  |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Здесь: очищаем список-коллекцию сигналов, заполняем список сигналами из базы Сигналов MQL5.com и, если количество сигналов в списке-коллекции больше нуля — т.е., список заполнен, то выводим сообщение об успешном создании списка-коллекции и возвращаем true.
В противном случае возвращаем false.

Метод обновления списка-коллекции:

//+------------------------------------------------------------------+
//| Обновляет список-коллекцию объектов-mql5-сигналов                |
//+------------------------------------------------------------------+
void CMQLSignalsCollection::Refresh(const bool messages=true)
  {
   this.m_signals_base_total=::SignalBaseTotal();
   //--- цикл по всем сигналам в базе сигналов
   for(int i=0;i<this.m_signals_base_total;i++) 
     { 
      //--- Выбираем сигнал из базы сигналов по индексу цикла
      if(!::SignalBaseSelect(i))
         continue;
      //--- Получаем идентификатор текущего сигнала и
      //--- на его основании создаём новый объект-mql5-сигнал
      long id=::SignalBaseGetInteger(SIGNAL_BASE_ID);
      CMQLSignal *signal=new CMQLSignal(id);
      if(signal==NULL)
         continue;
      //--- Устанавливаем списку флаг сортировки по идентификатору сигналов и,
      //--- если такой сигнал уже есть в списке-коллекции -
      //--- удаляем созданный объект и идём на следующую итерацию цикла
      m_list.Sort(SORT_BY_SIGNAL_MQL5_ID);
      if(this.m_list.Search(signal)!=WRONG_VALUE)
        {
         delete signal;
         continue;
        }
      //--- Если новый объект-сигнал не удалось добавить в список-коллекцию -
      //--- удаляем созданный объект и идём на следующую итерацию цикла
      if(!this.m_list.InsertSort(signal))
        {
         delete signal;
         continue;
        }
      //--- Если объект-mql5-сигнал успешно добавлен в коллекцию
      //--- и установлен флаг сообщения о новом объекте в переданных в метод параметрах -
      //--- выводим сообщение о новом найденном сигнале
      else if(messages)
        {
         ::Print(DFUN,CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_NEW),":");
         signal.PrintShort(true);
        }
     } 
  }
//+------------------------------------------------------------------+

Логика метода подробно описана в комментариях к коду. Вкратце: в метод передаётся флаг необходимости сообщить о новом найденном сигнале. Так как метод не очищает список-коллекцию, то только новый найденный сигнал может быть добавлен в него. Если флаг сообщения установлен, то при успешном добавлении нового объекта-сигнала в список, в журнал будет выведено сообщение о новом найденном сигнале.

На данный момент это простейший метод, в котором не реализовано обновление параметров существующих сигналов — их можно будет обновить самостоятельно в своих программах с помощью доступа к объекту-сигналу по его идентификатору и установке новых значений в его свойства. Позже мы добавим автоматическое обновление параметров существующих сигналов по времени и, если класс-коллекция Сигналов MQL5.com будет востребован, то мы создадим для него отправку событий о новых сигналах и об изменении параметров отслеживаемых сигналов.

Метод, возвращающий указатель на объект-mql5-сигнал по идентификатору сигнала:

//+------------------------------------------------------------------+
//| Возвращает указатель на объект-mql5-сигнал по идентификатору     |
//+------------------------------------------------------------------+
CMQLSignal *CMQLSignalsCollection::GetMQLSignal(const long id)
  {
   CArrayObj *list=GetList(SIGNAL_MQL5_PROP_ID,id,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Получаем список объектов-mql5-сигналов по идентификатору сигнала и возвращаем либо единственный объект из полученного списка, либо NULL.

Метод, возвращающий указатель на объект-mql5-сигнал по имени сигнала:

//+------------------------------------------------------------------+
//| Возвращает указатель на объект-mql5-сигнал по имени              |
//+------------------------------------------------------------------+
CMQLSignal *CMQLSignalsCollection::GetMQLSignal(const string name)
  {
   CArrayObj *list=GetList(SIGNAL_MQL5_PROP_NAME,name,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Получаем список объектов-mql5-сигналов по имени сигнала и возвращаем либо единственный объект из полученного списка, либо NULL.

Метод, выводящий в журнал полное описание коллекции:

//+------------------------------------------------------------------+
//| Выводит в журнал полное описание коллекции                       |
//+------------------------------------------------------------------+
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();
     }
  }
//+------------------------------------------------------------------+

Сначала выводится заголовок, а затем, в цикле по списку-коллекции получаем очередной объект-mql5-сигнал и выводим его полное описание.

Метод, выводящий в журнал краткое описание коллекции:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание коллекции                      |
//+------------------------------------------------------------------+
void CMQLSignalsCollection::PrintShort(const bool list=false,const bool paid=true,const bool free=true)
  {
   //--- Выводим в журнал заголовок
   ::Print(CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_MQL5_SIGNAL_COLLECTION),":");
   //--- Если полный список - выводим в журнал краткие описания всех сигналов в коллекции 
   //--- в соответствии с флагами необходимости вывода платных и бесплатных сигналов
   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);
        }
   //--- Если не список сигналов
   else
     {
      //--- Сортируем список по цене сигнала
      this.m_list.Sort(SORT_BY_SIGNAL_MQL5_PRICE);
      //--- Получаем список бесплатных сигналов и их количество
      CArrayObj *list_free=this.GetList(SIGNAL_MQL5_PROP_PRICE,0,EQUAL);
      int num_free=(list_free==NULL ? 0 : list_free.Total());
      //--- Сортируем список по цене сигнала
      this.m_list.Sort(SORT_BY_SIGNAL_MQL5_PRICE);
      //--- Получаем список платных сигналов и их количество
      CArrayObj *list_paid=this.GetList(SIGNAL_MQL5_PROP_PRICE,0,MORE);
      int num_paid=(list_paid==NULL ? 0 : list_paid.Total());
      //--- Распечатываем в журнал количество бесплатных и платных сигналов в коллекции
      ::Print
        (
         "- ",CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_FREE),": ",(string)num_free,
         ", ",CMessage::Text(MSG_MQLSIG_COLLECTION_TEXT_SIGNALS_PAID),": ",(string)num_paid
        );
     }
  }
//+------------------------------------------------------------------+

В этом методе, в зависимости от переданных флагов, распечатываются в журнал  разные сообщения и списки.

Сначала выводится заголовок, затем, если установлен флаг списка, то в журнал распечатываются краткие описания сигналов в коллекции. При этом учитываются флаги платных и бесплатных сигналов. В зависимости от их состояния в журнал выводятся либо все сигналы, либо только платные, либо только бесплатные.
Если выводить нужно описание не списком, то после заголовка выводится общее количество бесплатных и платных сигналов в списке-коллекции.

Методы, осуществляющие подписку на сигнал (приватный метод) и отписку от существующего сигнала (публичный метод):

//+------------------------------------------------------------------+
//| Осуществляет подписку на сигнал                                  |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::Subscribe(const long signal_id)
  {
//--- Если для программы не установлено разрешение на работу с сигналами -
//--- выводим об этом сообщение и рекомендацию проверить настройки программы
   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;
     }
//--- Если подписка на сигнал не удалась - выводим сообщение об ошибкеи возвращаем false
   ::ResetLastError();
   if(!::SignalSubscribe(signal_id))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
//--- Подписка успешна. Выводим сообщение об успешной подписке на сигнал и возвращаем true
   ::Print(CMessage::Text(MSG_SIGNAL_INFO_TEXT_SIGNAL_SUBSCRIBED)," ID ",(string)this.CurrentID()," \"",CurrentName(),"\"");
   return true;
  }
//+------------------------------------------------------------------+
//| Осуществляет отписку от подписанного сигнала                     |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentUnsubscribe(void)
  {
//--- Если для программы не установлено разрешение на работу с сигналами -
//--- выводим об этом сообщение и рекомендацию проверить настройки программы
   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;
     }
//--- Запоминаем идентификатор и имя текущего подписанного сигнала
   ::ResetLastError();
   long id=this.CurrentID();
   string name=this.CurrentName();
//--- Если идентификатор равен нулю (нет подписки) - возвращаем true
   if(id==0)
      return true;
//--- Если отписаться от сигнала не удалось - выводим сообщение об ошибкеи возвращаем false
   if(!::SignalUnsubscribe())
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
//--- Успешно отписались от сигнала. Выводим сообщение об успешной отписке от сигнала и возвращаем true
   ::Print(CMessage::Text(MSG_SIGNAL_INFO_TEXT_SIGNAL_UNSUBSCRIBED)," ID ",(string)id," \"",name,"\"");
   return true;
  }
//+------------------------------------------------------------------+

Логика работы методов подробно прокомментирована в листинге методов.

Публичный метод, осуществляющий подписку на сигнал по идентификатору сигнала:

//+------------------------------------------------------------------+
//| Осуществляет подписку на сигнал по идентификатору                |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+

Здесь: получаем указатель на объект-mql5-сигнал в списке-коллекции по переданному в метод идентификатору и возвращаем результат работы приватного метода подписки на сигнал, рассмотренному нами выше.

Публичный метод, осуществляющий подписку на сигнал по имени сигнала:

//+------------------------------------------------------------------+
//| Осуществляет подписку на сигнал по имени сигнала                 |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+

Здесь: получаем указатель на объект-mql5-сигнал в списке-коллекции по переданному в метод имени сигнала (имя должно быть заранее известно) и возвращаем результат работы приватного метода подписки на сигнал, рассмотренному нами выше.

Методы установки значений копирования торговых сигналов:

//+------------------------------------------------------------------+
//| Устанавливает процент для конвертации объема сделки              |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetEquityLimit(const double value) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetDouble(SIGNAL_INFO_EQUITY_LIMIT,value))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Устанавливает величину проскальзывания, с которой выставляются   |
//| рыночные ордера при синхронизации позиций и копировании сделок   |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetSlippage(const double value) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetDouble(SIGNAL_INFO_SLIPPAGE,value))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг разрешения синхронизации                      |
//| без показа диалога подтверждения                                 |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetConfirmationsDisableFlag(const bool flag) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_CONFIRMATIONS_DISABLED,flag))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг копирования Stop Loss и 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;
  }
//+------------------------------------------------------------------+
//| Устанавливает ограничения по депозиту (в %)                      |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetDepositPercent(const int value) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_DEPOSIT_PERCENT,value))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Устанавливает флаг разрешения на копирование сделок по подписке  |
//+------------------------------------------------------------------+
bool CMQLSignalsCollection::CurrentSetSubscriptionEnabledFlag(const bool flag) 
  {
   ::ResetLastError();
   if(!::SignalInfoSetInteger(SIGNAL_INFO_SUBSCRIPTION_ENABLED,flag))
     {
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Здесь во всех методах используются функции установки значений SignalInfoSetDouble() и SignalInfoSetInteger(). При неудачной установке значения методы выводят описание ошибки и возвращают false. При успешной установке значения методы возвращают true.

Методы, возвращающие описания параметров настройки копирования торговых сигналов:

//+------------------------------------------------------------------+
//| Возвращает описание разрешения работы с сигналами                |
//| для данной запущенной программы                                  |
//+------------------------------------------------------------------+
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))
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание процента для конвертации объема сделки       |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentEquityLimitDescription(void) 
  {
   return CMessage::Text(MSG_SIGNAL_INFO_EQUITY_LIMIT)+": "+::DoubleToString(this.CurrentEquityLimit(),2)+"%";
  }
//+------------------------------------------------------------------+
//| Возвращает описание проскальзывания, с которой выставляются      |
//| рыночные ордера при синхронизации позиций и копировании сделок   |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentSlippageDescription(void) 
  {
   return CMessage::Text(MSG_SIGNAL_INFO_SLIPPAGE)+": "+CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+" * "+::DoubleToString(this.CurrentSlippage(),2);
  }
//+------------------------------------------------------------------+
//| Возвращает описание значения ограничения по средствам для сигнала|
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentVolumePercentDescription(void)
  {
   return CMessage::Text(MSG_SIGNAL_INFO_VOLUME_PERCENT)+": "+::DoubleToString(this.CurrentVolumePercent(),2)+"%";
  }
//+------------------------------------------------------------------+
//| Возвращает описание флага разрешения синхронизации               |
//| без показа диалога подтверждения                                 |
//+------------------------------------------------------------------+
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))
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание флага копирования Stop Loss и 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))
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание ограничения по депозиту (в %)                |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentDepositPercentDescription(void) 
  {
   return CMessage::Text(MSG_SIGNAL_INFO_DEPOSIT_PERCENT)+": "+(string)this.CurrentDepositPercent()+"%";
  }
//+------------------------------------------------------------------+
//| Возвращает описание флага разрешения                             |
//| на копирование сделок по подписке                                |
//+------------------------------------------------------------------+
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))
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание id сигнала                                   |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentIDDescription(void)
  {
   return CMessage::Text(MSG_SIGNAL_INFO_ID)+": "+(this.CurrentID()>0 ? (string)this.CurrentID() : CMessage::Text(MSG_LIB_PROP_EMPTY));
  }
//+------------------------------------------------------------------+
//| Возвращает описание флага согласия                               |
//| с условиями использования сервиса "Сигналы"                      |
//+------------------------------------------------------------------+
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))
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание имени сигнала                                |
//+------------------------------------------------------------------+
string CMQLSignalsCollection::CurrentNameDescription(void)
  {
   return CMessage::Text(MSG_SIGNAL_INFO_NAME)+": "+(this.CurrentName()!="" ? this.CurrentName() : CMessage::Text(MSG_LIB_PROP_EMPTY));
  }
//+------------------------------------------------------------------+

В каждом методе создаётся и возвращается строка с заголовком описания параметра и его текущим значением.

Метод, выводящий в журнал параметры настроек копирования торговых сигналов:

//+------------------------------------------------------------------+
//| Выводит в журнал параметры настроек копирования сигналов         |
//+------------------------------------------------------------------+
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("");
  }  
//+------------------------------------------------------------------+

Сначала выводится заголовок, а затем поочерёдно распечатываются все параметры настройки копирования торговых сигналов, возвращаемые соответствующими вышерассмотренными методами.

На этом создание класса-коллекции объектов-mql5-сигналов завершено.
В последующем мы возможно к нему ещё вернёмся для доработок, но пока нет возможности знать его востребованность, и мы оставим его таким.

Для связи класса-коллекции торговых сигналов с "внешним миром" нам необходимо дописать методы для работы с ним в класс главного объекта библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh.

Подключим файл класса-коллекции торговых сигналов к файлу класса объекта CEngine и объявим объект класса-коллекции mql5-сигналов:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CAccountsCollection  m_accounts;                      // Коллекция аккаунтов
   CSymbolsCollection   m_symbols;                       // Коллекция символов
   CTimeSeriesCollection m_time_series;                  // Коллекция таймсерий
   CBuffersCollection   m_buffers;                       // Коллекция индикаторных буферов
   CIndicatorsCollection m_indicators;                   // Коллекция индикаторов
   CTickSeriesCollection m_tick_series;                  // Коллекция тиковых серий
   CMBookSeriesCollection m_book_series;                 // Коллекция серий стаканов цен
   CMQLSignalsCollection m_signals_mql5;                 // Коллекция сигналов сервиса сигналов mql5.com
   CResourceCollection  m_resource;                      // Список ресурсов
   CTradingControl      m_trading;                       // Объект управления торговлей
   CPause               m_pause;                         // Объект "Пауза"
   CArrayObj            m_list_counters;                 // Список счётчиков таймера

В публичной секции класса объявим и реализуем новые методы для работы с классом-коллекцией снимков стаканов цен и методы для работы с коллекцией торговых сигналов:

//--- Обновляет серию стакана цен указанного символа
   void                 MBookSeriesRefresh(const string symbol,const long time_msc)    { this.m_book_series.Refresh(symbol,time_msc);        }

//--- Возвращает (1) серию стакана цен указанного символа, стакан цен (2) по индексу, (3) по времени в миллисекундах
   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);}
   
//--- Возвращает объём указанного по символу и индексу стакана цен (1) на покупку, (2) на продажу
   long                 MBookVolumeBuy(const string symbol,const int index);
   long                 MBookVolumeSell(const string symbol,const int index);
//--- Возвращает объём с повышенной точностью указанного по символу и индексу стакана цен (1) на покупку, (2) на продажу
   double               MBookVolumeBuyReal(const string symbol,const int index);
   double               MBookVolumeSellReal(const string symbol,const int index);
//--- Возвращает объём указанного по символу и времени в миллисекундах стакана цен (1) на покупку, (2) на продажу
   long                 MBookVolumeBuy(const string symbol,const long time_msc);
   long                 MBookVolumeSell(const string symbol,const long time_msc);
//--- Возвращает объём с повышенной точностью указанного по символу и времени в миллисекундах стакана цен (1) на покупку, (2) на продажу
   double               MBookVolumeBuyReal(const string symbol,const long time_msc);
   double               MBookVolumeSellReal(const string symbol,const long time_msc);
   

//--- Возвращает (1) коллекцию сигналов сервиса сигналов mql5.com, (2) список сигналов из коллекции сигналов сервиса сигналов mql5.com
   CMQLSignalsCollection *GetSignalsMQL5Collection(void)                               { return &this.m_signals_mql5;                        }
   CArrayObj           *GetListSignalsMQL5(void)                                       { return this.m_signals_mql5.GetList();               }
//--- Возвращает список (1) платных, (2) бесплатных сигналов
   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) Создаёт, (2) обновляет коллекцию сигналов сервиса сигналов mql5.com
   bool                 SignalsMQL5Create(void)                                        { return this.m_signals_mql5.CreateCollection();      } 
   void                 SignalsMQL5Refresh(void)                                       { this.m_signals_mql5.Refresh();                      }
//--- Осуществляет подписку на сигнал по (1) идентификатору, (2) имени сигнала
   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);}
//--- Осуществляет отписку от текущего сигнала
   bool                 SignalsMQL5Unsubscribe(void)                                   { return this.m_signals_mql5.CurrentUnsubscribe();    }
//--- Возвращает (1) идентификатор, (2) имя текущего сигнала, на который осуществлена подписка
   long                 SignalsMQL5CurrentID(void)                                     { return this.m_signals_mql5.CurrentID();             }
   string               SignalsMQL5CurrentName(void)                                   { return this.m_signals_mql5.CurrentName();           }
     
//--- Устанавливает процент для конвертации объема сделки
   bool                 SignalsMQL5CurrentSetEquityLimit(const double value)  { return this.m_signals_mql5.CurrentSetEquityLimit(value);     }
//--- Устанавливает величину проскальзывания, с которой выставляются рыночные ордера при синхронизации позиций и копировании сделок
   bool                 SignalsMQL5CurrentSetSlippage(const double value)     { return this.m_signals_mql5.CurrentSetSlippage(value);        }
//--- Устанавливает ограничения по депозиту (в %)
   bool                 SignalsMQL5CurrentSetDepositPercent(const int value)  { return this.m_signals_mql5.CurrentSetDepositPercent(value);  }
//--- Устанавливает разрешение синхронизации без показа диалога подтверждения
   bool                 SignalsMQL5CurrentSetConfirmationsDisableON(void)     { return this.m_signals_mql5.CurrentSetConfirmationsDisableON();}
//--- Устанавливает запрет синхронизации без показа диалога подтверждения
   bool                 SignalsMQL5CurrentSetConfirmationsDisableOFF(void)    { return this.m_signals_mql5.CurrentSetConfirmationsDisableOFF();}
//--- Устанавливает разрешение копирования Stop Loss и Take Profit
   bool                 SignalsMQL5CurrentSetSLTPCopyON(void)                 { return this.m_signals_mql5.CurrentSetSLTPCopyON();           }
//--- Устанавливает запрет копирования Stop Loss и Take Profit
   bool                 SignalsMQL5CurrentSetSLTPCopyOFF(void)                { return this.m_signals_mql5.CurrentSetSLTPCopyOFF();          }
//--- Устанавливает разрешение на копирование сделок по подписке
   bool                 SignalsMQL5CurrentSetSubscriptionEnableON(void)       { return this.m_signals_mql5.CurrentSetSubscriptionEnableON(); }
//--- Устанавливает запрет на копирование сделок по подписке
   bool                 SignalsMQL5CurrentSetSubscriptionEnableOFF(void)      { return this.m_signals_mql5.CurrentSetSubscriptionEnableOFF();}
   
//--- Выводит в журнал (1) полное, (2) краткое описание коллекции, (3) параметры настроек копирования сигналов
   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();}

//--- Возвращает (1) коллекцию буферов, (2) список буферов из коллекции 

Реализованные методы возвращают результат вызова одноимённых методов соответствующих коллекций.

Рассмотрим реализацию методов, возвращающих указанные объёмы указанных снимков стаканов цен:

//+------------------------------------------------------------------+
//| Возвращает объём на покупку стакана цен,                         |
//| указанного по символу и индексу                                  |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeBuy(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeBuy() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на продажу стакана цен,                         |
//| указанного по символу и индексу                                  |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeSell(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeSell() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на покупку с повышенной точностью               |
//| стакана цен, указанного по символу и индексу                     |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeBuyReal(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeBuyReal() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на продажу с повышенной точностью               |
//| стакана цен,указанного по символу и индексу                      |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeSellReal(const string symbol,const int index)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,index);
   return(mbook!=NULL ? mbook.VolumeSellReal() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на покупку стакана цен,                         |
//| указанного по символу и времени в миллисекундах                  |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeBuy(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeBuy() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на продажу стакана цен,                         |
//| указанного по символу и времени в миллисекундах                  |
//+------------------------------------------------------------------+
long CEngine::MBookVolumeSell(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeSell() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на покупку с повышенной точностью               |
//| стакана цен, указанного по символу и времени в миллисекундах     |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeBuyReal(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeBuyReal() : 0);
  }
//+------------------------------------------------------------------+
//| Возвращает объём на продажу с повышенной точностью               |
//| стакана цен,указанного по символу и времени в миллисекундах      |
//+------------------------------------------------------------------+
double CEngine::MBookVolumeSellReal(const string symbol,const long time_msc)
  {
   CMBookSnapshot *mbook=this.GetMBook(symbol,time_msc);
   return(mbook!=NULL ? mbook.VolumeSellReal() : 0);
  }
//+------------------------------------------------------------------+

Здесь всё просто. Логика всех методов идентична: сначала получаем из списка-коллекции объект-снимок стакана цен по символу и индексу или времени в миллисекундах с помощью ранее реализованных методов GetMBook(), затем возвращаем соответствующий методу объём стакана цен из полученного объекта-снимка стакана цен, либо ноль, если объект получить не удалось.

Это все доработки и изменения на сегодня.

Тестирование

Протестируем создание коллекции Сигналов MQL5.com. Тестировать будем так:
Получим полный список из базы Сигналов, выведем список только бесплатных сигналов, затем найдём в этом списке самый прибыльный сигнал и подпишемся на него. При удачном оформлении подписки выведем параметры подписанного сигнала и параметры копирования торговых сигналов, установленные при подписке на сигнал. На следующем тике отпишемся от подписанного сигнала.

Для тестирования возьмём советник из прошлой статьи
и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part66\ под новым именем TestDoEasyPart66.mq5.

В список входных параметров советника добавим параметр настройки, позволяющий выбрать необходимость работы с сервисом Сигналы MQL5.com в советнике:

//--- 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

В прошлой статье мы все проверки работы с сигналами делали в обработчике OnInit() советника. Сегодня будем работать уже в OnTick().
Поэтому удалим уже ненужный прошлый тестовый блок кода из обработчика OnInit():

   CArrayObj *list=new CArrayObj();
   if(list!=NULL)
     {
      //--- запрашиваем общее количество сигналов в базе 
      int total=SignalBaseTotal(); 
      //--- цикл по всем сигналам 
      for(int i=0;i<total;i++) 
        { 
         //--- выбираем сигнал для дальнейшей работы 
         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;
           }
        } 
      //--- выводим все прибыльные бесплатные сигналы с ненулевым количеством подписчиков
      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;
         //--- Самый первый подходящий сигнал распечатываем полностью в журнале
         if(!done)
           {
            signal.Print();
            done=true;
           }
         //--- Для остальных выводим краткие описания
         else
            signal.PrintShort();
        }
      delete list;
     }
     
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

В обработчик OnTick() впишем новый тестовый блок кода, в котором выполняются все условия теста, оговорённые нами в самом начале этого раздела:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаление графических объектов советника по префиксу имени объектов
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Деинициализация библиотеки
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Обработка события NewTick в библиотеке
   engine.OnTick(rates_data);

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      engine.EventsHandling();      // Работа с событиями
     }

//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
   
//--- Поиск доступных сигналов в базе и проверка возможности подписаться на сигнал по его имени
   static bool done=false;
   //--- Если первый запуск и работа с сигналами разрешена в пользовательских настройках советника
   if(InpUseMqlSignals && !done)
     {
      //--- Выводим в журнал список всех бесплатных сигналов
      Print("");
      engine.GetSignalsMQL5Collection().PrintShort(true,false,true);
      //--- Получаем список только бесплатных сигналов
      CArrayObj *list=engine.GetListSignalsMQL5Free();
      //--- Если список получен
      if(list!=NULL)
        {
         //--- Находим в списке сигнал с максимальным приростом в процентах
         int index_max_gain=CSelect::FindMQLSignalMax(list,SIGNAL_MQL5_PROP_GAIN);
         CMQLSignal *signal_max_gain=list.At(index_max_gain);
         //--- Если сигнал найден
         if(signal_max_gain!=NULL)
           {
            //--- Выведем в журнал полное описание сигнала
            signal_max_gain.Print();
            //--- Если удалось подписаться на сигнал
            if(engine.SignalsMQL5Subscribe(signal_max_gain.ID()))
              {
               //--- Устанавливаем параметры подписки
               //--- Разрешаем копирование сделок по подписке
               engine.SignalsMQL5CurrentSetSubscriptionEnableON();
               //--- Устанавливаем запрет синхронизации без показа диалога подтверждения
               engine.SignalsMQL5CurrentSetConfirmationsDisableOFF();
               //--- Устанавливаем разрешение копирования Stop Loss и Take Profit
               engine.SignalsMQL5CurrentSetSLTPCopyON();
               //--- Устанавливаем величину проскальзывания, с которой выставляются рыночные ордера при синхронизации позиций и копировании сделок
               engine.SignalsMQL5CurrentSetSlippage(2);
               //--- Устанавливаем процент для конвертации объема сделки
               engine.SignalsMQL5CurrentSetEquityLimit(50);
               //--- Устанавливаем ограничения по депозиту (в %)
               engine.SignalsMQL5CurrentSetDepositPercent(70);
               //--- Выводим в журнал параметры подписки
               engine.SignalsMQL5CurrentSubscriptionParameters();
              }
           }
        }
      done=true;
      return;
     }
   //--- Если есть подписка на сигнал - отписываемся от него
   if(engine.SignalsMQL5CurrentID()>0)
     {
      engine.SignalsMQL5Unsubscribe();
     }
//---
  }
//+------------------------------------------------------------------+

Весь новый блок кода достаточно подробно прокомментирован. Если по нему возникли какие-либо вопросы, то их можно задать в обсуждении к статье.

В функции инициализации библиотеки OnInitDoEasy() впишем блок кода, в котором создаётся список-коллекция торговых сигналов и устанавливается флаг разрешения копирования торговых сигналов по подписке:

//--- Создание тиковых серий всех используемых символов
   engine.TickSeriesCreateAll();

//--- Проверка созданных тиковых серий - выводим в журнал описания всех созданных тиковых серий
   engine.GetTickSeriesCollection().Print();

//--- Проверка созданных серий стаканов цен - выводим в журнал описания всех созданных серий стаканов цен
   engine.GetMBookSeriesCollection().Print();
   
//--- Создание коллекции сигналов сервиса сигналов mql5.com
//--- Если работа с сигналами разрешена и коллекция сигналов создана
   if(InpUseMqlSignals && engine.SignalsMQL5Create())
     {
      //--- Устанавливаем разрешение на копирование сделок по подписке
      engine.SignalsMQL5CurrentSetSubscriptionEnableON();
      //--- Проверка созданных объектов-mql5-сигналов сервиса сигналов - выводим в журнал короткое описание коллекции
      engine.SignalsMQL5PrintShort();
     }
//--- Если работа с сигналами не разрешена или не удалось создать коллекцию сигналов - 
//--- устанавливаем запрет на копирование сделок по подписке
   else
      engine.SignalsMQL5CurrentSetSubscriptionEnableOFF();

//--- Создание тестовых файлов ресурсов

Скомпилируем советник и запустим его на графике символа, предварительно задав в настройках работу на текущем символе/таймфрейме и установив флаг необходимости работы с торговыми сигналами сервиса Сигналы MQL5.com:


Во вкладке "Общие" окна настроек советника обязательно нужно установить галочку на пункте "Разрешить изменение настроек Сигналов":


Без этого советнику будет запрещено работать с сервисом Сигналов MQL5.com.

После запуска советника, в журнал будет выведено сообщение об успешном создании коллекции сигналов и её краткое описание:

Коллекция сигналов сервиса сигналов mql5.com создана успешно
Коллекция сигналов сервиса сигналов mql5.com:
- Бесплатных сигналов: 195, Платных сигналов: 805

Далее будет выведен полный список бесплатных сигналов. Так как их много, то приведу в пример только часть:

Коллекция сигналов сервиса сигналов mql5.com:
- Сигнал "GBPUSD EXPERT 23233". Логин автора: mbt_trader, ID 919099, Прирост: 3.30, Просадка: 11.92, Цена: 0.00, Подписчиков: 0
- Сигнал "Willian". Логин автора: Desg, ID 917396, Прирост: 12.69, Просадка: 15.50, Цена: 0.00, Подписчиков: 0
- Сигнал "VahidVHZ1366". Логин автора: 39085485, ID 921427, Прирост: 34.36, Просадка: 12.84, Цена: 0.00, Подписчиков: 0
- Сигнал "Vikings". Логин автора: Myxx, ID 921040, Прирост: 7.05, Просадка: 2.22, Цена: 0.00, Подписчиков: 2
- Сигнал "VantageFX Sunphone Dragon". Логин автора: sunphone, ID 916421, Прирост: 537.89, Просадка: 39.06, Цена: 0.00, Подписчиков: 21
- Сигнал "Forex money maker free". Логин автора: Yggdrasills, ID 916328, Прирост: 44.66, Просадка: 61.15, Цена: 0.00, Подписчиков: 0
...
...
...
- Сигнал "Nine Pairs ST". Логин автора: ebi.pilehvar, ID 935603, Прирост: 25.92, Просадка: 26.41, Цена: 0.00, Подписчиков: 2
- Сигнал "FBS140". Логин автора: mohammeeeedali, ID 949720, Прирост: 42.14, Просадка: 23.11, Цена: 0.00, Подписчиков: 2
- Сигнал "StopTheFourthAddition". Логин автора: pinheirodps, ID 934990, Прирост: 41.78, Просадка: 28.03, Цена: 0.00, Подписчиков: 2
- Сигнал "The art of Forex". Логин автора: Myxx, ID 801685, Прирост: 196.39, Просадка: 40.95, Цена: 0.00, Подписчиков: 59
- Сигнал "Bongsanmaskdance1803". Логин автора: kim25801863, ID 936062, Прирост: 12.53, Просадка: 10.31, Цена: 0.00, Подписчиков: 0
- Сигнал "Prospector Scalper EA". Логин автора: robots4forex, ID 435626, Прирост: 334.76, Просадка: 43.93, Цена: 0.00, Подписчиков: 215
- Сигнал "ADS MT5". Логин автора: vluxus, ID 478235, Прирост: 295.68, Просадка: 40.26, Цена: 0.00, Подписчиков: 92

Затем нам будет выведено полное описание найденного сигнала с максимальным приростом в процентах и сообщение об успешной подписке на него:

============= Начало списка параметров (Сигнал сервиса сигналов mql5.com) =============
Тип счета: Демонстрационный счёт
Дата публикации: 2020.07.02 16:29
Дата начала мониторинга: 2020.07.02 16:29
Дата последнего обновления торговой статистики: 2021.03.07 15:11
ID: 784584
Плечо торгового счета: 33
Результат торговли в пипсах: -19248988
Позиция в рейтинге сигналов: 872
Количество подписчиков: 6
Количество трейдов: 1825
Состояние подписки счёта на этот сигнал: Нет
------
Баланс счета: 12061.98
Средства на счете: 12590.32
Прирост счета в процентах: 1115.93
Максимальная просадка: 70.62
Цена подписки на сигнал: 0.00
Значение ROI (Return on Investment) сигнала в %: 1169.19
------
Логин автора: "tradewai.com"
Наименование брокера (компании): "MetaQuotes Software Corp."
Сервер брокера: "MetaQuotes-Demo"
Имя: "Tradewai"
Валюта счета: "USD"
============= Конец списка параметров (Сигнал сервиса сигналов mql5.com) =============

Осуществлена подписка на сигнал ID 784584 "Tradewai"

После этого будут выведены параметры подписки:

============= Параметры копирования сигнала =============
Разрешение на работу с сигналами для программы: Да
Согласие с условиями использования сервиса "Сигналы": Да
Разрешение на копирование сделок по подписке: Да
Разрешение синхронизации без показа диалога подтверждения: Нет
Копирование Stop Loss и Take Profit: Да
Проскальзывание, с которым выставляются рыночные ордера при синхронизации позиций и копировании сделок: Спред * 2.00
Процент для конвертации объема сделки: 50.00%
Ограничение по депозиту: 70%
Ограничение по средствам для сигнала: 7.00%
Идентификатор сигнала: 784584
Имя сигнала: Tradewai

а при наступлении следующего тика мы получим сообщение об успешной отписке от сигнала.

Осуществлена отписка от сигнала ID 784584 "Tradewai"


Что дальше

В следующей статье начнём разрабатывать функционал библиотеки для работы с чартами — графиками символов.

Ниже прикреплены все файлы текущей версии библиотеки и файл тестового советника для MQL5. Их можно скачать и протестировать всё самостоятельно.
Хочу отметить, что тест работы с сигналами в тестовом советнике является именно тестом работы с сигналами, и не пригоден для его использования в том виде, в котором он реализован в прилагаемом советнике. Прошу учитывать, что это всего лишь пример без какой-либо полезной нагрузки.
Приведённый в советнике пример даёт лишь понимание общего направления действий при реализации собственных разработок на основе библиотеки и её классов для работы с сервисом Сигналов MQL5.com.

При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

Работа с ценами в библиотеке DoEasy (Часть 62): Реалтайм-обновление тиковых серий, подготовка к работе со стаканом цен
Работа с ценами в библиотеке DoEasy (Часть 63): Стакан цен, класс абстрактной заявки стакана цен
Работа с ценами в библиотеке DoEasy (Часть 64): Стакан цен, классы объекта-снимка и объекта-серии снимков стакана цен
Работа с ценами и Сигналами в библиотеке DoEasy (Часть 65): Коллекция стаканов и класс для работы с Сигналами MQL5.com