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
}
→ {
"id", "strategyId", "startDate", "endDate", "initialCapital", "finalCapital",
"totalReturn", "annualisedReturn", "sharpeRatio", "sortinoRatio", "calmarRatio",
"maxDrawdown", "maxDrawdownDuration", "winRate", "profitFactor", "totalTrades",
"avgWin", "avgLoss", "bestMonth", "worstMonth",
"equityCurve": [ { "date", "equity", "benchmark", "drawdown" } ]
}
The result fields are flat (not nested under a metrics object). This genuinely
executes the strategy via app/services/backtest_runner.py: rl_agent and
ml_alpha strategies train and evaluate a real ai_models agent/forecaster,
while momentum and mean_reversion run rule-based logic - all against a
synthetic (seeded, reproducible) price series. Because training happens
synchronously in the request, an rl_agent/ml_alpha backtest can take a
few seconds; see POST /api/v1/strategies/backtest below for a fire-and-forget
alternative.
Returns 404 if strategyId doesn't exist, 422 if the strategy's type isn't
one of momentum / mean_reversion / ml_alpha / rl_agent.
POST /api/v1/strategies/backtest
{ "strategyId": string, "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD", "initialCapital": number }
→ 202 { "id", "strategyId", "status": "pending", ... }
Returns immediately with status: "pending" and runs the same simulation as
a background task, moving through "running" to "completed"/"failed".
Poll GET /api/v1/strategies/backtests/{id} for the result, which has a
smaller field set: finalCapital, totalReturn, sharpeRatio, maxDrawdown,
totalTrades, winRate, status, errorMessage (set if status is
"failed").
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.