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#
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.