---
title: "Get portfolio risk assessment"
method: GET
path: "/agent/portfolio-risk"
---

# Get portfolio risk assessment

`GET /agent/portfolio-risk`

## Request

- Base URL: `https://app.fahaliai.com`
- URL: `https://app.fahaliai.com/agent/portfolio-risk`
- Auth: none declared

Returns overall risk score, value-at-risk, expected drawdown, per-position risk breakdown, and contagion risk. Requires connected exchange accounts for personalized data.

## Response `200`

Portfolio risk assessment

- SpectraPortfolioRiskAssessment
  - `portfolio` object, required
    - `totalValue` number, required
    - `overallRiskScore` number, required
    - `overallRiskLevel` 'critical' | 'high' | 'moderate' | 'low', required
    - `expectedDrawdownPct` number, nullable, required: Expected shortfall at 95%: the mean loss across the worst 5% of observations in the window, percent, positive for a loss. Historical measurement over the book's own return distribution, NOT a forecast. Null when unmeasurable — read riskMetricsBasis.reason.
    - `valueAtRisk95` number, nullable, required: Historical Value-at-Risk at 95% in currency, positive for a loss: on the worst 1 day in 20 of the window, the book lost at least this much. Historical measurement, NOT a forecast. Null when unmeasurable.
    - `expectedShortfall95` number, nullable: Expected shortfall at 95% in currency, positive for a loss: the mean loss across the worst 5% of observations. Reported in dollars as well as percent because the percent is rounded at 0.01pp, which is $50,000 on a $500M book. Null when unmeasurable.
    - `expectedShortfallAttribution` object, nullable: Which holdings the reported expected shortfall is attributable to, or null whenever the figures above are null. Contributions are SIGNED: a hedge that pays in the tail reports a negative number. componentSumDollars equals the measured expected shortfall exactly (the decomposition is linear once the tail set is fixed), and roundingResidualDollars reconciles the rounded components to the published dollar figure.
      - `method` 'expected_shortfall_linear_decomposition'
      - `tailObservations` integer
      - `tailSetBasis` 'worst_n_observations': Ties are broken by row order, so the same series always yields the same tail set.
      - `components` object[]
        - `symbol` string
        - `weightPct` number: Signed weight as a percent of GROSS covered exposure; a short is negative.
        - `contributionDollars` number: Positive adds to the tail loss, negative offsets it.
        - `contributionPctOfGross` number
      - `componentSumDollars` number: Sum of the components before rounding. Equals the measured expected shortfall exactly, not approximately.
      - `reportedExpectedShortfall95` number
      - `roundingResidualDollars` number: Expected shortfall minus the sum of the ROUNDED components: stated rather than hidden, so a consumer adding the components up knows what the cents do.
      - `covers` 'covered_book_only'
      - `uncoveredSymbols` string[]: Holdings with no usable history, so they have no attribution. Their contribution is unknown, not zero.
    - `varBacktest` object, nullable: Out-of-sample coverage test of the VaR quantile: did the worst 1 day in 20 happen about 1 day in 20, and did the misses cluster? Each tested day's VaR is estimated from strictly prior observations, so a day is never scored against a distribution containing it. 'not_rejected' means the sample did not contradict the model - NEVER that the model is correct, and never that the risk figure is validated. Status 'insufficient_data' means the sample could not support the test at all.
      - `status` 'measured' | 'insufficient_data'
      - `reason` string: Why the test was not run, when status is insufficient_data.
      - `method` 'rolling_historical_simulation'
      - `window` integer: Observations used to estimate each tested day's VaR.
      - `significanceLevel` number
      - `observations` integer: Out-of-sample days tested (or testable, when insufficient_data).
      - `covers` 'quantile_violations_only': These tests cover the VaR quantile only - they say nothing about expected shortfall.
      - `violations` integer
      - `expectedViolations` number
      - `violationRatePct` number
      - `kupiecLR` number
      - `christoffersenLR` number, nullable: Null when independence is not identifiable (no hits at all, or a hit on every day) - reported as null rather than invented.
      - `conditionalLR` number, nullable
      - `pValueUnconditional` number: Raw, unrounded, so the number can never print as exactly the threshold it was compared against.
      - `pValueIndependence` number, nullable
      - `pValueConditional` number, nullable
      - `unconditionalCoverage` 'not_rejected' | 'rejected'
      - `independence` 'not_rejected' | 'rejected' | 'not_testable'
      - `conditionalCoverage` 'not_rejected' | 'rejected' | 'not_testable'
      - `detectableDeviationPp` number: Smallest violation rate this sample could distinguish from the expected rate at 95% confidence, in percentage points. A short sample cannot falsify a model, and silence about that reads as approval.
      - `observationsForPower80AtDoubleRate` integer: Out-of-sample days needed to detect a DOUBLING of the violation rate (5% to 10%) at 80% power.
    - `riskMetricsBasis` object: How the drawdown and VaR figures were produced, or why they are absent.
      - `method` 'historical_simulation' | 'unmeasured', required
      - `reason` string
      - `observations` integer
      - `coveredWeightPct` number: Share of book weight with usable price history. The estimate treats this subset as representative; below 60% it abstains.
      - `uncoveredSymbols` string[]
      - `uncoveredWeightPct` number: Share of GROSS exposure with no usable history, stated explicitly so a coverage gap is never inferred from rounding.
      - `zeroWeightSymbols` string[]: Symbols with no history AND zero weight - listed apart from real blind spots because they cannot move the estimate.
      - `shortSymbols` string[]: Symbols carrying a negative weight. DISCLOSURE: signed books are measured on a gross-exposure basis, so a hedge reduces VaR rather than being excluded.
      - `netWeightPct` number: Signed net exposure of the measured book as a percent of its gross exposure, -100 to +100. 0 means fully hedged.
      - `exposureBasis` 'gross': The denominator the return series was normalised on. Always gross (sum of absolute weights): VaR as a percentage is meaningless without it.
      - `varLevel` number
      - `positionAligned` boolean: True when series were aligned by position rather than bar timestamp - a weaker basis.
  - `positions` object[], required
    - `symbol` string, required
    - `allocation` number, required
    - `riskScore` number, required
    - `riskLevel` 'critical' | 'high' | 'moderate' | 'low', required
    - `unrealizedPnlPct` number, required: P&L since entry, percent. Historical and already realized — NOT a forecast. Renamed 2026-08-06 from `expectedMovePct`, a forward-looking name that misrepresented this backward-looking figure.
    - `correlationToPortfolio` number, nullable, required: Not computed. Always null — never fabricated (2026-07-20).
    - `recommendation` object, required
      - `action` 'hold' | 'reduce' | 'increase' | 'exit' | 'hedge' | 'watch', required
      - `rationale` string, required
  - `analysisStatus` object, required: Reconciliation between the VaR/drawdown coverage figure and the uncovered-symbol list. PARTIAL when coveredWeightPct reads near-complete while symbols still lack price history, or when the risk metrics are unmeasured outright.
    - `status` 'OK' | 'PARTIAL', required
    - `reason` string
  - `contagionRisk` object, required
    - `highestRiskSymbol` string, required
    - `affectedCount` integer, required
    - `expectedPortfolioImpactPct` number, nullable, required: Magnitude of the estimated portfolio move, percent, measured as a beta-weighted response to the shock asset's own empirical lower-tail return. Null when it cannot be measured for this book — read basis.reason. Never 0 as a stand-in for unknown.
    - `basis` object: How the estimate was produced, or why it is absent.
      - `method` 'beta_weighted_empirical_tail' | 'unmeasured', required
      - `reason` string
      - `shockSymbol` string
      - `shockPct` number, nullable: Measured lower-tail move of the shock asset, percent.
      - `portfolioMovePct` number, nullable: Signed portfolio move. Negative is adverse; positive means the book hedges the shock.
      - `portfolioBeta` number, nullable
      - `coveredWeightPct` number
      - `tailPercentile` number
  - `provenance` SpectraProvenance, required
    - `source` 'predictions' | 'alerts' | 'agent_verdict' | 'derived', required
    - `modelVersion` string, required
    - `engineNames` string[], required
    - `walkForwardValidated` boolean, required
    - `dataAsOf` string, date-time, required
  - `aiMetadata` SpectraAiMetadata, required
    - `schemaVersion` '1.0', required
    - `reasoningTrace` boolean, required
    - `confidenceNormalized` boolean, required
    - `schemaStable` boolean, required
    - `lastModelRetrain` string, date, nullable

## Changes

- **2026-10-19** `d8115da5453f`: 2 info
  - added the optional property `portfolio/expectedShortfallAttribution` to the response with the `200` status
  - added the optional property `portfolio/varBacktest` to the response with the `200` status

[Operation history](https://skmtc.dev/fahaliai/apis/fahali-intelligence-api/changes/agent/portfolio-risk/get.md)

---

[API](https://skmtc.dev/fahaliai/apis/fahali-intelligence-api.md) · [All operations](https://skmtc.dev/fahaliai/apis/fahali-intelligence-api/llms.txt) · [OpenAPI document](https://skmtc.dev/fahaliai/apis/fahali-intelligence-api/revisions/d8115da5453f?raw)
