DoEasy 函数库中的时间序列(第五十七部分):指标缓冲区数据对象(基础篇)
📘

DoEasy 函数库中的时间序列(第五十七部分):指标缓冲区数据对象(基础篇)

第 1/3 篇

把指标缓冲区封装成可复用数据对象

在 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> &nbsp;&nbsp; MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Error. Failed to add indicator object to the list</span> &nbsp;&nbsp; MSG_LIB_SYS_INVALID_IND_POINTER,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">// Error. Invalid pointer to indicator object is passed</span> &nbsp;&nbsp; MSG_LIB_SYS_IND_ID_EXIST,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// Error. Indicator object with ID already exists</span> &nbsp;&nbsp; <span class="comment">//--- CDataInd</span> &nbsp;&nbsp; <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 的每个值,能直接验证偏移是否如代码所示连续。

MQL5 / C++
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)。实盘加载多个同名指标的不同周期实例时,用短名排序更不容易混淆。 外汇与贵金属品种波动受消息面影响大,多指标排序仅为管理方便,不预示任何方向,请自担高风险。

MQL5 / C++
  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 - ,我们可以在所有已创建的指标对象中依据此属性快速搜索,得到早前创建的指标对象的缓冲区数据。 由对象定义的 指标缓冲区编号 ,创建指标对象时依据的 品种和时间帧 ,以及由创建对象定义的缓冲区数据 开始时间

常见问题

先定义缓冲区对象的属性和方法,把每根 K 线的 buffer 值存进数组,再用类统一管理读写,避免每次都重算。
按库里预定义的枚举填,例如按时间或数值方向排序,缺省项用默认枚举值,不要手写未声明的常量。
小布可以粘贴代码自动拆解每段含义并标出缓冲区读写位置,帮你省去逐行读源码的时间。
为了统一描述开盘价、收盘价、自定义 buffer 等字段,后续排序和检索直接调枚举,少写判断分支。
若只动底层对象接口,上层调用传参方式不变就基本不用改,动到枚举名才需全局替换。