DoEasy 函数库中的时间序列(第六十一部分):品种即时报价序列集合·进阶篇
(2/3)· 单品种列表散落难维护?把各品种 Tick 序列收进一个集合,索引与更新一次搞定
「Tick序列的取用与开关控制」
在自建的 Tick 数据管理类里,按品种名拿到对应的序列对象是第一步。GetTickseries 先用 IndexTickSeries 定位下标,再从链表 m_list 里 At 取出指针,没命中就返回空。 单个品种的可用开关靠 SetAvailableTickSeries(symbol, flag) 控制,默认 flag=true 即启用;若对应序列不存在则直接 return,不会抛错。批量版本则遍历 m_list.Total() 个节点,逐个置位,循环里遇到空指针用 continue 跳过。 查询侧有对称设计:IsAvailableTickSeries(symbol) 查单品种,无对象返回 false;无参版本用 res &= tickseries.IsAvailable() 做按位与归约,只有全部品种都开启才返回 true。外汇与贵金属 tick 高频刷新,这类开关能降低不必要计算负载,但行情跳变仍属高风险,启停逻辑建议先在策略测试器验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the object of tick series of a specified symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ CTickSeries *CTickSeriesCollection::GetTickseries(class="kw">const class="type">class="kw">string symbol) { class="type">int index=this.IndexTickSeries(symbol); class="kw">return this.m_list.At(index); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the flag of using the tick series of a specified symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTickSeriesCollection::SetAvailableTickSeries(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">bool flag=true) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return; tickseries.SetAvailable(flag); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the flag of using the tick series of all symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTickSeriesCollection::SetAvailableTickSeries(class="kw">const class="type">bool flag=true) { for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) class="kw">continue; tickseries.SetAvailable(flag); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the flag of using the tick series of a specified symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTickSeriesCollection::IsAvailableTickSeries(class="kw">const class="type">class="kw">string symbol) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return class="kw">false; class="kw">return tickseries.IsAvailable(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the flag of using tick series of all symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTickSeriesCollection::IsAvailableTickSeries(class="type">void) { class="type">bool res=true; class="type">int total=this.m_list.Total(); for(class="type">int i=class="num">0;i<total;i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) class="kw">continue; res &=tickseries.IsAvailable(); } class="kw">return res; }
按品种或全市场约束 tick 历史天数
做高频重放或 tick 级回测时,内存和加载耗时直接由历史天数决定。下面这组 CTickSeriesCollection 的成员函数,就是用来动态设定某个品种、或一次性设定所有品种所需 tick 天数的入口。 单品种版本 SetRequiredUsedDays(const string symbol, const uint required=0) 先通过 GetTickseries 拿到该品种的 tick 序列指针,拿不到就返回 false;拿到后调用序列自身的 SetRequiredUsedDays(required) 并回 true。required 默认 0,表示不额外限制、沿用既有的加载逻辑。 全市场版本只传一个 required 参数,用 for 循环遍历 m_list 里全部 CTickSeries 指针,逐个下发同样的天数约束;只要有一个指针为 NULL,就用 res &= false 记下失败但继续跑完,最后返回 res。这种“局部失败不中断”的写法,适合在 EA 初始化阶段批量预设几十个品种的天数。 取数接口有两个重载:按 index 取用 GetTick(symbol, index) 走列表下标,适合顺序扫描;按 tick_time 取用 GetTick(symbol, tick_time) 走时间定位,适合事件对齐。两者都先校验品种序列是否存在,不存在返回 NULL,调用方必须判空。外汇与贵金属 tick 数据量大、跳空频繁,实际设天数时建议先在策略测试器里用 1~3 天小样本验证指针与耗时,再放大到实盘可能需要的窗口。
class="type">bool CTickSeriesCollection::SetRequiredUsedDays(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return class="kw">false; tickseries.SetRequiredUsedDays(required); class="kw">return true; } class="type">bool CTickSeriesCollection::SetRequiredUsedDays(class="kw">const class="type">uint required=class="num">0) { class="type">bool res=true; for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) { res &=class="kw">false; class="kw">continue; } tickseries.SetRequiredUsedDays(required); } class="kw">return res; } CDataTick *CTickSeriesCollection::GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">int index) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return NULL; class="kw">return tickseries.GetTickByListIndex(index); } CDataTick *CTickSeriesCollection::GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">class="kw">datetime tick_time) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return NULL; class="kw">return tickseries.GetTick(tick_time); }
◍ 按时间戳抓tick与批量建序列的实现细节
在 CTickSeriesCollection 类里,取某个品种最后一笔 tick 靠 GetTick() 完成:先通过 GetTickseries(symbol) 拿到该品种的序列指针,空指针直接返回 NULL,否则把毫秒时间戳交给序列对象去定位。 判断新 tick 用 IsNewTick(),逻辑同构——拿不到序列就返回 false,拿到就透传序列内部的新 tick 标志。这两个方法都不自己存数据,只是薄封装。 建序列有单品种和全市场两版。CreateTickSeries(symbol, required) 中 required 默认 0,代表不强制预取数量,返回 tickseries.Create(required)>0 的布尔结果;CreateTickSeriesAll(required) 则遍历 m_list 里全部序列,用 res &= 做逐条与运算,任一条创建失败都会让总结果变 false。 把下面代码贴进 MT5 的 MQL5 类实现里,能直接验证:若 required 传 1000,则每个序列倾向预载约 1000 个 tick 结构,外汇与贵金属 tick 流高风险,实盘前请用策略测试器跑一遍。
class=class="str">"cmt">//| Return the last tick object of the specified symbol | class=class="str">"cmt">//| by time in milliseconds | class=class="str">"cmt">//+------------------------------------------------------------------+ CDataTick *CTickSeriesCollection::GetTick(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">long tick_time_msc) { CTickSeries *tickseries=this.GetTickseries(symbol); class=class="str">"cmt">// 按品种名取序列指针 if(tickseries==NULL) class="kw">return NULL; class=class="str">"cmt">// 无序列直接空 class="kw">return tickseries.GetTick(tick_time_msc); class=class="str">"cmt">// 按毫秒时间取tick } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the new tick flag of a specified symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTickSeriesCollection::IsNewTick(class="kw">const class="type">class="kw">string symbol) { CTickSeries *tickseries=this.GetTickseries(symbol); class=class="str">"cmt">// 取序列指针 if(tickseries==NULL) class="kw">return class="kw">false; class=class="str">"cmt">// 无序列返回否 class="kw">return tickseries.IsNewTick(); class=class="str">"cmt">// 透传新tick标志 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a tick series of a specified symbol | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTickSeriesCollection::CreateTickSeries(class="kw">const class="type">class="kw">string symbol,class="kw">const class="type">uint required=class="num">0) { CTickSeries *tickseries=this.GetTickseries(symbol); class=class="str">"cmt">// 取序列指针 if(tickseries==NULL) class="kw">return class="kw">false; class=class="str">"cmt">// 无序列返回否 class="kw">return(tickseries.Create(required)>class="num">0); class=class="str">"cmt">// 建序列并判断数量>class="num">0 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create tick series of all symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTickSeriesCollection::CreateTickSeriesAll(class="kw">const class="type">uint required=class="num">0) { class="type">bool res=true; class=class="str">"cmt">// 总结果初值真 class="type">int total=this.m_list.Total(); class=class="str">"cmt">// 序列总数 for(class="type">int i=class="num">0;i<total;i++) class=class="str">"cmt">// 遍历所有序列 { CTickSeries *tickseries=this.m_list.At(i); class=class="str">"cmt">// 取第i个序列 if(tickseries==NULL) class="kw">continue; class=class="str">"cmt">// 空则跳过 res &=(tickseries.Create(required)>class="num">0); class=class="str">"cmt">// 与运算累结果 } class="kw">return res; class=class="str">"cmt">// 返回总结果 } class=class="str">"cmt">//+------------------------------------------------------------------+
「逐行拆一个 tick 序列集合类的刷新与打印」
做多品种 tick 级监控时,把各品种 tick 序列塞进一个集合类统一管理,比在 EA 里散着调 CopyTicks 更稳。下面这段是集合类的几个核心成员,直接决定你刷新和排查时的行为边界。 先看单品种刷新入口:传入 symbol 后从集合里取对应 CTickSeries 指针,取不到就直接 return,不抛错。这意味着你传了个没注册过的品种名,代码会静默跳过,日志里什么都看不到,排查时容易误判成「行情没动」。
class="type">void CTickSeriesCollection::Refresh(class="kw">const class="type">class="kw">string symbol) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return; tickseries.Refresh(); }
class="type">void CTickSeriesCollection::Refresh(class="type">void) { for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) class="kw">continue; tickseries.Refresh(); } }
class="type">void CTickSeriesCollection::Refresh(class="kw">const class="type">class="kw">string symbol) { CTickSeries *tickseries=this.GetTickseries(symbol); if(tickseries==NULL) class="kw">return; tickseries.Refresh(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update tick series of all symbols | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTickSeriesCollection::Refresh(class="type">void) { for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) class="kw">continue; tickseries.Refresh(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display complete collection description to the journal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTickSeriesCollection::Print(class="type">void) { for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) class="kw">continue; tickseries.Print(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display the class="type">short collection description in the journal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTickSeriesCollection::PrintShort(class="type">void) { for(class="type">int i=class="num">0;i<this.m_list.Total();i++) { CTickSeries *tickseries=this.m_list.At(i); if(tickseries==NULL) class="kw">continue; tickseries.PrintShort(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create a collection list of symbol tick series | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTickSeriesCollection::CreateCollection(class="kw">const CArrayObj *list_symbols,class="kw">const class="type">uint required=class="num">0) { class=class="str">"cmt">//--- If an empty list of symbol objects is passed, exit if(list_symbols==NULL) class="kw">return class="kw">false; class=class="str">"cmt">//--- Get the number of symbol objects in the passed list class="type">int total=list_symbols.Total();
多品种 tick 序列的批量装载与去重
在初始化行情缓存时,先调用 this.m_list.Clear() 把旧 tick 序列集合清空,避免跨周期重载时残留上一轮品种对象。随后用 for(int i=0;i<total;i++) 遍历外部传入的 symbol 对象列表,逐个建立 CTickSeries 实例。 循环里对每个 symbol_obj 先做空指针判断,取不到对象就 continue 跳过;new CTickSeries() 失败同样跳过,保证单品种内存分配异常不会中断整批加载。接着 tickseries.SetSymbol(symbol_obj.Name()) 绑定品种名,并每次 this.m_list.Sort() 维持集合有序。 去重逻辑靠 this.m_list.Search(tickseries),若返回值大于 WRONG_VALUE 说明同品种已存在,直接 delete tickseries;否则进入 else 分支,用 tickseries.SetRequiredUsedDays(required) 设定回看天数(required 由调用方传入,常见取值 1~30 天),再尝试 Add 进集合,添加失败则释放对象。 函数末尾 return this.m_list.Total()>0 给出布尔标志:只要成功挂进至少一个品种,就认为集合可用。实际在 MT5 里跑这段代码时,若 total 为 50 而 required 设 7,则正常情况下 m_list.Total() 应等于 50,外汇与贵金属品种切换频繁,需警惕点差跳变导致个别 symbol_obj 取空。
class=class="str">"cmt">//--- Clear the tick series collection list this.m_list.Clear(); class=class="str">"cmt">//--- In a loop by all symbol objects for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get the next symbol object CSymbol *symbol_obj=list_symbols.At(i); class=class="str">"cmt">//--- if failed to get a symbol object, move on to the next one in the list if(symbol_obj==NULL) class="kw">continue; class=class="str">"cmt">//--- Create a new empty tick series object CTickSeries *tickseries=new CTickSeries(); class=class="str">"cmt">//--- If failed to create the tick series object, move on to the next symbol in the list if(tickseries==NULL) class="kw">continue; class=class="str">"cmt">//--- Set a symbol name for a tick series object tickseries.SetSymbol(symbol_obj.Name()); class=class="str">"cmt">//--- Set the sorted list flag for the tick series collection list this.m_list.Sort(); class=class="str">"cmt">//--- If the object with the same symbol name is already present in the tick series collection list, remove the tick series object if(this.m_list.Search(tickseries)>WRONG_VALUE) class="kw">delete tickseries; class=class="str">"cmt">//--- otherwise, there is no object with such a symbol name in the collection yet else { class=class="str">"cmt">//--- Set the number of tick data days for a tick series object tickseries.SetRequiredUsedDays(required); class=class="str">"cmt">//--- if failed to add the tick series object to the collection list, remove the tick series object if(!this.m_list.Add(tickseries)) class="kw">delete tickseries; } } class=class="str">"cmt">//--- Return the flag indicating that the created collection list has a size greater than zero class="kw">return this.m_list.Total()>class="num">0;
◍ 引擎基类如何挂载tick序列
CEngine 作为交易引擎的基类,把历史、市场、事件、账户、品种、时序、缓冲、指标、资源、交易控制等集合全部收进私有成员,统一在实例化时分配内存。 高亮新增的 TickSeriesCollection 头文件与 CTickSeriesCollection m_tick_series 成员,说明该架构在原生 K 线之外补上了逐笔 tick 序列容器,给高频重绘与微观价差分析留了接口。 SetUsedSymbols 的第三个参数 required 默认 0,用来在创建品种时序集合时声明最少需要的 tick 数量;若实盘品种 tick 供给不足,返回 false 的概率会显著上升,外汇与贵金属品种在高波动时段才容易喂满。 把下面这段直接贴进 MT5 的 include 链,就能在自有 EA 里复用该 tick 容器,省去自己写 tick 缓存的麻烦。
class="macro">#include "Collections\EventsCollection.mqh" class="macro">#include "Collections\AccountsCollection.mqh" class="macro">#include "Collections\SymbolsCollection.mqh" class="macro">#include "Collections\ResourceCollection.mqh" class="macro">#include "Collections\TimeSeriesCollection.mqh" class="macro">#include "Collections\BuffersCollection.mqh" class="macro">#include "Collections\IndicatorsCollection.mqh" class="macro">#include "Collections\TickSeriesCollection.mqh" class="macro">#include "TradingControl.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Library basis class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEngine { class="kw">private: CHistoryCollection m_history; class=class="str">"cmt">// Collection of historical orders and deals CMarketCollection m_market; class=class="str">"cmt">// Collection of market orders and deals CEventsCollection m_events; class=class="str">"cmt">// Event collection CAccountsCollection m_accounts; class=class="str">"cmt">// Account collection CSymbolsCollection m_symbols; class=class="str">"cmt">// Symbol collection CTimeSeriesCollection m_time_series; class=class="str">"cmt">// Timeseries collection CBuffersCollection m_buffers; class=class="str">"cmt">// Collection of indicator buffers CIndicatorsCollection m_indicators; class=class="str">"cmt">// Indicator collection CTickSeriesCollection m_tick_series; class=class="str">"cmt">// Collection of tick series CResourceCollection m_resource; class=class="str">"cmt">// Resource list CTradingControl m_trading; class=class="str">"cmt">// Trading management object CPause m_pause; class=class="str">"cmt">// Pause object CArrayObj m_list_counters; class=class="str">"cmt">// List of timer counters class=class="str">"cmt">//--- Set the list of used symbols in the symbol collection and create the collection of symbol timeseries class="type">bool SetUsedSymbols(class="kw">const class="type">class="kw">string &array_symbols[],class="kw">const class="type">uint required=class="num">0); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the list of used symbols in the symbol collection | class=class="str">"cmt">//| and create the symbol timeseries collection | class=class="str">"cmt">//+------------------------------------------------------------------+