Polymarket Bot Tutorial · 32章中の30章

Polymarket bot向けの本番運用レベルのリスク管理コード:ポジション上限、日次損失上限、停止センチネル、約定率ウォッチドッグ、再起動時の整合性確認、冪等なリトライ。実際の本番トレーダーから得たコードパターン。

この章で扱う内容

リスクコードは、本番トレーディング bot の大部分を占めます。戦略ロジックは簡単な部分で、その周囲にある上限、停止、ウォッチドッグ、再整合の仕組みこそが、bot が最初の悪い週を生き残れるかを左右します。この章では、本番運用レベルのリスクパターンを扱います。

  • なぜリスクコードが実際の trading bot の大部分を占めるのか
  • ポジション上限(市場別、戦略別、総量)
  • 日次損失のキルスイッチ
  • 停止センチネル(ファイルベースの緊急停止)
  • 約定率ウォッチドッグ
  • 再起動時に台帳とオンチェーンを整合させる
  • コード:本番対応の停止認識ループ

なぜリスクコードが実際の trading bot の大部分を占めるのか

自社 bot のコードベースで測定したところ、LOC の60%がリスクコード(上限、停止、ウォッチドッグ、再整合)でした。30% が戦略、10% が接続用の glue コードです。

この比率は正しいです。戦略は簡単な部分で、いつ入っていつ出るかを記述するだけなら数十行で済みます。リスクコードはそれ以外すべてです。想定より速く価格が逆行したらどうするか、約定が止まったらどうするか、WebSocket が切れたらどうするか、戦略が実は収益性を持たないと分かったらどうするか。

多くの失敗談は同じ形をしています。戦略自体は動いていたのに、相場のレジーム転換時に停止が発動せず、そのまま取引を続けてしまったのです。戦略を書く前に、停止処理を書いてください。

ポジション上限(市場別、戦略別、総量)

3つの上限を、コードで強制します。

  • 市場別上限: edge の確信度に関わらず、1つの市場あたり最大 $X。典型値:小規模 bot なら $25-100、本番運用なら $200-500。単一市場での誤判断による損害範囲を限定します。
  • 戦略別上限: 複数戦略を運用する場合、各戦略に総資本の一部を割り当てます。典型値:戦略ごとに30-50%。ある戦略の不調が他の戦略の資本を食い潰すのを防ぎます。
  • 総量上限: ウォレット残高の何%までを同時に投入するかの上限。典型値:50-70%。予期しない機会や、bot 自身の会計バグに備えて資本を残します。

3つの上限はすべて、戦略ロジックだけでなく、注文発行関数の内部で強制すべきです。戦略にはバグが入り得ます。注文発行ゲートが最後の防衛線です。

日次損失のキルスイッチ

最も重要な単体のリスク制御は、日次損失のキルスイッチです。

ルール:UTCの深夜以降の実現損益 + 評価損益が、当日開始時点の残高の -X% を下回ったら、bot は新規ポジションの建玉を停止し、必要に応じて既存ポジションをフラット化します。典型的な X は 5-10% です。

数値の考え方:期待勝率 60% の bot でも、10連敗する確率はおそらく 5% ほどあります。キルスイッチがなければ、この連敗は損失を増幅させます。$200 の損失 → bot は取引を継続 → さらに $200 の損失 → ウォレットは40%減。-10% でスイッチが発火すれば、悪い日の損失は $200 で止まり、翌日 bot は新たに開始できます。

このスイッチはサーバー側で強制します。停止ファイルを書き込むか、トレーディングループが毎回確認するデータベースフラグを設定してください。再起動は手動レビュー後のみ行います。

停止センチネル(ファイルベースの緊急停止)

最もシンプルな停止機構は、bot が毎ループでファイル(例:/opt/pmt/HALT)の有無を確認し、ファイルが存在すれば取引を停止する方法です。

def trading_loop():
    while True:
        if os.path.exists("/opt/pmt/HALT"):
            log("HALT file detected, sleeping")
            time.sleep(30)
            continue
        run_one_iteration()
        time.sleep(5)

どこからでも即座に停止するには(SSH、Telegram bot、監視システムなど)、touch /opt/pmt/HALT を実行します。再開するには rm /opt/pmt/HALT です。

ファイルベースの方式をあえて低技術にしているのは、より高度な停止機構が失敗する状況でも機能するからです。bot が部分的にクラッシュしているとき、データベースに到達できないとき、API key にレート制限がかかっているときでも使えます。ファイルシステムへのアクセスは常に利用可能です。

約定率ウォッチドッグ

戦略は、FOK注文がある程度の割合で約定することを前提にしています(多くの場合60-80%)。その割合が大きく低下したら、何かが変わったということです。マーケットメイカーが引いた、戦略が識別された、API 障害が続いている、などです。理由が何であれ、戦略の PnL 計算を支えていた前提は壊れています。

ウォッチドッグのロジック:直近24時間の約定率を集計します。30%未満、または想定値の50%未満なら、アラートを出し、自動停止します。再開は手動レビュー後のみです。

ウォッチドッグは診断にも役立ちます。約定率の急落は通常、外部イベント(Polymarket のデプロイ、Polygon の混雑、自分の IP に対するレート制限など)と相関し、取引への影響に関係なく把握しておきたい事象です。

再起動時に台帳とオンチェーンを整合させる

bot は、自分が保有していると考えるポジションの台帳を保持します。チェーンは真実を保持します。両者は常に一致しているべきで、一致していない場合、bot は誤った認識のまま動作しており、誤った取引を行うことになります。

再整合ロジック:毎回の再起動時、および通常運用中は1時間に1回、bot が触れたすべての token についてオンチェーン残高を取得します。台帳と比較し、丸め誤差の許容範囲を超えていずれかの token の残高が異なる場合は、アラートを出して停止します。

乖離の最も一般的な原因は、bot の API 呼び出しが取り逃した成功注文です(タイムアウト、リトライが記録されなかった等)。チェーン上にはポジションがあるのに、bot はないと思っています。再整合がなければ、bot は利益確定の exit を出さず、ポジションはそのまま決済まで保有されます。

コード:本番対応の停止認識ループ

参考:すべてのリスク制御を組み込んだ本番トレーディングループです。

def production_loop():
    while True:
        # Halt checks
        if os.path.exists("/opt/pmt/HALT"):
            sleep_with_log(30); continue
        if daily_pnl_below_threshold():
            create_halt("daily PnL kill"); continue

        # Reconcile every hour
        if now() - last_reconcile > 3600:
            ok = reconcile_diary_vs_chain()
            last_reconcile = now()
            if not ok: create_halt("reconciliation failed"); continue

        # Fill-rate watchdog
        if recent_fill_rate() < 0.30:
            create_halt("fill rate collapse"); continue

        # Strategy
        try:
            run_strategy_once()
        except Exception as e:
            log_exception(e)
            if consecutive_exceptions >= 5:
                create_halt(f"exceptions: {e}"); continue
        time.sleep(5)

このパターンでは、すべての反復がゲートを通過します。構造上、戦略のバグが制御をすり抜けることはありません。

よくある質問

停止センチネルとは何ですか?
bot が各注文の前に確認するファイル(例:data/halt_autobuy)です。ファイルが存在する場合、戦略が注文しろと言っても bot は注文を出しません。touch コマンド1つで障害対応中に bot を止められます。2026年4月の約定詰まり事故の後、この正確なパターンを本番トレーダーに追加しました。
どのようなポジション上限を設定すべきですか?
市場別:資金の1-5%。戦略別:10-20%。総オープンエクスポージャー:資金の50-70%(現金バッファを残す)。単一注文は戦略に関係なく資金の1-2%に制限してください。1回の入力ミスで口座全体を危険にさらしてはいけません。
日次損失のキルスイッチはどう実装しますか?
UTC日ごとの実現損益 + 評価損益を追跡します。日次損益が資金の -3〜-5% を下回ったら、停止センチネルを設定して自分に通知します。bot は新規注文を停止し、既存ポジションは手動で管理します。毎日 00:00 UTC にリセットします。
クラッシュ後、再起動時に bot は何をすべきですか?
3つの手順です。(1) SDK を使ってローカル台帳と未決済注文を照合する。(2) オンチェーンの未決済ポジションをローカル状態と比較する。(3) 何かが乖離していたら bot を停止し、手動レビューを必須にする。不整合な状態で自動再開してはいけません。
単一のバグで口座が空になるのをどう防ぎますか?
多層的な制限を設けます:コードレベルのポジション上限、コードレベルの注文サイズ上限、ファイルレベルの停止センチネル、取引所レベル(Polymarket)の暗黙的な最小/最大制約、異常な注文頻度で通知する監視アラート。どれか1つだけでは不十分で、重ねる必要があります。
ログ記録に失敗した場合、bot は取引を続けるべきですか?
いいえ。bot が台帳へ書き込めないなら、再起動時に整合性を取れず、クラッシュ時に不整合状態になります。ログ記録に失敗したら、bot はハードフェイルさせてください。健全な本番 bot は、自身の可観測性に対して強い警戒心を持っています。