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.





