DoEasy 函数库中的时间序列(第五十三部分):抽象基准指标类(基础篇)
「给指标建个统一基类」
在 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。
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 不重复占用。
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 两个整数属性拿句柄比用全局变量更稳,外汇与贵金属波动大、滑点频繁,这类底层取法能减少跨周期引用的隐性错误。
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 把可排字段列全了:从状态、周期、句柄、分组这些整数字段,到绘图空值这样的双精度字段,再到品种、全名、短名等字符串字段。你可以直接拿这个枚举当函数参数,让指标面板按周期或名称重新排列。外汇与贵金属市场波动剧烈、杠杆风险高,这类工具只负责整理数据,不预示任何方向。
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 };