Source code for finance_risk.factor
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
import numpy as np
from pydantic import BaseModel, ConfigDict
from . import finance_risk as _native
__all__ = ["FactorRiskDecomposition", "factor_risk_decomposition"]
[docs]
class FactorRiskDecomposition(BaseModel):
model_config = ConfigDict(frozen=True)
total_variance: float
systematic_variance: float
specific_variance: float
factor_contributions: dict[str, float]
[docs]
def factor_risk_decomposition(
weights: Any,
factor_loadings: Any,
factor_covariance: Any,
specific_variance: Any,
*,
factors: Sequence[str] | None = None,
) -> FactorRiskDecomposition:
"""Decompose portfolio variance into systematic factor and specific risk."""
w = np.asarray(weights, dtype=float).reshape(-1)
loadings = np.asarray(factor_loadings, dtype=float)
factor_cov = np.asarray(factor_covariance, dtype=float)
specific = np.asarray(specific_variance, dtype=float).reshape(-1)
if loadings.shape[0] != w.size:
raise ValueError("factor_loadings rows must match weights")
if factor_cov.shape != (loadings.shape[1], loadings.shape[1]):
raise ValueError("factor_covariance dimensions must match factor_loadings columns")
if specific.size != w.size:
raise ValueError("specific_variance length must match weights")
factor_names = tuple(factors) if factors is not None else tuple(f"factor_{i}" for i in range(loadings.shape[1]))
total, systematic, specific_var, contributions_raw = _native.factor_risk_decomposition(
w.tolist(),
loadings.tolist(),
factor_cov.tolist(),
specific.tolist(),
)
contributions = {name: float(contributions_raw[i]) for i, name in enumerate(factor_names)}
return FactorRiskDecomposition(
total_variance=float(total),
systematic_variance=systematic,
specific_variance=specific_var,
factor_contributions=contributions,
)