A full-stack real-time dashboard for Polymarket prediction markets, featuring live order books, probability charts, and WebSocket-driven updates.
Polymarket Gamma API → PolymarketProvider → MarketService → PostgreSQL/TimescaleDB
Polymarket CLOB API → PolymarketProvider → OrderBookService → In-memory cache
Polymarket CLOB WS → IngestionWorker → ConnectionManager → Frontend WebSocket
- Backend: Python 3.12, FastAPI, async SQLAlchemy 2, asyncpg, Alembic, httpx
- Database: PostgreSQL 16 + TimescaleDB (hypertable for time-series probability data)
- Frontend: React 18, TypeScript, Vite, Tailwind CSS, React Query
- Infrastructure: Docker Compose (all services containerized)
Prerequisites: Docker Desktop only. No Python, Node, or database installation needed on the host.
cp .env.example .env
# Edit .env if needed (defaults work out of the box)
make upServices:
- Backend API: http://localhost:8000
- Frontend: http://localhost:5173
- API docs: http://localhost:8000/docs
make up # Start all services (build if needed)
make down # Stop all services
make migrate # Run Alembic migrations inside container
make test # Run pytest inside container
make logs # Tail all service logs
make backend-shell # Shell into backend container
make db-shell # psql into database container
make lint # Run ruff linter
make format # Run ruff formatter# Health check
curl http://localhost:8000/api/v1/health
# List active markets
curl http://localhost:8000/api/v1/markets
# Get market detail
curl http://localhost:8000/api/v1/markets/{market_id}
# Price history (last 24h)
curl "http://localhost:8000/api/v1/markets/{market_id}/history?hours=24"
# Order book for a specific outcome
curl "http://localhost:8000/api/v1/markets/{market_id}/order-book?outcome_id={outcome_id}"
# WebSocket (use wscat or similar)
wscat -c ws://localhost:8000/api/v1/ws/markets/{market_id}- markets: Market metadata, synced from Gamma API on startup
- outcomes: YES/NO outcomes per market with current probability
- probability_ticks: TimescaleDB hypertable — every probability change event, partitioned by time
- order_book_snapshots: Periodic JSONB snapshots of bids/asks (live data kept in memory)
probability_ticksuses TimescaleDB automatic time-partitioned chunks for efficient range queries- Composite index on
(outcome_id, observed_at DESC)for fast per-outcome history lookups external_token_idunique index onoutcomesfor O(1) lookup during WebSocket event routing
Adding a KalshiProvider would require:
- Implement
MarketDataProviderABC inapp/providers/kalshi.py - Register it in the DI container in
app/main.py - No changes needed to services, repositories, routers, or DB models
The provider column on the markets table already accommodates multi-provider data.
- Frontend connects to
ws://localhost:8000/api/v1/ws/markets/{market_id} - ConnectionManager registers the client for that market
- IngestionWorker receives events from Polymarket CLOB WebSocket
- Events are normalized into typed envelopes and broadcast to registered clients
- Frontend components update via the
useMarketSockethook
- Single-process ingestion: The upstream Polymarket WebSocket runs in-process. In production, move it to a separate worker to avoid duplicate subscriptions when horizontally scaling the API.
- In-memory order books: Live order books are kept in a dict for sub-millisecond reads; snapshots are persisted to DB periodically (not on every update) to reduce write amplification.
- Probability deduplication: Ticks are only written when the value actually changes, preventing time-series bloat from high-frequency no-op events.
- No auth: This is a read-only public dashboard. Production would add auth for any write operations.
- Separate ingestion worker process with message queue (Redis Streams or Kafka)
- Multi-provider support with provider-specific adapters (Kalshi, Manifold)
- Caching layer (Redis) for frequently-requested market data
- Rate limiting and circuit breaker on upstream API calls
- Horizontal scaling with sticky WebSocket sessions or pub/sub fanout
- E2E tests with Playwright
- OpenTelemetry tracing