Metadata-Version: 2.5
Name: factor-shrinkage
Version: 0.1.0
Summary: Modern shrinkage/DRO covariance estimators scoped to Barra-style factor risk models (Sigma = B Omega B^T + D), not dense asset covariance matrices.
Author-email: Vinh Nguyen <vinhnguyen3455@gmail.com>
License: MIT
License-File: LICENSE
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: scikit-learn>=1.3
Requires-Dist: scipy>=1.10
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pandas>=2.0; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# factor-shrinkage

Modern covariance-shrinkage estimators for Barra-style factor risk models, in Python.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Why this exists

Institutional equity risk models — Barra, Axioma, and their in-house equivalents — write the asset
covariance matrix as a low-rank-plus-diagonal decomposition:

```
Σ = B Ω Bᵀ + D
```

where `B` (N×K) holds each asset's exposure to K factors, `Ω` is the K×K factor-return covariance
matrix, and `D` is diagonal idiosyncratic variance. This collapses an O(N²) estimation problem into an
O(K²) one — but `Ω` still isn't easy to estimate well, and an ill-conditioned factor covariance is
directly harmful to downstream portfolio optimization.

Several strong shrinkage/DRO (distributionally robust optimization) estimators exist in the academic
literature for exactly this problem, but none of them had a packaged, tested, interoperable Python
implementation: Wasserstein shrinkage (Nguyen, Kuhn & Mohajerin Esfahani, *Operations Research* 2022),
Ledoit & Wolf's nonlinear-shrinkage family (QIS/LIS/GIS), and Fisher–Rao shrinkage (Yue, Rychener, Kuhn
& Nguyen 2025) each existed only as a paper plus, at best, unpackaged research code. `factor-shrinkage`
implements and validates all of them, scoped specifically to the **factor loadings B and factor
covariance Ω** rather than the raw N×N asset covariance — small enough (K is typically 20–150 in real
institutional risk models) that the estimators' real subtlety (positive-definiteness preservation,
eigenvalue-level bisection solves, correct ambiguity-set-to-shrinkage-intensity mapping) stays tractable
and verifiable.

## What it offers

- **Six shrinkage estimators, one consistent interface.** `WassersteinShrinkage`, `QISShrinkage`,
  `LISShrinkage`, `GISShrinkage`, `FisherRaoShrinkage`, and `HarmonicShrinkage` (this package's own
  construction, built on Fisher–Rao — see below) all subclass
  `sklearn.covariance.EmpiricalCovariance` directly, so each is a drop-in replacement for
  `sklearn.covariance.LedoitWolf` with no new API to learn.
- **A concrete recommendation, not just six options.** Validated on 125 real walk-forward windows of a
  16-factor equity model: `FisherRaoShrinkage` beats the other established estimators on realized
  portfolio variance and conditioning; `HarmonicShrinkage` further cuts basis-dependence (sensitivity to
  which coordinate system the factors happen to be expressed in) by a validated 34% median at matched
  regularization strength, at a small, honestly-reported cost in realized Sharpe. Use
  `HarmonicShrinkage` where basis-dependence risk (a refactored risk model, reconciled vendor factor
  definitions) is the primary concern, `FisherRaoShrinkage` where realized Sharpe is the sole criterion.
- **Composes into existing portfolio tools, not a new framework.** `reconstruct_covariance(B,
  model.covariance_, d)` turns a fitted `Ω̂` back into the full asset covariance matrix, ready for
  `Riskfolio-Lib`, `PyPortfolioOpt`, or `skfolio`; the decomposed pieces feed
  `cvxportfolio.FactorModelCovariance` directly, no reshaping.
- **A reconstruction-aware cross-validation scorer.** `gmv_variance_scorer` and `cross_val_kappa` select
  shrinkage strength by realized downstream portfolio variance, not a proxy loss on the factor
  covariance alone — plain `GridSearchCV(..., scoring=gmv_variance_scorer)` composition silently
  optimizes the wrong portfolio otherwise (one built over the factors, not the actual assets).

```python
import numpy as np
from factor_shrinkage import FisherRaoShrinkage, HarmonicShrinkage, reconstruct_covariance

# F: T x K matrix of factor returns; B, d: loadings and idiosyncratic variances from your risk model
model = FisherRaoShrinkage(kappa=0.1).fit(F)
Sigma = reconstruct_covariance(B, model.covariance_, d)  # N x N asset covariance, ready for a portfolio optimizer

# Where basis-dependence (not just realized Sharpe) is the concern:
robust_model = HarmonicShrinkage(kappa=0.1, h_frac=0.65).fit(F)
```

## Install

```bash
pip install factor-shrinkage
```

Requires Python 3.10+. Runtime dependencies are `numpy`, `scipy`, and `scikit-learn` — nothing else.

## Validation

All six estimators are checked against an independent source of truth, not just self-consistency: every
estimator with a published closed form is cross-checked against an independent reference oracle
(`tests/_reference/`), and `HarmonicShrinkage` — this package's own construction, with no published
formula to check against — is validated by property tests plus an independent hand-computed cross-check
of its own linear solve. All six pass formal `sklearn.utils.estimator_checks.check_estimator`
conformance, which caught and fixed two real degenerate-input bugs during development (both estimators
now raise a clear `ValueError` on a single-sample fit instead of silently overflowing or producing NaN).
87 tests, fully type-hinted, `mypy`-clean, `py.typed` marker included. See `tests/` for the full suite.

## Related work

`factor-shrinkage` interoperates rather than competes: it is not a new portfolio-construction framework,
just a robustified `Ω`. `cvxportfolio`'s `FactorModelCovariance` already targets the exact
`Σ = BΩBᵀ + D` shape this package robustifies, with no shrinkage of its own — confirming the gap this
package fills. `Riskfolio-Lib`, `PyPortfolioOpt`, and `skfolio` are all confirmed pluggable
(`Portfolio.cov`, `EfficientFrontier(mu, S)`, and the documented `BaseCovariance` ABC respectively).

## Development

```bash
git clone https://github.com/vinhnguyen3455/factor-shrinkage
cd factor-shrinkage
pip install -e ".[dev]"
pytest -q
mypy src/factor_shrinkage
```

Issues and pull requests welcome.

## License

MIT — see [LICENSE](LICENSE).
