DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象(基础篇)
📘

DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象(基础篇)

第 1/3 篇

把多品种多周期序列装进一个对象池

在 MT5 里做跨品种跨周期分析,最笨的办法是每次都调 iClose、iOpen 去抓数据,重复请求会拖慢 EA。DoEasy 思路是把「品种+周期」当成一个键,预先建好时间序列对象,后面直接按键取用。 原文示例里这套结构在 2020-06-09 发布,截至统计页显示有 1925 次查看、23 条讨论,说明多品种序列封装是实盘库的高频刚需,不是玩具代码。 落地时你可以在 MT5 用 #include 挂上这类库,先在初始化里枚举你要盯的 5~8 个外汇/贵金属品种和 M5、H1 周期,把对象一次性建好;之后策略里取 XXXUSD 的 H1 收盘价就走对象方法,不再反复调内置指标句柄。外汇与贵金属杠杆高、滑点突发行情多,任何序列缓存都要加「品种停盘/数据缺口」判断,否则对象里可能是旧柱。

◍ 从单周期柱线集合到多品种时序收纳

上一节我们在 DoEasy 里给单一品种、单一时间帧建了 Bar 对象集合,本质就是按 MT5 术语搭出一条时间序列,并把每根柱线的开高低收、时间等属性填进去。 在这个集合里,你已经能按任意属性做搜索和排序,比如找最近 500 根里实体最大的阴线,或者检测新柱创立后用实时数据刷新对象。 但实战里不可能只盯一个周期。程序往往要同时跑多个品种、多个时间帧,那我们就得让集合数量和实际要用的时间序列一一对应——好在集合是按「品种+周期」这对键值建的,同一个品种开 N 个周期就建 N 个集合。 更顺手的做法是把一个品种的全部周期集合,收进一个「品种时间序列对象」里;再往上建一个公用集合,统一装下不同品种、不同周期的这种对象。外汇与贵金属多周期叠加波动剧烈,高杠杆下风险显著,验证前先在策略测试器里用小资金跑一遍结构是否成立。

「把程序类型塞进基类,再搭一个品种多周期容器」

做跨周期分析时,函数库里一大堆类都得知道自己跑在哪种 MQL5 程序里(EA、指标还是脚本)。与其每个类都重复声明一个存程序类型的变量,不如在 CBaseObj 基类保护段里放一个 m_program_type,构造函数里用 MQLInfoInteger(MQL_PROGRAM_TYPE) 赋值,所有派生类直接继承,省得到处写。 具体落点:在 \MQL5\Include\DoEasy\Objects\BaseObj.mqh 的 CBaseObj 保护部分声明成员变量并构造赋值;CNewBarObj 和 CSeries 改成从 CBaseObj 继承后,原文件里“所有提及”的程序类型变量全部删掉。CSeries 公开段补上 SetSymbol()、SetPeriod() 和返回可用数据总量的方法,实现里对入参做必要检查再写变量,随后把值赋给“New bar”类对象。 品种时间序列对象(CTimeSeries)则是另一层封装:它内部是 CArrayObj 指针数组 m_list_series,每个指针指向一篇里写的单品种单周期时间序列;数组下标严格对应 ENUM_TIMEFRAMES 枚举顺序,所以时间帧和索引能互推。IndexTimeframe() 按时间帧返回枚举序号即列表索引,TimeframeByIndex() 反过来按索引取时间帧。 CTimeSeries 当前版本能做的事很具体:用 SetSymbol() 指定品种;Create(ENUM_TIMEFRAMES, depth) 建单周期或建全周期(depth 为 0 时默认 1000 根柱线历史);Refresh() 刷新指定或全周期数据;IsSynchronized() 查与服务器同步标志。历史深度若不显式传,就走预设的 1000 根。 代码层要把 CSelect 里包含的 Bar.mqh 换成 Series.mqh,Engine.mqh 里把单周期类文件引用替换为 TimeSeries.mqh,新类在基于该库的程序里才可见。外汇与贵金属市场杠杆高、滑点无常,多周期历史回放仅能在 MT5 策略测试器里验证可行性,不代表实盘概率优势。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Base object class for all library objects                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define   CONTROLS_TOTAL(class="num">10)
class CBaseObj : class="kw">public CObject
  {
class="kw">private:
   class="type">int               m_long_prop_total;
   class="type">int               m_double_prop_total;

控件基类的事件与程序类型初始化

在 MT5 自定义控件库里,CBaseObj 构造函数承担了对象生命周期起点的脏活:先把四个动态数组 m_long_prop_event、m_double_prop_event 及其 _prev 版本用 ArrayResize 预扩到 0 容量、预留 100 步增量,避免后续高频写入时反复重分配。 紧接着用 MQLInfoInteger(MQL_PROGRAM_TYPE) 把当前运行环境(EA / 指标 / 脚本)写进 m_program,这一步决定了后面事件路由逻辑是否走 OnChartEvent 还是定时器分支。 账户货币精度也在此敲定:MQL5 下取 ACCOUNT_CURRENCY_DIGITS,非 MQL5 环境硬编码为 2。外汇与贵金属杠杆品种点值计算差一位小数,可能直接影响面板显示与风控数值,开 MT5 后改这个数字验证下报价对齐。 下面这段是构造函数核心,注意 m_program 的强转写法与 tick 结构清零:

MQL5 / C++
CBaseObj::CBaseObj() : m_global_error(ERR_SUCCESS),
                     m_hash_sum(class="num">0),m_hash_sum_prev(class="num">0),
                     m_is_event(false),m_event_code(WRONG_VALUE),
                     m_chart_id_main(::ChartID()),
                     m_chart_id(::ChartID()),
                     m_folder_name(DIRECTORY),
                     m_name(__FUNCTION__),
                     m_long_prop_total(class="num">0),
                     m_double_prop_total(class="num">0),
                     m_first_start(true)
  {
  ::ArrayResize(this.m_long_prop_event,class="num">0,class="num">100);
  ::ArrayResize(this.m_double_prop_event,class="num">0,class="num">100);
  ::ArrayResize(this.m_long_prop_event_prev,class="num">0,class="num">100);
  ::ArrayResize(this.m_double_prop_event_prev,class="num">0,class="num">100);
  ::ZeroMemory(this.m_tick);
  this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
  this.m_digits_currency=(class="macro">#ifdef __MQL5__(class="type">int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) class="macro">#else class="num">2 class="macro">#endif);
  this.m_list_events.Clear();

◍ 新K线对象的私有成员与基类继承

在 MT5 自定义类里把「新K线」封装成对象,第一件事是明确它跑在什么环境。m_program 用 ENUM_PROGRAM_TYPE 记录当前是 EA、指标还是脚本,这决定了后续取价函数该走哪套接口。 m_symbol 和 m_timeframe 把监控目标钉死在某一品种某一周期上,避免多周期逻辑互相串台。m_new_bar_time 存最近一根成型 K 线的开盘时间,是判断「是否出新棒」的核心基准,自动时间管理全靠它比对。 类声明写成 class CNewBarObj : public CBaseObj,说明它复用了一套基础对象框架(BaseObj.mqh 里定义的通用方法)。开 MT5 把这段私有成员抄进你的 mqh,先别写方法,编译看继承链是否报错。

MQL5 / C++
class CNewBarObj : class="kw">public CBaseObj
  {
class="kw">private:
  ENUM_PROGRAM_TYPE m_program;                     class=class="str">"cmt">// Program type
  class="type">class="kw">string            m_symbol;                     class=class="str">"cmt">// Symbol
  ENUM_TIMEFRAMES   m_timeframe;                  class=class="str">"cmt">// Timeframe
  class="type">class="kw">datetime          m_new_bar_time;               class=class="str">"cmt">// New bar time for auto time management

「用类封装把新K线判断从EA里拆出来」

把「是否出新Bar」这件事做成独立类,EA 和指标都能直接调,不用在每个文件里重写时间比对逻辑。类里同时留了自动和手动两套时间管理:自动用 MT5 内置时间流,手动靠外部传入的 time 参数,回测和实时行情切换不用改业务代码。 下面这段声明给出了核心成员与接口。m_prev_time 管自动模式的上一根时间,m_new_bar_time_manual 与 m_prev_time_manual 留给手动模式;GetLastBarDate() 返回当前 Bar 时间,IsNewBar() 与 IsNewBarManual() 分别走两套判定。 [CODE] datetime m_prev_time; // 自动时间管理用的上一根K线时间 datetime m_new_bar_time_manual; // 手动时间管理用的新Bar时间 datetime m_prev_time_manual; // 手动时间管理用的上一根时间 //--- 返回当前Bar数据 datetime GetLastBarDate(const datetime time); public: //--- 设置(1)品种和(2)周期

void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULLsymbol=="" ? ::Symbol() : symbol); }

void SetPeriod(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //--- 手动模式下保存新Bar时间 void SaveNewBarTime(const datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); } //--- 返回(1)品种和(2)周期 string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Period(void) const { return this.m_timeframe; } //--- 返回(1)新Bar时间 datetime TimeNewBar(void) const { return this.m_new_bar_time; } //--- 返回自动/手动模式下的新Bar开启标志 bool IsNewBar(const datetime time); bool IsNewBarManual(const datetime time); //--- 构造函数 CNewBarObj(void) : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)), m_symbol(::Symbol()), [/CODE] 构造函数里顺手用 MQLInfoInteger(MQL_PROGRAM_TYPE) 抓运行环境,EA、脚本、指标进来都能自识别。实盘挂黄金或欧美前,先开 MT5 用一段 M1 数据跑 IsNewBar(),对比 m_new_bar_time 与服务器时间,差几秒就得查时区配置——外汇和贵金属杠杆高,错一根 Bar 可能就让信号晚一单。

MQL5 / C++
class="type">class="kw">datetime m_prev_time; class=class="str">"cmt">// Previous time for auto time management
class="type">class="kw">datetime m_new_bar_time_manual; class=class="str">"cmt">// New bar time for manual time management
class="type">class="kw">datetime m_prev_time_manual; class=class="str">"cmt">// Previous time for manual time management
class=class="str">"cmt">//--- Return the current bar data
class="type">class="kw">datetime GetLastBarDate(const class="type">class="kw">datetime time);
class="kw">public:
class=class="str">"cmt">//--- Set(class="num">1) symbol and(class="num">2) timeframe
class="type">void SetSymbol(const class="type">class="kw">string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); }
class="type">void SetPeriod(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); }
class=class="str">"cmt">//--- Save the new bar time during the manual time management
class="type">void SaveNewBarTime(const class="type">class="kw">datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); }
class=class="str">"cmt">//--- Return(class="num">1) symbol and(class="num">2) timeframe
class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.m_symbol; }
ENUM_TIMEFRAMES Period(class="type">void) const { class="kw">return this.m_timeframe; }
class=class="str">"cmt">//--- Return the(class="num">1) new bar time
class="type">class="kw">datetime TimeNewBar(class="type">void) const { class="kw">return this.m_new_bar_time; }
class=class="str">"cmt">//--- Return the new bar opening flag during the time(class="num">1) auto, (class="num">2) manual management
class="type">bool IsNewBar(const class="type">class="kw">datetime time);
class="type">bool IsNewBarManual(const class="type">class="kw">datetime time);
class=class="str">"cmt">//--- Constructors
CNewBarObj(class="type">void) : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)),
 m_symbol(::Symbol()),

常见问题

把各品种各周期的序列对象统一收进一个对象池,用容器按品种+周期索引,EA里只调接口拿数据,不重复写取数逻辑。
控件基类先完成程序类型与事件初始化,才能把多品种多周期容器挂上去,否则子对象拿不到正确的运行上下文。
可以,把代码结构贴给小布,它能帮你核对基类继承、私有成员和容器初始化是否漏了步骤,并给出修改建议。
建一个K线类继承基类,把新K线私有成员和判断方法封进去,EA只调用类实例,主流程立刻清爽。
基类统一持有程序类型,所有派生对象都能直接复用,避免EA层反复传参,也方便后续扩展指标或脚本形态。