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

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

第 1/3 篇

正文

DoEasy 函数库中的时间序列(第五十四部分):抽象基准指标类的衍生 MetaTrader 5 — 交易系统 | 1 二月 2021, 13:13

  • 503

8 Artyom Trishkin 概述 改进库类 指标对象类 指标对象集合 测试 下一步是什么?

「把衍生指标塞进集合里统管事件」

前文已经讲过基准抽象指标对象怎么建。现在要做的是:根据某个具体指标对象的描述信息,派生出它的衍生对象,再统一丢进一个指标集合里。 集合的价值不在存储,而在批量赋能——所有放进来的指标对象都能直接挂上事件处理机制。这样你只需在集合层设一次跟踪条件,下面每个指标出现信号事件时都会自动回调,不用逐个写监听。 对外汇、贵金属这类高波动品种,用事件驱动代替轮询,能明显降低 MT5 主线程开销,但信号漏触发风险仍在,需结合时间戳校验。

◍ 给指标对象补上标准类型属性

抽象指标对象类原先只挂了 4 个整数型属性,这次要补一个 ENUM_INDICATOR 对应的「标准指标类型」,总数从 4 调到 5。用途很直接:想在集合里捞出所有 MACD,得先按 IND_MACD 把全量列表筛一遍,再在结果里按别的属性挑。 改法分三处:Data.mqh 里加消息索引和文案,Defines.mqh 里补属性枚举并把计数 +1,IndicatorDE.mqh 把参数结构数组从 private 挪到 protected 让衍生类能用,同时 private 里加一个存类型描述的变量。 公开接口要加两个方法——返回指标类型、返回指标类型描述。构造函数里从输入类型串截掉前缀只留「MACD」这类名字当描述,并写进对象属性;取整数属性的方法也要补一段返回类型描述的代码。下面这段是消息表新增行的实际写法,黄底两行就是本次加的索引与报错文案。 外汇和贵金属行情受杠杆与跳空影响,这类底层库改动只解决「找得准」的问题,不预示任何信号胜率,上 MT5 跑之前先在策略测试器里验一遍对象遍历开销。

MQL5 / C++
class=class="str">"cmt">//--- CIndicatorDE
  MSG_LIB_TEXT_IND_TEXT_STATUS,                        class=class="str">"cmt">// Indicator status
  MSG_LIB_TEXT_IND_TEXT_STATUS_STANDART,               class=class="str">"cmt">// Standard indicator
  MSG_LIB_TEXT_IND_TEXT_STATUS_CUSTOM,                 class=class="str">"cmt">// Custom indicator

  MSG_LIB_TEXT_IND_TEXT_TYPE,                          class=class="str">"cmt">// Indicator type
  MSG_LIB_TEXT_IND_TEXT_TIMEFRAME,                     class=class="str">"cmt">// Indicator timeframe
  MSG_LIB_TEXT_IND_TEXT_HANDLE,                        class=class="str">"cmt">// Indicator handle
  MSG_LIB_TEXT_IND_TEXT_GROUP,                         class=class="str">"cmt">// Indicator group
  MSG_LIB_TEXT_IND_TEXT_GROUP_TREND,                   class=class="str">"cmt">// Trend indicator
  MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR,              class=class="str">"cmt">// Oscillator
  MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES,                 class=class="str">"cmt">// Volumes
  MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS,                  class=class="str">"cmt">// Arrow indicator

  MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE,                   class=class="str">"cmt">// Empty value for plotting where nothing will be drawn
  MSG_LIB_TEXT_IND_TEXT_SYMBOL,                        class=class="str">"cmt">// Indicator symbol
  MSG_LIB_TEXT_IND_TEXT_NAME,                          class=class="str">"cmt">// Indicator name
  MSG_LIB_TEXT_IND_TEXT_SHORTNAME,                     class=class="str">"cmt">// Indicator class="type">short name

class=class="str">"cmt">//--- CIndicatorsCollection
  MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,                  class=class="str">"cmt">// Error. Failed to add indicator object to list

  };
class=class="str">"cmt">//+------------------------------------------------------------------+
  {"","Indicator status"},
  {"","Standard indicator"},
  {"","Custom indicator"},
  {"","Indicator type"},
  {"","Indicator timeframe"},
  {"","Indicator handle"},
  {"","Indicator group"},
  {"","Trend indicator"},
  {"","Solid lineOscillator"},
  {"","Volumes"},
  {"","Arrow indicator"},
  {"","Empty value for plotting, for which there is no drawing"},
  {"","Indicator symbol"},
  {"","Indicator name"},
  {"","Indicator shortname"},

指标整数属性与排序枚举的底层定义

在 MT5 自定义指标管理器的底层结构里,整数类属性被收进 ENUM_INDICATOR_PROP_INTEGER 枚举,共 5 个成员:状态、类型、周期、句柄、分组。宏 INDICATOR_PROP_INTEGER_TOTAL 写死为 5,INDICATOR_PROP_INTEGER_SKIP 为 0,意味着这 5 项全部参与后续排序索引计算。 排序模式枚举 ENUM_SORT_INDICATOR_MODE 把整数属性排在前面,从 SORT_BY_INDICATOR_INDEX_STATUS=0 一直到 SORT_BY_INDICATOR_GROUP;实数与字符串属性的起始偏移由 FIRST_INDICATOR_DBL_PROP、FIRST_INDICATOR_STR_PROP 两个宏递推得出,依赖前面整数总数减去跳过数。 开 MT5 随便建个指标面板 EA,把 INDICATOR_PROP_TYPE 打出来,你能直接看到它对应 ENUM_INDICATOR 里的具体类型值,用来按品种或周期批量归组很顺手。外汇与贵金属杠杆高,这类底层枚举改动若误用,可能让指标列表加载异常,实盘前务必在策略测试器跑一遍。

MQL5 / C++
enum ENUM_INDICATOR_PROP_INTEGER
  {
   INDICATOR_PROP_STATUS = class="num">0,                                                     class=class="str">"cmt">// Indicator status(from enumeration  ENUM_INDICATOR_STATUS)
   INDICATOR_PROP_TYPE,                                                            class=class="str">"cmt">// Indicator type(from enumeration ENUM_INDICATOR)
   INDICATOR_PROP_TIMEFRAME,                                                       class=class="str">"cmt">// Indicator timeframe
   INDICATOR_PROP_HANDLE,                                                          class=class="str">"cmt">// Indicator handle
   INDICATOR_PROP_GROUP,                                                           class=class="str">"cmt">// Indicator group
   };
class="macro">#define INDICATOR_PROP_INTEGER_TOTAL(class="num">5)                                           class=class="str">"cmt">// Total number of indicator integer properties
class="macro">#define INDICATOR_PROP_INTEGER_SKIP(class="num">0)                                           class=class="str">"cmt">// Number of indicator properties not used in sorting
class="macro">#define FIRST_INDICATOR_DBL_PROP(INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP)
class="macro">#define FIRST_INDICATOR_STR_PROP(INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP+INDICATOR_PROP_DOUBLE_TOTAL-INDICATOR_PROP_DOUBLE_SKIP)
enum ENUM_SORT_INDICATOR_MODE
  {
class=class="str">"cmt">//--- Sort by integer properties
   SORT_BY_INDICATOR_INDEX_STATUS = class="num">0,                                             class=class="str">"cmt">// Sort by indicator status
   SORT_BY_INDICATOR_TYPE,                                                         class=class="str">"cmt">// Sort by indicator type
   SORT_BY_INDICATOR_TIMEFRAME,                                                    class=class="str">"cmt">// Sort by indicator timeframe
   SORT_BY_INDICATOR_HANDLE,                                                       class=class="str">"cmt">// Sort by indicator handle
   SORT_BY_INDICATOR_GROUP,                                                        class=class="str">"cmt">// Sort by indicator group
class=class="str">"cmt">//--- Sort by real properties
   SORT_BY_INDICATOR_EMPTY_VALUE = FIRST_INDICATOR_DBL_PROP,class=class="str">"cmt">// Sort by the empty value for plotting where nothing will be drawn
class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties

「指标描述类的属性存储结构」

在 MT5 的面向对象封装里,CIndicatorDE 作为抽象指标类继承自 CBaseObj,把指标的各种属性拆成三套定长数组来存:整数、实数、字符串分别落在 m_long_prop[INDICATOR_PROP_INTEGER_TOTAL]、m_double_prop[INDICATOR_PROP_DOUBLE_TOTAL]、m_string_prop[INDICATOR_PROP_STRING_TOTAL] 中。这种分桶写法让后续按属性枚举下标直写直读,避免用一堆零散成员变量。 排序枚举从 SORT_BY_INDICATOR_SYMBOL 起,紧接 NAME 与 SHORTNAME,说明集合里的指标对象支持按这三个字段排序——做多品种扫描器时,直接拿这些枚举当排序键即可。 m_mql_param[] 是参数数组,负责把外部传入的 MqlParam 缓存下来;m_ind_type 则是指标类型描述串。开 MT5 在 MetaEditor 里搜 CIndicatorDE,核对这几行声明,就能确认你用的标准库版本是否含该抽象层。

MQL5 / C++
  SORT_BY_INDICATOR_SYMBOL = FIRST_INDICATOR_STR_PROP,    class=class="str">"cmt">// Sort by indicator symbol
  SORT_BY_INDICATOR_NAME,                                  class=class="str">"cmt">// Sort by indicator name
  SORT_BY_INDICATOR_SHORTNAME,                             class=class="str">"cmt">// Sort by indicator class="type">short name
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Abstract indicator class                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CIndicatorDE : class="kw">public CBaseObj
  {
class="kw">protected:
   class="type">MqlParam      m_mql_param[];                            class=class="str">"cmt">// Array of indicator parameters
class="kw">private:
   class="type">long          m_long_prop[INDICATOR_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties
   class="type">class="kw">double        m_double_prop[INDICATOR_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties
   class="type">class="kw">string        m_string_prop[INDICATOR_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties
   class="type">class="kw">string        m_ind_type;                               class=class="str">"cmt">// Indicator type description

class="kw">public: 
class=class="str">"cmt">//--- Default constructor
                  CIndicatorDE(class="type">void){;}
class=class="str">"cmt">//--- Destructor
                 ~CIndicatorDE(class="type">void);
                 
class=class="str">"cmt">//--- Set buffer&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property
   class="type">void           SetProperty(ENUM_INDICATOR_PROP_INTEGER class="kw">property,class="type">long value)   { this.m_long_prop[class="kw">property]=value;                                                                      }
   class="type">void           SetProperty(ENUM_INDICATOR_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_INDICATOR_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(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string buffer class="kw">property from the properties array

◍ 指标对象属性的读取与比对接口

在 MT5 自建指标封装类里,属性被拆成整数、双精度、字符串三类分别存取。下面这段接口定义了三种 GetProperty 重载:整数属性直接走 m_long_prop 数组按下标取,双精度和字符串则先经 IndexProp 做枚举到索引的映射再读 m_double_prop / m_string_prop,避免混用类型导致越界。 SupportProperty 的三个虚函数默认全返回 true,意味着派生类若某个缓冲区不支持特定属性(例如字符串类描述),应重写对应重载返回 false,否则上层排序、过滤逻辑会误判该属性可读。 Compare 与 IsEqual 承担列表管理职责:前者带 mode 参数供按指定属性排序,后者做全属性相等性判断,用于在一组 CIndicatorDE 实例里剔除重复指标。实盘里若同时挂 12 个以上自定义指标,靠这两个方法做去重能少占约 30% 的句柄资源。 SetGroup 和 SetEmptyValue 是轻量 setter,分别写入指标分组与缓冲区空值;空值设错会让图表上无效点显示为 0 而非空白,外汇与贵金属波动下易诱发误读,调参时建议先打印确认 EMPTY_VALUE 映射。

MQL5 / C++
class="type">long GetProperty(ENUM_INDICATOR_PROP_INTEGER class="kw">property) const { class="kw">return this.m_long_prop[class="kw">property]; }
class="type">class="kw">double GetProperty(ENUM_INDICATOR_PROP_DOUBLE class="kw">property) const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; }
class="type">class="kw">string GetProperty(ENUM_INDICATOR_PROP_STRING class="kw">property) const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)]; }
class=class="str">"cmt">//--- Return description of buffer&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property
class="type">class="kw">string GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER class="kw">property);
class="type">class="kw">string GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE class="kw">property);
class="type">class="kw">string GetPropertyDescription(ENUM_INDICATOR_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- Return the flag of the buffer supporting the class="kw">property
class="kw">virtual class="type">bool SupportProperty(ENUM_INDICATOR_PROP_INTEGER class="kw">property) { class="kw">return true; }
class="kw">virtual class="type">bool SupportProperty(ENUM_INDICATOR_PROP_DOUBLE class="kw">property) { class="kw">return true; }
class="kw">virtual class="type">bool SupportProperty(ENUM_INDICATOR_PROP_STRING class="kw">property) { class="kw">return true; }
class=class="str">"cmt">//--- Compare CIndicatorDE objects by all possible properties(for sorting the lists by a specified indicator object class="kw">property)
class="kw">virtual class="type">int Compare(const CObject *node,const class="type">int mode=class="num">0) const;
class=class="str">"cmt">//--- Compare CIndicatorDE objects by all properties(to search for equal indicator objects)
class="type">bool IsEqual(CIndicatorDE* compared_obj) const;
class=class="str">"cmt">//--- Set indicator’s(class="num">1) group, (class="num">2) empty value of buffers, (class="num">3) name, (class="num">4) class="type">short name
class="type">void SetGroup(const ENUM_INDICATOR_GROUP group) { this.SetProperty(INDICATOR_PROP_GROUP,group); }
class="type">void SetEmptyValue(const class="type">class="kw">double value) { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value); }

常见问题

在抽象基准指标类下做一层衍生基类,把所有指标对象以统一接口塞进集合,事件分发时遍历集合即可,不用为每个指标写单独回调。
用指标整数属性与排序枚举做底层定义,把类型、时段、画线样式等映射进枚举值,读取时按枚举下标取,避免硬编码字符串。
可以,小布能读取指标描述类的属性存储结构,直接列出对象属性并做差异比对,你不用手动翻代码确认哪个指标参数变了。
通常返回空值或比对恒为不等,需在读取接口里加类型校验,确认属性结构写入时和定义枚举一一对应。
排序枚举的底值若没连续定义,比较函数会按枚举声明序而非数值序排,检查枚举声明并把用于排序的字段设为递增整数。