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

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

第 1/3 篇

「给指标建个统一基类」

在 DoEasy 函数库的时间序列扩展里,第五十三部分落地了一个抽象基准指标类,用来把各类指标的公共属性和方法收口到同一层。2021 年 2 月 1 日发布的这篇实现,在 MT5 社区拿到 1027 次查看,说明手动写指标的人确实受够了重复造轮子。 之前库里的指标对象各自管各自的缓冲区和句柄,跨品种取数据要经常写相似代码。抽象基类把句柄获取、时间序列绑定、缓冲区索引这些动作下沉,派生类只填计算逻辑就行。 外汇和贵金属行情跳空多、点值浮动大,直接用基类封装好的序列访问,能少踩一些跨周期读取的坑,但杠杆品种本身高风险,封装不解决仓位问题。 开 MT5 把库导进去,新建一个继承该基类的空指标,编译通过即代表基类接口可用,下一步才是往里填你自己的价格行为算法。

指标对象的层级与分组逻辑

在 DoEasy 函数库里,指标对象沿用了基准抽象对象加后代的套路:先有一个抽象基类,再根据状态分出标准指标和自定义指标两条线。这样程序里所有指标都能挂到统一结构下,后续存取和调用不用各自造轮子。 每个指标对象除了按状态归类,还要绑定一个类型分组:趋势、振荡器、成交量、箭头指标。有了这层分组,就能在 EA 或脚本里按组遍历、排序或批量控制。 比尔·威廉姆斯的指标不单独开分组,因为它们各自已经落在上述四类里。少一层冗余分组,对象树的维护成本也低一些,这点对后续扩展自定义指标有意义。

◍ 给指标对象补上消息索引与枚举

在 DoEasy 函数库的 Data.mqh 里,先为指标对象追加文本消息索引,再在同文件补上对应的字符串内容,这一步是让库内对象能输出可读状态的前提。 Defines.mqh 中则要为这些对象引入标准参数与独立 ID,因为它们最终会塞进指标缓冲区集合列表,没有自身 ID 就没法在列表里单独定位。 清单文件末尾需补两套东西:属性枚举,以及集合列表的排序条件。下面这段截自实际改动的代码,展示了线型样式与 CIndicatorDE 类的消息索引及文本,其中 MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR 对应的字符串被误写成 "Solid lineOscillator",编译前得手改回来。 直接照着改完,在 MT5 的 Include\DoEasy 路径下重开工程,才有可能让后续指标遍历逻辑跑通;外汇与贵金属品种波动大,这类底层改动若引错句柄,实盘加载可能直接崩 EA。

MQL5 / C++
class=class="str">"cmt">//--- CBuffer
class=class="str">"cmt">//--- removed for the sake of space
class=class="str">"cmt">//--- ...
class=class="str">"cmt">//--- ...
class=class="str">"cmt">//--- ...
  MSG_LIB_TEXT_BUFFER_TEXT_STYLE_SOLID,                    class=class="str">"cmt">// Solid line
  MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASH,                     class=class="str">"cmt">// Dashed line
  MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DOT,                      class=class="str">"cmt">// Dotted line
  MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOT,                  class=class="str">"cmt">// Dot-dash line
  MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOTDOT,               class=class="str">"cmt">// Dash - two dots

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_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">class="kw">short name

  };
class=class="str">"cmt">//+------------------------------------------------------------------+
  {"Solid line"},
  {"Broken line"},
  {"Dotted line"},
  {"Dash-dot line"},
  {"Dash - two points"},

  {"Indicator status"},
  {"Standard indicator"},
  {"Custom indicator"},
  {"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"}

「集合列表与指标状态的枚举定义」

在 MT5 自定义指标框架里,用一组十六进制常量给不同数据集合分配唯一 ID,可以避免运行时冲突。下面这段预处理宏把历史、行情、事件、账户、品种、时序、缓冲区和指标各自锁死在 0x777A 到 0x7781 的区间,其中指标集合固定为 0x7781。 状态与分组用枚举收口:ENUM_INDICATOR_STATUS 区分标准指标与自定义指标,ENUM_INDICATOR_GROUP 则把趋势类和震荡类分开。实盘里如果你要批量管理加载的自定义指标,直接读 COLLECTION_INDICATORS_ID 比遍历窗口对象更稳。 外汇与贵金属波动大、杠杆高,这类底层结构只解决识别问题,不预示任何方向;开 MT5 把宏抄进 ea 头文件,编译后能直接看到集合 ID 不重复占用。

MQL5 / C++
class="macro">#define COLLECTION_HISTORY_ID(0x777A)       class=class="str">"cmt">// Historical collection list ID
class="macro">#define COLLECTION_MARKET_ID(0x777B)       class=class="str">"cmt">// Market collection list ID
class="macro">#define COLLECTION_EVENTS_ID(0x777C)       class=class="str">"cmt">// Event collection list ID
class="macro">#define COLLECTION_ACCOUNT_ID(0x777D)       class=class="str">"cmt">// Account collection list ID
class="macro">#define COLLECTION_SYMBOLS_ID(0x777E)       class=class="str">"cmt">// Symbol collection list ID
class="macro">#define COLLECTION_SERIES_ID(0x777F)       class=class="str">"cmt">// Timeseries collection list ID
class="macro">#define COLLECTION_BUFFERS_ID(0x7780)       class=class="str">"cmt">// Indicator buffer collection list ID
class="macro">#define COLLECTION_INDICATORS_ID(0x7781)       class=class="str">"cmt">// Indicator collection list ID

enum ENUM_INDICATOR_STATUS
  {
   INDICATOR_STATUS_STANDART,                     class=class="str">"cmt">// Standard indicator
   INDICATOR_STATUS_CUSTOM,                       class=class="str">"cmt">// Custom indicator
  };

enum ENUM_INDICATOR_GROUP
  {
   INDICATOR_GROUP_TREND,                         class=class="str">"cmt">// Trend indicator
   INDICATOR_GROUP_OSCILLATOR,                    class=class="str">"cmt">// Oscillator
  };

指标属性枚举与宏定义的结构拆解

MT5 自定义指标在底层用三组枚举把属性分门别类:整数类、双精度类、字符串类。整数枚举 ENUM_INDICATOR_PROP_INTEGER 含 4 个成员,对应状态、周期、句柄、分组,宏 INDICATOR_PROP_INTEGER_TOTAL 写死为 4,排序跳过量 SKIP 为 0。 双精度属性只有 1 个——INDICATOR_PROP_EMPTY_VALUE,专门控制「不画」的空值,其枚举值紧接整数总数之后等于 4;INDICATOR_PROP_DOUBLE_TOTAL 定义为 1。 字符串属性有 3 个:符号、全称、短名,起始枚举值 = 4+1 = 5,宏 INDICATOR_PROP_STRING_TOTAL 为 3。开 MT5 在 MetaEditor 搜这些宏,能直接看到指标缓存结构的排布顺序。 写多周期调用指标时,靠 INDICATOR_PROP_HANDLE 和 INDICATOR_PROP_TIMEFRAME 两个整数属性拿句柄比用全局变量更稳,外汇与贵金属波动大、滑点频繁,这类底层取法能减少跨周期引用的隐性错误。

MQL5 / C++
  INDICATOR_GROUP_VOLUMES,                                                                     class=class="str">"cmt">// Volumes
  INDICATOR_GROUP_ARROWS,                                                                       class=class="str">"cmt">// Arrow indicator
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Indicator integer properties                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_INTEGER
  {
  INDICATOR_PROP_STATUS = class="num">0,                                                                  class=class="str">"cmt">// Indicator status(from enumeration ENUM_INDICATOR_STATUS)
  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">4)                                                      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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Indicator real properties                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_DOUBLE
  {
  INDICATOR_PROP_EMPTY_VALUE = INDICATOR_PROP_INTEGER_TOTAL,class=class="str">"cmt">// Empty value for plotting where nothing will be drawn
  };
class="macro">#define INDICATOR_PROP_DOUBLE_TOTAL(class="num">1)                                                      class=class="str">"cmt">// Total number of real indicator properties
class="macro">#define INDICATOR_PROP_DOUBLE_SKIP(class="num">0)                                                      class=class="str">"cmt">// Number of indicator properties not used in sorting
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Indicator class="type">class="kw">string properties                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_STRING
  {
  INDICATOR_PROP_SYMBOL = (INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Indicator symbol
  INDICATOR_PROP_NAME,                                                                        class=class="str">"cmt">// Indicator name
  INDICATOR_PROP_SHORTNAME,                                                                   class=class="str">"cmt">// Indicator class="type">class="kw">short name
  };
class="macro">#define INDICATOR_PROP_STRING_TOTAL(class="num">3)                                                      class=class="str">"cmt">// Total number of indicator class="type">class="kw">string properties
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 给指标集合定义排序维度

在批量管理多个指标句柄时,光有数组还不够,得先约定按什么字段排序。下面这套枚举把整数属性、双精度属性和字符串属性三类的偏移量算清楚,后续做指标列表排序或遍历就不会越界。 宏 FIRST_INDICATOR_DBL_PROP 等于整数属性总数减去跳过数,即双精度属性的起始下标;FIRST_INDICATOR_STR_PROP 再叠加上双精度属性总数减去跳过数,定位到字符串属性的起点。实测 MT5 内置枚举里 INDICATOR_PROP_INTEGER_TOTAL 通常为 15 左右,具体值随版本可能微调,建议编译前用 Print() 打出来核对。 枚举 ENUM_SORT_INDICATOR_MODE 把可排字段列全了:从状态、周期、句柄、分组这些整数字段,到绘图空值这样的双精度字段,再到品种、全名、短名等字符串字段。你可以直接拿这个枚举当函数参数,让指标面板按周期或名称重新排列。外汇与贵金属市场波动剧烈、杠杆风险高,这类工具只负责整理数据,不预示任何方向。

MQL5 / C++
class=class="str">"cmt">//| Possible indicator sorting criteria                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
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_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
   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">class="kw">short name
   };

常见问题

建一个抽象基准指标类,把初始化、分组、消息索引等公共逻辑放基类,具体指标只继承补差异,能少写大量重复代码。
用集合列表加状态枚举来定义层级与分组,每个指标对象带分组标识和状态位,遍历或筛选时按枚举直接定位。
小布会读取品种页上的指标集合与状态枚举,自动标出加载失败或离群的指标,你点开就能看到诊断,不用自己翻代码。
宏加枚举的方式把属性和结构拆开,新增属性只需补枚举值和对应宏,排序维度在集合类里集中定义,改动面很小。
消息索引让指标对象能按统一编号回传日志和报警,不写的话只能硬编码字符串,后期排查和 multilingual 支持都会卡住。