4、经典做市模型:Avellaneda-Stoikov模型、基于库存的做市模型、基于信号的做市模型、模型参数校准

做市模型这东西,说白了就是回答三个问题:报什么价?报多深?什么时候调? 我入行那会儿,团队还在用Excel手动算报价,后来发现根本跑不过机器。今天聊的这几个模型,算是做市领域的「基本功」,你想想看,理解了它们,你就能看懂市面上80%的做市策略。

4.1 Avellaneda-Stoikov模型:做市界的「Hello World」

这个模型是2008年提出的,虽然年代久远,但至今仍是很多量化团队的起点。我个人习惯把它叫做「库存厌恶模型」——核心思想很简单:库存越多,报价越激进,目的是把货甩出去

模型假设做市商是风险中性的,但有一个风险厌恶系数γ。它推导出的最优报价公式长这样:

# 伪代码示意:Avellaneda-Stoikov 最优报价
def compute_quotes(s, q, gamma, sigma, T, k):
    """
    s: 当前中间价
    q: 当前库存(正数表示多头)
    gamma: 风险厌恶系数
    sigma: 波动率
    T: 剩余时间
    k: 订单簿深度参数
    """
    # 库存调整项
    inventory_skew = -gamma * sigma**2 * q * T
    # 最优买价和卖价
    bid = s + inventory_skew - (1/gamma) * (1 + gamma * sigma**2 * T / (2*k))
    ask = s + inventory_skew + (1/gamma) * (1 + gamma * sigma**2 * T / (2*k))
    return bid, ask

嗯,这里要注意:公式里的k是订单簿的流动性参数,我见过有人直接取0.1,结果报价飘得离谱。实际项目中,k需要根据历史数据做最大似然估计。

核心洞察: Avellaneda-Stoikov 模型告诉我们,做市不是「对称报价」。库存为正时,买价要压得更低,卖价也要降低——说白了就是「贱卖贵买」,加速库存回归中性。

我在项目中遇到过一个问题:这个模型假设波动率σ是常数,但实盘中波动率会突变。有一次比特币闪崩,模型还在按低波动率报价,结果库存瞬间爆仓。后来我加了一个波动率实时更新模块,才稳住。

4.2 基于库存的做市模型:更直接的玩法

Avellaneda-Stoikov 其实也属于库存模型,但这里说的「基于库存的做市模型」更直接——它不依赖复杂的随机控制理论,而是用一些启发式规则。

常见的做法是:

  • 库存阈值触发:当库存超过某个阈值(比如总资金的5%),直接调整报价偏移量
  • 线性调整:报价偏移 = -α × 库存量,α是调整系数
  • 非线性调整:用sigmoid函数做平滑,避免报价突变

举个例子,我曾在某商品期货上用过这个策略:

# 基于库存的报价调整
def inventory_adjusted_price(mid_price, inventory, max_inventory=100):
    # 库存比率
    ratio = inventory / max_inventory
    # 非线性调整:用tanh做平滑
    skew = 0.5 * np.tanh(ratio * 3)  # 3是陡峭度参数
    bid = mid_price - spread/2 - skew * spread
    ask = mid_price + spread/2 - skew * spread
    return bid, ask

避坑指南: 我曾经把α设得太大,结果库存稍微波动一下,报价就来回跳,产生了大量「虚假成交」。后来我加了低通滤波,让报价变化更平滑。

这种模型的优点是简单、计算快,适合高频场景。缺点是缺乏理论支撑,参数调起来全凭经验。我个人习惯先用Avellaneda-Stoikov跑仿真,再用库存模型做线上微调。

4.3 基于信号的做市模型:让数据说话

如果说库存模型是「被动防守」,那信号模型就是「主动进攻」。它的思路是:利用市场微观结构信号,预测短期价格走势,然后调整报价

常见的信号包括:

  • 订单簿不平衡:买一量和卖一量的比值
  • 成交单流:主动买和主动卖的成交量差
  • 价差变化:买卖价差的短期趋势
  • 波动率聚集:用GARCH模型预测未来波动

我做过一个实盘策略,用LSTM预测未来1秒的价格方向,然后动态调整报价偏移:

# 基于信号模型的报价调整(简化版)
def signal_based_quote(mid_price, signal_value, base_spread):
    # signal_value 范围 [-1, 1],正数表示看涨
    # 看涨时,提高卖价,降低买价
    bid_offset = -0.3 * signal_value * base_spread
    ask_offset = 0.3 * signal_value * base_spread
    bid = mid_price - base_spread/2 + bid_offset
    ask = mid_price + base_spread/2 + ask_offset
    return bid, ask

注意: 信号模型容易过拟合。我见过有人用20个特征做XGBoost,回测曲线漂亮得不行,一上实盘就亏成狗。信号越简单越好,我个人建议不超过3个特征。

你想想看,信号模型和库存模型其实可以结合。比如:库存高的时候,信号权重降低;库存低的时候,信号权重提高。这种混合策略在实盘中表现更稳健。

4.4 模型参数校准:别让参数毁了你的策略

模型再好,参数不对也是白搭。参数校准这块,我踩过的坑比走过的路还多。

常见的校准方法有:

参数 校准方法 我的经验
风险厌恶系数 γ 最大似然估计(MLE) 用过去30天的成交数据,滚动估计
波动率 σ EWMA或GARCH EWMA的衰减因子取0.94比较稳
流动性参数 k 订单簿斜率拟合 用限价单深度数据做线性回归
库存调整系数 α 网格搜索+回测 先粗搜再细搜,别一上来就全量

举个例子,校准γ的时候,我习惯这么做:

# 用历史数据校准风险厌恶系数
def calibrate_gamma(price_series, inventory_series, realized_pnl):
    # 假设做市商的目标是最大化期望效用
    # 用MLE估计gamma
    def neg_log_likelihood(gamma):
        # 计算每个时间点的效用
        utility = -np.exp(-gamma * realized_pnl)
        return -np.sum(np.log(utility))
    
    result = minimize(neg_log_likelihood, x0=0.1, bounds=[(0.01, 10)])
    return result.x[0]

重要提醒: 参数校准不是「一次搞定,终身使用」。市场环境变了,参数也得跟着变。我建议每天收盘后重新校准一次,遇到重大行情变化(比如加息、财报),盘中也要重新校准。

最后说一句,模型参数校准这件事,回测里表现最好的参数,实盘往往不是最优的。为什么?因为回测用的是历史数据,而市场是动态演化的。我个人的做法是:回测选3-5组参数,实盘用小资金并行跑,让市场来「投票」。

好了,经典做市模型就聊到这儿。下一章我们会讲做市策略的回测框架,到时候我会分享一个我自己写的回测引擎——嗯,那个引擎帮我抓过不少bug。