## `eigenkit.algorithms.portfolio_risk`

### `PortfolioRiskEstimator`

Bases: `BaseQuantumEstimator`

Quantum estimator of portfolio risk.

It estimates the Value at Risk (VaR) and the Conditional Value at Risk (CVaR) of a portfolio. The estimator can use IQAE to estimate the tail probability on a quantum circuit. The oracle for IQAE is built from the fitted return distribution with amplitude encoding.

Note

This is not a standard scikit-learn regressor. The risk at a confidence level comes from `estimate()` and `var_at()`, which take confidence levels and not feature vectors, and the estimator has no meaningful scikit-learn `score()`. It inherits only from BaseQuantumEstimator.

Not financial advice

The estimates come from a model fitted to the price history you pass in, and they carry no guarantee. They are not investment advice.

Parameters:

| Name                       | Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Default                                       |
| -------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `n_qubits`                 | `int`          | The total number of qubits, including the ancilla. The loss distribution is discretised into 2^(n_qubits - 1) bins. The minimum is 2: one data qubit and the ancilla.                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `6`                                           |
| `epsilon`                  | `float`        | The additive precision of the IQAE estimate of the tail probability.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `0.01`                                        |
| `alpha`                    | `float`        | The failure probability of IQAE. The confidence level is 1 − alpha.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `0.05`                                        |
| `horizon_days`             | `int`          | The investment horizon, in trading days, used to scale daily log-returns to the target horizon. The default is 1, which gives daily risk measures.                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `1`                                           |
| `provider`                 | `ProviderSpec` | The backend that IQAE uses: a registered name ("pennylane", "qiskit", "braket" and so on), a config instance, or a backend object. The default is "numpy". Note The Grover operator built internally uses the adjoint of an AmplitudeEncoding block, so the provider must support the adjoint of a state preparation and provide a statevector. The built-in "numpy" simulator and PennyLane both do, and their results agree to the last digit. A backend that does not raises an error when the operator is built. Calling IQAE directly with a CircuitSpec based on QubitUnitary avoids this requirement.                    | `'numpy'`                                     |
| `n_shots`                  | `int`          | The number of shots per IQAE round. The default is 100.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `100`                                         |
| `random_state`             | \`int          | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | The seed that makes the results reproducible. |
| `n_bisection_steps`        | `int`          | The number of binary-search steps used to locate the VaR with quantum estimates of the tail probability. With 0, the default, the VaR and CVaR are computed classically from the fitted bins. A positive value calls \_quantum_var_bisection(), which refines the threshold by running IQAE at each step of the bisection.                                                                                                                                                                                                                                                                                                      | `0`                                           |
| `measure_tail_probability` | `bool`         | When True, the default, estimate() runs IQAE once after the VaR and CVaR are computed, and reports the quantum estimate of the tail in tail_probability and confidence_interval. Set it to False to skip that final IQAE call when only the classical VaR and CVaR are needed and n_bisection_steps=0.                                                                                                                                                                                                                                                                                                                          | `True`                                        |
| `distribution`             | `str`          | The model of the loss distribution that fit() uses. "gaussian" (the default) fits the mean μ and the standard deviation σ of the scaled log-returns, and integrates the Gaussian cumulative distribution function (CDF) over each bin. The distribution is truncated at ±6σ. A VaR beyond that point is capped at the limit, and a RuntimeWarning is issued. "empirical" places each historical return in the nearest bin of equal width, without assuming a parametric form. "t" fits a Student-t distribution by maximum-likelihood estimation, and integrates its CDF over each bin. It has heavier tails than the Gaussian. | `'gaussian'`                                  |

Example

```python
import numpy as np
from eigenkit.algorithms import PortfolioRiskEstimator

rng = np.random.default_rng(0)
returns = rng.normal(0.0005, 0.02, size=(250, 3))
prices = 100 * np.exp(np.cumsum(returns, axis=0))

estimator = PortfolioRiskEstimator(
    n_qubits=5, provider="numpy", random_state=0
)
estimator.fit(prices, weights=np.array([0.5, 0.3, 0.2]))

risk = estimator.estimate(confidence_level=0.95)

print(risk.var)               # 0.0237: at most a 2.37% loss on 95% of days
print(risk.cvar)              # 0.0262: the average loss on the worst 5%
print(risk.tail_probability)  # 0.0659: the IQAE estimate of the tail mass
```

#### `distribution_params`

Summary statistics of the fitted return distribution.

The value is `None` if `fit()` has not been called. Otherwise it always contains `"mu"` (the mean) and `"sigma"` (the standard deviation) of the scaled log-returns, whatever the `distribution` model. For `"t"`, the degrees of freedom are not stored here, because they are an intermediate result of the maximum-likelihood fit and not part of the state of the estimator.

#### `is_fitted`

Whether `fit()` has completed successfully.

#### `estimate(confidence_level=0.95, *, _seed_seq=None)`

Estimate the VaR and CVaR at `confidence_level`.

The VaR and CVaR come from the discretised loss distribution. When `n_bisection_steps > 0`, the VaR is refined by a bisection that uses IQAE. Otherwise, the classical quantiles of the bins are used. Unless `measure_tail_probability=False`, a final IQAE run estimates `P(loss ≥ VaR)`, for checking and diagnostics.

Parameters:

| Name               | Type           | Description                                                                                          | Default                                                                                                                                                                                                        |
| ------------------ | -------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `confidence_level` | `float`        | The probability level of the risk measures, for example 0.95 for the 95 % VaR. It must be in (0, 1). | `0.95`                                                                                                                                                                                                         |
| `_seed_seq`        | \`SeedSequence | None\`                                                                                               | A private override of the random seed, used by var_at() so that the shot noise of the IQAE runs for different confidence levels is independent. The default is None, which derives the seed from random_state. |

Returns:

| Type                  | Description            |
| --------------------- | ---------------------- |
| `PortfolioRiskResult` | A PortfolioRiskResult. |

Raises:

| Type           | Description                                                                      |
| -------------- | -------------------------------------------------------------------------------- |
| `RuntimeError` | If fit() has not been called.                                                    |
| `ValueError`   | If the selected provider cannot compile the oracle for the tail that IQAE needs. |

#### `fit(X, y=None, *, weights=None)`

Fit the return distribution to historical price data.

Parameters:

| Name      | Type      | Description                                                                                                                                                        | Default                                                                                                           |
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `X`       | `ndarray` | A 2-D array of shape (T, n_assets) with daily closing prices, where T must be at least 2. A 1-D array of length T is accepted for a portfolio with a single asset. | *required*                                                                                                        |
| `y`       | \`ndarray | None\`                                                                                                                                                             | Ignored, because this estimator is unsupervised. It is present for compatibility with the scikit-learn interface. |
| `weights` | \`ndarray | None\`                                                                                                                                                             | A 1-D array of portfolio weights that sum to 1. If it is not given, all assets have equal weights.                |

Raises:

| Type         | Description                                                                                                                            |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If distribution is not one of the supported values. This can happen when distribution is changed with set_params() after construction. |

#### `predict(X)`

Not supported. Use `var_at()` instead.

The estimator base class requires a `predict` method. In scikit-learn, `predict` takes samples from the same feature space that `fit` received, but the risk at a confidence level is a different question, which `var_at()` answers.

Parameters:

| Name | Type      | Description | Default    |
| ---- | --------- | ----------- | ---------- |
| `X`  | `ndarray` | Ignored.    | *required* |

Raises:

| Type                  | Description                             |
| --------------------- | --------------------------------------- |
| `NotImplementedError` | Always. The message points to var_at(). |

#### `var_at(confidence_levels)`

Return the VaR at each confidence level.

This is the array version of `estimate()`. It runs IQAE once for each level and returns only the `var` of each result.

Parameters:

| Name                | Type      | Description                                                                                                                                      | Default    |
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `confidence_levels` | `ndarray` | A 1-D array of confidence levels in (0, 1), for example [0.95, 0.99]. A scalar or an (n, 1) column is also accepted, and it is read as n levels. | *required* |

Returns:

| Type      | Description                                                                                 |
| --------- | ------------------------------------------------------------------------------------------- |
| `ndarray` | An array of VaR values, as fractions of the portfolio value lost, with one value per level. |

Raises:

| Type             | Description                                                   |
| ---------------- | ------------------------------------------------------------- |
| `ValueError`     | If the argument is not 1-D after a single column is squeezed. |
| `NotFittedError` | If fit() has not been called.                                 |

Warns:

| Type          | Description                                                                                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `UserWarning` | If any level is below 0.5. Such a level is valid, but values below 0.5 are also what a price series or a feature matrix looks like, and this warning catches the mistake of passing one of those. |

Warning

Each level runs IQAE once, so `n` levels need `n` separate quantum circuits. For a single risk measure, call `estimate()` instead.

Example

```python
estimator.var_at([0.90, 0.95, 0.99])
```

### `PortfolioRiskResult`

Bases: `ValueEquality`

Outcome of a PortfolioRiskEstimator run.

The result is read-only. Assigning to a field raises `FrozenInstanceError`, so a mistyped `=` (instead of `==`) cannot change a result. This type holds no arrays, so nothing else needs protecting.

Results compare by value. They are not hashable, so that all result types behave in the same way: the types that hold arrays cannot be hashed both correctly and efficiently.

Attributes:

| Name                  | Type                  | Description                                                                                                                                   |
| --------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `var`                 | `float`               | The Value at Risk (VaR) at the requested confidence_level, as a fraction of the portfolio value lost. For example, 0.12 means a loss of 12 %. |
| `cvar`                | `float`               | The Conditional Value at Risk (CVaR), also called Expected Shortfall, at the same confidence level.                                           |
| `tail_probability`    | `float`               | The IQAE point estimate of P(loss ≥ VaR).                                                                                                     |
| `confidence_interval` | `tuple[float, float]` | The (lower, upper) Clopper-Pearson interval for the tail probability, at level 1 - alpha.                                                     |
| `iqae_result`         | \`IQAEResult          | None\`                                                                                                                                        |
