Polymarket Bot 教程 · 第 29 章,共 32 章

在正式上线前,先构建一个 Polymarket paper trading 引擎:基于真实价格模拟下单,跟踪 P&L,在任何真实资金投入前执行 30 笔交易门槛(胜率 >=55%,且 PnL 为正),并提供代码骨架。

本章内容

Paper trading 是从策略想法到正式部署之间不可妥协的一步。本章展示的是一个简单的 paper engine-我们发布过的每个 live bot 都必须先通过它的门槛-代码不到 200 行 Python,能把每一笔交易记录到 JSONL 日志里,并应用与 live 路径相同的费用/滑点。

  • 为什么要先 paper,后 live(永远如此)
  • 30 笔交易门槛(验证过的 +55% 胜率 + 正 PnL)
  • 构建一个简单的 paper engine
  • 将 paper 日志与 live 日志并行跟踪
  • paper 与 live 何时出现偏差(以及原因)
  • 升级到 live:先小额首存
  • 代码:最小 paper engine

为什么要先 paper,后 live(永远如此)

30 笔交易的 paper 门槛,是区分 15.9% 盈利的 Polymarket trader 与 84.1% 亏损者的唯一纪律。大多数 builder 会跳过这一步,然后交学费。它之所以有效,原因很朴素:paper trading 会在足够多的样本下,暴露策略真实胜率,从而区分信号与运气。

跳过 paper 其实比它省下的成本更多。一种看起来在 backtest 中盈利、但实际上只是抛硬币的策略,往往会先烧掉 $200-500 的 live 资金,才积累到 30 个 live 样本。相同的 30 笔交易如果先做 paper,成本是 $0。

paper engine 不需要复杂。它需要诚实-同样的 fees、同样的 slippage、同样的 fill latency,和 live 路径保持一致。越简单越好,因为任何可有可无的东西都会被删掉,bot 就会比该上线的时间更早地进入 live。

30 笔交易门槛(验证过的 +55% 胜率 + 正 PnL)

这个门槛是二元的:30 笔已平仓的 paper 交易、事先写明的成功标准(通常是对于正 EV 策略,胜率 WR ≥ 55%),否则就不允许 live 部署。

30 是最小样本量,此时真实胜率的 95% 置信区间已经足够窄,可以区分信号和噪声。少于 30 时,观测到的 60% 胜率可能对应真实胜率 45-75%。到 30 及以上时,区间会收窄到大约 50-70%-仍然不算窄,但足以排除“这个策略只是抛硬币”这种情况。

成功标准必须在 paper 运行开始之前就设定好。之后再设定,只会导致事后合理化(你会想办法把任何 30 笔交易解释成“差不多够好”)。

构建一个简单的 paper engine

paper engine 本质上就是 live trading 代码,只是把下单函数替换成了模拟成交。这个模拟会:

  • 读取 live order book:与 live bot 会调用的接口相同。
  • 模拟成交:如果以 FOK 买入且价格 >= best ask,则按吃掉的 asks 的 volume-weighted average 成交;把成交写入 paper 日志。
  • 应用 fees:扣除 live 路径本来会支付的同样费用。
  • 跟踪 inventory:维护一个并行的 paper-balance 和 paper-positions 字典。

整个引擎只需 100-200 行 Python。关键纪律是:live 路径的每一个假设(成交率、延迟、费用)都必须在 paper 中复现,哪怕略微比现实更差-paper 应该是下限,而不是上限。

将 paper 日志与 live 日志并行跟踪

paper trading 运行会生成一个 JSONL 日志,其结构与 bot 以后会写出的 live 日志完全一致。字段相同:timestamp、action、market_slug、side、size、price、expected_fill_price、simulated_pnl_at_exit。

使用相同格式有两个原因。第一,读取 live 交易的分析工具(PnL 报告、胜率计算器)无需修改就能用于 paper。第二,后续将 paper 与 live 对比时,可以发现那些指向 bug 的偏差。

生产建议:让 paper engine 将数据写入与 live per_trade.jsonl 同一目录下的 per_trade_paper.jsonl。一条命令即可比较二者:diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).

paper 与 live 何时出现偏差(以及原因)

paper 和 live 之间出现偏差是不可避免的。常见的有三种。

  • Slippage:paper 以快照中的 ask 成交;live 会逐级吃单,在流动性薄的市场里可能差 1-2c。解决办法:在 paper 中模拟 slippage,给每笔交易增加一个等于 spread 一半的惩罚。
  • Fill latency:paper 立即成交;live 则需要 200-500ms,在这段时间价格可能已经变动。解决办法:在 paper 中模拟等待,并在“成交”前重新读取 order book。
  • Adverse selection:paper 假设你拿到的是最佳 ask;live 中你要与其他 bots 竞争,它们可能已经先一步吃掉了那个 ask。解决办法:这更难模拟;至少要诚实地告诉自己,paper 往往会高估结果。

当 paper 显示 +5%/月,而 live 跑出来是 -2%/月时,差距通常就来自这些因素之一。应当逐项审计,而不是直接假设策略本身错了。

升级到 live:先小额首存

paper 通过 30 笔交易后,live 部署计划如下:

  1. 先存入 $25-50 作为 smoke-test 资金。把它当作学费;如果亏掉了,这个教训也值这个价。
  2. 让 bot 以 live 模式运行 5-10 笔交易,仓位保持最小尺寸(5 shares)。
  3. 验证每一笔成交与 paper 预期的误差是否在 2c 以内。若偏差更大,先调查再继续。
  4. 如果这 5-10 笔 live 交易与 paper 一致,则追加 $200-500,并按正常尺寸运行。
  5. 如果不一致,立即停止,调试,修复,然后从步骤 1 重新开始。

首次部署时,live 与 paper 最常见的差距是遗漏 fee 或对 slippage 的估计错误。修复这些通常并不复杂;难点在于,在扩大资金规模之前先发现偏差。

代码:最小 paper engine

参考:读取 live book + 模拟 FOK 成交的简单 paper engine。

import json, time
PAPER_BAL = 10_000.0   # USD starting
positions = {}         # token_id -> shares

def paper_fok_buy(token_id, max_price, size):
    book = fetch_book(token_id)
    # Walk asks, fill what we can within max_price
    filled = 0; cost = 0
    for level in book.asks:
        px = float(level["price"])
        if px > max_price: break
        avail = float(level["size"])
        take = min(avail, size - filled)
        filled += take
        cost += take * px
        if filled >= size: break
    if filled < size:
        return {"status":"rejected","filled":0}  # FOK semantics

    global PAPER_BAL
    PAPER_BAL -= cost
    positions[token_id] = positions.get(token_id, 0) + filled

    log_paper({"ts": int(time.time()), "action":"buy",
               "token": token_id, "size": filled, "price": cost/filled})
    return {"status":"matched","filled":filled,"cost":cost}

生产补充:paper 卖出函数(与 buy 对称)、paper GTC 模拟(在 book 上按价格挂单,当 mid 到达该价格时模拟成交)、paper 日志与“本可以发生的” live 日志之间的对账。

常见问题

什么是 30 笔交易门槛?
这是我们内部从 paper 过渡到 live 的门槛规则:至少 30 笔已平仓的 paper 交易,胜率 >= 55%,且扣除 slippage 后净 PnL 为正。任何一项不满足,就继续留在 paper 阶段。我们在 2025 年经历了几次过早上线并导致账户亏损后,才制定了这条规则。
为什么是 30 笔,而不是 100 笔?
这是统计功效的问题。30 笔交易时,55% 胜率大致有 70% 的概率是真实 edge(而不是噪声)。100 笔交易时,这个置信度会提高到 90%+。我们选择 30 作为最低门槛,是因为更长的 paper 周期往往会诱发 overfitting-trader 会不断调参,而不是测试策略。
如果我很有把握,可以跳过 paper trading 吗?
越有把握,越不应该跳过。Polymarket 上表现最好的 bots,往往是由曾经多次判断失误的人在运行。30 笔交易门槛就是为了抓出那些看起来对、实际上不对的策略。我们自己的大多数策略一开始都没通过 paper-这正是它的价值。
paper 结果会和 live 结果一致吗?
对于慢速策略(政治、天气),通常一致;对于快速策略(5 分钟 crypto、sports microstructure),通常不一致。差距主要来自“paper trading 不支付 slippage”-真实成交通常比你看到的价格更差。在相信 live 之前,我们通常会把 paper 收益打 30-50% 折扣,尤其是快速策略。
如何用 Python 实现一个 paper engine?
为你交易的市场订阅真实的 CLOB WebSocket。当策略决定“下单”时,把它记录到一个 JSONL 文件里,并写入本应成交的价格(买入用当前 bid,卖出用当前 ask)。虚拟地跟踪 positions。按 live 价格做 mark-to-market。整个引擎大约 200 行 Python。
在上线前,我应该 paper trading 多久?
直到满足 30 笔交易门槛,或者 2-4 周,以更长者为准。如果你太快达到门槛,说明你过拟合了;应放慢速度,并确认你的胜率对小参数变化具有鲁棒性。如果几个月后仍达不到门槛,那这个策略大概率没有 edge,应该直接放弃。