DoEasy 函数库中的价格(第六十五部分):市场深度集合并操控 MQL5.com 信号的类·综合运用
📚

DoEasy 函数库中的价格(第六十五部分):市场深度集合并操控 MQL5.com 信号的类·综合运用

(3/3)· 从零散快照到集合类再到信号对象,收尾篇打通 DoEasy 价格库的最后拼图

进阶 第 3/3 篇
不少人在接 BookEvent 时直接堆数组,品种一多就分不清哪个序列属于谁。把 DOM 快照散落在全局变量里,回看历史档位几乎不可能,更别提跨品种比对。

从符号与时间毫秒抓取订单簿快照

做深度盘口分析时,核心动作是按品种和毫秒时间戳定位某一帧市场簿。下面这段 MQL5 实现展示了集合类如何把请求转发到具体品种序列,再按时间抽取快照。 GetMBook 先通过 GetMBookseries(symbol) 拿到该品种对应的序列对象,若返回 NULL 直接退出,避免空指针访问;拿到序列后调用其 GetMBook(time_msc) 返回对应毫秒的簿对象。这里 time_msc 是 long 型,精度到毫秒级,足够区分高频行情下的相邻帧。 Print 与 PrintShort 的差异只在输出详尽度:前者遍历 m_list 对所有序列调用 Print(true) 输出完整描述,后者调用 PrintShort(true) 仅输出简版。两者都用 for(int i=0;i<this.m_list.Total();i++) 遍历,遇到 NULL 节点 continue 跳过。 在 MT5 里接这套逻辑时,注意自己维护的毫秒戳要和行情事件时间对齐,否则可能取到空帧。外汇与贵金属盘口跳动快、滑点大,直接用于实盘前务必在策略测试器用历史 tick 验证命中率。

MQL5 / C++
CMBookSnapshot *CMBookSeriesCollection::GetMBook(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">long time_msc)
  {
   CMBookSeries *bookseries=this.GetMBookseries(symbol);
   if(bookseries==NULL)
      class="kw">return NULL;
   class="kw">return bookseries.GetMBook(time_msc);
  }
class="type">void CMBookSeriesCollection::Print(class="type">void)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(class="type">int i=class="num">0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         class="kw">continue;
      bookseries.Print(true);
     }
  }
class="type">void CMBookSeriesCollection::PrintShort(class="type">void)
  {
   ::Print(CMessage::Text(MSG_MB_COLLECTION_TEXT_MBCOLLECTION),":");
   for(class="type">int i=class="num">0;i<this.m_list.Total();i++)
     {
      CMBookSeries *bookseries=this.m_list.At(i);
      if(bookseries==NULL)
         class="kw">continue;
      bookseries.PrintShort(true);
     }
  }

「引擎骨架里的集合对象清单」

写 MT5 通用库时,先把所有数据面抽象成集合类再统一挂到引擎基类,是降低耦合的直接办法。下面这段头文件引用和 CEngine 私有成员,就是一套已落地的基础结构:历史与当前挂单、事件、账户、品种、时间序列、缓冲、指标、tick 序列各自独立成类。 注意高亮的那行 BookSeriesCollection,它管的是 DOM(深度行情)序列,做贵金属或外汇剥皮策略时,盘口厚度变化往往比报价领先一两跳,这个类留着就能直接读十档。 把 12 个 include 和对应的成员变量逐行对上,你在 MT5 里新建 EA 时照抄这套私有声明,编译后 m_book_series 这类对象就能在 OnTick 里点出来用,不用每次重写行情容器。外汇与贵金属杠杆高,真跑之前先用策略测试器验证集合读取延迟。

MQL5 / C++
class=class="str">"cmt">//| Include files                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Services\TimerCounter.mqh"
class="macro">#include "Collections\HistoryCollection.mqh"
class="macro">#include "Collections\MarketCollection.mqh"
class="macro">#include "Collections\EventsCollection.mqh"
class="macro">#include "Collections\AccountsCollection.mqh"
class="macro">#include "Collections\SymbolsCollection.mqh"
class="macro">#include "Collections\ResourceCollection.mqh"
class="macro">#include "Collections\TimeSeriesCollection.mqh"
class="macro">#include "Collections\BuffersCollection.mqh"
class="macro">#include "Collections\IndicatorsCollection.mqh"
class="macro">#include "Collections\TickSeriesCollection.mqh"
class="macro">#include "Collections\BookSeriesCollection.mqh"
class="macro">#include "TradingControl.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Library basis class                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CEngine
  {
class="kw">private:
   CHistoryCollection    m_history;             class=class="str">"cmt">// Collection of historical orders and deals
   CMarketCollection     m_market;              class=class="str">"cmt">// Collection of market orders and deals
   CEventsCollection     m_events;              class=class="str">"cmt">// Event collection
   CAccountsCollection   m_accounts;            class=class="str">"cmt">// Account collection
   CSymbolsCollection    m_symbols;             class=class="str">"cmt">// Symbol collection
   CTimeSeriesCollection m_time_series;         class=class="str">"cmt">// Timeseries collection
   CBuffersCollection    m_buffers;             class=class="str">"cmt">// Collection of indicator buffers
   CIndicatorsCollection m_indicators;          class=class="str">"cmt">// Indicator collection
   CTickSeriesCollection m_tick_series;         class=class="str">"cmt">// Collection of tick series
   CMBookSeriesCollection m_book_series;        class=class="str">"cmt">// Collection of DOM series
   CResourceCollection   m_resource;            class=class="str">"cmt">// Resource list
   CTradingControl       m_trading;             class=class="str">"cmt">// Trading management object
   CPause                m_pause;               class=class="str">"cmt">// Pause object

◍ EA 内核的状态容器与事件入口

把交易机器人当成一个常驻进程看,它得记住自己跑在哪种账户、是不是第一次启动、刚发生了哪类事件。下面这组成员变量就是干这个的:m_is_hedge 标记对冲账户,m_is_tester 区分实盘与回测,m_first_start 控制初始化只跑一次,m_global_error 承接全局错误码。 事件侧用一组 bool 把来路分干净:m_is_market_trade_event 是成交流,m_is_history_trade_event 是历史成交变动,m_is_account_event 和 m_is_symbol_event 分别盯账户属性与品种属性改动。m_last_trade_event、m_last_account_event、m_last_symbol_event 则存最近一次的具体事件枚举或 ID,方便回调里做差分判断。 对外暴露的五个方法就是 MT5 的生命周期钩子:OnTimer 跑定时器逻辑,OnTick 吃每笔报价,OnCalculate 处理指标计算(都带 SDataCalculate 引用和 required 参数),OnBookEvent 接深度簿事件,OnDeinit 做退场清理。注意 OnBookEvent 返回 bool,意味着你可以拦截并决定是否继续传递。 开 MT5 新建一个 EA 骨架,把这段声明原样贴进类里,编译后能在调试窗口看到 m_program 和 m_name 自动填上当前程序类型与文件名,验证你的状态容器确实被实例化。

MQL5 / C++
  CArrayObj              m_list_counters;                 class=class="str">"cmt">// List of timer counters
  class="type">int                   m_global_error;                    class=class="str">"cmt">// Global error code
  class="type">bool                  m_first_start;                     class=class="str">"cmt">// First launch flag
  class="type">bool                  m_is_hedge;                        class=class="str">"cmt">// Hedge account flag
  class="type">bool                  m_is_tester;                       class=class="str">"cmt">// Flag of working in the tester
  class="type">bool                  m_is_market_trade_event;           class=class="str">"cmt">// Account trading event flag
  class="type">bool                  m_is_history_trade_event;          class=class="str">"cmt">// Account history trading event flag
  class="type">bool                  m_is_account_event;                class=class="str">"cmt">// Account change event flag
  class="type">bool                  m_is_symbol_event;                 class=class="str">"cmt">// Symbol change event flag
  ENUM_TRADE_EVENT      m_last_trade_event;                class=class="str">"cmt">// Last account trading event
  class="type">int                   m_last_account_event;              class=class="str">"cmt">// Last event in the account properties
  class="type">int                   m_last_symbol_event;               class=class="str">"cmt">// Last event in the symbol properties
  ENUM_PROGRAM_TYPE     m_program;                         class=class="str">"cmt">// Program type
  class="type">class="kw">string                m_name;                            class=class="str">"cmt">// Program name
class=class="str">"cmt">//--- (class="num">1) Timer, event handler(class="num">2) NewTick, (class="num">3) Calculate, (class="num">4) BookEvent, (class="num">4) Deinit
  class="type">void                  OnTimer(SDataCalculate &data_calculate);
  class="type">void                  OnTick(SDataCalculate &data_calculate,class="kw">const class="type">uint required=class="num">0);
  class="type">int                   OnCalculate(SDataCalculate &data_calculate,class="kw">const class="type">uint required=class="num">0);
  class="type">bool                  OnBookEvent(class="kw">const class="type">class="kw">string &symbol);
  class="type">void                  OnDeinit(class="type">void);

class=class="str">"cmt">//--- Set the list of used symbols in the symbol collection and create the collection of symbol timeseries

逐笔与深度数据的接口封装

在 MT5 的 EA 或指标里直接碰 tick 和 DOM 原始流,代码会很快变脏。把这两类数据收进集合类,再用一组薄封装函数暴露出来,是更稳的做法。 下面这段声明给出了典型接口形态:既能按符号数组批量登记,也能单独创建某个品种的 tick 序列;required_ticks 与 required_books 默认填 0,表示不强制预载最少条数,实盘跑起来更省内存。 [CODE] bool SetUsedSymbols(const string &array_symbols[],const uint required_ticks=0,const uint required_books=0); CTickSeriesCollection *GetTickSeriesCollection(void) { return &this.m_tick_series; } CArrayObj *GetListTickSeries(void) { return this.m_tick_series.GetList(); } bool TickSeriesCreate(const string symbol,const uint required=0) { return this.m_tick_series.CreateTickSeries(symbol,required); } bool TickSeriesCreateAll(const uint required=0) { return this.m_tick_series.CreateTickSeriesAll(required); } void TickSeriesRefresh(const string symbol) { this.m_tick_series.Refresh(symbol); } void TickSeriesRefreshAll(void) { this.m_tick_series.Refresh(); } void TickSeriesRefreshAllExceptCurrent(void) { this.m_tick_series.RefreshExpectCurrent(); } CMBookSeriesCollection *GetMBookSeriesCollection(void) { return &this.m_book_series; } [/CODE] 逐行看:SetUsedSymbols 接收字符串数组,决定盯哪些品种;GetTickSeriesCollection 返回集合指针,GetListTickSeries 拿出具体序列对象数组,方便遍历。TickSeriesCreate 只建一个符号,TickSeriesCreateAll 则按已登记清单全建。 刷新逻辑分三档——指定符号、全部、除当前图符号外的全部。RefreshAllExceptCurrent 在高频刷 new tick 时很有用:当前图表由 OnTick 自然驱动,其余品种手动拉,避免重复开销。GetMBookSeriesCollection 同理把深度簿序列收口到一个入口。 外汇与贵金属属高杠杆品种,这类接口若用于实时 DOM 策略,滑点和断流可能导致成交偏离,先在策略测试器用历史 tick 验证再上模拟盘。

MQL5 / C++
class="type">bool SetUsedSymbols(class="kw">const class="type">class="kw">string &array_symbols[],class="kw">const class="type">uint required_ticks=class="num">0,class="kw">const class="type">uint required_books=class="num">0);
CTickSeriesCollection *GetTickSeriesCollection(class="type">void) { class="kw">return &this.m_tick_series; }
CArrayObj *GetListTickSeries(class="type">void) { class="kw">return this.m_tick_series.GetList(); }
class="type">bool TickSeriesCreate(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0) { class="kw">return this.m_tick_series.CreateTickSeries(symbol,required); }
class="type">bool TickSeriesCreateAll(class="kw">const class="type">uint required=class="num">0) { class="kw">return this.m_tick_series.CreateTickSeriesAll(required); }
class="type">void TickSeriesRefresh(class="kw">const class="type">class="kw">string symbol) { this.m_tick_series.Refresh(symbol); }
class="type">void TickSeriesRefreshAll(class="type">void) { this.m_tick_series.Refresh(); }
class="type">void TickSeriesRefreshAllExceptCurrent(class="type">void) { this.m_tick_series.RefreshExpectCurrent(); }
CMBookSeriesCollection *GetMBookSeriesCollection(class="type">void) { class="kw">return &this.m_book_series; }

「从深度册事件到多品种序列的引擎接法」

在自建行情引擎里,盘口深度(DOM)数据不是被动轮询来的,而是靠 BookEvent 回调驱动。OnBookEvent 先按品种名取 CSymbol 对象,若未订阅深度或对象为空直接返回 false,否则用该对象的 Name() 与 Time() 刷新对应的深度册序列。 实际落地时,GetListMBookSeries 返回内部 m_book_series 的列表指针,MBookSeriesRefresh 允许按 symbol+time_msc 手动重刷,GetMBookSeries 则按名取单品种深度册。这三行封装决定了你后续读盘口是走统一列表还是定向抓取。 SetUsedSymbols 是初始化入口:传入品种数组后,引擎依次建时间序列、tick 序列与 DOM 序列。required_ticks 与 required_books 默认 0,意味着不预载历史,只在事件触发后累积;若你要回测盘口博弈,得显式给非零值。外汇与贵金属盘口瞬变,实盘订阅深度册存在滑点扩大与流动性断裂的高风险,参数须按品种波动调。 下面这段是引擎核心接法的原样代码,逐行看清楚对象归属与返回值,直接抄进 MT5 能编译跑通。

MQL5 / C++
  CArrayObj                 *GetListMBookSeries(class="type">void)                                                { class="kw">return this.m_book_series.GetList();                     }
class=class="str">"cmt">//--- Update the DOM series of a specified symbol
  class="type">void                      MBookSeriesRefresh(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">long time_msc)             { this.m_book_series.Refresh(symbol,time_msc);             }
class=class="str">"cmt">//--- Return the DOM series of a specified symbol
  CMBookSeries             *GetMBookSeries(class="kw">const class="type">class="kw">string symbol)                                      { class="kw">return this.m_book_series.GetMBookseries(symbol);       }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| BookEvent event handler                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CEngine::OnBookEvent(class="kw">const class="type">class="kw">string &symbol)
  {
  CSymbol *sym=this.m_symbols.GetSymbolObjByName(symbol);
  if(sym==NULL || !sym.BookdepthSubscription())
     class="kw">return class="kw">false;
  class="kw">return this.m_book_series.Refresh(sym.Name(),sym.Time());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Set the list of used symbols in the symbol collection            |
class=class="str">"cmt">//| and create the collections of timeseries, tick series            |
class=class="str">"cmt">//| and symbol DOM series                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CEngine::SetUsedSymbols(class="kw">const class="type">class="kw">string &array_symbols[],class="kw">const class="type">uint required_ticks=class="num">0,class="kw">const class="type">uint required_books=class="num">0)
  {
  class="type">bool res=this.m_symbols.SetUsedSymbols(array_symbols);
  CArrayObj *list=this.GetListAllUsedSymbols();
  if(list==NULL)
     class="kw">return class="kw">false;
  res&=this.m_time_series.CreateCollection(list);
  res&=this.m_tick_series.CreateCollection(list,required_ticks);
  res&=this.m_book_series.CreateCollection(list,required_books);
  class="kw">return res;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ MQL5 信号对象类

在 \MQL5\Include\DoEasy\ Defines.mqh 里,加入 MQL5 对象的 整数型 、 实数型 和 字符串型 属性枚举: 我并不打算引入 可能的 mql5 信号事件的列表 。 代之,我只会引入一个常量,该常量指定 MQL5 信号事件代码之后的下一个事件的代码。 我很可能会进一步修改这一点。 为了能够按 MQL5 信号的属性进行搜索和排序,定义 所有可能的排序标准枚举 : 这是为新函数库对象创建新类的一套必要参数。 在 \MQL5\Include\DoEasy\ Objects\ 里,创建一个新的文件夹 MQLSignalBase\ ,并新增一个含有 CMQLSignal 类的文件 MQLSignal.mqh 。 所有 CBaseObj 库对象的基准对象类都应作为基类 : 我们来研究一下类的组成,及其方法的实现: 在私密部分中声明存储整数型、实数型和字符串型对象属性的数组,以及返回对象的实数型和字符串型属性索引的方法。 在类的公开部分,我们可以看到设置和接收对象属性、搜索和排序、显示对象描述和类构造函数的标准方法。 公开部分里还含有 简化访问对象属性的方法 — 这些方法具有不言自明的名称,如此函数库用户即可访问任何属性而无需记住所有对象枚举的名称。 The composition of the object classes was considered in details in the first part of the library description . 我们来看一下类方法的实现。 参数类构造函数 接收信号 ID 。 然后,所有对象属性都用从相应函数获得的值(考虑到该信号已被选择)来填充: 信号订阅标志设置为 false — 所选信号的订阅是从另一个类执行的。 按照指定属性比较 MQL5 信号对象的方法: 简而言之,该方法接收需比较的对象,以及两个对象需比较的属性。 根据所传递的属性,比较两个对象的整数型、实数型或字符串型属性。 返回的比较结果为 -1、1 或 0(小于、大于、等于)。 比较 MQL5 信号对象所有属性的方法: 依据三个对象属性的三重循环中,比较两个对象的每个后续属性,如果两个对象的属性不相等,则返回 false 。 两个对象在三个数组中的所有属性经过比较后,如果未发现不相等的属性,则返回 true 。 返回指定的 整数型 、 实数型 和 字符串型 对象属性描述的方法: 根据传递给该方法的属性,创建并返回带有其描述和值的字符串。 在日志中显示所有 MQL5 信号对象属性的方法: 利用相应的 GetPropertyDescription() 方法,依据整数型、实数型和字符串型属性在三重循环里显示每个后续对象属性的描述。 该方法返回 MQL5 信号对象的简称: 该方法接收标志 ,指定 简短 或 长篇 说明。 默认情况下显示长篇说明: 如果标志是 true ,则显示简短说明: 该方法在日志中显示对象简述: 首先创建简短的信号标题。 终端日志中显示的某些参数已添加到其中。 例如,它也许看起来像这样: 该方法返回帐户类型名称: 根据信号源帐户,返回含有帐户类型(实盘、模拟,或竞赛)的字符串。 MQL5 信号对象的创建至此完毕。 [CODE] <span class="comment">//+------------------------------------------------------------------+</span> <span class="comment">//| Data for

信号属性枚举怎么排进排序表

在 MT5 的信号筛选接口里,信号被拆成整数、实数、字符串三类属性,分别用三个 enum 承载。整数类从 0 开始编号,实数类接在整数类总数后面,字符串类再接在整数+实数总数之后,这样所有属性可以压平成一个连续的索引空间,方便按列排序。 实数属性 enum 里定义了 6 个字段:余额、净值、增长百分比、最大回撤、订阅价、ROI,对应宏 SIGNAL_MQL5_PROP_DOUBLE_TOTAL 等于 6。字符串属性有 5 个:作者登录名、经纪商、服务器、信号名、账户币种,宏 SIGNAL_MQL5_PROP_STRING_TOTAL 等于 5。 排序模式的起点由两个宏控制:FIRST_SIGNAL_MQL5_DBL_PROP 跳过不用于排序的整数项后,定位到第一个实数属性;FIRST_SIGNAL_MQL5_STR_PROP 在前面基础上再跳过实数跳过项,指向第一个字符串属性。实际写 EA 或信号雷达时,改这两个宏的 SKIP 值就能决定哪些列不参与排序。 下面这段是原文里的枚举与宏定义,直接贴进 mt5 的 include 头文件就能编译: #define SIGNAL_MQL5_PROP_INTEGER_SKIP (0) // Number of integer DOM properties not used in sorting //+------------------------------------------------------------------+

//MQL5 signal real properties

//+------------------------------------------------------------------+ enum ENUM_SIGNAL_MQL5_PROP_DOUBLE { SIGNAL_MQL5_PROP_BALANCE = SIGNAL_MQL5_PROP_INTEGER_TOTAL, // Account balance SIGNAL_MQL5_PROP_EQUITY, // Account equity SIGNAL_MQL5_PROP_GAIN, // Account growth in % SIGNAL_MQL5_PROP_MAX_DRAWDOWN, // Maximum drawdown SIGNAL_MQL5_PROP_PRICE, // Signal subscription price SIGNAL_MQL5_PROP_ROI, // Signal's ROI (Return on Investment) in % }; #define SIGNAL_MQL5_PROP_DOUBLE_TOTAL (6) // Total number of real properties #define SIGNAL_MQL5_PROP_DOUBLE_SKIP (0) // Number of real properties not used in sorting //+------------------------------------------------------------------+

//MQL5 signal string properties

//+------------------------------------------------------------------+ enum ENUM_SIGNAL_MQL5_PROP_STRING { SIGNAL_MQL5_PROP_AUTHOR_LOGIN = (SIGNAL_MQL5_PROP_INTEGER_TOTAL+SIGNAL_MQL5_PROP_DOUBLE_TOTAL), // Signal author's login SIGNAL_MQL5_PROP_BROKER, // Broker (company) name SIGNAL_MQL5_PROP_BROKER_SERVER, // Broker server SIGNAL_MQL5_PROP_NAME, // Signal name SIGNAL_MQL5_PROP_CURRENCY, // Signal account currency }; #define SIGNAL_MQL5_PROP_STRING_TOTAL (5) // Total number of string properties //+------------------------------------------------------------------+ //+------------------------------------------------------------------+

//Possible sorting criteria of MQL5 signals

//+------------------------------------------------------------------+ #define FIRST_SIGNAL_MQL5_DBL_PROP (SIGNAL_MQL5_PROP_INTEGER_TOTAL-SIGNAL_MQL5_PROP_INTEGER_SKIP) #define FIRST_SIGNAL_MQL5_STR_PROP (SIGNAL_MQL5_PROP_INTEGER_TOTAL-SIGNAL_MQL5_PROP_INTEGER_SKIP+SIGNAL_MQL5_PROP_DOUBLE_TOTAL-SIGNAL_MQL5_PROP_DOUBLE_SKIP) enum ENUM_SORT_SIGNAL_MQL5_MODE { //--- Sort by integer properties SORT_BY_SIGNAL_MQL5_TRADE_MODE = 0, // Sort by signal type

MQL5 / C++
class="macro">#define SIGNAL_MQL5_PROP_INTEGER_SKIP(class="num">0)                 class=class="str">"cmt">// Number of integer DOM properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| MQL5 signal real properties                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_SIGNAL_MQL5_PROP_DOUBLE
  {
   SIGNAL_MQL5_PROP_BALANCE = SIGNAL_MQL5_PROP_INTEGER_TOTAL, class=class="str">"cmt">// Account balance
   SIGNAL_MQL5_PROP_EQUITY,                                    class=class="str">"cmt">// Account equity
   SIGNAL_MQL5_PROP_GAIN,                                      class=class="str">"cmt">// Account growth in %
   SIGNAL_MQL5_PROP_MAX_DRAWDOWN,                              class=class="str">"cmt">// Maximum drawdown
   SIGNAL_MQL5_PROP_PRICE,                                     class=class="str">"cmt">// Signal subscription price
   SIGNAL_MQL5_PROP_ROI,                                       class=class="str">"cmt">// Signal&class="macro">#x27;s ROI(Return on Investment) in %
  };
class="macro">#define SIGNAL_MQL5_PROP_DOUBLE_TOTAL(class="num">6)                     class=class="str">"cmt">// Total number of real properties
class="macro">#define SIGNAL_MQL5_PROP_DOUBLE_SKIP(class="num">0)                     class=class="str">"cmt">// Number of real properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| MQL5 signal class="type">class="kw">string properties                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_SIGNAL_MQL5_PROP_STRING
  {
   SIGNAL_MQL5_PROP_AUTHOR_LOGIN = (SIGNAL_MQL5_PROP_INTEGER_TOTAL+SIGNAL_MQL5_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Signal author&class="macro">#x27;s login
   SIGNAL_MQL5_PROP_BROKER,                                    class=class="str">"cmt">// Broker(company) name
   SIGNAL_MQL5_PROP_BROKER_SERVER,                             class=class="str">"cmt">// Broker server
   SIGNAL_MQL5_PROP_NAME,                                      class=class="str">"cmt">// Signal name
   SIGNAL_MQL5_PROP_CURRENCY,                                  class=class="str">"cmt">// Signal account currency
  };
class="macro">#define SIGNAL_MQL5_PROP_STRING_TOTAL(class="num">5)                     class=class="str">"cmt">// Total number of class="type">class="kw">string properties
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Possible sorting criteria of MQL5 signals                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define FIRST_SIGNAL_MQL5_DBL_PROP(SIGNAL_MQL5_PROP_INTEGER_TOTAL-SIGNAL_MQL5_PROP_INTEGER_SKIP)
class="macro">#define FIRST_SIGNAL_MQL5_STR_PROP(SIGNAL_MQL5_PROP_INTEGER_TOTAL-SIGNAL_MQL5_PROP_INTEGER_SKIP+SIGNAL_MQL5_PROP_DOUBLE_TOTAL-SIGNAL_MQL5_PROP_DOUBLE_SKIP)
enum ENUM_SORT_SIGNAL_MQL5_MODE
  {
class=class="str">"cmt">//--- Sort by integer properties
   SORT_BY_SIGNAL_MQL5_TRADE_MODE = class="num">0,                         class=class="str">"cmt">// Sort by signal type

「信号排序枚举的字段维度」

在 MT5 的 MQL5 信号筛选接口里,排序方式被拆成了整数枚举,覆盖时间、账户、字符串三类属性。时间类里能按发布日、监控起始日、统计更新日排;账户类则暴露了杠杆、点数、评级、订阅数、成交笔数等可直接比大小的字段。 真正影响实盘参考价值的,是 REAL 与 STRING 两段。从 FIRST_SIGNAL_MQL5_DBL_PROP 起,余额、净值、增益百分比、最大回撤、订阅价、ROI 都是双精度排序键;字符串段从 FIRST_SIGNAL_MQL5_STR_PROP 起,可按作者登录名、券商名、服务器、信号名、账户币种过滤。 跑筛选时若只按 SORT_BY_SIGNAL_MQL5_RATING 排,容易漏掉低订阅但高 ROI 的信号;加一个 SORT_BY_SIGNAL_MQL5_MAX_DRAWDOWN 做交叉比对,外汇与贵金属信号的高风险特征会暴露得更直观。

MQL5 / C++
  SORT_BY_SIGNAL_MQL5_DATE_PUBLISHED,              class=class="str">"cmt">// Sort by signal publication date 
  SORT_BY_SIGNAL_MQL5_DATE_STARTED,                class=class="str">"cmt">// Sort by signal monitoring start date
  SORT_BY_SIGNAL_MQL5_DATE_UPDATED,                class=class="str">"cmt">// Sort by date of the latest update of the signal trading statistics
  SORT_BY_SIGNAL_MQL5_ID,                          class=class="str">"cmt">// Sort by signal ID
  SORT_BY_SIGNAL_MQL5_LEVERAGE,                    class=class="str">"cmt">// Sort by trading account leverage
  SORT_BY_SIGNAL_MQL5_PIPS,                        class=class="str">"cmt">// Sort by trading result in pips
  SORT_BY_SIGNAL_MQL5_RATING,                      class=class="str">"cmt">// Sort by a position in the signal rating
  SORT_BY_SIGNAL_MQL5_SUBSCRIBERS,                 class=class="str">"cmt">// Sort by the number of subscribers
  SORT_BY_SIGNAL_MQL5_TRADES,                      class=class="str">"cmt">// Sort by the number of trades
  SORT_BY_SIGNAL_MQL5_SUBSCRIPTION_STATUS,         class=class="str">"cmt">// Sort by the status of account subscription to a signal
class=class="str">"cmt">//--- Sort by real properties
  SORT_BY_SIGNAL_MQL5_BALANCE = FIRST_SIGNAL_MQL5_DBL_PROP, class=class="str">"cmt">// Sort by account balance
  SORT_BY_SIGNAL_MQL5_EQUITY,                      class=class="str">"cmt">// Sort by account equity
  SORT_BY_SIGNAL_MQL5_GAIN,                        class=class="str">"cmt">// Sort by account growth in %
  SORT_BY_SIGNAL_MQL5_MAX_DRAWDOWN,                class=class="str">"cmt">// Sort by maximum drawdown
  SORT_BY_SIGNAL_MQL5_PRICE,                       class=class="str">"cmt">// Sort by signal subscription price
  SORT_BY_SIGNAL_MQL5_ROI,                         class=class="str">"cmt">// Sort by ROI
class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties
  SORT_BY_SIGNAL_MQL5_AUTHOR_LOGIN = FIRST_SIGNAL_MQL5_STR_PROP, class=class="str">"cmt">// Sort by signal author login
  SORT_BY_SIGNAL_MQL5_BROKER,                      class=class="str">"cmt">// Sort by Broker(company) name
  SORT_BY_SIGNAL_MQL5_BROKER_SERVER,               class=class="str">"cmt">// Sort by broker server
  SORT_BY_SIGNAL_MQL5_NAME,                        class=class="str">"cmt">// Sort by signal name
  SORT_BY_SIGNAL_MQL5_CURRENCY,                    class=class="str">"cmt">// Sort by signal account currency
  };

◍ 信号基类的属性槽位设计

在 MT5 里写可复用的信号模块,第一步往往是抽一个抽象基类把属性管理吃掉。下面这段把整数、实数、字符串三类属性分别塞进定长数组,避免每个具体信号重复声明一大堆成员变量。 核心思路是用枚举偏移量做索引:双精度属性在数组里的真实下标 = 枚举值减去整数属性的总数。这样 SIGNAL_MQL5_PROP_DOUBLE 系列的枚举可以连续编号,又不和整数属性数组撞车。 外汇与贵金属杠杆高、滑点跳变频繁,这类底层结构若数组越界会直接让 EA 在实盘掉线,建议在策略测试器里先用历史数据跑一遍边界访问。 别把正态当圣经:枚举总数宏(如 SIGNAL_MQL5_PROP_INTEGER_TOTAL)必须和 BaseObj 里定义的一致,否则 IndexProp 算出来的下标会静默错位。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2021, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "https:class=class="str">"cmt">//MQL5.com/en/users/artmedia70"
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property strict    class=class="str">"cmt">// Necessary for mql4
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "..\..\Objects\BaseObj.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Abstract MQL5 signal class                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CMQLSignal : class="kw">public CBaseObj
  {
class="kw">private:
   class="type">long            m_long_prop[SIGNAL_MQL5_PROP_INTEGER_TOTAL];      class=class="str">"cmt">// Integer properties
   class="type">class="kw">double          m_double_prop[SIGNAL_MQL5_PROP_DOUBLE_TOTAL];     class=class="str">"cmt">// Real properties
   class="type">class="kw">string          m_string_prop[SIGNAL_MQL5_PROP_STRING_TOTAL];     class=class="str">"cmt">// String properties
class=class="str">"cmt">//--- Return the index of the array the(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are actually located at
   class="type">int             IndexProp(ENUM_SIGNAL_MQL5_PROP_DOUBLE class="kw">property)  class="kw">const { class="kw">return(class="type">int)class="kw">property-SIGNAL_MQL5_PROP_INTEGER_TOTAL; }
  };

信号对象的属性存取与自描述

在 MQL5 里把交易信号抽象成对象时,整数、双精度、字符串三类属性要分开存。上面这段代码用 m_long_prop、m_double_prop、m_string_prop 三个数组承接,SetProperty 和 GetProperty 各写了三套重载,调用时靠枚举类型自动分流。 IndexProp 是个偏移换算函数:把字符串枚举值减去 SIGNAL_MQL5_PROP_INTEGER_TOTAL 和 SIGNAL_MQL5_PROP_DOUBLE_TOTAL,得到它在字符串数组里的下标。如果你自己扩展信号类,这两个宏的总数必须和实际枚举条目数一致,否则字符串属性会写错位置。 SupportProperty 的三个虚函数默认全返回 true,意味着基类宣称自己支持所有属性。派生类若某些属性无意义(比如字符串型的「止损描述」在裸价差信号里不存在),应当重写返回 false,避免 Print 时输出脏数据。 Print 方法带一个 full_prop 开关,默认 false 只打支持项,传 true 则连不支持的占位属性一并列出。接 MT5 后建个信号实例、随便 SetProperty 几个值再 Print(),就能在专家日志里核对三类属性的落盘格式。

MQL5 / C++
class="type">int IndexProp(ENUM_SIGNAL_MQL5_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-SIGNAL_MQL5_PROP_INTEGER_TOTAL-SIGNAL_MQL5_PROP_DOUBLE_TOTAL; }
class="kw">public:
class=class="str">"cmt">//--- Set object&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
  class="type">void  SetProperty(ENUM_SIGNAL_MQL5_PROP_INTEGER class="kw">property,class="type">long value)    { this.m_long_prop[class="kw">property]=value;                    }
  class="type">void  SetProperty(ENUM_SIGNAL_MQL5_PROP_DOUBLE class="kw">property,class="type">class="kw">double value)   { this.m_double_prop[this.IndexProp(class="kw">property)]=value;    }
  class="type">void  SetProperty(ENUM_SIGNAL_MQL5_PROP_STRING class="kw">property,class="type">class="kw">string value)   { this.m_string_prop[this.IndexProp(class="kw">property)]=value;    }
class=class="str">"cmt">//--- Return object’s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property from the properties array
  class="type">long    GetProperty(ENUM_SIGNAL_MQL5_PROP_INTEGER class="kw">property)        class="kw">const { class="kw">return this.m_long_prop[class="kw">property];           }
  class="type">class="kw">double    GetProperty(ENUM_SIGNAL_MQL5_PROP_DOUBLE class="kw">property)          class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)];  }
  class="type">class="kw">string    GetProperty(ENUM_SIGNAL_MQL5_PROP_STRING class="kw">property)          class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)];  }
class=class="str">"cmt">//--- Return itself
  CMQLSignal    *GetObject(class="type">void)                                { class="kw">return &this;}
class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property
  class="kw">virtual class="type">bool    SupportProperty(ENUM_SIGNAL_MQL5_PROP_INTEGER class="kw">property)         { class="kw">return true; }
  class="kw">virtual class="type">bool    SupportProperty(ENUM_SIGNAL_MQL5_PROP_DOUBLE class="kw">property)          { class="kw">return true; }
  class="kw">virtual class="type">bool    SupportProperty(ENUM_SIGNAL_MQL5_PROP_STRING class="kw">property)          { class="kw">return true; }
class=class="str">"cmt">//--- Get description of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
  class="type">class="kw">string    GetPropertyDescription(ENUM_SIGNAL_MQL5_PROP_INTEGER class="kw">property);
  class="type">class="kw">string    GetPropertyDescription(ENUM_SIGNAL_MQL5_PROP_DOUBLE class="kw">property);
  class="type">class="kw">string    GetPropertyDescription(ENUM_SIGNAL_MQL5_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Display the description of object properties in the journal(full_prop=true - all properties, class="kw">false - supported ones only)
  class="type">void    Print(class="kw">const class="type">bool full_prop=class="kw">false);

「信号对象的封装与快捷读取」

在 MT5 里做信号筛选,直接调 SignalCopy 系列属性很啰嗦。把 MQL5 信号包成 CMQLSignal 类,等于给每个信号建了一个本地结构体,后面排序、查重都靠它。 类里先留了几个虚函数:PrintShort 往日志打简介,Header 取短名,Compare 按指定字段比大小用来排序,IsEqual 则全属性比对找重复信号。构造函数支持空对象或直接用 signal_id 初始化,省去手动绑定的步骤。 公开方法才是日常用的主力。DatePublished、DateStarted、DateUpdated 三个返回 datetime,分别对应信号发布、监控启动、统计更新的时间点;ID 取信号唯一编号;Leverage 读账户杠杆倍数;Pips 是累计盈利点数(long 型,可能为负数);此外源码注释还列了评级位置、订阅人数、交易笔数、账户类型、当前是否订阅等字段,均走 GetProperty 统一取。 把这些封装好后,你写 EA 时只要 cmql_list.Sort(0) 就能按默认模式排信号,比每次裸调信号属性快得多。外汇与贵金属信号自带高杠杆风险,回测结论仅代表历史概率,实盘须自行验证。

MQL5 / C++
class=class="str">"cmt">//--- Display a class="type">class="kw">short description of the object in the journal
  class="kw">virtual class="type">void        PrintShort(class="type">void);
class=class="str">"cmt">//--- Return the object class="type">class="kw">short name
  class="kw">virtual class="type">class="kw">string      Header(class="kw">const class="type">bool shrt=class="kw">false);

class=class="str">"cmt">//--- Compare CMQLSignal objects by a specified class="kw">property (to sort the list by an MQL5 signal object)
  class="kw">virtual class="type">int         Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const;
class=class="str">"cmt">//--- Compare CMQLSignal objects by all properties(to search for equal MQL5 signal objects)
  class="type">bool                IsEqual(CMQLSignal* compared_obj) class="kw">const;

class=class="str">"cmt">//--- Constructors
                   CMQLSignal(){;}
                   CMQLSignal(class="kw">const class="type">long signal_id);

class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of simplified access to MQL5 signal object              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Returns the dates of(class="num">1) publication, (class="num">2) monitoring start, (class="num">3) trading statistics last update,
class=class="str">"cmt">//--- (class="num">4) ID, (class="num">5) trading account leverage, (class="num">6) trading result in pips, (class="num">7) position in the signal rating,
class=class="str">"cmt">//--- (class="num">8) number of subscribers, (class="num">9) number of trades, (class="num">10) account type, (class="num">11) flag of the current account subscription to a signal
  class="type">class="kw">datetime            DatePublished(class="type">void)    class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SIGNAL_MQL5_PROP_DATE_PUBLISHED);      }
  class="type">class="kw">datetime            DateStarted(class="type">void)      class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SIGNAL_MQL5_PROP_DATE_STARTED);        }
  class="type">class="kw">datetime            DateUpdated(class="type">void)      class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(SIGNAL_MQL5_PROP_DATE_UPDATED);        }
  class="type">long                ID(class="type">void)               class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_ID);                           }
  class="type">long                Leverage(class="type">void)         class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_LEVERAGE);                     }
  class="type">long                Pips(class="type">void)             class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_PIPS);                         }

◍ 信号对象的只读属性怎么取

在 MT5 里封装信号对象时,通常会用一组 const 成员函数把底层属性暴露出来,调用方只能读不能改。下面这段就是典型的 getter 集合,全部走 this.GetProperty 拉取信号服务端的实时字段。 long 型字段负责整数类指标:Rating 取信号评分,Subscribers 取订阅人数,Trades 取总交易笔数,TradeMode 取交易模式枚举,SubscriptionStatus 返回是否当前已订阅的布尔值。 double 型字段覆盖资金和收益维度:Balance 是账户余额,Equity 是净值,Gain 是账户增长百分比,MaxDrawdown 是最大回撤,Price 是订阅价,ROI 是投资回报率百分比。回测或筛选信号时,若 MaxDrawdown 超过 50% 而 Gain 低于 20%,该信号在历史样本上大概率是高回撤低效率组合。 把这些 getter 直接塞进 EA 的筛选模块,就能在本地按阈值过滤信号,而不用每次都手动翻终端界面。外汇与贵金属信号受杠杆和跳空影响,实盘表现可能与历史 ROI 出现明显偏离,接入前务必用策略测试器跑一遍。

MQL5 / C++
class="type">long Rating(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_RATING); }
class="type">long Subscribers(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_SUBSCRIBERS); }
class="type">long Trades(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_TRADES); }
class="type">long TradeMode(class="type">void) class="kw">const { class="kw">return (class="type">long)this.GetProperty(SIGNAL_MQL5_PROP_TRADE_MODE); }
class="type">bool SubscriptionStatus(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(SIGNAL_MQL5_PROP_SUBSCRIPTION_STATUS); }
class=class="str">"cmt">//--- Return(class="num">1) the account balance, (class="num">2) account funds, (class="num">3) account growth in %,
class=class="str">"cmt">//--- (class="num">4) maximum drawdown, (class="num">5) subscription price and(class="num">6) signal ROI value(Return on Investment) in %
class="type">class="kw">double Balance(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_BALANCE); }
class="type">class="kw">double Equity(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_EQUITY); }
class="type">class="kw">double Gain(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_GAIN); }
class="type">class="kw">double MaxDrawdown(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_MAX_DRAWDOWN); }
class="type">class="kw">double Price(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_PRICE); }
class="type">class="kw">double ROI(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_ROI); }

读取与写入信号元数据的接口方法

在 MT5 的信号对象封装里,一组 const 成员函数负责把信号提供方的静态信息暴露出来:作者登录名、经纪商名称、服务器、信号名和账户币种,全部通过 GetProperty 按预定义常量取回。 string AuthorLogin(void) const { return this.GetProperty(SIGNAL_MQL5_PROP_AUTHOR_LOGIN); } string Broker(void) const { return this.GetProperty(SIGNAL_MQL5_PROP_BROKER); } string BrokerServer(void) const { return this.GetProperty(SIGNAL_MQL5_PROP_BROKER_SERVER); } string Name(void) const { return this.GetProperty(SIGNAL_MQL5_PROP_NAME); } string Currency(void) const { return this.GetProperty(SIGNAL_MQL5_PROP_CURRENCY); } 上面五行逐行拆解:每行先声明返回 string 的常量方法;方法体直接调 this.GetProperty,传入对应的信号属性宏;例如 SIGNAL_MQL5_PROP_AUTHOR_LOGIN 取信号作者登录名,SIGNAL_MQL5_PROP_CURRENCY 取账户结算币种。 另一批 void 方法用于回写或初始化信号统计字段,覆盖发布日期、监控起始、最后更新、ID、杠杆、盈利点数等 11 类数据。 void SetDatePublished(const datetime date) { this.SetProperty(SIGNAL_MQL5_PROP_DATE_PUBLISHED,date); } void SetDateStarted(const datetime date) { this.SetProperty(SIGNAL_MQL5_PROP_DATE_STARTED,date); } void SetDateUpdated(const datetime date) { this.SetProperty(SIGNAL_MQL5_PROP_DATE_UPDATED,date); } void SetID(const long id) { this.SetProperty(SIGNAL_MQL5_PROP_ID,id); } void SetLeverage(const long value) { this.SetProperty(SIGNAL_MQL5_PROP_LEVERAGE,value); } void SetPips(const long value) { this.SetProperty(SIGNAL_MQL5_PROP_PIPS,value); } 拆解要点:Set 系列接收 datetime 或 long 参数,转手交给 SetProperty 落地;例如 SetLeverage 写入杠杆倍数,SetPips 记录累计盈利点数(单位 pips,非金额)。 开 MT5 自建 EA 调一下 SignalInfo::Name() 和 SetPips(),能直接验证本地信号对象能否正确往返这些字段;外汇与贵金属信号订阅存在滑点、断连等高风险,回测数据不代表实盘倾向。

MQL5 / C++
class="type">class="kw">string AuthorLogin(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_AUTHOR_LOGIN); }
class="type">class="kw">string Broker(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_BROKER); }
class="type">class="kw">string BrokerServer(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_BROKER_SERVER); }
class="type">class="kw">string Name(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_NAME); }
class="type">class="kw">string Currency(class="type">void) class="kw">const { class="kw">return this.GetProperty(SIGNAL_MQL5_PROP_CURRENCY); }
class="type">void SetDatePublished(class="kw">const class="type">class="kw">datetime date) { this.SetProperty(SIGNAL_MQL5_PROP_DATE_PUBLISHED,date); }
class="type">void SetDateStarted(class="kw">const class="type">class="kw">datetime date) { this.SetProperty(SIGNAL_MQL5_PROP_DATE_STARTED,date); }
class="type">void SetDateUpdated(class="kw">const class="type">class="kw">datetime date) { this.SetProperty(SIGNAL_MQL5_PROP_DATE_UPDATED,date); }
class="type">void SetID(class="kw">const class="type">long id) { this.SetProperty(SIGNAL_MQL5_PROP_ID,id); }
class="type">void SetLeverage(class="kw">const class="type">long value) { this.SetProperty(SIGNAL_MQL5_PROP_LEVERAGE,value); }
class="type">void SetPips(class="kw">const class="type">long value) { this.SetProperty(SIGNAL_MQL5_PROP_PIPS,value); }

「信号对象的属性写入接口」

在 MT5 的信号封装类里,这一类 Set 方法负责把外部抓取到的信号指标写进对象属性。整组方法都走同一个底层入口 this.SetProperty,只是枚举键不同,调用方无需关心存储结构。 前五个是整数 / 布尔类字段:评分、订阅人数、交易笔数、交易模式、订阅状态,分别映射到 SIGNAL_MQL5_PROP_RATING 到 SUBSCRIPTION_STATUS。后面六个是浮点类:余额、净值、增长百分比、最大回撤、订阅价、ROI,键名带 _BALANCE / _EQUITY / _GAIN / _MAX_DRAWDOWN / _PRICE / _ROI。 [CODE]void SetRating(const long value) { this.SetProperty(SIGNAL_MQL5_PROP_RATING,value); }[/CODE] 上面这行就是典型写法:入参用 const long 锁死不可改,函数体只有一句属性写入。其余 SetSubscribers、SetTrades 等结构完全一致,只是枚举键换掉。 实盘接信号源时,建议先核对 MaxDrawdown 与 ROI 两个浮点值再决定是否订阅,外汇与贵金属信号受杠杆和跳空影响,回撤可能快速扩大,属于高风险类别。打开 MT5 导航栏的信号选项卡,用脚本批量读这些属性,就能自己筛掉 drawdown 超过 30% 的源。

MQL5 / C++
class="type">void SetRating(class="kw">const class="type">long value) { this.SetProperty(SIGNAL_MQL5_PROP_RATING,value); }
class="type">void SetSubscribers(class="kw">const class="type">long value) { this.SetProperty(SIGNAL_MQL5_PROP_SUBSCRIBERS,value); }
class="type">void SetTrades(class="kw">const class="type">long value) { this.SetProperty(SIGNAL_MQL5_PROP_TRADES,value); }
class="type">void SetTradeMode(class="kw">const class="type">long mode) { this.SetProperty(SIGNAL_MQL5_PROP_TRADE_MODE,mode); }
class="type">void SetSubscriptionStatus(class="kw">const class="type">bool flag) { this.SetProperty(SIGNAL_MQL5_PROP_SUBSCRIPTION_STATUS,flag); }
class=class="str">"cmt">//--- Set(class="num">1) the account balance, (class="num">2) account funds, (class="num">3) account growth in %,
class=class="str">"cmt">//--- (class="num">4) maximum drawdown, (class="num">5) subscription price and(class="num">6) signal ROI value(Return on Investment) in %
class="type">void SetBalance(class="kw">const class="type">class="kw">double value) { this.SetProperty(SIGNAL_MQL5_PROP_BALANCE,value); }
class="type">void SetEquity(class="kw">const class="type">class="kw">double value) { this.SetProperty(SIGNAL_MQL5_PROP_EQUITY,value); }
class="type">void SetGain(class="kw">const class="type">class="kw">double value) { this.SetProperty(SIGNAL_MQL5_PROP_GAIN,value); }
class="type">void SetMaxDrawdown(class="kw">const class="type">class="kw">double value) { this.SetProperty(SIGNAL_MQL5_PROP_MAX_DRAWDOWN,value); }
class="type">void SetPrice(class="kw">const class="type">class="kw">double value) { this.SetProperty(SIGNAL_MQL5_PROP_PRICE,value); }
class="type">void SetROI(class="kw">const class="type">class="kw">double value) { this.SetProperty(SIGNAL_MQL5_PROP_ROI,value); }

◍ 信号对象的属性写入与构造初始化

在 MQL5 信号封装类里,一组 Set 方法负责把字符串类元数据写进内部属性表,覆盖作者登录名、经纪商、服务器、信号名和结算币种等字段。它们本质都是对 this.SetProperty 的薄封装,键名对应 SIGNAL_MQL5_PROP_* 常量,调用成本极低。 构造阶段用信号 ID 拉起对象时,会顺手从 SignalBaseGetInteger 批量取基础整数属性:交易模式、发布/启动/更新时间、杠杆与累计 pip 数。其中订阅状态被硬编码为 false,意味着新实例默认处于未订阅态,后续需显式改写。 外汇与贵金属信号自带高杠杆与滑点不确定性,订阅前应在 MT5 终端用这类接口核对杠杆字段(SIGNAL_MQL5_PROP_LEVERAGE)与 broker server 字符串,避免跟单到一个不匹配你账户类型的源。

MQL5 / C++
class="type">void SetAuthorLogin(class="kw">const class="type">class="kw">string value) { this.SetProperty(SIGNAL_MQL5_PROP_AUTHOR_LOGIN,value); }
class="type">void SetBroker(class="kw">const class="type">class="kw">string value) { this.SetProperty(SIGNAL_MQL5_PROP_BROKER,value); }
class="type">void SetBrokerServer(class="kw">const class="type">class="kw">string value) { this.SetProperty(SIGNAL_MQL5_PROP_BROKER_SERVER,value); }
class="type">void SetName(class="kw">const class="type">class="kw">string value) { this.SetProperty(SIGNAL_MQL5_PROP_NAME,value); }
class="type">void SetCurrency(class="kw">const class="type">class="kw">string value) { this.SetProperty(SIGNAL_MQL5_PROP_CURRENCY,value); }
class=class="str">"cmt">//--- Return the account type name
class="type">class="kw">string TradeModeDescription(class="type">void);
};
CMQLSignal::CMQLSignal(class="kw">const class="type">long signal_id)
{
 this.m_long_prop[SIGNAL_MQL5_PROP_ID] = signal_id;
 this.m_long_prop[SIGNAL_MQL5_PROP_SUBSCRIPTION_STATUS] = class="kw">false;
 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);

把信号源属性抓进对象里

在 MT5 里做信号源横向对比,第一步是把 SignalBase 里的字段搬进自己的 CMQLSignal 对象。下面这段赋值把评级、订阅数、交易次数等整型量,以及余额、权益、增益、最大回撤、ROI 等双精度量,还有作者登录名、经纪商、服务器、信号名、币种等字符串,分别写进三个数组。 整型用 SignalBaseGetInteger,双精度用 SignalBaseGetDouble,字符串用 SignalBaseGetString,宏名对应 SIGNAL_BASE_* 系列。跑完这轮,一个信号源就变成可排序的内存对象,而不是每次都去查 API。 [CODE]…[/CODE] 后面紧接的 Compare 方法决定两个信号谁排前面:mode 落在整型区就比 long,落在双精度区就比 double,返回 1 / -1 / 0。实际筛信号时,把 mode 设成 SIGNAL_MQL5_PROP_GAIN 或 SIGNAL_MQL5_PROP_MAX_DRAWDOWN,就能按收益或回撤给一篮子信号源排座次——外汇与贵金属信号自带高杠杆风险,排序只是概率参考,不等于跟单安全。

MQL5 / C++
  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);
}

class="type">int CMQLSignal::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const
  {
  class="kw">const CMQLSignal *obj_compared=node;
class=class="str">"cmt">//--- compare integer properties of two objects
  if(mode<SIGNAL_MQL5_PROP_INTEGER_TOTAL)
    {
    class="type">long value_compared=obj_compared.GetProperty((ENUM_SIGNAL_MQL5_PROP_INTEGER)mode);
    class="type">long value_current=this.GetProperty((ENUM_SIGNAL_MQL5_PROP_INTEGER)mode);
    class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0);
    }
class=class="str">"cmt">//--- compare real properties of two objects
  else if(mode<SIGNAL_MQL5_PROP_DOUBLE_TOTAL+SIGNAL_MQL5_PROP_INTEGER_TOTAL)
    {
    class="type">class="kw">double value_compared=obj_compared.GetProperty((ENUM_SIGNAL_MQL5_PROP_DOUBLE)mode);

「信号对象的比较与属性描述实现」

在自定义信号类 CMQLSignal 里,比较逻辑按属性类型分了三块走。double 和 string 类型的比较都返回 1 / -1 / 0 三态,对应大于、小于、等于,方便上层做排序或阈值判断。 IsEqual 方法则把整数、双精度、字符串三类属性依次遍历一遍:循环起点 beg 从 0 开始,end 每次加上该类属性的总数宏(SIGNAL_MQL5_PROP_INTEGER_TOTAL 等),任一项不等就直接 return false。三类全过才 return true,等价于「全属性深比较」。 GetPropertyDescription 针对整数属性生成可读文本,例如 SIGNAL_MQL5_PROP_TRADE_MODE 会拼上交易模式描述,不支持的属性则追加 MSG_LIB_PROP_NOT_SUPPORTED 提示。这种封装让你在日志或面板里直接打印信号状态,不用自己写 switch。 外汇与贵金属信号比较结果仅反映历史属性一致性,市场高风险,实际触发概率仍受点差与滑点影响。

MQL5 / C++
class="type">class="kw">double value_current=this.GetProperty((ENUM_SIGNAL_MQL5_PROP_DOUBLE)mode);
class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0);
 }
class=class="str">"cmt">//--- compare class="type">class="kw">string properties of two objects
 else if(mode<SIGNAL_MQL5_PROP_DOUBLE_TOTAL+SIGNAL_MQL5_PROP_INTEGER_TOTAL+SIGNAL_MQL5_PROP_STRING_TOTAL)
  {
   class="type">class="kw">string value_compared=obj_compared.GetProperty((ENUM_SIGNAL_MQL5_PROP_STRING)mode);
   class="type">class="kw">string value_current=this.GetProperty((ENUM_SIGNAL_MQL5_PROP_STRING)mode);
   class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0);
  }
 class="kw">return class="num">0;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Compare CMQLSignal objects by all properties                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CMQLSignal::IsEqual(CMQLSignal *compared_obj) class="kw">const
  {
  class="type">int beg=class="num">0, end=SIGNAL_MQL5_PROP_INTEGER_TOTAL;
  for(class="type">int i=beg; i<end; i++)
   {
    ENUM_SIGNAL_MQL5_PROP_INTEGER prop=(ENUM_SIGNAL_MQL5_PROP_INTEGER)i;
    if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false;
   }
  beg=end; end+=SIGNAL_MQL5_PROP_DOUBLE_TOTAL;
  for(class="type">int i=beg; i<end; i++)
   {
    ENUM_SIGNAL_MQL5_PROP_DOUBLE prop=(ENUM_SIGNAL_MQL5_PROP_DOUBLE)i;
    if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false;
   }
  beg=end; end+=SIGNAL_MQL5_PROP_STRING_TOTAL;
  for(class="type">int i=beg; i<end; i++)
   {
    ENUM_SIGNAL_MQL5_PROP_STRING prop=(ENUM_SIGNAL_MQL5_PROP_STRING)i;
    if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false;
   }
  class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return description of object&class="macro">#x27;s integer class="kw">property                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CMQLSignal::GetPropertyDescription(ENUM_SIGNAL_MQL5_PROP_INTEGER class="kw">property)
  {
  class="kw">return
   (
    class="kw">property==SIGNAL_MQL5_PROP_TRADE_MODE       ?  CMessage::Text(MSG_SIGNAL_MQL5_TRADE_MODE)+
     (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+this.TradeModeDescription()
     )  :
    class="kw">property==SIGNAL_MQL5_PROP_DATE_PUBLISHED ?  CMessage::Text(MSG_SIGNAL_MQL5_DATE_PUBLISHED)+
     (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+::TimeToString(this.DatePublished())
     )  :
    class="kw">property==SIGNAL_MQL5_PROP_DATE_STARTED   ?  CMessage::Text(MSG_SIGNAL_MQL5_DATE_STARTED)+
     (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :

◍ 信号属性描述的三元嵌套写法

这段 CMQLSignal 的成员函数把信号对象的各类属性转成可读文本,核心是一个多层三元运算符链。它先按 property 枚举值分支,再判断当前信号是否支持该属性:不支持就拼上「不支持」提示,支持则取对应值转 string。 日期类属性(DateStarted / DateUpdated)走 TimeToString 格式化,订阅状态(SUBSCRIPTION_STATUS)把布尔值映射成「是 / 否」本地化文本,其余整型或双精度属性直接 (string)GetProperty(property) 强转。 在 MT5 里把这段贴进自行封装的 signal 包装类,断点跑一遍 GetPropertyDescription(SIGNAL_MQL5_PROP_PIPS),能看到返回字符串形如「Pips: 123.4」——若 broker 端信号不携带该字段,则输出「Pips: 不支持」。外汇与贵金属信号订阅涉及跟单执行,滑点与断连风险偏高,返回值仅作展示参考,不构成任何方向判断。

MQL5 / C++
class="type">class="kw">string CMQLSignal::GetPropertyDescription(ENUM_SIGNAL_MQL5_PROP_DOUBLE class="kw">property)
  {
   class="kw">return
    (
     class="kw">property==SIGNAL_MQL5_PROP_DATE_STARTED ?  CMessage::Text(MSG_SIGNAL_MQL5_DATE_STARTED)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+::TimeToString(this.DateStarted())
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_DATE_UPDATED  ?  CMessage::Text(MSG_SIGNAL_MQL5_DATE_UPDATED)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+::TimeToString(this.DateUpdated())
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_ID           ?  CMessage::Text(MSG_SIGNAL_MQL5_ID)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_LEVERAGE     ?  CMessage::Text(MSG_SIGNAL_MQL5_LEVERAGE)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_PIPS         ?  CMessage::Text(MSG_SIGNAL_MQL5_PIPS)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_RATING       ?  CMessage::Text(MSG_SIGNAL_MQL5_RATING)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_SUBSCRIBERS  ?  CMessage::Text(MSG_SIGNAL_MQL5_SUBSCRIBERS)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_TRADES       ?  CMessage::Text(MSG_SIGNAL_MQL5_TRADES)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
      )  :
     class="kw">property==SIGNAL_MQL5_PROP_SUBSCRIPTION_STATUS ?  CMessage::Text(MSG_SIGNAL_MQL5_SUBSCRIPTION_STATUS)+
      (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
       ": "+(this.GetProperty(class="kw">property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
      )  :
     ""
    );
  }

信号对象属性的文本化拼装逻辑

在 MQL5 信号类里,数值型属性(余额、净值、收益、最大回撤、报价、ROI)和字符串型属性(作者登录名、经纪商、服务器)分别由两个重载方法负责转成可读描述。 数值分支统一走 DoubleToString(GetProperty(property),2),也就是全部保留 2 位小数;若当前信号不支持该属性,则追加「不支持」提示而非数值。 字符串分支直接拼接属性原值并加英文双引号包裹,没有精度处理,也没有不支持判断——这意味着字符串属性在底层默认可用。 打开 MT5 自编 EA 时,若想复用这套描述格式,直接抄下面两段分支结构即可,改 MSG_* 文本常量就能中文化。

MQL5 / C++
class="kw">property==SIGNAL_MQL5_PROP_BALANCE        ?  CMessage::Text(MSG_ACC_PROP_BALANCE)+
      (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
      )  :
class="kw">property==SIGNAL_MQL5_PROP_EQUITY   ?  CMessage::Text(MSG_SIGNAL_MQL5_EQUITY)+
      (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
      )  :
class="kw">property==SIGNAL_MQL5_PROP_GAIN   ?  CMessage::Text(MSG_SIGNAL_MQL5_GAIN)+
      (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
      )  :
class="kw">property==SIGNAL_MQL5_PROP_MAX_DRAWDOWN   ?  CMessage::Text(MSG_SIGNAL_MQL5_MAX_DRAWDOWN)+
      (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
      )  :
class="kw">property==SIGNAL_MQL5_PROP_PRICE   ?  CMessage::Text(MSG_SIGNAL_MQL5_PRICE)+
      (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
      )  :
class="kw">property==SIGNAL_MQL5_PROP_ROI   ?  CMessage::Text(MSG_SIGNAL_MQL5_ROI)+
      (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
         ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
      )  :
      ""
   );
}
class="type">class="kw">string CMQLSignal::GetPropertyDescription(ENUM_SIGNAL_MQL5_PROP_STRING class="kw">property)
  {
   class="kw">return
     (
      class="kw">property==SIGNAL_MQL5_PROP_AUTHOR_LOGIN   ?  CMessage::Text(MSG_SIGNAL_MQL5_AUTHOR_LOGIN)+": \""+this.GetProperty(class="kw">property)+"\""    :
      class="kw">property==SIGNAL_MQL5_PROP_BROKER         ?  CMessage::Text(MSG_SIGNAL_MQL5_BROKER)+": \""+this.GetProperty(class="kw">property)+"\""         :
      class="kw">property==SIGNAL_MQL5_PROP_BROKER_SERVER  ?  CMessage::Text(MSG_SIGNAL_MQL5_BROKER_SERVER)+": \""+this.GetProperty(class="kw">property)+"\""  :

「把信号属性打印到日志里」

CMQLSignal 类提供 Print() 与 PrintShort() 两个方法,专门把信号对象的属性按整型、双精度、字符串三类分段输出到 MT5 专家日志。Print() 默认只打印当前信号支持(SupportProperty 返回 true)的字段,传 full_prop=true 才会遍历 SIGNAL_MQL5_PROP_INTEGER_TOTAL、SIGNAL_MQL5_PROP_DOUBLE_TOTAL、SIGNAL_MQL5_PROP_STRING_TOTAL 覆盖的全部枚举项。 日志输出用 "============= " 加 Header() 文本做首尾分隔,中间每段属性群用 "------" 隔开,方便肉眼核对一份信号配置到底挂了哪些参数。Header() 本身接受一个 bool shrt 参数:shrt=true 返回短名(MSG_SIGNAL_MQL5_TEXT_SIGNAL),false 返回全称(MSG_SIGNAL_MQL5_TEXT_SIGNAL_MQL5)。 PrintShort() 则只打 Header(true) 一行短描述,适合在策略循环里高频调用时少刷屏。外汇与贵金属信号订阅涉及杠杆与跟单延迟,属高风险操作,日志里的参数仅作核对用途,不代表任何收益倾向。

MQL5 / C++
class="type">void CMQLSignal::Print(class="kw">const class="type">bool full_prop=class="kw">false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") =============");
   class="type">int beg=class="num">0, end=SIGNAL_MQL5_PROP_INTEGER_TOTAL;
   for(class="type">int i=beg; i<end; i++)
     {
      ENUM_SIGNAL_MQL5_PROP_INTEGER prop=(ENUM_SIGNAL_MQL5_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) class="kw">continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=SIGNAL_MQL5_PROP_DOUBLE_TOTAL;
   for(class="type">int i=beg; i<end; i++)
     {
      ENUM_SIGNAL_MQL5_PROP_DOUBLE prop=(ENUM_SIGNAL_MQL5_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) class="kw">continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=SIGNAL_MQL5_PROP_STRING_TOTAL;
   for(class="type">int i=beg; i<end; i++)
     {
      ENUM_SIGNAL_MQL5_PROP_STRING prop=(ENUM_SIGNAL_MQL5_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) class="kw">continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n");
  }

class="type">class="kw">string CMQLSignal::Header(class="kw">const class="type">bool shrt=class="kw">false)
  {
   class="kw">return(shrt ? CMessage::Text(MSG_SIGNAL_MQL5_TEXT_SIGNAL) : CMessage::Text(MSG_SIGNAL_MQL5_TEXT_SIGNAL_MQL5));
  }

class="type">void CMQLSignal::PrintShort(class="type">void)
  {
   ::Print(
      this.Header(true),

◍ 信号对象的日志拼装与账户类型判定

在 MQL5 信号类里,把元数据拼成一行可读日志是很常见的做法。下面这段把信号名、作者登录名、ID、增益、最大回撤和订阅价格用 DoubleToString 统一保留两位小数输出,最终控制台会打印出类似 Signal "DemoForArticle". Author login: login, ID XXXXXX, Growth: XX.XX, Drawdown: XX.XX, Price: XX.XX 的字符串,方便你直接在专家日志里核对信号概貌。 TradeModeDescription 则负责把整数型的交易模式翻译成文字。0 对应实盘、1 对应模拟、2 对应竞赛,其余返回未知;这种三元嵌套写法在 MQL5 里比写一堆 if 更紧凑,但调试时容易看错优先级,建议复制后单步跑一遍。 外汇与贵金属信号订阅涉及杠杆与滑点风险,回测增益不代表实盘表现,接入前应在 MT5 策略测试器用历史数据验证字段取值是否与信号源一致。

MQL5 / C++
                                                                 " \"",this.Name(),"\". ",
                                                                 CMessage::Text(MSG_SIGNAL_MQL5_AUTHOR_LOGIN),": ",this.AuthorLogin(),
                                                                 ", ID ",this.ID(),
                                                                 ", ",CMessage::Text(MSG_SIGNAL_MQL5_TEXT_GAIN),": ",::DoubleToString(this.Gain(),class="num">2),
                                                                 ", ",CMessage::Text(MSG_SIGNAL_MQL5_TEXT_DRAWDOWN),": ",::DoubleToString(this.MaxDrawdown(),class="num">2),
                                                                 ", ",CMessage::Text(MSG_LIB_TEXT_REQUEST_PRICE),": ",::DoubleToString(this.Price(),class="num">2)
                                                   );
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
Signal "DemoForArticle". Author login: login, ID XXXXXX, Growth: XX.XX, Drawdown: XX.XX, Price: XX.XX
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the account type name                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CMQLSignal::TradeModeDescription(class="type">void)
  {
   class="kw">return
     (
      this.TradeMode()==class="num">0 ? CMessage::Text(MSG_ACC_TRADE_MODE_REAL)     :
      this.TradeMode()==class="num">1 ? CMessage::Text(MSG_ACC_TRADE_MODE_DEMO)     :
      this.TradeMode()==class="num">2 ? CMessage::Text(MSG_ACC_TRADE_MODE_CONTEST)  :
      CMessage::Text(MSG_ACC_TRADE_MODE_UNKNOWN)
     );
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

把 MQL5 信号对象接进 EA 跑通日志

把上一篇文章里的 EA 复制到 \MQL5\Experts\TestDoEasy\Part65\ 目录,改名为 TestDoEasyPart65.mq5,DOM 快照序列集合类此刻已对所有主库对象可见,但引擎还没挂上 MQL5 信号对象。 先动包含关系:删掉 DOM 快照序列类的 include,换成 MQL5 信号对象头文件 DoEasy\Objects\MQLSignalBase\MQLSignal.mqh,EA 全局变量里同步移除快照序列对象的声明。 OnInit() 里写入信号对象初始化模块,表面代码量比帮助文档的示例(取免费、盈利、订户数非零的信号列表)更长,等信号集合类封装完就会收敛。OnBookEvent() 因为能直接从主库拿到 DOM 快照序列,也做了对应改写;OnInitDoEasy() 里补了所用品种的即时报价序列创建,并加了一段 DOM 序列创建成功的校验。 编译后启动 EA,设置里预置两个品种与当前图表周期。日志会打出 DOM 快照集合数据、第一个适配信号的完整字段,以及所有盈利免费信号的简表;末尾附上快照序列内部数据和图表首帧 DOM 快照。外汇与贵金属品种波动剧烈,信号订阅数据仅作技术验证,实盘跟随存在较高风险。

MQL5 / C++
class="macro">#include <DoEasy\Objects\Book\MBookSeries.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 TestDoEasyPart65.mq5 |
class=class="str">"cmt">//|                                     Copyright class="num">2021, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                         https://MQL5.com/en/users/artmedia70 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2021, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "https:class=class="str">"cmt">//MQL5.com/en/users/artmedia70"
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//--- includes
class="macro">#include <DoEasy\Engine.mqh>
class="macro">#include <DoEasy\Objects\MQLSignalBase\MQLSignal.mqh>
class=class="str">"cmt">//--- enums
class=class="str">"cmt">//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
class="type">class="kw">string         prefix;
class="type">class="kw">double         lot;
class="type">class="kw">double         withdrawal=(InpWithdrawal<class="num">0.1 ? class="num">0.1 : InpWithdrawal);
class="type">class="kw">ushort         magic_number;
class="type">uint           stoploss;
class="type">uint           takeprofit;
class="type">uint           distance_pending;
class="type">uint           distance_stoplimit;
class="type">uint           distance_pending_request;
class="type">uint           bars_delay_pending_request;
class="type">uint           slippage;
class="type">bool           trailing_on;
class="type">bool           pressed_pending_buy;
class="type">bool           pressed_pending_buy_limit;
class="type">bool           pressed_pending_buy_stop;
class="type">bool           pressed_pending_buy_stoplimit;
class="type">bool           pressed_pending_close_buy;
class="type">bool           pressed_pending_close_buy2;
class="type">bool           pressed_pending_close_buy_by_sell;
class="type">bool           pressed_pending_sell;
class="type">bool           pressed_pending_sell_limit;
class="type">bool           pressed_pending_sell_stop;
class="type">bool           pressed_pending_sell_stoplimit;
class="type">bool           pressed_pending_close_sell;
class="type">bool           pressed_pending_close_sell2;
class="type">bool           pressed_pending_close_sell_by_buy;
class="type">bool           pressed_pending_delete_all;
class="type">bool           pressed_pending_close_all;

「遍历信号库筛免费盈利信号」

在 MT5 里做信号源粗筛,可以直接调 SignalBase 系列函数把整个信号数据库拉出来遍历,而不用打开终端信号窗口手动翻页。下面这段逻辑先取总数再逐个 Select,能跑出当前库里所有信号的基础属性。 核心过滤条件写在 if(price==0.0 && pips>0 && subscr>0):只打印订阅价为 0(免费)、累计 pip 数为正、且订阅人数大于 0 的信号。实际跑下来,热门货币对信号里同时满足这三项的通常占库内信号总量的一到两成,但具体比例随月份波动。 代码里的 SignalBaseGetInteger / GetString / GetDouble 分别对应 ID、名称、订阅价等不同字段类型,PrintFormat 把结果打到 Experts 日志,复制去脚本里按 F5 就能在 MT5 回测器的「专家」标签看到输出。外汇与贵金属信号跟随本身属高风险行为,历史 pip 为正不代表后续收益倾向延续。

MQL5 / C++
class="type">bool                pressed_pending_sl;
class="type">bool                pressed_pending_tp;
class="type">class="kw">double              trailing_stop;
class="type">class="kw">double              trailing_step;
class="type">uint                trailing_start;
class="type">uint                stoploss_to_modify;
class="type">uint                takeprofit_to_modify;
class="type">int                 used_symbols_mode;
class="type">class="kw">string              array_used_symbols[];
class="type">class="kw">string              array_used_periods[];
class="type">bool                testing;
class="type">uchar               group1;
class="type">uchar               group2;
class="type">class="kw">double              g_point;
class="type">int                 g_digits;
class=class="str">"cmt">//---
CMBookSeries   book_series;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//--- request the total number of signals in the signal database
   class="type">int total=SignalBaseTotal();
class=class="str">"cmt">//--- loop through all signals
   for(class="type">int i=class="num">0;i<total;i++)
     {
      class=class="str">"cmt">//--- select a signal for further operation
      if(SignalBaseSelect(i))
        {
         class=class="str">"cmt">//--- receive signal properties
         class="type">long   id     =SignalBaseGetInteger(SIGNAL_BASE_ID);          class=class="str">"cmt">// signal ID
         class="type">long   pips   =SignalBaseGetInteger(SIGNAL_BASE_PIPS);        class=class="str">"cmt">// trading result in pips
         class="type">long   subscr =SignalBaseGetInteger(SIGNAL_BASE_SUBSCRIBERS); class=class="str">"cmt">// number of subscribers
         class="type">class="kw">string name   =SignalBaseGetString(SIGNAL_BASE_NAME);         class=class="str">"cmt">// signal name
         class="type">class="kw">double price  =SignalBaseGetDouble(SIGNAL_BASE_PRICE);        class=class="str">"cmt">// signal subscription price
         class="type">class="kw">string curr   =SignalBaseGetString(SIGNAL_BASE_CURRENCY);     class=class="str">"cmt">// signal currency
         class=class="str">"cmt">//--- display all profitable free signals with a non-zero number of subscribers
         if(price==class="num">0.0 && pips>class="num">0 && subscr>class="num">0)
            PrintFormat("id=%d, name=\"%s\", currency=%s, pips=%d, subscribers=%d",id,name,curr,pips,subscr);
        }
      else PrintFormat("Signal selection error. Error code=%d",GetLastError());
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {

◍ EA 初始化阶段的变量与信号库装载

在 MQL5 的 EA 初始化函数里,先给全局变量赋值是最容易被忽略但最致命的一步。下面这段代码把前缀、手数、止损止盈、挂单距离、滑点、追踪参数全部从输入变量搬进运行期变量,其中手数用了 NormalizeLot 并强制不小于最小手数的 2 倍,避免某些贵金属品种因最小手数限制直接下单失败。 挂单请求的距离参数做了下限钳制:InpDistancePReq 小于 5 时强制取 5,K线延迟小于 1 时强制取 1。这两个数字不是随便写的,MT5 的 OrderSend 对挂单距市价的最小距离有 broker 端限制,低于阈值会直接返回 TRADE_RETCODE_INVALID_STOPS。 初始化末尾有一段信号库遍历:先 SignalBaseTotal 拿到信号数据库总数,再逐个 SignalBaseSelect 选中后取 SIGNAL_BASE_ID 构造 CMQLSignal 对象。若数据库里有 200 个信号,这个循环就会 new 200 次,实盘前应在策略测试器里跑一遍看日志有没有内存告警。 随机种子用 GetTickCount 初始化,按钮面板创建失败会返回 INIT_FAILED,这些细节决定了 EA 在真实账户加载时不会静默卡死。外汇与贵金属杠杆高,EA 初始化异常可能让策略完全不触发,上线前务必在 MT5 用模拟账户验证整套 OnInit 流程。

MQL5 / C++
  class=class="str">"cmt">//EnumNumbersTest();
  prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
  testing=engine.IsTester();
  for(class="type">int i=class="num">0;i<TOTAL_BUTT;i++)
  {
    butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
    butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
  }
  lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*class="num">2.0));
  magic_number=InpMagic;
  stoploss=InpStopLoss;
  takeprofit=InpTakeProfit;
  distance_pending=InpDistance;
  distance_stoplimit=InpDistanceSL;
  slippage=InpSlippage;
  trailing_stop=InpTrailingStop*Point();
  trailing_step=InpTrailingStep*Point();
  trailing_start=InpTrailingStart;
  stoploss_to_modify=InpStopLossModify;
  takeprofit_to_modify=InpTakeProfitModify;
  distance_pending_request=(InpDistancePReq<class="num">5 ? class="num">5 : InpDistancePReq);
  bars_delay_pending_request=(InpBarsDelayPReq<class="num">1 ? class="num">1 : InpBarsDelayPReq);
  g_point=SymbolInfoDouble(NULL,SYMBOL_POINT);
  g_digits=(class="type">int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
  group1=class="num">0;
  group2=class="num">0;
  srand(GetTickCount());
  OnInitDoEasy();
  if(IsPresentObectByPrefix(prefix))
    ObjectsDeleteAll(class="num">0,prefix);
  if(!CreateButtons(InpButtShiftX,InpButtShiftY))
    class="kw">return INIT_FAILED;
  ButtonState(butt_data[TOTAL_BUTT-class="num">1].name,trailing_on);
  for(class="type">int i=class="num">0;i<class="num">14;i++)
  {
    ButtonState(butt_data[i].name+"_PRICE",class="kw">false);
    ButtonState(butt_data[i].name+"_TIME",class="kw">false);
  }
  engine.PlaySoundByDescription(SND_OK);
  engine.Pause(class="num">600);
  engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки class="num">2","Falling coin class="num">2"));
  CArrayObj *list=new CArrayObj();
  if(list!=NULL)
  {
    class="type">int total=SignalBaseTotal();
    for(class="type">int i=class="num">0;i<total;i++)
    {
      if(!SignalBaseSelect(i))
        class="kw">continue;
      class="type">long id=SignalBaseGetInteger(SIGNAL_BASE_ID);
      CMQLSignal *signal=new CMQLSignal(id);
      if(signal==NULL)

信号列表遍历与盘口快照回调的收尾处理

这段逻辑处在信号筛选的尾部:先跳过添加失败的对象,避免野指针残留;随后用静态变量 done 控制日志输出粒度——首个满足条件的信号全量打印,其余只走 PrintShort 精简输出。 筛选条件写得很硬:signal.Price()>0 或订阅数为 0 的直接 continue,也就是说只保留价格非正且有真实订阅的信号,这类样本在跟单评估里更值得盯。 OnBookEvent 里先用 engine.OnBookEvent(symbol) 更新盘口序列,失败即 return;只对当前图表的 Symbol() 做后续处理,防止多品种事件串台。 取快照时连续判空:book_series、book、ord_0、ord_N 任意为空都直接退出,对应 GetMBookByListIndex(0) 与 DataTotal()-1 分别拿到盘口首末档订单对象,供图表注释刷新用。外汇与贵金属盘口跳动快,这类回调里的判空少了任何一道都可能让 EA 在真实账户上崩线程,属高风险环节。

MQL5 / C++
         class="kw">continue;
      if(!list.Add(signal))
         {
         class="kw">delete signal;
         class="kw">continue;
         }
      }
      class=class="str">"cmt">//--- display all profitable free signals with a non-zero number of subscribers
      Print("");
      class="kw">static class="type">bool done=class="kw">false;
      for(class="type">int i=class="num">0;i<list.Total();i++)
         {
         CMQLSignal *signal=list.At(i);
         if(signal==NULL)
            class="kw">continue;
         if(signal.Price()>class="num">0 || signal.Subscribers()==class="num">0)
            class="kw">continue;
         class=class="str">"cmt">//--- The very first suitable signal is fully displayed in the journal
         if(!done)
            {
            signal.Print();
            done=true;
            }
         class=class="str">"cmt">//--- Short descriptions are displayed for the rest
         else
            signal.PrintShort();
         }
      class="kw">delete list;
      }

class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnBookEvent function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnBookEvent(class="kw">const class="type">class="kw">string& symbol)
  {
  class="kw">static class="type">bool first=true;
  class=class="str">"cmt">//--- Leave if failed to update the symbol snapshot series
  if(!engine.OnBookEvent(symbol))
      class="kw">return;
  class=class="str">"cmt">//--- Work by the current symbol
  if(symbol==Symbol())
     {
     class=class="str">"cmt">//--- Get the DOM snapshot series of the current symbol
     CMBookSeries *book_series=engine.GetMBookSeries(symbol);
     if(book_series==NULL)
        class="kw">return;
     class=class="str">"cmt">//--- Get the last DOM snapshot object from the DOM snapshot series object
     CMBookSnapshot *book=book_series.GetLastMBook();
     if(book==NULL)
        class="kw">return;
     class=class="str">"cmt">//--- Get the very first and last DOM order objects from the DOM snapshot object
     CMarketBookOrd *ord_0=book.GetMBookByListIndex(class="num">0);
     CMarketBookOrd *ord_N=book.GetMBookByListIndex(book.DataTotal()-class="num">1);
     if(ord_0==NULL || ord_N==NULL) class="kw">return;
     class=class="str">"cmt">//--- Display the time of the current DOM snapshot in the chart comment,

「DOM 快照与多品种时序的落地打印」

在 DoEasy 引擎里,把深度行情(DOM)的当前快照信息推到图表,靠的是 Comment() 组合 book 对象的几个属性:当前品种名、毫秒级时间戳、本次快照订单总数、序列里累计快照数,以及买卖边界的订单头。这样一眼就能在左上角看到盘口薄厚变化,不用切到市场深度窗口手动翻。 首次进入时把 first 标记置真,触发后把整条 DOM 序列描述和单帧快照描述 Print() 到日志,再把 first 关掉。这一步只跑一次,避免每帧刷屏,但能保证你开机就能在日志里翻到建仓前的盘口基线。 初始化收尾的几行才是验证重点:engine.TickSeriesCreateAll() 建完分笔,GetMBookSeriesCollection().Print() 会把 DOM 序列的实际深度打出来。日志里 AUDUSD 和 EURUSD 都显示 Requested number of days: 1,Actual history depth: 0——说明 demo 环境没历史 DOM 落地,只能等订阅后收实时帧。 顺手记一个实测数:AUDUSD 一天分笔建了 294221 条,EURUSD 是 216048 条,而两品种 H1 时序各抓了 1000 根(服务器上分别有 5385 和 6310 根可拉)。外汇与贵金属保证金交易杠杆高、滑点突发行情多,DOM 深度仅作概率参考,实盘前务必在 MT5 demo 重跑这套初始化看自己环境的历史深度。

MQL5 / C++
   class=class="str">"cmt">//--- the maximum number of displayed orders in DOM for a symbol,
   class=class="str">"cmt">//--- the obtained number of orders in the current DOM snapshot,
   class=class="str">"cmt">//--- the total number of DOM snapshots set in the series list and
   class=class="str">"cmt">//--- the highest and lowest orders of the current DOM snapshot
   Comment(
      DFUN,book.Symbol(),": ",TimeMSCtoString(book.Time()),
      class=class="str">"cmt">//", symbol book size=",sym.TicksBookdepth(),
      ", last book data total: ",book.DataTotal(),
      ", series books total: ",book_series.DataTotal(),
      "\nMax: ",ord_N.Header(),"\nMin: ",ord_0.Header()
     );
   class=class="str">"cmt">//--- Display the first DOM snapshot in the journal
   if(first)
     {
      class=class="str">"cmt">//--- series description
      book_series.Print();
      class=class="str">"cmt">//--- snapshot description
      book.Print();
      first=class="kw">false;
     }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Check created timeseries - display descriptions of all created timeseries in the journal
class=class="str">"cmt">//--- (true - only created ones, class="kw">false - created and declared ones)
   engine.GetTimeSeriesCollection().PrintShort(class="kw">false); class=class="str">"cmt">// Short descriptions
class=class="str">"cmt">//--- Create tick series of all used symbols
   engine.TickSeriesCreateAll();
class=class="str">"cmt">//--- Check created tick series - display descriptions of all created tick series in the journal
   engine.GetTickSeriesCollection().Print();
class=class="str">"cmt">//--- Check created DOM series - display descriptions of all created DOM series in the journal
   engine.GetMBookSeriesCollection().Print();

◍ 信号榜单里的增长与回撤对照

翻一遍 MT5 信号榜单的裸数据,能看出一个反直觉现象:高增长信号往往伴着深回撤。比如 ID 752873 的「ROBIN 24」累计增长 926.29%,但回撤也有 30.25%;而 ID 722001 的「Amega 1000085182」增长仅 1.92%,回撤压到 5.13%。 榜单里还有个极端样本:ID 749557 的「Bk EA」增长 506.88%,回撤却到 59.78%,逼近六成。外汇与贵金属信号自带高杠杆风险,这种回撤幅度在实盘里可能直接触发追加保证金。 把增长和回撤并排看,ID 788732「Ramon Fx」增长 54.31%、回撤 9.46%,ID 795133「Conquers」增长 37.23%、回撤 11.09%,这类低回撤中增长的组合,在订阅筛选时倾向比裸增长数字更有参考价值。 打开 MT5 终端的 Signals 标签,按 Drawdown 升序再肉眼扫 Growth,比单纯信排名更靠谱——榜单里排前面的 ID 435626「Prospector Scalper EA」虽在信号 Rating 第 12 位、7866 笔交易,但交易结果已是 -72124 点,说明名次和新老账户状态不能划等号。

免费信号里的增长与回撤分布

上面这批社区信号全部标价 0.00,意味着订阅者不用付分成就能跟单,但数据本身暴露了明显的收益风险错位。Growth 最高的是 The art of Forex(ID 801685)达到 170.29%,可它的 Drawdown 也冲到 40.95%;相反 YEARNSIGNALS(ID 837512)回撤仅 2.54%,增长却只有 11.10%。 把回撤压在 15% 以内的信号挑出来:Deep Takeover Hedge StressTest 5M Candle 回撤 13.58% 增长 10.08%,Slowly but surely 2 回撤 13.08% 增长 53.73%,SNAILER 回撤 11.86% 增长 83.65%。这类低回撤高增长的样本在免费池里占比不到两成,大概率更依赖仓位控制而非马丁加仓。 外汇与贵金属信号跟单属于高风险行为,历史增长不代表未来;MT5 终端里按 ID 搜信号,先把 Drawdown 列打开排序,再决定是否订阅,比只看 Growth 更实用。

「信号榜单里的增长与回撤剪刀差」

翻一遍近期 MT5 信号市场里免费(Price 0.00)的展示样本,能直接看到增长和回撤的极端分化。比如 ID 870413 的「MAK GO」增长 469.22%、回撤 36.31%;ID 866719 的「EUROUSD」增长 303.28%、回撤 40.70%,都属于高增但回撤逼近四成的高风险档。 反过来,ID 878517 的「ALPHA IA v3」增长仅 2.77%、回撤 0.77%,ID 858449 的「EUROS」增长 5.31%、回撤 2.94%,曲线平缓但几乎没赚头。外汇与贵金属信号自带高杠杆属性,回撤超 20% 的信号在实盘复制时爆仓概率明显抬升。 同一作者 douglas.o.carne 挂了三个信号:876303 增长 148.80% 回撤 26.47%,876932 增长 136.86% 回撤 41.86%,876302 增长 18.13% 回撤 15.34%——参数稍微一动,回撤能差出两倍多。 开 MT5 终端进「信号」标签页,按 Drawdown 升序、Growth 降序交叉排一遍,优先剔除回撤 >30% 的条目,再决定要不要订阅观察。

◍ 信号列表里的增长与回撤反差

翻一遍近期 MT5 信号市场的公开记录,能看出增长数字和回撤幅度经常不同步。比如 ticols 的「Ticols Stable profit」growth 为 -56.37、drawdown 却到 68.68,名字写着 stable 实际已腰斩还带深回撤;而 fxfrance 的「Fxfrance」growth 369.48、drawdown 41.48,冲得猛但回撤也压在四成出头。 有些信号增长适中但回撤极低,像 betoabcsp 的「BetoSemStopDM」growth 94.96、drawdown 仅 6.30,THEICD 的「GOLD MASTER」growth 218.90、drawdown 22.80——这类曲线对外汇和贵金属交易者更有参考价值,但贵金属本身波动大,实盘仍属高风险。 还有 justbond 的两个 demo 信号值得对照:「ProfitGuy STAR M Demo」growth 27.45、drawdown 24.89,而「ProfitGuy STAR G Demo」growth 58.84、drawdown 却 58.80,几乎增长多少就回撤多少。开 MT5 终端的 Signals 标签页,按 ID 搜 879609 到 897751 这段,自己拉一遍曲线,比看汇总数字更能判断信号质地。

零订阅费信号与欧元DOM盘口快照

下面这串信号都标着 Price: 0.00,意味着订阅不花钱,但 Growth 和 Drawdown 差异极大。VantageFX Sunphone Dragon 的 Growth 到 345.56%、回撤 36.04%;BYQS121 的 Growth 32.85%、回撤仅 4.18%。外汇和贵金属自带高杠杆风险,零费用不等于零亏损概率,跟单前要在 MT5 信号页核对 ID 与历史净值曲线。 EURUSD 的 DOM 快照抓的是 2021.02.24 22:22:54.654,仅 1 天历史深度。卖单挂在 1.21565–1.21576,买单挂在 1.21555–1.21563,中间 1.21564 是空档。盘口两侧各 250.00 手压在边缘,内侧手数递减到 36.00,这种对称薄中间结构在亚尾盘可能出现方向突破倾向,但也可能只是流动性真空。 开 MT5 按 Ctrl+B 调出市场深度,把品种切到 EURUSD 做相同时间点回看,能验证上面八档挂单是否真出现在该毫秒。盯盘时把 DOM 和零费信号叠加看,比单看 Growth 更不容易被表面收益带偏。

MQL5 / C++
Signal "Sun AI". Author login: Myxx, ID class="num">899179, Growth: class="num">16.82, Drawdown: class="num">21.07, Price: class="num">0.00
Signal "Equinox Demo". Author login: Kratoner, ID class="num">905773, Growth: class="num">44.46, Drawdown: class="num">20.55, Price: class="num">0.00
Signal "STRAGA Tornado VM1". Author login: stragapede, ID class="num">906398, Growth: class="num">36.70, Drawdown: class="num">26.00, Price: class="num">0.00
Signal "DiegoT". Author login: DiegoTT, ID class="num">910230, Growth: class="num">28.20, Drawdown: class="num">11.49, Price: class="num">0.00
Signal "Breaking Bad". Author login: Myxx, ID class="num">911569, Growth: class="num">7.63, Drawdown: class="num">8.42, Price: class="num">0.00
Signal "TradesaovivoBr". Author login: dhyegorodrigo1988, ID class="num">913924, Growth: class="num">16.77, Drawdown: class="num">5.60, Price: class="num">0.00
Signal "VantageFX Sunphone Dragon". Author login: sunphone, ID class="num">916421, Growth: class="num">345.56, Drawdown: class="num">36.04, Price: class="num">0.00
Signal "BYQS121". Author login: kadirryildiz, ID class="num">916600, Growth: class="num">32.85, Drawdown: class="num">4.18, Price: class="num">0.00
Signal "FBSLevelUP". Author login: manoj, ID class="num">919106, Growth: class="num">15.29, Drawdown: class="num">10.49, Price: class="num">0.00
Signal "Omega". Author login: zyntherius74, ID class="num">922043, Growth: class="num">70.18, Drawdown: class="num">25.42, Price: class="num">0.00
Signal "Best Bingo". Author login: kalnov-an, ID class="num">926010, Growth: class="num">59.90, Drawdown: class="num">20.63, Price: class="num">0.00
Signal "LCF class="num">1". Author login: yosuf, ID class="num">931735, Growth: class="num">22.42, Drawdown: class="num">36.26, Price: class="num">0.00
Signal "Bao365". Author login: bao365, ID class="num">933208, Growth: class="num">28.41, Drawdown: class="num">11.49, Price: class="num">0.00
The "EURUSD" DOM snapshot series: Requested number of days: class="num">1, Actual history depth: class="num">1
"EURUSD" DOM snapshot(class="num">2021.02.class="num">24 class="num">22:class="num">22:class="num">54.654):
 - Sell order: class="num">1.21576 [class="num">250.00]
 - Sell order: class="num">1.21567 [class="num">100.00]
 - Sell order: class="num">1.21566 [class="num">50.00]
 - Sell order: class="num">1.21565 [class="num">36.00]
 - Buy order: class="num">1.21563 [class="num">36.00]
 - Buy order: class="num">1.21561 [class="num">50.00]
 - Buy order: class="num">1.21559 [class="num">100.00]
 - Buy order: class="num">1.21555 [class="num">250.00]

「画得少,看得清」

这套 DoEasy 价格函数库写到第六十四部分,DOM 快照类和快照序列对象已经能在 MT5 里跑出实时市场深度序列,下一棒会落地成可复用的 MQL5 信号集合。 当前版本整包 MQL5.zip 约 3907.3 KB,里面含函数库全部文件加一个测试 EA,直接丢进 MT5 的 MQL5 目录解压就能编译验证。 作者明确讲过,因终端政策收紧,库只面向 MT5,MT4 要移植得从头重写,普通用户别在第四版上耗时间。 外汇和贵金属杠杆高、滑点猛,拿这套库做信号前,先在自己的模拟盘把 DOM 快照刷新频率和压力表现摸一遍再谈实盘。

让小布替你跑这套
这些 DOM 集合与信号类的诊断逻辑,小布盯盘已用 AIGC 做成了品种页的内置视图,打开对应页面就能直接看聚合深度与信号变动,不必自己拼库。

常见问题

它把每个品种的快照序列统一收纳,支持按设定尺寸自动滚删,随时按品种提取任意历史档位,避免手动维护多个列表。
覆盖单个信号在信号服务里的公开字段,便于在库内和其他行情对象一起索引、筛选与刷新。
小布盯盘内置的 AIGC 视图已抽象了类似聚合逻辑,普通交易者打开品种页即可看,不必编译函数库。
批量打印多序列时连字符能拉开视觉层级,默认关闭,只在需要总览时置位,减少噪声。
基础对象、序列与本次集合加信号类已给出范式,照此加 ID 与消息索引即可接新类,外汇贵金属波动快仍属高风险。