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

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

第 1/3 篇

「把指标缓冲区接进时间序列类」

DoEasy 函数库做到第五十八部分,核心改动是把「指标缓冲区数据」封装成标准时间序列对象。此前库里的时间序列主要服务价格与成交量,指标算出来的缓冲区只能靠外部数组临时持有,跨周期取历史值很别扭。 新加的 CIndSeries 类直接继承既有时间序列基类,缓冲区索引与时间轴对齐,调用方式和 K 线序列一致。你在 EA 里写 series.GetData(1) 就能拿到上一根指标值,不用再手动算 buffer 位移。 本次示例基于 MT5 环境,作者 Artyom Trishkin 于 2021-03-08 发布,原文展示代码被社区查看 981 次、评论 18 条,说明这类底层封装在中低频策略里确有刚需。 外汇与贵金属杠杆高、滑点跳空频繁,直接拿指标历史值做信号时,要先在策略测试器跑多品种回测,确认缓冲区在休市重连后不会错位。

◍ 指标缓冲区的序列化存储思路

在 MQL5 里做时间序列操控,核心是先解决“存”的问题:把每个指标的多个缓冲区数据,按对应时间序列的长度完整落进一个列表里。缓冲区里的条目数必须和指标所依赖的时间序列条目数一致,否则后续检索和分类都会错位。 这套做法和函数库里既有的集合类思路同源——用统一容器装数据,就能直接复用已有的查找逻辑,不必为每个指标重写一遍搜索代码。今天新写的类也只是把同样能力拆出来单独用。 往后我会补一批分析类,直接吃函数库里现成的集合数据做统计。等于把“存”和“算”切开,你后面想对任一指标缓冲区做任意维度的研究,都不用再从零搭架子。外汇和贵金属行情跳空频繁,这类自行维护的序列在切换品种或周期时要核对数据量,高风险环境下错一条就可能带偏整段分析。

改进库类

指标缓冲区数据的集合类会自动更新所有依据当前柱线创建的指标数据;每当出现一个新的柱线时,它会在指标缓冲区创建新数据,并将其放置到集合中。 在 NewBarObj.mqh 文件里我们已拥有了为时间序列集合而创建的 “New bar” 类。 它存储在函数库目录中的时间序列类文件夹 \MQL5\Include\DoEasyPart57\Objects\Series\ 当中。 现在,还需要跟踪指标缓冲区数据集合类中的新柱线。 因此,将其移动到服务函数和函数库类的文件夹 \MQL5\Include\DoEasy\ Services\ 当中。 删除先前位置的文件。 鉴于 “Timeseries” 类会用到 “New bar” 类,因此在 “Timeseries” 类文件 \MQL5\Include\DoEasy\Objects\Series\ SeriesDE.mqh 中的包含文件模块里 必须更改旧的类文件路径 : 指向新的路径 : 在文件里 \MQL5\Include\DoEasy\ Data.mqh 加入新的消息索引 : ... 和与新添加的索引相对应的 消息文本 : ... 从今天开始,我将创建一个新集合,我们来为其设置 集合 ID 。 除此之外, 为新集合计时器创建参数常量 ,因为指标缓冲区数据将在计时器中自动更新。 将新数据添加到文件 \MQL5\Include\DoEasy\ Defines.mqh 里: 为了在其集合中搜索指标缓冲区数据,必须另外按指标句柄搜索数据(因为数据将与该指标关联,并且对于搜索数据,如果不使用与指标的隶属关系的确切 ID,感觉会很奇怪)。 需为指标数据的整数型属性 添加新属性 ,然后 将此类型属性的数量从 5 增加到 6 : 鉴于我们添加了一个新的整数型属性, 故需将其添加到可能的排序条件列表之中 ,以便能够依据该属性进行搜索和排序: 存储在集合中的指标缓冲区数据由 上一篇文章中创建的指标缓冲区数据类 表示。 现在,将其加进(在文件 \MQL5\Include\DoEasy\Objects\Indicators\ DataInd.mqh 中) 指标句柄的安装方法 ,在该类的对象中存储了缓冲区数据。 还有, 在类构造函数里添加传递指标句柄的参数 ,以及 其缓冲区的数值 。 这样即可在对象创建期间为这些参数设置数值: 在简化访问对象属性的方法模块中, 加入一个返回指标句柄的方法 ,并 重命名了返回指标缓冲区数据数值的方法 (之前的方法称为 PriceValue()): 在类构造函数的主体中,设置创建新对象时传递给它的新属性数值: 在返回整数型属性说明的方法中,添加显示指标句柄说明的代码模块 : [CODE] <span class="comment">//+------------------------------------------------------------------+</span> <span class="comment">//| Include files&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs

「采集引擎的定时器与列表标识常量」

在 MT5 自建行情采集模块时,定时器节奏和容器 ID 必须硬编码成宏,否则运行时调参会牵一发动全身。下面这段常量定义直接决定了历史、实时、指标缓冲各类数据以什么频率落盘。 普通行情序列的采集定时器暂停值设为 64 毫秒,计数器每次累加 16,计数器 ID 用 6 来标记;指标数据序列的暂停值同样是 64 毫秒,步长 16,ID 则为 7。两个定时器精度一致,说明设计者希望指标与裸价格序列在同一种节拍下被抓取。 集合列表用 0x777A 到 0x7781 这一段十六进制空间做区分:历史、市场、事件、账户、品种、序列、缓冲、指标八大类各占一个槽位。你在 EA 里调用 CList 或自建映射时,照这套 ID 起名能避免和多品种插件撞车。 别把 64 毫秒当万能值。黄金或外汇交叉盘在美盘流动性爆发时,Tick 间隔可能低于 10 毫秒,按 64 毫秒采会发现缓冲里丢了几跳,概率上会出现重构 K 线不完整。

MQL5 / C++
class="macro">#define COLLECTION_TS_PAUSE(class="num">64)            class=class="str">"cmt">// Timeseries collection timer pause in milliseconds
class="macro">#define COLLECTION_TS_COUNTER_STEP(class="num">16)            class=class="str">"cmt">// Timeseries timer counter increment
class="macro">#define COLLECTION_TS_COUNTER_ID(class="num">6)             class=class="str">"cmt">// Timeseries timer counter ID
class=class="str">"cmt">//--- Parameters of the timer of indicator data timeseries collection
class="macro">#define COLLECTION_IND_TS_PAUSE(class="num">64)            class=class="str">"cmt">// Pause of the timer of indicator data timeseries collection in milliseconds
class="macro">#define COLLECTION_IND_TS_COUNTER_STEP(class="num">16)            class=class="str">"cmt">// Increment of indicator data timeseries timer counter 
class="macro">#define COLLECTION_IND_TS_COUNTER_ID(class="num">7)             class=class="str">"cmt">// ID of indicator data timeseries timer counter
class=class="str">"cmt">//--- Collection list IDs
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

◍ 指标数据的整数属性与排序基元

在 MT5 里做多指标协同分析时,先把每个指标实例的元数据固化成整数属性,后续检索和排序才不会乱。下面这套枚举把时间、周期、类型、缓冲编号、ID 和句柄全部整数化,总共 6 个属性(IND_DATA_PROP_INTEGER_TOTAL 定义为 6),其中 0 个被标记为跳过不参与排序。 #define COLLECTION_INDICATORS_DATA_ID (0x7782) // 指标数据收集列表的唯一 ID //+------------------------------------------------------------------+

//Integer properties of indicator data

//+------------------------------------------------------------------+ enum ENUM_IND_DATA_PROP_INTEGER { IND_DATA_PROP_TIME = 0, // 指标数据柱周期的开始时间 IND_DATA_PROP_PERIOD, // 指标数据周期(时间框架) IND_DATA_PROP_INDICATOR_TYPE, // 指标类型 IND_DATA_PROP_IND_BUFFER_NUM, // 指标数据缓冲编号 IND_DATA_PROP_IND_ID, // 指标 ID IND_DATA_PROP_IND_HANDLE, // 指标句柄 }; #define IND_DATA_PROP_INTEGER_TOTAL (6) // 整数属性总数=6 #define IND_DATA_PROP_INTEGER_SKIP (0) // 不参与排序的整数属性数=0 //+------------------------------------------------------------------+ //+------------------------------------------------------------------+

//Possible criteria for indicator data sorting

//+------------------------------------------------------------------+ #define FIRST_IND_DATA_DBL_PROP (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP) #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 { //--- 按整数属性排序 SORT_BY_IND_DATA_TIME = 0, // 按指标数据柱周期开始时间排序 SORT_BY_IND_DATA_PERIOD, // 按指标数据周期排序 SORT_BY_IND_DATA_INDICATOR_TYPE, // 按指标类型排序 SORT_BY_IND_DATA_IND_BUFFER_NUM, // 按指标数据缓冲编号排序 SORT_BY_IND_DATA_IND_ID, // 按指标 ID 排序 排序基元用 FIRST_IND_DATA_DBL_PROP 和 FIRST_IND_DATA_STR_PROP 两个宏来切分整数、双精度、字符串三类属性的起始偏移。因为 SKIP 为 0,双精度属性起点就是 6,字符串起点则是 6 加上双精度属性总数再减跳过数。 开 MT5 新建一个 include 头文件,把上面代码原样贴进去编译,能验证 ENUM_SORT_IND_DATA_MODE 里 SORT_BY_IND_DATA_TIME 到 SORT_BY_IND_DATA_IND_ID 五个枚举值连续分布,方便你后面写排序器时直接当数组下标用。外汇与贵金属杠杆高,这类底层结构只解决数据组织问题,不预示任何方向。

MQL5 / C++
class="macro">#define COLLECTION_INDICATORS_DATA_ID(0x7782) class=class="str">"cmt">// Indicator data collection list ID
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Integer properties of indicator data                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_IND_DATA_PROP_INTEGER
  {
   IND_DATA_PROP_TIME = class="num">0,              class=class="str">"cmt">// Start time of indicator data bar period
   IND_DATA_PROP_PERIOD,                class=class="str">"cmt">// Indicator data period(timeframe)
   IND_DATA_PROP_INDICATOR_TYPE,        class=class="str">"cmt">// Indicator type
   IND_DATA_PROP_IND_BUFFER_NUM,        class=class="str">"cmt">// Indicator data buffer number
   IND_DATA_PROP_IND_ID,                class=class="str">"cmt">// Indicator ID
   IND_DATA_PROP_IND_HANDLE,            class=class="str">"cmt">// Indicator handle
   };
class="macro">#define IND_DATA_PROP_INTEGER_TOTAL(class="num">6) class=class="str">"cmt">// Total number of indicator data integer properties
class="macro">#define IND_DATA_PROP_INTEGER_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">//| 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

给指标数据对象挂上句柄与排序键

在自定义指标数据容器里,排序枚举收尾处定义了按指标句柄、缓冲区数值、品种名、指标名及短名排序的常量。其中 SORT_BY_IND_DATA_IND_HANDLE 与 SORT_BY_IND_DATA_BUFFER_VALUE 最常用,前者按 iCustom 返回的句柄归组,后者直接按某根 K 线上缓冲区算出的 double 值排队。 下面这组 setter 把对象的属性一次性补齐:品种周期时间用 SetSymbolPeriod 一次性写入,类型、缓冲区编号、ID、句柄、数值、名称分别由独立方法封装进 SetProperty。注意 SetIndicatorHandle 接收的 handle 就是 IndicatorCreate 或 iCustom 的返回值,写错会导致后续 CopyBuffer 取到空序列。 给对象填完属性后,列表就能按指定键排序。比如多币种多指标扫描时,先 SetIndicatorHandle 再按 SORT_BY_IND_DATA_IND_HANDLE 排,可把同指标不同品种聚成一簇,方便批量比对边际强度。外汇与贵金属波动受杠杆与消息驱动,这类排序只解决组织效率,不预示方向。

MQL5 / C++
SORT_BY_IND_DATA_IND_HANDLE,                                                                     class=class="str">"cmt">// Sort by indicator handle
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
  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">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Set(class="num">1) symbol, timeframe and time for the object, (class="num">2) indicator type, (class="num">3) number of buffers, (class="num">4) number of data buffer,
class=class="str">"cmt">//--- (class="num">5) ID, (class="num">6) handle, (class="num">7) data value, (class="num">8) name, (class="num">9) indicator class="type">class="kw">short name
  class="type">void              SetSymbolPeriod(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">class="kw">datetime time);
  class="type">void              SetIndicatorType(class="kw">const ENUM_INDICATOR type)            { this.SetProperty(IND_DATA_PROP_INDICATOR_TYPE,type);            }
  class="type">void              SetBufferNum(class="kw">const class="type">int num)                            { this.SetProperty(IND_DATA_PROP_IND_BUFFER_NUM,num);            }
  class="type">void              SetIndicatorID(class="kw">const class="type">int id)                           { this.SetProperty(IND_DATA_PROP_IND_ID,id);                    }
  class="type">void              SetIndicatorHandle(class="kw">const class="type">int handle)                   { this.SetProperty(IND_DATA_PROP_IND_HANDLE,handle);            }
  class="type">void              SetBufferValue(class="kw">const class="type">class="kw">double value)                     { this.SetProperty(IND_DATA_PROP_BUFFER_VALUE,value);           }
  class="type">void              SetIndicatorName(class="kw">const class="type">class="kw">string name)                    { this.SetProperty(IND_DATA_PROP_IND_NAME,name);                }
  class="type">void              SetIndicatorShortname(class="kw">const class="type">class="kw">string shortname)          { this.SetProperty(IND_DATA_PROP_IND_SHORTNAME,shortname);      }

class=class="str">"cmt">//--- Compare CDataInd objects with each other by all possible properties(for sorting the lists by a specified class="kw">property of data object )

常见问题

在指标初始化时把缓冲区的句柄和索引传给时间序列类的构造方法,再按缓冲区序号写入对应序列槽位即可。
先定义好每个缓冲区的整数属性(如类型、索引、排序键),再用列表标识常量把同指标的多缓冲区分组,才能稳定落盘。
可以。小布能扫描你挂上去的指标数据对象,比对句柄与排序键是否齐全,并提示缺失的缓冲区配置。
给每个指标分配独立的列表标识常量作为定时器触发标记,避免多个指标数据在回调里互相覆盖。
排序键决定缓冲区在序列里的读取顺序;不挂会导致多缓冲区数据错位,回看历史时对应关系混乱。