即时报价数据对象:搭建可检索的逐笔报价存储底座(基础篇)
📊

即时报价数据对象:搭建可检索的逐笔报价存储底座(基础篇)

(1/3)· 多数自制指标卡在即时报价堆积无序,本篇从对象类定义厘清一次 tick 的存储与检索路径

实战向进阶 第 1/3 篇
把每笔即时报价直接塞进数组却不做属性封装,后续按品种或点差回查基本无解。很多人等到要分析报价流异动时才发现数据结构扛不住,返工成本极高。外汇贵金属报价高频跳动,裸存数据在高波动时段极易丢上下文。

「把单笔即时报价装进一个对象」

在 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 部分新增了毫秒时间、点差、高精度成交量等字段,直接决定你之后调试报价对象时能看到哪些英文标签。

MQL5 / C++
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 频率高、跳空频繁,直接读整型时间戳算毫秒级价差,对剥头皮策略的风控有意义,但这类品种杠杆高、滑点不可控,回测与实盘偏差可能很大。

MQL5 / C++
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 的值,能确认自己平台的枚举偏移是否和原设计一致。

MQL5 / C++
   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 里读到空结构。

MQL5 / C++
 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;
    }
  }
把逐笔诊断交给小布盯盘
这些即时报价对象的存储与检索逻辑,小布盯盘的 AIGC 已内置到对应品种页,打开即可看到报价流异常标注,你只管读形态做决策。

常见问题

至少应包含 bid、ask、time 及由二者算出的点差,并绑定品种标识,否则无法按属性检索与跨品种比对。
分桶后自动更新与体量控制更清晰,搜索排序时只需遍历目标品种,避免全量扫描拖慢 EA 运行。
小布盯盘内置的报价流诊断基于同源结构封装,自写对象若暴露标准属性即可在品种页复用其检索与异动提示。
标志位随机值可能导致新报价判断失灵,漏触或误触逻辑,应在构造初始化清单显式置 false。
排序只认声明为用到的属性索引,未用到留占位,保证后续插入新属性不破坏原有排序基准。