3. 行情数据特征
在行情系统中,数据并不是单一形态。同一只标的、同一秒内,可能同时存在逐笔成交、盘口变更、快照切片和K线聚合结果。它们的数据结构、更新频率、业务语义和存储策略差异极大。如果建模时把它们混为一谈,后续的实时聚合、回测查询和存储成本都会失控。本节从数据结构与业务语义两个维度,逐一拆解五类核心行情数据。
一、五类行情数据的本质差异
行情数据的分类,本质上取决于两个问题:它记录的是事件还是状态?它的时间粒度是逐笔还是切片?逐笔成交和Tick是事件流,盘口是状态快照,快照行情是周期性切片,K线则是聚合结果。理解这一点,才能为每类数据选择正确的表引擎和排序键。
二、逐笔成交与Tick:事件流的数据结构
逐笔成交是最细粒度的成交记录,每笔成交对应一次撮合。其核心字段包括:成交时间、成交价、成交量、买卖方向(主动买/主动卖)、成交序号。Tick则更宽泛,通常指行情源推送的最小更新单元,可能同时携带成交和盘口变化。
-- 逐笔成交典型结构(ClickHouse 建表片段)
CREATE TABLE market.trade_tick (
symbol LowCardinality(String),
trade_time DateTime64(3, 'Asia/Shanghai'),
price Decimal64(4),
volume UInt64,
direction Enum8('buy'=1, 'sell'=-1, 'unknown'=0),
seq UInt64,
exchange LowCardinality(String)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(trade_time)
ORDER BY (symbol, trade_time, seq);
业务语义上,逐笔成交是不可变事件,只能追加、不能修改。它是计算成交额、VWAP、主动买卖比、大单监控的原始依据。排序键必须包含时间与序号,保证同一毫秒内多笔成交的顺序可还原。
三、快照与盘口:状态切片的结构
快照行情是行情源按固定周期(如3秒)推送的切片,包含最新价、开盘价、最高价、最低价、成交量、成交额以及买卖盘口。盘口则是快照中的核心部分,记录买一至买N、卖一至卖N的价格与挂单量。
| 数据形态 | 时间语义 | 可变性 | 典型字段 | 主要用途 |
|---|---|---|---|---|
| 逐笔成交 | 事件时间 | 不可变 | 价、量、方向、序号 | 成交分析、VWAP |
| Tick | 事件时间 | 不可变 | 成交+盘口变更 | 高频回测、微观结构 |
| 盘口 | 快照时间 | 可覆盖 | 买卖N档价量 | 流动性、价差分析 |
| 快照行情 | 切片时间 | 可覆盖 | OHLC+盘口+统计 | 实时展示、回放 |
| K线 | 窗口时间 | 可重算 | OHLCV | 策略信号、图表 |
四、K线:聚合结果与业务语义
K线不是原始数据,而是由Tick或快照按时间窗口聚合得到的OHLCV结果。它的业务语义是可重算的派生数据:只要原始Tick完整,任意周期的K线都可以重新生成。因此K线表通常使用 AggregatingMergeTree 或 SummingMergeTree,配合物化视图实现增量聚合。
-- 1分钟K线聚合(物化视图思路)
CREATE MATERIALIZED VIEW market.bar_1m_mv
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(bar_time)
ORDER BY (symbol, bar_time)
AS SELECT
symbol,
toStartOfMinute(trade_time) AS bar_time,
argMinState(price, trade_time) AS open,
maxState(price) AS high,
minState(price) AS low,
argMaxState(price, trade_time) AS close,
sumState(volume) AS volume
FROM market.trade_tick
GROUP BY symbol, bar_time;
注意K线的时间窗口语义:是左闭右开还是左闭右闭,直接影响边界成交归属。实盘中必须与行情源定义一致,否则会出现开盘价错位。
五、常见误区与复盘
- 误区一:用快照代替Tick做回测。快照是切片,会丢失切片间的价格路径,导致回测结果偏乐观。高频策略必须使用逐笔或Tick。
- 误区二:盘口与成交混存一张表。两者更新频率和可变性不同,混存会导致排序键失效、查询放大。应分表存储,通过时间对齐关联。
- 误区三:K线直接落原始表。K线是派生数据,应通过物化视图或定时任务生成,避免手工写入造成口径不一致。
- 误区四:忽略交易所时间与本地时间差异。行情时间戳必须保留交易所原始时间,并单独记录入库时间,便于排查延迟与乱序。
总结来看,行情建模的第一步不是写SQL,而是先回答:这条数据是事件还是状态?是原始还是派生?是定频还是不定频?把五类数据的结构与语义对齐后,表引擎、排序键、分区策略和聚合路径都会自然清晰。