2. 交易市场与订单类型:交易所撮合机制、限价单与市价单、订单簿数据结构
好,咱们今天聊聊交易市场最底层的那些事。说白了,不管你用多牛逼的策略,最终都要跟交易所的撮合引擎打交道。我刚开始做高频的时候,以为策略牛逼就完事了,结果第一次实盘就被订单簿的细节坑得够呛。嗯,今天咱们就把这块彻底掰扯清楚。
2.1 交易所的撮合机制:价格优先、时间优先
交易所的撮合引擎,核心就八个字:价格优先,时间优先。你想想看,如果两个人同时想买,一个出价100块,一个出价99块,那肯定100块的先成交。如果价格一样,那就看谁先下单,先到先得。
我在项目中遇到过一个问题:我们的策略在某个交易所上,明明价格挂得比对手高,但就是成交慢。后来一查,原来是交易所的撮合引擎在处理同价位订单时,用的是「按订单ID排序」而不是「按到达时间排序」。这种细节,你不踩坑根本想不到。
- 价格优先:买单价格高的优先,卖单价格低的优先
- 时间优先:同价格下,先到达的订单优先
- 不同交易所可能有细微差异(比如同价位按订单ID排序)
这里有个容易忽略的点:市价单和限价单的交互逻辑。市价单说白了就是「不管价格,先成交再说」。它会直接吃掉订单簿里最优的价格。而限价单则是「我只接受这个价格,不满足我就不成交」。
2.2 限价单与市价单:两种订单的底层逻辑
咱们来细说一下这两种订单。限价单(Limit Order)和市价单(Market Order),其实是交易系统里最基础的两个概念。但很多人对它们的理解只停留在表面。
2.2.1 限价单(Limit Order)
限价单就是你指定一个价格,只有市场价格达到这个价格时,订单才会成交。比如你想买比特币,你挂一个限价买单,价格是50000。那只有当卖单价格降到50000或以下时,你的单子才会成交。
我个人习惯把限价单分为两种:
- 被动限价单:挂单后等待别人来吃。这种单子通常能拿到更好的价格,但可能成交不了。
- 主动限价单:虽然挂了限价,但价格已经优于当前最优价,实际上会立即成交。这种单子本质上跟市价单差不多,但能控制最大成交价格。
2.2.2 市价单(Market Order)
市价单就是「不管价格,立即成交」。你下市价买单,系统会从当前最优卖单开始,一路吃下去,直到你的订单数量全部成交。
这里有个关键点:市价单的成交价格是不确定的。如果订单簿深度不够,你可能吃到很远的价位。我记得有一次做回测,市价单的滑点直接让策略的夏普比率从2.0掉到了0.8。嗯,从那以后我再也不敢轻视滑点了。
2.3 订单簿数据结构:L2、L3与增量更新
订单簿(Order Book)是交易系统的核心数据结构。它记录了所有未成交的限价单。咱们做高频交易,订单簿就是我们的战场地图。
2.3.1 L2与L3订单簿
不同交易所提供的订单簿深度不一样:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| L2(Level 2) | 只显示每个价格档位的总挂单量 | 大多数策略够用,数据量小 |
| L3(Level 3) | 显示每个价格档位下的每笔订单详情 | 高频做市、订单流分析 |
我个人建议,如果你做高频做市,尽量用L3数据。因为L3能让你看到每个价格档位下的订单分布情况。比如,某个价格档位虽然总挂单量很大,但可能是一笔大单撑起来的,这种单子很容易被撤掉。
2.3.2 订单簿的增量更新
交易所不会每次都把整个订单簿推给你,那样数据量太大了。它们用的是增量更新:只告诉你哪些订单发生了变化。
典型的增量更新消息包含:
- 新增订单:新的限价单进入订单簿
- 撤销订单:已有的限价单被撤销
- 成交订单:订单被吃掉,从订单簿移除
- 修改订单:订单价格或数量发生变化
我在项目中遇到过一个问题:交易所的增量更新消息偶尔会丢包。如果你只依赖增量更新来维护本地订单簿,一旦丢包,你的本地订单簿就跟交易所的不一致了。所以,我建议你定期做一次全量快照同步。
- 初始化时拉取一次全量快照
- 后续只处理增量更新
- 每N秒或每N条消息后,做一次全量快照校验
- 如果发现不一致,立即重新拉取全量快照
2.4 订单簿数据结构:代码实现
咱们来看看订单簿在代码里长什么样。下面是一个简化版的订单簿实现,用Python写的:
class OrderBook:
def __init__(self):
# 买单:价格从高到低排序
self.bids = {} # price -> total_volume
# 卖单:价格从低到高排序
self.asks = {} # price -> total_volume
def update(self, side, price, volume):
"""更新订单簿"""
if volume == 0:
# 如果数量为0,说明这个价格档位的订单全部撤销
if price in self.bids:
del self.bids[price]
if price in self.asks:
del self.asks[price]
else:
if side == 'buy':
self.bids[price] = volume
else:
self.asks[price] = volume
def get_top_of_book(self):
"""获取最优买卖价"""
if not self.bids or not self.asks:
return None
best_bid = max(self.bids.keys())
best_ask = min(self.asks.keys())
return {
'bid': best_bid,
'bid_vol': self.bids[best_bid],
'ask': best_ask,
'ask_vol': self.asks[best_ask]
}
这个实现虽然简单,但已经能处理大部分场景了。实际生产环境中,你还需要考虑:
- 使用红黑树或跳表来维护价格排序(Python的sortedcontainers库很好用)
- 处理并发访问(用读写锁)
- 内存管理(订单簿数据量可能很大)
2.5 知识体系总览
下面这张图,是我画的本章节知识体系。你可以把它当作一个思维导图来看:
嗯,这张图把本章的核心内容都串起来了。你可以看到,撮合机制、订单类型、订单簿数据结构,这三者是相互关联的。理解了它们,你就掌握了交易系统最底层的逻辑。