做市商报价引擎:报价生成算法,报价更新频率控制,订单取消与重发策略
做市商的核心,说白了就是「两边报价,赚取差价」。但怎么报、报多快、什么时候撤单重发,这里面的门道可不少。我做了这么多年量化系统,见过太多团队在报价引擎上栽跟头——要么报价太慢被市场甩开,要么撤单太频繁被交易所罚款。
今天咱们就聊聊报价引擎的三个核心模块:报价生成算法、报价更新频率控制、订单取消与重发策略。这三个东西串起来,就是一套完整的做市商报价流水线。
一、报价生成算法:你的价格从哪里来?
报价生成,不是拍脑袋定个买一卖一就完事了。你得考虑市场深度、持仓风险、对手盘行为。我个人习惯把报价生成拆成三层:
- 基准价格层:确定当前公允价格
- 价差计算层:根据风险参数算出买卖价差
- 报价偏移层:根据库存、订单流做微调
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 什么时候该撤单?
我总结了三类必须撤单的场景:
- 价格偏离:你的报价与当前市场价差距超过阈值
- 库存超标:某个方向的库存超过安全线
- 订单被部分成交:部分成交后,剩余数量需要重新评估
我曾经犯过一个错误:只检查价格偏离,没检查库存。结果有一次库存已经偏多了,还在继续卖,最后被迫在不利价位平仓。嗯,从那以后我把库存检查加到了撤单逻辑的最前面。
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)
注意:撤单和重发之间,一定要检查「价格是否还合理」。市场变化太快,你撤单时算的价格,到重发时可能已经过时了。
四、整体架构:把三个模块串起来
说了这么多,咱们看看这三个模块怎么配合工作。下面这张图展示了报价引擎的核心流程:
从图上可以看到,整个流程是串行的:市场数据进来 → 算出报价 → 控制更新频率 → 决定是否撤单重发 → 最终输出到交易所。每一步都有它的职责,缺一不可。
五、实战中的几个坑
最后分享几个我踩过的坑,希望能帮你少走弯路:
坑1:报价生成和撤单重发用同一个线程
我曾经把报价生成和撤单重发放在同一个线程里,结果报价生成卡住了,撤单也发不出去。后来改成独立线程,各干各的,问题就解决了。
坑2:忽略交易所的限流规则
每个交易所的限流规则都不一样。有的按「每秒请求数」限流,有的按「每秒撤改单次数」限流。一定要仔细读API文档,不然被封了都不知道为什么。
坑3:撤单后不检查订单状态
撤单请求发出去了,不代表订单真的撤成功了。网络延迟、交易所处理延迟都可能导致撤单失败。一定要等撤单确认后再发新单,不然可能出现「重复挂单」的问题。
好了,报价引擎的核心内容就这些。说白了,报价引擎就是做市商的「大脑」——它决定了你在什么价格、什么时间、以什么方式参与市场。把这个模块做好了,你的做市策略就成功了一半。