English 中文 Español Deutsch 日本語 Português
Прочие классы в библиотеке DoEasy (Часть 66): Класс-коллекция Сигналов MQL5.com

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

MetaTrader 5Примеры | 12 марта 2021, 08:19
1 574 6
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

В прошлой статье мы создали класс объекта-сигнала, представляющего собой один сигнал из множества, транслируемых в сервисе Сигналы 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

Прикрепленные файлы |
MQL5.zip (3957.77 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Artyom Trishkin
Artyom Trishkin | 14 мар. 2021 в 08:45
Maxim Dmitrievsky:

Почему таг стоит "торговые системы" в разделе статей? здесь же нет ТС

Весь раздел "торговые системы" забит вашими статьями, по итогу

Потому, что библиотека создаётся в частности и для создания торговых систем.

Maxim Dmitrievsky
Maxim Dmitrievsky | 14 мар. 2021 в 14:41
Artyom Trishkin:

Потому, что библиотека создаётся в частности и для создания торговых систем.

в этом есть некоторое логическое противоречие

Artyom Trishkin
Artyom Trishkin | 14 мар. 2021 в 18:03
Maxim Dmitrievsky:

в этом есть некоторое логическое противоречие

Да. Равно, как в задачке про курицу и яйцо.
Francisco Carlos Sobral Ribeiro
Francisco Carlos Sobral Ribeiro | 16 июн. 2023 в 02:34

Boa noite!

Saudações aqui do Brasil.


Estou tendo impedimento na hora de compilar o código e como meu conhecimento de mql5 é muito básico, resta pedir sua ajuda para superar os problemas na compilação...não sei resolver.

Quero aproveitar e parabenizá-lo pelo trabalho brilhante, seus códigos me ajudam muito.

Artyom Trishkin
Artyom Trishkin | 16 июн. 2023 в 08:20
Francisco Carlos Sobral Ribeiro #:

Boa noite!

Saudações aqui do Brasil.


Estou tendo impedimento na hora de compilar o código e como meu conhecimento de mql5 é muito básico, resta pedir sua ajuda para superar os problemas na compilação...não sei resolver.

Quero aproveitar e parabenizá-lo pelo trabalho brilhante, seus códigos me ajudam muito.

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

В строках 84 - 89 файла Trading.mqh нужно сделать такие изменения:


In the Trading.mqh file, some methods need to be moved to a protected section so that they can be accessed from derived classes. Now they are in the private section of the class. This mistake was made by me inadvertently, but the old compiler missed it. After updating the terminal, this error began to be detected.

In lines 84 - 89 of the Trading.mqh file, you need to make the following changes:

//--- Возвращает направление ордера по типу операции
   ENUM_ORDER_TYPE      DirectionByActionType(const ENUM_ACTION_TYPE action)  const;
//--- Устанавливает торговому объекту нужный звук
   void                 SetSoundByMode(const ENUM_MODE_SET_SOUND mode,const ENUM_ORDER_TYPE action,const string sound,CTradeObj *trade_obj);

protected:
//--- Устанавливает цены торгового запроса
   template <typename PR,typename SL,typename TP,typename PL> 
   bool                 SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj);

private:
//--- Возвращает флаг проверки разрешённости по дистанции (1) StopLoss, (2) TakeProfit, (3) цены установки ордера от цены по уровню StopLevel
   bool                 CheckStopLossByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double sl,const CSymbol *symbol_obj);
   bool                 CheckTakeProfitByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const double tp,const CSymbol *symbol_obj);
   bool                 CheckPriceByStopLevel(const ENUM_ORDER_TYPE order_type,const double price,const CSymbol *symbol_obj,const double limit=0);


и в строках 155 - 181 сделать аналогичные правки:


and in lines 155 - 181 make similar edits:
//--- Возвращает метод обработки ошибки
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(const uint result_code);
//--- Корректировка ошибок
   ENUM_ERROR_CODE_PROCESSING_METHOD   RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj);

protected:
//--- (1) Открывает позицию, (2) устанавливает отложенный ордер
   template<typename SL,typename TP> 
   bool                 OpenPosition(const ENUM_POSITION_TYPE type,
                                    const double volume,
                                    const string symbol,
                                    const ulong magic=ULONG_MAX,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const string comment=NULL,
                                    const ulong deviation=ULONG_MAX,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
   template<typename PR,typename PL,typename SL,typename TP>
   bool                 PlaceOrder( const ENUM_ORDER_TYPE order_type,
                                    const double volume,
                                    const string symbol,
                                    const PR price,
                                    const PL price_limit=0,
                                    const SL sl=0,
                                    const TP tp=0,
                                    const ulong magic=ULONG_MAX,
                                    const string comment=NULL,
                                    const datetime expiration=0,
                                    const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
                                    const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);

private:
//--- Возвращает индекс объекта-запроса в списке по (1) идентификатору,
//--- (2) тикету ордера, (3) тикету позиции в запросе
   int                  GetIndexPendingRequestByID(const uchar id);
   int                  GetIndexPendingRequestByOrder(const ulong ticket);
   int                  GetIndexPendingRequestByPosition(const ulong ticket);

public:

После этого всё будет компилироваться.

Исправленный файл прилагаю к этому посту.


After that, everything will compile.

The corrected file is attached to this post.

Брутфорс-подход к поиску закономерностей (Часть IV): Минимальная функциональность Брутфорс-подход к поиску закономерностей (Часть IV): Минимальная функциональность
В данной статье я покажу улучшенную версию брутфорса, основанную на целях поставленных в предыдущей статье, и постараюсь наиболее широко осветить эту тему, используя советники и настройки добытые с помощью данного метода. Также дам сообществу попробовать новую версию программы.
Многослойный перцептрон и алгоритм обратного распространения ошибки Многослойный перцептрон и алгоритм обратного распространения ошибки
В последнее время, с ростом популярности этих двух методов появилось много библиотек на Matlab, R, Python, C ++ и т.д., которые получают на вход обучающий набор и автоматически создают соответствующую нейронную сеть для вашей задачи. Мы постараемся понять, как работает базовый тип нейронной сети — перцептрон с одним нейроном и многослойный перцептрон — замечательный алгоритм, который отвечает за обучение сети (градиентный спуск и обратное распространение). Эти сетевые модели будут основой для более сложных моделей, существующих на сегодняшний день.
Прочие классы в библиотеке DoEasy (Часть 67): Класс объекта-чарта Прочие классы в библиотеке DoEasy (Часть 67): Класс объекта-чарта
В статье создадим класс объекта-чарта (одного графика торгового инструмента) и доработаем класс-коллекцию объектов mql5-сигнал так, чтобы каждый объект-сигнал, хранящийся в коллекции при обновлении списка также обновлял все свои параметры.
Нейросети — это просто (Часть 12): Dropout Нейросети — это просто (Часть 12): Dropout
Продвигаясь дальше в изучении нейронных сетей, наверное, стоит немного уделить внимания методам повышения их сходимости при обучении. Существует несколько таких методов. В этой статье предлагаю рассмотреть один из них — Dropout.