即时报价数据对象:搭建可检索的逐笔报价存储底座(基础篇)
(1/3)· 多数自制指标卡在即时报价堆积无序,本篇从对象类定义厘清一次 tick 的存储与检索路径
「把单笔即时报价装进一个对象」
在 DoEasy 函数库的时间序列体系里,第五十七部分解决的是一个很具体的问题:如何用独立对象承载一次即时报价(tick)的完整数据,而不是散落在全局数组里。 原文示例标注时间为 2021 年 3 月 9 日 11:32,发布后阅读量 1 284,说明这类底层封装即便偏冷门,仍有实盘开发者持续跟进。 核心做法是新建一个即时报价数据对象类,把单笔 tick 的时间、买卖价、成交量等字段封装为成员,对外只暴露取值接口。这样在回放历史 tick 或做价差统计时,调用方不必关心底层缓冲区结构。 读者可直接在 MT5 用 OnInit 里构造该类实例、打印首笔实时 tick 的 time 与 last,验证对象是否如实承接报价流;外汇与贵金属 tick 跳动频繁,实盘测试请注意点差突变与滑点高风险。
把即时报价拆成最小存储单元
开发价格数据函数库时,第一步不是直接画指标,而是先定清楚“数据怎么存”。即时报价流里最小的可用单位,是一次报价的 MqlTick 结构——它携带最新 Bid、Ask 以及成交方向等字段,而不是像时间序列那样以柱线(Bar)为最小粒度的存储对象。 为了能按品种隔离管理,应为每个交易品种单独建一个即时报价对象列表。新到的 tick 自动追加进对应列表尾部,列表长度由调用方设定上限,避免内存随行情无限制膨胀。 这种存储方式的好处是复用了函数库已有的“按对象属性检索与排序”能力。也就是说,你可以直接按点差、按报价跳变间隔去筛 tick,快速看数据流是否出现异动,或者捞特定形态。外汇与贵金属杠杆高、滑点随机,这类微观结构分析只能作为辅助,不构成方向判断依据。
◍ 给即时报价对象铺好消息与枚举底座
要让库里的对象能在面板里被搜索、按属性排序,第一步是把参数描述文本和属性枚举先建起来。否则对象即使跑起来,MT5 里也只能看到一堆无标注的字段,排查时极费眼。 具体落点在两个头文件:Data.mqh 里加消息索引和对应文本,Defines.mqh 里加整数、实数、字符串三类即时报价属性的枚举。每个枚举都要显式写明“排序用到 / 未用到”的属性总数,这是后续排序逻辑能否正确遍历的硬前提。 早先在第三十八篇做的 NewTickObj 已能跟踪指定品种新报价,类文件在 Include\DoEasy\Objects\Ticks\NewTickObj.mqh。这次改两个点:允许给设置当前品种的方法传 NULL 或空串;构造函数初始化清单里给 m_new_tick 赋 false。之前这变量没初始化过,属于隐藏 bug,可能在品种切换时误触发“新 tick”标志。 下面这段是消息索引与文本的对照片段,注意 CTick 部分新增了毫秒时间、点差、高精度成交量等字段,直接决定你之后调试报价对象时能看到哪些英文标签。
class=class="str">"cmt">//--- CSeriesDataInd MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS, class=class="str">"cmt">// The method is not intended to work with indicator programs MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA, class=class="str">"cmt">// Failed to get indicator data timeseries MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA, class=class="str">"cmt">// Failed to get current data of indicator buffer MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ, class=class="str">"cmt">// Failed to create indicator data object MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST, class=class="str">"cmt">// Failed to add indicator data object to list class=class="str">"cmt">//--- CTick MSG_TICK_TEXT_TICK, class=class="str">"cmt">// Tick MSG_TICK_TIME_MSC, class=class="str">"cmt">// Time of the last update of prices in milliseconds MSG_TICK_TIME, class=class="str">"cmt">// Time of the last update of prices MSG_TICK_VOLUME, class=class="str">"cmt">// Volume for the current Last price MSG_TICK_FLAGS, class=class="str">"cmt">// Flags MSG_TICK_VOLUME_REAL, class=class="str">"cmt">// Volume for the current Last price with greater accuracy MSG_TICK_SPREAD, class=class="str">"cmt">// Spread MSG_LIB_TEXT_TICK_CHANGED_DATA, class=class="str">"cmt">// Changed data on tick: MSG_LIB_TEXT_TICK_FLAG_BID, class=class="str">"cmt">// Bid price change MSG_LIB_TEXT_TICK_FLAG_ASK, class=class="str">"cmt">// Ask price change MSG_LIB_TEXT_TICK_FLAG_LAST, class=class="str">"cmt">// Last deal price change MSG_LIB_TEXT_TICK_FLAG_VOLUME, class=class="str">"cmt">// Volume change }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- CSeriesDataInd {"The method is not intended for working with indicator programs"}, {"Failed to get indicator data timeseries"}, {"Failed to get the current data of the indicator buffer"}, {"Failed to create indicator data object"}, {"Failed to add indicator data object to the list"}, class=class="str">"cmt">//--- CTick {"Tick"}, {"Last price update time in milliseconds"}, {"Last price update time"}
「逐笔 tick 的整型与浮点字段拆解」
在 MT5 的 tick 数据结构里,整数类属性与浮点类属性是分开枚举的。整数侧定义了 4 个成员:TICK_PROP_TIME_MSC(毫秒级更新时间)、TICK_PROP_TIME(秒级更新时间)、TICK_PROP_VOLUME(Last 价成交量)、TICK_PROP_FLAGS(tick 标志位),宏 TICK_PROP_INTEGER_TOTAL 显式写死为 4,排序时跳过 0 个属性。 浮点侧枚举 ENUM_TICK_PROP_DOUBLE 的起始下标直接接在整数总数之后,即 TICK_PROP_BID = 4,后续依次是 TICK_PROP_ASK、TICK_PROP_LAST、TICK_PROP_VOLUME_REAL(高精度 Last 成交量)。这意味着用数组存 tick 字段时,整型占 0~3,浮点占 4~7,中间无空隙。 开 MT5 自建一个 CTick 结构时,照这个下标排布写缓冲区,能在 CopyTicks 后直接用下标访问,不必每次都走 MqlTick 结构体做转换。外汇与贵金属 tick 频率高、跳空频繁,直接读整型时间戳算毫秒级价差,对剥头皮策略的风控有意义,但这类品种杠杆高、滑点不可控,回测与实盘偏差可能很大。
enum ENUM_TICK_PROP_INTEGER { TICK_PROP_TIME_MSC = class="num">0, class=class="str">"cmt">// Time of the last price update in milliseconds TICK_PROP_TIME, class=class="str">"cmt">// Time of the last update TICK_PROP_VOLUME, class=class="str">"cmt">// Volume for the current Last price TICK_PROP_FLAGS, class=class="str">"cmt">// Tick flags }; class="macro">#define TICK_PROP_INTEGER_TOTAL(class="num">4) class=class="str">"cmt">// Total number of tick integer properties class="macro">#define TICK_PROP_INTEGER_SKIP(class="num">0) class=class="str">"cmt">// Number of tick properties not used in sorting enum ENUM_TICK_PROP_DOUBLE { TICK_PROP_BID = TICK_PROP_INTEGER_TOTAL, class=class="str">"cmt">// Tick Bid price TICK_PROP_ASK, class=class="str">"cmt">// Tick Ask price TICK_PROP_LAST, class=class="str">"cmt">// Current price of the last trade(Last) TICK_PROP_VOLUME_REAL, class=class="str">"cmt">// Volume for the current Last price with greater accuracy };
Tick 属性枚举与排序模式的底层划分
在 MT5 的自定义 tick 处理结构里,整数、双精度、字符串三类属性被显式拆开枚举。整数属性总数 TICK_PROP_INTEGER_TOTAL 减去跳过项后得到可用排序起点,双精度里 TICK_PROP_DOUBLE_TOTAL 固定为 5,字符串仅 1 个即 TICK_PROP_SYMBOL,这种硬编码计数方便后续数组偏移计算。 排序模式枚举 ENUM_SORT_TICK_MODE 把整数类排在前:SORT_BY_TICK_TIME_MSC 值为 0,接着是 TIM、VOLUME、FLAGS。双精度类从 FIRST_TICK_DBL_PROP 开始承接,也就是整数总数减跳过数之后的位置,Bid、Ask、Last、Volume_Real 依次排进去。 实战里若你要按 Ask-Bid 价差做 tick 流过滤,得先认准 TICK_PROP_SPREAD 在双精度段的位置,而不是去整数段找。外汇与贵金属点差跳动频繁,这类微观结构的高频读取本身带平台延迟风险,回测和实盘可能偏差明显。 直接把下面片断塞进你的 include 头文件,编译后打印 FIRST_TICK_DBL_PROP 和 FIRST_TICK_STR_PROP 的值,能确认自己平台的枚举偏移是否和原设计一致。
TICK_PROP_SPREAD, class=class="str">"cmt">// Tick spread(Ask - Bid) }; class="macro">#define TICK_PROP_DOUBLE_TOTAL(class="num">5) class=class="str">"cmt">// Total number of real tick properties class="macro">#define TICK_PROP_DOUBLE_SKIP(class="num">0) class=class="str">"cmt">// Number of tick properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| String tick properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_TICK_PROP_STRING { TICK_PROP_SYMBOL = (TICK_PROP_INTEGER_TOTAL+TICK_PROP_DOUBLE_TOTAL), class=class="str">"cmt">// Tick symbol }; class="macro">#define TICK_PROP_STRING_TOTAL(class="num">1) class=class="str">"cmt">// Total number of class="type">class="kw">string tick properties class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible tick sorting criteria | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_TICK_DBL_PROP(TICK_PROP_INTEGER_TOTAL-TICK_PROP_INTEGER_SKIP) class="macro">#define FIRST_TICK_STR_PROP(TICK_PROP_INTEGER_TOTAL-TICK_PROP_INTEGER_SKIP+TICK_PROP_DOUBLE_TOTAL-TICK_PROP_DOUBLE_SKIP) enum ENUM_SORT_TICK_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_TICK_TIME_MSC = class="num">0, class=class="str">"cmt">// Sort by the time of the last price update in milliseconds SORT_BY_TICK_TIM, class=class="str">"cmt">// Sort by the time of the last price update SORT_BY_TICK_VOLUME, class=class="str">"cmt">// Sort by volume for the current Last price SORT_BY_TICK_FLAGS, class=class="str">"cmt">// Sort by tick flags class=class="str">"cmt">//--- Sort by real properties SORT_BY_TICK_BID = FIRST_TICK_DBL_PROP, class=class="str">"cmt">// Sort by tick Bid price SORT_BY_TICK_ASK, class=class="str">"cmt">// Sort by tick Ask price SORT_BY_TICK_LAST, class=class="str">"cmt">// Sort by current price of the last trade(Last) SORT_BY_TICK_VOLUME_REAL, class=class="str">"cmt">// Sort by volume for the current Last price with greater accuracy
◍ 用构造函数给新报价对象铺底
CNewTickObj 的带参构造函数只接一个 symbol 字符串,初始化列表里顺手把 m_symbol 赋好,并把 m_new_tick 默认置为 false,意味着对象诞生瞬间还不算收到新 tick。 进函数体先 ZeroMemory 清空 m_tick 与 m_tick_prev 两个结构,避免遗留脏数据干扰后续比价。若 SymbolInfoTick 成功把当前报价写进 m_tick,就把它整份拷贝到 m_tick_prev,同时把 m_first_start 标成 false——这说明首启时prev tick直接沿用现价,不至于开局误判跳空。 外汇与贵金属报价瞬变,这类对象在 EA 里实例化后,建议直接开 MT5 用 Print 打出 m_tick_prev.last 验证首帧是否已落地,否则可能在第一次 OnTick 里读到空结构。
SORT_BY_TICK_SPREAD, class=class="str">"cmt">// Sort by tick spread class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties SORT_BY_TICK_SYMBOL = FIRST_TICK_STR_PROP, class=class="str">"cmt">// Sort by tick symbol }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Set a symbol class="type">void SetSymbol(class="kw">const class="type">class="kw">string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parametric constructor CNewTickObj | class=class="str">"cmt">//+------------------------------------------------------------------+ CNewTickObj::CNewTickObj(class="kw">const class="type">class="kw">string symbol) : m_symbol(symbol),m_new_tick(class="kw">false) { class=class="str">"cmt">//--- Reset the structures of the new and previous ticks ::ZeroMemory(this.m_tick); ::ZeroMemory(this.m_tick_prev); class=class="str">"cmt">//--- If managed to get the current prices to the tick structure - class=class="str">"cmt">//--- copy data of the obtained tick to the previous tick data and reset the first launch flag if(::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_tick_prev=this.m_tick; this.m_first_start=class="kw">false; } }