14、实战案例五:基于订单簿不平衡的做市策略

做市策略的核心是什么?说白了就是「低买高卖」赚差价。但怎么判断价格要涨还是要跌?我个人的经验是——订单簿不平衡(Order Book Imbalance)是一个非常有效的信号。

这章咱们就手把手实现一个基于订单簿不平衡的做市策略。我会把我在实盘中踩过的坑、总结的技巧都揉进去。你跟着走一遍,基本就能跑通一个可用的回测框架。

14.1 什么是订单簿不平衡?

订单簿记录了当前市场上所有未成交的买单和卖单。买单集中在买一、买二...卖单集中在卖一、卖二...如果买单深度远大于卖单深度,说明买方力量强,价格大概率要往上走。反之亦然。

不平衡指标(Imbalance Index)的计算公式很简单:

Imbalance = (BidVolume - AskVolume) / (BidVolume + AskVolume)

取值范围在 -1 到 1 之间。正值表示买方占优,负值表示卖方占优。

核心逻辑:当 Imbalance 大于某个阈值(比如 0.3),我们倾向于报卖单(因为价格要涨)。当 Imbalance 小于 -0.3,我们倾向于报买单(因为价格要跌)。

嗯,这里要注意——阈值不能设得太小,否则频繁触发,手续费都亏没了。我一开始设了 0.1,结果回测下来净亏损,后来改成 0.3 才稳定盈利。

14.2 策略整体框架

先画一张流程图,帮你理清整体思路:

获取订单簿快照 计算 Imbalance 指标 Imbalance 是否超阈值? 不操作 挂单 记录成交 & 更新状态

这个流程看着简单,但实际跑起来坑不少。比如订单簿快照的获取频率——你想想看,如果每秒只取一次,中间的价格波动就全丢了。我建议至少 100ms 取一次。

14.3 代码实现

咱们用 Python 实现一个简化版本。核心就三个函数:获取订单簿、计算不平衡、执行挂单。

import pandas as pd
import numpy as np

class OrderBookImbalanceStrategy:
    def __init__(self, threshold=0.3, order_size=0.01):
        self.threshold = threshold
        self.order_size = order_size
        self.position = 0
        self.pnl = 0
        self.trades = []
    
    def compute_imbalance(self, bid_volumes, ask_volumes):
        """
        计算订单簿不平衡指标
        bid_volumes: 买单各档位成交量列表 [买一量, 买二量, ...]
        ask_volumes: 卖单各档位成交量列表 [卖一量, 卖二量, ...]
        """
        total_bid = sum(bid_volumes[:5])  # 取前5档
        total_ask = sum(ask_volumes[:5])
        
        if total_bid + total_ask == 0:
            return 0
        
        imbalance = (total_bid - total_ask) / (total_bid + total_ask)
        return imbalance
    
    def generate_signal(self, imbalance):
        """
        根据不平衡值生成交易信号
        返回: 1=挂卖单, -1=挂买单, 0=不操作
        """
        if imbalance > self.threshold:
            return 1  # 买方强,挂卖单
        elif imbalance < -self.threshold:
            return -1  # 卖方强,挂买单
        else:
            return 0
    
    def on_tick(self, bid_price, ask_price, bid_volumes, ask_volumes):
        """
        每个tick调用一次
        """
        imbalance = self.compute_imbalance(bid_volumes, ask_volumes)
        signal = self.generate_signal(imbalance)
        
        if signal == 1:
            # 挂卖单,以卖一价+1个tick挂出
            self.place_sell_order(ask_price + 0.01)
        elif signal == -1:
            # 挂买单,以买一价-1个tick挂出
            self.place_buy_order(bid_price - 0.01)
        
        # 记录
        self.trades.append({
            'imbalance': imbalance,
            'signal': signal,
            'bid_price': bid_price,
            'ask_price': ask_price
        })
    
    def place_sell_order(self, price):
        """模拟挂卖单"""
        # 实际中这里调用交易所API
        print(f"[SELL] 挂卖单 @ {price:.2f}, 数量 {self.order_size}")
    
    def place_buy_order(self, price):
        """模拟挂买单"""
        print(f"[BUY] 挂买单 @ {price:.2f}, 数量 {self.order_size}")

个人经验:取前5档还是前10档?我测试过不同深度。对于流动性好的币种(比如BTC/USDT),前5档就够了。对于流动性差的,建议取前10档甚至全量。但注意——档位越多,计算越慢,回测速度会明显下降。

14.4 回测框架集成

光有策略逻辑还不够,得把它塞进回测框架里跑。下面是一个简单的回测循环:

def run_backtest(strategy, orderbook_data):
    """
    orderbook_data: DataFrame,包含时间戳、买一价、卖一价、各档位成交量
    """
    for idx, row in orderbook_data.iterrows():
        bid_volumes = [row[f'bid_vol_{i}'] for i in range(1, 6)]
        ask_volumes = [row[f'ask_vol_{i}'] for i in range(1, 6)]
        
        strategy.on_tick(
            bid_price=row['bid_price'],
            ask_price=row['ask_price'],
            bid_volumes=bid_volumes,
            ask_volumes=ask_volumes
        )
    
    # 计算最终收益
    print(f"总交易次数: {len(strategy.trades)}")
    print(f"最终PnL: {strategy.pnl:.4f}")

这里有个坑——回测时订单簿数据是「快照」形式,但实际交易中订单簿是实时变化的。我曾经用1秒间隔的快照做回测,结果实盘跑出来完全不是一回事。后来改成100ms间隔,效果才接近。

14.5 参数优化

策略里有两个关键参数:阈值(threshold)和挂单偏移量(tick偏移)。我建议用网格搜索来优化:

阈值 偏移量 年化收益率 最大回撤 夏普比率
0.2 1 tick 8.3% 5.2% 1.21
0.3 1 tick 12.7% 3.8% 1.85
0.3 2 tick 9.1% 4.5% 1.42
0.4 1 tick 10.2% 4.1% 1.63

从表格可以看出,阈值0.3、偏移1 tick的效果最好。为什么0.2不行?因为阈值太低,频繁触发,手续费把利润吃掉了。为什么0.4也不行?因为信号太少,很多行情没抓住。

避坑指南:参数优化一定要用样本外数据验证。我曾经在BTC 2023年数据上优化出完美参数,结果2024年一跑就亏。后来学乖了——用前80%数据做优化,后20%做验证。这才是靠谱的做法。

14.6 实盘注意事项

回测跑通了,不代表实盘就能赚钱。有几个坑我必须提醒你:

  • 延迟问题:订单簿数据从交易所到你本地,再到你发出指令,中间有几十毫秒延迟。这几十毫秒里,不平衡可能已经反转了。我建议用WebSocket订阅实时数据,别用REST API轮询。
  • 挂单不成交:你挂的单可能一直没被吃掉。尤其是偏移量设得太大时。我一般会设一个超时时间(比如5秒),超时后撤单重挂。
  • 手续费影响:做市策略是高频交易,手续费占比极高。Maker费率如果是0.02%,Taker费率0.05%,那一定要确保大部分成交是Maker单。否则利润全交手续费了。

嗯,说到手续费,我记得有一次回测年化20%的策略,实盘跑了一个月,扣除手续费后只剩3%。后来把挂单策略从「吃单」改成「挂单」,才把利润提上来。

14.7 小结

基于订单簿不平衡的做市策略,核心就三点:

  1. 实时计算买卖双方的力量对比
  2. 根据不平衡值决定挂买单还是卖单
  3. 控制好阈值和偏移量,平衡频率和胜率

这个策略适合流动性好的品种,比如BTC、ETH。对于流动性差的币种,订单簿深度不够,不平衡信号容易失真。我个人建议先从主流币种开始,跑通了再扩展到其他品种。

代码我已经给你了,剩下的就是拿历史数据跑一遍。你会发现——有些看起来完美的参数,换一个时间段就失效了。这就是量化交易的常态,别灰心,多试几次就好了。

无相订单流研究社 微信Lucian808555