---
title: "Daily returns time series with rolling hedge ratios (stocks and ETFs)"
method: GET
path: "/ticker-returns"
tags: ["Risk Metrics"]
---

# Daily returns time series with rolling hedge ratios (stocks and ETFs)

`GET /ticker-returns`

## Request

- Base URL: `https://riskmodels.app/api`
- URL: `https://riskmodels.app/api/ticker-returns`
- Auth: HTTP bearer

Returns a daily time series of gross returns (`returns_gross`) and, for stocks, V3 rolling hedge ratios and explained-risk fields per day: `l3_mkt_hr`, `l3_sec_hr`, `l3_sub_hr`, `l3_mkt_er`, `l3_sec_er`, `l3_sub_er`, `l3_res_er`. Up to 15 years per request. **ETFs are supported**: requests for ETF tickers (e.g. `SPY`, `XLK`) return `date`, `returns_gross`, and `price_close` sourced from `ds_etf.zarr`; L1/L2/L3 columns are `null` because ETFs are not factor-decomposed. The response includes `asset_type` ("stock" or "etf") so clients can branch cleanly. For the full L1/L2/L3 **snapshot** (all six HR names on the latest day), use `GET /metrics/{ticker}` (`metrics` object). Response does not include a top-level `_agent` block; billing is applied server-side. Cost: $0.02 for 1 year + $0.01 per extra year (up to 15). A response with no rows is not billed; `_metadata.data_warning_code` names the reason (`etf_outside_priced_universe`, `etf_no_return_history_in_window` or `no_return_history_in_window`). Only ETFs with the `priced` capability return rows — see `GET /data/etf/universe`.

## Query parameters

- `ticker` string, required
- `years` integer
- `limit` integer
- `nocache` boolean
- `format` 'json' | 'parquet' | 'csv'

## Response `200`

Time series of daily returns, `price_close`, and V3 rolling HR/ER fields. JSON wraps rows in `data` with `symbol`, `ticker`, `meta`, `_metadata`. Parquet/CSV are a single table: one row per trading day; columns match `#/components/schemas/TickerReturnsDailyRow` in a stable key order (same field names as JSON object keys in each `data` element).

- TickerReturnsResponseV3
  - `symbol` string, required
  - `ticker` string, required
  - `asset_type` 'stock' | 'etf': Returned for every response. ETF responses populate `date`, `returns_gross`, and `price_close` only; all L1/L2/L3 columns are `null` because ETFs are not factor-decomposed.
  - `resolved_via` 'most_recent_holder': Present only when the modelled registry has no live row for the requested ticker and the most-recent-holder check chose the answer: the security that holds the ticker today (it may have a registry row under another ticker, then disclosed in `modelled_ticker`) or, failing that, the one that held it most recently (e.g. the delisted `CTRA`). Tickers with a live registry row, including recycled ones, resolve to today's company and omit this field, as do answers unchanged from earlier releases (an alias rename such as `FB` → META, or a delisted registry row) unless a live holder of the ticker that the API can serve exists.
  - `modelled_ticker` string, nullable: With `resolved_via`: the answering security's own ticker (registry or security master) when it differs from the requested ticker, e.g. `BYON` for a `BBBY` request answered by a listing pin.
  - `delisted` boolean: `true` when the answering security is closed in the security master or flagged delisted in the registry; `false` when the most-recent-holder pass found it open. Absent on ordinary answers and on answers kept unchanged from earlier releases whose registry row is not flagged delisted.
  - `ticker_reassigned` boolean: `true` when the security master shows a different security holding the ticker today that the API cannot serve (no registry row under the ticker, no daily history), so the answer is still the previous holder's series (e.g. `FB` keeps answering Meta while another security holds FB). Absent otherwise.
  - `current_holder` object: With `ticker_reassigned`, the security holding the ticker today.
    - `symbol` string
    - `name` string, nullable
  - `resolution_degraded` boolean: `true` when the most-recent-holder check could not read its evidence (security master, registry or daily index outage) and the previous answer was returned unchecked; a later request may answer a different security. Absent otherwise.
  - `_metadata` object: Lineage block (model_version, data_as_of, factor_set_id, range, history_row_count, ...). When `data` is empty the response is not billed and carries `data_warning`, `data_warning_code` and, for ETFs, `see`.
    - `data_warning` string: Human-readable reason the series is empty.
    - `data_warning_code` 'etf_outside_priced_universe' | 'etf_no_return_history_in_window' | 'no_return_history_in_window': `etf_outside_priced_universe` — a known ETF with no daily return series (see `/data/etf/{ticker}` capabilities); `etf_no_return_history_in_window` — priced ETF with no rows in the requested window; `no_return_history_in_window` — a stock with no rows in the window.
    - `see` string: ETFs only — the endpoint that lists the fund's capabilities.
  - `periodicity` string, required
  - `data` TickerReturnsDailyRow[], required
    - `date` string, date
    - `returns_gross` number, float, nullable: Daily gross return of the stock. Null when rolling-window metrics are unavailable (trailing data).
    - `price_close` number, float, nullable: End-of-day price when available from history.
    - `l3_mkt_hr` number, float, nullable: Rolling L3 market hedge ratio (SPY notional per $1 stock). Null in trailing rows where the rolling regression window has insufficient data. May be negative (orthogonalization); negative market HR at L2/L3 is common.
    - `l3_sec_hr` number, float, nullable: Rolling L3 sector ETF hedge ratio (dollar_ratio). Null when the rolling window has insufficient data. May be negative due to orthogonalization (factor neutralization); not a sign error in the data.
    - `l3_sub_hr` number, float, nullable: Rolling L3 subsector ETF hedge ratio (dollar_ratio). Null when the rolling window has insufficient data. May be negative (orthogonalization or long subsector ETF leg); not a sign error in the data.
    - `l3_mkt_er` number, float, nullable: Market variance share at L3 (explained risk, 0–1). Null in trailing rows where the rolling window has insufficient data.
    - `l3_sec_er` number, float, nullable: Sector variance share at L3 (explained risk, 0–1). Null when the rolling window has insufficient data.
    - `l3_sub_er` number, float, nullable: Subsector variance share at L3 (explained risk). Usually 0–1, but can be slightly negative: see the note on incremental shares under `/batch/analyze`. Null when the rolling window has insufficient data.
    - `l3_res_er` number, float, nullable: Idiosyncratic (residual) variance share at L3 (0–1); SDK semantic name `l3_residual_er`. Null when the rolling window has insufficient data.
  - `meta` TickerReturnsMeta, required
    - `market_etf` string
    - `sector_etf` string, nullable: Null for ETF tickers (e.g. `SPY` itself).
    - `subsector_etf` string, nullable
    - `universe` string: `US_EQUITY` for stocks, `US_ETF` for ETFs.

## Other responses

- `401`: Missing or invalid Bearer token.
- `402`: Insufficient balance.
- `404`: Ticker not found.
- `429`: Rate limit exceeded.

---

[API](https://skmtc.dev/riskmodels/apis/riskmodels-api.md) · [All operations](https://skmtc.dev/riskmodels/apis/riskmodels-api/llms.txt) · [OpenAPI document](https://skmtc.dev/riskmodels/apis/riskmodels-api/revisions/1a034cad408a?raw)
