Skip to main content

Portfolio return attribution in Python: Brinson-Hood-Beebower, Brinson-Fachler, and multi-period linking.

Project description

pybrinson

CI License: MIT

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.0 — single-period BHB and Brinson-Fachler plus Cariño / GRAP / geometric multi-period linking. Single-level classification only; nested hierarchies are scheduled for v1.1.

Methods supported

Method Reference
Single-period Brinson-Hood-Beebower (3-effect) Brinson, Hood & Beebower (1986)
Single-period Brinson-Fachler (3-effect) Brinson & Fachler (1985)
Multi-period linking Cariño log-smoothing Cariño (1999)
Multi-period linking GRAP factors GRAP (1997)
Multi-period linking Geometric (Bacon) Bacon (2008), chap. 6

Deferred to v1.1+: Menchero / Frongello linking, multi-level (nested) classification, currency attribution.

Install

pip install pybrinson

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

Quickstart

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)
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

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 / geometric linking.

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
Geometric linking 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 and targets Python 3.14.

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
  • Brinson, G. P., & Fachler, N. (1985). "Measuring Non-U.S. Equity Portfolio Performance." Journal of Portfolio Management, 11(3). DOI
  • 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.
  • Bacon, C. R. (2008). Practical Portfolio Performance Measurement and Attribution, 2nd ed., Wiley. Wiley page
  • Bacon, C. R. (2019). Performance Attribution: History and Progress. CFA Institute Research Foundation. Free PDF

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pybrinson-1.0.0.tar.gz (19.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pybrinson-1.0.0-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file pybrinson-1.0.0.tar.gz.

File metadata

  • Download URL: pybrinson-1.0.0.tar.gz
  • Upload date:
  • Size: 19.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pybrinson-1.0.0.tar.gz
Algorithm Hash digest
SHA256 338d910348b1da85f620c3ded93ede364b6fb4c8900896305893537d39714fb7
MD5 03573f1c273ff5e1575f65828b7d79b7
BLAKE2b-256 2b218b86c40f35a6935a001930016a5be43d110425a79b62bb98f03d46fa191e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pybrinson-1.0.0.tar.gz:

Publisher: release.yml on gghez/pybrinson

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pybrinson-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pybrinson-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pybrinson-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 37a571660b326c5c6606435372655e068b98b0ebbcb2e6cbc1312344c2b32dc5
MD5 90a9d5de85bd3a65f6fd50ff81f52090
BLAKE2b-256 c20466f1f50982c45a4f5db916adac29a60f539d64fd286cd159f0516ffe14fc

See more details on using hashes here.

Provenance

The following attestation bundles were made for pybrinson-1.0.0-py3-none-any.whl:

Publisher: release.yml on gghez/pybrinson

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page