做市商报价引擎:报价生成算法,报价更新频率控制,订单取消与重发策略

做市商的核心,说白了就是「两边报价,赚取差价」。但怎么报、报多快、什么时候撤单重发,这里面的门道可不少。我做了这么多年量化系统,见过太多团队在报价引擎上栽跟头——要么报价太慢被市场甩开,要么撤单太频繁被交易所罚款。

今天咱们就聊聊报价引擎的三个核心模块:报价生成算法报价更新频率控制订单取消与重发策略。这三个东西串起来,就是一套完整的做市商报价流水线。

一、报价生成算法:你的价格从哪里来?

报价生成,不是拍脑袋定个买一卖一就完事了。你得考虑市场深度、持仓风险、对手盘行为。我个人习惯把报价生成拆成三层:

  1. 基准价格层:确定当前公允价格
  2. 价差计算层:根据风险参数算出买卖价差
  3. 报价偏移层:根据库存、订单流做微调

1.1 基准价格:用谁的价格?

最常见的做法是用「中间价」——也就是买一价和卖一价的均值。但这里有个坑:如果市场深度很薄,买一和卖一可能差得很远,中间价就不太靠谱了。

我在项目中遇到过这种情况:某个冷门合约,买一在100,卖一在105,中间价102.5。但实际成交价一直在101附近晃悠。后来我改用「加权中间价」,把深度也考虑进去:

def weighted_mid_price(bid_prices, bid_sizes, ask_prices, ask_sizes):
    """
    加权中间价:按深度加权计算
    """
    total_bid_volume = sum(bid_sizes[:5])  # 取前5档
    total_ask_volume = sum(ask_sizes[:5])
    
    weighted_bid = sum(p * s for p, s in zip(bid_prices[:5], bid_sizes[:5])) / total_bid_volume
    weighted_ask = sum(p * s for p, s in zip(ask_prices[:5], ask_sizes[:5])) / total_ask_volume
    
    return (weighted_bid + weighted_ask) / 2

嗯,这样算出来的基准价格,更贴近真实的「流动性中心」。

1.2 价差计算:赚多少合适?

价差不是固定的。市场波动大的时候,你得把价差拉宽,不然容易被「吃」掉。波动小的时候,价差可以收窄,提高竞争力。

我常用的一个模型是「动态价差模型」:

参数 含义 典型值
base_spread 基础价差(bps) 2-5 bps
volatility_multiplier 波动率乘数 1.0 - 3.0
inventory_skew 库存偏移因子 -0.5 ~ 0.5
def calculate_spread(base_spread, volatility, inventory_ratio):
    """
    动态价差计算
    """
    # 波动率调整
    vol_factor = 1.0 + volatility * 2.0  # 波动率越高,价差越大
    
    # 库存调整
    inv_factor = 1.0 + abs(inventory_ratio) * 0.5  # 库存偏离越大,价差越大
    
    spread = base_spread * vol_factor * inv_factor
    return max(spread, min_spread)  # 不能小于最小价差

你想想看,如果库存已经偏多了,你还按正常价差卖,那不是越卖越多?所以库存偏移因子就是干这个的——库存偏多时,把卖价调高一点,买价调低一点,慢慢把库存消化掉。

二、报价更新频率控制:快还是稳?

报价更新频率,这是个经典的两难问题。

更新太快:容易被交易所限流,甚至罚款。我记得有家交易所规定,每秒最多撤改单20次,超了直接封API。

更新太慢:报价跟不上市场变化,容易被套利者盯上。我曾经见过一个团队,报价更新间隔设了500ms,结果被高频交易者反复「钓鱼」,一晚上亏了十几万。

2.1 频率控制策略

我个人建议采用「自适应频率控制」:

  • 市场平稳时:降低更新频率,比如每200ms更新一次
  • 市场波动时:提高更新频率,比如每50ms更新一次
  • 极端行情:暂停报价,或者切换到「保护模式」

核心原则:报价更新的频率,应该与市场变化的「速度」匹配,而不是固定不变。

怎么判断市场变化速度?我一般用「价格变化率」和「订单簿变化率」两个指标:

def get_update_interval(price_change_rate, orderbook_change_rate):
    """
    自适应计算更新间隔
    """
    # 价格变化率:最近1秒内价格变化的bps
    # 订单簿变化率:最近1秒内订单簿变化的百分比
    
    if price_change_rate > 10 or orderbook_change_rate > 0.5:
        return 50  # 毫秒,快速更新
    elif price_change_rate > 3 or orderbook_change_rate > 0.2:
        return 100
    else:
        return 200

小技巧:不要把更新间隔设成固定值。用「上次更新后价格是否超出阈值」来判断要不要更新,比定时更新更高效。

三、订单取消与重发策略:该撤就撤,该发就发

做市商最怕什么?怕报价挂在那边被「吃掉」还不自知。

举个例子:你在买一挂了100手,突然市场暴跌,你的买一变成了「山顶价」。如果不及时撤单,你就成了接盘侠。

3.1 什么时候该撤单?

我总结了三类必须撤单的场景:

  1. 价格偏离:你的报价与当前市场价差距超过阈值
  2. 库存超标:某个方向的库存超过安全线
  3. 订单被部分成交:部分成交后,剩余数量需要重新评估

我曾经犯过一个错误:只检查价格偏离,没检查库存。结果有一次库存已经偏多了,还在继续卖,最后被迫在不利价位平仓。嗯,从那以后我把库存检查加到了撤单逻辑的最前面。

3.2 重发策略:撤了之后怎么办?

撤单不是终点,重发才是。重发策略的核心是「时机」和「价格」:

  • 立即重发:适用于价格快速变化时,撤单后马上按新价格挂单
  • 延迟重发:适用于市场震荡时,等几毫秒再挂,避免「追涨杀跌」
  • 分批重发:大单拆成小单,分批挂出,减少市场冲击
def cancel_and_resend(order, new_price, strategy='immediate'):
    """
    撤单并重发
    """
    cancel_order(order.order_id)
    
    if strategy == 'immediate':
        send_order(order.symbol, order.side, order.quantity, new_price)
    elif strategy == 'delayed':
        # 延迟50ms再发
        schedule_task(lambda: send_order(...), delay_ms=50)
    elif strategy == 'batched':
        # 拆成3批,每批间隔30ms
        for i in range(3):
            batch_qty = order.quantity // 3
            schedule_task(lambda: send_order(...), delay_ms=i * 30)

注意:撤单和重发之间,一定要检查「价格是否还合理」。市场变化太快,你撤单时算的价格,到重发时可能已经过时了。

四、整体架构:把三个模块串起来

说了这么多,咱们看看这三个模块怎么配合工作。下面这张图展示了报价引擎的核心流程:

报价引擎核心流程 市场数据输入 报价生成算法 基准价格 → 价差计算 → 报价偏移 报价更新频率控制 自适应间隔:50ms / 100ms / 200ms 订单取消与重发策略 撤单判断 → 重发时机 → 分批策略 报价输出到交易所

从图上可以看到,整个流程是串行的:市场数据进来 → 算出报价 → 控制更新频率 → 决定是否撤单重发 → 最终输出到交易所。每一步都有它的职责,缺一不可。

五、实战中的几个坑

最后分享几个我踩过的坑,希望能帮你少走弯路:

坑1:报价生成和撤单重发用同一个线程

我曾经把报价生成和撤单重发放在同一个线程里,结果报价生成卡住了,撤单也发不出去。后来改成独立线程,各干各的,问题就解决了。

坑2:忽略交易所的限流规则

每个交易所的限流规则都不一样。有的按「每秒请求数」限流,有的按「每秒撤改单次数」限流。一定要仔细读API文档,不然被封了都不知道为什么。

坑3:撤单后不检查订单状态

撤单请求发出去了,不代表订单真的撤成功了。网络延迟、交易所处理延迟都可能导致撤单失败。一定要等撤单确认后再发新单,不然可能出现「重复挂单」的问题。

好了,报价引擎的核心内容就这些。说白了,报价引擎就是做市商的「大脑」——它决定了你在什么价格、什么时间、以什么方式参与市场。把这个模块做好了,你的做市策略就成功了一半。

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