限价单与市价单:订单类型详解、订单生命周期、撮合引擎原理
大家好,我是你们的量化交易工程师讲师。今天我们来聊聊订单簿里最基础、也最容易被忽视的两个概念——限价单和市价单。
说实话,我见过不少新手一上来就研究什么高频策略、套利模型,结果连订单类型都没搞明白。有一次我在做回测时,发现策略收益曲线特别漂亮,结果实盘一跑就崩了。后来排查了半天,原来是市价单的滑点没算进去。嗯,从那以后,我对订单类型的理解就再也不敢马虎了。
一、订单类型:限价单 vs 市价单
先说说最核心的区别。你想想看,你去菜市场买菜:
- 市价单:就是「老板,给我来两斤排骨,多少钱都行,赶紧的!」——你只关心成交速度,不关心价格。
- 限价单:就是「老板,排骨超过30块一斤我就不买了,你看着办。」——你只关心成交价格,不关心能不能立刻买到。
在交易系统里,这两种订单类型对应着完全不同的行为逻辑。
1. 市价单(Market Order)
市价单,说白了就是「以当前市场最优价格立即成交」。你下单的时候,系统会直接吃掉对手盘里最便宜的卖单(如果你是买入)或者最贵的买单(如果你是卖出)。
我个人习惯在以下场景用市价单:
- 行情剧烈波动时,我需要快速进场或离场
- 流动性非常好的品种,比如BTC/USDT,买卖盘口深度足够
- 做市策略中,需要快速调整仓位
我曾经在流动性差的币种上用过市价单,结果滑点高达2%。你以为你买在了100块,实际成交均价到了102块。所以记住:市价单的代价是「价格不确定性」。
2. 限价单(Limit Order)
限价单就更有意思了。你指定一个价格,系统只在价格达到或优于你设定的条件时才成交。如果你是买入限价单,系统只会在价格≤你的限价时成交;卖出限价单则相反。
我建议在以下场景用限价单:
- 你想在某个支撑位或阻力位挂单等待
- 你做网格交易或做市策略时
- 你想吃盘口的买卖价差(也就是做「maker」)
很多交易所对maker订单(限价单挂单)有手续费减免,甚至返佣。我自己的策略里,能挂限价单就绝不市价成交,一年下来手续费能省不少。
二、订单生命周期:从下单到成交
一个订单从你点击「提交」到最终成交,经历了什么?我画了一张图,你看完就明白了。
流程其实不复杂:
- 提交订单:你通过API或交易界面发送订单请求
- 风控检查:系统检查你的账户余额、订单数量是否合规、是否在涨跌停范围内等。我见过有人因为风控没通过,订单直接被拒,还以为是系统bug。
- 进入订单簿:通过检查后,订单进入撮合引擎的内存订单簿
- 撮合引擎:核心环节,我们下面详细讲
- 三种结果:完全成交、部分成交、未成交(挂单等待)
三、撮合引擎原理:价格优先、时间优先
撮合引擎,说白了就是「红娘」。它负责把买卖双方的订单配对。核心规则只有两条:
价格优先:买单出价高的优先成交,卖单出价低的优先成交。
时间优先:同等价格下,先到订单簿的优先成交。
举个例子你就明白了。假设当前订单簿是这样的:
| 方向 | 价格 | 数量 | 时间顺序 |
|---|---|---|---|
| 卖单 | 100.50 | 10 | 第1个 |
| 卖单 | 100.60 | 5 | 第2个 |
| 卖单 | 100.60 | 8 | 第3个 |
| 买单 | 100.40 | 12 | 第4个 |
| 买单 | 100.30 | 6 | 第5个 |
现在你下了一个市价买单,数量15。会发生什么?
- 先吃掉100.50的10个(价格最低的卖单)
- 还差5个,继续吃100.60的5个(下一个最低价)
- 成交均价 = (100.50×10 + 100.60×5) / 15 = 100.533
你看,这就是市价单的滑点来源。如果你下的是限价买单,价格设在100.50,那只能成交10个,剩下的5个会挂在订单簿上等待。
我在做高频策略时,经常需要预估市价单的滑点。一个简单的办法是看订单簿的「深度」——也就是当前价位附近有多少挂单。深度越浅,滑点越大。我一般会写个脚本实时监控买卖盘口的前10档深度,超过阈值就改用限价单。
四、撮合引擎的内部实现
如果你对底层实现感兴趣,我简单说一下。撮合引擎的核心数据结构是两个「价格队列」:
- 买单队列:按价格从高到低排序,同价格按时间从早到晚
- 卖单队列:按价格从低到高排序,同价格按时间从早到晚
每次新订单进来,引擎会从对手盘队列的头部开始匹配,直到订单完全成交或无法继续匹配。代码实现大概长这样:
// 伪代码示例:撮合引擎核心逻辑
function matchOrder(newOrder, orderBook) {
let remainingQty = newOrder.qty;
let counterQueue = newOrder.isBuy ? orderBook.asks : orderBook.bids;
while (remainingQty > 0 && counterQueue.length > 0) {
let bestPrice = counterQueue[0].price;
// 检查价格是否满足条件
if (newOrder.isBuy && bestPrice > newOrder.price) break; // 限价单价格不够
if (!newOrder.isBuy && bestPrice < newOrder.price) break;
let matchQty = Math.min(remainingQty, counterQueue[0].qty);
// 执行成交
executeTrade(newOrder, counterQueue[0], matchQty);
remainingQty -= matchQty;
counterQueue[0].qty -= matchQty;
if (counterQueue[0].qty === 0) {
counterQueue.shift(); // 移除已成交的订单
}
}
// 如果还有剩余数量,且是限价单,则挂入订单簿
if (remainingQty > 0 && newOrder.type === 'LIMIT') {
orderBook.add(newOrder);
}
}
嗯,代码其实不复杂,但性能要求极高。我见过一些交易所的撮合引擎是用C++写的,单机每秒能处理几十万笔订单。为什么这么快?因为订单簿完全在内存里,用红黑树或者跳表来维护价格队列的排序。
如果你自己写回测系统,千万别用数据库来模拟订单簿。我曾经犯过这个错,回测一次要跑半小时。后来改成内存数据结构,几秒钟就搞定了。性能差距就是这么大。
五、总结一下
今天的内容其实就三个核心点:
- 限价单 vs 市价单:一个控价格,一个控速度。没有绝对的好坏,看场景。
- 订单生命周期:提交→风控→订单簿→撮合→成交/挂单。每一步都可能出问题。
- 撮合引擎原理:价格优先+时间优先,就这么简单。但实现起来细节很多。
我个人觉得,理解订单类型和撮合逻辑,是做量化交易的第一道门槛。你连订单怎么成交的都不清楚,策略写得再花哨也没用。下次我们聊聊订单簿的深度分析,看看怎么从盘口数据里挖出有用的信息。