Metadata-Version: 2.4
Name: wayfault
Version: 0.3.0
Summary: Wrong-Way Risk (WWR) estimation for counterparty credit risk.
Project-URL: Homepage, https://daibeal.github.io/wayfault/
Project-URL: Documentation, https://daibeal.github.io/wayfault/
Project-URL: Repository, https://github.com/daibeal/wayfault
Project-URL: Issues, https://github.com/daibeal/wayfault/issues
Project-URL: Playground, https://daibeal.github.io/wayfault/playground/
Author: wayfault contributors
License: MIT
Keywords: counterparty-credit-risk,cva,quant,wrong-way-risk,xva
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.11
Requires-Dist: numpy>=1.24
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.3; extra == 'dev'
Requires-Dist: sympy>=1.12; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
Provides-Extra: io
Requires-Dist: pandas>=2.0; extra == 'io'
Requires-Dist: pyarrow>=12.0; extra == 'io'
Provides-Extra: ml
Requires-Dist: scikit-learn>=1.3; extra == 'ml'
Provides-Extra: viz
Requires-Dist: matplotlib>=3.7; extra == 'viz'
Description-Content-Type: text/markdown

# wayfault

**Wrong-Way Risk (WWR) estimation for counterparty credit risk.**

`wayfault` quantifies the adverse dependence between exposure and counterparty
credit quality — the risk that exposure rises precisely when the counterparty
deteriorates (WWR), and its favourable mirror, Right-Way Risk (RWR). It takes a
Monte-Carlo **exposure cube** and a **credit curve** as inputs and produces:

- baseline (independence-assumption) exposure metrics and CVA,
- a **conditional expected exposure given default** under a pluggable
  dependence model,
- a **WWR-adjusted CVA** and the empirical **alpha multiplier**
  `α = WWR-CVA / independent-CVA`,
- ML-based **calibration** of the dependence parameter,
- WWR/RWR **classification and diagnostics**.

The library does **not** generate exposures or bootstrap curves — those are
inputs.

## 🎮 Live playground

An interactive **[browser playground](https://daibeal.github.io/wayfault/playground/)**
runs the real `wayfault` wheel via WebAssembly (Pyodide) — no install, no server.
Adjust the dependence model and parameters and watch the CVA, alpha multiplier,
and exposure charts recompute live.

## Architecture

`wayfault` follows a strict **hexagonal (ports & adapters)** design. The
dependency rule points inward: `adapters → application → ports → domain`. The
`domain`, `application`, and `ports` layers import **only the standard library
and numpy**. Optional adapters import their heavy dependencies *lazily* and
raise a clear `MissingDependencyError` if the extra is not installed.

## Install

```bash
pip install -e .                       # core (numpy only)
pip install -e '.[io,ml,viz,dev]'      # with all optional extras + tooling
```

Optional extras:

| Extra   | Enables                                   | Pulls in              |
|---------|-------------------------------------------|-----------------------|
| `[io]`  | CSV/Parquet source adapters               | `pandas`, `pyarrow`   |
| `[ml]`  | scikit-learn survival calibrator          | `scikit-learn`        |
| `[viz]` | diagnostic plots                          | `matplotlib`          |
| `[dev]` | tests, type-checking, linting             | `pytest`, `mypy`, …   |

## Quickstart

```python
import numpy as np
from wayfault import estimate_wwr
from wayfault.adapters.outbound.exposure_inmemory import InMemoryExposureSource
from wayfault.adapters.outbound.credit_flat import FlatHazardCreditCurveSource
from wayfault.adapters.outbound.dependence_hullwhite import HullWhiteHazardModel

cube = np.random.default_rng(0).normal(size=(10_000, 12)) + 1.0  # scenarios x tenors
tenors = [i / 4 for i in range(1, 13)]                           # quarterly to 3y

result = estimate_wwr(
    exposure=InMemoryExposureSource(cube, tenors),
    credit=FlatHazardCreditCurveSource(hazard=0.02, recovery=0.4),
    model=HullWhiteHazardModel(b=0.5),   # b > 0  ->  wrong-way
)

print(result.baseline_cva, result.wwr_cva, result.alpha, result.classification)
```

A full runnable example lives in [`examples/quickstart.py`](examples/quickstart.py)
and uses only the in-memory adapters (zero extras).

## CLI

```bash
python -m wayfault estimate \
    --exposure cube.csv --credit curve.csv \
    --model hullwhite --b 0.5 --out result.json
```

(`--exposure`/`--credit` CSV ingestion requires the `[io]` extra.)

## Dependence models

| Model                   | Knob | WWR when | Notes                                       |
|-------------------------|------|----------|---------------------------------------------|
| `IndependentModel`      | —    | —        | conditional EE ≡ unconditional EE           |
| `HullWhiteHazardModel`  | `b`  | `b > 0`  | `λ(t) = exp(a(t) + b·V(t))` (Hull–White)    |
| `GaussianCopulaModel`   | `ρ`  | `ρ > 0`  | one-factor Gaussian copula                  |
| `ClaytonCopulaModel`    | `θ`  | `θ > 0`  | Archimedean, **lower-tail dependence**      |
| `FrankCopulaModel`      | `θ`  | `θ > 0`  | Archimedean, symmetric (sign sets direction)|

All models are numpy-only and **fully vectorised** across tenors.

## Calibration

- `RegressionCalibrator` — numpy-only OLS estimate of the Hull–White `b`.
- `SklearnSurvivalCalibrator` — `[ml]` covariate-hazard surrogate.

### Inverse solvers (prescriptive)

Invert the relationship — target-driven calibration and reverse-stress:

```python
from wayfault import calibrate_to_alpha, find_breakpoint
hw = lambda b: HullWhiteHazardModel(b=b)

calibrate_to_alpha(exposure, credit, hw, target_alpha=1.25, lo=-1.5, hi=1.5)  # which b -> alpha 1.25?
find_breakpoint(exposure, credit, hw, threshold=1.40, lo=0.0, hi=3.0)          # b where alpha breaches 1.40
```

## Visualization

The `[viz]` extra adds a beautiful matplotlib plotting module
(`wayfault.adapters.outbound.viz`) — lazily imported, so the core stays
numpy-only. Regenerate the gallery with `pip install 'wayfault[viz]'` then
`python examples/gallery.py`.

|  |  |
|--|--|
| **Exposure profiles** — EPE vs conditional EE, shaded WWR adjustment | **EE ratio** — per-tenor conditional/unconditional |
| ![Exposure profiles](docs/assets/img/exposure_profiles.png) | ![EE ratio](docs/assets/img/ee_ratio.png) |
| **Alpha sweep** — alpha & CVA vs the dependence knob | **Dashboard** — everything at a glance |
| ![Alpha sweep](docs/assets/img/alpha_sweep.png) | ![Dashboard](docs/assets/img/dashboard.png) |

```python
from wayfault.adapters.outbound import viz
fig = viz.plot_dashboard(result, bs=bs, alphas=alphas, wwr_cvas=wwr_cvas)
viz.save(fig, "dashboard.png")
```

## Performance

Two acceleration techniques, both numpy-only:

- **Vectorised re-weighting** — every model re-weights the whole exposure cube
  in a single numpy expression (no per-tenor Python loop).
- **Parallel batch & sweep** — `wayfault.application.parallel` fans independent
  estimates across workers with deterministic, input-order results:

```python
from wayfault.application.parallel import sweep_models
from wayfault.adapters.outbound.dependence_hullwhite import HullWhiteHazardModel

results = sweep_models(
    exposure, credit,
    [HullWhiteHazardModel(b=b) for b in np.linspace(-1.2, 1.2, 25)],
    max_workers=8,            # threads (numpy releases the GIL); or pass a ProcessPoolExecutor
)
alphas = [r.alpha for r in results]
```

See the [Performance docs](https://daibeal.github.io/wayfault/performance/).

## Development

```bash
pytest                 # tests
mypy --strict src      # type checks
ruff check             # lint
```

Reference: Hull & White, *CVA and Wrong-Way Risk* (2012).

## License

MIT.
