2. 订单簿数据结构:限价单与市价单、订单簿的Bid/Ask结构、价格优先与时间优先原则

做市商的订单簿,说白了就是一张实时更新的「买卖挂单表」。我刚开始接触这个的时候,觉得不就是个价格列表嘛,有什么难的?后来真上手做高频策略才发现,这里面的门道比想象中深得多。

今天咱们就把订单簿的底层数据结构拆开来看。你理解了这些,后面搭建策略时才能心里有底。

2.1 限价单与市价单:两种最基础的订单类型

先说说订单类型。交易所里每天跑着几百万笔订单,但归根结底就两种:限价单和市价单。

限价单(Limit Order)

限价单就是你指定一个价格,只有市场价到了这个位置才成交。比如你想在100元买入,那就挂一个限价买单。价格不到100,单子就一直在订单簿里挂着。

我个人习惯把限价单叫做「耐心单」。它不急着成交,但能帮你拿到更好的价格。做市商的核心工作,其实就是大量挂限价单,赚取买卖价差。

关键点: 限价单不保证成交,但保证成交价格。

市价单(Market Order)

市价单就简单了——你不管价格,直接按当前最优价吃掉对手盘。想快速买入?直接下市价单,系统会从卖一、卖二、卖三一路吃下去。

市价单我管它叫「急性子单」。它保证成交,但价格可能滑点。我在项目中遇到过好几次,行情剧烈波动时,市价单吃下去,成交均价比预期差了好几个tick,那叫一个心疼。

避坑指南: 我曾经在流动性差的币对上用过市价单,结果直接把价格打穿了两个档位。后来我学乖了,流动性不足时,宁可用限价单慢慢磨,也别用市价单去冲。
订单类型 成交保证 价格保证 典型用途
限价单 做市、挂单、低买高卖
市价单 快速建仓、止损、套利

2.2 订单簿的Bid/Ask结构:买卖双方的「战场地图」

订单簿长什么样?说白了就是两张表:一张买单表(Bid),一张卖单表(Ask)。

买单这边,价格从高到低排列。最高价的买单叫「买一」,次高的叫「买二」,以此类推。卖单那边正好相反,价格从低到高排列,最低价的卖单叫「卖一」。

你想想看,买一和卖一之间的差价,就是做市商最关心的「Spread」。这个差价越小,说明市场流动性越好。

我的经验: 我一般会盯着买一到买五、卖一到卖五的挂单量。如果买一突然出现大单,说明有人在托底;如果卖一被快速吃掉,说明多头在发力。这些细节,都是做市策略的信号来源。

下面这张图,是我用SVG画的订单簿结构图,帮你直观理解Bid/Ask的排列逻辑:

订单簿 Bid/Ask 结构示意图 Ask 卖单区(价格从低到高) 卖五 价格: 101.20 数量: 1,200 卖四 价格: 101.15 数量: 800 卖三 价格: 101.10 数量: 2,500 Spread(价差): 101.05 - 100.95 = 0.10 Bid 买单区(价格从高到低) 买一 价格: 100.95 数量: 3,000 买二 价格: 100.90 数量: 1,500 买三 价格: 100.85 数量: 900

2.3 价格优先与时间优先原则:订单簿的「排队规则」

订单簿里的单子怎么排队?两个原则:价格优先,时间优先。

价格优先——买单出价高的排前面,卖单出价低的排前面。这很好理解,谁出的价更「狠」,谁就先成交。

时间优先——价格相同的情况下,先挂单的先成交。这就跟排队买奶茶一样,先来的先得。

嗯,这里要注意:不同交易所对时间优先的实现细节略有不同。有的按纳秒级时间戳排序,有的按订单到达网关的顺序。做高频策略时,这些细节会直接影响你的成交概率。

实战要点: 做市商挂单时,如果想提高成交概率,可以在价格上稍微让利一点(比如比买一高0.01),或者在同一价格上尽量早挂单。我一般会在价格上让利,因为时间优先的竞争太激烈了,拼不过那些机房离交易所近的大户。

2.4 用Python实现一个简单的订单簿

光说不练假把式。下面我用Python写一个极简的订单簿,帮你把上面的概念串起来。

class OrderBook:
    def __init__(self):
        self.bids = []  # 买单列表,格式: [价格, 数量, 时间戳]
        self.asks = []  # 卖单列表
    
    def add_limit_order(self, side, price, quantity, timestamp):
        """添加限价单"""
        order = [price, quantity, timestamp]
        if side == 'buy':
            self.bids.append(order)
            # 价格优先:买单从高到低排序
            self.bids.sort(key=lambda x: (-x[0], x[2]))
        else:
            self.asks.append(order)
            # 价格优先:卖单从低到高排序
            self.asks.sort(key=lambda x: (x[0], x[2]))
    
    def get_top_of_book(self):
        """获取最优买卖价"""
        best_bid = self.bids[0][0] if self.bids else None
        best_ask = self.asks[0][0] if self.asks else None
        spread = best_ask - best_bid if best_bid and best_ask else None
        return best_bid, best_ask, spread

# 使用示例
ob = OrderBook()
ob.add_limit_order('buy', 100.95, 3000, 1)
ob.add_limit_order('buy', 100.90, 1500, 2)
ob.add_limit_order('sell', 101.05, 1200, 1)
ob.add_limit_order('sell', 101.10, 2500, 2)

bid, ask, spread = ob.get_top_of_book()
print(f"买一: {bid}, 卖一: {ask}, 价差: {spread}")
# 输出: 买一: 100.95, 卖一: 101.05, 价差: 0.10

这段代码虽然简单,但核心逻辑都在了。实际生产环境中,订单簿的数据结构会用红黑树或跳表来实现,保证O(log n)的插入和查询性能。不过原理是一样的——价格优先排第一,时间优先排第二。

小技巧: 我调试订单簿策略时,经常用这个简化版本来验证逻辑。等逻辑跑通了,再换成高性能的C++或Rust实现。别一上来就搞复杂的数据结构,容易把自己绕进去。

好了,订单簿的数据结构就聊到这儿。记住三个核心:限价单和市价单的区别、Bid/Ask的排列方式、价格优先和时间优先的排队规则。这些东西看着基础,但做市商策略的每一行代码,都建立在这些基石之上。


公众号:蓝海资料掘金营,微信deep3321