{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/riskmodels-app/json-schema/riskmodels-app-ticker-returns-response-v3-schema.json",
  "title": "TickerReturnsResponseV3",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/riskmodels-app-openapi.json#/components/schemas/TickerReturnsResponseV3",
  "type": "object",
  "required": [
    "symbol",
    "ticker",
    "periodicity",
    "data",
    "meta"
  ],
  "properties": {
    "symbol": {
      "type": "string"
    },
    "ticker": {
      "type": "string"
    },
    "asset_type": {
      "type": "string",
      "enum": [
        "stock",
        "etf"
      ],
      "description": "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.\n"
    },
    "resolved_via": {
      "type": "string",
      "enum": [
        "most_recent_holder",
        "share_class_sibling"
      ],
      "description": "`share_class_sibling`: the requested share class has no modelled series and another class of the same company answers it (e.g. `GOOGL` answered with `GOOG`'s series, named in `modelled_ticker`); returns and hedge ratios are the sibling's and `price_close` is omitted (`price_withheld: true`). `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.\n"
    },
    "requested_ticker": {
      "type": "string",
      "description": "With `resolved_via: share_class_sibling`: the ticker requested (e.g. `GOOGL`).\n"
    },
    "price_withheld": {
      "type": "boolean",
      "description": "With `resolved_via: share_class_sibling`: `true`; price-bearing fields (close, market cap) are omitted or null because the sibling's price is not the requested class's price.\n"
    },
    "modelled_ticker": {
      "type": [
        "string",
        "null"
      ],
      "description": "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.\n"
    },
    "delisted": {
      "type": "boolean",
      "description": "`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.\n"
    },
    "ticker_reassigned": {
      "type": "boolean",
      "description": "`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.\n"
    },
    "current_holder": {
      "type": "object",
      "description": "With `ticker_reassigned`, the security holding the ticker today.",
      "properties": {
        "symbol": {
          "type": "string"
        },
        "name": {
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "resolution_degraded": {
      "type": "boolean",
      "description": "`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.\n"
    },
    "history_starts": {
      "type": "string",
      "format": "date",
      "description": "Present only on an empty response when the requested window ends before this security's earliest non-null daily close or return observation. Rows before this date are omitted.\n"
    },
    "_metadata": {
      "type": "object",
      "additionalProperties": true,
      "description": "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`.\n",
      "properties": {
        "data_warning": {
          "type": "string",
          "description": "Human-readable reason the series is empty."
        },
        "data_warning_code": {
          "type": "string",
          "enum": [
            "etf_outside_priced_universe",
            "etf_no_return_history_in_window",
            "no_return_history_in_window"
          ],
          "description": "`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.\n"
        },
        "see": {
          "type": "string",
          "description": "ETFs only — the endpoint that lists the fund's capabilities."
        }
      }
    },
    "periodicity": {
      "type": "string"
    },
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/TickerReturnsDailyRow"
      }
    },
    "meta": {
      "$ref": "#/$defs/TickerReturnsMeta"
    }
  },
  "$defs": {
    "TickerReturnsDailyRow": {
      "type": "object",
      "description": "One trading day in `GET /ticker-returns` `data` array. Stock rows populate the full V3 security_history field set below. **ETF rows are a strict subset — only `date`, `returns_gross`, and `price_close` are emitted** (the L1/L2/L3 hedge/ER properties are omitted entirely from ETF rows rather than returned as `null`, since ETFs are not factor-decomposed). Branch on the response's top-level `asset_type` before reading L* fields.\n",
      "properties": {
        "date": {
          "type": "string",
          "format": "date"
        },
        "returns_gross": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "Daily gross return of the stock. Null when rolling-window metrics are unavailable (trailing data)."
        },
        "price_close": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "End-of-day price when available from history."
        },
        "l3_mkt_hr": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "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.\n"
        },
        "l3_sec_hr": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "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.\n"
        },
        "l3_sub_hr": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "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.\n"
        },
        "l3_mkt_er": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "Market variance share at L3 (explained risk, 0–1). Null in trailing rows where the rolling window has insufficient data.\n"
        },
        "l3_sec_er": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "Sector variance share at L3 (explained risk, 0–1). Null when the rolling window has insufficient data.\n"
        },
        "l3_sub_er": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "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.\n"
        },
        "l3_res_er": {
          "type": [
            "number",
            "null"
          ],
          "format": "float",
          "description": "Idiosyncratic (residual) variance share at L3 (0–1); SDK semantic name `l3_residual_er`. Null when the rolling window has insufficient data.\n"
        }
      }
    },
    "TickerReturnsMeta": {
      "type": "object",
      "properties": {
        "market_etf": {
          "type": "string"
        },
        "sector_etf": {
          "type": [
            "string",
            "null"
          ],
          "description": "Null for ETF tickers (e.g. `SPY` itself)."
        },
        "subsector_etf": {
          "type": [
            "string",
            "null"
          ]
        },
        "universe": {
          "type": "string",
          "description": "`US_EQUITY` for stocks, `US_ETF` for ETFs."
        }
      }
    }
  }
}