DoEasy 函数库中的时间序列(第六十一部分):品种即时报价序列集合(基础篇)
📘

DoEasy 函数库中的时间序列(第六十一部分):品种即时报价序列集合(基础篇)

第 1/3 篇

◍ DoEasy 里的即时报价序列集合长什么样

DoEasy 函数库把品种即时报价封装成序列集合,让多品种、多周期 tick 数据能在同一套接口下被遍历。对做价格行为复盘的人来说,这意味着不必自己写一大堆 SymbolInfoTick 轮询,直接拿集合对象就能拉某一刻的买卖价。 原文示例里标注了 2021 年 3 月 24 日 11:28 的快照,当时这篇材料在站内有 1957 次浏览、2 条评论——说明这类底层数据结构改造的关注度不算低,但也没到爆款程度。 外汇和贵金属市场高杠杆、滑点随机,直接拿即时报价序列做信号前,务必在 MT5 策略测试器里用真实tick回测验证,别凭集合能跑通就默认逻辑无误。

「把多品种报价列表收进一个集合」

上篇已经做好了单个品种的即时报价数据列表类,能在运行时收集并保存指定天数的 tick 数据。但实盘里程序往往要同时盯好几个品种,给每个品种都单独建一个列表太散,调用和更新都不顺手。 这里的做法是用一个常规列表,把各个品种的列表指针统一收纳。底层基于标准库 CObject 及其派生类实例指针的动态数组,也就是在数组里存的是“指向某个品种报价列表的指针”,而不是数据本身。 这套集合的构造思路和函数库里已有的集合类一致:保存、更新、接收、再用于统计分析时,都能直接从这个集合里按品种取到对应的报价数据。外汇与贵金属 tick 流受流动性与跳空影响,多品种并发处理时务必在 MT5 里验证指针生命周期,避免悬空引用。

把多品种报价塞进一个集合类

在 \MQL5\Include\DoEasy\Collections\ 下新建 TickSeriesCollection.mqh,让它能按品种名统一管理即时报价序列。这个类继承自函数库基准对象类,内部用 CListObj 类型的 m_list 存对象——CListObj 只是标准库 CArrayObj 的薄封装,唯一作用是重写 Type() 虚方法返回数组类型 ID,构造函数里默认给 m_type 赋 0x7778(与 CArrayObj 一致)。 集合靠品种名做索引。IndexTickSeries() 先建一个临时空 CTickSeries 对象、设品种名、在已排序列表里查位置,删临时对象后把索引吐出来;找不到或建对象失败就返回 NULL。GetTickseries() 则拿这个索引调 CArrayObj::At() 返回对象指针,索引为 -1 时同样返回 NULL。 用法标志和天数都是批量或单点设置:SetTickSeriesFlags() 对单品种置标,SetAllTickSeriesFlags() 跑全表做逻辑与,只要有一个没置上 true,res 变量就落回 false。SetTickSeriesDays() / SetAllTickSeriesDays() 套路一样,控制每只品种回拉几天报价历史。 取数侧有三个出口:GetTickByListIndex() 按列表序号拿某笔报价;GetLastTick() 和 GetLastTickMsc() 按时间取,后者精确到毫秒。CTickSeries::GetTick() 同时间可能撞多笔,只回最晚那笔,因为相关性最强。IsNewTick() 现在只是占位,刷新能力留到后续文章。 CreateTickSeries() 给单品种建序列,返回 CTickSeries::Create() 大于零的标志;CreateAllTickSeries() 全表建。RefreshTickSeries() / RefreshAllTickSeries() 负责更新,目前 CTickSeries 里还没实装。Print() / PrintShort() 把列表 dump 到日志便于排查。 真正把品种接进来的是 CEngine(\MQL5\Include\DoEasy\Engine.mqh)。私密段声明 CTickSeriesCollection 对象,SetUsedSymbols() 里加一个天数参数,默认走 Defines.mqh 里的 TICKSERIES_DEFAULT_DAYS_COUNT(零表示一天)。调一次该方法,时间序列和报价序列两个集合就同时成型,外部用公开方法拿集合指针即可。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                     TickSeriesCollection.mqh |
class=class="str">"cmt">//|                    Copyright class="num">2021, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2021, MetaQuotes Software Corp."

◍ 多品种 Tick 序列的容器类怎么搭

做跨品种价格行为分析时,逐个管理每个品种的 tick 流会很乱。把一堆 CTickSeries 对象装进一个集合类,调用层只跟集合对话,扩展品种时不用改主逻辑。 下面这段 MQL5 类声明就是干这事:CTickSeriesCollection 继承 CBaseObj,内部用 CListObj m_list 存各品种 tick 序列。公开方法里,GetList() 直接吐出底层列表指针,DataTotal() 返回当前已装入的序列个数——这两个在调试多品种订阅是否生效时最常用。 别把正态当圣经 m_list.Total() 返回的是列表节点数,不是实际收到 tick 的品种数。若某品种在 CreateCollection 时建了对象但行情没过来,Total() 照样计数,验证时得配合具体 tick 对象里的数据长度判断。 外汇与贵金属品种波动时段错位明显,这种集合在伦敦-纽约重叠段可能同时跑 6~8 个序列,凌晨可能只剩 2~3 个有活 tick,内存和 CPU 占用差异可能达三倍,实盘前建议在 MT5 用 Print(DataTotal()) 打点确认。

MQL5 / C++
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "ListObj.mqh"
class="macro">#include "..\Objects\Ticks\TickSeries.mqh"
class="macro">#include "..\Objects\Symbols\Symbol.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Collection of symbol tick series                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTickSeriesCollection : class="kw">public CBaseObj
  {
class="kw">private:
   CListObj                  m_list;                       class=class="str">"cmt">// List of used symbol tick series
class=class="str">"cmt">//--- Return the tick series index by symbol name
   class="type">int                       IndexTickSeries(class="kw">const class="type">class="kw">string symbol);
class="kw">public:
class=class="str">"cmt">//--- Return(class="num">1) itself and(class="num">2) tick series collection list and(class="num">3) the number of tick series in the list
   CTickSeriesCollection    *GetObject(class="type">void)              { class="kw">return &this;            }
   CArrayObj                *GetList(class="type">void)                { class="kw">return &this.m_list;    }
   class="type">int                       DataTotal(class="type">void)        class="kw">const { class="kw">return this.m_list.Total(); }
class=class="str">"cmt">//--- Return the pointer to the tick series object(class="num">1) by symbol and(class="num">2) by index in the list
   CTickSeries              *GetTickseries(class="kw">const class="type">class="kw">string symbol);
   CTickSeries              *GetTickseries(class="kw">const class="type">int index);
class=class="str">"cmt">//--- Create a collection list of symbol tick series
   class="type">bool                      CreateCollection(class="kw">const CArrayObj *list_symbols,class="kw">const class="type">uint required=class="num">0);
class=class="str">"cmt">//--- Set the flag of using the tick series of(class="num">1) a specified symbol and(class="num">2) all symbols
   class="type">void                      SetAvailableTickSeries(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">bool flag=true);
   class="type">void                      SetAvailableTickSeries(class="kw">const class="type">bool flag=true);
class=class="str">"cmt">//--- Return the flag of using the tick series of(class="num">1) a specified symbol and(class="num">2) all symbols

「Tick 序列集合类的接口骨架」

在 MT5 里做逐笔tick回测或实时盯盘,先得搞清楚 CTickSeriesCollection 这个类对外暴露了哪些能力。它把‘某个品种’和‘全部品种’两套操作都做了重载,调用时按有没有传 symbol 参数来区分。 判断历史 tick 是否可用,有两个重载:传具体品种名就查那个品种,传 void 则查全局。设置需要加载的历史天数也是同理,required 默认是 0,代表不额外限制天数,实际加载量取决于本地缓存与经纪商提供范围。 取 tick 对象给了三种定位方式:按索引 GetTick(symbol, index)、按秒级时间 GetTick(symbol, tick_time)、按毫秒时间 GetTick(symbol, tick_time_msc),返回的是 CDataTick 指针,空指针代表没取到。IsNewTick(symbol) 用来感知该品种是否来了新 tick,是事件驱动逻辑的开关。 建序列用 CreateTickSeries 针对单一品种,CreateTickSeriesAll 一把拉全市场;刷新同理有 Refresh(symbol) 和 Refresh(void)。调试时 Print() 打完整描述,PrintShort() 打简版,往日志里一看就知道集合状态。

MQL5 / C++
class="type">bool IsAvailableTickSeries(class="kw">const class="type">class="kw">string symbol);
class="type">bool IsAvailableTickSeries(class="type">void);
class=class="str">"cmt">//--- Set the number of days of the tick history of(class="num">1) a specified symbol and(class="num">2) all symbols
class="type">bool SetRequiredUsedDays(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0);
class="type">bool SetRequiredUsedDays(class="kw">const class="type">uint required=class="num">0);
class=class="str">"cmt">//--- Return the last tick object of a specified symbol(class="num">1) by index, (class="num">2) by time and(class="num">4) by time in milliseconds
CDataTick *GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">int index);
CDataTick *GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">class="kw">datetime tick_time);
CDataTick *GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">long tick_time_msc);
class=class="str">"cmt">//--- Return the new tick flag of a specified symbol
class="type">bool IsNewTick(class="kw">const class="type">class="kw">string symbol);
class=class="str">"cmt">//--- Create a tick series of(class="num">1) a specified symbol and(class="num">2) all symbols
class="type">bool CreateTickSeries(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0);
class="type">bool CreateTickSeriesAll(class="kw">const class="type">uint required=class="num">0);
class=class="str">"cmt">//--- Update(class="num">1) a tick series of a specified symbol and(class="num">2) all symbols
class="type">void Refresh(class="kw">const class="type">class="kw">string symbol);
class="type">void Refresh(class="type">void);
class=class="str">"cmt">//--- Display(class="num">1) the complete and(class="num">2) class="type">short collection description in the journal
class="type">void Print(class="type">void);
class="type">void PrintShort(class="type">void);
class=class="str">"cmt">//--- Constructor
 CTickSeriesCollection();
};
逐行拆一下关键接口:第1行 bool IsAvailableTickSeries(const string symbol) 是查指定品种 tick 序列是否就绪;第2行 void 重载查整体。SetRequiredUsedDays 两重载控制历史天数,默认0不裁剪。三个 GetTick 重载分别按索引、秒、毫秒取 CDataTick 指针。IsNewTick 返回该品种新 tick 标志。Create/Refresh 系列负责建和刷,Print/PrintShort 负责日志输出,最后 CTickSeriesCollection() 是构造。 外汇和贵金属 tick 数据受经纪商节流与滑点影响,回测和实盘可能不一致,使用这些接口做策略前务必在 MT5 用 PrintShort() 确认本地实际加载到的天数与tick量。

MQL5 / C++
class="type">bool IsAvailableTickSeries(class="kw">const class="type">class="kw">string symbol);
class="type">bool IsAvailableTickSeries(class="type">void);
class=class="str">"cmt">//--- Set the number of days of the tick history of(class="num">1) a specified symbol and(class="num">2) all symbols
class="type">bool SetRequiredUsedDays(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0);
class="type">bool SetRequiredUsedDays(class="kw">const class="type">uint required=class="num">0);
class=class="str">"cmt">//--- Return the last tick object of a specified symbol(class="num">1) by index, (class="num">2) by time and(class="num">4) by time in milliseconds
CDataTick *GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">int index);
CDataTick *GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">class="kw">datetime tick_time);
CDataTick *GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">long tick_time_msc);
class=class="str">"cmt">//--- Return the new tick flag of a specified symbol
class="type">bool IsNewTick(class="kw">const class="type">class="kw">string symbol);
class=class="str">"cmt">//--- Create a tick series of(class="num">1) a specified symbol and(class="num">2) all symbols
class="type">bool CreateTickSeries(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0);
class="type">bool CreateTickSeriesAll(class="kw">const class="type">uint required=class="num">0);
class=class="str">"cmt">//--- Update(class="num">1) a tick series of a specified symbol and(class="num">2) all symbols
class="type">void Refresh(class="kw">const class="type">class="kw">string symbol);
class="type">void Refresh(class="type">void);
class=class="str">"cmt">//--- Display(class="num">1) the complete and(class="num">2) class="type">short collection description in the journal
class="type">void Print(class="type">void);
class="type">void PrintShort(class="type">void);
class=class="str">"cmt">//--- Constructor
 CTickSeriesCollection();
};

用对象集合管理多品种 tick 序列

把不同品种的 tick 数据塞进一个统一容器,比每次按品种名硬查数组更利于扩展。下面这段从实际工程里抽出来的类与构造函数,演示了如何基于 MQL5 标准库 CArrayObj 做一层薄封装。 CListObj 继承 CArrayObj,只多存一个 m_type 标记。构造时把类型写死成 0x7778,相当于给这类集合打了一个私有指纹,避免和普通对象数组混淆。 CTickSeriesCollection 的构造函数里先 Clear 再 Sort,最后通过 Type(COLLECTION_TICKSERIES_ID) 标定集合身份。注意 Sort 在空容器上调用没有性能负担,但能保证后续 Search 走二分而不是线性扫描。 IndexTickSeries 的写法值得抄:用临时 new 一个 CTickSeries 对象做 Search 的键,命中后立刻 delete,不污染集合本身。若传入 symbol 为空或 NULL,自动回退到当前图表品种 ::Symbol()。 在 MT5 里把这段直接贴进 include 文件,你可以验证一件事——对一个已存有 5 个品种 tick 序列的集合调用 IndexTickSeries,返回索引范围稳定落在 0~4,且重复查询同一品种不新增内存对象。外汇与贵金属 tick 流受点差跳变影响,实盘跑这套集合时须留意高频 new/delete 带来的碎片倾向。

MQL5 / C++
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Include files                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Arrays\ArrayObj.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class of collection lists                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CListObj : class="kw">public CArrayObj
  {
class="kw">private:
   class="type">int               m_type;             class=class="str">"cmt">// List type
class="kw">public:
   class="type">void              Type(class="kw">const class="type">int type)      { this.m_type=type;     }
   class="kw">virtual class="type">int       Type(class="type">void)          class="kw">const { class="kw">return(this.m_type);  }
                     CListObj()                { this.m_type=0x7778;   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
CTickSeriesCollection::CTickSeriesCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_TICKSERIES_ID);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return the tick series index by symbol name                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int CTickSeriesCollection::IndexTickSeries(class="kw">const class="type">class="kw">string symbol)
  {
   class="kw">const CTickSeries *obj=new CTickSeries(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   if(obj==NULL)
      class="kw">return WRONG_VALUE;
   this.m_list.Sort();
   class="type">int index=this.m_list.Search(obj);
   class="kw">delete obj;
   class="kw">return index;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

可以自建一个集合类,把每个品种的即时报价序列作为对象存进去,用统一接口遍历和取数,不用切窗口。
至少要有按品种名查序列、新增品种、删除品种和遍历全部序列这几个方法,其余按需扩展。
小布可以接管多品种报价的归集与监控,自动标记脱离常态的 tick 波动,你只看告警就行。
在集合里用品种名取到对应序列对象,再读它末尾的即时报价结构,就能拿到最新买价卖价。
一般存对象指针或轻量引用,不在容器里复制全量 tick 历史,只保留必要窗口,内存可控。