Let LLMs play on prediction markets.
Python 3.14, uv, SQLAlchemy 2 ORM, Cloud SQL Postgres (Alembic-driven migrations), Qdrant Cloud for embeddings, GCP Cloud Run + Cloud Tasks + Cloud Scheduler, Pulumi for infra.
uv sync # repo-level deps + prek
uv run prek install
docker compose up postgres -d # local Postgres (mirrors Cloud SQL)
cd backend/apis/llm # any service's venv has alembic
uv sync
uv run alembic -c ../../db/alembic.ini upgrade head # create the schema locallyDrop a .env at the repo root with the DB / OpenAI / Qdrant creds (see CLAUDE.md for the full list).
Event-driven pipeline running on GCP. A daily Cloud Scheduler cron kicks off the orchestrator; everything after that is Cloud Tasks fan-out between three Cloud Run services.
daily cron ──▶ orchestrator /prepare-scraping
└─ enqueue 1 bootstrap task ──▶ scrape-markets-polymarket queue
│
▼
polymarket /scrape (one page)
├─ upsert markets to Postgres
├─ enqueue next page ──▶ scrape-markets-polymarket
├─ per new market ──▶ save-embeddings-markets queue ──▶ llm /embed-market
└─ per (market × cfg) ──▶ solve-market-llm queue ──▶ llm /predict
Services (backend/apis/)
| Service | Purpose |
|---|---|
orchestrator |
POST /prepare-scraping — cron entry point. Enqueues the bootstrap scrape task. |
polymarket |
POST /scrape — handles one Polymarket CLOB page (one cursor), upserts all markets to Postgres (closed ones populate outcome.market_winner), enriches the tradeable subset with gamma-api volume/liquidity, then fans out embed + predict tasks for tradeable markets. |
llm |
POST /predict (one PredictorLLM cycle) + POST /embed-market (one market embedding into Qdrant). |
Infrastructure (backend/infra/)
Five Pulumi stacks, deployed by .github/workflows/deploy.yml. cloud_sql_deployer runs first; cloud_run_deployer depends on it (mounts the Cloud SQL socket); queue_deployer + cron_deployer depend on cloud_run_deployer; qdrant_deployer is independent (Qdrant Cloud, not GCP) and runs in parallel via its own workflow.
| Stack | Manages |
|---|---|
cloud_sql_deployer |
The Cloud SQL Postgres instance. (App data lives in the default postgres DB; the admin password is set in the GCP UI and stored in GSM.) |
cloud_run_deployer |
Artifact Registry repo + the three Cloud Run services. Reads instance_connection_name from cloud_sql_deployer via StackReference and mounts the Cloud SQL Unix socket on services flagged needs_cloudsql: true. |
queue_deployer |
The shared task-runner SA + Cloud Tasks queues declared in backend/queues/ + IAM bindings. |
cron_deployer |
Cloud Scheduler jobs declared in backend/crons/. |
qdrant_deployer |
The Qdrant Cloud cluster (control plane). Collections are not managed here — they live in backend/qdrant/schema.py and are reconciled by the qdrant_sync deploy job (python -m qdrant.sync). |
GCP resources live in europe-west3 (Frankfurt); the Qdrant Cloud cluster is in the matching region.
| What | Where | Owned by |
|---|---|---|
| Relational data (markets, outcomes, snapshots, configs, predictions) | Cloud SQL Postgres (prediction-market instance, default postgres DB) |
Schema in backend/db/schema.py; migrations in backend/db/alembic/ applied automatically by the alembic_migrate deploy job |
| Embeddings | Qdrant Cloud cluster | Schemas in backend/qdrant/schema.py; applied by python -m qdrant.sync in the qdrant_sync deploy job |
| Raw scraped payloads + raw LLM responses | GCS bucket (prediction-market-llm-raw) |
Written by the polymarket + llm services via RawStore; pointers stored in market.raw_path / llm_prediction.raw_response_path |
The DB migration loop: edit schema.py → cd backend/apis/llm && uv run alembic -c ../../db/alembic.ini revision --autogenerate -m "..." → review the generated revision → commit. The next deploy applies it through the Cloud SQL Auth Proxy.
Each service's Dockerfile COPYs the libs it needs:
| Lib | Used by |
|---|---|
backend/db/ |
polymarket, llm — SQLAlchemy schema + queries + Alembic migrations |
backend/qdrant/ |
llm — Qdrant client, collection schema + sync_collections |
backend/embedder/ |
llm — OpenAI embeddings wrapper |
backend/tasks/ |
orchestrator, polymarket — Cloud Tasks enqueue() + QUEUE_DISPATCH_DEADLINES |
backend/observability/ |
all three — tracing/log correlation + trace-header propagation |
backend/shared_models/ |
all three — request models for inter-service tasks |
settings/ |
all three — Settings (pulls secrets from GSM when running on Cloud Run) |
make check # ruff lint+format + ty typecheck (also runs in CI)
make lint
make format
make typecheckSee CLAUDE.md for the agent-facing project guide with conventions, gotchas, and local dev recipes.