4. 经典做市模型:Avellaneda-Stoikov模型、基于库存的做市模型、基于信号的自适应模型

做市策略的核心,说白了就是回答三个问题:报什么价?报多少量?什么时候调整?

我做了这么多年量化,见过太多人一上来就写策略,结果回测曲线漂亮得像假的一样。为什么?因为他们忽略了做市最本质的东西——库存风险信息不对称

这一章,我们来拆解三个经典模型。它们分别代表了做市策略的三个进化阶段:从理论最优,到风险控制,再到自适应学习。

经典做市模型演进路线 Avellaneda-Stoikov 理论最优报价 库存风险中性 基于库存的模型 库存上限管理 不对称报价偏移 自适应信号模型 多因子信号融合 动态参数调整 核心要素对比 A-S模型 • 假设订单流泊松到达 • 风险厌恶系数γ控制库存 • 报价对称偏移 库存模型 • 硬性库存上限 • 报价偏移与库存线性相关 • 偏向清仓方向报价 自适应模型 • 融合订单流/波动率信号 • 参数随时间动态调整 • 可结合机器学习

4.1 Avellaneda-Stoikov模型:理论基石

这个模型是2008年由Marco Avellaneda和Sasha Stoikov提出的。说实话,它至今仍是做市策略的理论天花板。我当年第一次读这篇论文时,被它的优雅震撼到了——用几个数学公式就把做市的核心矛盾说清楚了。

模型的核心思想很简单:做市商的最优报价,取决于他对库存风险的容忍度

公式长这样(别怕,我会拆开讲):

# Avellaneda-Stoikov 模型核心公式
# 最优买价 = 中间价 - 价差/2 - γ * σ² * (T - t) * q
# 最优卖价 = 中间价 + 价差/2 - γ * σ² * (T - t) * q

# 其中:
# γ: 风险厌恶系数(越大越保守)
# σ: 波动率
# T-t: 剩余时间
# q: 当前库存(正数表示多头,负数表示空头)

嗯,这里要注意:γ(风险厌恶系数)是这个模型最关键的参数。我见过有人把它设成0.1,结果回测爆仓;也有人设成10,结果一天都成交不了几笔。

核心洞察:

  • 当库存q为正(持有多头),买价会下移,卖价也会下移——整体报价偏向卖出
  • 当库存q为负(持有空头),报价整体偏向买入
  • γ越大,报价偏移越剧烈,库存回归越快

我的经验:γ的初始值可以从0.5开始调。我在BTC永续合约上做过测试,γ=0.8~1.2之间效果最好。但注意,不同品种的γ差异很大——波动率高的品种需要更大的γ。

4.2 基于库存的做市模型:实战派的选择

Avellaneda-Stoikov模型虽然漂亮,但有个问题:它假设订单流是均匀的、连续的。现实世界呢?我告诉你,订单流经常是突发的、聚集的

所以,基于库存的模型更接地气。它的逻辑就一句话:库存高了就降价卖,库存低了就提价买

具体实现上,我习惯用这样的策略框架:

class InventoryBasedMarketMaker:
    def __init__(self, max_inventory=100, target_inventory=0):
        self.max_inventory = max_inventory  # 最大库存容忍度
        self.target_inventory = target_inventory  # 目标库存
        self.base_spread = 0.001  # 基础价差(0.1%)
        
    def calculate_quote(self, mid_price, current_inventory):
        # 库存偏移量
        inventory_skew = (current_inventory - self.target_inventory) / self.max_inventory
        
        # 报价偏移:库存每偏离10%,价差偏移0.02%
        skew_adjustment = inventory_skew * 0.0002
        
        # 不对称报价
        bid_price = mid_price * (1 - self.base_spread/2 + skew_adjustment)
        ask_price = mid_price * (1 + self.base_spread/2 + skew_adjustment)
        
        return bid_price, ask_price

避坑指南:我曾经在ETH上跑这个策略,max_inventory设成了500。结果遇到一次剧烈波动,库存瞬间冲到800,报价偏移太大,直接被套利者吃掉了。后来我加了硬性风控——库存超过阈值就暂停报价。

这个模型的好处是直观、可控。你想想看,每个参数都有明确的业务含义:

参数 含义 典型值
max_inventory 你能承受的最大库存 日均成交量的1-2%
target_inventory 理想库存水平 通常为0(中性)
base_spread 基础买卖价差 市场平均价差的1.2-1.5倍
skew_factor 库存偏移敏感度 0.1-0.3(需回测确定)

4.3 基于信号的自适应模型:进化的方向

前面两个模型有个共同缺点:参数是固定的。市场环境变了,你还用同一套参数,那不是刻舟求剑吗?

自适应模型要解决的就是这个问题。它的核心思想是:根据市场信号动态调整做市参数

我个人比较喜欢的一种实现方式,是融合三个信号:

  1. 订单流信号:买卖订单的不平衡程度
  2. 波动率信号:近期波动率变化
  3. 库存信号:当前库存偏离程度

代码实现大概是这样的:

class AdaptiveMarketMaker:
    def __init__(self):
        self.base_spread = 0.001
        self.signal_weights = {'orderflow': 0.4, 'volatility': 0.3, 'inventory': 0.3}
        
    def calculate_signals(self, order_book, trades, inventory):
        # 订单流信号:最近1分钟买卖量比
        buy_volume = sum(t['volume'] for t in trades if t['side'] == 'buy')
        sell_volume = sum(t['volume'] for t in trades if t['side'] == 'sell')
        orderflow_signal = (buy_volume - sell_volume) / (buy_volume + sell_volume + 1e-8)
        
        # 波动率信号:最近20笔交易的收益率标准差
        returns = [t['price'] for t in trades[-20:]]
        volatility_signal = np.std(returns) / np.mean(returns)
        
        # 库存信号
        inventory_signal = inventory / self.max_inventory
        
        # 融合信号
        combined_signal = (
            self.signal_weights['orderflow'] * orderflow_signal +
            self.signal_weights['volatility'] * volatility_signal +
            self.signal_weights['inventory'] * inventory_signal
        )
        
        return combined_signal

关键点:自适应模型不是要完全取代前两个模型,而是在它们的基础上叠加信号层。我通常的做法是:用A-S模型或库存模型作为基础报价引擎,然后用自适应信号来微调参数。

为什么会这样设计?因为纯自适应的模型太「飘」了——参数变来变去,你很难判断它到底在学什么。我见过一个团队用强化学习做自适应,结果模型学会了「在市场平静时疯狂刷单」——这显然不是我们想要的。

我的建议:刚开始做自适应时,只让模型调整一个参数,比如只调整价差偏移量。等跑顺了,再逐步放开其他参数。步子迈大了,容易扯着蛋。

最后说一句:这三个模型不是互斥的。我现在的生产环境里,用的是Avellaneda-Stoikov的框架 + 库存模型的约束 + 自适应信号的微调。说白了,就是把三个模型的优点揉在一起。

做市策略没有银弹。但理解了这三个模型,你就有了搭建自己策略的乐高积木。怎么搭,看你的市场理解和风险偏好了。


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