一、订单簿基础:限价单与市价单、买卖盘口、订单簿数据结构

做量化交易这些年,我见过不少新手一上来就盯着K线图猛看,却忽略了最底层的东西——订单簿。说白了,订单簿就是市场的「实时账本」,每一笔成交、每一次价格跳动,都源于这里。今天我们就从最基础的东西聊起。

1.1 两种最基础的订单类型

先问个问题:你想买比特币,是直接按市场价买,还是挂个低价等着?这两种操作,对应了两种最核心的订单类型。

市价单(Market Order)

市价单,就是「我现在就要成交,不管价格多少」。你下单的那一刻,系统会立刻帮你吃掉对手盘上最便宜的卖单(如果你是买入)。

  • 优点:成交速度快,几乎瞬间完成
  • 缺点:价格不可控,遇到流动性差的时候,滑点会让你怀疑人生
我的经验:我在做高频策略时,市价单用得很少。除非是抢流动性极好的主流币种,否则我宁愿用限价单慢慢磨。有一次在某个小交易所做ETH套利,市价单下去直接滑了0.3%,利润全没了。

限价单(Limit Order)

限价单,就是「我只愿意在这个价格买/卖,不到这个价我就不动」。你的订单会挂在订单簿上,等着别人来吃。

  • 优点:价格可控,还能赚到买卖价差(如果你是做市商)
  • 缺点:可能永远成交不了,或者只成交一部分
注意:限价单不是「挂上去就完事了」。行情剧烈波动时,你的限价单可能被瞬间吃掉,也可能因为价格跳空而永远挂在那里。我曾经挂过一个BTC的限价买单,结果价格一路下跌,我的单子反而成了「接飞刀」,亏了不少。

1.2 买卖盘口:Bid与Ask

打开任何交易软件的深度图,你会看到左右两边的柱状图。左边是买盘(Bid),右边是卖盘(Ask)。

  • Bid(买方):所有人愿意出的最高买入价。比如Bid价格是100,意味着有人愿意花100块买。
  • Ask(卖方):所有人愿意卖的最低卖出价。比如Ask价格是101,意味着有人愿意101块卖。

Bid和Ask之间的差距,叫「价差」(Spread)。价差越小,说明市场流动性越好。我一般看一个币种是否值得做,先看价差——如果价差超过0.1%,我基本就放弃了。

核心概念:订单簿的「深度」就是看Bid和Ask两侧挂单的总量。深度越厚,大单对价格的冲击越小。

1.3 订单簿的数据结构

好了,现在我们来点硬核的。订单簿在计算机里是怎么存的?

说白了,就是一个「价格-数量」的映射。每个价格点,对应一个挂单总量。我习惯用两个数据结构:

  • 买盘(Bids):按价格从高到低排序。最高价在最前面。
  • 卖盘(Asks):按价格从低到高排序。最低价在最前面。

为什么这么排?因为成交逻辑是「价格优先,时间优先」。买单出价高的先成交,卖单出价低的先成交。

下面是我用Python模拟的一个简单订单簿结构:

class OrderBook:
    def __init__(self):
        self.bids = {}  # 买盘:{价格: 数量}
        self.asks = {}  # 卖盘:{价格: 数量}
    
    def update(self, side, price, quantity):
        if quantity == 0:
            # 数量为0,删除该价格档位
            if side == 'bid':
                self.bids.pop(price, None)
            else:
                self.asks.pop(price, None)
        else:
            if side == 'bid':
                self.bids[price] = quantity
            else:
                self.asks[price] = quantity
    
    def get_top_bid(self):
        # 获取最高买入价
        return max(self.bids.keys()) if self.bids else None
    
    def get_top_ask(self):
        # 获取最低卖出价
        return min(self.asks.keys()) if self.asks else None

嗯,这里要注意:实际交易所的订单簿更新频率极高,每秒可能有几百次变化。所以一般不会用Python的dict直接存,而是用跳表(Skip List)或者红黑树来维护有序性。不过原理是一样的。

1.4 订单簿的增量更新

你有没有想过,交易所是怎么把订单簿数据推送给你的?

如果每次变化都发全量数据,那带宽根本扛不住。所以交易所一般用「增量更新」——只告诉你哪些价格变了。

举个例子:

  • 原来买盘在100元有10个BTC
  • 有人撤单了5个,交易所推送一条消息:「买盘100元,数量变为5」
  • 你的本地程序收到后,更新本地订单簿

我刚开始做的时候,没处理好增量更新的时序问题,导致本地订单簿和交易所对不上。后来加了个序列号校验,才解决。

避坑指南:我曾经因为没处理「快照+增量」的同步逻辑,导致策略在开盘时疯狂报错。后来我养成了一个习惯:每次重启程序,先拉一次全量快照,再订阅增量流。这样能保证数据一致性。

1.5 知识体系结构图

下面这张图,是我自己梳理的订单簿知识体系。你可以把它当作本章的「地图」:

订单簿基础 订单类型 市价单 限价单 买卖盘口 Bid Ask 数据结构 价格-数量映射 增量更新 订单簿知识体系结构图 延伸:订单簿深度 → 市场冲击成本

这张图把本章的核心内容串起来了。从左到右,分别是订单类型、买卖盘口、数据结构。最下面那条虚线,指向的是我们下一节要讲的内容——订单簿深度与市场冲击成本的关系。

1.6 小结

好了,这一章我们聊了:

  • 市价单和限价单的区别,以及各自的坑
  • Bid和Ask的含义,价差怎么看
  • 订单簿的数据结构,以及增量更新的原理

这些东西看着简单,但我在实际项目中见过太多人栽跟头。尤其是增量更新的时序问题,几乎每个新手都会踩一次。嗯,希望你能少走点弯路。


专注资料整理