DoEasy 函数库中的时间序列(第五十四部分):抽象基准指标类的衍生·进阶篇
(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 里的创建顺序。外汇与贵金属杠杆高,指标句柄失效会造成信号断流,实盘前务必在策略测试器跑一轮多周期验证。
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 和类型描述,看日志里品种与裁剪后的类型名是否对得上。外汇与贵金属波动剧烈,这类封装仅降低代码耦合,不预示任何方向。
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] 的赋值,能确认句柄是否真的建出来。外汇与贵金属指标调用受报价会话影响,实盘前请在模拟盘验证句柄稳定性。
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 枚举值确认落到了哪个分支。
(!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">// | IndAC.mqh | </span> |
|---|
<span class="comment">//|
◍ 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",说明封装链路通了。外汇与贵金属波动剧烈,指标封装仅降低编码成本,不预示任何方向。
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) {}