Base URL (development): http://localhost:8000
Interactive documentation generated by FastAPI is available at /docs (Swagger UI) and /redoc while the server runs. This file is a hand-written companion describing the shapes the official clients rely on.
All endpoints exist under a versioned prefix (/api/v1/...). Non-versioned aliases (/api/...) are also mounted for backward compatibility and are hidden from the schema.
- Requests and responses are JSON.
- Money and ratios are numbers, not strings. Percentages in
allocationare 0–100; fractions likemaxDrawdownandvolatilityare 0–1 unless noted. - The data endpoints (portfolio, strategies, risk, backtest, market data, trading, research, alternative data) are currently open. Authentication applies to
/api/auth.
GET /health
GET /
Returns a small status object indicating the service is alive.
Tokens are HS256 JWTs. Send the token as Authorization: Bearer <token> on subsequent requests.
POST /api/auth/register
{
"email": "trader@example.com", // email or username
"name": "Ada Lovelace", // optional
"password": "a-strong-password"
}
→ 201 { "token": "<jwt>", "user": { "id", "email", "name" } }
POST /api/auth/login
{ "email": "trader@example.com", "password": "a-strong-password" }
→ 200 { "token": "<jwt>", "user": { ... } }
→ 401 on bad credentials
POST /api/auth/refresh → { "token": "<jwt>" }
GET /api/auth/profile → { "id", "email", "name" }
GET /api/v1/portfolio/
→ {
"totalValue": number,
"cash": number,
"dailyPnL": number,
"totalPnL": number,
"allocation": [ { "ticker": string, "value": number, "percentage": number } ]
}
GET /api/v1/portfolio/positions
→ [ { "ticker", "quantity", "entryPrice", "currentPrice", "unrealizedPnL", "weight" } ]
GET /api/v1/portfolio/holdings
→ [ { "symbol", "value", "weight" } ]
GET /api/v1/portfolio/performance
→ {
"equityCurve": [ { "timestamp": string, "value": number } ],
"metrics": { "sharpeRatio": number, ... }
}
GET /api/v1/strategies/
→ [ {
"id", "name", "status",
"performance": {
"sharpeRatio", "maxDrawdown", "profitFactor",
"winRate", "totalReturn", "volatility", "alpha", "beta"
}
} ]
GET /api/v1/strategies/{id}/equity-curve
→ { "equityCurve": [ { "day": number, "value": number, "benchmark": number } ] }
GET /api/v1/risk/metrics
→ {
"var": number, // value at risk (% of NAV)
"cvar": number,
"sharpeRatio": number,
"sortinoRatio": number,
"maxDrawdown": number, // fraction
"beta": number,
"correlation": number,
"volatility": number // fraction
}
GET /api/v1/risk/stress-scenarios
→ [ { "name", "pnl", "duration", "recovery", "portfolioImpact" } ]
GET /api/v1/risk/radar
→ [ { "metric": string, "value": number } ]
POST /api/v1/backtest/
{
"strategyId": string,
"startDate": "YYYY-MM-DD",
"endDate": "YYYY-MM-DD",
"initialCapital": number
}
→ {
"totalReturn", "annualisedReturn", "sharpeRatio", "sortinoRatio",
"maxDrawdown", "winRate", "profitFactor", "finalCapital"
}
The result fields are flat (not nested under a metrics object).
GET /api/v1/market-data/quotes
→ [ {
"ticker", "timestamp", "bid", "ask", "last",
"volume", "high", "low", "open", "close",
"source": "live" | "synthetic"
} ]
GET /api/v1/market-data/quote/{ticker}
→ single quote object (shape as above)
GET /api/v1/market-data/historical/{ticker}?days=90
→ [ { "timestamp", "open", "high", "low", "close", "volume" } ]
source reports whether the row came from a live connector (Yahoo Finance / Polygon) or the synthetic fallback.
Trading is simulated in-process; there is no live broker.
GET /api/v1/trading/orders → [ Order ]
GET /api/v1/trading/orders/{id} → Order
POST /api/v1/trading/orders → 201 Order
DELETE /api/v1/trading/orders/{id} → 204
OrderCreate (POST body):
{
"ticker": string,
"side": "BUY" | "SELL",
"quantity": number,
"orderType": "MARKET" | "LIMIT" | "STOP",
"price": number // required for LIMIT and STOP
}
Order:
{
"id", "ticker", "side", "quantity", "orderType",
"price?", "filledPrice?", "status", "timestamp?", "filledAt?"
}
MARKET orders fill immediately and cannot be cancelled. LIMIT and STOP orders stay pending and can be cancelled with DELETE.
GET /api/v1/research/papers
→ [ { "id", "title", "authors": [string], "abstract", "category", "year", "url?" } ]
Also available as /api/research/papers.
GET /api/v1/alternative-data/sources
→ [ {
"id", "name",
"type": "satellite" | "sentiment" | "sec" | "social",
"status": "active" | "inactive",
"lastUpdate", "dataPoints",
"description?", "latency?"
} ]
In development the API accepts cross-origin requests from localhost, 127.0.0.1, and private LAN IP ranges on any port, so the web dashboard (port 3000) and the Expo web build (port 8081) can both call it. Configure with CORS_ORIGINS.