QuantDinger

July 30, 2026 · View on GitHub

ItemValue
StatusDraft (for review and phased rollout)
AudienceMaintainers; integrators wiring external AI agents into QuantDinger
Depends onAGENT_ENVIRONMENT_DESIGN.md — three-layer contracts
RepositoryQuantDinger

Companion to the multi-agent runtime design. That doc explains how coding agents work inside the repo. This doc explains how external and embedded AI agents consume QuantDinger as a product — research, strategy, backtest, and (carefully) execution.


1. Goals and non-goals

1.1 Goals

  1. Treat AI agents as first-class API consumers, alongside the existing human web UI and in-product AI chat.
  2. Provide a stable, documented capability surface so the same agent can do research, backtest, and supervised execution without screen-scraping the UI.
  3. Enforce least privilege, auditability, and kill-switches before any money-adjacent automation is allowed.
  4. Allow multiple front doors (HTTP / MCP / event stream) without forking business logic.

1.2 Non-goals (this phase)

  • Not a marketplace of third-party plugins.
  • No fully unattended live trading by an external LLM out-of-the-box. Live order routing requires explicit per-tenant opt-in and a documented allowlist.
  • Not replacing the in-product AI chat (ai_chat) — this design is what that chat (and external agents) will call underneath.

2. Personas

PersonaExamplePrimary needs
P1 Human trader (existing)QuantDinger user in browserUI + REST + JWT session
P2 In-product AI assistant (existing)ai_chat route, code-gen helpersSame backend services, on behalf of a logged-in user
P3 External coding agentCursor / Claude Code / Codex working in the repoRepository contracts (covered by AGENT_ENVIRONMENT_DESIGN.md)
P4 External AI agent / app (new)Custom LLM workflow, MCP client, third-party automationAuthenticated, scoped access to research / backtest / (optional) trading
P5 Autonomous strategy AI (gated)Generator → Strategy API V2 backtest → review → proposeProgrammatic strategy deployment and bounded backtest jobs; no autonomous experiment/tuning service

This document focuses on P4 + P5, keeping consistency with P1/P2.


3. Capability catalog

Capabilities are grouped by risk class. Every endpoint or MCP tool must declare exactly one class.

ClassExamplesDefault for new tokens
R — ReadMarket data, klines, indicators, strategy listing, backtest results, account readAllowed
W — Workspace writeCreate/update Strategy API V2 deployments and save chart-indicator codeAllowed (workspace-scoped)
B — Backtest / simulationRun Strategy API V2 backtestsAllowed
N — Notifications & misc side-effectsSend test notification, write user prefsAllowed (rate-limited)
C — CredentialsStore/rotate exchange or LLM credentialsDenied by default; admin-only
T — Trading / capitalQuick trade, place/cancel order, adjust live strategy live capitalDenied by default; per-tenant explicit opt-in + allowlist

Rule: A new agent token cannot acquire class C or T without an explicit operator action. Class T further requires a configured paper / sandbox path before it can be flipped to live.

Capability set is sourced from existing route groups: market, kline, indicator, backtest, strategy, portfolio, dashboard, quick_trade, ibkr, polymarket, credentials, settings, community, fast_analysis, ai_chat, health. The legacy experiment/tuning service is retired. New code should not add another way to trade; it should expose existing services with proper class tags.


4. Architecture

4.1 Layered view

                         ┌───────────────────────────────┐
                         │      External AI agents       │  P4 / P5
                         │  (LLM apps, MCP clients, ...) │
                         └──────────────┬────────────────┘
                                        │  HTTPS + Agent token

┌─────────────────────────────┐   ┌────────────────────────────┐
│  Browser UI (existing)      │   │  Agent Gateway (NEW)       │
│  /api/...  + JWT session    │   │  /api/agent/v1/...         │
└──────────────┬──────────────┘   │  • token auth + scopes     │
               │                  │  • rate limit + audit log  │
               │                  │  • idempotency-key support │
               │                  └──────────────┬─────────────┘
               │                                 │
               ▼                                 ▼
        ┌───────────────────────────────────────────────┐
        │     Existing service layer (single source)    │
        │  market / strategies / backtest               │
        │  portfolio / quick_trade / credentials / ...  │
        └───────────────────────┬───────────────────────┘


              ┌───────────────────────────────────┐
              │  Postgres • Redis • Workers       │
              └───────────────────────────────────┘

                Optional, additive:
                ┌────────────────────────────────────┐
                │  MCP server (read-mostly subset)   │  --> Cursor / Claude-style clients
                │  thin wrapper over /api/agent/v1   │
                └────────────────────────────────────┘

Key decision: the Agent Gateway is a thin layer, not a parallel implementation. It reuses the same Flask services; it adds identity, scopes, rate limits, idempotency, and a stable URL/version.

4.2 URL and versioning convention

  • /api/agent/v1/... — agent-facing namespace, stable contract.
  • The browser UI keeps using /api/... as today; it may continue to evolve more freely.
  • Breaking changes to the agent surface bump to /v2; /v1 is supported for a defined window.

4.3 Identity model

  • A Tenant is the existing QuantDinger user (single-user or multi-user mode).
  • An Agent token belongs to a Tenant and carries:
    • agent_id (human-readable label, e.g. cursor-mcp, strategy-bot-1)
    • scopes (subset of capability classes from §3)
    • markets allowlist (e.g. crypto, ibkr)
    • instruments allowlist (optional, for trading scope)
    • expires_at
    • paper_only flag (default true for any token with T)
  • Tokens are prefixed and hashed at rest (e.g. qd_agent_xxx); only the prefix is shown in audit logs.
  • Existing JWT user sessions are not valid for /api/agent/v1 and vice versa — separate auth pipelines, no accidental cross-use.

5. Endpoint shape (illustrative)

These are contract sketches, not committed routes. Final names follow AGENT_ENVIRONMENT_DESIGN.md Layer 3 conventions.

GET    /api/agent/v1/health                         class R
GET    /api/agent/v1/markets                        class R
GET    /api/agent/v1/markets/{market}/symbols       class R
GET    /api/agent/v1/klines                         class R
GET    /api/agent/v1/indicators/authoring-contract  class R
POST   /api/agent/v1/indicators/validate            class R
POST   /api/agent/v1/indicators                     class W

GET    /api/agent/v1/strategies                     class R
POST   /api/agent/v1/strategies                     class W
PATCH  /api/agent/v1/strategies/{id}                class W

GET    /api/agent/v1/strategy-sources/templates     class R
POST   /api/agent/v1/strategy-sources/compile       class R
GET    /api/agent/v1/strategy-sources               class R
POST   /api/agent/v1/strategy-sources               class W
GET    /api/agent/v1/strategy-sources/{id}          class R
PATCH  /api/agent/v1/strategy-sources/{id}          class W
GET    /api/agent/v1/strategy-sources/{id}/versions class R

POST   /api/agent/v1/backtest/run                   class B  (async, returns job_id)
GET    /api/agent/v1/jobs/{job_id}                 class R
GET    /api/agent/v1/jobs/{job_id}/stream          class R

GET    /api/agent/v1/portfolio/positions            class R
POST   /api/agent/v1/quick-trade/orders             class T  (paper_only honored)
POST   /api/agent/v1/quick-trade/kill-switch        class T
GET    /api/agent/v1/research/universes              class R
GET    /api/agent/v1/research/factors                class R
GET/POST/DELETE /api/agent/v1/research/watchlist     class R/W
GET    /api/agent/v1/trading/accounts                class R
GET    /api/agent/v1/trading/strategies/{id}/trades class R
GET/POST/PATCH/DELETE /api/agent/v1/notifications/signal-alerts class N
POST   /api/agent/v1/jobs/{id}/cancel                class B

5.1 Cross-cutting requirements

  • Idempotency-Key header required for mutating class W, B, N, and T calls. The server atomically reserves method + route + key + request hash, replays completed responses, and rejects in-progress or mismatched reuse.
  • Async job pattern for backtests to avoid long-lived HTTP and let LLMs poll.
  • Pagination is explicit (?limit=&cursor=); no implicit caps.
  • Errors follow a single envelope (code, message, details, retriable).
  • X-RateLimit-* headers always returned.

6. Optional MCP layer

When integrators want tool-style rather than REST:

  • Ship an MCP server that wraps a curated subset of /api/agent/v1 (start with class R and B).
  • The MCP server reads an agent token from its own config; it never asks the model for credentials.
  • Tool descriptions explicitly state risk class and scope (e.g. run_backtest (B, paper)).

MCP is additive: REST stays the source of truth, MCP only re-shapes it for clients that prefer the protocol (Cursor, Claude-style, future tools).


7. Safety, audit, and ops

7.1 Trading safety (class T)

  • Tokens default to paper_only=true. Real-money flip requires:
    1. Operator confirmation in the UI.
    2. A documented allowlist of instruments and max notional per order / per day.
    3. A kill switch that revokes all T tokens with one click and cancels open agent-originated orders.
  • The Agent Gateway tags every order with source=agent:<agent_id> so portfolio, audit, and kill-switch logic can scope by agent.

7.2 Audit log

  • One append-only log per tenant: (ts, agent_id, route, scope_class, status, idempotency_key, redacted_request_summary).
  • Stored alongside existing user activity; viewable per agent and per class in admin UI.
  • Class T writes additionally include (market, instrument, side, qty, est_notional, paper_or_live).

7.3 Rate limits and quotas

  • Per-token: requests/min and concurrent backtest-job caps.
  • Per-tenant: aggregate cap across all that tenant’s tokens.
  • LLM-cost-bearing endpoints (e.g. anything proxying to LLM_PROVIDER) carry their own quota and are denied for tokens without an explicit ai-llm sub-scope.

7.4 Secrets and credentials

  • Class C is admin-only; the Agent Gateway must never accept exchange API keys in request bodies for non-admin tokens.
  • Existing encryption-at-rest (SECRET_KEY → Fernet for qd_exchange_credentials.encrypted_config) stays unchanged.

7.5 Multi-tenancy

  • All queries through the Agent Gateway are forced through tenant-scoped service calls (no cross-tenant data leakage even on internal bugs).
  • Test plan: an integration test that issues a token for tenant A and asserts every class-R route returns 404/403 for tenant B objects.

8. Deployment topologies (self-hosted vs SaaS)

QuantDinger ships as a single backend that intentionally supports two operational topologies. The Agent Gateway code is identical in both; the differences are entirely operator-controlled environment variables and where the database lives.

8.1 Topologies

DimensionSelf-hosted (default)SaaS / shared / hosted
Selector env varQUANTDINGER_DEPLOYMENT_MODE unset (or self/local)QUANTDINGER_DEPLOYMENT_MODE=saas (also hosted/shared/multitenant)
Tenants per instance1 (the operator)N (one per signup)
Token issuanceOperator decides every fieldTenant users may issue T tokens; paper_only=true remains the default and C remains admin-only
Live trading (AGENT_LIVE_TRADING_ENABLED)Operator may flip to trueRequires risk acknowledgement, a live-capable token, notional caps, and the deployment-wide server flag
Exchange credentialsOperator may store + use themRecommended: do not accept; if you do, encrypt-at-rest and never expose via Agent Gateway (class C is admin-only)
Rate limitsRedis-backed per-token + per-tenant quotasSame gateway quotas plus a per-IP outer proxy cap
Audit visibilityOperatorSaaS operator (you) sees everyone; tenant admins see only their own (already enforced by user_id filter in /admin/audit)
MCP BASE_URLhttp://localhost:8888 (or LAN URL)https://ai.quantdinger.com (or your hosted URL)

8.2 The hosted-mode guard (V3.1.0+)

When QUANTDINGER_DEPLOYMENT_MODE is a hosted spelling, self-service issuance still excludes C scope and defaults every T token to paper-only. A live-capable T token requires ack_live_trading_risk=true, positive per-order and per-day notional caps, and AGENT_LIVE_TRADING_ENABLED=true. The emergency stop revokes all tenant T tokens and reports any live exchange order that could not be cancelled.

This policy is covered by tests/test_agent_v1_saas_guard.py.

Beyond the gateway controls, a hosted deployment should add at the proxy / infra layer:

  • HTTPS-only with HSTS; no plaintext Agent token traffic.
  • Per-IP rate limiting in front of the app, in addition to the Redis-backed token and tenant quotas.
  • CORS: /api/agent/v1/* should not be exposed to browser CORS — agents call from servers / IDE subprocess / native code, not from web pages.
  • Quota / billing hook: wrap agent_jobs.submit_job in billing middleware when future job kinds consume paid services.
  • Token reveal hygiene: full token shown once in the Vue admin UI, never logged, never echoed back from /admin/tokens GET. Already enforced.

8.4 Migration between topologies

Switching a running deployment from self-hosted to SaaS is non-destructive:

# Add to the env file used by docker-compose
QUANTDINGER_DEPLOYMENT_MODE=saas
docker compose up -d backend

After restart:

  • Existing T-scope tokens continue to work (the guard runs at issuance, not on each request) — the operator should DELETE /admin/tokens/{id} for any token they no longer want active under SaaS rules. A future enhancement may add a one-shot "revoke all T tokens on mode change" startup task.
  • New issuances follow SaaS rules immediately.

8.5 Why a single binary serves both

We deliberately did not fork SaaS into a separate codebase:

  • Less drift: every bug fix and feature ships to both topologies on the same release.
  • Self-host parity: enterprise/private users get bit-for-bit identical Agent Gateway behavior to the hosted demo, so trust transfers.
  • Test surface: the _is_saas_mode() branch is a single env-var check, easy to cover (and is, in test_agent_v1_saas_guard.py).

9. Mapping to existing code

ConcernExisting codeReuse strategy
User auth (JWT)app/routes/auth.py, app/utils/auth.pyKeep. Agent tokens live in a parallel module (app/utils/agent_auth.py proposed).
Tradingquick_trade, ibkr, polymarketWrap in T-class endpoints; reuse service layer; do not fork order logic.
Backtestbacktest, app/services/strategy_v2/*Keep long-running entrypoints behind the async job table; agent endpoints remain thin submit/poll wrappers.
AI chat / code-genai_chatRefactor to call internal services; agent endpoints expose the same services without the chat shell.
HealthhealthReuse for /api/agent/v1/health.

No new Python packages are required for the gateway itself; storage uses existing Postgres (new tables: qd_agent_tokens, qd_agent_jobs, qd_agent_audit).


10. Phased roadmap

PhaseDeliverableRisk class enabledHuman action required
A0Spec freeze: this doc + endpoint table + scope schemaReview and merge
A1Agent token issuance + /api/agent/v1/health, markets, symbols, klines, indicators/runRIssue first token in admin UI
A2Strategies CRUD + backtest async jobs + audit log v1R, W, BPer-tenant opt-in for W
A3Per-token rate limits and bounded job streamingR, W, B
A4Optional MCP server wrapping the current Agent GatewayR, B, WConfigure MCP client
A5Trading endpoints in paper-only mode + per-agent kill switchT (paper)Explicit per-token opt-in
A6Live trading promotion path: instrument allowlist, notional caps, dual-control toggleT (live)Operator dual confirmation

A1–A4 are safe to ship without trading exposure. A5/A6 are gated and reversible.


11. Open questions

  1. Token storage location — share qd_users table family vs new schema namespace?
  2. Job runner — reuse existing worker toggles (ENABLE_PENDING_ORDER_WORKER, etc.) or introduce a dedicated agent-jobs worker? Prefer the latter for blast-radius isolation.
  3. OpenAPI generation — auto-derive from Flask blueprints or hand-maintain a single agent-openapi.json checked into docs/agent/?
  4. MCP transport — stdio first (simplest for desktop IDEs), HTTP later for cloud agents.
  5. Cost passthrough — when class B triggers LLM use indirectly (e.g. NL→code helpers), should the response include token-cost telemetry?

12. Implementation status

AreaStatusWhere it lives
Schema (tokens / jobs / audit / paper-orders)Shippedbackend_api_python/migrations/init.sql (section 30) + runtime ensure in app/utils/agent_auth.py
Token auth + scopes + distributed token/tenant quotas + auditShippedapp/utils/agent_auth.py
Atomic idempotency for W/B/N/T mutationsShippedqd_agent_idempotency + agent_required
Async job runner, progress, and cancellationShippedapp/utils/agent_jobs.py
Read endpoints (R)Shippedapp/routes/agent_v1/{health,markets,strategies,jobs,portfolio,research,trading_data}.py
Workspace endpoints (W)Shippedapp/routes/agent_v1/{strategies,strategy_sources,indicators,research}.py
Notification automation (N)Shippedapp/routes/agent_v1/notifications.py
Strategy API V2 backtest endpoint (B)Shippedapp/routes/agent_v1/backtests.py
Trading endpoints (T) — paper and hard-gated liveShippedapp/routes/agent_v1/quick_trade.py (allowlists, token notional caps, server flag, emergency stop)
Admin token CRUD + audit viewerShippedapp/routes/agent_v1/admin.py
OpenAPI 3.0 specShippeddocs/agent/agent-openapi.json
MCP server (Python)Shippedmcp_server/stdio (default), sse, and streamable-http transports via QUANTDINGER_MCP_TRANSPORT
Operator quickstartShippeddocs/agent/AGENT_QUICKSTART.md
Job progress streaming (SSE)ShippedGET /api/agent/v1/jobs/{id}/streamsnapshot / progress / ping / result frames; resume via ?since= or Last-Event-ID
Token UI (Profile + admin audit)ShippedProfileAgentTokens.vue at Profile → My Agent Token (/api/agent/v1/me/tokens); admin route /agent-tokens retained
Hosted-mode hardening (QUANTDINGER_DEPLOYMENT_MODE=saas)ShippedC scope stays admin-only; T defaults paper-only and live eligibility requires explicit risk acknowledgement
Published MCP package on PyPIShippedquantdinger-mcp — install via pipx, uvx, or pip
Live quick-order execution (T, self-host only)Shipped, hard-gatedapp/routes/agent_v1/quick_trade.py; requires live-capable token, explicit MCP confirmation, credential, and server flag

13. Revision history

VersionDateNotes
0.12026-05-02First draft: personas, capability classes, gateway, MCP, safety, roadmap
0.22026-05-02A0–A5 implemented (schema, auth, R/W/B + paper-only T, admin, MCP, tests, OpenAPI, quickstart)
0.32026-05-02Added: SSE progress streaming for jobs, MCP HTTP/SSE transport, Vue admin UI for token & audit management
0.42026-05-02Added §8 Deployment topologies; shipped hosted-mode guard (QUANTDINGER_DEPLOYMENT_MODE=saas → T-scope rejected, paper_only pinned); MCP package published to PyPI; README EN/CN now documents the SaaS vs self-host paths side-by-side
0.52026-07-31Added full research/observation/notification MCP surface, atomic idempotency, Redis quotas, job cancellation, notional caps, and tenant emergency stop