Risk Metrics

(internal/legacy) Multi-ticker batch analysis

Per Snapshot Architecture v3, prefer POST /snapshot for new clients. This endpoint remains callable as an internal building block (cohort scans, parity checks, zarr alignment). Fetch data for up to 100 tickers in one JSON response (results map keyed by ticker). Supported metrics flags: returns (daily arrays with l1/l2/l3 columns — see operation schema for semantics), hedge_ratios (latest six hedge ratios with short keys l1_market, …), full_metrics (flat L1/L2/L3 ER/HR snapshot with long keys l1_market_hr, l1_market_er, …). Request every block you need: metrics is a whitelist (no silent backfill). For ERM3 zarr parity (L*_ER / L*_HR), request ["full_metrics","hedge_ratios"] and see docs/ERM3_ZARR_API_PARITY.md. The l3_decomposition flag is accepted but may be unpopulated depending on deployment. Cost: $0.015 per ticker that returns data, minimum $0.03/call. Tickers that fail (symbol_not_found, no_risk_metrics) are not billed, and a batch where every ticker fails costs nothing. ETFs. ETFs, including the SPY / sector / subsector hedge instruments, have no ERM3 decomposition. With full_metrics or hedge_ratios they return status: "error" with error_code: "no_risk_metrics". Use /etf-returns for ETF return series. Explained-risk shares. Incremental shares (sector, subsector) can be slightly negative: after orthogonalization an added layer can explain less than nothing over the window. The residual is defined as 1 minus the systematic shares, so the four L3 shares still sum to 1. Do not clip a negative share to 0 without re-deriving the residual.

Status code semantics (clients must read summary to detect partial / total failure)

This endpoint always returns HTTP 200 as long as the request is well-formed and authentication passes. Per-ticker failures are reported in results[ticker].status = "error" with a human-readable error string and a machine-readable error_code (symbol_not_found, no_risk_metrics, internal_error), and counted in summary.errors. Treat this as a 207 Multi-Status conceptually: you cannot infer success from the HTTP code alone. Recommended client logic: - summary.errors == 0 → all ok - 0 < summary.errors < summary.total → partial; retry only the failed tickers (see results[ticker].error) - summary.errors == summary.total → total failure; surface or escalate (do not loop-retry the whole batch — the failure is likely structural, e.g. all tickers unresolvable or upstream zarr gap) 4xx is reserved for malformed requests (tickers empty, > 100, unknown metrics flags, bad auth). Parquet/CSV: When format is parquet or csv, the response is a single long-format table of daily rows (ticker, date, gross_return, l1, l2, l3) per #/components/schemas/BatchAnalyzeExportRow. Only tickers with successful returns payloads are included; full_metrics, hedge_ratios, and l3_decomposition are not exported in tabular form. Ensure metrics includes returns or the table may be empty.

post/batch/analyze

Request

  • Base URL: https://riskmodels.app/api
  • URL: https://riskmodels.app/api/batch/analyze
  • Auth: HTTP bearer

Request body

tickersstring[] required

List of ticker symbols. Maximum 100.

metricsstring[] required

Which payloads to include per ticker (full_metrics recommended for portfolio screens).

yearsinteger

Number of years of history (applies to returns and l3_decomposition).

format'json' | 'parquet' | 'csv'

json (default): BatchAnalyzeResponse with results, summary, _agent, _metadata. parquet / csv: long-format daily table only — see operation description and #/components/schemas/BatchAnalyzeExportRow. Requires returns in metrics for non-empty data.

Example request

{
  "tickers": [
    "AAPL",
    "MSFT",
    "NVDA",
    "GOOGL"
  ],
  "metrics": [
    "full_metrics"
  ]
}

Response

Batch results as JSON (results map) or as a single Parquet/CSV table of merged daily returns rows across tickers.

resultsobject required

Example response

{
  "_agent": {
    "cost_usd": 0.005,
    "latency_ms": 145,
    "request_id": "req_abc123xyz",
    "confidence": {
      "overall": 0.98
    },
    "data_freshness": "2026-02-21T10:30:00Z",
    "billing_code": "ticker_returns_v2"
  },
  "_metadata": {
    "model_version": "ERM3-L3-v30",
    "data_as_of": "2026-03-17",
    "factor_set_id": "SPY_uni_mc_3000",
    "universe_size": 2987,
    "wiki_uri": "https://riskmodels.app/docs/methodology/erm3-l3",
    "factors": [
      "SPY",
      "XLK",
      "XLF",
      "XLV",
      "XLE",
      "XLI",
      "XLY",
      "XLP",
      "XLU",
      "XLB",
      "XLRE",
      "XLC"
    ],
    "range": [
      "2018-01-02",
      "2026-04-14"
    ]
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.