Source code for merton.portfolio.correlation

"""Asset-correlation helpers."""

from __future__ import annotations

import numpy as np

from .._typing import ArrayLike, FloatArray
from ..exceptions import MertonInputError
from .vasicek_factor import basel_irb_correlation as _basel_corr


[docs] def basel_irb_correlation(pd: ArrayLike, *, asset_class: str = "corporate") -> FloatArray: """Re-export of :func:`merton.portfolio.vasicek_factor.basel_irb_correlation`.""" return _basel_corr(pd, asset_class=asset_class)
[docs] def asset_correlation_from_equity( returns: ArrayLike, *, leverage: ArrayLike | None = None, shrinkage: float = 0.0, ) -> FloatArray: """Asset-correlation matrix estimated from a panel of equity log-returns. Parameters ---------- returns 2-D array of shape ``(n_obs, n_firms)`` containing equity log-returns. leverage Optional 1-D array of length ``n_firms`` of equity/(equity+debt) ratios. When supplied, equity correlations are scaled by the leverage product to approximate the asset-return correlation matrix. shrinkage Linear-shrinkage parameter in ``[0, 1]``. ``0`` returns the sample correlation; ``1`` returns the identity (no correlation). """ R = np.asarray(returns, dtype=np.float64) if R.ndim != 2: raise MertonInputError("returns must be a 2-D (n_obs, n_firms) array") if not 0 <= shrinkage <= 1: raise MertonInputError("shrinkage must lie in [0, 1]") centred = R - R.mean(axis=0, keepdims=True) cov = np.cov(centred, rowvar=False) sd = np.sqrt(np.clip(np.diag(cov), 1e-30, np.inf)) corr = cov / np.outer(sd, sd) np.fill_diagonal(corr, 1.0) if leverage is not None: lev = np.asarray(leverage, dtype=np.float64) if lev.ndim != 1 or lev.shape[0] != corr.shape[0]: raise MertonInputError("leverage must be a 1-D array matching n_firms") corr = corr * np.outer(lev, lev) np.fill_diagonal(corr, 1.0) if shrinkage > 0: corr = (1.0 - shrinkage) * corr + shrinkage * np.eye(corr.shape[0]) return corr
__all__ = ["asset_correlation_from_equity", "basel_irb_correlation"]