DoEasy 函数库中的时间序列(第五十七部分):指标缓冲区数据对象(基础篇)
把指标缓冲区封装成可复用数据对象
在 DoEasy 函数库的时间序列体系里,指标缓冲区不再是 EA 里随手引用的数组,而是被抽象成一个独立的数据对象。这样做的好处是:调用方不需要关心缓冲区的索引映射和跨周期同步细节,直接拿对象方法取值即可。
- 年 2 月 26 日发布的这一版示例,在 MT5 社区获得 1370 次查看,说明把指标缓冲区对象化是实盘开发者高频碰到的痛点。对象内部维护了缓冲区类型、所属时间帧与偏移量,对外只暴露标准化接口。
要验证这套结构是否适合你的策略,直接在 MT5 里新建一个指标,把原有 IndicatorBuffer 数组改用库里的缓冲区对象接管,观察编译后是否减少了重复的取值判空代码。外汇与贵金属品种波动剧烈,对象化封装只解决代码结构问题,不降低交易本身的高风险。
「把指标缓冲区拆成可检索的对象」
函数库里的数据组织方式,本质是一组同类型对象的集合列表。指标缓冲区与早前建的序列对象结构相近,也能塞进集合里,按需要挑选、排序、过滤,从而抽出子集做横向比较。 今天先落一个最小数据对象:它装一根柱线上某个指标缓冲区的全部信息,同时带出该缓冲区隶属的指标标识、以及这根柱线自身的数据。对每个指标的每个缓冲区、时间序列的每根柱线,都会生成独立对象,再按品种+时间帧归到对应集合列表。 直接调指标拿某根柱线数据当然可行,眼下看这层封装像多此一举。但后续文章要建跨指标的集合缓存,靠这种对象能秒级定位任意指标的任意缓冲区历史,跑基准对照。外汇与贵金属市场高波动,这类结构先铺好,后面回测与 AIGC 批量扫描才不至于每次都重新拉数。
◍ 改进库类
首先,像往常一样,添加新的函数库消息。 在文件 \MQL5\Include\DoEasy\ Data.mqh 里加入新的消息索引 : 且进一步 - 添加与新索引相对应的 消息文本 : 由于指标缓冲区数据对象会存储在集合列表当中,因此,为了对象的搜索和排序,必须接收存储在列表中的其他函数库对象的所有固有属性。 在文件 \MQL5\Include\DoEasy\ Defines.mqh 里定义所有必须的对象的新属性 — 对象整数型属性: 按时间排序是主要排序类型,其中所有数据将按其在终端中的指标缓冲区顺序进行组织。 时间帧值包含在整数型属性当中,如此即可在不同的时间帧上比较这两个指标缓冲区的值。 指标类型将包含来自 ENUM_INDICATOR 枚举的数值。 指标缓冲区编号 - 依据指标缓冲区的数量,从零开始的顺序编号。 指标 ID - 依据该指标的必要属性数据,可找到需由程序设置数值的 ID。 我们已在 上一篇文章中 对此进行了讨论。 对象的实数型属性: 在此,只有一个属性可用 - 指标缓冲区中的一个值,该值对应于所创建指标数据对象的柱线。 对象的字符串型属性: 依据这些属性值,将能够根据所计算指标的品种\指标名称和简称来选择和排序集合数据。 现在,将对象所有已创建的属性添加到可能的排序条件枚举之中: [CODE] <span class="comment">//--- CIndicatorsCollection</span> MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST, <span class="comment">// Error. Failed to add indicator object to the list</span> MSG_LIB_SYS_INVALID_IND_POINTER, <span class="comment">// Error. Invalid pointer to indicator object is passed</span> MSG_LIB_SYS_IND_ID_EXIST, <span class="comment">// Error. Indicator object with ID already exists</span> <span class="comment">//--- CDataInd</span> <span style="background-color:rgb(255, 242, 153);">MSG_LIB_TEXT_IND_D
指标数据属性与排序枚举的底层定义
在 MT5 的指标数据容器里,双精度属性目前只挂了一个:IND_DATA_PROP_BUFFER_VALUE,它承接整数属性总数之后的偏移位,专门存缓冲区的数值。宏 IND_DATA_PROP_DOUBLE_TOTAL 写死为 1,IND_DATA_PROP_DOUBLE_SKIP 为 0,说明这类实数属性不参与排序跳过逻辑,结构很干净。 字符串属性则有 3 个:IND_DATA_PROP_SYMBOL、IND_DATA_PROP_IND_NAME、IND_DATA_PROP_IND_SHORTNAME,起始偏移 = 整数总数 + 双精度总数。宏 IND_DATA_PROP_STRING_TOTAL 等于 3,如果你自己扩展指标元数据,改这两个宏和枚举要同步,否则 Collection 排序会错位。 排序模式枚举 ENUM_SORT_IND_DATA_MODE 把整数、实数、字符串三类属性拼成连续索引。SORT_BY_IND_DATA_BUFFER_VALUE 用 FIRST_IND_DATA_DBL_PROP 定位,SORT_BY_IND_DATA_SYMBOL 用 FIRST_IND_DATA_STR_PROP 定位,这两个宏都扣掉了对应的 SKIP 量。开 MT5 新建一个 indicator collection 脚本,打印 ENUM_SORT_IND_DATA_MODE 的每个值,能直接验证偏移是否如代码所示连续。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Indicator data real properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_IND_DATA_PROP_DOUBLE { class=class="str">"cmt">//--- bar data IND_DATA_PROP_BUFFER_VALUE = IND_DATA_PROP_INTEGER_TOTAL,class=class="str">"cmt">// Indicator data value }; class="macro">#define IND_DATA_PROP_DOUBLE_TOTAL(class="num">1) class=class="str">"cmt">// Total number of indicator data real properties class="macro">#define IND_DATA_PROP_DOUBLE_SKIP(class="num">0) class=class="str">"cmt">// Number of indicator data properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Indicator data class="type">class="kw">string properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_IND_DATA_PROP_STRING { IND_DATA_PROP_SYMBOL = (IND_DATA_PROP_INTEGER_TOTAL+IND_DATA_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Indicator data symbol IND_DATA_PROP_IND_NAME, class=class="str">"cmt">// Indicator name IND_DATA_PROP_IND_SHORTNAME, class=class="str">"cmt">// Indicator class="type">class="kw">short name }; class="macro">#define IND_DATA_PROP_STRING_TOTAL(class="num">3) class=class="str">"cmt">// Total number of class="type">class="kw">string properties of indicator data class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible criteria for indicator data sorting | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_IND_DATA_DBL_PROP(IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP) class="macro">#define FIRST_IND_DATA_STR_PROP(IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP+IND_DATA_PROP_DOUBLE_TOTAL-IND_DATA_PROP_DOUBLE_SKIP) enum ENUM_SORT_IND_DATA_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_IND_DATA_TIME = class="num">0, class=class="str">"cmt">// Sort by bar period start time of indicator data SORT_BY_IND_DATA_PERIOD, class=class="str">"cmt">// Sort by indicator data period(timeframe) SORT_BY_IND_DATA_INDICATOR_TYPE, class=class="str">"cmt">// Sort by indicator type SORT_BY_IND_DATA_IND_BUFFER_NUM, class=class="str">"cmt">// Sort by indicator data buffer number SORT_BY_IND_DATA_IND_ID, class=class="str">"cmt">// Sort by indicator ID class=class="str">"cmt">//--- Sort by real properties SORT_BY_IND_DATA_BUFFER_VALUE = FIRST_IND_DATA_DBL_PROP, class=class="str">"cmt">// Sort by indicator data value class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties SORT_BY_IND_DATA_SYMBOL = FIRST_IND_DATA_STR_PROP, class=class="str">"cmt">// Sort by indicator data symbol
「指标集合的排序枚举怎么填」
在 MQL5 里处理多个指标句柄的集合时,排序方式由一组枚举常量决定,直接写在数组或结构体定义里即可。 下面这段枚举给出了两种常见排序依据:按指标完整名称(IND_NAME)或按指标短名(IND_SHORTNAME)。实盘加载多个同名指标的不同周期实例时,用短名排序更不容易混淆。 外汇与贵金属品种波动受消息面影响大,多指标排序仅为管理方便,不预示任何方向,请自担高风险。
SORT_BY_IND_DATA_IND_NAME, class=class="str">"cmt">// Sort by indicator name SORT_BY_IND_DATA_IND_SHORTNAME, class=class="str">"cmt">// Sort by indicator class="type">class="kw">short name }; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 指标缓冲区数据对象
对象属性已准备就绪。 现在,创建一个新对象,可存储一个指标的一个缓冲区数据。 在函数库文件夹 \MQL5\Include\ DoEasy in folder \Objects\ Indicators\ 里的 的 DataInd.mqh 当中创建一个新类 CDataInd: 该类派生自 所有函数库对象的基准对象 CBaseObj 。 实际上,该对象包含了函数库对象的标准字段和方法,并且与 文章第三十五部份 中所分析的柱线对象相同,但与柱线对象相比,它的属性较少(对象的所有属性 - 指标缓冲区数据已在上面的枚举中进行了定义)。 我们来分析指标缓冲区数据对象的类主体: 我们来简要分析一下类的内容。 类的私密部分含有: 三个数组 存储相应的对象属性 - 整数型、实数型和字符串型。 该方法计算对象属性在相应数组中的真实索引 。 变量 - 类成员,存储其他对象属性的数值 。 该类的公开部分含有: 将传递的对象属性值写入整数型、实数型和字符串型属性数组的方法 。 该方法从数组里返回所请求的整数型、实数型或字符串型属性值 。 虚方法返回该对象支持每个属性的标志 。 这些方法应在该对象的衍生对象中实现,且如果衍生对象不支持指定的属性,则应返回 false 。 在指标缓冲区数据的对象支持所有属性,则方法返回 true 。 在第一篇文章 当中,我们讨论了函数库对象的整体结构。 在此,我们将简要探究其余类方法的实现。 设置和返回对象属性的所有附加方法 仅在编写程序时提供更多便利;它们只是复制 设置方法 和 接收对象属性 的操作 — 因此函数库用户不必记住对象属性枚举中的常量名称,而是为正在浏览的这些属性设置和接收数值的附加方法名称。 虚拟方法 Compare() 按指定的属性比较两个对象。 它在标准库的 CObject 基准对象类中定义的,如果数值相等,则应返回零;如果所比较的数值之一或高、或低,则应分别返回 1 或 -1。 为了搜索和排序,调用标准库的 Search() 方法。 该方法应在衍生类中重新定义: 判断指标缓冲区数据的两个相同对象的方法 服务于比较两个数据对象,且仅当两个比较对象的所有字段都相等时,才返回 true : 在时间序列中设置品种、时间帧和数据对象索引的方法 : 在日志里显示所有对象属性说明的方法 : 在三个循环中显示对象属性数组每个属性的描述。 如果不支持该属性,它不会在日志里显示 full_prop 方法的输入参数是否为 false (默认)。 在日志中显示简短对象描述的虚拟方法 : 该方法遵照以下格式显示标准指标缓冲区数据的描述: 对于自定义指标: 可利用 SetIndicatorShortname() 方法更改已包含在缓冲区数据描述中的 指标简称 。 该方法可在衍生对象中更改,以便显示在衍生对象中实现的相对应数据对象的其他描述。 返回对象的整数型、实数型和字符串型属性描述的方法 : 这些方法接收相应的属性,并根据其值返回已添加到上述 Datas.mqh 文件中设置的文本说明。 该类含有两个构造函数。 默认情况下,第一个构造函数会创建一个空的数据对象,然后必须用所有必要的数据填充该对象。 第二个是参数化构造函数;它为了创建含有指定基本属性的对象,接收必要的数据: 一次性为对象指定: 指标类型 ,其为该对象定义的缓冲区数据, 指标 ID - ,我们可以在所有已创建的指标对象中依据此属性快速搜索,得到早前创建的指标对象的缓冲区数据。 由对象定义的 指标缓冲区编号 ,创建指标对象时依据的 品种和时间帧 ,以及由创建对象定义的缓冲区数据 开始时间