如何根据二级市场数据估算队列位置
队列位置决定被动订单能否赚取买卖价差。本文详细介绍如何利用聚合的二级市场深度数据估算排队顺序,并分析何时应选择购买订单簿逐笔数据以获得更高精度。
队列位置是指在同一价格水平上,所有挂单按排队顺序所处的位置。在采用价格-时间优先原则的交易场所,它决定了被动订单是能成交并赚取买卖价差,还是根本无法成交。聚合后的市场数据不会直接提供这一数值:二级市场深度(Level 2)仅显示每个价格上的总挂单量,而不显示具体归属。以下内容将介绍如何根据大多数交易者可获取的数据估算队列位置,以及如何判断估算何时不再可靠。
为什么队列位置是核心优势所在
被动订单挂在订单簿中,等待其他订单跨越买卖价差与其成交。根据 价格-时间优先 原则,撮合引擎首先按价格排序,其次按到达时间排序。如果 $10.00 的买单已有 10,000 股,而你在此之后又挂入 100 股,那么那 10,000 股必须先成交或撤单,轮到你时才能成交。如果在同一价格撤单并重新挂单,你将回到队列末尾。
这种排序决定了经济效益。排在队列前列意味着交易频繁,可以赚取买卖价差以及任何流动性提供者返点,详见 做市商费用与返点。排在末尾则意味着只有在前方所有订单都成交后你才能成交,这种情况通常发生在单边大额流向出现时。排在末尾的成交往往集中在价格即将穿透你所在价位的前一刻。同样的价格,同样的订单,结果却截然不同。
二级市场数据能与不能显示的内容
一级市场数据(Level 1)显示最佳买卖价及对应的规模。二级市场数据(Level 2),也称价格深度,增加了深度信息:即每个价格水平上的总挂单量列表。两者均为聚合数据,一级与二级市场数据对比 对此有完整说明。当 $10.00 的买单从 10,000 股降至 8,500 股时,数据源仅报告减少了 1,500 股。它不会说明这些股份是成交了,还是撤单了,是单笔订单撤销还是多笔订单撤销,也不说明它们在队列中的位置。
成交是可见的部分:行情记录(Tape)会打印每一笔成交及其规模,因此这部分可以精确扣除。其余部分则是撤单,而撤单正是猜测的起点。下表统计了 2026 年 6 月 10 日(周三)某流动性股票在常规交易时段内,综合盘口顶端的消息数量与行情记录的成交笔数对比。
每个数字背后的完整 SQL
SELECT
q.et_time AS et_time,
q.quote_updates AS quote_updates,
t.trades AS trades,
round(q.quote_updates / t.trades, 1) AS updates_per_trade
FROM
(
SELECT
formatDateTime(toStartOfHour(toTimeZone(sip_timestamp, 'America/New_York')), '%H:%i') AS et_time,
count() AS quote_updates
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 12:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
GROUP BY et_time
) AS q
INNER JOIN
(
SELECT
formatDateTime(toStartOfHour(toTimeZone(sip_timestamp, 'America/New_York')), '%H:%i') AS trade_hour,
count() AS trades
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 12:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
GROUP BY trade_hour
) AS t ON q.et_time = t.trade_hour
ORDER BY et_time08:00 时段位于开盘钟声之前:该时段盘口顶端的变化次数为每笔成交 0.8 次,消息数少于成交数。进入常规交易时段后,比例发生逆转。在 15:00 时段,盘口顶端的变化次数为每笔成交 1.6 次,仅该时段内就有 233433 条消息。在公开市场中,价格水平上发生的大多数事件都是订单的进入和离开,且并未成交,而这些事件中的每一项都会改变你的排队位置,且没有任何聚合数据源会说明这一点。
均匀撤单假设及其误导性
标准的初步估算假设撤单在队列中均匀分布。设 x 为你的相对深度:即你前方股份数除以该价格的总挂单量。均匀模型设定任何撤单股份位于你前方的概率等于 x,即 p(x) = x。如果你在 10,000 股的队列中排在中间,观察到 1,000 股撤单且无成交记录,模型会认为你的位置前进了 500 位。
实际队列存在偏差。已经挂单一段时间的订单通常属于愿意等待的交易者,而刚刚加入的订单更有可能是几秒内就会消失的瞬时报价。撤单倾向于集中在队列后方,即靠近你或在你身后。在那 1,000 股撤单中,可能只有 200 股位于你前方,而模型却为你记入了 500 股。整天重复这种计算,模拟队列的前进速度就会快于真实队列。这种误差是单向的:即模拟出的成交次数更多、时机更好,远超真实订单的实际表现。
队列如何消耗
撤单会让你隐形地前进。成交则通过成交规模的打印记录让你可见地前进。
每个数字背后的完整 SQL
SELECT
multiIf(size < 100, 'under 100 shares',
size = 100, 'exactly 100 shares',
size <= 499, '101 to 499 shares',
size <= 999, '500 to 999 shares',
'1000 shares or more') AS trade_size_group,
round(100 * count() / sum(count()) OVER (), 1) AS share_of_prints_pct,
round(100 * sum(size) / sum(sum(size)) OVER (), 1) AS share_of_shares_pct
FROM global_markets.stocks_trades
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
GROUP BY trade_size_group
ORDER BY min(size)在此次交易时段中,小于整手(100股)的成交占比为 90%,成交股份占比为 42.5%。1,000 股或以上的成交大单占比为 0.2%,成交量占比为 19.9%。队列的消耗是小额进行的,因此位置 2,000 和位置 3,500 之间的差距需要数百笔成交才能填补。如果模拟器在两笔大额成交后就判定成交,那么它已经跳过了大部分行情记录。
你无法看到的订单
深度数据源会截断显示。如果你接收十个价格水平,而你的订单挂在第十一个,那么你的订单就在数据之外:你既不知道前方有多少规模,也不知道身后有多少订单加入。此时你是在编造数据而非估算。这种情况很常见,因为不追随市场的订单会迅速脱离盘口内部。
每个数字背后的完整 SQL
SELECT
formatDateTime(toStartOfFifteenMinutes(toTimeZone(sip_timestamp, 'America/New_York')), '%H:%i') AS et_time,
round(avg(toFloat64(bid_price)), 2) AS best_bid,
uniqExact(bid_price) AS bid_levels_touched
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'AAPL'
AND sip_timestamp >= '2026-06-10 14:00:00'
AND sip_timestamp < '2026-06-10 20:00:00'
AND bid_price > 0
GROUP BY et_time
ORDER BY et_time在 10:00 区间内,最佳买单平均价格为 $290.29,且在十五分钟内触及了 187 个不同的价格。对于以分计价的股票,每个价格都是一个独立的水平,因此十个水平的视图仅覆盖十美分的深度。在这样的交易时段中,挂在某一价格的订单可能会长时间处于所有者可见的最深水平之外。
队列位置价值最高的地方
在无法进行价格改进的地方,队列位置的价值最高。一只买卖价差仅为一分的股票,没有空间通过更好的价格来插队:所有人都堆积在同一个价位,到达时间决定了一切。当买卖价差有几分宽时,交易者只需多出一分钱即可排在整个队列之前,此时价格决策优于排队顺序。
每个数字背后的完整 SQL
SELECT
ticker AS symbol,
round(avg(toFloat64(ask_price - bid_price)) * 100, 2) AS avg_spread_cents,
round(100 * countIf(round(toFloat64(ask_price - bid_price) * 100) <= 1) / count(), 1) AS one_cent_pct
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('SPY', 'AAPL', 'KO', 'NVDA', 'MSFT', 'BKNG')
AND sip_timestamp >= '2026-06-10 15:00:00'
AND sip_timestamp < '2026-06-10 19:00:00'
AND bid_price > 0
AND ask_price > bid_price
GROUP BY ticker
ORDER BY avg_spread_cents在同一交易时段的四个午间小时内,六个标的中最紧凑的 KO,平均价差为 1.18 分,且在 82.5% 的更新中显示一分钱的价差。最宽的 MSFT,平均价差为 8.46 分,仅在 1.1% 的更新中显示一分钱价差。基于前者校准的队列模型对后者毫无参考价值。挂在报价之间的订单,即 中间价挂单,遵循相同的规则形成它们自己的队列。
针对回测的四个诊断指标
- 将模拟成交率与同一标的在相同时间段内的真实成交率进行比较。如果模拟器在真实成交率仅为 40% 的情况下实现了 70% 的成交,那么它描述的是你的假设,而非市场。
- 将模拟成交分为两类:一类是成交后该价格水平依然存在,另一类是仅在价格水平完全清空时才成交。如果结果偏向后者,意味着模拟器是在价格穿透你时才给你成交。
- 设定假设区间。分别以“所有撤单均来自队列前方”和“所有撤单均来自队列后方”进行重测。这两次运行的结果构成了你所选 p(x) 的真实误差范围。
- 测量你的价格处于数据源深度之外的频率。那里的成交是编造出来的。回测中的前瞻偏差 从另一个角度描述了同样的失败:即结果依赖于策略本身无法获取的信息。
何时停止建模并购买订单数据
逐单市场数据(MBO)携带每一笔独立订单的消息,从到达那一刻起,直到成交或撤单,每笔都有唯一的标识符。回放该数据源,一旦你假设了订单到达交易场所的现实延迟,你的排队位置就是可计数的,而非估算的。它比深度数据源更昂贵,存储空间也大得多。
诊断 3 是决策准则。如果前后两种假设区间都使策略盈利,那么中间假设即可。如果策略在一种假设下盈利,在另一种下亏损,那么队列模型本身就是策略的核心,此时购买逐单数据源比维护一个猜测更划算。
比例分配机制改变了问题
一些期货和期权市场将进入的订单按规模比例分配给同一价格上的挂单,而非按到达顺序。时间在那里的重要性大大降低,报价规模成为杠杆:你的规模翻倍,你在每笔成交中的份额大致也会翻倍。失败模式也随之转移,倾向于报价超过你实际想持有的规模。为什么期权订单无法成交 涵盖了从期权订单簿零售端看这一现象的情况。
常见问题解答
交易中的队列位置是什么?
队列位置是指你在同一交易场所、同一价格水平上挂单的排队顺序。在价格-时间优先的订单簿中,你前方的订单必须先成交或撤单,你的订单才能成交。
能否从二级市场数据计算队列位置?
不能精确计算。二级市场数据仅显示每个价格水平的总规模,没有订单标识,因此当规模在没有成交记录的情况下消失时,你无法判断这些股份是在你前方还是后方。你可以估算该数值并设定误差区间。精确数值需要逐单数据。
为什么回测的成交率通常过高?
常见的假设是将撤单在队列中均匀分布,而真实的撤单倾向于集中在后方。均匀模型使你的模拟订单前进速度快于真实队列,表现为成交次数更多、时机更好,这与真实交易不符。
什么是 MBO 数据?
逐单市场数据(Market-by-order)为每一笔独立订单提供消息,从到达、执行到撤单。这是唯一能让你精确计算排队位置而非进行建模的数据源,对于在买卖价差仅为一个跳动单位(tick)的策略中生存至关重要。
队列位置在比例分配机制的场所重要吗?
重要性较低。比例分配机制根据规模将进入的订单分配给挂单,因此先到达的优势很小,报价规模起到了决定性作用。在进行任何队列建模之前,先核实交易场所使用的分配模型是首要任务。
此处每个面板均包含生成该数据的 SQL。更改代码(ticker)、移动日期,并在 Strasmore 终端上针对你交易的标的提出同样的问题。