第四章:限价单与市价单——订单簿里的两种灵魂
好,我们继续往下走。前面几章我们把订单簿的骨架搭好了,现在该往里面塞真正的「血肉」了——也就是订单本身。
限价单和市价单,说白了就是撮合引擎里最核心的两种订单类型。我刚开始做撮合系统时,觉得这玩意儿太简单了,不就是「挂单等成交」和「直接吃掉对手盘」吗?结果第一次上线就出了bug——市价单把整个深度吃穿,导致系统崩溃。嗯,从那以后我再也不敢小看它们了。
4.1 限价单:订单簿的「基石」
限价单,你想想看,就是「我指定一个价格,到了这个价我才成交」。比如我说「我要在100块买入10个BTC」,那系统就得老老实实等着,直到有人愿意以100块或更低的价格卖给我。
我个人习惯把限价单分成两类:
- 被动限价单:挂上去后不立即成交,躺在订单簿里等别人来吃
- 主动限价单:挂上去后发现价格能对上,直接吃掉对手盘
这里有个关键点——限价单的匹配逻辑其实很简单:
// 伪代码:限价买单匹配逻辑
function matchLimitBuyOrder(order):
while order.remainingQuantity > 0:
// 从卖单队列头部取最优卖单
bestSell = sellOrderBook.getBestOrder()
// 如果最优卖单价格 > 限价单价格,停止匹配
if bestSell.price > order.price:
break
// 否则,按最优卖单价格成交
tradeQuantity = min(order.remainingQuantity, bestSell.remainingQuantity)
executeTrade(order, bestSell, tradeQuantity)
// 更新剩余数量
order.remainingQuantity -= tradeQuantity
bestSell.remainingQuantity -= tradeQuantity
// 如果对手盘被吃完,移除它
if bestSell.remainingQuantity == 0:
sellOrderBook.removeOrder(bestSell)
我在项目中遇到过一个问题:限价单的价格精度。有个交易所支持小数点后8位,结果有个用户挂了0.00000001 BTC的买单,直接把系统搞卡了。后来我们加了个最小价格跳动限制,才解决这个问题。
我曾经踩过一个坑:限价单的「价格相等」判断。浮点数比较千万别用 ==,要用价格档位比较。比如价格档位是0.01,那99.99和100.00就是不同档位,不能匹配。
4.2 市价单:订单簿的「推土机」
市价单就简单粗暴多了——「我不在乎价格,我只要成交」。你挂一个市价买单,系统会从最优卖单开始,一路吃下去,直到你的数量全部成交。
说白了,市价单就是「价格优先」的极致体现——它根本不关心价格,只关心能不能成交。
// 伪代码:市价买单匹配逻辑
function matchMarketBuyOrder(order):
while order.remainingQuantity > 0:
// 从卖单队列头部取最优卖单
bestSell = sellOrderBook.getBestOrder()
// 如果卖单队列为空,停止匹配
if bestSell is None:
// 这里要处理「部分成交」的情况
break
// 按最优卖单价格成交
tradeQuantity = min(order.remainingQuantity, bestSell.remainingQuantity)
executeTrade(order, bestSell, tradeQuantity)
// 更新剩余数量
order.remainingQuantity -= tradeQuantity
bestSell.remainingQuantity -= tradeQuantity
// 如果对手盘被吃完,移除它
if bestSell.remainingQuantity == 0:
sellOrderBook.removeOrder(bestSell)
你可能会问:「市价单会不会把整个深度吃穿?」
答案是:会的。而且这是个大问题。
我记得有一次,一个用户用市价单买了1000个BTC,而当时卖单深度只有500个BTC。结果呢?系统把500个BTC全部吃掉后,剩下的500个BTC无法成交,变成了「部分成交」状态。用户投诉说「我明明下了市价单,为什么没全部成交?」
市价单的匹配逻辑本质上和限价单一样,只是「价格条件」被移除了。市价单会一直吃,直到:
- 订单全部成交
- 对手盘队列为空
- 触发了风控限制(比如最大滑点)
4.3 两者在订单簿中的表现
限价单和市价单在订单簿里的表现,完全是两种画风。
| 特性 | 限价单 | 市价单 |
|---|---|---|
| 是否进入订单簿 | 是(未成交部分) | 否(立即成交或撤销) |
| 价格影响 | 提供流动性,稳定价格 | 消耗流动性,可能造成滑点 |
| 成交确定性 | 低(取决于价格是否到达) | 高(但价格不确定) |
| 订单簿可视化 | 显示为挂单(红色/绿色柱子) | 不显示,直接吃掉挂单 |
| 典型使用场景 | 做市商、策略交易 | 散户、紧急交易 |
我举个例子你就明白了:
假设当前订单簿是这样的:
卖单队列(价格从低到高):
100.00 10个
100.10 5个
100.20 8个
买单队列(价格从高到低):
99.90 12个
99.80 6个
99.70 4个
现在来了一个市价买单,要买15个。系统会怎么做?
- 先吃100.00的10个,成交价100.00
- 再吃100.10的5个,成交价100.10
- 总共成交15个,平均成交价 = (10*100.00 + 5*100.10) / 15 ≈ 100.033
你看,市价单把最优的卖单全部吃掉了,价格从100.00推到了100.10。这就是「滑点」的来源。
而如果来的是一个限价买单,价格100.05,要买15个:
- 检查最优卖单100.00,价格低于100.05,可以成交
- 吃掉100.00的10个
- 检查下一个卖单100.10,价格高于100.05,停止匹配
- 最终只成交10个,剩余5个挂在买单队列里
在实际系统中,市价单的处理要比限价单复杂得多。因为市价单可能造成「价格断层」——如果最优卖单和次优卖单之间差价很大,市价单会直接跳过这个差价,造成用户损失。我建议给市价单加一个「最大滑点限制」,比如「最多允许1%的滑点」,超过就撤销。
4.4 两者的「相爱相杀」
限价单和市价单,其实是互相依存的关系。
没有限价单,市价单就没东西可吃;没有市价单,限价单就永远无法成交。你想想看,如果所有人都挂限价单,那订单簿就变成了一潭死水——大家都在等别人先动。
我记得在做一个高频交易系统时,有个策略专门利用这个关系:它同时挂限价单和市价单。限价单用来提供流动性,赚取手续费折扣;市价单用来快速建仓或平仓。两者配合,效果出奇的好。
但这里有个坑——「订单簿的深度」问题。如果限价单太少,市价单就会造成巨大的价格波动。我见过一个极端案例:某个小币种,卖单深度只有100个币,结果一个市价买单要买200个,直接把价格从1块推到了10块——滑点900%。
在设计撮合引擎时,一定要考虑「市价单保护机制」。我常用的做法是:
- 限制市价单的最大数量(比如不超过总深度的20%)
- 设置价格保护(比如成交均价不能超过当前价格的X%)
- 对市价单收取更高的手续费(抑制滥用)
好了,这一章的内容就到这里。限价单和市价单,说白了就是订单簿里的「阴阳两面」——一个提供流动性,一个消耗流动性。理解了它们,你就理解了撮合引擎的一半。
下一章我们会讲「订单簿的更新与维护」,包括如何高效地插入、删除、修改订单。嗯,那才是真正考验数据结构功底的地方。