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
pyvallocation.stress.stress_test()- master entry point combining probability tilts and linear scenario transformations, returning tidy DataFrames.pyvallocation.stress.exp_decay_stress()- historical half-life stress out of the box.pyvallocation.stress.kernel_focus_stress()- Gaussian-kernel focusing on a target regime.pyvallocation.stress.entropy_pooling_stress()- plug posterior probabilities from entropy pooling.pyvallocation.stress.linear_map()- helper to build mean/scale/factor shocks.
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()orpyvallocation.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*andp.- 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 tostress_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
hsupplied topyvallocation.probabilities.generate_gaussian_kernel_probabilities().target – Target state
x_Taround 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
Bwith shape(N_out, N_in). When provided, scenarios are multiplied on the right byB^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_viewstoFlexibleViewsProcessor. 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
Rwith 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 alongsideprobabilitiesthe KL divergenceKL(p* || p)is reported.transform – Callable applied to
R(after conversion tofloat) 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
Truethe 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)