Skip to content

API reference#

Stable, typed public surface. Everything below is importable from the top-level pybrinson module. The package ships a py.typed marker so mypy / pyright pick up the annotations out of the box.

Data model#

Segment#

@dataclass(frozen=True, slots=True)
class Segment:
    name: str
    portfolio_weight: float
    benchmark_weight: float
    portfolio_return: float
    benchmark_return: float
    parent: str | None = None
    local_return: float | None = None       # currency attribution only
    currency_return: float | None = None    # currency attribution only

One classification group for one period. Weights are fractions (\(0.30 = 30\%\)) and must sum to 1 across all segments in the same period. Returns are fractions (\(0.05 = +5\%\)) and must be finite.

PeriodAttribution#

Result of bhb() or fachler(). Frozen dataclass with:

Field Type
period str \| None caller-provided label
portfolio_return float \(R_p\)
benchmark_return float \(R_b\)
excess_return float \(R_p - R_b\)
allocation float \(\sum A_i\)
selection float \(\sum S_i\)
interaction float \(\sum I_i\)
by_segment tuple[SegmentAttribution, ...] per-group breakdown
method str "BHB" or "BF"

A + S + I == excess_return within \(10^{-9}\) by construction.

SegmentAttribution#

Per-segment row inside PeriodAttribution.by_segment.

Field Type
name str
allocation float
selection float
interaction float
parent str \| None grandparent label for hierarchical results
level int depth (root = 0)
is_leaf bool leaves vs roll-up parents

LinkedAttribution#

Result of any link_*() function.

Field Type
periods tuple[PeriodAttribution, ...] inputs, unchanged
portfolio_return float compounded \(R_p\)
benchmark_return float compounded \(R_b\)
excess_return float arithmetic or geometric excess
allocation float linked
selection float linked
interaction float linked (0 for geometric)
method str "Cariño", "GRAP", "Frongello", "Menchero", "Geometric"

CurrencyPeriodAttribution, CurrencySegmentAttribution#

Four-effect result types for currency_attribution(). See currency guide.

Single-period entry points#

def bhb(
    segments: Sequence[Segment],
    *,
    period: str | None = None,
    parents: Mapping[str, str | None] | None = None,
) -> PeriodAttribution: ...

def fachler(
    segments: Sequence[Segment],
    *,
    period: str | None = None,
    parents: Mapping[str, str | None] | None = None,
) -> PeriodAttribution: ...

def currency_attribution(
    segments: Sequence[Segment],
    *,
    period: str | None = None,
) -> CurrencyPeriodAttribution: ...

Multi-period linking#

def link_carino(    periods: Sequence[PeriodAttribution]) -> LinkedAttribution: ...
def link_grap(      periods: Sequence[PeriodAttribution]) -> LinkedAttribution: ...
def link_frongello( periods: Sequence[PeriodAttribution]) -> LinkedAttribution: ...
def link_menchero(  periods: Sequence[PeriodAttribution]) -> LinkedAttribution: ...
def link_geometric( periods: Sequence[PeriodAttribution]) -> LinkedAttribution: ...

All five require \(\ge 2\) periods sharing the same method.

Rendering#

def period_to_table(result: PeriodAttribution) -> str: ...
def linked_to_table(result: LinkedAttribution) -> str: ...

Fixed-width plain-text tables. __repr__ of PeriodAttribution and LinkedAttribution calls these automatically, so print(result) renders the table.

Errors#

class AttributionError(ValueError): ...

Single exception type for all input-validation and identity-closure failures. Subclass of ValueError so existing except ValueError clauses work.

Fixtures#

from pybrinson.fixtures import (
    bacon_2008_ch5_bhb,
    bacon_2008_ch5_fachler,
    frongello_2002_table1_identical,
    frongello_2002_table3,
    karnosky_singer_3segment,
    two_period_linking_example,
)

Public, citable worked examples. See the fixtures guide.

Stability#

Public API (everything listed here) follows semver. Breaking changes require a major version bump. Additive changes (new methods, new optional fields, new result types) are minor bumps. Bug fixes are patches.