4. 经典做市模型:Avellaneda-Stoikov模型、基于库存的做市模型、基于信号的自适应模型
做市策略的核心,说白了就是回答三个问题:报什么价?报多少量?什么时候调整?
我做了这么多年量化,见过太多人一上来就写策略,结果回测曲线漂亮得像假的一样。为什么?因为他们忽略了做市最本质的东西——库存风险和信息不对称。
这一章,我们来拆解三个经典模型。它们分别代表了做市策略的三个进化阶段:从理论最优,到风险控制,再到自适应学习。
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 基于信号的自适应模型:进化的方向
前面两个模型有个共同缺点:参数是固定的。市场环境变了,你还用同一套参数,那不是刻舟求剑吗?
自适应模型要解决的就是这个问题。它的核心思想是:根据市场信号动态调整做市参数。
我个人比较喜欢的一种实现方式,是融合三个信号:
- 订单流信号:买卖订单的不平衡程度
- 波动率信号:近期波动率变化
- 库存信号:当前库存偏离程度
代码实现大概是这样的:
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