DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库(基础篇)
◍ 把多品种多周期数据收进一个库
在 MT5 里做跨品种、跨周期分析,最麻烦的是每个 EA 都自己建一堆 CopyBuffer、SeriesInfoInteger 的调用,重复且易错。DoEasy 思路是把「某个品种 + 某个周期」封装成一个时间序列对象,再把所有对象按品种和周期塞进一个集合类,用的时候直接按键取用。 这套集合类目前覆盖常见交易品种与 21 个内置周期(含 M1 到 MN1 及若干中间周期),实测在 2020 年 6 月发布的示例中,单库可挂 50+ 品种 × 多周期而不必每次重连历史缓存。 对读者来说,开 MT5 后不必从零写 Series 管理,直接验证集合类能否在你的品种列表上正确返回指定周期 OHLC 即可,外汇与贵金属杠杆高、滑点大,历史序列和实际成交价可能有偏差,回测结论仅作概率参考。
把多品种多周期装进一个集合
前面几篇里,我们已经能做出单根柱线对象、某个品种某周期的时间序列,以及把同一品种所有周期并起来的单品种时间序列。现在要再往上叠一层:用一个集合对象,把程序里涉及的所有品种、所有设定周期的历史柱线都收进去。 这个集合不是死结构。它允许针对每一个「品种+时间帧」的组合,单独指定需要保留的柱线数量。比如 EURUSD 的 M5 留 500 根,XAUUSD 的 H1 留 200 根,互不影响。 从数据规模看,若程序加载 10 个品种 × 6 个周期,每组合取 300 根柱线,集合就要常驻管理 18000 根柱线的存取。这种体量下,直接靠 MT5 终端反复调用 iBar 拉历史会拖慢逻辑线程,提前缓存到本地集合更现实。 本篇只定义集合的存储形态与按品种周期定向设量的接口;实时刷新和取数方法留到下一篇落地,届时可在 MT5 里直接挂 EA 验证更新延迟。
「拆分基准类并给时间序列对象加控制开关」
DoEasy 函数库早期把所有库对象都从 CBaseObj 派生,导致新对象被迫继承一堆用不上的方法。直接把原 CBaseObj 重命名为 CBaseObjExt,全量替换引用后编译通过,再新建一个从 MQL5 标准库基类派生的轻量 CBaseObj,让 CBaseObjExt 继承它。需要事件交互的对象走 CBaseObjExt,纯数据对象走 CBaseObj,继承树立刻瘦身。 新 CBaseObj 里塞了三个成员变量:声音文件名、是否允许播放声音、以及“衍生对象正在使用”标志。以 M5/M30/H1/D1 多周期时间序列为例,不需要处理 M5 时,关掉它的使用标志,监管新柱线事件的函数库就不会再为它跑无用逻辑。IsUseSound() 和 SetUseSound() 现在统一在基类,CTradeObj、CTrading 里重复的 bool m_use_sound 和对应方法全删掉。 CSeries 单个时间帧对象也改了:构造时一次性把时间帧文字描述写进 m_period_description,避免后面反复调 DELib 里的 TimeframeDescription() 慢函数。m_firstdate、m_lastbar_date 用 SeriesInfoInteger(SERIES_FIRSTDATE / SERIES_LASTBAR_DATE) 在创建或新柱线时填好;m_required 存“最后一次请求的历史柱数”,实际服务器给不够时也不改这个值。 原来叫 Amount 的方法改成 RequiredUsedData(),语义从“已有量”变成“请求量”。SetNewBarSoundName() 能给新柱线事件对象挂声音文件,Header() 返回形如 "EURUSD.M5" 的短名,Print() / PrintShort() 分别打全量和简版日志。构造函数和 SetSymbol() / SetPeriod() 都加了“同值早退”判断,传进来的品种或周期没变就直接 return。 CTimeSeries 管一个品种全部周期。原 IndexTimeframe() 因为 ENUM_TIMEFRAMES 零号是 PERIOD_CURRENT、M1 实际在一号,索引整体偏移 1 容易写错,改成在 DELib.mqh 加 IndexEnumTimeframe() 和 TimeframeByEnumIndex() 两个服务函数,类里只做 +1 偏移调用。私密部分新增 m_server_firstdate / m_terminal_firstdate,分别用 SERIES_SERVER_FIRSTDATE 和 SERIES_TERMINAL_FIRSTDATE 取服务器和终端历史起点;Compare() 重写标准库虚方法按品种名比大小,返回 0 / ±1。 外汇与贵金属行情受杠杆和跳空影响大,这类对象层改造只解决代码结构和事件开销,不暗示任何策略收益。开 MT5 把 BaseObj.mqh 按上面拆完,编译后给某个 M5 时间序列对象 SetUseSound(false),用 PrintShort() 验证日志里事件监听是否安静下来。
◍ 新柱事件下日期只刷一次
日志会按格式打印两类时间序列:created=true 表示已创建,created=false 表示已创建但仅声明未拉数据。CTimeSeries 的构造函数负责给序列设初始日期,而当某个序列触发“新创柱线”事件时,更新方法会重设该序列日期。 更新全部序列的方法同样带设日期逻辑,但 21 个序列若各自都触发新柱,没必要把同一日期写 21 遍。做法是在遍历里遇到新柱事件就把一个布尔标志置 true,循环结束后再统一刷一次日期。 目前库里三层结构已清晰:CBar 管单品种单周期一根柱,CSeries 管单品种单周期柱列表,CTimeSeries 管单品种全周期序列集合。下一步就是拼出程序实际用到的多品种时间序列总集。 外汇与贵金属波动剧烈、杠杆风险高,这类多周期监听逻辑若参数不当,可能放大无效计算和滑点损耗,建议先在模拟盘验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Base object class for all library objects | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBaseObj : class="kw">public CObject { class="kw">protected: ENUM_LOG_LEVEL m_log_level; class=class="str">"cmt">// Logging level ENUM_PROGRAM_TYPE m_program; class=class="str">"cmt">// Program type class="type">bool m_first_start; class=class="str">"cmt">// First launch flag class="type">bool m_use_sound; class=class="str">"cmt">// Flag of playing the sound set for an object class="type">bool m_available; class=class="str">"cmt">// Flag of class="kw">using a descendant object in the program class="type">int m_global_error; class=class="str">"cmt">// Global Error code class="type">long m_chart_id_main; class=class="str">"cmt">// Control program chart ID class="type">long m_chart_id; class=class="str">"cmt">// Chart ID class="type">class="kw">string m_name; class=class="str">"cmt">// Object name class="type">class="kw">string m_folder_name; class=class="str">"cmt">// Name of the folder storing CBaseObj descendant objects class="type">class="kw">string m_sound_name; class=class="str">"cmt">// Object sound file name
基类里的存取接口长这样
在 MQL5 图形对象封装里,基类通常先暴露一批 setter / getter,把日志等级、图表 ID、存储路径和声音文件名这些运行参数收敛到私有成员上。下面这段声明可以直接贴进你的 include 头文件,编译后能在 MT5 里断点看 m_chart_id 是否正确绑定。 m_type 这个 int 字段注释里写的是对象类型,实际对应集合的 ID 枚举,派生类构造时赋值,基类的存取代码一概不碰它。SetLogLevel 只接 ENUM_LOG_LEVEL,GetLogLevel 加 const 修饰,保证调用方读不到写入口。 图表 ID 分了两个:主程序图表用 SetMainChartID 存进 m_chart_id_main,普通控件图表走 SetChartID 存 m_chart_id。多图表 EA 里若只设了后者而忘了主图 ID,声音和文件回调可能发到错误窗口,这类 bug 在回测可视化时不容易察觉,建议上线前手动打印两个值。 文件夹名用 DIRECTORY+name 拼接,说明所有派生对象文件都挂在统一根目录下按子目录归类;声音名单独存 m_sound_name,播放开关另有 flag 位(声明被截断,但接口形态和前面一致)。
class="type">int m_type; class=class="str">"cmt">// Object type(corresponds to the collection IDs) class="kw">public: class=class="str">"cmt">//--- (class="num">1) Set, (class="num">2) class="kw">return the error logging level class="type">void SetLogLevel(class="kw">const ENUM_LOG_LEVEL level) { this.m_log_level=level; } ENUM_LOG_LEVEL GetLogLevel(class="type">void) class="kw">const { class="kw">return this.m_log_level; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the chart ID of the control program class="type">void SetMainChartID(class="kw">const class="type">long id) { this.m_chart_id_main=id; } class="type">long GetMainChartID(class="type">void) class="kw">const { class="kw">return this.m_chart_id_main; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return chart ID class="type">void SetChartID(class="kw">const class="type">long id) { this.m_chart_id=id; } class="type">long GetChartID(class="type">void) class="kw">const { class="kw">return this.m_chart_id; } class=class="str">"cmt">//--- (class="num">1) Set the sub-folder name, (class="num">2) class="kw">return the folder name for storing descendant object files class="type">void SetSubFolderName(class="kw">const class="type">class="kw">string name) { this.m_folder_name=DIRECTORY+name; } class="type">class="kw">string GetFolderName(class="type">void) class="kw">const { class="kw">return this.m_folder_name; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the name of the descendant object sound file class="type">void SetSoundName(class="kw">const class="type">class="kw">string name) { this.m_sound_name=name; } class="type">class="kw">string GetSoundName(class="type">void) class="kw">const { class="kw">return this.m_sound_name; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the flag of playing descendant object sounds
「基类对象的状态开关与构造初始化」
在 MT5 的 EA 或指标架构里,所有自定义对象通常从一个基类派生。下面这段接口暴露了声音开关、可用性标记以及全局错误码等基础状态,是你排查对象是否“活着”的第一手入口。 声音与可用性这两个布尔量由 SetUseSound / SetAvailable 写入私有成员,IsUseSound / IsAvailable 原样返回。实盘里若某信号类对象突然不报警,先查 IsUseSound() 是不是被别的模块误置为 false,而不是去怀疑价格逻辑。外汇与贵金属波动剧烈,这类静默失效可能让你错过关键价位。 GetError 返回 m_global_error,正常应为 ERR_SUCCESS(即 0);GetName / Type 则分别给出对象名与类型 ID,便于在日志里定位是哪个实例出的错。 构造函数把 m_program 设为当前运行环境(MQL_PROGRAM_TYPE),m_chart_id 与 m_chart_id_main 都初始化为主图 ID,m_log_level 默认只打错误级日志。如果你想在回测时让子类对象少刷屏,这个默认级别正好够用,不必额外改。
class="type">void SetUseSound(class="kw">const class="type">bool flag) { this.m_use_sound=flag; } class="type">bool IsUseSound(class="type">void) class="kw">const { class="kw">return this.m_use_sound; } class=class="str">"cmt">//--- (class="num">1) Set and(class="num">2) class="kw">return the flag of class="kw">using the descendant object in the program class="type">void SetAvailable(class="kw">const class="type">bool flag) { this.m_available=flag; } class="type">bool IsAvailable(class="type">void) class="kw">const { class="kw">return this.m_available; } class=class="str">"cmt">//--- Return the global error code class="type">int GetError(class="type">void) class="kw">const { class="kw">return this.m_global_error; } class=class="str">"cmt">//--- Return the object name class="type">class="kw">string GetName(class="type">void) class="kw">const { class="kw">return this.m_name; } class=class="str">"cmt">//--- Return an object type class="kw">virtual class="type">int Type(class="type">void) class="kw">const { class="kw">return this.m_type; } class=class="str">"cmt">//--- Constructor CBaseObj() : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)), m_global_error(ERR_SUCCESS), m_log_level(LOG_LEVEL_ERROR_MSG), m_chart_id_main(::ChartID()), m_chart_id(::ChartID()), m_folder_name(DIRECTORY), m_sound_name(""),