Skip to content

Cited fixture pack#

pybrinson.fixtures exposes worked examples drawn directly from the primary literature. The same fixtures back pybrinson's own test suite — one source of truth. Use them to:

  • Validate your own in-house attribution engine against published numbers.
  • Cross-check a vendor report (Bloomberg, Barra, FactSet) when the numbers feel off.
  • Regression-test a legacy spreadsheet before you retire it.
  • Reproduce an academic paper's worked example end-to-end.

If a number disagrees with the fixture's published source, file an issue — the fixture is the bug, not your implementation.

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

bacon_2008_ch5_bhb()#

3-sector UK / Japan / US example from Bacon (2008) Chapter 5. Canonical BHB teaching vehicle; also cites the 1986 Brinson-Hood- Beebower paper and the CFA Institute free reprint.

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
assert abs(result.allocation - expected["allocation"]) < 1e-12

bacon_2008_ch5_fachler()#

Same input segments as above, with the Brinson-Fachler allocation formula and its own expected numbers.

frongello_2002_table3()#

3-period 2-sector "challenge" example from Frongello (2002) pp. 10-12. The paper hand-derives Frongello-linked allocation as 57.39475% and total excess as 88.9805% — pybrinson reproduces both to \(10^{-10}\).

from pybrinson import bhb, link_frongello
from pybrinson.fixtures import frongello_2002_table3

periods, expected = frongello_2002_table3()
attrs = [bhb(segs, period=label) for label, segs in periods]
linked = link_frongello(attrs)

assert abs(linked.allocation - expected["frongello_linked_allocation"]) < 1e-10

frongello_2002_table1_identical()#

3 identical periods from Frongello (2002) Table 1. Frongello endnote 13 pins this as the special case where the Frongello, Cariño, and Menchero methods produce identical results — pybrinson's consistency tests verify exactly this.

karnosky_singer_3segment()#

3-region US / UK / Japan example for the currency attribution 4-effect decomposition. Constructed for pedagogical clarity so the additive consistency \(R_p = R^L + c\) holds exactly.

from pybrinson import currency_attribution
from pybrinson.fixtures import karnosky_singer_3segment

segments, expected = karnosky_singer_3segment()
result = currency_attribution(segments)
assert abs(result.excess_return - expected["excess_return"]) < 1e-12

two_period_linking_example()#

2-period 2-sector example small enough to derive every linking method by hand. Backs the cross-method consistency suite in tests/consistency/.

Each per-period BHB closes exactly; compounded portfolio return is 21.5%, benchmark 18.25%, arithmetic excess 3.25%, geometric excess ≈ 2.7484%.

Return shape#

Each fixture returns a 2-tuple:

  • (segments, expected) for single-period fixtures.
  • (periods, expected) for multi-period fixtures, where periods is tuple[(period_label, segments), ...].

expected is a dict with the published numbers — totals, per-segment breakdowns, and linking outcomes depending on the fixture.

Adding your own#

The fixture module is pure data. If you reproduce a worked example from the literature that is not yet covered, the pattern is:

def my_paper_table_4() -> tuple[tuple[Segment, ...], dict[str, float]]:
    """3-sector example from Smith & Jones (2020), Table 4.

    Source: Smith, J., & Jones, K. (2020). ...
    DOI: 10.1234/jpm.2020.xyz
    https://doi.org/10.1234/jpm.2020.xyz
    """
    segments = (
        Segment("A", ...),
        ...
    )
    expected = {
        "portfolio_return": ...,
        "benchmark_return": ...,
        "excess_return":    ...,
        # Numbers taken DIRECTLY from the paper, not recomputed by pybrinson.
        "allocation":  ...,
        "selection":   ...,
        "interaction": ...,
    }
    return segments, expected

The rule is that expected values come from the paper, never from what pybrinson produces — otherwise the fixture is circular and has no value.