DoEasy 函数库中的时间序列(第五十四部分):抽象基准指标类的衍生·进阶篇
🧩

DoEasy 函数库中的时间序列(第五十四部分):抽象基准指标类的衍生·进阶篇

(2/3)· 当指标对象只能靠手写参数逐个抓取,集合化派生的类结构能省掉多少重复代码

实战向进阶 第 2/3 篇

接上篇,我们继续深挖函数库里指标对象的构造逻辑。很多人在写 EA 时习惯把每个指标的句柄和参数写死在脚本里,一旦要跟踪同类型多个实例就陷入复制粘贴的泥潭。把衍生类挂到集合上,才是让事件和属性检索自动化的前提。

指标对象的属性读写接口

在 MT5 自定义指标封装里,CIndicator 基类提供了一组直接读写指标属性的成员函数。设置名称用 SetName,设置图表上显示的简名用 SetShortName,两者都走 this.SetProperty 写入对应枚举键。 下面这段是获取类接口,覆盖状态、分组、周期、句柄、空值、名称、简名、品种与类型共 9 个维度。返回类型做了枚举强转,比如 TypeIndicator 把 INDICATOR_PROP_TYPE 转成 ENUM_INDICATOR,Handle 转成 int。 [CODE]…[/CODE] 开 MT5 自建指标类时,直接调 Handle() 拿到的句柄可用于 iCustom 跨指标引用;若返回 0 或 INVALID_HANDLE,大概率是指标未成功初始化,需排查 OnInit 里的创建顺序。外汇与贵金属杠杆高,指标句柄失效会造成信号断流,实盘前务必在策略测试器跑一轮多周期验证。

MQL5 / C++
class="type">void SetName(const class="type">class="kw">string name) { this.SetProperty(INDICATOR_PROP_NAME,name); }
class="type">void SetShortName(const class="type">class="kw">string shortname) { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname); }

class=class="str">"cmt">//--- Return indicator’s(class="num">1) status, (class="num">2) group, (class="num">3) timeframe, (class="num">4) handle, (class="num">5) empty value of buffers, (class="num">6) name, (class="num">7) class="type">short name, (class="num">8) symbol, (class="num">9) type
ENUM_INDICATOR_STATUS Status(class="type">void) const { class="kw">return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);}
ENUM_INDICATOR_GROUP Group(class="type">void) const { class="kw">return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP); }
ENUM_TIMEFRAMES Timeframe(class="type">void) const { class="kw">return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME); }
ENUM_INDICATOR TypeIndicator(class="type">void) const { class="kw">return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE); }
class="type">int Handle(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(INDICATOR_PROP_HANDLE); }
class="type">class="kw">double EmptyValue(class="type">void) const { class="kw">return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE); }
class="type">class="kw">string Name(class="type">void) const { class="kw">return this.GetProperty(INDICATOR_PROP_NAME); }
class="type">class="kw">string ShortName(class="type">void) const { class="kw">return this.GetProperty(INDICATOR_PROP_SHORTNAME); }

「指标对象的属性封装与构造细节」

在自定义指标描述类里,Symbol() 方法直接返回 INDICATOR_PROP_SYMBOL 属性值,相当于把当前指标绑定的交易品种透传出来,方便上层逻辑按品种分流处理。 GetTypeDescription() 已内联实现,返回成员变量 m_ind_type;而状态、分组、周期、空值这四类的描述方法只声明未实现,留给子类去补。注意 GetTypeDescription 里拿到的 m_ind_type 并非原始枚举名。 构造函数 CIndicatorDE 接收 8 个参数,从指标类型、品种、周期到参数数组全塞进对象。关键一行是 m_ind_type=::StringSubstr(::EnumToString(ind_type),4),把枚举字符串砍掉前 4 个字符(如 IND_ 前缀),只留语义部分。 想验证这套封装,把下面代码丢进 MT5 的包含文件,建个指标对象打印 Symbol 和类型描述,看日志里品种与裁剪后的类型名是否对得上。外汇与贵金属波动剧烈,这类封装仅降低代码耦合,不预示任何方向。

MQL5 / C++
class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.GetProperty(INDICATOR_PROP_SYMBOL); }

class=class="str">"cmt">//--- Return description of indicator’s(class="num">1) type, (class="num">2) status, (class="num">3) group, (class="num">4) timeframe, (class="num">5) empty value
class="type">class="kw">string GetTypeDescription(class="type">void) const { class="kw">return m_ind_type; }
class="type">class="kw">string GetStatusDescription(class="type">void) const;
class="type">class="kw">string GetGroupDescription(class="type">void) const;
class="type">class="kw">string GetTimeframeDescription(class="type">void) const;
class="type">class="kw">string GetEmptyValueDescription(class="type">void) const;

class=class="str">"cmt">//--- Display the description of indicator object properties in the journal(full_prop=true - all properties, false - supported ones only)
class="type">void Print(const class="type">bool full_prop=false);
class=class="str">"cmt">//--- Display a class="type">short description of indicator object in the journal(implementation in the descendants)
class="kw">virtual class="type">void PrintShort(class="type">void) {;}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Closed parametric constructor                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
CIndicatorDE::CIndicatorDE(ENUM_INDICATOR ind_type,
                           class="type">class="kw">string symbol,
                           ENUM_TIMEFRAMES timeframe,
                           ENUM_INDICATOR_STATUS status,
                           ENUM_INDICATOR_GROUP group,
                           class="type">class="kw">string name,
                           class="type">class="kw">string shortname,
                           class="type">MqlParam &mql_params[])
  {
class=class="str">"cmt">//--- Set collection ID to the object
   this.m_type=COLLECTION_INDICATORS_ID;
class=class="str">"cmt">//--- Write description of indicator type
   this.m_ind_type=::StringSubstr(::EnumToString(ind_type),class="num">4);

◍ 构造函数里把指标参数灌进对象

在封装指标对象的构造函数中,若传入的参数数组长度大于 0,就需要先把外部参数逐字段拷贝进内部的 m_mql_param 结构数组,再拿去建句柄。下面这段代码就是干这件事的核心路径。 先按传入数组尺寸重设内部数组,再用 for 循环把 type、double_value、integer_value、string_value 四个字段逐个赋值。注意 ArrayResize 的返回值直接当作循环上界,避免越界。 建完句柄后,把状态、类型、分组、周期、句柄本身写进 m_long_prop 整型属性表;空值标记 EMPTY_VALUE 写进双精度属性;交易品种名若为空则回退到当前 Symbol(),名称与短名写进字符串属性。这样对象就具备了自描述能力。 后续 GetPropertyDescription 用三元嵌套把整型属性转成可读文本:不支持的属性会拼上「不支持」提示,支持的则调各自 Description 方法。调试时若某属性显示异常,优先查 SupportProperty 的判断分支。 开 MT5 把这段塞进你自己的指标封装类,断点跟一遍 m_long_prop[INDICATOR_PROP_HANDLE] 的赋值,能确认句柄是否真的建出来。外汇与贵金属指标调用受报价会话影响,实盘前请在模拟盘验证句柄稳定性。

MQL5 / C++
class=class="str">"cmt">//--- If parameter array size passed to constructor is more than zero
class=class="str">"cmt">//--- fill in the array of object parameters with data from the array passed to constructor
   class="type">int count=::ArrayResize(m_mql_param,::ArraySize(mql_params));
   for(class="type">int i=class="num">0;i<count;i++)
     {
       this.m_mql_param[i].type=mql_params[i].type;
       this.m_mql_param[i].double_value=mql_params[i].double_value;
       this.m_mql_param[i].integer_value=mql_params[i].integer_value;
       this.m_mql_param[i].string_value=mql_params[i].string_value;
     }
class=class="str">"cmt">//--- Create indicator handle
   class="type">int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,this.m_mql_param);

class=class="str">"cmt">//--- Save integer properties
   this.m_long_prop[INDICATOR_PROP_STATUS]                       = status;
   this.m_long_prop[INDICATOR_PROP_TYPE]                         = ind_type;
   this.m_long_prop[INDICATOR_PROP_GROUP]                        = group;
   this.m_long_prop[INDICATOR_PROP_TIMEFRAME]                    = timeframe;
   this.m_long_prop[INDICATOR_PROP_HANDLE]                       = handle;

class=class="str">"cmt">//--- Save real properties
   this.m_double_prop[this.IndexProp(INDICATOR_PROP_EMPTY_VALUE)]=EMPTY_VALUE;
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_SYMBOL)]   = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_NAME)]      = name;
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_SHORTNAME)]= shortname;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return description of indicator&class="macro">#x27;s integer class="kw">property               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER class="kw">property)
  {
   class="kw">return
     (
       class="kw">property==INDICATOR_PROP_STATUS       ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS)+
       (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
       )  :
       class="kw">property==INDICATOR_PROP_TYPE         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+
       (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeDescription()
       )  :
       class="kw">property==INDICATOR_PROP_GROUP         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP)+
       (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetGroupDescription()
       )  :
       class="kw">property==INDICATOR_PROP_TIMEFRAME     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+

指标属性读取的兜底分支

这段片段处在指标库属性描述的拼接逻辑里,专门处理两个非默认属性:时间周期与句柄。当 property 不是这两个值时,整段表达式直接返回空串,意味着上层调用若传错枚举,界面上不会显示任何冗余文字。 SupportProperty 先判支持性,不支持就接 MSG_LIB_PROP_NOT_SUPPORTED 文案;支持时时间周期走 GetTimeframeDescription,句柄走 GetProperty 再强转 string。这种三元嵌套把「不支持」和「取值」都压缩在一行返回里,复制进 MT5 的自定义指标类能直接复用。 外汇与贵金属指标在多线程刷新时若乱传 INDICTOR_PROP_XXX,可能静默丢信息,调试时建议先打印 property 枚举值确认落到了哪个分支。

MQL5 / C++
   (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": "+this.GetTimeframeDescription()
   ) :
   class="kw">property==INDICATOR_PROP_HANDLE        ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+
   (!this.SupportProperty(class="kw">property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
   ) :
   ""
 );

「指标对象类」

现在,创建基准抽象指标的衍生对象,该对象将指定有关所创建指标对象的所有信息。 且这些类将用于创建不同类型的指标,并从中获取数据。 在目录 \MQL5\Include\DoEasy\Objects\Indicators\ 里创建一个新的文件夹 Standart ,并在其中创建一个新文件 IndAC.mqh ,其中包含加速振荡器标准指标的 CIndAC 类, 它继承自抽象指标基类 , 该文件与这个类的列表连接 。 由于该类并不太大,因此我们来分析其完整清单: 总共,我们必须制作 38 个这样的类 - 根据标准指标的数量来(我尚未创建自定义指标,因为其实现会略有不同)。 所有这些类均拥有相同的方法,它们的区别仅在于从其构造函数传递给父类的参数有所不同: 在类的输入中,我传递品种名称、时间帧、及已填充指标参数的结构 (在此示例中为加速振荡器)。 在初始化清单中 传递给父类的封闭参数型构造函数(按顺序): 指标类型 — IND_AC, 品种名称,时间帧 指标状态 - 标准 指标分组 - 振荡器 指标名称 - 加速振荡器 指标简称 - AC (品种, 时间帧) 和已填充的指标参数结构 我们已知以前创建的函数库对象其余的所有方法,且它们执行相同的任务。 方法返回对象所支持整数型和实数型属性的标志,若返回 true — 指标对象支持所有属性。 返回指标简述的方法返回以下字符串类型: 在与指标对象类相似的所有其余文件中,区别仅在于类构造函数当中 - 将与指标对应的参数传递给父类的构造函数。 例如,对于指标建仓/派发,类构造函数将如下所示: 如我们所见,此处传递的 参数与标准指标 AD 相对应 : 指标类型 — IND_AD, 品种名称, 时间帧 指标状态 - 标准 指标分组 -交易量 指标名称 - Accumulation/Distribution 指标简称 - AC (品种, 时间帧) 和已填充的指标参数结构 抽象指标基类衍生类的所有文件都已实现,可在文章附件中的 \MQL5\Include\DoEasy\Objects\Indicators\Standart 文件夹中查看它们。 [CODE] <span class="comment">//+------------------------------------------------------------------+</span>

<span class="comment">//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IndAC.mqh</span>

<span class="comment">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

◍ AC与AD指标类的属性接管与构造落地

把指标封装成 CIndAC / CIndAD 这类对象后,最先要补的是属性支持声明。下面两个 SupportProperty 重载直接返回 true,意味着该指标同时认整数型与双精度型属性,调用方无需再判断兼容性,省掉一层防御代码。 PrintShort 负责把状态、名称、品种、周期打到日志:GetStatusDescription 取运行状态,Name 取实例名,Symbol 与 TimeframeDescription 拼出 EURUSD H4 这种可读串。实盘排错时,一行日志就能定位是哪个品种哪个周期的 AC 在异常。 构造器里 CIndAC 写死了 IND_AC、分组 INDICATOR_GROUP_OSCILLATOR、显示名 "Accelerator Oscillator",标签自动拼成 "AC(EURUSD,H4)";CIndAD 则归到 INDICATOR_GROUP_VOLUMES,名为 "Accumulation/Distribution",标签 "AD(...)"。这两段告诉你:换品种周期只传 symbol 和 timeframe 即可,其余元数据在类里锁死。 复制下面代码到 MT5 的 include 指标包装层,建 EURUSD H4 的 AC 实例后调一次 PrintShort,日志若出 "Standard indicator Accelerator Oscillator EURUSD H4",说明封装链路通了。外汇与贵金属波动剧烈,指标封装仅降低编码成本,不预示任何方向。

MQL5 / C++
class="type">bool CIndAC::SupportProperty(ENUM_INDICATOR_PROP_INTEGER class="kw">property)
  {
   class="kw">return true;
  }
class="type">bool CIndAC::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE class="kw">property)
  {
   class="kw">return true;
  }
class="type">void CIndAC::PrintShort(class="type">void)
  {
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe()));
  }
  CIndAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,class="type">MqlParam &mql_param[]) :
   CIndicatorDE(IND_AC,symbol,timeframe,
                INDICATOR_STATUS_STANDART,
                INDICATOR_GROUP_OSCILLATOR,
                "Accelerator Oscillator",
                "AC("+symbol+","+TimeframeDescription(timeframe)+")",mql_param) {}
  CIndAD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,class="type">MqlParam &mql_param[]) :
   CIndicatorDE(IND_AD,symbol,timeframe,
                INDICATOR_STATUS_STANDART,
                INDICATOR_GROUP_VOLUMES,
                "Accumulation/Distribution",
                "AD("+symbol+","+TimeframeDescription(timeframe)+")",mql_param) {}
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到指标集合的事件触发状态,把重复劳动交给小布,你专注决策。

常见问题

该属性用于在指标集合中按标准指标类型快速过滤,例如先筛出全部 IND_MACD 再按其他属性细分,避免遍历全部对象。
受保护部分对衍生类可见,衍生指标对象类可直接继承并使用该数组,不必重新声明,减少冗余代码。
在参数型构造函数中从指标类型输入提取子串,仅保留指标名称如 MACD 作为描述写入对象属性,便于显示和检索。
可以,小布盯盘的 AIGC 模块已内置常见指标集合诊断,打开品种页就能看到事件触发与属性概览,不用自己写监听。
该目录用于存放继承自抽象基准指标的具体标准指标对象类,保持库结构清晰,方便后续扩展自定义指标类。