2. 交易市场与订单类型:交易所撮合机制、限价单与市价单、订单簿数据结构

好,咱们今天聊聊交易市场最底层的那些事。说白了,不管你用多牛逼的策略,最终都要跟交易所的撮合引擎打交道。我刚开始做高频的时候,以为策略牛逼就完事了,结果第一次实盘就被订单簿的细节坑得够呛。嗯,今天咱们就把这块彻底掰扯清楚。

2.1 交易所的撮合机制:价格优先、时间优先

交易所的撮合引擎,核心就八个字:价格优先,时间优先。你想想看,如果两个人同时想买,一个出价100块,一个出价99块,那肯定100块的先成交。如果价格一样,那就看谁先下单,先到先得。

我在项目中遇到过一个问题:我们的策略在某个交易所上,明明价格挂得比对手高,但就是成交慢。后来一查,原来是交易所的撮合引擎在处理同价位订单时,用的是「按订单ID排序」而不是「按到达时间排序」。这种细节,你不踩坑根本想不到。

核心要点:
  • 价格优先:买单价格高的优先,卖单价格低的优先
  • 时间优先:同价格下,先到达的订单优先
  • 不同交易所可能有细微差异(比如同价位按订单ID排序)

这里有个容易忽略的点:市价单和限价单的交互逻辑。市价单说白了就是「不管价格,先成交再说」。它会直接吃掉订单簿里最优的价格。而限价单则是「我只接受这个价格,不满足我就不成交」。

避坑指南: 我曾经在回测时忽略了市价单的滑点问题。实盘时发现,市价单在流动性不足的时候,成交价格可能比你预期的差好几个tick。所以,做高频策略时,我建议你尽量用限价单,除非你确定流动性足够好。

2.2 限价单与市价单:两种订单的底层逻辑

咱们来细说一下这两种订单。限价单(Limit Order)和市价单(Market Order),其实是交易系统里最基础的两个概念。但很多人对它们的理解只停留在表面。

2.2.1 限价单(Limit Order)

限价单就是你指定一个价格,只有市场价格达到这个价格时,订单才会成交。比如你想买比特币,你挂一个限价买单,价格是50000。那只有当卖单价格降到50000或以下时,你的单子才会成交。

我个人习惯把限价单分为两种:

  • 被动限价单:挂单后等待别人来吃。这种单子通常能拿到更好的价格,但可能成交不了。
  • 主动限价单:虽然挂了限价,但价格已经优于当前最优价,实际上会立即成交。这种单子本质上跟市价单差不多,但能控制最大成交价格。

2.2.2 市价单(Market Order)

市价单就是「不管价格,立即成交」。你下市价买单,系统会从当前最优卖单开始,一路吃下去,直到你的订单数量全部成交。

这里有个关键点:市价单的成交价格是不确定的。如果订单簿深度不够,你可能吃到很远的价位。我记得有一次做回测,市价单的滑点直接让策略的夏普比率从2.0掉到了0.8。嗯,从那以后我再也不敢轻视滑点了。

小技巧: 如果你必须用市价单,建议设置一个「滑点保护」。比如,只允许市价单在最优价格上下浮动0.1%的范围内成交。很多交易所都支持这种「限价市价单」。

2.3 订单簿数据结构:L2、L3与增量更新

订单簿(Order Book)是交易系统的核心数据结构。它记录了所有未成交的限价单。咱们做高频交易,订单簿就是我们的战场地图。

2.3.1 L2与L3订单簿

不同交易所提供的订单簿深度不一样:

类型 说明 适用场景
L2(Level 2) 只显示每个价格档位的总挂单量 大多数策略够用,数据量小
L3(Level 3) 显示每个价格档位下的每笔订单详情 高频做市、订单流分析

我个人建议,如果你做高频做市,尽量用L3数据。因为L3能让你看到每个价格档位下的订单分布情况。比如,某个价格档位虽然总挂单量很大,但可能是一笔大单撑起来的,这种单子很容易被撤掉。

2.3.2 订单簿的增量更新

交易所不会每次都把整个订单簿推给你,那样数据量太大了。它们用的是增量更新:只告诉你哪些订单发生了变化。

典型的增量更新消息包含:

  • 新增订单:新的限价单进入订单簿
  • 撤销订单:已有的限价单被撤销
  • 成交订单:订单被吃掉,从订单簿移除
  • 修改订单:订单价格或数量发生变化

我在项目中遇到过一个问题:交易所的增量更新消息偶尔会丢包。如果你只依赖增量更新来维护本地订单簿,一旦丢包,你的本地订单簿就跟交易所的不一致了。所以,我建议你定期做一次全量快照同步。

订单簿维护的最佳实践:
  1. 初始化时拉取一次全量快照
  2. 后续只处理增量更新
  3. 每N秒或每N条消息后,做一次全量快照校验
  4. 如果发现不一致,立即重新拉取全量快照

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 知识体系总览

下面这张图,是我画的本章节知识体系。你可以把它当作一个思维导图来看:

交易市场与订单类型 撮合机制 价格优先 时间优先 订单类型 限价单 市价单 订单簿数据结构 L2 / L3 数据 增量更新 核心:价格优先 + 时间优先 + 订单簿维护 做高频交易,订单簿就是你的战场地图

嗯,这张图把本章的核心内容都串起来了。你可以看到,撮合机制、订单类型、订单簿数据结构,这三者是相互关联的。理解了它们,你就掌握了交易系统最底层的逻辑。

我的建议: 如果你刚开始接触这块,建议你先在模拟环境里跑一跑订单簿的代码。亲手感受一下增量更新是怎么工作的,比看一百篇文章都管用。

专注资料整理