> ## Documentation Index
> Fetch the complete documentation index at: https://docs.itoflow.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Risk measure

> Measure volatility, tracking error, beta, factor exposures and the names that drive risk for up to ten books, at one date or over up to 60 dates, and split a change in risk into what trades did and what the market did.

A risk measure gives factor risk for 1 to 10 books on one version of the factor model, at one date or over a range of up to 60 dates. You start it, read it until it ends, and stop it if you no longer want it. Send your usual [bearer token](https://docs.itoflow.ai/authentication.md). The credential sets the owner. Do not send an owner in the body. No account flag is needed.

Use a [risk run](https://docs.itoflow.ai/api-reference/risk-runs.md) for today's full report on one book: attribution, profit and loss, scenarios and peers. Use a measure for the figures at past dates, over a range of dates, or for several books side by side.

```mermaid theme={null}
flowchart LR
  Start[Start a measure] --> Read[Read by measure id]
  Read -->|running| Read
  Read -->|ok| Answer[One entry per date]
  Read -->|failed| Code[Failure code and reason]
  Start --> Stop[Stop by measure id]
```

| Operation | Body | Success |
| - | - | - |
| [`POST /risk/measure`](https://docs.itoflow.ai/api-reference/endpoints/startriskmeasure) | `{ request_key, request }` | 202 `{ measure_id, status, joined_existing_measure, dates_total }` |
| [`GET /risk/measure/{measure_id}`](https://docs.itoflow.ai/api-reference/endpoints/getriskmeasure) | None | 200 with the measure's state |
| [`POST /risk/measure/{measure_id}/cancel`](https://docs.itoflow.ai/api-reference/endpoints/cancelriskmeasure) | None | 200 `{ cancelled }` |

## Request

| Field | What it takes |
| - | - |
| `model.as_of` | One date, `YYYY-MM-DD`, not after today in New York. The newest publication on or before it counts. Not with `range`. |
| `model.range` | `{ from, to, step }`: a range of dates, at most 60. `from` is on or before `to`, and `to` is not after today in New York. |
| `model.version` | A factor model version. Left out: the version live when the measure starts. Every date uses it. |
| `benchmark` | A fund, such as `SPY.US`. Each book gets active risk against it. |
| `books` | 1 to 10 books. Each has a `label`, unique in the call and at most 80 characters, and either `portfolio` or `holdings`. |
| `top_names` | How many names to list by their share of each book's risk, 0 to 50. Default 10. |

With neither `as_of` nor `range`, the measure takes the version's newest publication. `step` is `month_end`, the default, or `daily`:

| `step` | Dates |
| - | - |
| `month_end` | The last day of each month in the range, and `to` when it is not one. |
| `daily` | Each weekday in the range. |

Each date reads the newest publication of the version on or before it. Dates that share one read the same books, but each date checks the age of inputs carried forward, so their figures can differ. The answer's `model.dates` says which publications exist: month-end ones from `month_ends_from`, daily ones from `daily_from`. A daily range before `daily_from` reads month-end publications, so use `month_end` for older history.

A saved book is `{ "label", "portfolio": { "portfolio_id", "holdings_at" } }`. It is the portfolio's newest saved snapshot on or before `holdings_at`, a time with its offset. With no `holdings_at`, `as_of` or range, it reads the newest snapshot at the measure's start time. Every book uses that same time. A dated request, including `as_of` today, uses the end of the publication's model date in New York, so a range follows the portfolio as it was saved. `holdings_as_of` may be later than `publication_date` for an undated request.

A book of weights is `{ "label", "holdings": [{ "symbol", "weight" }] }`, with 1 to 3,000 lines. Weights are fractions of the book's net assets; a short is below zero. With a `CASH` line, weights add to 1, within 0.01. Without one, the rest is cash; a book that borrows lists `CASH` below zero. Nothing is rescaled, and a symbol may appear once. A bare ticker is read as a US listing, so `QQQ` becomes `QQQ.US`.

Funds in a book, and the benchmark, are read through their stored holdings on or before each publication's model date.

```json theme={null}
{
  "request_key": "9b0f6f0e-3c1a-4d2e-8f51-6a7c2b9d4e10",
  "request": {
    "model": { "range": { "from": "2025-10-31", "to": "2026-09-30" } },
    "benchmark": "SPY.US",
    "books": [
      { "label": "growth", "holdings": [{ "symbol": "QQQ.US", "weight": 0.9 }, { "symbol": "CASH", "weight": 0.1 }] },
      { "label": "saved", "portfolio": { "portfolio_id": "4f1c2a9e-7b3d-4e8a-9c61-2d5b8f0a6e17" } }
    ]
  }
}
```

Keep one `request_key` for each measure. A retry with the same key and request joins the measure it started. Within 24 hours of its end, a changed request with that key returns 409. After 24 hours, the key starts a new measure.

## What moved risk

To split a change in risk between two dates D0 and D1, start one measure with `model.range` from D0 to D1 and the portfolio twice: `held`, at `holdings_at` D0, and `live`, with `holdings_at` left out. On D1, `live` against `held` is the trades. `held` on D1 against `held` on D0 is the market and the model. With `step: "month_end"`, D0 is on the grid only when it is a month end; otherwise use `daily`.

## Reading a measure

A 30 October 2026 check took about 30 seconds per date for ten books. Time varies with book size and load. Each request covers at most 60 dates; split longer ranges into more requests. Each newly launched background job has a 60-minute budget for all its phases. Initial validation, startup and stopping a timed-out job can add time before a terminal status is available. Jobs already running retain their original deadline. A measure that runs past its budget stops and reads `failed` with `timed_out`. Read `running` again in a few seconds. A running measure may carry `progress: { dates_done, dates_total }`.

An `ok` measure has `answer`:

| Field | What it holds |
| - | - |
| `requested` | The request as the measure read it: defaults filled and symbols written in full. |
| `model` | `version`, `dates` and `base_currency`. |
| `units` | Volatility and tracking error are annual decimals: 0.21 is 21%. Weights are fractions of net assets. Shares are percent of variance. |
| `factors` | Each factor's `id`, `name` and `group`, across every date. Every per-factor list follows this order. |
| `dates` | One entry per date, oldest first. |
| `warnings` | Sentences about gaps across the measure, such as names left out on any date. |
| `provenance` | `classification_inputs` and `factor_count`. |

A date the model has comes back with `status: "ok"`:

| Field | What it holds |
| - | - |
| `as_of` | The date on the grid. |
| `publication_date` | The publication this date read. Dates that share one read the same books, but each date checks the age of inputs carried forward, so their figures can differ. |
| `benchmark` | The benchmark's `symbol`, the date of its stored book in `holdings_as_of`, and its own `coverage`. Null with no benchmark, or when no book for it is stored. |
| `books` | One entry for each book sent, in the same order. |
| `warnings` | Sentences about gaps on this date, such as a book with no active risk. |
| `provenance` | `snapshot_instant`. |

A date with no publication of the version in the 45 days up to it comes back with `status: "missing"`, `reason_code: "model_date_unavailable"`, a `reason`, and `nearest_before` and `nearest_after`: the nearest model dates on each side, or null.

A measured book has `status: "ok"` and these fields:

| Field | What it holds |
| - | - |
| `holdings_as_of` | When the saved snapshot was taken. Null for a book of weights. |
| `exposure` | `long_weight`, `short_weight`, `gross_weight` and `net_weight`. |
| `coverage` | What the model covers. See below. |
| `risk` | `total_volatility`, `factor_share`, `specific_share`, `exposures` and `factor_shares`. Null when no risky name in the book is modelled. |
| `active` | `tracking_error`, `beta`, `modelled_share_of_gross_active`, `factor_share`, `specific_share`, `exposures` and `factor_shares`. Null with no benchmark, when no book for it is stored, or when the book or the benchmark holds no name the model covers; a warning says which. |
| `top_names` | The names with the largest share of the book's risk, with `symbol`, `weight` and `share`. |

`active.modelled_share_of_gross_active` is the share of the gross active weight the model covers, 0 to 1. Gaps in the book and the benchmark net out by name. A null figure was not computed; it does not mean zero.

A book the measure could not read on a date has `status: "omitted"` and a `reason`:

| `reason` | Meaning |
| - | - |
| `no_holdings_at_date` | The portfolio has no saved snapshot on or before `holdings_at`. |
| `holdings_valuation_unknown` | No line has a usable value, or the valued lines have no positive net value. |

## Coverage

`coverage.modelled_share_of_gross` is the share of the book's gross weight the model covers, 0 to 1: not a share of net assets. Cash in the model's currency counts in neither part. `lookthrough_weight` is the gross weight of funds measured through their own holdings. `unmodelled_count` counts every line the model left out. `unmodelled` lists at most 100 lines, each with `symbol`, signed `weight`, `reason_code` and `reason`. Weighted gaps come first, largest by size. A holding with no value has `weight: null`, code `invalid_inputs`, and reason "This holding could not be valued; its risk is excluded." Null means its weight is not known. Unvalued lines sort after all weighted gaps and count in `unmodelled_count`.

A saved book with unvalued lines measures its valued lines. Their weights divide each value by the sum of valued values, cash included and shorts negative. A warning states how many lines have no value and that shares and exposures, including the modelled share of gross, describe the valued lines.

| `reason_code` | Meaning |
| - | - |
| `unmodelled` | The model has no score for the name at this publication. A risk run can add it. |
| `no_equity_listing` | A line with no confirmed equity listing, such as a bill or bond. No risk run can add it. |
| `no_fund_book` | A fund the measure could not look through. |
| `invalid_inputs` | A holding that could not be valued. |
| `not_listed` | A symbol absent from the instrument catalogue, with its unique catalogue match named when known; `symbol` is the caller's symbol. For the part of a fund its source does not list, `symbol` is null. |
| `cash_currency` | Cash in a currency other than the model's. `symbol` is `CASH`. |

The engine may add codes; `reason` says each one in words.

A measure does not add names to the model. To add them, start a risk run on the book: it adds each name it can score to the model's publications from the last two years. Then measure again. A name the run could not add comes back with its reason; `not_added_in_time` means the run stopped waiting first, so run it again later.

## Failed measures

A failed measure returns 200 with `failure_code`, `reason` and `details`:

| `failure_code` | What to do |
| - | - |
| `model_version_not_found` | Leave `model.version` out, or send a version an earlier answer gave. |
| `answer_too_large` | The answer passed 8 MiB; `details` gives `size_bytes` and `limit_bytes`. Split the books or the range across measures. |
| `timed_out` | The measure did not finish within its job budget, 60 minutes for newly launched jobs. Split the books or the range. |
| `cancelled` | You stopped the measure. Start again with a new `request_key`. |
| `engine_fault` | The engine hit an internal error. `details.exception_type` names the failure class; `details.publication_date` gives the selected publication's date when known, or null when it is not available. If a process stops without a fault record, the class is `UnknownError`. The detail contains no exception message. |

Finished answers stay for 24 hours after the measure ends, even after the sandbox sleeps. This store holds the result, a request hash and the end time. Cancelled and timed-out measures have no stored result.

## Limits and errors

A measure counts toward the three runs an owner may have going, with risk runs, while it runs. Responses carry `Cache-Control: no-store`. An error body is `{ "error": { "code", "message" } }`.

| Status | Meaning |
| - | - |
| 400 | `invalid_request` names the field, such as a range over 60 dates or a date after today. |
| 401 | No valid session or bearer token. |
| 404 | `portfolio_not_found` at the start, or `measure_not_found` on a read or stop: the portfolio or measure is missing or not yours. Both share one body. |
| 409 | `risk_measure_request_key_conflict`: the key started a measure of another request. |
| 429 | `risk_engine_busy`: three runs or measures are going. |
| 503 | `risk_engine_unavailable`: the risk engine did not answer, or its answer could not be read. |

429 and 503 carry `Retry-After` in seconds. Call again after it; on 503, retry the start with the same key, which joins the measure it may have started.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.