2. 订单簿数据结构与关键字段
微价格的计算只依赖订单簿最顶端的一小段信息,但这段信息必须从正确的数据结构中、以正确的口径取出来。如果对限价订单簿(Limit Order Book,LOB)的层级组织、字段含义和更新机制理解不清,后续的微价格公式即使写得完全正确,也会因为输入数据错位而得到误导性的结果。本节先把订单簿"长什么样"讲清楚,再落到微价格真正需要的那几个字段上。
限价订单簿的层级结构
限价订单簿本质上是一个按价格排序的双边队列结构。买侧(Bid)按价格从高到低排列,卖侧(Ask)按价格从低到高排列,两侧各自形成若干"价格档位",每个档位聚合了该价格上的所有挂单数量。最靠近中间价的一档分别是买一(Best Bid)和卖一(Best Ask),二者构成盘口(Top of Book)。
需要区分两种常见的组织方式。第一种是聚合档位视图:每个价格只保留一个总量,例如"买一 100.02 元,共 3500 股"。第二种是订单级视图:保留每一笔挂单的订单号、价格、数量、方向、时间戳,档位总量由订单聚合得到。交易所行情推送通常给的是聚合档位(如 Level-1 只有买一卖一,Level-2 给多档),而自建撮合或高频策略内部往往维护订单级簿。微价格计算在两种视图下都能做,但取数字段不同:聚合视图直接读档位量,订单级视图需要先按价格聚合再取顶档。
档位的"深度"指从顶档向内数第几档。买一/卖一为第 1 档,买二/卖二为第 2 档,依此类推。深度档位越多,能刻画的市场压力越丰富,但微价格的标准形式只用到第 1 档,扩展形式才会引入第 2 档甚至更深。因此本节的重点是把第 1 档的字段口径彻底固定下来。
关键字段与口径约定
微价格的标准形式是:以买卖一价的中点为基础,再按买卖一量的不平衡程度向压力较大的一侧偏移。因此真正进入计算的字段只有四个:bid_px、bid_qty、ask_px、ask_qty。围绕这四个字段,还需要两个派生量:价差 spread = ask_px − bid_px,以及订单簿不平衡度 imbalance = bid_qty / (bid_qty + ask_qty)。
口径必须在动手前固定,否则同一份行情在不同人手里会算出不同结果。建议约定如下:价格单位统一为元,数量单位统一为股(或手,但全流程一致);imbalance 以买量为分子,取值范围 0 到 1,0.5 表示买卖均衡,大于 0.5 表示买侧压力更大;价差恒为非负,若出现负值说明数据错位或买卖侧取反。方向约定同样重要:本文所有"偏移"均指相对中间价向卖侧(价格更高方向)为正,向买侧为负。
还有一个容易被忽略的字段是时间戳与更新序号。交易所行情通常带有 timestamp 和逐笔序号(sequence number),用于判断快照是否连续、是否丢包。微价格是逐笔更新的量,如果两次快照之间发生丢包,直接用新快照计算会得到跳变的结果。工程上应校验序号连续性,发现缺口时丢弃该次更新或触发重新拉取全量快照。
从原始数据到微价格输入
假设我们拿到一条 Level-1 快照,字段为 bid_px=100.02, bid_qty=3000, ask_px=100.04, ask_qty=4200。先做合法性检查:买一价必须小于卖一价,数量必须为正,否则该条数据不可用。检查通过后计算派生量:
spread = 100.04 - 100.02 = 0.02
imbalance = 3000 / (3000 + 4200) = 0.4167
mid = (100.02 + 100.04) / 2 = 100.03
此时买量小于卖量,imbalance 为 0.4167,低于 0.5,说明卖侧挂单更重、上方压力更大,微价格应当低于中间价。若采用线性形式 microprice = mid + spread × (imbalance − 0.5),则偏移量为 0.02 × (0.4167 − 0.5) = −0.00167,微价格约为 100.0283。这个数值本身不是重点,重点是它由哪四个字段、按什么口径推导而来——只要字段取错或买卖侧颠倒,偏移方向就会整体反号。
imbalance 写成 ask_qty / (bid_qty + ask_qty),或在取数时把买一卖一互换。这两种错误都会让微价格的偏移方向完全相反,且在回测中不易察觉,务必在数据接入层用断言固定下来。数据结构落地建议
在代码中,建议用一个明确的结构体承载顶档数据,而不是散落的变量。字段命名与上文口径一一对应,并在构造时完成合法性校验:
type TopOfBook struct {
BidPx float64 // 买一价
BidQty float64 // 买一量
AskPx float64 // 卖一价
AskQty float64 // 卖一量
Seq uint64 // 行情序号
}
func (t TopOfBook) Valid() bool {
return t.BidPx > 0 && t.AskPx > 0 &&
t.BidPx < t.AskPx &&
t.BidQty > 0 && t.AskQty > 0
}
若使用多档数据,可把顶档抽象为 Levels[0],并保证数组按"越靠近中间价索引越小"排列,这样微价格计算只依赖索引 0,扩展时再取索引 1 即可,无需改动取数逻辑。对于订单级簿,则先按价格聚合、排序后取首档,聚合时注意同一价格上可能有多个订单,数量需累加而非覆盖。
最后提醒一点:微价格对顶档量的变化非常敏感,而顶档量在撮合过程中可能瞬间被吃掉或补充。因此数据准备阶段应保留原始快照的更新频率信息,明确微价格是按快照周期计算还是按逐笔更新计算。这个选择会直接影响后续信号的反应速度,属于数据基础的一部分,应在进入公式实现之前就确定下来。