DoEasy 函数库中的其他类(第六十六部分):MQL5.com 信号集合类

Artyom Trishkin | 31 五月, 2021

内容


概述

在上一篇文章中,我创建了信号对象类,它是 MQL5.com 信号服务中众多广播信号中的一个。
今天,我将在信号数据库中创建可用的信号集合类,可按指定的所需信号索引调用 SignalBaseSelect() 函数获得。
该集合能将数据库中存在的所有信号存储到列表,从而便于搜索和排序。 我们能够依据其各种属性来检测并返回信号列表。 举例来说,我们可以仅获取免费/付费信号的列表,并按参数之一对它们进行排序(例如信号盈利能力),或者立即得到列表中参数等于、大于或小于指定值的信号索引。 对所需信号的了解令我们能够在集合中快速找到它。
在集合类中,实现订阅选择的信号,或从当前帐户上退订信号的能力。

除了操控 MQL5.com 信号服务对象之外,我们还能通过添加额外的属性来改进市场深度(DOM)快照对象类,从而令我们能够在创建 DOM 快照对象时立即计算买卖订单的交易量。 这会令最终用户务须在操控 DOM 时进行额外的计算。 我们会立即知道每个 DOM 快照的总买卖量。 这意味着依据 DOM 及其交易量创建策略时,我们不必在 DOM 中另外搜索买卖订单及其后续订单的交易量总和。


改进库类

与往常一样,我们立即将所有新函数库消息添加到 \MQL5\Include\DoEasy\Data.mqh
首先加入新的消息索引:

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

...

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

接下来,加入新索引对应的消息

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

在日志中显示消息(尤其是调试消息)时,我们通常会在消息本身的开头指明发送消息的方法名称。 在第十九篇文章中,我已开发了类库消息。 不过,我目前仅用它们来指定消息索引,消息会利用标准 Print() 函数显示在日志之中。 鉴于我打算开始一个操控图形新的函数库部分,我将逐渐转向利用这个类来显示函数库消息。 今天,我将在其内添加 ToLog() 方法重载,从而我们可以额外将消息“来源”传递给类的方法,或调用该方法的程序函数。 因此,我们将有 ToLog() 方法的两个变体,如此我们即可显示带有指定其源函数或方法的消息,而无需指定它。

打开 \MQL5\Include\DoEasy\Services\Message.mqh,并在其内加入重载方法的声明:

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

我们在类的主体之外编写它的实现

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

不同于调用方法的第一种形式它的第二种形式还有另一个输入,其中包含调用 ToLog() 方法的方法或函数名称,在日志中会将其显示在消息之前

我们将在后续文章中回到这个类,并在把显示所有库类消息转移至该方法时对其进行改进。

我们来改进 \MQL5\Include\DoEasy\Objects\Book\MarketBookSnapshot.mqh 中的 CMBookSnapshot 类。

在类的私密部分,添加类成员变量,存储 DOM 快照的总买卖量

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

在类中简化访问 DOM 快照对象属性的方法部分,加入返回这些新加类属性的方法,和显示其描述的方法

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

当创建一个新的 DOM 快照对象时,我们在一个循环中能看到它的所有订单,为这些订单创建对象并将它们发送到列表中。 现在我们需要在类构造函数中考虑订单类型,并根据当前订单类型,将订单交易量立即分别累加到存储买入或卖出订单总量的变量之中。 因此,在创建 DOM 快照对象后,每个变量会立即存储买入亦或卖出订单的总交易量。

我们把这些改进加入类的参数型构造函数当中:

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

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

首先,存储所有 DOM 快照买卖订单总交易量的变量进行初始化。 接着,在循环体中遍历所有 DOM 订单,根据订单类型,将当前订单交易量添加到存储总交易量的相应变量之中。 因此,遍历所有 DOM 快照订单的循环完毕后,总买卖交易量会存储在所有变量当中。

该方法在日志中显示对象简述,和所有对象属性以及显示总买卖交易量

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

在类的主体之外,实现 两个新的方法返回 DOM 买卖交易量的说明

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

两种方法均会检查增加的准确交易量。 如果超过零,则返回:标题 + 交易量(实数型),否则返回 — 整数型。

在 \MQL5\Include\DoEasy\Collections\BookSeriesCollection.mqh 里的 CMBookSeriesCollection DOM 快照序列集合类,在其公开部分,定义方法依据指定的列表属性作为条件,返回符合的列表

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

在 \MQL5\Include\DoEasy\Objects\MQLSignalBase\MQLSignal.mqh 里的 CMQLSignal DOM 订单对象类中,补充带有标志值的 PrintShort() 方法,表示需要在对象说明之前显示连字符:

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

我们在方法主体中进行修改:

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

根据所传递的数值,在对象说明之前显示或不显示连字符。 订阅者数量 则于描述的最后设置。

在类主体的最后,添加依据对象描述执行信号订阅的新方法

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

在类构造函数中,修复存储信号订阅状态的变量的初始化

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

以前,它是以 false 进行初始化的。 现在,我们打算用 信号对象 ID当前订阅的有效信号 ID 的比较结果来初始化它。 如果信号已经激活订阅,其 ID 将是来自 MQL5.com 信号数据库“当前已订阅”信号中之一。 如果它们相等,这意味着该信号的订阅处于激活状态 — 比较结果相等则为 true,否则 — false

由于我正在这里开发一个新的集合,我需要为它定义一个自定义 ID。 在 \MQL5\Include\DoEasy\Defines.mqh 里,加入 MQL5.com 信号服务的信号集合 ID:

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

为了能够操控 MQL5.com 信号服务的信号集合,我们需要创建依据信号对象属性进行搜索和排序的方法。 为每个集合创建单独的搜索和排序方法。 所有方法都相同。 在第三篇文章中曾详细讲述过它们。

在 \MQL5\Include\DoEasy\Services\Select.mqh 文件里的 CSelect 类,包含 MQL5 信号对象类文件,并声明操控信号对象集合的新方法:

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

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

我们在类主体之外编写它们的实现:

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

正如已经提过的,所有这些方法(对于每个函数库对象类都是相同的)已被研究过多次了。 参看 第三篇文章获取更详细内容。

现在一切准备就绪,可以开发 MQL5.com 信号服务的信号对象集合类了。

MQL5 信号对象的集合类

在 \MQL5\Include\DoEasy\Collections\ 函数库文件夹之下,于 MQLSignalsCollection.mqh 文件里创建新类 CMQLSignalsCollection。

在类文件里,包含其操作所需的所有类文件:

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

该类应从所有函数库对象的基准对象中派生:

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

我们来看看类主体,并分析一下它包含的方法:

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

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

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

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

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

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

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

在类的私密部分提供存储 MQL5 信号对象的列表对象,以及辅助变量和方法。

在类的公开部分,提供操控对象集合列表的标准方法,以及两种依据 ID 和名称选择信号并订阅的方法。 类的公开部分还提供了操控处于订阅激活状态的当前信号的方法

我们来看看一些方法的实现。

在类的构造方法里清除集合列表为其设置已排序列表标志为列表设置 MQL5 信号对象集合的 ID写下 MQL5.com 信号数据库中的信号总数,并调用集合创建方法

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

由于我们不打算自动更新信号列表,也不会托管给函数库,故列表更新方法就足够了。 读取数据库中出现的所有信号,并在方法中将其发送集合列表。 如果用户希望从 MQL5.com 信号数据库中得到更新的信号列表,则从集合中接收任何数据之前,他们必须自己调用 Refresh() 更新方法。 不过,无论如何我们的集合创建方法都会提供与典型函数库一套集合方法的兼容性。 该方法本身将简单地清除列表,并调用集合更新方法。 从集合创建方法第一次调用 Refresh() 方法之后,集合列表就已被填充完毕,可以处理了。 如果为了搜索可能的新信号应该更新集合列表,只需在访问集合列表之前调用 Refresh() 方法。

集合创建方法:

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

此处,我们清除信号集合列表,并用来自 MQL5.com 信号数据库的信号填充如果集合列表中的信号数量超过零(列表已满),显示集合列表创建成功的消息,并返回 true
否则,返回 false

更新集合列表的方法:

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

该方法的逻辑在代码注释中已有详述。 简言之:该方法接收标志,指示检测到新信号之后是否需要通知。 由于该方法不会清除集合列表,因此只能将新发现的信号添加到其中。 如果消息标志已设置,则日志里会显示有关新检测到信号,并成功添加到列表中的消息。

目前,这是最简单的方法,务须提供更新现有信号的参数 — 您能够在您的程序中自行更新它们,或依据其 ID 访问信号对象,并为其属性设置新值。 稍后,我将添加按时间参数自动更新现有信号。 如果 MQL5.com 信号集合类是刚需,我将实现发送有关新信号的事件,并更改跟踪信号的参数。

该方法依据信号 ID 返回指向 MQL5 信号对象的指针:

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

依据信号 ID 获取 MQL5 信号对象列表, 并从得到的列表中返回单一对象,或 NULL

该方法依据信号名称返回指向 MQL5 信号对象的指针:

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

依据信号名称获取 MQL5 信号对象列表,并从得到的列表中返回单一对象,或者返回 NULL

该方法将完整的集合列表反馈到日志:

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

标题会首先显示。 然后,循环遍历集合列表获取下一个 MQL 信号对象,并显示其完整描述

该方法返回显示在日志里的集合列表简要:

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

根据所传递的标志,该方法在日志中显示各种消息和列表。

标题会排第一。 如果列表标志已设置,则日志会显示来自集合信号的简述。 会考虑付费信号免费信号的标志。 取决于它们的状态,日志显示所有信号,或仅显示付费信号,或仅显示免费信号。
如果不显示列表说明,则标题后面是集合列表中免费和付费信号的总数。

该方法订阅信号(私密方法),和取消订阅(公开方法):

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

该方法的逻辑已在方法清单里有详述。

依据信号 ID 订阅信号的公开方法:

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

此处,我们依据传递给方法的 ID 获取指向集合列表中 MQL5 信号对象的指针,并返回上述信号订阅私密方法的结果

依据信号名称订阅信号的公开方法:

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

此处,我们依据传递给方法的信号名称获得指向集合列表中 MQL5 信号对象的指针(名称应事先知道),并返回上述信号订阅私密方法的结果

该方法设置信号跟单值:

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

在所有方法里用到的设置数值的函数 SignalInfoSetDouble()SignalInfoSetInteger()。 如果数值设置未成功,则这些方法会显示错误描述,并返回 false。 如果设置成功,方法返回 true

该方法返回所设置信号跟单参数的说明:

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

在每个方法中创建含有参数说明标题及其当前值的字符串。

该方法在日志中显示所设置的信号跟单参数:

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

首先显示标题,然后逐一显示信号跟单的所有参数。 参数由上面研究过的相应方法返回。

MQL5 信号对象集合类的创建至此完毕。
我也许在以后会改进它。 不过,我将暂时保留这种方式,直到我能判定其需求是否很强烈。

为了将交易信号集合类与“外部世界”连接起来,我们需要在 \MQL5\Include\DoEasy\Engine.mqh 函数库主对象类 CEngine 中开发完成操控它们的方法。

将交易信号集合类的文件集中到 CEngine 对象类文件之中,并声明 MQL5 信号集合类对象

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

在该类的公开部分,声明实现操控 DOM 快照集合类的新方法,和操控交易信号集合的方法

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

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

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

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

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

所实现的方法返回调用同名集合相应方法的结果。

我们看一下实现返回指定 DOM 快照的指定交易量的方法:

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

此处的一切都很简单。 所有方法背后的逻辑都是相同的:首先,我们利用先前实现的 GetMBook() 方法依据品种和索引,或以毫秒为单位的时间,从集合列表中获取 DOM 快照对象,然后从所获 DOM 快照对象方法返回对应 DOM 的交易量,如果获取对象失败则返回零值。

这些都是至今的改进和变化。

测试

我们来测试 MQL5.com 信号 集合的创建。 测试应如下执行:
从信号数据库中获取完整列表,仅显示免费信号列表,在列表中找到最有盈利潜力的信号,并订阅它。 如果订阅成功,我们将显示当前信号的参数,和订阅时设置的信号跟单参数。 在下一次即时报价时取消订阅当前信号。

为了执行测试,我将取用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part66\ 中,命名为 TestDoEasyPart66.mq5

在 EA 的输入列表里含有允许用户选择操控 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

在之前的文章中,所有操控信号的检查都是在 EA 的OnInit() 处理程序中完成的。 今天,我们将在 OnTick() 中操作。
因此,我们从 OnInit() 处理程序中删除不必要的测试代码模块

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

OnTick() 处理程序定义了一个新的测试代码模块,满足本章节开头讲述的所有测试条件:

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

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

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

整个新代码模块已有详细注释。 如有任何疑问,请在评论中提问。

在 OnInitDoEasy() 函数库初始化函数中,添加代码模块,在其中创建交易信号集合列表,并设置启用订阅信号跟单的标志:

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

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

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

//--- Create resource text files

编译 EA 并在交易品种图表上启动它,同时初步设置在当前交易品种/时间帧上操作,并激活操控 MQL5.com 信号服务的交易信号的标志:


在 EA 设置窗口的通用选项卡中,选中“允许修改信号设置”:


否则,EA 将无法操控 MQL5.com 信号。

启动 EA 后,日志会显示有关创建信号集合成功的消息,及其简述:

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

然后显示免费信号的完整列表。 由于数量众多,我仅展示其中的一部分作为示例:

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

接着,我们得到检测到的信号的完整描述,以 % 为单位的最大涨幅,和成功订阅消息

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

Subscribed to signal ID 784584 "Tradewai"

之后显示订阅参数:

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

在下一次即时报价时,我们获得有关成功取消订阅信号的消息。

Unsubscribed from the signal ID 784584 "Tradewai"


下一步是什么?

在下一篇文章中,我将开始开发操控品种图表的函数库功能。

以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。
请注意,该测试 EA 在此处显示的操控信号仅用于测试目的。
EA 中提供的示例,仅给出了一种总体思路,基于函数库及其类来实现操控 MQL5.com 信号服务的自定义解决方案。

请您在评论中留下问题和建议。

返回内容目录

*该系列的前几篇文章:

DoEasy 函数库中的价格(第六十二部分):实时更新即时报价序列,为操控市场深度做准备
DoEasy 函数库中的价格(第六十三部分):市场深度及其抽象请求类
DoEasy 函数库中的价格(第六十四部分):市场深度,DOM 快照类和快照序列对象
DoEasy 函数库中的价格(第六十五部分):市场深度集合并操控 MQL5.com 信号的类