f468e30af0
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
New service at services/signal_engine/ implementing concurrent heuristic (deterministic scoring) and probabilistic (Bayesian inference) pipelines that evaluate technical signals across 6 timeframes (M30-M) and produce independent BUY/WATCH/SKIP verdicts per ticker per evaluation tick. Components: - Input Normalizer: multi-source data assembly with sentinel fallbacks - Signal Library: Fibonacci, MA Stack, RSI, Cup & Handle, Elliott Wave - Multi-Timeframe Confluence Engine: weighted scoring with D/W/M anchors - Hard Filter Engine: macro_bias, valuation, earnings proximity gating - Heuristic Pipeline: S_total scoring with confidence-gated verdicts - Probabilistic Pipeline: Bayesian log-odds with regime priors, entropy gating, EV_R calculation, and signal correlation penalty - Exit Engine: stop-loss, targets, trailing ATR-based stops - Delta Analyzer: pipeline agreement tracking with rolling Redis metrics - Output Formatter: SignalOutput contract + Recommendation schema mapping - Worker orchestrator: concurrent pipelines with failure isolation - Main entry point: queue polling with fail-safe config loading Infrastructure: - Migration 039: signal_engine_outputs table with 3 indexes - Helm chart: signalEngine service entry (processing tier) - Redis key: QUEUE_SIGNAL_ENGINE constant Tests: 390 tests (unit + property-based) covering all components Config: dual_pipeline_enabled=false by default (safe rollout)
1308 lines
52 KiB
Markdown
1308 lines
52 KiB
Markdown
# Stonks Oracle — API Reference
|
||
|
||
This document covers every HTTP endpoint exposed by the four FastAPI services in the Stonks Oracle platform. For each endpoint: HTTP method, path, query parameters (with type, default, and constraints), request body schema, response schema, and error codes.
|
||
|
||
**Live endpoints:**
|
||
|
||
| Service | Base URL | Source |
|
||
|---------|----------|--------|
|
||
| Query API | `https://stonks-api.celestium.life` | `services/api/app.py` |
|
||
| Symbol Registry | `https://stonks-registry.celestium.life` | `services/symbol_registry/app.py` |
|
||
| Trading Engine | `https://stonks-trading.celestium.life` | `services/trading/app.py` |
|
||
| Risk Engine | (cluster-internal) | `services/risk/app.py` |
|
||
|
||
**Common error format:** All services return errors as `{"detail": "error message"}` with the appropriate HTTP status code.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
- [1. Query API](#1-query-api)
|
||
- [2. Symbol Registry API](#2-symbol-registry-api)
|
||
- [3. Trading Engine API](#3-trading-engine-api)
|
||
- [4. Risk Engine API](#4-risk-engine-api)
|
||
|
||
---
|
||
|
||
## 1. Query API
|
||
|
||
Source: `services/api/app.py`
|
||
Base path: `/` (most endpoints prefixed with `/api/`)
|
||
|
||
### 1.1 Health and Metrics
|
||
|
||
#### `GET /health`
|
||
Liveness probe. Verifies database connectivity.
|
||
|
||
- **Response:** `{"status": "ok"}`
|
||
- **Errors:** `503` — Database unavailable
|
||
|
||
#### `GET /metrics`
|
||
Prometheus metrics endpoint for scraping.
|
||
|
||
- **Response:** Prometheus text format (`text/plain`)
|
||
|
||
### 1.2 Companies
|
||
|
||
#### `GET /api/companies`
|
||
List tracked companies with optional filters.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `active` | bool | `true` | Filter by active status |
|
||
| `sector` | string | — | Filter by sector |
|
||
| `ticker` | string | — | Filter by ticker (auto-uppercased) |
|
||
|
||
- **Response:** Array of company objects with `id`, `ticker`, `legal_name`, `exchange`, `sector`, `industry`, `market_cap_bucket`, `active`, `created_at`, `updated_at`
|
||
|
||
#### `GET /api/companies/{company_id}`
|
||
Get a single company with aliases and active source count.
|
||
|
||
- **Path params:** `company_id` (UUID string)
|
||
- **Response:** Company object + `aliases[]` + `active_source_count`
|
||
- **Errors:** `404` — Company not found
|
||
|
||
#### `GET /api/companies/{company_id}/sources`
|
||
List sources configured for a company.
|
||
|
||
- **Path params:** `company_id` (UUID string)
|
||
- **Response:** Array of source objects with `id`, `source_type`, `source_name`, `config`, `credibility_score`, `retention_days`, `access_policy`, `active`
|
||
|
||
### 1.3 Documents
|
||
|
||
#### `GET /api/documents`
|
||
List documents with optional filters, ordered by `published_at` descending.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | — | Filter by ticker |
|
||
| `company_id` | string | — | — | Filter by company UUID |
|
||
| `document_type` | string | — | — | Filter by type |
|
||
| `status` | string | — | — | Filter by processing status |
|
||
| `since` | string | — | ISO 8601 timestamp | Documents published after this time |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
- **Response:** Array of document objects
|
||
|
||
#### `GET /api/documents/{document_id}`
|
||
Get a single document with intelligence extraction and company mentions.
|
||
|
||
- **Path params:** `document_id` (UUID string)
|
||
- **Response:** Document object + `company_mentions[]` + `intelligence` (with `company_impacts[]`)
|
||
- **Errors:** `404` — Document not found
|
||
|
||
### 1.4 Trends
|
||
|
||
#### `GET /api/trends`
|
||
List trend summaries with optional filters.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | — | Filter by entity_id (ticker) |
|
||
| `entity_type` | string | `"company"` | — | Entity type filter |
|
||
| `window` | string | — | — | Time window filter |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
- **Response:** Array of trend objects with JSONB fields parsed, plus `projection` sub-object from `trend_projections`
|
||
|
||
#### `GET /api/trends/history`
|
||
Historical trend snapshots for charting (time series from `trend_history` table).
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | — | Filter by entity_id |
|
||
| `window` | string | — | — | Time window filter |
|
||
| `limit` | int | `200` | max `1000` | Max rows |
|
||
|
||
- **Response:** Array of trend history objects ordered by `generated_at` ascending
|
||
|
||
#### `GET /api/trends/{trend_id}`
|
||
Get a single trend summary by ID.
|
||
|
||
- **Path params:** `trend_id` (UUID string)
|
||
- **Response:** Trend object with parsed JSONB fields
|
||
- **Errors:** `404` — Trend not found
|
||
|
||
#### `GET /api/trends/{trend_id}/evidence`
|
||
Drill down from a trend window to contributing documents and raw artifacts. Full provenance chain.
|
||
|
||
- **Path params:** `trend_id` (UUID string)
|
||
- **Response:** `{ trend, evidence[] }` — each evidence item includes `intelligence` and `company_impacts[]`
|
||
- **Errors:** `404` — Trend not found
|
||
|
||
#### `GET /api/trends/{trend_id}/projection`
|
||
Trend projection for a specific trend window.
|
||
|
||
- **Path params:** `trend_id` (UUID string)
|
||
- **Response:** Projection object with `projected_direction`, `projected_strength`, `projected_confidence`, `projection_horizon`, `driving_factors`, `macro_contribution_pct`, `diverges_from_current`
|
||
- **Errors:** `404` — Trend not found
|
||
|
||
### 1.5 Market Prices
|
||
|
||
#### `GET /api/market/prices/{ticker}`
|
||
Historical OHLCV bars from `market_snapshots`, deduplicated by bar timestamp and ordered oldest-first. Also returns 90-day high/low range.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `limit` | int | `200` | max `500` | Max bars returned |
|
||
|
||
- **Path params:** `ticker` (auto-uppercased)
|
||
- **Response:** `{ bars: [{ ticker, close, open, high, low, volume, bar_timestamp, captured_at }], range_90d: { low, high } }`
|
||
|
||
#### `POST /api/market/backfill/{ticker}`
|
||
Backfill daily OHLCV bars from Polygon for the last N days. Deduplicates by bar timestamp.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `days` | int | `90` | max `365` | Number of days to backfill |
|
||
|
||
- **Path params:** `ticker` (auto-uppercased)
|
||
- **Response:** `{ ticker, inserted, total_bars, days }`
|
||
- **Errors:** `503` — No market data API key configured
|
||
|
||
#### `POST /api/market/backfill-all`
|
||
Backfill daily bars for all active companies from Polygon.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `days` | int | `90` | max `365` | Number of days to backfill |
|
||
|
||
- **Response:** `{ total_inserted, tickers, details[] }` — each detail has `{ ticker, inserted }` or `{ ticker, inserted: 0, error }`
|
||
- **Errors:** `503` — No market data API key configured
|
||
|
||
### 1.6 Recommendations
|
||
|
||
#### `GET /api/recommendations`
|
||
List recommendations with optional filters.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | — | Filter by ticker |
|
||
| `action` | string | — | — | Filter by action (buy/sell/hold) |
|
||
| `mode` | string | — | — | Filter by mode |
|
||
| `since` | string | — | ISO 8601 | Generated after this time |
|
||
| `min_confidence` | float | — | 0.0–1.0 | Minimum confidence threshold |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
| `latest` | bool | `true` | — | Return only latest per ticker |
|
||
|
||
- **Response:** Array of recommendation objects
|
||
|
||
#### `GET /api/recommendations/{recommendation_id}`
|
||
Get a single recommendation with evidence and risk evaluation.
|
||
|
||
- **Path params:** `recommendation_id` (UUID string)
|
||
- **Response:** Recommendation + `evidence[]` + `risk_evaluation`
|
||
- **Errors:** `404` — Recommendation not found
|
||
|
||
#### `GET /api/recommendations/{recommendation_id}/evidence`
|
||
Full evidence drill-down: provenance chain from recommendation to source documents and raw artifacts.
|
||
|
||
- **Path params:** `recommendation_id` (UUID string)
|
||
- **Response:** `{ recommendation, evidence[], trend_window }` — each evidence item includes `intelligence` and `company_impacts[]`
|
||
- **Errors:** `404` — Recommendation not found
|
||
|
||
### 1.7 Orders
|
||
|
||
#### `GET /api/orders`
|
||
List orders with optional filters.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | — | Filter by ticker |
|
||
| `status` | string | — | — | Filter by order status |
|
||
| `side` | string | — | — | Filter by side (buy/sell) |
|
||
| `since` | string | — | ISO 8601 | Created after this time |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
- **Response:** Array of order objects
|
||
|
||
#### `GET /api/orders/{order_id}`
|
||
Get a single order with events, decision trace, and full audit trail.
|
||
|
||
- **Path params:** `order_id` (UUID string)
|
||
- **Response:** Order object + `events[]` + `audit_trail`
|
||
- **Errors:** `404` — Order not found
|
||
|
||
### 1.8 Positions
|
||
|
||
#### `GET /api/positions`
|
||
List current positions.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `ticker` | string | — | Filter by ticker |
|
||
|
||
- **Response:** Array of position objects with `id`, `broker_account_id`, `ticker`, `quantity`, `avg_entry_price`, `current_price`, `unrealized_pnl`, `realized_pnl`, `updated_at`
|
||
|
||
### 1.9 Audit Trail
|
||
|
||
#### `GET /api/audit/{entity_type}/{entity_id}`
|
||
Get audit events for any entity type and ID.
|
||
|
||
- **Path params:** `entity_type` (string), `entity_id` (string)
|
||
- **Response:** Array of audit event objects
|
||
|
||
### 1.10 Admin: Source Health
|
||
|
||
#### `GET /api/admin/sources/health`
|
||
Source health overview with latest ingestion status and failure counts.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `source_type` | string | — | Filter by source type |
|
||
| `company_id` | string | — | Filter by company UUID |
|
||
| `active_only` | bool | `true` | Only show active sources |
|
||
|
||
- **Response:** Array of source health objects with `source_id`, `source_type`, `source_name`, `credibility_score`, `ticker`, `legal_name`, `last_run_status`, `last_run_at`, `last_error`, `last_items_fetched`, `last_items_new`, `total_runs_24h`, `failed_runs_24h`, `total_items_24h`
|
||
|
||
#### `GET /api/admin/sources/{source_id}/runs`
|
||
Recent ingestion runs for a specific source.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `limit` | int | `20` | max `100` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
#### `PUT /api/admin/sources/{source_id}/toggle`
|
||
Enable or disable a source.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `active` | bool | `true` | New active state |
|
||
|
||
- **Errors:** `404` — Source not found
|
||
|
||
#### `PUT /api/admin/sources/{source_id}/credibility`
|
||
Update a source's credibility score.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `credibility_score` | float | — | 0.0–1.0 | New credibility score |
|
||
|
||
- **Errors:** `404` — Source not found
|
||
|
||
### 1.11 Admin: Company Management
|
||
|
||
#### `PUT /api/admin/companies/{company_id}/toggle`
|
||
Enable or disable a tracked company.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `active` | bool | `true` | New active state |
|
||
|
||
- **Errors:** `404` — Company not found
|
||
|
||
#### `PUT /api/admin/companies/{company_id}/sector`
|
||
Update a company's sector and industry classification.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `sector` | string | — | **Required.** New sector |
|
||
| `industry` | string | — | Optional new industry |
|
||
|
||
- **Errors:** `404` — Company not found
|
||
|
||
#### `GET /api/admin/companies/coverage`
|
||
Source coverage overview per active company. Shows active source counts by type.
|
||
|
||
- **Response:** Array of objects with `company_id`, `ticker`, `legal_name`, `sector`, `active_sources`, `market_sources`, `news_sources`, `filings_sources`, `web_scrape_sources`, `broker_sources`
|
||
|
||
### 1.12 Admin: Trading Configuration
|
||
|
||
#### `GET /api/admin/trading/config`
|
||
Get the current active risk/trading configuration.
|
||
|
||
- **Response:** Risk config object with `id`, `name`, `trading_mode`, `config` (JSONB), `active`, timestamps
|
||
|
||
#### `PUT /api/admin/trading/mode`
|
||
Switch the active trading mode.
|
||
|
||
| Parameter | Type | Constraints | Description |
|
||
|-----------|------|-------------|-------------|
|
||
| `mode` | string | `paper`, `live`, or `disabled` | **Required.** New trading mode |
|
||
|
||
#### `PUT /api/admin/trading/config`
|
||
Update the active risk configuration JSON.
|
||
|
||
- **Body:** `dict[str, Any]` — partial or full risk config object
|
||
- **Response:** Updated config object
|
||
|
||
#### `GET /api/admin/trading/approvals`
|
||
List pending operator approval requests for live trading orders.
|
||
|
||
- **Response:** Array of approval objects with `order_job` (JSONB), `recommendation_id`, `ticker`, `side`, `quantity`, `estimated_value`, `status`, `expires_at`, etc.
|
||
|
||
#### `PUT /api/admin/trading/approvals/{approval_id}`
|
||
Approve or reject a pending operator approval request.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `approved` | bool | — | **Required.** Approve or reject |
|
||
| `reviewed_by` | string | `"operator"` | Reviewer identity |
|
||
| `review_note` | string | `""` | Optional note |
|
||
|
||
- **Errors:** `404` — Approval not found or no longer pending
|
||
|
||
#### `GET /api/admin/trading/lockouts`
|
||
List active symbol lockouts (news-shock, cooldown, manual).
|
||
|
||
- **Response:** Array of lockout objects
|
||
|
||
#### `POST /api/admin/trading/lockouts`
|
||
Create a manual symbol lockout.
|
||
|
||
- **Body:** `{ ticker: string, reason: string, duration_minutes: int, lockout_type?: string }`
|
||
- **Errors:** `400` — Missing or invalid fields
|
||
|
||
#### `DELETE /api/admin/trading/lockouts/{lockout_id}`
|
||
Delete a symbol lockout (early removal).
|
||
|
||
- **Errors:** `404` — Lockout not found
|
||
|
||
#### `GET /api/admin/trading/approval-config`
|
||
Get operator approval settings from the active risk config.
|
||
|
||
- **Response:** `{ auto_approve_paper, require_approval_for_live, approval_timeout_minutes }`
|
||
|
||
#### `PUT /api/admin/trading/approval-config`
|
||
Update operator approval settings.
|
||
|
||
- **Body:** `{ auto_approve_paper?: bool, require_approval_for_live?: bool, approval_timeout_minutes?: int }`
|
||
- **Response:** Updated approval settings
|
||
|
||
### 1.13 Operational Dashboard
|
||
|
||
#### `GET /api/ops/ingestion/throughput`
|
||
Ingestion throughput over time, bucketed by interval.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | 1–168 | Time window |
|
||
| `bucket` | string | `"1h"` | `15m`, `1h`, `6h`, `1d` | Bucket interval |
|
||
|
||
- **Response:** Array of bucketed throughput objects by source type
|
||
|
||
#### `GET /api/ops/ingestion/summary`
|
||
High-level ingestion summary for the operational dashboard.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | 1–168 | Time window |
|
||
|
||
- **Response:** `{ total_runs, completed, failed, pending, running, total_items_fetched, total_items_new, active_sources, active_companies, by_source_type[], hours }`
|
||
|
||
#### `GET /api/ops/model/failures`
|
||
Recent model extraction failures with error details.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | 1–168 | Time window |
|
||
| `limit` | int | `50` | max `200` | Max results |
|
||
|
||
#### `GET /api/ops/model/performance`
|
||
Aggregated model performance metrics.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | 1–168 | Time window |
|
||
| `model_name` | string | — | — | Filter by model |
|
||
|
||
#### `GET /api/ops/pipeline/health`
|
||
Pipeline stage health summary across ingestion, parsing, extraction, and aggregation.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | 1–168 | Time window |
|
||
|
||
- **Response:** `{ hours, pipeline_enabled, document_stages[], parsing, extraction, aggregation, queue_depths }`
|
||
|
||
#### `GET /api/ops/pipeline/stream`
|
||
Server-Sent Events stream of live pipeline status. Pushes queue depths and document stage counts every 3 seconds.
|
||
|
||
- **Response:** `text/event-stream` with JSON data payloads
|
||
|
||
#### `POST /api/ops/pipeline/retry-failed`
|
||
Re-enqueue documents stuck in `extraction_failed` for another attempt (up to 200).
|
||
|
||
- **Response:** `{ retried: int, message: string }`
|
||
|
||
#### `GET /api/ops/pipeline/toggle`
|
||
Get the current pipeline enabled/disabled state.
|
||
|
||
- **Response:** `{ pipeline_enabled: bool }`
|
||
|
||
#### `POST /api/ops/pipeline/toggle`
|
||
Toggle the pipeline on or off.
|
||
|
||
- **Body:** `{ enabled: bool }`
|
||
- **Response:** `{ pipeline_enabled: bool, message: string }`
|
||
|
||
#### `GET /api/ops/sources/coverage-gaps`
|
||
Identify symbols with missing or insufficient source coverage.
|
||
|
||
- **Response:** `{ missing_source_types[], stale_sources[] }`
|
||
|
||
### 1.14 System
|
||
|
||
#### `GET /api/system/rate-limits`
|
||
Current rate limit configuration and usage.
|
||
|
||
- **Response:** `{ polygon_global_limit, polygon_source_types, per_type_limits, cadences_seconds, market_api: { rate_per_minute, cadence_seconds, max_tickers_per_cycle, active_sources }, news_api: {...} }`
|
||
|
||
### 1.15 Analytics
|
||
|
||
#### `POST /api/analytics/query`
|
||
Proxy SQL to Trino with row limits.
|
||
|
||
- **Body:** `{ sql: string, limit?: int (default 1000, max 10000) }`
|
||
- **Response:** `{ columns[], rows[][], row_count, elapsed_ms }`
|
||
- **Errors:** `400` — Empty SQL; `502` — Trino connection error; `504` — Trino timeout
|
||
|
||
#### `GET /api/analytics/schema`
|
||
Trino catalog/schema/table/column metadata for the schema browser.
|
||
|
||
- **Response:** `{ catalog, schema, tables[{ name, columns[{ name, type }] }] }`
|
||
|
||
#### `GET /api/analytics/pg-schema`
|
||
PostgreSQL table/column metadata with primary keys, foreign keys, and row estimates.
|
||
|
||
- **Response:** `{ catalog: "postgresql", schema: "public", tables[{ name, row_estimate, columns[{ name, type, nullable, primary_key?, references?, has_default? }] }] }`
|
||
|
||
#### `POST /api/analytics/pg-query`
|
||
Run read-only SQL against PostgreSQL directly. Only SELECT statements allowed.
|
||
|
||
- **Body:** `{ sql: string, limit?: int (default 1000, max 10000) }`
|
||
- **Response:** `{ columns[], rows[][], row_count, elapsed_ms }`
|
||
- **Errors:** `400` — Non-SELECT query, syntax error, table not found, or query error
|
||
|
||
#### `GET /api/analytics/saved-queries`
|
||
List all saved queries.
|
||
|
||
- **Response:** Array of `{ id, name, description, sql_text, created_by, created_at, updated_at }`
|
||
|
||
#### `POST /api/analytics/saved-queries` (201)
|
||
Save a new query.
|
||
|
||
- **Body:** `{ name: string, description?: string, sql_text: string }`
|
||
- **Response:** `{ id, name, description, sql_text, created_by, created_at }`
|
||
|
||
#### `DELETE /api/analytics/saved-queries/{query_id}`
|
||
Delete a saved query.
|
||
|
||
- **Errors:** `404` — Query not found
|
||
|
||
### 1.16 Macro Signal Layer
|
||
|
||
#### `GET /api/admin/macro/status`
|
||
Return the current macro signal layer enabled/disabled state.
|
||
|
||
- **Response:** `{ macro_enabled: bool, source: "default" | "risk_configs" }`
|
||
|
||
#### `PUT /api/admin/macro/toggle`
|
||
Toggle the macro signal layer on or off. Records an audit event.
|
||
|
||
- **Body:** `{ enabled: bool, operator?: string (default "operator") }`
|
||
- **Response:** `{ macro_enabled, previous_enabled, toggled_by }`
|
||
|
||
### 1.17 Macro Events and Impacts
|
||
|
||
#### `GET /api/macro/events`
|
||
List recent global events with filtering.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `severity` | string | — | — | Filter by severity |
|
||
| `region` | string | — | — | Filter by affected region |
|
||
| `sector` | string | — | — | Filter by affected sector |
|
||
| `since` | string | — | ISO 8601 | Events after this time |
|
||
| `until` | string | — | ISO 8601 | Events before this time |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
- **Response:** Array of global event objects with `id`, `event_types`, `severity`, `affected_regions`, `affected_sectors`, `affected_commodities`, `summary`, `key_facts`, `estimated_duration`, `confidence`, `source_document_id`, `created_at`
|
||
|
||
#### `GET /api/macro/events/{event_id}`
|
||
Event detail with affected companies and macro impact scores.
|
||
|
||
- **Path params:** `event_id` (UUID string)
|
||
- **Response:** Global event object + `impacts[]` (each with `company_id`, `ticker`, `macro_impact_score`, `impact_direction`, `contributing_factors`, `confidence`, `legal_name`, `sector`)
|
||
- **Errors:** `404` — Global event not found
|
||
|
||
#### `GET /api/macro/impacts/{ticker}`
|
||
Macro impacts and exposure profile for a specific company.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `since` | string | — | ISO 8601 | Impacts after this time |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
- **Path params:** `ticker` (auto-uppercased)
|
||
- **Response:** `{ exposure_profile, impacts[] }` — each impact includes `event_summary`, `event_severity`, `event_types`, `affected_regions`
|
||
|
||
### 1.18 Competitive Signal Layer
|
||
|
||
#### `GET /api/admin/competitive/status`
|
||
Return the current competitive signal layer enabled/disabled state.
|
||
|
||
- **Response:** `{ competitive_enabled: bool, source: "default" | "risk_configs" }`
|
||
|
||
#### `PUT /api/admin/competitive/toggle`
|
||
Toggle the competitive signal layer on or off. Records an audit event.
|
||
|
||
- **Body:** `{ enabled: bool, operator?: string (default "operator") }`
|
||
- **Response:** `{ competitive_enabled, previous_enabled, toggled_by }`
|
||
|
||
### 1.19 Patterns and Competitive Signals
|
||
|
||
#### `GET /api/patterns/{ticker}`
|
||
Historical patterns for a company.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `catalyst_type` | string | — | Filter by catalyst type |
|
||
| `time_horizon` | string | — | Filter by time horizon |
|
||
|
||
- **Path params:** `ticker` (string)
|
||
- **Response:** `{ ticker, patterns[], count }`
|
||
|
||
#### `GET /api/patterns/{ticker}/competitors`
|
||
Cross-company patterns showing how this company's catalysts affected competitors.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `catalyst_type` | string | — | Filter by catalyst type |
|
||
| `time_horizon` | string | — | Filter by time horizon |
|
||
|
||
- **Response:** `{ ticker, cross_company_patterns[], count }`
|
||
|
||
#### `GET /api/patterns/{ticker}/competitive-signals`
|
||
Recent competitive signals targeting this company (limit 100).
|
||
|
||
- **Path params:** `ticker` (string)
|
||
- **Response:** `{ ticker, competitive_signals[], count }`
|
||
|
||
#### `GET /api/patterns/{ticker}/decisions`
|
||
Major corporate decision history with trend outcomes and pattern statistics.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `time_horizon` | string | — | Filter by time horizon |
|
||
|
||
- **Path params:** `ticker` (string)
|
||
- **Response:** `{ ticker, decisions[], count }` — each decision includes `pattern_statistics[]`
|
||
|
||
### 1.20 AI Agents
|
||
|
||
#### `GET /api/agents`
|
||
List all AI agent configurations.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `active_only` | bool | `false` | Only show active agents |
|
||
|
||
- **Response:** Array of agent objects with `id`, `name`, `slug`, `purpose`, `model_provider`, `model_name`, `system_prompt`, `user_prompt_template`, `prompt_version`, `schema_version`, `temperature`, `max_tokens`, `timeout_seconds`, `max_retries`, `active`, `source`, `created_at`, `updated_at`
|
||
|
||
#### `GET /api/agents/{agent_id}`
|
||
Get a single agent configuration.
|
||
|
||
- **Path params:** `agent_id` (UUID string)
|
||
- **Errors:** `404` — Agent not found
|
||
|
||
#### `POST /api/agents` (201)
|
||
Create a new user-defined agent.
|
||
|
||
- **Body:** `AgentCreateBody`
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `name` | string | — | **Required** |
|
||
| `slug` | string | auto-generated | URL-safe identifier |
|
||
| `purpose` | string | `""` | Agent purpose |
|
||
| `model_provider` | string | `"ollama"` | LLM provider |
|
||
| `model_name` | string | `"llama3.1:8b"` | Model identifier |
|
||
| `system_prompt` | string | `""` | System prompt |
|
||
| `user_prompt_template` | string | `""` | User prompt template |
|
||
| `prompt_version` | string | `""` | Prompt version tag |
|
||
| `schema_version` | string | `"1.0.0"` | Output schema version |
|
||
| `temperature` | float | `0.0` | Sampling temperature |
|
||
| `max_tokens` | int | `32768` | Max output tokens |
|
||
| `timeout_seconds` | int | `120` | Request timeout |
|
||
| `max_retries` | int | `2` | Max retry attempts |
|
||
|
||
#### `PUT /api/agents/{agent_id}`
|
||
Update an agent configuration. Partial updates supported — only provided fields are changed.
|
||
|
||
- **Body:** `AgentUpdateBody` — all fields optional (same fields as create plus `active`)
|
||
- **Errors:** `400` — No fields to update; `404` — Agent not found
|
||
|
||
#### `DELETE /api/agents/{agent_id}`
|
||
Delete a user-created agent. System agents cannot be deleted.
|
||
|
||
- **Errors:** `403` — Cannot delete system agents; `404` — Agent not found
|
||
|
||
#### `GET /api/agents/{agent_id}/performance`
|
||
Aggregated performance metrics for an agent.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | max `720` | Time window |
|
||
|
||
- **Response:** `{ total_invocations, successes, failures, avg_duration_ms, p95_duration_ms, avg_confidence, avg_retries, total_input_tokens, total_output_tokens, success_rate }`
|
||
|
||
#### `GET /api/agents/{agent_id}/performance/history`
|
||
Hourly performance time-series for an agent.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | max `720` | Time window |
|
||
|
||
- **Response:** Array of `{ hour, invocations, successes, avg_duration_ms, avg_confidence }`
|
||
|
||
### 1.21 Agent Variants
|
||
|
||
#### `GET /api/agents/{agent_id}/variants`
|
||
List all variants for an agent, ordered by `created_at` ascending.
|
||
|
||
- **Response:** Array of variant objects with `id`, `agent_id`, `variant_name`, `variant_slug`, `description`, `model_provider`, `model_name`, `system_prompt`, `user_prompt_template`, `prompt_version`, `temperature`, `max_tokens`, `context_window`, `input_token_limit`, `token_budget`, `timeout_seconds`, `max_retries`, `is_active`, `created_at`, `updated_at`
|
||
|
||
#### `GET /api/agents/{agent_id}/variants/{variant_id}`
|
||
Get a single variant.
|
||
|
||
- **Errors:** `404` — Variant not found
|
||
|
||
#### `POST /api/agents/{agent_id}/variants` (201)
|
||
Create a new variant for an agent.
|
||
|
||
- **Body:** `VariantCreateBody`
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `variant_name` | string | — | **Required** |
|
||
| `variant_slug` | string | auto-generated | URL-safe identifier |
|
||
| `description` | string | `""` | Variant description |
|
||
| `model_provider` | string | `"ollama"` | LLM provider |
|
||
| `model_name` | string | — | **Required.** Model identifier |
|
||
| `system_prompt` | string | `""` | System prompt |
|
||
| `user_prompt_template` | string | `""` | User prompt template |
|
||
| `prompt_version` | string | `""` | Prompt version tag |
|
||
| `temperature` | float | `0.0` | Sampling temperature |
|
||
| `max_tokens` | int | `32768` | Max output tokens |
|
||
| `context_window` | int | `0` | Context window size |
|
||
| `input_token_limit` | int | `0` | Input token limit |
|
||
| `token_budget` | int | `0` | Token budget |
|
||
| `timeout_seconds` | int | `120` | Request timeout |
|
||
| `max_retries` | int | `2` | Max retry attempts |
|
||
|
||
- **Errors:** `409` — Duplicate variant slug
|
||
|
||
#### `PUT /api/agents/{agent_id}/variants/{variant_id}`
|
||
Partial update a variant.
|
||
|
||
- **Body:** `VariantUpdateBody` — all fields optional
|
||
- **Errors:** `400` — No fields to update; `404` — Variant not found
|
||
|
||
#### `DELETE /api/agents/{agent_id}/variants/{variant_id}`
|
||
Delete a variant. Cannot delete active variants.
|
||
|
||
- **Errors:** `400` — Cannot delete active variant; `404` — Variant not found
|
||
|
||
#### `POST /api/agents/{agent_id}/clone` (201)
|
||
Clone an agent's configuration as a new variant with optional overrides.
|
||
|
||
- **Body:** `VariantCloneBody { variant_name, variant_slug?, description?, model_provider?, model_name?, system_prompt?, user_prompt_template?, prompt_version?, temperature?, max_tokens?, context_window?, input_token_limit?, token_budget?, timeout_seconds?, max_retries? }`
|
||
- **Errors:** `404` — Agent not found; `409` — Duplicate slug
|
||
|
||
#### `POST /api/agents/{agent_id}/variants/{variant_id}/clone` (201)
|
||
Clone an existing variant as a new variant with optional overrides.
|
||
|
||
- **Body:** `VariantCloneBody` (same as above)
|
||
- **Errors:** `404` — Source variant not found; `409` — Duplicate slug
|
||
|
||
#### `POST /api/agents/{agent_id}/variants/{variant_id}/activate`
|
||
Set a variant as the active variant for its agent. Deactivates any currently active variant in a transaction.
|
||
|
||
- **Errors:** `404` — Variant not found
|
||
|
||
#### `POST /api/agents/{agent_id}/variants/deactivate`
|
||
Deactivate the currently active variant. Agent falls back to base configuration.
|
||
|
||
- **Response:** `{ deactivated: true }`
|
||
|
||
#### `GET /api/agents/{agent_id}/variants/{variant_id}/performance`
|
||
Aggregated performance metrics for a specific variant.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | max `720` | Time window |
|
||
|
||
- **Response:** Same shape as agent performance (invocations, successes, failures, durations, confidence, tokens, success_rate)
|
||
|
||
#### `GET /api/agents/{agent_id}/variants/{variant_id}/performance/history`
|
||
Hourly performance time-series for a specific variant.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `hours` | int | `24` | max `720` | Time window |
|
||
|
||
- **Response:** Array of `{ hour, invocations, successes, avg_duration_ms, avg_confidence }`
|
||
|
||
### 1.22 Model Validation
|
||
|
||
#### `GET /api/validation/summary`
|
||
Latest model metric snapshot plus quality gate status.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `lookback` | string | `"30d"` | `7d`, `30d`, `90d`, `all` | Lookback window |
|
||
| `horizon` | string | `"7d"` | `1h`, `6h`, `1d`, `7d`, `30d` | Prediction horizon |
|
||
|
||
- **Response:** `{ snapshot: { id, generated_at, lookback_window, horizon, prediction_count, win_rate, directional_accuracy, information_coefficient, rank_information_coefficient, avg_return, avg_excess_return_vs_spy, avg_excess_return_vs_sector, calibration_error, brier_score, buy_win_rate, sell_win_rate, hold_win_rate, metadata }, gate_status }`
|
||
- **Errors:** `400` — Invalid lookback or horizon value
|
||
|
||
#### `GET /api/validation/calibration`
|
||
Calibration table with confidence buckets showing predicted vs observed win rates.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `lookback` | string | `"30d"` | `7d`, `30d`, `90d`, `all` | Lookback window |
|
||
| `horizon` | string | `"7d"` | `1h`, `6h`, `1d`, `7d`, `30d` | Prediction horizon |
|
||
|
||
- **Response:** `{ buckets: [{ bucket_low, bucket_high, avg_confidence, observed_win_rate, prediction_count, miscalibrated }], lookback, horizon }`
|
||
- Buckets: 0.50–0.60, 0.60–0.70, 0.70–0.80, 0.80–0.90, 0.90–1.00
|
||
- `miscalibrated` is `true` when `|avg_confidence - observed_win_rate| > 0.15`
|
||
- **Errors:** `400` — Invalid lookback or horizon value
|
||
|
||
#### `GET /api/validation/ic-by-horizon`
|
||
Information Coefficient and Rank IC per prediction horizon.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `lookback` | string | `"30d"` | `7d`, `30d`, `90d`, `all` | Lookback window |
|
||
|
||
- **Response:** `{ horizons: [{ horizon, information_coefficient, rank_information_coefficient, prediction_count, generated_at }], lookback }`
|
||
- Horizons ordered: `1h`, `6h`, `1d`, `7d`, `30d`
|
||
- **Errors:** `400` — Invalid lookback value
|
||
|
||
#### `GET /api/validation/gate-status`
|
||
Quality gate evaluation detail from `risk_configs` where `name = 'model_quality_gate'`.
|
||
|
||
- **Response:** `{ gate_status, updated_at }` or `{ gate_status: null, message: "No gate evaluation found..." }`
|
||
|
||
### 1.23 Attribution
|
||
|
||
#### `GET /api/validation/attribution/sources`
|
||
Per-source performance metrics: win rate, IC, average return, duplicate rate.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `lookback` | string | `"30d"` | `7d`, `30d`, `90d`, `all` | Lookback window |
|
||
| `horizon` | string | `"7d"` | `1h`, `6h`, `1d`, `7d`, `30d` | Prediction horizon |
|
||
|
||
- **Response:** `{ sources[], lookback, horizon }`
|
||
- **Errors:** `400` — Invalid lookback or horizon; `500` — Computation failed
|
||
|
||
#### `GET /api/validation/attribution/catalysts`
|
||
Per-catalyst-type performance metrics: win rate, IC, average return.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `lookback` | string | `"30d"` | `7d`, `30d`, `90d`, `all` | Lookback window |
|
||
| `horizon` | string | `"7d"` | `1h`, `6h`, `1d`, `7d`, `30d` | Prediction horizon |
|
||
|
||
- **Response:** `{ catalysts[], lookback, horizon }`
|
||
- **Errors:** `400` — Invalid lookback or horizon; `500` — Computation failed
|
||
|
||
#### `GET /api/validation/attribution/layers`
|
||
Per-signal-layer (company, macro, competitive) performance metrics.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `lookback` | string | `"30d"` | `7d`, `30d`, `90d`, `all` | Lookback window |
|
||
| `horizon` | string | `"7d"` | `1h`, `6h`, `1d`, `7d`, `30d` | Prediction horizon |
|
||
|
||
- **Response:** `{ layers[], lookback, horizon }` — each layer has `avg_contribution_pct`, `dominant_win_rate`, `dominant_ic`
|
||
- **Errors:** `400` — Invalid lookback or horizon; `500` — Computation failed
|
||
|
||
### 1.24 Trading Reports
|
||
|
||
#### `GET /api/reports`
|
||
Paginated list of trading reports with optional filtering.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `report_type` | string | — | `daily` or `weekly` | Filter by report type |
|
||
| `start_date` | string | — | ISO date (YYYY-MM-DD) | Filter `period_start >= this` |
|
||
| `end_date` | string | — | ISO date (YYYY-MM-DD) | Filter `period_end <= this` |
|
||
| `limit` | int | `20` | max `100` | Page size |
|
||
| `offset` | int | `0` | min `0` | Pagination offset |
|
||
|
||
- **Response:** Array of `{ id, report_type, period_start, period_end, validation_status, generated_at }`
|
||
- **Errors:** `400` — Invalid `report_type` or date format
|
||
|
||
#### `GET /api/reports/{report_id}`
|
||
Fetch a single report including full `report_data` JSONB.
|
||
|
||
- **Path params:** `report_id` (UUID string)
|
||
- **Response:** `{ id, report_type, period_start, period_end, report_data, validation_status, generated_at, created_at }`
|
||
- **Errors:** `404` — Report not found
|
||
|
||
---
|
||
|
||
## 2. Symbol Registry API
|
||
|
||
Source: `services/symbol_registry/app.py` with routers from `exposure.py`, `competitors.py`, `competitor_inference.py`
|
||
|
||
### 2.1 Health
|
||
|
||
#### `GET /health`
|
||
Liveness probe. Verifies database connectivity.
|
||
|
||
- **Response:** `{"status": "ok"}`
|
||
- **Errors:** `503` — Database unavailable
|
||
|
||
### 2.2 Companies
|
||
|
||
#### `POST /companies` (201)
|
||
Create a new tracked company.
|
||
|
||
- **Body:** `CompanyCreate`
|
||
|
||
| Field | Type | Default | Constraints | Description |
|
||
|-------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | 1–10 uppercase letters | **Required.** Stock ticker |
|
||
| `legal_name` | string | — | — | **Required.** Company name |
|
||
| `exchange` | string | `null` | — | Stock exchange |
|
||
| `sector` | string | `null` | — | Sector classification |
|
||
| `industry` | string | `null` | — | Industry classification |
|
||
| `market_cap_bucket` | string | `null` | — | Market cap bucket |
|
||
|
||
- **Response:** `CompanyResponse { id, ticker, legal_name, exchange, sector, industry, market_cap_bucket, active }`
|
||
- **Errors:** `409` — Company already exists; `422` — Invalid ticker format
|
||
|
||
#### `GET /companies`
|
||
List tracked companies.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `active` | bool | `true` | Filter by active status |
|
||
|
||
- **Response:** Array of `CompanyResponse`
|
||
|
||
#### `GET /companies/{company_id}`
|
||
Get a single company.
|
||
|
||
- **Path params:** `company_id` (UUID string)
|
||
- **Errors:** `404` — Company not found
|
||
|
||
#### `PUT /companies/{company_id}`
|
||
Update a company.
|
||
|
||
- **Body:** `CompanyCreate` (same as create)
|
||
- **Errors:** `404` — Company not found
|
||
|
||
### 2.3 Aliases
|
||
|
||
#### `POST /companies/{company_id}/aliases` (201)
|
||
Add an alias for a company.
|
||
|
||
- **Body:** `{ alias: string, alias_type?: string (default "brand") }`
|
||
- **Response:** `{ id, alias, alias_type }`
|
||
|
||
#### `GET /companies/{company_id}/aliases`
|
||
List aliases for a company.
|
||
|
||
- **Response:** Array of `{ id, alias, alias_type }`
|
||
|
||
### 2.4 Watchlists
|
||
|
||
#### `POST /watchlists` (201)
|
||
Create a new watchlist.
|
||
|
||
- **Body:** `{ name: string, description?: string }`
|
||
- **Response:** `{ id, name, description, active }`
|
||
- **Errors:** `409` — Watchlist name already exists
|
||
|
||
#### `GET /watchlists`
|
||
List all watchlists.
|
||
|
||
- **Response:** Array of `{ id, name, description, active }`
|
||
|
||
#### `POST /watchlists/{watchlist_id}/members/{company_id}` (201)
|
||
Add a company to a watchlist.
|
||
|
||
- **Response:** `{ status: "added" }`
|
||
- **Errors:** `409` — Already a member; `404` — Watchlist or company not found
|
||
|
||
#### `GET /watchlists/{watchlist_id}/members`
|
||
List companies in a watchlist.
|
||
|
||
- **Response:** Array of `CompanyResponse`
|
||
|
||
### 2.5 Sources
|
||
|
||
#### `POST /companies/{company_id}/sources` (201)
|
||
Add a data source for a company.
|
||
|
||
- **Body:** `SourceCreate`
|
||
|
||
| Field | Type | Default | Constraints | Description |
|
||
|-------|------|---------|-------------|-------------|
|
||
| `source_type` | string | — | `market_api`, `news_api`, `filings_api`, `web_scrape`, `broker` | **Required** |
|
||
| `source_name` | string | — | — | **Required** |
|
||
| `config` | dict | `{}` | URLs validated for `base_url` | Source configuration |
|
||
| `credibility_score` | float | `0.5` | — | Source credibility |
|
||
| `retention_days` | int | `365` | — | Data retention period |
|
||
| `access_policy` | string | `"internal"` | `internal`, `public`, `restricted` | Access policy |
|
||
|
||
- **Response:** `{ id, source_type, source_name, credibility_score, active }`
|
||
- **Errors:** `404` — Company not found; `422` — Invalid source_type or access_policy
|
||
|
||
#### `GET /companies/{company_id}/sources`
|
||
List sources for a company.
|
||
|
||
- **Response:** Array of `{ id, source_type, source_name, config, credibility_score, retention_days, access_policy, active }`
|
||
|
||
### 2.6 Exposure Profiles
|
||
|
||
#### `GET /companies/{company_id}/exposure`
|
||
Get the current active exposure profile for a company.
|
||
|
||
- **Response:** `ExposureProfileResponse { id, company_id, geographic_revenue_mix, supply_chain_regions, key_input_commodities, regulatory_jurisdictions, market_position_tier, export_dependency_pct, source, confidence, version, active, created_at, updated_at }`
|
||
- **Errors:** `404` — No active exposure profile found
|
||
|
||
#### `PUT /companies/{company_id}/exposure`
|
||
Create or update an exposure profile. Archives the previous active version.
|
||
|
||
- **Body:** `ExposureProfileCreate`
|
||
|
||
| Field | Type | Default | Constraints | Description |
|
||
|-------|------|---------|-------------|-------------|
|
||
| `geographic_revenue_mix` | dict[str, float] | `{}` | — | Region to revenue percentage |
|
||
| `supply_chain_regions` | list[str] | `[]` | — | Supply chain regions |
|
||
| `key_input_commodities` | list[str] | `[]` | — | Key input commodities |
|
||
| `regulatory_jurisdictions` | list[str] | `[]` | — | Regulatory jurisdictions |
|
||
| `market_position_tier` | string | `"regional"` | `global_leader`, `multinational`, `regional`, `domestic` | Market position |
|
||
| `export_dependency_pct` | float | `0.0` | 0.0–1.0 | Export dependency |
|
||
| `source` | string | `"manual"` | `manual`, `inferred` | Data source |
|
||
| `confidence` | float | `1.0` | 0.0–1.0 | Confidence score |
|
||
|
||
- **Errors:** `404` — Company not found
|
||
|
||
#### `GET /companies/{company_id}/exposure/history`
|
||
Get all exposure profile versions for a company, ordered by version descending.
|
||
|
||
- **Response:** Array of `ExposureProfileResponse`
|
||
|
||
### 2.7 Competitor Relationships
|
||
|
||
#### `POST /companies/{company_id}/competitors` (201)
|
||
Create a competitor relationship. Records an audit event.
|
||
|
||
- **Body:** `CompetitorRelationshipCreate`
|
||
|
||
| Field | Type | Default | Constraints | Description |
|
||
|-------|------|---------|-------------|-------------|
|
||
| `company_b_id` | string | — | UUID | **Required.** Other company |
|
||
| `relationship_type` | string | — | `direct_rival`, `same_sector`, `overlapping_products`, `supply_chain_adjacent` | **Required** |
|
||
| `strength` | float | `0.5` | 0–1 | Relationship strength |
|
||
| `bidirectional` | bool | `true` | — | Bidirectional relationship |
|
||
| `source` | string | `"manual"` | `manual`, `inferred` | Data source |
|
||
|
||
- **Response:** `CompetitorRelationship { id, company_a_id, company_b_id, relationship_type, strength, bidirectional, source, active, created_at, updated_at }`
|
||
- **Errors:** `400` — Self-reference; `404` — Company not found; `409` — Relationship already exists
|
||
|
||
#### `GET /companies/{company_id}/competitors`
|
||
List active competitor relationships, enriched with `ticker` and `legal_name` of the other company. Ordered by strength descending.
|
||
|
||
- **Errors:** `404` — Company not found
|
||
|
||
#### `PUT /companies/{company_id}/competitors/{relationship_id}`
|
||
Update a competitor relationship. Records an audit event with previous state.
|
||
|
||
- **Body:** `CompetitorRelationshipCreate`
|
||
- **Errors:** `404` — Relationship not found
|
||
|
||
#### `DELETE /companies/{company_id}/competitors/{relationship_id}`
|
||
Soft-delete a competitor relationship (sets `active=false`). Records an audit event.
|
||
|
||
- **Response:** `{ status: "deleted", id }`
|
||
- **Errors:** `404` — Active relationship not found
|
||
|
||
### 2.8 Competitor Inference
|
||
|
||
#### `POST /companies/{company_id}/competitors/infer`
|
||
Auto-infer competitor relationships based on sector/industry match and document co-mention frequency.
|
||
|
||
Strength formula: `0.3 * sector_match + 0.7 * normalized_co_mention_count`
|
||
|
||
Upserts relationships with `source='inferred'` and `relationship_type='same_sector'`.
|
||
|
||
- **Response:** Array of `CompetitorRelationship` objects sorted by strength descending
|
||
- **Errors:** `400` — Company missing sector or industry; `404` — Company not found
|
||
|
||
---
|
||
|
||
## 3. Trading Engine API
|
||
|
||
Source: `services/trading/app.py`
|
||
|
||
### 3.1 Health and Readiness
|
||
|
||
#### `GET /health`
|
||
Liveness probe.
|
||
|
||
- **Response:** `{"status": "ok"}`
|
||
|
||
#### `GET /ready`
|
||
Readiness probe — reports whether the engine is running.
|
||
|
||
- **Response:** `{"ready": bool}`
|
||
|
||
### 3.2 Debug
|
||
|
||
#### `GET /api/trading/debug`
|
||
Diagnostic endpoint showing engine internals for troubleshooting.
|
||
|
||
- **Response:** `{ running, has_pool, has_redis, config_enabled, polling_interval, last_poll, portfolio_state: { active_pool, reserve_pool, total_value, open_positions }, risk_tier, tasks, processed_rec_count }`
|
||
|
||
### 3.3 Engine Status and Control
|
||
|
||
#### `GET /api/trading/status`
|
||
Return current engine state.
|
||
|
||
- **Response:** `{ enabled, paused, risk_tier, circuit_breaker_status, active_pool, reserve_pool, portfolio_heat, open_positions, open_position_count, max_open_positions, absolute_position_cap, last_decision_at }`
|
||
- **Errors:** `503` — Engine not initialised
|
||
|
||
#### `PUT /api/trading/config`
|
||
Update trading engine configuration. Returns previous and new values for audit trail.
|
||
|
||
- **Body:** `ConfigUpdateRequest`
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `enabled` | bool | Enable/disable engine |
|
||
| `risk_tier` | string | Risk tier level |
|
||
| `reserve_siphon_pct` | float | Reserve pool siphon percentage |
|
||
| `polling_interval_seconds` | int | Polling interval |
|
||
| `absolute_position_cap` | float | Max position value |
|
||
| `active_pool_minimum` | float | Minimum active pool |
|
||
| `micro_trading_enabled` | bool | Enable micro trades |
|
||
| `max_open_positions` | int | Max concurrent positions |
|
||
|
||
All fields optional. Only provided fields are updated.
|
||
|
||
- **Response:** `{ previous, updated, change_source: "api", changed_at }`
|
||
- **Errors:** `503` — Engine not initialised
|
||
|
||
#### `POST /api/trading/pause`
|
||
Pause the trading engine.
|
||
|
||
- **Response:** `{"paused": true}`
|
||
|
||
#### `POST /api/trading/resume`
|
||
Resume the trading engine.
|
||
|
||
- **Response:** `{"paused": false}`
|
||
|
||
#### `POST /api/trading/reset`
|
||
Full paper trading reset: liquidate broker positions, cancel orders, clear trading state, reset capital.
|
||
|
||
- **Body:** `CapitalRequest`
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `initial_capital` | float | `0.0` | If 0, uses broker balance or defaults to 100,000 |
|
||
| `reserve_pct` | float | `null` | Reserve pool percentage (0–1). If null, uses engine config `reserve_siphon_pct` |
|
||
|
||
- **Response:** `{ reset: true, initial_capital, active_pool, reserve_pool, broker: { orders_cancelled, positions_closed, portfolio_value, cash, buying_power } }`
|
||
- **Errors:** `503` — Engine not initialised; `500` — Database reset failed
|
||
|
||
### 3.4 Decision Audit Trail
|
||
|
||
#### `GET /api/trading/decisions`
|
||
Return recent trading decisions from the database.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | — | Filter by ticker |
|
||
| `decision` | string | — | — | Filter by decision type |
|
||
| `is_micro_trade` | bool | — | — | Filter micro trades |
|
||
| `limit` | int | `50` | max `200` | Page size |
|
||
| `offset` | int | `0` | — | Pagination offset |
|
||
|
||
- **Response:** Array of `{ id, recommendation_id, decision, skip_reason, ticker, computed_position_size, computed_share_quantity, risk_tier_at_decision, portfolio_heat_at_decision, active_pool_at_decision, reserve_pool_at_decision, circuit_breaker_status, is_micro_trade, created_at }`
|
||
|
||
### 3.5 Performance Metrics
|
||
|
||
#### `GET /api/trading/metrics`
|
||
Return current performance metrics.
|
||
|
||
- **Response:** `{ total_portfolio_value, active_pool, reserve_pool, unrealized_pnl, realized_pnl, daily_pnl, win_rate, profit_factor, sharpe_ratio, max_drawdown, portfolio_heat }`
|
||
- **Errors:** `503` — Engine not initialised
|
||
|
||
#### `GET /api/trading/metrics/history`
|
||
Return historical daily portfolio snapshots.
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `limit` | int | `30` | max `365` | Max snapshots |
|
||
|
||
- **Response:** Array of `{ id, snapshot_date, portfolio_value, active_pool, reserve_pool, daily_return, cumulative_return, unrealized_pnl, realized_pnl, win_count, loss_count, win_rate, sharpe_ratio, max_drawdown, current_drawdown_pct, portfolio_heat, risk_tier, created_at }`
|
||
|
||
### 3.6 Backtesting
|
||
|
||
#### `POST /api/trading/backtest`
|
||
Launch a backtest run asynchronously.
|
||
|
||
- **Body:** `BacktestRequest`
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `start_date` | string | — | **Required.** ISO date (YYYY-MM-DD) |
|
||
| `end_date` | string | — | **Required.** ISO date (YYYY-MM-DD) |
|
||
| `initial_capital` | float | `500.0` | Starting capital |
|
||
| `risk_tier` | string | `"moderate"` | Risk tier for backtest |
|
||
|
||
- **Response:** `{ id: string, status: "running" }`
|
||
- **Errors:** `503` — Engine not initialised
|
||
|
||
#### `GET /api/trading/backtest/{backtest_id}`
|
||
Retrieve backtest results.
|
||
|
||
- **Path params:** `backtest_id` (UUID string)
|
||
- **Response:** `{ id, start_date, end_date, initial_capital, risk_tier, config, total_return, sharpe_ratio, max_drawdown, win_rate, profit_factor, trade_count, equity_curve[], trades[], status, completed_at, created_at }`
|
||
- Status values: `running`, `completed`, `not_found`, `pending`
|
||
|
||
### 3.7 Notifications
|
||
|
||
#### `GET /api/trading/notifications/config`
|
||
Return current notification configuration.
|
||
|
||
- **Response:** `{ sms_enabled, email_enabled, phone_number, email_recipient }`
|
||
- **Errors:** `503` — Engine not initialised
|
||
|
||
#### `PUT /api/trading/notifications/config`
|
||
Update notification preferences.
|
||
|
||
- **Body:** `NotificationConfigRequest`
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `sms_enabled` | bool | Enable SMS notifications |
|
||
| `email_enabled` | bool | Enable email notifications |
|
||
| `phone_number` | string | SMS phone number |
|
||
| `email_recipient` | string | Email recipient address |
|
||
|
||
All fields optional.
|
||
|
||
- **Response:** `{ updated: { ...changed fields } }`
|
||
- **Errors:** `503` — Engine not initialised
|
||
|
||
#### `GET /api/trading/notifications/history`
|
||
Return recent notifications (placeholder — currently returns empty array).
|
||
|
||
| Parameter | Type | Default | Constraints | Description |
|
||
|-----------|------|---------|-------------|-------------|
|
||
| `limit` | int | `50` | max `200` | Max results |
|
||
|
||
### 3.8 Override Orders
|
||
|
||
#### `POST /api/trading/override/order` (202)
|
||
Submit a manual override order to the broker queue. Auto-registers untracked tickers in the Symbol Registry.
|
||
|
||
- **Body:** `OverrideOrderRequest`
|
||
|
||
| Field | Type | Default | Constraints | Description |
|
||
|-------|------|---------|-------------|-------------|
|
||
| `ticker` | string | — | 1–10 alphabetic chars | **Required.** Stock ticker |
|
||
| `side` | string | — | `buy` or `sell` | **Required.** Order side |
|
||
| `quantity` | float | — | Must be positive | **Required.** Share quantity |
|
||
| `order_type` | string | `"market"` | `market`, `limit`, `stop`, `stop_limit` | Order type |
|
||
| `limit_price` | float | `null` | Required for `limit`/`stop_limit` | Limit price |
|
||
| `stop_price` | float | `null` | Required for `stop`/`stop_limit` | Stop price |
|
||
|
||
- **Response:** `OverrideOrderResponse { job_id, status: "queued", ticker, side, quantity, auto_registered }`
|
||
- **Errors:** `503` — Engine not initialised or broker queue unavailable; `422` — Validation errors
|
||
|
||
---
|
||
|
||
## 4. Risk Engine API
|
||
|
||
Source: `services/risk/app.py`
|
||
|
||
### 4.1 Health
|
||
|
||
#### `GET /health`
|
||
Liveness probe.
|
||
|
||
- **Response:** `{"status": "ok"}`
|
||
|
||
### 4.2 Order Evaluation
|
||
|
||
#### `POST /evaluate`
|
||
Evaluate a proposed order against risk rules.
|
||
|
||
- **Body:** `EvaluateRequest`
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `order` | ProposedOrder | **Required.** The order to evaluate |
|
||
| `config` | PortfolioRiskConfig | Optional. Risk config (uses defaults if null) |
|
||
| `state` | AccountRiskState | Optional. Current account state |
|
||
|
||
**ProposedOrder schema:**
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `recommendation_id` | string | `null` | Source recommendation |
|
||
| `ticker` | string | — | **Required.** Stock ticker |
|
||
| `sector` | string | `""` | Company sector |
|
||
| `action` | string | `"buy"` | `buy` or `sell` |
|
||
| `quantity` | float | `0.0` | Share quantity |
|
||
| `estimated_value` | float | `0.0` | Estimated order value |
|
||
| `confidence` | float | `0.0` | Recommendation confidence |
|
||
|
||
- **Response:** `RiskEvaluation { evaluation_id, recommendation_id, ticker, eligible, allowed_mode, checks[], rejection_reasons[], config_snapshot, state_snapshot, evaluated_at }`
|
||
|
||
### 4.3 Approvals
|
||
|
||
#### `GET /approvals/pending`
|
||
List pending approval requests.
|
||
|
||
- **Response:** Array of approval request objects (serialized via `to_dict()`)
|
||
- **Errors:** `503` — Database not ready
|
||
|
||
#### `GET /approvals/{approval_id}`
|
||
Get a single approval request.
|
||
|
||
- **Path params:** `approval_id` (UUID string)
|
||
- **Response:** Approval request object
|
||
- **Errors:** `404` — Approval not found; `503` — Database not ready
|
||
|
||
#### `POST /approvals/{approval_id}/review`
|
||
Approve or reject a pending approval request.
|
||
|
||
- **Body:** `ReviewRequest`
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `approved` | bool | — | **Required.** Approve or reject |
|
||
| `reviewed_by` | string | `"operator"` | Reviewer identity |
|
||
| `review_note` | string | `""` | Optional review note |
|
||
|
||
- **Response:** `{ approval_id, status }`
|
||
- **Errors:** `404` — Approval not found or no longer pending; `503` — Database not ready
|
||
|
||
### 4.4 Approval Expiration
|
||
|
||
#### `POST /approvals/expire`
|
||
Expire stale approvals that have passed their expiration time.
|
||
|
||
- **Response:** `{ expired: int, items: [] }`
|
||
- **Errors:** `503` — Database not ready
|