Polymarket Bot Tutorial · Chapter 30 of 32

Production-grade risk management code for Polymarket bots: position caps, daily loss limits, halt sentinels, fill-rate watchdogs, reconcile-on-restart, idempotent retries. Code patterns from real production trader.

Що охоплює цей розділ

Risk code - це більша частина production trading bot. Логіка стратегії - найлегша частина; навколо неї саме caps, halts, watchdogs і reconcilers визначають, чи переживе бот свій перший невдалий тиждень. Цей розділ - production-grade risk pattern.

  • Чому risk code - це більшість справжнього trading bot
  • Position caps (per-market, per-strategy, total)
  • Daily loss kill switch
  • Halt sentinels (file-based emergency stop)
  • Fill-rate watchdog
  • Reconcile diary vs on-chain on restart
  • Code: production-grade halt-aware loop

Чому risk code - це більшість справжнього trading bot

Один із показників, який ми виміряли у власній codebase бота: 60% LOC - це risk code (caps, halts, watchdogs, reconciliation). 30% - strategy. 10% - glue.

Це правильне співвідношення. Strategy - найлегша частина: опис того, коли входити і коли виходити, уміщується в кілька десятків рядків. Risk code - це все інше: що робити, коли ціна рухається проти вас швидше, ніж очікувалося, що робити, коли fills перестають проходити, що робити, коли WebSocket обривається, що робити, коли strategy виявляється збитковою.

У більшості історій провалу builder'ів спільний сюжет один: strategy працювала, але бот продовжував торгувати після regime change, тому що жоден halt не спрацював. Пишіть halts раніше, ніж strategy.

Position caps (per-market, per-strategy, total)

Три caps, які мають бути enforced у code.

  • Per-market cap: максимум $X на один market незалежно від confidence у edge. Типово: $25-100 для small bots, $200-500 для production. Обмежує blast radius однієї неправильної ставки на окремий market.
  • Per-strategy cap: якщо ви запускаєте кілька strategies, кожна отримує свою частку total capital. Типово: 30-50% на одну strategy. Захищає capital інших strategies від поганого дня однієї з них.
  • Total cap: максимальний % від wallet balance, який може бути deployed одночасно. Типово: 50-70%. Залишає capital для неочікуваних opportunity або щоб зловити власні bookkeeping bugs бота.

Усі три caps слід enforce всередині order-placement function, а не лише в strategy logic. У strategy може бути bug; order-placement gate - це остання лінія захисту.

Daily loss kill switch

Найважливіший окремий risk control: daily-loss kill switch.

Правило: якщо realized + unrealized PnL з опівночі UTC падає нижче -X% від starting daily balance, бот припиняє відкривати нові positions і (за бажанням) закриває existing ones. Типове X: 5-10%.

Математика: bot із 60% expected win rate має, можливо, 5% шанс на losing streak із 10 trades. Без kill switch ця серія наростає: $200 loss → бот продовжує торгувати → ще $200 loss → wallet просідає на 40%. Якщо switch спрацьовує на -10%, поганий день обмежується $200, а завтра бот починає з нуля.

Switch enforce server-side: створіть halt file або встановіть database flag, який trading loop перевіряє на кожній ітерації. Перезапускати лише після manual review.

Halt sentinels (file-based emergency stop)

Найпростіший можливий halt mechanism: бот перевіряє наявність file (наприклад, /opt/pmt/HALT) на кожній ітерації loop і зупиняє trading, якщо file існує.

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, monitoring system): touch /opt/pmt/HALT. Щоб відновити роботу: rm /opt/pmt/HALT.

File-based підхід навмисно low-tech, бо він працює в умовах, коли більш складні halt mechanisms fail: коли бот частково crashed, коли database недоступна, коли API key має rate limit. File system доступ завжди є.

Fill-rate watchdog

Strategy припускає, що FOK orders заповнюються з певною rate (часто 60-80%). Коли rate помітно падає, щось змінилося: market makers пішли, вашу strategy ідентифікували, триває API outage. Якою б не була причина, припущення, на якому будувалася PnL math strategy, зламане.

Watchdog logic: rolling 24-hour fill-rate count. Якщо < 30% (або 50% від expected), alert + auto-halt. Відновлення лише після manual review.

Watchdog також корисний як diagnostic. Раптове падіння fill-rate зазвичай корелює із зовнішньою подією (Polymarket deploy, Polygon congestion, ваш IP отримав rate limit), про яку вам варто знати незалежно від trading impact.

Reconcile diary vs on-chain on restart

Bot веде diary positions, які, на його думку, він тримає. Chain зберігає truth. Вони мають завжди збігатися; коли це не так, бот працює на хибному припущенні й торгує неправильно.

Reconciliation logic: на кожному restart і раз на годину під час normal operation отримуйте on-chain balances для кожного token, якого бот торкався. Порівнюйте з diary; alert + halt, якщо баланс будь-якого token відрізняється від diary більше, ніж допускає rounding tolerance.

Найпоширеніша причина розбіжності - successful order, який bot's API call пропустив (timeout, retry never recorded). Chain має position; бот думає, що не має. Без reconciliation бот не виставить take-profit exit, і position дійде до resolution.

Code: production-grade halt-aware loop

Reference: production trading loop з усіма risk controls, підключеними належним чином.

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)

Pattern такий: кожна ітерація проходить через gate. Strategy bugs не можуть обійти controls за самою конструкцією.

Поширені запитання

What is a halt sentinel?
A file (e.g., data/halt_autobuy) that the bot checks before every order. If the file exists, the bot refuses to place orders even if the strategy says to. Lets you stop the bot mid-incident with a single touch command. We added this exact pattern to our production trader after a wedged-fill incident in April 2026.
What position caps should I set?
Per-market: 1-5% of bankroll. Per-strategy: 10-20%. Total open exposure: 50-70% of bankroll (keep cash buffer). Cap a SINGLE order at 1-2% of bankroll regardless of strategy - one fat-fingered order should never be account-sized.
How do I implement a daily loss kill switch?
Track realized + unrealized PnL per UTC day. If daily PnL drops below -3 to -5% of bankroll, set the halt sentinel and notify yourself. The bot stops new orders; existing positions are managed manually. Reset at 00:00 UTC daily.
What should the bot do on restart after a crash?
Three steps: (1) Reconcile open orders via SDK against your local diary. (2) Check open positions on-chain against your local state. (3) If anything diverges, halt the bot and require manual review. Never auto-resume into an inconsistent state.
How do I prevent a single bug from emptying my account?
Layered limits: code-level position cap, code-level order-size cap, file-level halt sentinel, exchange-level (Polymarket) implicit minimum/maximum, monitoring alerts that page you on unusual order rate. No single layer is sufficient - they multiply.
Should the bot trade if my logging fails?
No. If the bot cannot write to its diary, it cannot reconcile on restart, which means a crash leads to inconsistent state. Hard-fail the bot if logging fails. Healthy production bots are paranoid about their own observability.