2. 金融市场数据基础
在构建任何实时行情系统之前,必须先回答一个看似简单却极易被低估的问题:一条Tick到底是什么?很多开发者把Tick理解为“价格变动”,但在真实交易所数据链路中,Tick是带有明确语义、字段约束和时间归属的最小成交或盘口事件。理解它的定义与结构,是后续聚合、去重、对齐和K线生成正确性的根基。
一、Tick数据的定义与本质
Tick通常指交易所行情网关推送的一次市场状态变更事件,最常见的是逐笔成交(Trade)与逐笔委托(Order)以及盘口快照(Quote/Depth)。它不是一个“价格点”,而是一条带有事件类型、标的、时间、价格、数量、方向等属性的结构化记录。不同市场对Tick的定义差异很大:
- 股票逐笔成交:一次撮合成交,包含成交价、成交量、买卖方向、成交编号。
- 期货Level-1行情:常以固定频率快照推送,包含最新价、买卖一档、成交量、持仓量。
- 期货Level-2行情:包含多档盘口与逐笔委托,数据量可达每秒数万条。
- 加密货币Tick:通常为逐笔成交或增量深度更新,时间精度可达微秒甚至纳秒。
因此,在系统设计中不能笼统地说“处理Tick”,而要先明确数据源类型、事件语义和字段契约,否则聚合逻辑会出现方向错误、重复计数或时间错位。
二、Tick字段结构与时间戳类型
一条典型Tick的字段可分为四类:标识类、时间类、数值类、方向类。以下是一个通用化的字段结构示例:
{
"symbol": "IF2406",
"exchange": "CFFEX",
"event_type": "trade",
"exchange_ts": 1718000000123456789,
"local_ts": 1718000000124000000,
"price": 3850.2,
"volume": 3,
"turnover": 1155060.0,
"direction": "buy",
"seq": 88231007
}
其中时间戳是实战中最容易出问题的部分,常见有三类:
| 时间戳类型 | 含义 | 典型精度 | 使用场景 |
|---|---|---|---|
| 交易所时间 | 交易所撮合或发布时刻 | 毫秒/微秒/纳秒 | K线归属、跨市场对齐 |
| 本地接收时间 | 本机网卡或进程收到时刻 | 微秒/纳秒 | 延迟监控、乱序检测 |
| 业务时间 | 交易日或结算日归属 | 日期 | 夜盘归属、日切处理 |
三、交易所数据源与常见数据格式
不同交易所的数据接入方式差异显著,常见可分为三类:
- 二进制私有协议:如CTP、飞马、恒生UFT,字段紧凑、延迟低,但需要按结构体解析,字节序和对齐必须严格匹配。
- FIX/FAST协议:国际交易所常用,字段自描述,扩展性好,但解析开销较大。
- WebSocket/HTTP JSON:加密货币交易所主流,开发快,但延迟和吞吐不如二进制。
以CTP行情为例,解析后的Tick结构通常包含InstrumentID、LastPrice、Volume、BidPrice1、AskPrice1、UpdateTime、UpdateMillisec等字段。注意CTP的UpdateTime只有时分秒,毫秒在独立字段中,拼接时必须补零对齐,否则会出现时间跳变。
# 伪代码:拼接CTP时间戳
ts_str = f"{UpdateTime}.{UpdateMillisec:03d}"
exchange_ts = parse_datetime(ActionDay, ts_str)
四、案例场景:从原始Tick到可聚合事件
假设我们接入某期货交易所的Level-1快照,原始数据每秒推送2次。若直接按“每条记录生成一根K线”,会得到大量重复且无意义的K线。正确流程是:先按symbol + exchange_ts去重,再按事件类型过滤,只保留成交类Tick进入聚合管道,盘口快照单独存入行情快照表。
下图展示了从多源Tick到统一事件模型的转换关系,重点体现字段映射与时间归属的判断路径:
在这个模型中,seq字段用于检测丢包与乱序,direction用于判断主动买卖方向,exchange_ts用于K线归属。缺少任一字段,聚合结果都可能出现偏差。
五、误区复盘与检查清单
在实际项目中,Tick处理最常见的错误集中在时间与语义两个维度。以下清单可用于接入新数据源时的自检:
- 是否确认了时间戳精度与单位(秒/毫秒/微秒/纳秒)?
- 是否区分了交易所时间与本地接收时间?
- 是否处理了夜盘与日盘的交易日归属?
- 是否对重复推送做了去重?
- 是否确认了价格单位与最小变动价位?
- 是否保留了原始报文用于回放与对账?