4. 经典做市策略模型:Avellaneda-Stoikov模型原理、库存风险与定价偏移、策略参数调优实战
做市策略千千万,但要说最经典的、被写进教科书最多的,那一定是 Avellaneda-Stoikov 模型。我当年刚入行时,带我的老交易员就扔给我一篇论文:「把这个看懂,你就算入门了。」说实话,第一次看那堆随机微分方程,我头都大了。但后来在实际项目中跑起来,才发现这模型是真的好用。
今天我们就把它拆开揉碎,讲清楚三个核心问题:模型到底在算什么?库存风险怎么管?参数怎么调才能赚钱?
4.1 模型核心思想:报价不是拍脑袋
传统做市商怎么报价?很多人凭感觉:买一价挂低一点,卖一价挂高一点,赚个差价完事。但 Avellaneda-Stoikov 模型告诉我们——报价应该是一个数学优化问题。
模型假设市场上有两个关键变量:
- 库存 q(t):你当前持有的资产数量(正数表示多头,负数表示空头)
- 最优报价 s(t):市场当前的中间价
你的任务就是:在控制库存风险的前提下,最大化做市收益。说白了,就是既要赚价差,又不能让库存砸手里。
核心公式(简化版):
最优买价 = 中间价 - 价差/2 - 库存调整项
最优卖价 = 中间价 + 价差/2 - 库存调整项
其中库存调整项 = γ * σ² * q(t) * (T - t)
γ:风险厌恶系数(你有多怕库存风险)
σ:波动率
T - t:剩余时间
嗯,这里要注意:库存调整项是带符号的。如果你持有多头(q>0),买价和卖价都会往下调——你想把货卖出去,所以报价要更有竞争力。反过来,空头时报价往上调,吸引别人卖给你。
4.2 库存风险:做市商的头号敌人
我在项目中遇到过最惨的一次:某币种突然暴跌,我们库存里全是多头,报价还没来得及调整,直接亏了六位数。从那以后,我对库存风险就特别敏感。
Avellaneda-Stoikov 模型把库存风险量化成了两个维度:
| 风险维度 | 数学表达 | 实际含义 |
|---|---|---|
| 库存规模 | |q(t)| | 你手里有多少货 |
| 波动率暴露 | σ² * q(t)² | 价格波动对你的影响 |
| 时间衰减 | (T - t) | 你还有多久来平仓 |
你想想看,如果波动率很大(比如 5%),你又持有了大量库存,那模型会建议你把报价偏移调得非常大——甚至可能比市场价低好几个 tick。这听起来有点激进,但逻辑是对的:宁可少赚点,也要先把风险降下来。
我曾经踩过的坑:刚开始做 BTC 做市时,我把 γ 设得太小(0.01),觉得「波动大正好多赚点价差」。结果一次闪崩,库存亏损直接吃掉了一个月的利润。后来我把 γ 调到 0.1,虽然每天少赚 15%,但回撤小了很多。记住:做市不是赌博,是管理风险。
4.3 定价偏移:如何动态调整报价
定价偏移(Pricing Shift)是 Avellaneda-Stoikov 模型最精彩的部分。它告诉你:报价不是对称的。
举个例子:假设 BTC 当前价格 60000,你持有 10 个 BTC 多头。模型算出来:
- 基础价差:0.1%(60 美元)
- 库存调整项:-0.05%(-30 美元)
- 最优买价:60000 - 30 - 30 = 59940
- 最优卖价:60000 + 30 - 30 = 60000
看到了吗?买价和卖价都往下移了 30 美元。你的买价更低(更不愿意买),卖价也更低(更愿意卖)。这就是库存驱动的非对称报价。
我的个人习惯:在实际系统中,我会把库存调整项拆成两部分:
库存调整项 = 线性项 + 非线性项
线性项 = γ₁ * σ * q(t)
非线性项 = γ₂ * σ² * q(t)² * sign(q(t))
线性项处理小库存时的微调,非线性项处理大库存时的紧急平仓。这样在极端行情下反应更快。
4.4 策略参数调优实战
理论讲完了,咱们来点实际的。参数调优是 Avellaneda-Stoikov 模型落地的关键。我一般分三步走:
4.4.1 参数清单
| 参数 | 符号 | 典型范围 | 调优方向 |
|---|---|---|---|
| 风险厌恶系数 | γ | 0.01 - 1.0 | 越大越保守 |
| 波动率窗口 | σ_window | 5分钟 - 1小时 | 越短越敏感 |
| 目标库存 | q_target | 0 或 对称区间 | 通常设为0 |
| 最大库存 | q_max | 日均交易量的1-5% | 风控红线 |
| 报价更新频率 | Δt | 100ms - 1s | 越快越占优 |
4.4.2 回测框架示例
下面是我常用的回测代码片段,用 Python 写的,你可以直接拿去改:
import numpy as np
import pandas as pd
def avellaneda_stoikov_backtest(price_series, gamma=0.1, sigma=0.02, q_max=100):
"""
简化版 Avellaneda-Stoikov 回测
"""
inventory = 0
pnl = 0
trades = []
for t in range(1, len(price_series)):
mid_price = price_series[t]
sigma_hat = price_series[t-50:t].std() # 滚动波动率
# 计算库存调整项
inventory_adj = gamma * sigma_hat**2 * inventory
# 报价
bid = mid_price - 0.5 * sigma_hat - inventory_adj
ask = mid_price + 0.5 * sigma_hat - inventory_adj
# 模拟成交(简化:假设50%概率成交)
if np.random.random() < 0.5:
# 买入
inventory += 1
pnl -= bid
if np.random.random() < 0.5:
# 卖出
inventory -= 1
pnl += ask
# 库存风控
if abs(inventory) > q_max:
# 强制平仓
pnl += inventory * mid_price * 0.99 # 打折平仓
inventory = 0
return pnl, inventory
调优实战建议:
- 先跑历史数据:用过去 30 天的 tick 数据,调 γ 和 σ_window
- 观察库存曲线:如果库存长期偏离 0,说明 γ 太小
- 看最大回撤:如果单日回撤超过 2%,调大 γ 或缩小 q_max
- 实盘前模拟:用模拟盘跑一周,确认参数稳定
4.5 模型局限性
说实话,Avellaneda-Stoikov 模型也不是万能的。我在实际项目中发现了几个问题:
- 假设太理想:模型假设市场是连续的、成交概率是固定的。但真实市场有跳空、有流动性黑洞
- 波动率估计:用历史波动率预测未来,在极端行情下会滞后
- 不考虑对手方:模型没考虑其他做市商的策略,容易陷入「报价战」
避坑指南:我曾经在某个小币种上直接套用标准模型,结果因为流动性太差,报价半天成交不了,库存越积越多。后来我加了「最小成交量」约束——如果过去 10 秒成交量低于阈值,就缩小报价规模。这才稳住局面。
4.6 本章小结
Avellaneda-Stoikov 模型的核心就三句话:
- 报价 = 市场价 + 价差/2 - 库存风险调整
- 库存风险 = γ * σ² * q * (T-t)
- 参数调优 = 平衡收益与风险
你想想看,做市商本质上就是个「风险搬运工」——把流动性从充裕的地方搬到稀缺的地方,赚点搬运费。Avellaneda-Stoikov 模型给了你一个数学框架,但最终能不能赚钱,还得看你对市场的理解。
我个人建议:先把模型跑通,再慢慢加自己的「私货」。比如我后来加了订单簿不平衡因子、加了时间衰减权重、还加了机器学习预测的波动率。但基础框架,还是 Avellaneda-Stoikov。
公众号:蓝海资料掘金营,微信deep3321