定价引擎设计:基础定价模型与动态调整
做市商的核心,说白了就是「怎么报价」。你手里有单子,市场在波动,你报高了没人买,报低了被人吃掉。这个平衡点怎么找?这就是定价引擎要解决的问题。
我个人习惯把定价引擎拆成四个模块来看:基础定价模型、库存调整、市场冲击、动态价差。今天咱们一个一个过。
Avellaneda-Stoikov 基础定价模型
这个模型是我入行时第一个认真啃过的定价模型。它不复杂,但很实用。核心思想就一句话:你的报价 = 市场中间价 + 库存调整项。
公式长这样:
# Avellaneda-Stoikov 基础定价
reservation_price = mid_price - gamma * sigma^2 * (T - t) * inventory
bid_price = reservation_price - spread / 2
ask_price = reservation_price + spread / 2
这里几个参数我解释一下:
- gamma:风险厌恶系数。你越怕亏,这个值越大。
- sigma:波动率。市场越动荡,你的报价偏移越大。
- T - t:剩余时间。快收盘了,你更想清库存。
- inventory:当前持仓。正数表示你手里有多单。
我在项目中遇到过一个问题:gamma 设得太小,库存风险敞口太大;设得太大,报价偏离市场太远,根本成交不了。后来我总结了一个经验——gamma 先取 0.1,然后根据回测结果慢慢调。
核心要点:Avellaneda-Stoikov 模型给你的不是「最优报价」,而是「合理报价区间」。真正的成交价,还得看市场深度和对手盘行为。
库存调整项:你手里的货决定了你的底气
库存调整,说白了就是「你手里货多了,就降价卖;货少了,就提价买」。这个逻辑听着简单,但实现起来有坑。
我常用的库存调整函数是这样的:
def inventory_adjustment(inventory, target_inventory, max_inventory):
"""
库存调整项计算
inventory: 当前持仓
target_inventory: 目标持仓(通常为0)
max_inventory: 最大允许持仓
"""
deviation = (inventory - target_inventory) / max_inventory
# 用双曲正切做平滑,避免突变
adjustment = np.tanh(deviation * 2)
return adjustment * max_spread_adjustment
为什么要用 tanh?因为线性函数在库存接近上限时,调整幅度会变得非常激进。我曾经吃过这个亏——有一次库存到了上限的 80%,线性调整直接把报价推到了市场深度之外,结果一整天没成交。后来换成 tanh,平滑多了。
我的经验:库存调整的幅度不要超过价差的一半。否则你的报价会变成「钓鱼单」,看着好看,实际成交不了。
市场冲击模型:你的单子会影响价格
这个点很多人会忽略。你想想看,如果你在盘口挂 1000 手卖单,市场价格会不会被你压下去?肯定会。这就是市场冲击。
我常用的冲击模型是 Almgren-Chriss 框架:
# 市场冲击成本
temporary_impact = alpha * trade_size / market_depth
permanent_impact = beta * trade_size / total_volume
# 实际成交价格
execution_price = mid_price + temporary_impact + permanent_impact
参数 alpha 和 beta 需要根据历史数据拟合。我一般用最小二乘法做回归,样本选最近 20 个交易日的逐笔成交数据。
嗯,这里要注意:市场冲击不是线性的。当你的单子超过市场深度的 30% 时,冲击成本会急剧上升。我建议在代码里加一个保护逻辑:
if trade_size > market_depth * 0.3:
# 拆单,分批执行
split_trades = split_order(trade_size, num_slices=5)
for slice in split_trades:
execute(slice)
time.sleep(0.5) # 等市场恢复
避坑指南:我曾经在流动性差的合约上没做拆单保护,一笔 500 手的单子直接把价格打穿了 3 个 tick,亏损惨重。从那以后,拆单逻辑成了我的标配。
动态价差调整:让报价适应市场节奏
价差不是固定的。市场活跃时,价差可以收窄;市场冷清时,价差要拉宽。否则你就是在给交易所送手续费。
我常用的动态价差模型:
def dynamic_spread(mid_price, volatility, order_book_imbalance, min_spread, max_spread):
"""
动态价差计算
"""
# 基础价差:波动率驱动
base_spread = volatility * mid_price * 2
# 订单簿不平衡调整
imbalance = (bid_volume - ask_volume) / (bid_volume + ask_volume)
imbalance_adjustment = 1 + imbalance * 0.5
# 最终价差
spread = base_spread * imbalance_adjustment
# 限制在合理范围内
spread = max(min_spread, min(spread, max_spread))
return spread
这个逻辑里,订单簿不平衡是个很妙的指标。如果买单深度远大于卖单深度,说明买方力量强,你可以适当收窄卖单价差,加快成交。
我个人习惯把动态价差和库存调整联动起来。库存高了,价差收窄一点,加速去库存;库存低了,价差拉宽,赚取更多 premium。
整体架构图
下面这张图是我做定价引擎时的核心框架,你可以看到四个模块是怎么协同工作的:
这个架构图里,四个模块并行处理市场数据,最终汇总成最优买卖报价。注意那条虚线反馈回路——实际成交后,库存变化会重新输入到库存调整模块,形成闭环。
实战中的参数调优
理论讲完了,说点实际的。参数怎么调?我一般分三步走:
- 历史回测:用过去 30 天的 tick 数据跑一遍,看 PnL 和 Sharpe ratio。
- 参数扫描:对 gamma、alpha、beta 做网格搜索,找最优组合。
- 实盘小量测试:用 10% 的资金跑一周,验证参数在真实市场中的表现。
我记得有一次,回测结果特别漂亮,年化收益 40%。结果实盘第一天就亏了 2%。后来发现是参数过拟合了——回测数据里有一段趋势行情,模型学会了「追涨杀跌」,但实盘是震荡市。从那以后,我坚持用不同市场环境的数据做交叉验证。
总结一下:定价引擎不是一锤子买卖。你需要持续监控参数表现,根据市场状态动态调整。我每周五下午都会花一小时检查参数,看看有没有 drift。
好了,定价引擎的核心内容就这些。下一节我们会聊订单簿重建和 tick 级数据清洗,那才是真正考验代码功底的地方。
无相订单流研究社 微信Lucian808555