An automated prediction-market trading bot that uses a two-stage AI pipeline to find and bet on mispriced markets on Polymarket.
PolyMind watches 200+ live prediction markets in real time via WebSocket, runs each event through a two-stage AI pipeline, and places bets when the AI's probability estimate diverges significantly from the market price.
The core hypothesis: Polymarket frequently has illiquid markets where the implied probability is flat 50% by default, while news-aware AI can assign a much more accurate estimate — creating a tradable edge.
Default mode: DRY_RUN=true — no real orders are submitted until you explicitly flip the flag.
Polymarket WebSocket
│
▼
┌──────────────┐
│ Watcher │ Dedup · Price recording · Spread filter (≥90% dropped at source)
└──────┬───────┘
│ chan MarketEvent (500 buffer)
▼
┌──────────────────────────────────────┐
│ Analyzer (N parallel workers) │
│ │
│ 1. Cooldown check (Redis) │
│ 2. AI budget check ($2/day cap) │
│ 3. Spread pre-filter (>15% skip) │
│ 4. News fetch (NewsAPI + Serper) │
│ 5. Gemini 2.5 Flash ← Stage 1 │ cheap fast relevance check
│ └─ not relevant → drop + cooldown│
│ 6. Claude Sonnet ← Stage 2 │ calibrated probability estimate
│ └─ low confidence → drop │
└──────┬───────────────────────────────┘
│ chan Prediction (100 buffer)
▼
┌──────────────────────────────────────┐
│ Risk Engine (single goroutine) │
│ │
│ · Order book fetch (REST) │
│ · Spread check (≤3%) │
│ · Depth check (≥$50/side) │
│ · Drawdown guard │
│ · Kelly criterion sizing │
│ · Per-market & total exposure caps │
└──────┬───────────────────────────────┘
│ chan OrderRequest (10 buffer)
▼
┌──────────────────────────────────────┐
│ Executor │
│ │
│ · EIP-712 gasless order signing │
│ · Submit to CLOB REST API │
│ · Poll PENDING → MATCHED/CANCELLED │
│ · Consecutive-loss circuit breaker │
│ · Audit trail → SQLite │
└──────────────────────────────────────┘
Calling Claude Sonnet on every event would cost ~$0.003/call × hundreds of events/hour. Instead, Gemini 2.5 Flash acts as a cheap ($0.00003) relevance pre-filter — only events with actionable news context reach Claude.
Rather than fixed bet sizes, PolyMind uses the Kelly Criterion with a 0.25× safety multiplier (quarter-Kelly):
f* = (edge) / (odds)
bet_size = f* × 0.25 × available_bankroll × confidence_multiplier
Where edge = AI_probability - market_implied_probability.
Illiquid markets (bid=0.001, ask=0.999) were burning AI budget before any orders could be placed. Three layers of defense now stop them at different stages:
| Layer | Where | Threshold | Purpose |
|---|---|---|---|
| Source | WS parser | ≥ 90% | Drop placeholder order books |
| Pre-AI | Analyzer | > 15% | Save AI budget on wide markets |
| Final | Risk engine | > 3% | Hard trading gate |
Every external call (Gemini, Claude, Polymarket REST, news) is wrapped in a circuit breaker with configurable failure threshold and probe timeout — the bot stays alive even when individual services go down.
The codebase follows hexagonal architecture — each layer depends only on interfaces, not concrete implementations:
cmd/bot/ ← wiring only
internal/
entity/ ← domain types (no dependencies)
dto/ ← data transfer objects
repository/ ← external adapters (Polymarket, Claude, Gemini, Redis, SQLite)
usecase/ ← business logic (watcher, analyzer, riskengine, executor)
pkg/ ← utilities (kelly, circuitbreaker, ratelimiter, crypto, fsm)
bootstrap/ ← config loading, logger, health server
{"level":"INFO","msg":"analyzer: prediction ready",
"market":"Will Iran win the 2026 FIFA World Cup?",
"probability":0.01,"confidence":0.85,
"reasoning":"Multiple credible reports confirm Iran's sports minister stated Iran
won't participate in the 2026 World Cup in the US, making a win essentially impossible."}
{"level":"INFO","msg":"riskengine: order request ready",
"market":"Will Iran win the 2026 FIFA World Cup?",
"price":0.018,"size":0.42,"kelly_f*":0.031}The bot identified a market priced at 50% (coin flip) for a team that publicly announced they won't participate — a clear 49-point edge.
| Component | Technology |
|---|---|
| Language | Go 1.22+ |
| AI Stage 1 | Google Gemini 2.5 Flash |
| AI Stage 2 | Anthropic Claude Sonnet |
| News | NewsAPI + Serper (fallback) |
| Market data | Polymarket CLOB WebSocket + REST |
| State / cooldowns | Redis |
| Audit trail | SQLite |
| Order signing | EIP-712 (gasless) |
| Deployment | Railway (Docker) |
| Control | Value |
|---|---|
| Max drawdown | $5 hard stop |
| Max total exposure | 60% of bankroll |
| Max per-market bet | 20% of bankroll |
| Kelly fraction | 0.25× (quarter-Kelly) |
| Max bid-ask spread | ≤ 3% |
| Min order book depth | ≥ $50 each side |
| Consecutive loss pause | 3 losses → 1h trading pause |
| AI daily budget | $2.00/day |
- Go 1.22+
- Redis 7.x
- API keys: Gemini, Claude, NewsAPI, Serper
git clone https://github.com/<you>/PolyMind
cd PolyMind
go mod downloadcp .env.example .envFill in .env:
GEMINI_API_KEY=AIza...
CLAUDE_API_KEY=sk-ant-...
NEWS_API_KEY=...
SERPER_API_KEY=...
REDIS_URL=redis://localhost:6379
BANKROLL_USD=15.0Polymarket uses EIP-712 signed authentication. Run this once with your wallet private key to exchange it for API credentials:
PRIVATE_KEY=<hex_key_no_0x_prefix> \
go run ./cmd/authsetupCopy the printed values into your .env:
POLY_API_KEY=...
POLY_SECRET=...
POLY_PASSPHRASE=...
POLY_ADDRESS=0x...You only need to do this once per wallet. The credentials don't expire.
redis-server &
go run ./cmd/botgo test ./...
# No external services required — Redis uses miniredis, AI uses MockClientsAfter verifying predictions look correct in dry-run for 48h:
DRY_RUN=false go run ./cmd/botMake sure
PRIVATE_KEY,POLY_API_KEY,POLY_SECRET,POLY_PASSPHRASE, andPOLY_ADDRESSare all set in your.envbefore flippingDRY_RUN=false.
All values can be set via config/config.yaml or environment variables:
| Variable | Default | Description |
|---|---|---|
DRY_RUN |
true |
No real orders when true |
BANKROLL_USD |
15.0 |
Starting capital in USD |
MAX_DRAWDOWN_USD |
5.0 |
Hard stop loss |
KELLY_FRACTION |
0.25 |
Safety multiplier |
MAX_EXPOSURE_PCT |
0.60 |
Max % bankroll in open positions |
MAX_SINGLE_MARKET_PCT |
0.20 |
Max % per market |
AI_DAILY_BUDGET_USD |
2.00 |
Max AI spend per day |
ANALYZER_WORKER_COUNT |
10 |
Parallel analysis goroutines |
ANALYZER_MAX_SPREAD_PCT |
0.15 |
Skip market before AI if spread > this |
COOLDOWN_DEFAULT_MINUTES |
5 |
Re-analysis cooldown per market |
LOG_LEVEL |
info |
debug / info / warn / error |
- Push to GitHub
- New Railway project → connect repo
- Set all secret env vars in Railway Variables panel
- Railway auto-detects
Dockerfile - Monitor health at
https://<service>.railway.app/health