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.

What this chapter covers

Risk code is most of a production trading bot. Strategy logic is the easy part; the surrounding caps, halts, watchdogs, and reconcilers are what determines whether the bot survives its first bad week. This chapter is the production-grade risk pattern.

  • Why risk code is most of a real 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

Why risk code is most of a real trading bot

Here is a measurement from our own bot codebase: about 60% of the lines of code are risk code (caps, halts, watchdogs, reconciliation), roughly 30% is the actual strategy, and the last 10% is glue that holds it together.

That ratio is correct. Strategy is the easy part - describing when to enter and when to exit fits in a few dozen lines. Risk code is everything else: what to do when the price moves against you faster than expected, what to do when fills stop landing, what to do when the WebSocket drops, what to do when the strategy turns out to be unprofitable.

Most builder failure stories share the same shape: the strategy worked, but the bot kept trading through a regime change because no halt fired. Write the halts before you write the strategy.

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

There are three caps you want, and all of them belong directly in your order-placing code, not just in the strategy.

  • Per-market cap: max $X per market regardless of edge confidence. Typical: $25-100 for small bots, $200-500 for production. Bounds the blast radius of a single-market wrong call.
  • Per-strategy cap: if you run multiple strategies, each gets a slice of total capital. Typical: 30-50% per strategy. Prevents one strategy's bad day from consuming the others' capital.
  • Total cap: max % of wallet balance deployed simultaneously. Typical: 50-70%. Leaves capital for unexpected opportunities or for catching the bot's own bookkeeping bugs.

All three caps should be enforced inside the order-placement function, not just in the strategy logic. The strategy can have a bug; the order-placement gate is the last line of defense.

Daily loss kill switch

The most important single risk control: a daily-loss kill switch.

Rule: if realized + unrealized PnL since midnight UTC drops below -X% of starting daily balance, the bot stops opening new positions and (optionally) flattens existing ones. Typical X: 5-10%.

The math: a bot with 60% expected win rate has perhaps a 5% chance of a 10-trade losing streak. Without the kill switch, that streak compounds: $200 loss → bot keeps trading → another $200 loss → wallet down 40%. With the switch firing at -10%, the bad day caps at $200, and tomorrow the bot starts fresh.

The switch is enforced server-side: write a halt file or set a database flag that the trading loop checks every iteration. Restart only after manual review.

Halt sentinels (file-based emergency stop)

The simplest possible halt mechanism: the bot checks for a file (e.g. /opt/pmt/HALT) every loop iteration and stops trading if the file exists.

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)

To halt immediately from anywhere (SSH, Telegram bot, a monitoring system): touch /opt/pmt/HALT. To resume: rm /opt/pmt/HALT.

The file-based approach is intentionally low-tech because it works under conditions where more sophisticated halt mechanisms fail: when the bot is partially crashed, when the database is unreachable, when the API key is rate-limited. File-system access is always available.

Fill-rate watchdog

The strategy assumes FOK orders fill at some rate (often 60-80%). When the rate drops significantly, something has changed: market makers pulled, your strategy got identified, an API outage is ongoing. Whatever the reason, the assumption that drove the strategy's PnL math is broken.

Watchdog logic: rolling 24-hour fill-rate count. If < 30% (or 50% of expected), alert + auto-halt. Resume only after manual review.

The watchdog is also useful as a diagnostic. A sudden fill-rate drop usually correlates with an external event (Polymarket deploy, Polygon congestion, your IP getting rate-limited) that you'd want to know about regardless of the trading impact.

Reconcile diary vs on-chain on restart

The bot maintains a diary of positions it thinks it holds. The chain maintains the truth. They should always agree; when they don't, the bot is operating on a wrong belief and will trade incorrectly.

Reconciliation logic: on every restart and once an hour during normal operation, fetch on-chain balances for every token the bot has touched. Compare against the diary; alert + halt if any token's balance differs from diary by more than rounding tolerance.

The most common cause of divergence is a successful order that the bot's API call missed (timeout, retry never recorded). The chain has the position; the bot thinks it doesn't. Without reconciliation, the bot won't post the take-profit exit and the position rides to resolution.

Code: production-grade halt-aware loop

Reference: the production trading loop with all the risk controls wired in.

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)

The pattern: every iteration goes through the gate. Strategy bugs cannot bypass the controls, by construction.

Frequently asked questions

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.