2. 盘口基础与多档数据解析

要模拟大额卖单如何穿透多档盘口,第一步不是写撮合逻辑,而是先把“盘口”这个数据结构看透。限价订单簿(Limit Order Book,LOB)是交易所对当前所有未成交限价委托的组织方式,它按价格优先、时间优先排序,并分别维护买方和卖方两侧。理解它的字段、档位含义与更新机制,才能让后续的穿透计算有真实的输入基础。

一、限价订单簿的基本结构

订单簿的核心是两条按价格排序的队列:买方(Bid)价格从高到低排列,卖方(Ask)价格从低到高排列。最优买价(Best Bid)与最优卖价(Best Ask)之间的差额就是买卖价差(Spread)。每一侧通常只对外披露前若干档,形成“多档盘口”。以常见的十档盘口为例,卖方十档从卖一(最低卖价)向上递增,买方十档从买一(最高买价)向下递减。

每一档包含两个关键量:价格与挂单量。挂单量是该价位上所有未成交委托的数量总和,单位取决于标的,股票通常为“股”或“手”,期货为“手”,数字货币为“币”。需要特别注意的是,盘口展示的是“静态快照”,而真实订单簿是持续变化的:新委托进入、成交消耗、撤单都会改变某一档的数量甚至让该档消失。

方向约定:本文统一以“卖方档位由低到高”为穿透方向。大额卖单向下砸盘时,消耗的是买方档位,从买一逐级向买二、买三……推进;若讨论的是大额买单,则消耗卖方档位,从卖一向上推进。全文与图示保持一致。

二、盘口数据字段解析

一个标准的多档盘口快照通常包含以下字段,理解每个字段的语义是正确建模的前提:

字段含义建模时的作用
symbol标的代码区分不同合约/股票
timestamp快照时间戳(毫秒/微秒)判断数据新鲜度与对齐
bid_price[i] / bid_size[i]买方第 i 档价格与挂单量卖单穿透时的消耗对象
ask_price[i] / ask_size[i]卖方第 i 档价格与挂单量买单穿透时的消耗对象
last_price最新成交价校验穿透后的成交均价
spread买卖价差衡量流动性与冲击成本起点

其中 bid_size 与 ask_size 是穿透模拟最核心的输入。举例来说,若买一价为 10.00 元、挂单量 5000 股,买二价 9.99 元、挂单量 8000 股,那么一笔 10000 股的卖单会先吃掉买一的 5000 股,剩余 5000 股继续消耗买二,最终成交均价约为 (10.00×5000 + 9.99×5000) / 10000 ≈ 9.995 元。这个简单算式正是多档穿透的雏形。

三、多档盘口的层级与流动性含义

档位越深,价格越不利,但往往挂单量越大。多档盘口实际上刻画了一条“流动性阶梯”:靠近盘口的一档流动性最好、冲击成本最低,越往后越稀疏。把各档挂单量按价格累加,可以得到“累计可成交量曲线”,它直接决定一笔大单能走多深、均价被拉低多少。这也是后续章节计算滑点和冲击成本的基础。

限价订单簿多档结构与卖单穿透路径 卖方 Ask(价格由低到高) 卖三 10.03 × 3000 卖二 10.02 × 6000 卖一 10.01 × 4000 买卖价差 Spread = 0.01 买方 Bid(价格由高到低) 买一 10.00 × 5000 买二 9.99 × 8000 买三 9.98 × 12000 买四 9.97 × 15000 卖单 20000 股 逐档向下消耗 穿透过程(示例参数) 买一 5000 → 买二 8000 → 买三 12000,累计 25000 ≥ 20000,止于买三 成交均价 ≈ (10.00×5000 + 9.99×8000 + 9.98×7000) / 20000 ≈ 9.9865 元

上图把订单簿的两侧结构、价差位置以及一笔 20000 股卖单的穿透路径放在同一视图中。可以看到,卖单从买一开始逐档消耗,累计到买三时已满足数量,因此成交止于买三,买四未被触及。这个“累计量 ≥ 委托量”的判断,就是多档穿透模拟最核心的循环条件。

四、常见盘口数据源与获取方式

不同市场的数据源差异较大,建模前要明确数据粒度与更新频率:

  • 交易所官方行情:如上交所/深交所 Level-1(最优一档)与 Level-2(十档),期货交易所的深度行情。Level-2 提供多档快照,是穿透模拟的首选。
  • 券商/期货公司柜台接口:通过 CTP、恒生等柜台获取,字段规范但需处理推送频率与快照对齐。
  • 第三方数据服务:如 Wind、聚宽、Tushare 等,适合回测与研究,需注意其盘口为抽样或聚合后的快照。
  • 数字货币交易所 WebSocket:如 depth 频道推送增量或全量盘口,更新频率高,需自行维护本地订单簿。

实际工程中,盘口数据常以“快照 + 增量”两种形式出现。快照是某一时刻的完整多档状态,增量只推送变化的价格档。若使用增量数据,必须在本地维护一份订单簿副本,按“先快照、后增量”的顺序重放,否则会出现档位错乱。下面是一段简化的增量更新伪代码,体现“定位档位—覆盖或删除”的处理逻辑:

# 本地订单簿:bids/asks 为 {价格: 数量} 字典
def apply_delta(side, price, size):
    book = bids if side == "bid" else asks
    if size == 0:
        book.pop(price, None)      # 数量为 0 表示该档被撤空
    else:
        book[price] = size         # 覆盖该档挂单量
    # 重排后取前 N 档作为盘口快照
    sorted_prices = sorted(book, reverse=(side == "bid"))
    return [(p, book[p]) for p in sorted_prices[:10]]

这段代码的关键在于:数量为 0 时必须删除该档,而不是保留为 0,否则后续累计成交量会把“空档”误算进去。很多穿透模拟的偏差,恰恰来自对增量数据的处理不严谨。

五、建模前的数据检查清单

在把盘口数据送入穿透模型之前,建议先做几项基础校验,避免“垃圾进、垃圾出”:

  1. 检查 bid_price[0] < ask_price[0],若出现买价高于卖价,说明数据错位或跨市场拼接错误。
  2. 检查各档价格严格单调:买方递减、卖方递增,出现乱序需重新排序。
  3. 检查挂单量非负,且累计量足以覆盖待模拟委托量,否则需标记“深度不足”。
  4. 核对时间戳与成交回报的时间差,过大的延迟会让快照失去参考意义。
盘口快照是“某一瞬间”的状态,而大额卖单的实际成交发生在随后的时间里。用单张快照做穿透模拟,本质上是一种“静态假设”:假设在成交过程中盘口不发生其他变化。这一假设在流动性好的标的上近似成立,但在剧烈波动时会明显低估冲击成本,后续章节会讨论如何修正。