Metadata-Version: 2.4
Name: pybrinson
Version: 1.2.1
Summary: Portfolio return attribution in Python: Brinson-Hood-Beebower, Brinson-Fachler, and multi-period linking.
Project-URL: Homepage, https://github.com/gghez/pybrinson
Project-URL: Repository, https://github.com/gghez/pybrinson
Project-URL: Issues, https://github.com/gghez/pybrinson/issues
Author: pybrinson contributors
License-Expression: MIT
License-File: LICENSE
Keywords: asset-management,attribution,brinson,finance,performance,portfolio
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.14
Description-Content-Type: text/markdown

# pybrinson

[![CI](https://github.com/gghez/pybrinson/actions/workflows/ci.yml/badge.svg)](https://github.com/gghez/pybrinson/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

Portfolio return attribution in Python — with sources you can audit.

`pybrinson` decomposes a portfolio's excess return versus a benchmark into
**allocation**, **selection**, and **interaction** effects, across any
user-defined classification (sector, country, asset class). Every formula
ships with its mathematical statement, an academic citation, and a
clickable URL — readers can verify the math without leaving the file.

It targets the gap left by the existing Python finance stack: R has the
`pa` package on CRAN and MATLAB ships `brinsonAttribution`, but no
maintained PyPI package implements the Brinson family of models.

## Status

v1.2 — adds Menchero (2000, 2004) optimised multi-period linking
(patent US 7,249,082 B2 expired 2024-02-18), second-source literature
fixtures from Frongello (2002), and a ppar cross-validation script for
the Cariño coefficient. Also fixes the v1.1 Frongello implementation to
match the published convention (prefix portfolio × suffix benchmark, per
the recursion on pp. 4-5 of Frongello 2002).

## Methods supported

| | Method | Reference |
|---|---|---|
| Single-period | Brinson-Hood-Beebower (3-effect) | Brinson, Hood & Beebower (1986) |
| Single-period | Brinson-Fachler (3-effect) | Brinson & Fachler (1985) |
| Single-period | Multi-level hierarchical roll-up (any depth) | Bacon (2008), chap. 5 |
| Multi-period linking | Cariño log-smoothing | Cariño (1999) |
| Multi-period linking | GRAP factors | GRAP (1997) |
| Multi-period linking | Frongello recursive | Frongello (2002) |
| Multi-period linking | Menchero optimised | Menchero (2000, 2004) |
| Multi-period linking | Geometric (Bacon) | Bacon (2008), chap. 6 |

Deferred to v1.3+: currency attribution (Karnosky-Singer),
pandas / polars adapters, fixed-income (Campisi) attribution.
See `docs/implementation-v1.3.md`.

## Install

```bash
pip install pybrinson
```

`pybrinson` has **zero runtime dependencies** — just the Python standard
library. Requires Python 3.14+.

## Quickstart

```python
from pybrinson import Segment, bhb

segments = [
    Segment("UK Equity", portfolio_weight=0.40, benchmark_weight=0.40,
            portfolio_return=0.20, benchmark_return=0.10),
    Segment("Japan Equity", portfolio_weight=0.30, benchmark_weight=0.20,
            portfolio_return=-0.05, benchmark_return=-0.04),
    Segment("US Equity", portfolio_weight=0.30, benchmark_weight=0.40,
            portfolio_return=0.06, benchmark_return=0.08),
]

result = bhb(segments, period="2024-Q1")
print(result)
```

```text
BHB attribution — period 2024-Q1
  R_p = 8.3000%   R_b = 6.4000%   excess = 1.9000%

Segment       Allocation  Selection  Interaction     Total
------------  ----------  ---------  -----------  --------
UK Equity        0.0000%    4.0000%      0.0000%   4.0000%
Japan Equity    -0.4000%   -0.2000%     -0.1000%  -0.7000%
US Equity       -0.8000%   -0.8000%      0.2000%  -1.4000%
Total           -1.2000%    3.0000%      0.1000%   1.9000%
```

The identity ``allocation + selection + interaction == excess_return``
holds within ``1e-9`` by construction; pybrinson **raises**
`AttributionError` rather than storing a silent residual when it fails.

### Multi-period linking

```python
from pybrinson import bhb, link_carino, Segment

period_attrs = [
    bhb([Segment("Equities", 0.6, 0.5, 0.10, 0.05),
         Segment("Bonds",    0.4, 0.5, 0.05, 0.10)], period="P1"),
    bhb([Segment("Equities", 0.5, 0.5, 0.20, 0.10),
         Segment("Bonds",    0.5, 0.5, 0.05, 0.10)], period="P2"),
]

print(link_carino(period_attrs))
```

See `examples/` for runnable scripts covering BHB, Brinson-Fachler, and
Cariño / GRAP / Frongello / geometric linking.

### Multi-level hierarchies (v1.1+)

Each `Segment` may declare an immediate `parent` label. For chains
deeper than one level, pass a `parents={parent: grandparent}` mapping
to `bhb()` / `fachler()`:

```python
from pybrinson import Segment, bhb

leaves = [
    Segment("UK",      0.20, 0.25,  0.10,  0.08, parent="Europe"),
    Segment("Germany", 0.25, 0.20,  0.05,  0.06, parent="Europe"),
    Segment("US",      0.30, 0.35,  0.12,  0.10, parent="Americas"),
    Segment("Brazil",  0.25, 0.20, -0.04, -0.02, parent="Americas"),
]
result = bhb(leaves, parents={"Europe": "Equity", "Americas": "Equity", "Equity": None})
```

Allocation, selection and interaction roll up additively at every
level: each parent equals the sum of its descendants, and the root
parent equals the period total.

### Reusable cited fixtures (v1.1+)

```python
from pybrinson import bhb
from pybrinson.fixtures import bacon_2008_ch5_bhb

segments, expected = bacon_2008_ch5_bhb()
result = bhb(segments)
assert abs(result.excess_return - expected["excess_return"]) < 1e-12
```

The same fixtures back the test suite — there is one source of truth.

## Design principles

- **Pure Python first.** Zero runtime dependencies. Realistic attribution
  inputs are small (≤1k segments × ≤1k periods); a NumPy dependency
  would not pay for itself.
- **Specification-driven, externally verified.** Every function carries
  its formula, an academic citation, and a clickable URL. The math is
  cross-checked against published worked examples.
- **No silent residuals.** Identity failures raise. Bad inputs raise.
  pybrinson never imputes, rescales or swallows residuals.
- **Typed and tested.** Public API is fully type-annotated; ships
  `py.typed`; tests cover both pinned worked examples and randomised
  identity checks.

## Positioning vs `ppar` / `fincore`

| | `ppar` | `fincore` | `pybrinson` |
|---|---|---|---|
| BHB | no | yes | yes |
| Brinson-Fachler 3-effect | 2-effect only | no | yes |
| Cariño linking | yes | no | yes |
| GRAP linking | no | no | **yes** |
| Frongello linking | no | no | **yes** |
| Menchero linking | no | no | **yes** |
| Geometric linking | no | no | **yes** |
| Multi-level hierarchical roll-up | no | no | **yes** |
| Public fixture pack of cited examples | no | no | **yes** |
| Cross-method consistency suite | no | no | **yes** |
| Inline source citations | no | no | **mandatory** |
| Identity failure handling | n/a | silent residual | **raises** |
| Runtime dependencies | 9 | 2 | **0** |

See `docs/implementation-v1.md` for the audited findings against pinned
upstream commits.

## Development

This project uses [uv](https://docs.astral.sh/uv/) and targets Python 3.14.

```bash
uv sync                  # install deps, fetch Python 3.14 if needed
uv run pytest            # full test suite
uv run pytest -k bhb     # subset
uv build                 # sdist + wheel into dist/
```

## References

Primary papers cited in the source:

- Brinson, G. P., Hood, L. R., & Beebower, G. L. (1986). "Determinants of Portfolio Performance." *Financial Analysts Journal*, 42(4). [DOI](https://doi.org/10.2469/faj.v42.n4.39)
- Brinson, G. P., & Fachler, N. (1985). "Measuring Non-U.S. Equity Portfolio Performance." *Journal of Portfolio Management*, 11(3). [DOI](https://doi.org/10.3905/jpm.1985.409005)
- Cariño, D. R. (1999). "Combining Attribution Effects Over Time." *Journal of Performance Measurement*, 3(4).
- Groupe de Recherche en Attribution de Performance (1997). *Synthèse des modèles d'attribution de performance*.
- Frongello, A. (2002). "Linking Single Period Attribution Results." *Journal of Performance Measurement*, 6(3).
- Frongello, A. (2002). "Attribution Linking: Proofed and Clarified." *Journal of Performance Measurement*, 7(1). [Author PDF](https://frongello.com/support/Works/JPMFall2002.pdf)
- Menchero, J. (2000). "An Optimized Approach to Linking Attribution Effects Over Time." *Journal of Performance Measurement*, 5(1).
- Menchero, J. (2004). "Multiperiod Arithmetic Attribution." *Financial Analysts Journal*, 60(4). [DOI](https://doi.org/10.2469/faj.v60.n4.2638)
- Bacon, C. R. (2008). *Practical Portfolio Performance Measurement and Attribution*, 2nd ed., Wiley. [Wiley page](https://www.wiley.com/en-us/Practical+Portfolio+Performance+Measurement+and+Attribution%2C+2nd+Edition-p-9780470059289)
- Bacon, C. R. (2019). *Performance Attribution: History and Progress*. CFA Institute Research Foundation. [Free PDF](https://www.cfainstitute.org/-/media/documents/book/rf-publication/2019/rf-v2019-n4-1-pdf.pdf)

## License

MIT — see [LICENSE](LICENSE).
