/api/v1/portfolio/analyzeFull portfolio analytics for a set of holdings
Commercial API access retains its existing authentication and 10-token metering. The first-party Portfolio website may call this same batched endpoint with its verified Portfolio Day Pass from the matching approved website origin. Analysis requires current portfolio:read:analytics permission; include_research requests require current portfolio:read:intelligence permission. Website requests retain the 30-per-minute address allowance and do not use commercial token billing. 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. With include_research=true, that same response also carries stored filing-derived holding research plus daily, weekly and combined sector Signal Pulse rankings and evidence-backed nonzero moves in sectors the submitted portfolio does not hold. Each research record now includes available latest trailing-twelve-month financial metrics from the shared XBRL time-series reader (latest basis, one-year lookback, unchanged reporting-window checks). Portfolio margins, yields, return on equity and payout are percentages; price multiples, debt/equity and dividends per share retain engine units. source_period identifies the reporting-period end. Missing companies or metrics remain null. All holdings use the same request and a serial database session; no per-symbol HTTP requests or financial recalculation are introduced. 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, latest trailing-year XBRL financial metrics, source dates and coverage omissions. The same research object includes daily, weekly and combined sector Signal Pulse rankings computed from distinct bullish and bearish symbols, plus nonzero measured moves for sectors absent from the submitted portfolio. No News input, account identity, transactions or writes. Missing sources remain explicit rather than being treated as zero. | 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 | — | — | JSON array of 1-100 holding objects. Each requires symbol: preferably a ticker string; the API converts it to text, trims and uppercases it, then requires 1-12 letters, digits, dots or hyphens. shares may be omitted or null; otherwise it must parse to a positive finite number (numeric strings are accepted). When all shares are omitted or null, analysis is equal-weighted. Duplicate symbols are collapsed after normalization, with the last shares value winning. Send an array, not JSON encoded inside a string. | [{"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, version, sector_pulse and unheld_sector_moves. Each record includes company/sector/industry, completed/early institutional data, insider summary, buy/sell clusters, ownership filings, convergence and fundamentals. Existing insider summary fields remain the 90-day figures; insider.periods.30 supplies the same canonical summary for the 30-day filing and transaction windows. Both windows reuse one held-symbol query. A missing 30-day source remains null rather than reusing 90-day figures. Switching display windows changes no convergence signal. fundamentals contains available pe_ratio, ev_to_ebitda, price_to_sales, fcf_yield, dividend_yield, earnings_yield, net_margin, operating_margin, fcf_margin, roe, debt_to_equity, payout_ratio and dividend_per_share from the existing XBRL time-series reader. Margins, yields, roe and payout_ratio are percentages; multiples and dividend_per_share retain engine units. source_period gives the reporting-period end, and unavailable cells or companies remain null. Both institutional blocks preserve the stored current holder count (fund_count), full previous holder count (fund_count_prev_reported), comparable current and previous holder counts (fund_count_comparable and fund_count_prev), measured change and percent (fund_change and fund_change_pct), current shares (total_shares_curr), previous_period_end, comparison_status and is_complete. Partial comparison coverage is distinct from an early filing quarter; unavailable values remain null. Ambiguous institutional ticker matches use the existing preferred security catalogue and accepted successor chains; still-ambiguous matches remain unavailable. ownership.filings contains up to 25 recent filings per held symbol, newest first, including filings that crossed no ownership threshold. Each filing supplies filing_id, form_type, filed_at, date_of_event, filers (canonical display names), aggregate_percent, aggregate_shares, primary_document_url, sec_filing_url and sec_index_url. Canonical amendment fields priorPercent, priorShares, priorSharesAdjusted, percentChange, sharesChange, sharesChangePct and daysSincePriorFiling are populated only when a prior filing is linked. Zero is preserved; unavailable comparisons are null. An empty filings array means no matching stored filings; null means that source was unavailable. These are reported filing figures, not current stakes or inferred trades. Existing threshold_filings, activist and convergence rules remain unchanged. sector_pulse provides daily, weekly and combined sector rows with bullish/bearish symbol counts, a signed percentage score, rank and source dates. unheld_sector_moves contains only sectors absent from the portfolio with at least one nonzero measured Pulse score and carries the underlying daily/weekly/combined evidence. Existing source-window rules, comparisons 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" \
-H "X-API-Key: YOUR_API_KEY" \
-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).