Skip to content

portfolio_risk

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.

42
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
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 property

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 property

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.

None

Returns:

Type Description
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.

None
weights ndarray | None

A 1-D array of portfolio weights that sum to 1. If it is not given, all assets have equal weights.

None

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
estimator.var_at([0.90, 0.95, 0.99])

PortfolioRiskResult dataclass

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

The full IQAEResult when measure_tail_probability=True, and None otherwise.