DoEasy 函数库中的时间序列(第六十一部分):品种即时报价序列集合(基础篇)
◍ 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(零表示一天)。调一次该方法,时间序列和报价序列两个集合就同时成型,外部用公开方法拿集合指针即可。
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()) 打点确认。
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() 打简版,往日志里一看就知道集合状态。
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(); };
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 带来的碎片倾向。
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">//+------------------------------------------------------------------+