How to Build Risk Controls for a Polymarket Trading Bot
Description and use
The bot now rejects trades that violate any risk rule, preventing over‑exposure, liquidity problems, duplicate orders, and trading on stale data, while providing clear logs for debugging.
Problem
Automated trading bots can amplify mistakes by executing the same erroneous signal repeatedly, leading to excessive exposure, liquidity issues, and other risks.
Outcome
The bot now rejects trades that violate any risk rule, preventing over‑exposure, liquidity problems, duplicate orders, and trading on stale data, while providing clear logs for debugging.
Workflow
- Define constants for limits (MAX_TRADE_SIZE, MAX_EXPOSURE, MAX_MARKET_EXPOSURE, MAX_PRICE_DEVIATION, MIN_LIQUIDITY, MAX_OPEN_POSITIONS, COOLDOWN_SECONDS, MAX_DAILY_LOSS).
- Implement helper checks: check_trade_size, check_exposure, check_market_exposure, price_is_valid, check_liquidity, is_new_event, can_open_position, cooldown_finished, daily_limit_reached.
- Create a RiskEngine class with an approve method that runs all checks and returns approval status and reason.
- In the strategy loop, call risk_engine.approve(trade, portfolio, market). If approved, execute or log simulated trade; if rejected, log reason.
- Ensure dry‑run mode uses the same risk engine to avoid bypassing controls.
- Persist processed event IDs to avoid duplicate trades across restarts.
- Handle API failures by fail‑closed logic, rejecting trades when market state is unavailable or stale.
- Configure all limits via a central configuration to avoid scattered constants.
Evidence
A trading bot does not become safer because it is automated.
The risk engine exists between the strategy and execution.
MAX_TRADE_SIZE = 50
MAX_EXPOSURE = 500
MAX_MARKET_EXPOSURE = 100
MAX_PRICE_DEVIATION = 0.03
MIN_LIQUIDITY = 100
processed_events = set()
MAX_OPEN_POSITIONS = 10
COOLDOWN_SECONDS = 60
MAX_DAILY_LOSS = 50
class RiskEngine: ... def approve(self, trade, portfolio, market): ...
return {"approved": False, "reason": "Maximum market exposure reached" }
approved = risk_engine.approve(trade, portfolio, market)
market = get_market() if market is None: return reject("Market state unavailable")
- Record date: 2026-10-04T20:25:50.192Z