2026-07-05 18:07:28 -04:00
|
|
|
|
# poly-maker
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
A maker-only market-making bot for **Polymarket CLOB V2**, focused on political
|
2026-07-05 18:47:11 -04:00
|
|
|
|
markets. Single async process, local-file config, typed and
|
2026-07-05 18:07:28 -04:00
|
|
|
|
tested.
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
> [!WARNING]
|
|
|
|
|
|
> Market making on Polymarket is competitive and can lose money. This is a
|
|
|
|
|
|
> reference implementation and a research harness, not a guaranteed-profitable
|
|
|
|
|
|
> product. Test in `--paper` mode first; go live with small size.
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
## What it does
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 18:07:28 -04:00
|
|
|
|
- Discovers political markets via the **Gamma API** (seconds) and ranks them by
|
|
|
|
|
|
reward + rebate income vs. volatility/spread risk.
|
2026-07-05 17:50:04 -04:00
|
|
|
|
- Maintains a live order book per token from the **market WebSocket**.
|
2026-07-05 18:07:28 -04:00
|
|
|
|
- Quotes **maker-only** — every order is post-only. Fair-value + inventory-skew
|
|
|
|
|
|
strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live
|
|
|
|
|
|
volatility/toxicity estimation and a regime machine that pulls quotes during
|
|
|
|
|
|
news events (see [Strategy](#strategy)).
|
2026-07-05 17:50:04 -04:00
|
|
|
|
- Reconciles a target quote set against live orders with churn tolerances; runs
|
|
|
|
|
|
the exchange **heartbeat** dead-man switch; enforces risk caps and a daily-loss
|
|
|
|
|
|
kill switch.
|
|
|
|
|
|
- Config, market selection, and state are **local files + SQLite**. An operator
|
|
|
|
|
|
with the repo, a `.env`, and a funded wallet is a complete deployment.
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
## Install
|
2025-11-03 09:26:42 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
Uses [uv](https://docs.astral.sh/uv/) and Python 3.12+.
|
2025-11-03 09:26:42 -08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-05 17:50:04 -04:00
|
|
|
|
uv sync --extra dev # install deps + dev tools
|
|
|
|
|
|
uv run polymaker --help
|
2025-03-31 12:16:51 -04:00
|
|
|
|
```
|
2025-11-03 09:26:42 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
## Configure
|
2025-11-03 09:26:42 -08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-06 15:28:07 -04:00
|
|
|
|
cp .env.example .env # then edit two values:
|
2025-11-03 09:26:42 -08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-06 15:28:07 -04:00
|
|
|
|
- `PK` — the private key of your signer wallet
|
|
|
|
|
|
- `BROWSER_ADDRESS` — your Polymarket address (shown on the profile / developer page)
|
2025-11-03 09:26:42 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
Everything else is TOML under [`config/`](config/):
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
- `config.toml` — wallet/engine/risk/execution settings
|
|
|
|
|
|
- `strategy.toml` — named parameter profiles (`political-longdated`, `political-hot`)
|
|
|
|
|
|
- `markets.toml` — the trade list (populated via the CLI below)
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
## Use
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2025-11-03 09:26:42 -08:00
|
|
|
|
```bash
|
2026-07-05 17:50:04 -04:00
|
|
|
|
# 1. discover + rank political markets (writes to state.db)
|
|
|
|
|
|
uv run polymaker scan
|
|
|
|
|
|
uv run polymaker markets
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
# 2. add markets to the trade list
|
|
|
|
|
|
uv run polymaker markets-add <slug> --profile political-longdated
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
# 3. dry run: full pipeline against the live feed, no orders posted
|
|
|
|
|
|
uv run polymaker run --paper
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
# 4. preflight the wallet before going live
|
|
|
|
|
|
uv run polymaker doctor
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 18:47:11 -04:00
|
|
|
|
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
|
|
|
|
|
|
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
|
|
|
|
|
|
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
# 6. go live
|
|
|
|
|
|
uv run polymaker run
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
# ops
|
|
|
|
|
|
uv run polymaker status # positions / open orders
|
|
|
|
|
|
uv run polymaker cancel-all # panic button
|
|
|
|
|
|
```
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
## Architecture
|
2025-11-03 09:28:34 -08:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
```
|
|
|
|
|
|
market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
|
|
|
|
|
|
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
|
|
|
|
|
|
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘
|
2025-11-03 09:28:34 -08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-05 18:07:28 -04:00
|
|
|
|
One async event loop. The strategy layer is a pure function `(book, inventory,
|
|
|
|
|
|
params, clock) → TargetQuotes` — deterministic and unit-tested. The engine owns
|
|
|
|
|
|
all I/O and state around it; the `ExecutionGateway` wraps `py-clob-client-v2`
|
|
|
|
|
|
(which handles the V2 EIP-712 signing) and offloads its blocking calls to a
|
|
|
|
|
|
thread pool so the hot path never stalls. State (positions, orders, PnL, catalog)
|
|
|
|
|
|
lives in one SQLite file; raw WS/order events are journaled to `journal/` for
|
|
|
|
|
|
replay.
|
|
|
|
|
|
|
|
|
|
|
|
## Strategy
|
|
|
|
|
|
|
|
|
|
|
|
Maker-only, quoting both sides of each market as USDC-collateralized bids:
|
|
|
|
|
|
|
|
|
|
|
|
- **Fair value** — depth-weighted microprice off the live book, nudged by an
|
|
|
|
|
|
EWMA of signed trade flow.
|
|
|
|
|
|
- **Quote construction** — reservation price `r = FV − skew(inventory)`;
|
|
|
|
|
|
half-spread `δ = base + c_vol·σ + c_tox·toxicity`. Post **BUY-YES at `r − δ`**
|
|
|
|
|
|
and **BUY-NO at `(1 − r) − δ`**. Because both legs are bids that sum below 1,
|
|
|
|
|
|
a filled pair merges back to USDC at locked edge `1 − p − q` — a maker-only
|
|
|
|
|
|
exit that never crosses the spread.
|
|
|
|
|
|
- **Inventory skew** — net position leans both quotes: long YES → bid YES lower,
|
|
|
|
|
|
bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches
|
|
|
|
|
|
a soft cap, then the adding side is pulled entirely.
|
|
|
|
|
|
- **Volatility / toxicity** — realized-vol and per-fill markout (adverse
|
|
|
|
|
|
selection) EWMAs widen the spread and shrink size in markets that pick us off.
|
|
|
|
|
|
- **Regime machine** — per market: `QUIET` (farm rewards in-band), `TRENDING`
|
|
|
|
|
|
(lean + widen + half size), `EVENT` (sweep/jump detected → pull quotes, cool
|
|
|
|
|
|
off), `REDUCE_ONLY` (inventory cap / near end date → exits only), `HALTED`
|
|
|
|
|
|
(stale data / resolved / kill switch → cancel all).
|
|
|
|
|
|
- **Rewards + rebates** — quotes stay inside the liquidity-rewards band in QUIET;
|
|
|
|
|
|
the market selector also scores the new maker-rebate program (a share of taker
|
|
|
|
|
|
fees rebated to makers).
|
|
|
|
|
|
- **Risk** — per-market notional cap, neg-risk event-group worst-case cap, total
|
|
|
|
|
|
exposure cap, daily-loss kill switch, WS-staleness halt.
|
|
|
|
|
|
|
|
|
|
|
|
Tune it all via profiles in `config/strategy.toml`.
|
2025-04-02 09:20:16 -07:00
|
|
|
|
|
2026-07-05 17:50:04 -04:00
|
|
|
|
## Develop
|
2025-04-02 09:20:16 -07:00
|
|
|
|
|
2025-11-03 09:26:42 -08:00
|
|
|
|
```bash
|
2026-07-05 17:50:04 -04:00
|
|
|
|
uv run pytest # unit suite (offline)
|
|
|
|
|
|
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
|
|
|
|
|
|
uv run ruff check src tests # lint
|
|
|
|
|
|
uv run mypy src # types (strict)
|
2025-03-31 12:16:51 -04:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-05 18:07:28 -04:00
|
|
|
|
## Status
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 18:07:28 -04:00
|
|
|
|
Implemented and live-verified end to end (auth → book → strategy → sign → post →
|
|
|
|
|
|
cancel): config, catalog/scanner, order book + analytics, strategy (FV,
|
2026-07-05 17:50:04 -04:00
|
|
|
|
vol/toxicity, regime, quoting), state store + lifecycle, execution gateway +
|
|
|
|
|
|
reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path),
|
2026-07-05 18:07:28 -04:00
|
|
|
|
engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
2026-07-05 18:07:28 -04:00
|
|
|
|
Not yet built: a replay backtester over the captured journals, and external data
|
|
|
|
|
|
feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a
|
|
|
|
|
|
tx via the relayer and isn't wired yet — until then inventory exits via limit
|
|
|
|
|
|
sells rather than merging.
|
2025-03-31 12:16:51 -04:00
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
2025-06-06 17:20:59 -07:00
|
|
|
|
MIT
|