定价引擎设计:基础定价模型与动态调整

做市商的核心,说白了就是「怎么报价」。你手里有单子,市场在波动,你报高了没人买,报低了被人吃掉。这个平衡点怎么找?这就是定价引擎要解决的问题。

我个人习惯把定价引擎拆成四个模块来看:基础定价模型、库存调整、市场冲击、动态价差。今天咱们一个一个过。

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。

整体架构图

下面这张图是我做定价引擎时的核心框架,你可以看到四个模块是怎么协同工作的:

定价引擎核心架构 市场数据输入 Avellaneda-Stoikov 基础定价模型 库存调整项 风险敞口管理 市场冲击模型 Almgren-Chriss 动态价差调整 波动率+订单簿 最优买卖报价 反馈调整

这个架构图里,四个模块并行处理市场数据,最终汇总成最优买卖报价。注意那条虚线反馈回路——实际成交后,库存变化会重新输入到库存调整模块,形成闭环。

实战中的参数调优

理论讲完了,说点实际的。参数怎么调?我一般分三步走:

  1. 历史回测:用过去 30 天的 tick 数据跑一遍,看 PnL 和 Sharpe ratio。
  2. 参数扫描:对 gamma、alpha、beta 做网格搜索,找最优组合。
  3. 实盘小量测试:用 10% 的资金跑一周,验证参数在真实市场中的表现。

我记得有一次,回测结果特别漂亮,年化收益 40%。结果实盘第一天就亏了 2%。后来发现是参数过拟合了——回测数据里有一段趋势行情,模型学会了「追涨杀跌」,但实盘是震荡市。从那以后,我坚持用不同市场环境的数据做交叉验证。

总结一下:定价引擎不是一锤子买卖。你需要持续监控参数表现,根据市场状态动态调整。我每周五下午都会花一小时检查参数,看看有没有 drift。

好了,定价引擎的核心内容就这些。下一节我们会聊订单簿重建和 tick 级数据清洗,那才是真正考验代码功底的地方。


交易系统化学习资料 微信Strategy888888