Skip to content

Install & quickstart#

Requirements#

  • Python 3.14 or later (pinned by pybrinson's requires-python).
  • The Python standard library. That is all.

pybrinson has zero runtime dependencies. No pandas, no numpy, no polars — ever. This is a deliberate design rule, not an oversight.

Install#

pip install pybrinson
uv add pybrinson
poetry add pybrinson

Your first attribution#

from pybrinson import Segment, bhb

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

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

print(f"Portfolio return: {result.portfolio_return:.2%}")
print(f"Benchmark return: {result.benchmark_return:.2%}")
print(f"Excess return:    {result.excess_return:.2%}")
print()
print(f"Allocation:  {result.allocation:.2%}")
print(f"Selection:   {result.selection:.2%}")
print(f"Interaction: {result.interaction:.2%}")
Portfolio return: 8.30%
Benchmark return: 6.40%
Excess return:    1.90%

Allocation:  -1.20%
Selection:   3.00%
Interaction: 0.10%

result is a frozen PeriodAttribution dataclass. Print it directly for a fixed-width summary table; iterate result.by_segment for the per-group breakdown.

The Segment input#

Segment(
    name: str,
    portfolio_weight: float,   # fraction, e.g. 0.30 means 30%
    benchmark_weight: float,
    portfolio_return: float,   # fraction, e.g. 0.05 means +5%
    benchmark_return: float,
    parent: str | None = None,          # for hierarchies
    local_return: float | None = None,  # for currency attribution
    currency_return: float | None = None,
)

Input validation#

pybrinson refuses to guess. It will raise AttributionError on:

  • An empty segments list.
  • Non-finite values (NaN, ±inf) anywhere in weights or returns.
  • Weight vectors that do not sum to 1 within \(10^{-8}\) (both portfolio and benchmark are checked independently).
  • Segments with zero weight in both portfolio and benchmark (empty groups are a classification bug — fix upstream, do not silently drop).
  • Attribution identity violations (A + S + I != excess_return beyond \(10^{-9}\)). These only fire on numerical pathology, never on valid inputs.

No silent coercion, no warnings.warn, no imputation.

Next steps#