portfolio-engine / tests /test_risk_attribution.py
engineportf's picture
Initial Deployment from Local Engine
208fbf8 verified
Raw
History Blame Contribute Delete
2.14 kB
import sys
import os
import pytest
import numpy as np
import pandas as pd
_this_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _this_dir)
from risk_attribution import cvar_attribution, stress_correlation
def test_cvar_fallback_branch():
rng = np.random.default_rng(42)
# Generate an artificially small dataset with very low variance
# to trigger the "Empirical tail too thin (<3 obs)" fallback branch
tickers = ["AAPL", "TLT"]
dates = pd.date_range("2023-01-01", periods=10, freq="B")
# Only 10 observations, tail at 95% will have 0.5 obs < 3
returns_df = pd.DataFrame({
"AAPL": rng.normal(0, 0.01, 10),
"TLT": rng.normal(0, 0.01, 10)
}, index=dates)
weights = pd.Series({"AAPL": 0.6, "TLT": 0.4})
# This should trigger the parametric fallback
component_cvar, total_cvar = cvar_attribution(weights, returns_df, alpha=0.95)
assert total_cvar > 0
assert len(component_cvar) == 2
assert "AAPL" in component_cvar
def test_stress_correlation_bounds():
rng = np.random.default_rng(42)
tickers = ["A", "B", "C"]
# Base covariance matrix with some positive correlation
cov = np.array([
[0.04, 0.01, 0.01],
[0.01, 0.04, 0.01],
[0.01, 0.01, 0.04]
])
cov_df = pd.DataFrame(cov, index=tickers, columns=tickers)
weights = pd.Series({"A": 0.4, "B": 0.4, "C": 0.2})
# Apply a massive shock to trigger clipping
stressed_cov_df, stressed_vol = stress_correlation(weights, cov_df, shock_corr=0.9)
# Check that correlations don't exceed 1.0 (implied by variance and covariance)
vols = np.sqrt(np.diag(stressed_cov_df.values))
outer_vols = np.outer(vols, vols)
corr_mat = stressed_cov_df.values / outer_vols
# Max correlation off-diagonal should be <= 1.0
np.testing.assert_array_less(corr_mat - 1e-5, 1.0)
# Stressed vol should be higher than normal vol
normal_vol = np.sqrt(weights.values.T @ cov_df.values @ weights.values)
assert stressed_vol > normal_vol