Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

quantaugur

A self-hosted quant trading platform — market-data ingestion, backtesting, and real-time signals.

Status: Phase 1 (Data Pipeline) complete (PR #1); Phase 2 (Backtesting) complete — strategy plugin library, event-driven backtest engine, FastAPI service with queue-isolated worker, multi-timeframe aggregation via TimescaleDB continuous aggregates. Phase 3 (Live Signal Engine) is complete — qa_strategies.on_bar runs against live bars:new, subscriptions API + WebSocket bridge, email notifications, subscription-driven universe (poller widens on new subscriptions without redeploy), and durable catch-up replay from ohlcv on restart. Phase 4 (Web UI) is complete — React+Vite+TS SPA served by nginx, five routes (Overview/Charts/ Backtest/Portfolio/Signals, /overview is the landing page), live WebSocket bridges for signals and candles, new API endpoints (candles/quotes/positions/heatmap), and a positions-aware subscription universe. Phase 5 (Multi-user) is complete — Google OAuth login, per-user data scoping (positions, subscriptions, backtests scoped to current_user.id), multi-user signal fan-out delivering to each subscriber's own email, and security middleware (CORS allowlist, /auth/* rate limiting).

Phase 1 builds a reliable, idempotent US-equity market-data store in TimescaleDB, fed by a single calendar-aware Poller that ingests intraday 1m bars, reconciles each session at the close with authoritative 1d and full-day 1m bars, backfills new symbols to per-interval depths, and announces new bars on a Redis event bus.

Phase 2 adds a strategy plugin library (qa_strategies: six classic strategies sharing a single on_bar(window) → Signal | None interface), an event-driven backtest engine + paper broker + performance metrics (qa_backtest), a FastAPI service (/api/v1: strategy discovery, timeframe listing, backtest submit/status) with a Redis-queue-isolated backtest worker (so a heavy backtest never starves the live engine), and multi-timeframe aggregation: 5m/15m/30m/1h/4h/1w/1mo derived from the 1m/1d base (15m/1h/1w/1mo as TimescaleDB continuous aggregates, the rest on-the-fly time_bucket) — always consistent with P1's authoritative data, no re-fetching.

Phase 3 adds the live signal engine (services/live): subscribes to bars:new, evaluates every active user strategy subscription once per bar (dedup via signals unique constraint), and fans out to the configured notifier. New in P3: a subscriptions API + WebSocket bridge (/api/v1/subscriptions, /api/v1/ws/signals) for real-time signal delivery; an email notifier (SMTP / Gmail App Password); subscription-driven universe (the poller optionally widens its fetch universe to include all subscribed tickers without redeploy); and durable catch-up — on restart the engine replays ohlcv rows missed during downtime via per-(symbol, interval) cursors before going live, making the Redis Pub/Sub gap transparent to downstream subscribers.

Documentation

Doc What it covers
docs/poller-architecture.md The Poller service — state machine, wiring, event flow, the data-correctness contract, configuration, and the swappable seams.
docs/database-schema.md The TimescaleDB ohlcv hypertable + ingest_state table — columns, hypertable/compression config, the is_final upsert merge semantics, and migration mechanics.
docs/manual-testing.md Hands-on tutorial: run the automated suite, stand up the stack with Docker Compose, ingest real bars end-to-end, watch bars:new events, run a backtest through the API + worker, exercise the live signal engine (subscribe → replay → WebSocket push), drive the P4 web UI + market/portfolio endpoints (candles/quotes/positions/heatmap, /ws/candles), and verify the data.
docs/backtest-sweep-findings.md Empirical P2 sweep (229 param sets × 3 symbols): in-sample wins vs Buy & Hold evaporate out-of-sample — the findings that shape the P4 parameter UI.
deploy/helm/README.md Helm chart reference — install, upgrade, platform-specific values, external DB/secrets, backtest smoke-test, P3 live signal smoke-test, and subscription-driven universe.

The pipeline at a glance

flowchart LR
    YF[yfinance<br/>MarketDataSource] -->|fetch / fetch_latest| P[Poller daemon]
    CLK[MarketClock<br/>XNYS calendar] -->|phase / session bounds| P
    P -->|upsert_bars ON CONFLICT| TS[(TimescaleDB<br/>ohlcv hypertable)]
    P -->|"eod_done:{symbol}:{date} gate<br/>+ quote:{symbol} cache"| R[(Redis)]
    P -->|publish bars:new| BUS[Redis Pub/Sub<br/>EventBus]
    BUS -->|bars:new| LIVE((live engine))
    LIVE -->|signals + live_cursor| TS
    LIVE -->|publish signals:new| BUS
    BUS -->|"signals:new → WS /api/v1/ws/signals"| API
    BUS -->|"bars:new → WS /api/v1/ws/candles"| API
    R -->|"quote:{symbol} → /quotes, heatmap"| API
    LIVE -->|EmailNotifier SMTP| MAIL[email inbox]
    FE[React SPA<br/>nginx] -->|"/api proxy (REST + WS)"| API
    P --- HZ[/healthz :8080/]
    TS -->|"CAGGs (15m/1h/1w/1mo)<br/>+ on-the-fly time_bucket"| CAGG[(derived frames<br/>5m…1mo)]
    CAGG -->|load_bars| WRK[backtest worker]
    API[FastAPI /api/v1] -->|RPUSH backtest:jobs| RQ[(Redis queue)]
    RQ -->|BLPOP| WRK
    WRK -->|save_result| TS
Loading
  • Source (qa_datasource) — a swappable MarketDataSource registry; yfinance today, Polygon/Alpaca later by adding one module.
  • Clock (qa_core.market_calendar) — wraps exchange_calendars (XNYS); decides whether to poll intraday, run EOD, or sleep.
  • Store (qa_core.db + db/migrations) — the ohlcv hypertable; idempotent upsert_bars with the is_final authoritative-value latch; continuous aggregates for derived timeframes.
  • Bus (qa_core.bus) — EventBus abstraction over Redis Pub/Sub; the Poller publishes bars:new.
  • Poller (services/poller) — the daemon that ties it together; /healthz for k8s liveness.
  • Strategies (qa_strategies) — plugin library: six classic strategies (ma_crossover, rsi, macd, bollinger, momentum, mean_reversion) each implementing one on_bar(window) → Signal | None interface; @register for auto-discovery.
  • Backtest engine (qa_backtest) — event-driven engine that calls strategy.on_bar bar-by-bar; pluggable CostModel; Metrics (total_return, CAGR, Sharpe, Sortino, max drawdown, win rate).
  • API (services/api) — FastAPI app (uvicorn api.main:app); GET /api/v1/{healthz,strategies,timeframes,backtests,backtests/{id}} + POST /api/v1/backtests; enqueues only — never runs inline.
  • Auth (qa_core.auth + services/api/src/api/{routers/auth.py,security.py,middleware.py}) — Google OAuth2/OIDC behind an OIDCProvider ABC + registry (add GitHub/Apple = one provider), HS256 JwtSessionIssuer behind a SessionIssuer ABC, and a single current_user dependency that scopes every per-user endpoint (positions/subscriptions/backtests; cross-user reads 404, not 403). Auth is optional at boot and fails closed when unconfigured; market-data endpoints stay public. Security middleware adds CORS (locked by default), security headers, and a per-IP rate limit on /auth/*. /ws/signals authenticates via a ?token= query param.
  • Worker (services/api/src/api/worker.py, qa-worker / python -m api.worker) — BLPOP backtest:jobs; runs the engine; persists BacktestResult; statuses: queued → running → done | error; publishes backtests:done.
  • Live engine (services/live) — subscribes to bars:new; evaluates active subscriptions once per bar; dedup-guards via signals unique constraint; fans out via WebSocket (signals:new) and email notifier; durable catch-up replay on restart.
  • Frontend (services/frontend) — React+Vite+TS SPA served by nginx; proxies /api to the FastAPI service; monochrome Tailwind CSS 4 + shadcn/ui dashboard, all charts via KLineCharts; five routes: Overview (landing page — KPIs, market chart, positions/signals tabs), Charts (K-line + /ws/candles live bars), Backtest (schema-driven form + results), Portfolio (positions table), Signals (live feed via /ws/signals). P5 auth: an AuthProvider captures the JWT from the OAuth callback URL fragment, persists it, and wires the apiClient Bearer interceptor; RequireAuth gates Overview/Backtest/Portfolio/Signals while Charts stays public. make e2e / make e2e-docker for the Playwright smoke suite.
  • Deploy (deploy/helm/quantaugur + services/{poller,api,live,frontend}/Dockerfile) — one portable, vendor-neutral Helm chart; API, worker, live engine, and frontend are separate Deployments that scale independently.

Repository layout

quantaugur/
├── packages/
│   ├── qa-core/        # config, models, db (upsert_bars/load_bars), schemas (Bar+validation), universe (+subscription), market_calendar, bus, timeframes, jobs, backtest_runs, signals, subscriptions, live_cursor, notify (+email_notifier), dispatch, auth (OIDCProvider/GoogleOIDC + JwtSessionIssuer + registry), users
│   ├── qa-datasource/  # MarketDataSource ABC + registry + yfinance adapter
│   ├── qa-strategies/  # Strategy ABC, Signal, BarWindow, @register registry, indicators, 6 classic strategies
│   └── qa-backtest/    # event-driven engine, Broker, CostModel, Metrics, runner (worker logic)
├── services/
│   ├── poller/         # the daemon: poller.py, health.py, main.py + Dockerfile
│   ├── api/            # FastAPI app (main.py) + worker entrypoint (worker.py) + Dockerfile
│   ├── live/           # live signal engine (engine.py, main.py, health.py) + Dockerfile
│   └── frontend/       # React+Vite+TS SPA — nginx serving + /api proxy + Dockerfile
├── db/
│   ├── alembic.ini
│   └── migrations/     # 0001_ohlcv, 0002_backtest_runs, 0002b_continuous_aggregates, 0003_user_strategies, 0004_signals, 0005_live_cursor, 0006_signal_delivery, 0007_positions, 0008_users, 0009_scope_user_id
├── deploy/helm/quantaugur/   # portable chart (values.yaml + values-{k3s,generic}.yaml)
├── docker-compose.yml        # local dev: timescaledb + redis
└── docs/                      # architecture, schema, and manual-testing guides

Architectural rule: services/* may import qa_*; qa_* packages must not import from services/*. Shared logic always lives in a qa_* package so later phases (P2 backtest, P3 live) reuse it without duplication.

Quickstart

Make targets (make help lists everything):

make build         # build the api/poller/live/frontend images (REGISTRY=… TAG=… overridable)
make compose-up    # run the FULL stack with docker compose (db, redis, migrate, api, worker,
                   # poller, live, frontend) — UI at http://localhost:8080
                   # poller defaults to QA_UNIVERSE=subscription — subscribing via the API (or
                   # adding a position) pulls new tickers into ingestion without a redeploy
make kind-up       # build + load + helm-install the whole platform on a local kind cluster
make kind-down     # delete the kind cluster
make test lint     # quality gates
make e2e-docker    # frontend Playwright smoke suite, hermetic (make e2e = native variant)

Manual dev loop (infra in containers, services on the host):

docker compose up -d                                              # TimescaleDB + Redis only
cp .env.example .env
export QA_DATABASE_URL=postgresql+asyncpg://quant:quant@localhost:5432/quantaugur
uv run alembic -c db/alembic.ini upgrade head                    # create the schema
uv run pytest -q                # full suite — the integration tests (incl. all P3 live-signal
                                # behaviors) need Docker and SKIP without it; a green run without
                                # Docker does NOT verify them
uv run qa-poller                                                 # run the ingestion daemon

API + worker (local, from the repo root):

uv run uvicorn api.main:app --host 0.0.0.0 --port 8000 &
uv run qa-worker        # console script for python -m api.worker
uv run qa-live          # live signal engine (P3) — warns if QA_NOTIFY_EMAIL unset

See docs/manual-testing.md for the full data-pipeline walkthrough, and deploy/helm/README.md for the Kubernetes smoke-test flow (port-forward, submit a backtest, poll to done).

Tech stack

Python 3.12 · uv workspace · SQLAlchemy 2.0 (async) + asyncpg · Alembic · TimescaleDB (PG16) · Redis 7 · exchange_calendars · yfinance · FastAPI + uvicorn + websockets · Authlib (OIDC) + PyJWT (HS256 sessions) · aiosmtplib (SMTP) · numpy / pandas (metrics) · pydantic v2 / pydantic-settings · pytest + testcontainers · React 18 + Vite + TypeScript · TanStack Query · Tailwind CSS 4 + shadcn/ui · KLineCharts · vitest + Testing Library · Playwright (e2e) · nginx · Helm 3 / Kubernetes.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages