Polymarket Bot 教程 · 第 17/32 章
将 Polymarket 订单簿失衡作为短期价格信号:买卖盘量比、microprice 计算、信号半衰期,以及失衡 bot 何时会胜过随机执行。
本章内容概览
订单簿失衡是限价订单簿中买方深度与卖方深度的比率。在 Polymarket 上,它确实存在,但持续时间很短的预测优势-通常在中间价变动前 5-30 秒。本章讲的是计算方式,以及信号何时会失真。
- 什么是订单簿失衡
- Microprice 计算
- 将失衡作为方向性信号
- Polymarket 上的信号半衰期
- 失衡信号何时会失真
- 代码:每个 WS tick 计算一次失衡
什么是订单簿失衡
订单簿失衡是限价订单簿中买方总深度与卖方总深度的比率。按前 N 层计算(通常 N=5),它捕捉的是在中间价尚未反映之前的整体 trader 压力。
公式:imbalance = (Σ bid[i].price × bid[i].size for i in [0..N]) − (Σ ask[i].price × ask[i].size) / (Σ both)。范围从 -1 到 +1;正值表示更强的买入压力,负值表示更强的卖出压力。
从经验上看,这个信号在 Polymarket 上是真实存在的,但噪声很大。单个 whale 可以在 30-60 秒内伪造失衡,随后再被 arbed 掉。它适合作为多个特征之一,但危险在于把它当成唯一触发条件。
Microprice 计算
Microprice 是对简单 mid 的改进:按最优买价和最优卖价各自的数量进行加权平均。
microprice = (best_bid × ask_size + best_ask × bid_size) / (bid_size + ask_size)
当买盘队列远大于卖盘时,microprice 会更靠近 ask。直觉是:排队等待买入的人更多,下一笔成交更可能把价格推向 ask,因此 fair value 更接近 ask。
Microprice 是实际中间价在 5-30 秒内的领先指标。生产环境中的 bots 会把它作为 take-profit 决策的参考价格,而不是天真的 mid。
将失衡作为方向性信号
来自生产观察:当失衡在 10 秒内从 -0.3 翻转到 +0.5,且没有伴随新闻事件时,接下来 30-60 秒内中间价上行 1-2 美分的概率约为 65%。
这确实是一个优势,但在小仓位下,扣除费用后会消失。想把它变现,bot 必须下足够大的量来覆盖价格波动减去费用,但又不能大到自己把订单簿推动起来。Polymarket 的订单簿通常很薄,超过 50 shares 的单子就可能移动市场。
应将失衡与其他特征结合使用:成交速度(成交越多 = 越像真实信号)、best-bid 是否真的上移(而不只是深度变动)、市场是否处于新闻驱动模式。
Polymarket 上的信号半衰期
失衡信号会衰减。来自我们 trader 的生产数据:imbalance > 0.6 → 预期在 60 秒内产生 1.2c 的中间价变动,半衰期约为 30 秒。90 秒后,预测价值已经归零。
对 bot 设计的含义是:要么快速响应,要么直接跳过。一个需要 15 秒才能决策的 bot,在下单前已经消耗掉一半优势。失衡策略的延迟预算应控制在从信号到触发 FOK 不超过 5 秒。
如果策略持仓时间长于半衰期(1-2 分钟),那本质上是在赌下一个信号,而不是当前信号。请明确这一点;不要不小心把基于失衡的仓位一直持有到结算。
失衡信号何时会失真
当以下三种情况之一成立时,信号会误导你。
- 新闻驱动的波动:失衡是你尚未看到的新闻的结果。逆着它交易会亏;顺着它交易则是 news arbitrage,属于另一种策略。
- Whale spoofing:一笔大单被挂出并迅速撤销,会在其存在期间制造虚假的失衡。通过检查失衡是否持续 10 秒以上再触发来过滤。
- 周期末再平衡:market makers 出于库存原因而不是信息原因撤 quote。几分钟后,当 MM 重新报价时,失衡会反转。
组合过滤条件是:imbalance > threshold AND trade velocity > baseline AND 最近 5 分钟内没有新闻事件。任何单一过滤条件都会产生太多误报。
代码:每个 WS tick 计算一次失衡
参考:订阅 WebSocket book 更新,在每个 tick 上重新计算失衡。
def on_book_message(msg):
bids = msg.get("bids", [])[:5]
asks = msg.get("asks", [])[:5]
bid_usd = sum(float(b["price"]) * float(b["size"]) for b in bids)
ask_usd = sum(float(a["price"]) * float(a["size"]) for a in asks)
total = bid_usd + ask_usd
if total < 100: return # illiquid
imb = (bid_usd - ask_usd) / total
state[msg["asset_id"]] = {
"imb": imb,
"best_bid": float(bids[0]["price"]) if bids else 0,
"best_ask": float(asks[0]["price"]) if asks else 1,
"ts": time.time()
}
# decision logic with cooldown + filters
if imb > 0.6 and time.time() - last_fired.get(msg["asset_id"], 0) > 60:
check_filters_and_maybe_fire(msg["asset_id"])
状态是按 token 维护的。冷却时间可防止对同一信号过度触发。过滤器(news 检查、trade velocity)负责放行实际交易。





