Polymarket Bot Tutorial · 32章中29章目

本番稼働前にPolymarketのペーパートレーディングエンジンを構築しましょう。実際の価格に対して注文をシミュレートし、P&Lを追跡し、実資金投入の前に30トレードゲート(勝率55%以上、プラスのPnL)を適用し、コードの骨組みを作ります。

この章で扱う内容

ペーパートレーディングは、戦略アイデアと本番デプロイの間にある必須のステップです。この章では、これまで公開したすべての本番ボットにゲートとして使ってきたシンプルなペーパーエンジンを紹介します。Python 200行未満で、すべての取引をJSONLの日誌に記録し、本番ルートと同じ手数料・スリッページを適用します。

  • なぜ本番前にペーパーが必要か(常に)
  • 30トレードゲート(検証済みの勝率55%以上 + 正のPnL)
  • シンプルなペーパーエンジンの構築
  • ライブの日誌と並行してペーパージャーナルを追跡する
  • ペーパーが本番と乖離する場合(その理由)
  • 本番へ移行するタイミング:最初は少額入金
  • コード:最小限のペーパーエンジン

なぜ本番前にペーパーが必要か(常に)

30トレードのペーパーゲートは、利益を出しているPolymarketトレーダー7.6%と損失を出している84.1%を分ける、たった一つの規律です。多くのビルダーはこれを省略し、授業料を払います。正直に言うと、これが機能する理由は、ペーパートレーディングによって十分なサンプル数で戦略の真の勝率を明らかにでき、シグナルと運の差を見分けられるからです。

ペーパーを飛ばすと、節約できる以上のコストが発生します。バックテストでは利益が出ているように見えても、実際はコイントス程度の戦略なら、ライブ資金の$200-500を燃やしてから、30サンプル分のライブデータが得られるだけです。同じ30トレードをペーパーで回せばコストは$0です。

ペーパーエンジンは高度である必要はありません。必要なのは誠実さです。つまり、本番ルートと同じ手数料、同じスリッページ、同じ約定遅延を再現することです。シンプルであるほど良いです。というのも、任意機能があると削られ、本来より早くボットが本番投入されてしまうからです。

30トレードゲート(検証済みの勝率55%以上 + 正のPnL)

このゲートは二択です。30件のクローズ済みペーパートレード、事前に定義した成功条件(通常は正の期待値戦略で勝率≥55%)、これを満たさなければ本番デプロイはしません。

30は、真の勝率に対する95%信頼区間が、シグナルとノイズを区別できるほど十分に狭くなる最小サンプルサイズです。30未満では、観測上60%の勝率でも真の勝率は45〜75%に相当し得ます。30を超えると、この区間はおよそ50〜70%に狭まります。まだ広いですが、「この戦略はコイントスだ」という判断は排除できます。

成功条件は、ペーパー開始前に必ず設定しなければなりません。後から設定すると、事後的な正当化につながります(どんな30トレードでも「十分良い」と解釈する余地を作ってしまうためです)。

シンプルなペーパーエンジンの構築

ペーパーエンジンは基本的に、本番の売買コードから注文発行部分をシミュレーション約定に差し替えたものです。シミュレーションでは次の処理を行います。

  • ライブの板情報を読む: 本番ボットが行うのと同じ呼び出しを使う。
  • 約定をシミュレートする: FOKで買い、価格が最良売り気配以上なら、消費した売り板の出来高加重平均で約定させ、ペーパー日誌に記録する。
  • 手数料を適用する: 本番ルートで支払うのと同じ手数料を差し引く。
  • 在庫を追跡する: ペーパー残高とペーパー保有ポジションの辞書を並行して維持する。

エンジン全体はPython 100〜200行に収まります。重要なのは、本番ルートが前提にしているあらゆる仮定(約定率、遅延、手数料)を、ペーパーでも再現することです。たとえ現実より少し悪くしてでもです。ペーパーは上限ではなく下限であるべきです。

ライブの日誌と並行してペーパージャーナルを追跡する

ペーパートレーディングの実行結果は、後でボットが書くライブ日誌と構造上区別できないJSONL日誌を生成します。フィールドは同じです。timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit。

同じ形式を使う理由は2つあります。第一に、ライブ取引を読む分析ツール(PnLレポート、勝率計算機)が、そのままペーパーでも使えること。第二に、後でペーパーとライブを比較することで、バグを示す乖離を見つけられることです。

運用のコツ: ペーパーエンジンは、ライブのper_trade.jsonlと同じディレクトリにper_trade_paper.jsonlを書き出すようにします。1コマンドで両者を比較できます。diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl)

ペーパーが本番と乖離する場合(その理由)

ペーパーと本番の乖離は避けられません。よくあるものを3つ挙げます。

  • スリッページ: ペーパーはスナップショットの売り気配で約定しますが、本番では板を食い進めるため、薄い市場では1〜2セント不利になることがあります。解決策: ペーパーで1回の取引ごとにスプレッドの半分に相当するペナルティを加えてスリッページをシミュレートする。
  • 約定遅延: ペーパーは即時約定しますが、本番では200〜500msかかり、その間に価格が動く可能性があります。解決策: ペーパーでも待機してから板を再読込し、その後に「約定」させる。
  • 逆選択: ペーパーは最良売り気配で取れると仮定しますが、本番ではその気配をすでに他のボットが取り去っていることがあります。解決策: シミュレーションは難しいので、ペーパーが過大評価しがちだと自分に正直に認識すること。

ペーパーでは月+5%なのに、本番では月-2%になる場合、その差はたいていこのどれかです。戦略自体が間違っていたと決めつけず、1つずつ監査してください。

本番へ移行するタイミング:最初は少額入金

ペーパーで30トレードを通過したら、本番デプロイの計画は以下の通りです。

  1. $25-50を検証用資金として入金する。授業料だと考え、失っても学びの価値があったとみなす。
  2. 最小サイズ(5シェア)で、5〜10トレードだけボットを本番モードで動かす。
  3. 各約定がペーパーの予想と2セント以内で一致するか確認する。差がそれ以上なら先に進まず調査する。
  4. 5〜10回の本番トレードがペーパーと一致したら、$200-500を入金し、通常サイズのポジションで運用する。
  5. 一致しない場合は停止し、デバッグして修正し、ステップ1からやり直す。

初回デプロイで最も多いペーパーと本番の差は、手数料の抜け落ちかスリッページ見積もりの誤りです。そこを修正するのは難しくありません。重要なのは、資金を拡大する前にその差を見つける規律です。

コード:最小限のペーパーエンジン

参考: ライブの板を読み込み、FOK約定をシミュレートするシンプルなペーパーエンジン。

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}

本番用の追加要素: ペーパー売却関数(買いの反対)、ペーパーGTCシミュレーション(価格を付けて板に出し、ミッドがその価格に達したら約定をシミュレート)、ペーパー日誌と「起こり得た」ライブ日誌の照合。

よくある質問

30トレードゲートとは何ですか?
ペーパーから本番へ移行するための社内ゲート基準です。少なくとも30件のクローズ済みペーパートレード、勝率55%以上、スリッページ控除後のネットPnLがプラスであること。これらのいずれかを満たさなければ、ペーパー継続です。このルールは、2025年に早すぎる本番移行を何度も試して口座を吹き飛ばした後に作りました。
なぜ100トレードではなく30トレードなのですか?
統計的な検出力です。30トレードなら、勝率55%は概ね70%の確率で本当のエッジだと判断できます(ノイズではない)。100トレードなら、その確信度は90%以上に上がります。30を最低基準にしたのは、長すぎるペーパー期間がしばしば過学習を招くからです。トレーダーはテストを続けるより、戦略をいじり続けてしまいます。
自信があるならペーパートレードを飛ばしてもいいですか?
まさに自信があるときこそ、飛ばしてはいけません。Polymarketで最もうまくいくボットは、過去に間違えた経験のある人たちが運用しています。30トレードゲートは、一見正しそうだが実際には違う戦略を見つけるためにあります。私たち自身の戦略の多くも、最初はペーパーで失敗しました。それが価値です。
ペーパーの結果は本番の結果と一致しますか?
通常、ゆっくり動く戦略(政治、天気)では一致しやすく、速い戦略(5分クリプト、スポーツのマイクロストラクチャー)では一致しません。差の正体は「ペーパートレーディングではスリッページを払わない」ことです。実際の約定は見えていた価格より悪くなります。特に速い戦略では、本番で信じる前にペーパー収益を30〜50%割り引いて評価します。
Pythonでペーパーエンジンをどう実装すればいいですか?
取引する市場の実際のCLOB WebSocketに接続します。戦略が「注文を出す」と判断したら、JSONLファイルに想定約定価格(買いなら現在の買い気配、売りなら現在の売り気配)を記録します。ポジションは仮想で管理し、ライブ価格で時価評価します。エンジン全体は約200行のPythonです。
本番へ行く前に、どれくらいペーパートレードすべきですか?
30トレードゲートを満たすまで、または2〜4週間のうち長い方です。もしゲートに早く到達しすぎるなら、それは過学習です。少しパラメータを変えても勝率が堅牢か確認してください。数か月たってもゲートに届かないなら、その戦略にはおそらくエッジがなく、捨てるべきです。