Stress Testing Utilities

The pyvallocation.stress module offers user-friendly wrappers around the single-period scenario engine so allocations can be evaluated under alternative probability measures or transformed scenarios.

Highlights

The <no title> notebook demonstrates these functions alongside the pyvallocation.utils.performance helpers.

Reference

Scenario-based stress testing helpers.

pyvallocation.stress.entropy_pooling_stress(weights, scenarios, *, posterior_probabilities, probabilities=None, confidence=0.95, demean=False)[source]

Stress test using posterior probabilities produced by entropy pooling.

Parameters:
  • weights, scenarios – See stress_test().

  • posterior_probabilities – Probability vector returned by pyvallocation.views.entropy_pooling() or pyvallocation.views.FlexibleViewsProcessor.

  • probabilities – Nominal probabilities p. Defaults to uniform.

  • confidence, demean – Risk settings forwarded to stress_test().

Returns:

pd.DataFrame – Tidy comparison between nominal and stressed metrics including KL divergence between p* and p.

Parameters:
  • weights (numpy.ndarray | pandas.Series | pandas.DataFrame | Mapping[str, float])

  • scenarios (numpy.ndarray | pandas.DataFrame | pandas.Series)

  • posterior_probabilities (numpy.ndarray | pandas.Series | Sequence[float])

  • probabilities (numpy.ndarray | pandas.Series | Sequence[float] | None)

  • confidence (float)

  • demean (bool)

Return type:

pandas.DataFrame

Examples

>>> import numpy as np
>>> from pyvallocation.stress import entropy_pooling_stress
>>> scenarios = np.array([[0.01, -0.02], [0.02, 0.01], [-0.03, 0.00]])
>>> posterior = np.array([0.10, 0.70, 0.20])  # output of entropy_pooling
>>> df = entropy_pooling_stress([0.4, 0.6], scenarios, posterior_probabilities=posterior)
pyvallocation.stress.exp_decay_stress(weights, scenarios, *, probabilities=None, half_life=60, confidence=0.95, demean=False)[source]

Historical-simulation stress with exponential decay weights.

The helper builds stressed probabilities using pyvallocation.probabilities.generate_exp_decay_probabilities() and passes them to stress_test().

Parameters:
  • weights, scenarios – See stress_test().

  • probabilities – Nominal scenario probabilities p. Defaults to uniform.

  • half_life – Half-life (in observations) of the exponential decay kernel. Defaults to 60.

  • confidence, demean – Risk settings forwarded to stress_test().

Returns:

pd.DataFrame – Tidy comparison between nominal and stressed metrics.

Parameters:
  • weights (numpy.ndarray | pandas.Series | pandas.DataFrame | Mapping[str, float])

  • scenarios (numpy.ndarray | pandas.DataFrame | pandas.Series)

  • probabilities (numpy.ndarray | pandas.Series | Sequence[float] | None)

  • half_life (int)

  • confidence (float)

  • demean (bool)

Return type:

pandas.DataFrame

Examples

>>> import numpy as np
>>> from pyvallocation.stress import exp_decay_stress
>>> weights = np.array([0.5, 0.5])
>>> scenarios = np.array([[0.01, 0.00], [-0.02, 0.03], [0.015, -0.01]])
>>> df = exp_decay_stress(weights, scenarios, half_life=2)
pyvallocation.stress.kernel_focus_stress(weights, scenarios, *, focus_series, probabilities=None, bandwidth=None, target=None, confidence=0.95, demean=False)[source]

Gaussian-kernel stress that focuses on a state variable.

Parameters:
  • weights, scenarios – See stress_test().

  • focus_series – One-dimensional feature (e.g., realised volatility) with length equal to the number of scenarios.

  • probabilities – Nominal probabilities p (defaults to uniform).

  • bandwidth – Optional kernel bandwidth h supplied to pyvallocation.probabilities.generate_gaussian_kernel_probabilities().

  • target – Target state x_T around which probability mass is concentrated. When omitted, the last observation is used.

  • confidence, demean – Risk settings forwarded to stress_test().

Returns:

pd.DataFrame – Tidy comparison between nominal and stressed metrics.

Parameters:
  • weights (numpy.ndarray | pandas.Series | pandas.DataFrame | Mapping[str, float])

  • scenarios (numpy.ndarray | pandas.DataFrame | pandas.Series)

  • focus_series (numpy.ndarray | pandas.DataFrame | pandas.Series)

  • probabilities (numpy.ndarray | pandas.Series | Sequence[float] | None)

  • bandwidth (float | None)

  • target (float | None)

  • confidence (float)

  • demean (bool)

Return type:

pandas.DataFrame

Examples

>>> import numpy as np
>>> from pyvallocation.stress import kernel_focus_stress
>>> returns = np.array([[0.01, 0.00], [-0.02, 0.03], [0.015, -0.01]])
>>> vol_proxy = np.array([0.10, 0.15, 0.30])  # e.g. rolling volatility
>>> df = kernel_focus_stress([0.5, 0.5], returns, focus_series=vol_proxy, target=0.30)
pyvallocation.stress.linear_map(*, mean_shift=None, scale=None, matrix=None)[source]

Build a linear scenario transform R -> R @ matrix.T * scale + mean_shift.

Parameters:
  • mean_shift – Optional vector added to every scenario (shape (N,)).

  • scale – Scalar multiplier applied after the optional matrix projection.

  • matrix – Optional matrix B with shape (N_out, N_in). When provided, scenarios are multiplied on the right by B^T.

Returns:

Callable[[np.ndarray], np.ndarray] – Function that accepts a scenario matrix and returns a transformed copy.

Parameters:
  • mean_shift (numpy.ndarray | pandas.DataFrame | pandas.Series | None)

  • scale (float | None)

  • matrix (numpy.ndarray | None)

Return type:

Callable[[numpy.ndarray], numpy.ndarray]

Examples

>>> import numpy as np
>>> from pyvallocation.stress import linear_map
>>> R = np.array([[0.01, 0.00], [-0.02, 0.03]])
>>> transform = linear_map(mean_shift=np.array([0.0, -0.01]), scale=1.2)
pyvallocation.stress.stress_invariants(invariants, weights, reprice, *, stress_views=None, horizon=1, n_simulations=5000, seed=None, confidence=0.95, demean=False)[source]

Stress test at the invariant level through the full Prayer pipeline.

Applies stress views to the risk-driver distribution via entropy pooling, projects to horizon, reprices to P&L, and compares nominal vs stressed risk metrics. This is the correct way to answer questions like “what happens to my portfolio if HY spreads widen by 200 bp?” — the stress is applied to invariants, not directly to P&L.

Parameters:
  • invariants – Historical risk-driver scenarios (T, K).

  • weights – Portfolio weights (N,) or Series.

  • reprice – Repricing specification — a callable or dict, same format as from_invariants().

  • stress_views – Dict of views on the invariants, passed as mean_views to FlexibleViewsProcessor. Example: {"credit_spread": at_least(0.02)}.

  • horizon – Investment horizon in invariant time steps.

  • n_simulations – Number of projected scenarios.

  • seed – Random seed for reproducibility.

  • confidence – VaR/CVaR confidence level (default 0.95).

  • demean – Demean P&L for VaR/CVaR computation.

Returns:

pd.DataFrame – Nominal and stressed metrics (return, stdev, VaR, CVaR, ENS).

Parameters:
  • invariants (numpy.ndarray | pandas.DataFrame | pandas.Series)

  • weights (numpy.ndarray | pandas.Series | pandas.DataFrame | Mapping[str, float])

  • reprice (Callable | dict)

  • stress_views (dict | None)

  • horizon (int)

  • n_simulations (int)

  • seed (int | None)

  • confidence (float)

  • demean (bool)

Return type:

pandas.DataFrame

pyvallocation.stress.stress_test(weights, scenarios, *, probabilities=None, stressed_probabilities=None, transform=None, confidence=0.95, demean=False)[source]

Evaluate allocations under nominal and stressed conditions.

Parameters:
  • weights – Portfolio weights (Series, DataFrame, NumPy array, or mapping). Multiple portfolios can be evaluated at once by passing a 2-D array/DataFrame.

  • scenarios – Scenario matrix R with shape (T, N) (rows = observations, columns = assets). Accepts pandas objects or ndarrays.

  • probabilities – Optional nominal scenario probabilities p. Defaults to a uniform distribution across scenarios.

  • stressed_probabilities – Optional stressed probabilities p* on the same scenario grid. When supplied alongside probabilities the KL divergence KL(p* || p) is reported.

  • transform – Callable applied to R (after conversion to float) to obtain stressed scenarios (e.g., shocks, factor projections).

  • confidence – Confidence level for VaR/CVaR metrics (default 0.95). E.g. 0.95 means 5% tail CVaR. Risk is reported as a positive loss.

  • demean – When True the scenario P&L used for VaR/CVaR is demeaned by the respective probabilities.

Returns:

pd.DataFrame – Tidy DataFrame indexed by portfolio label containing nominal metrics, stressed metrics (when supplied), effective number of scenarios, and optional KL divergence.

Parameters:
  • weights (numpy.ndarray | pandas.Series | pandas.DataFrame | Mapping[str, float])

  • scenarios (numpy.ndarray | pandas.DataFrame | pandas.Series)

  • probabilities (numpy.ndarray | pandas.Series | Sequence[float] | None)

  • stressed_probabilities (numpy.ndarray | pandas.Series | Sequence[float] | None)

  • transform (Callable[[numpy.ndarray], numpy.ndarray] | None)

  • confidence (float)

  • demean (bool)

Return type:

pandas.DataFrame

Examples

>>> import numpy as np
>>> from pyvallocation.stress import stress_test, linear_map
>>> weights = np.array([0.6, 0.4])
>>> scenarios = np.array([[0.01, 0.02], [0.00, 0.01], [-0.02, 0.00]])
>>> shock = linear_map(scale=1.5)  # magnify returns by 50%
>>> df = stress_test(weights, scenarios, transform=shock)