/api/v1/portfolio/analyzeFull portfolio analytics for a set of holdings
Computes a complete portfolio report for the CURRENT composition from dividend-adjusted daily prices: dollar-weighted allocation by sector (vs the benchmark) and market-cap bucket, a base-100 portfolio-vs-benchmark performance curve with period returns and excess, 52-week and 3-year risk (beta, annualized volatility, Sharpe, tracking error, information ratio), 1-day historical VaR and expected shortfall at 95% and 99%, maximum drawdown, real-weight risk contribution, correlation clusters, per-stock beta/volatility/drawdown, traffic-light concentration flags, and a 0-100 portfolio score with diversification/risk/quality subscores. Equal-weight requests may set one performance start date; risk and scoring retain the trailing 3-year window. Aggregates only — no per-ticker price series is ever returned.
Why use this
Common use case
Portfolio analytics for the current composition over a trailing 3-year window. Post a set of holdings (with optional share counts) and a benchmark; receive dollar-weighted allocation vs the benchmark, a base-100 performance curve with period returns and excess, 52-week and 3-year risk statistics (beta, volatility, Sharpe, tracking error, information ratio, VaR/shortfall at 95/99, max drawdown), real-weight risk contribution, correlation clusters, per-stock risk, traffic-light concentration flags, and a deterministic 0-100 portfolio score. Returns aggregates only — the base-100 curves are portfolio-level and benchmark-level; no per-ticker price series is exposed.
Parameters
| Name | In | Required | Default | Allowed | Description | Example |
|---|---|---|---|---|---|---|
| include_research | body | optional | — | — | Optional boolean, default false. Include stored public research for all resolved holdings in one serial batch: completed and early-quarter institutional ownership, discretionary insider activity and clusters, 13D/G threshold filings, convergence, source dates and coverage omissions. No account identity, transactions or writes. Missing fundamentals and technical records remain null; website consumers may join the existing financial and technical batch readers. | true |
| start_date | body | optional | — | — | Optional ISO date for an equal-weight request, no later than today. Both displayed performance curves begin on this date and are rebased to 100. The response also includes one exact-date unadjusted closing-price reference for every submitted holding through a single batched query. Missing closes remain null; no nearby date is substituted. The trailing 3-year risk and score calculations remain unchanged. Cannot be combined with share counts or price_dates. | 2024-01-02 |
| price_dates | body | optional | — | — | Optional list of up to 100 exact symbol/date pairs for submitted holdings. ISO dates must be no later than today. Returns unadjusted closing-price references for missing purchase costs. Never substitutes another date, changes the current-composition analysis or posts a transaction. | [{"symbol":"AAPL","date":"2026-09-11"}] |
| holdings | body | required | — | — | Array of 1-100 holdings. Each item: `symbol` (required, ticker string) and `shares` (optional, positive number). If EVERY item omits `shares`, the portfolio is analyzed equal-weighted and the response sets `equal_weighted: true`. Duplicate symbols are collapsed (last `shares` wins). More than 100 holdings returns 400. | [{"symbol":"AAPL","shares":50},{"symbol":"MSFT","shares":30}] |
| benchmark | body | optional | SPY | — | Benchmark symbol for beta, tracking error, excess return, and the sector-weight comparison. Defaults to `SPY`. Must have price coverage or the call returns 502 BENCHMARK_UNAVAILABLE. | SPY |
Response schema
| Field | Type | Nullable | Description |
|---|---|---|---|
| research | object | no | Present only when include_research=true. Contains as_of, records (one per resolved symbol), sections, omissions and version. Each record includes company/sector/industry, completed/early institutional data, insider summary, buy/sell clusters, ownership filings and convergence. Source windows, comparison rules and missing-data semantics are unchanged; no News input. |
| entry_prices | array | no | Present with nonempty price_dates or an equal-weight start_date. One row per requested pair; start_date produces one row for every submitted holding. Each row contains symbol, date, price (positive decimal string or null), price_type=unadjusted_close, and reason (null or no_price_for_date). These are exact-date market references, not actual trade prices or a price series. Missing references do not alter weights. |
| data_date | string | no | ISO `YYYY-MM-DD` of the latest trading date the analysis is computed through. |
| benchmark | string | no | The benchmark symbol used (echoes the request; default `SPY`). |
| risk_free_rate | number | no | Annualized risk-free rate (decimal, e.g. `0.043`) used in every Sharpe/information-ratio calculation — the 30-day average SOFR. |
| equal_weighted | boolean | no | `true` when the portfolio was analyzed equal-weighted because no share counts were supplied. |
| holdings | array | no | Per-holding rows: `{ symbol, company, sector, industry, mkt_cap, shares, adj_close, value, weight, pct_change_1d, beta, vol_ann, max_drawdown }`, sorted by weight descending. `beta`/`vol_ann`/`max_drawdown` are null for names with insufficient trading history (never a substituted value). |
| totals | object | no | `{ value, day_change_pct, day_change_value, positions }`. `value`/`day_change_value` are null under equal-weight (no share counts to value). |
| allocation | object | no | `{ by_sector: [{ name, weight, spy_weight }], by_cap: [{ bucket, weight }], spy_weight_source }`. Dollar-weighted. `spy_weight` is the benchmark's static sector weight (source tagged by `spy_weight_source`). |
| performance | object | no | `{ index: [{ date, portfolio, benchmark }], periods: [{ period, portfolio, benchmark, excess }] }`. `index` is base-100; with start_date both curves begin there and are rebased to 100, otherwise the existing trailing window applies. Periods: `1w`, `mtd`, `ytd`, `1m`, `3m`, `6m`, `12m` (periods beyond the available performance history are omitted). |
| risk | object | no | `{ windows: { "52w": {...}, "3y": {...} }, max_drawdown, risk_contribution: [{ symbol, pct }], correlation_clusters: [[symbol,...]], flags: [{ key, level, label, detail }] }`. Each window carries `{ beta, vol, sharpe, tracking_error, information_ratio, var95, var99, shortfall95, shortfall99 }`. VaR/shortfall are positive 1-day loss magnitudes. Flags are red/amber/green. |
| score | object | no | `{ total (0-100), band, subscores: { diversification, risk, quality }, quality_basis }`. Deterministic — the same holdings always score the same. `band` is one of Strong / Solid / Mixed / Fragile / High Risk. |
| excluded | array | no | `[{ symbol, reason }]` for submitted symbols with no price coverage (e.g. `reason: "no_price_data"`). Empty array when every holding resolved. |
Sample response
- "data_date": "2026-06-30"
- "benchmark": "SPY"
- "risk_free_rate": 0.043
- "equal_weighted": false
- "holdings":
- "totals":
- "value": 17361
- "day_change_pct": 0.34
- "day_change_value": 59.03
- "positions": 2
- "allocation":
- "by_sector":
- "by_cap":
- "spy_weight_source": "static_2026Q2"
- "performance":
- "index":
- "periods":
- "risk":
- "windows":
- "max_drawdown": -0.29
- "risk_contribution":
- "correlation_clusters":
- "flags":
- "score":
- "total": 41
- "band": "Fragile"
- "subscores":
- "quality_basis": "risk_adjusted_return_3y"
- "excluded": []
Errors
| Status | Label | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 400 | Bad Request | Invalid query, body, or path parameter. |
| 401 | Unauthorized | Missing or invalid Authorization header / api_Token. |
| 402 | Payment Required | Insufficient token balance for this call. Top up |
| 429 | Too Many Requests | Rate limit exceeded for your tier (see /pricing for tier limits). Tier limits |
| 500 | Server Error | Unexpected server-side failure. Retry with backoff; report if persistent. |
Code samples
curl -X POST "https://api.finradar.ai/api/v1/portfolio/analyze?api_Token=YOUR_API_KEY" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"include_research": "true",
"start_date": "2024-01-02",
"price_dates": "[{\"symbol\":\"AAPL\",\"date\":\"2026-09-11\"}]",
"holdings": "[{\"symbol\":\"AAPL\",\"shares\":50},{\"symbol\":\"MSFT\",\"shares\":30}]",
"benchmark": "SPY"
}'Generate an API key in /account/credentials to run live queries (literal YOUR_API_KEY placeholder shown until then).