Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

perfattr

perfattr is a small, auditable portfolio performance-attribution calculation library built with pandas and NumPy.

The package provides a reusable Brinson attribution calculation core and a portable preparation layer for source-period weights and returns. Portfolio accounting, vendor schemas, and presentation remain outside the package boundary.

Main features

  • Accept weights and returns, or authoritative contributions when accounting results are available.
  • Validate, select, and align portfolio and benchmark histories.
  • Consolidate smaller source periods into complete monthly, quarterly, or yearly reporting periods using calendar and holiday rules.
  • Apply static or effective-dated classification mappings before consolidation.
  • Calculate Brinson-Fachler or Brinson-Hood-Beebower with compact two-effect selection or explicit three-effect selection and interaction.
  • Link contributions logarithmically and attribution effects using Carino linking.
  • Preserve zero-weight fee and financing contributions without inventing returns.
  • Return deterministic pandas result frames with explicit financial reconciliation.

The completed initial calculation roadmap is recorded in _extras/perfattr_roadmap_1.md. The completed portable preparation work is recorded in _extras/perfattr_roadmap_2.md, while later candidates are kept in the noncommitted _extras/perfattr_roadmap_3.md. Effective-dated classification was the first promoted candidate and its completed work is recorded in _extras/perfattr_roadmap_4_effective_dated_classification.md, with its accepted contract in docs/effective_dated_classification_specification.md. The released opt-in Brinson-Fachler three-effect work is recorded in _extras/perfattr_roadmap_5_brinson_fachler_three_effect.md and docs/brinson_fachler_three_effect_specification.md. The released Brinson-Hood-Beebower three-effect work is recorded in _extras/perfattr_roadmap_6_brinson_hood_beebower_three_effect.md and docs/brinson_hood_beebower_three_effect_specification.md. The accepted compact Brinson-Hood-Beebower work is recorded in _extras/perfattr_roadmap_7_brinson_hood_beebower_two_effect.md and docs/brinson_hood_beebower_two_effect_specification.md. The complete portable calculation contract is defined in docs/specification.md, and the accepted roadmap 2 preparation contract is in docs/preparation_specification.md.

import pandas as pd

from perfattr import calculate_attribution, prepare_attribution

portfolio = pd.DataFrame(
    [
        {
            "from_date": "2024-01-01",
            "thru_date": "2024-01-31",
            "identifier": "Equity",
            "weight": 0.60,
            "return": 0.04,
        },
        {
            "from_date": "2024-01-01",
            "thru_date": "2024-01-31",
            "identifier": "Bonds",
            "weight": 0.40,
            "return": 0.01,
        },
    ]
)
benchmark = pd.DataFrame(
    [
        {
            "from_date": "2024-01-01",
            "thru_date": "2024-01-31",
            "identifier": "Equity",
            "weight": 0.50,
            "return": 0.03,
        },
        {
            "from_date": "2024-01-01",
            "thru_date": "2024-01-31",
            "identifier": "Bonds",
            "weight": 0.50,
            "return": 0.015,
        },
    ]
)

prepared = prepare_attribution(portfolio, benchmark)
result = calculate_attribution(prepared.portfolio, prepared.benchmark)
print(result.period_detail)

Attribution methods

The default remains the released two-effect Brinson-Fachler convention: allocation is reported separately, while portfolio-weighted selection absorbs interaction. The public method enum also provides an explicit-interaction Brinson-Fachler calculation and compact or explicit-interaction Brinson-Hood-Beebower (BHB) calculations:

from perfattr import AttributionMethod, calculate_attribution

three_effect = calculate_attribution(
    prepared.portfolio,
    prepared.benchmark,
    method=AttributionMethod.BRINSON_FACHLER_THREE_EFFECT,
)
bhb_three_effect = calculate_attribution(
    prepared.portfolio,
    prepared.benchmark,
    method=AttributionMethod.BRINSON_HOOD_BEEBOWER_THREE_EFFECT,
)
bhb_two_effect = calculate_attribution(
    prepared.portfolio,
    prepared.benchmark,
    method=AttributionMethod.BRINSON_HOOD_BEEBOWER_TWO_EFFECT,
)
print(
    three_effect.period_detail[
        ["allocation_effect", "selection_effect", "interaction_effect"]
    ]
)

For portfolio and benchmark weights wP and wB, effective returns rP and rB, and total benchmark return B, the explicit effects are:

Brinson-Fachler allocation = (wP - wB) * (rB - B)
BHB allocation             = (wP - wB) * rB
selection                  = wB * (rP - rB)
interaction                = (wP - wB) * (rP - rB)
compact selection          = total - allocation

BHB uses the group's absolute benchmark return, so overweighting a positive-return group produces positive allocation even when that return trails the total benchmark. Brinson-Fachler instead measures the group return relative to the total benchmark; the two methods can therefore assign opposite allocation signs. perfattr reports the signed formulas without labeling an effect favorable or unfavorable.

At identifier level, BHB total_effect is unadjusted active contribution, while the Brinson-Fachler total includes its benchmark-relative adjustment. With exactly normalized portfolio and benchmark weights, their period totals agree. Supplied contribution remains authoritative. If either effective return is undefined, interaction is zero and selection retains the reconciled residual rather than inventing a return.

Compact BHB is a derived perfattr reporting convention, not a claim that the original BHB methodology defined a historical two-effect model. It retains BHB allocation and total, omits the interaction column, and reports selection directly as total_effect - allocation_effect. When returns are defined, compact BHB and compact Brinson-Fachler therefore share portfolio-weighted selection but can assign different identifier-level allocation and total values.

The opt-in result inserts interaction_effect immediately after selection_effect and linked_interaction_effect immediately after linked_selection_effect wherever those channels apply. Cumulative output also places cumulative_interaction_effect immediately after cumulative_selection_effect. AttributionResult.method records the selected convention. See the Brinson-Fachler specification, BHB three-effect specification, and compact BHB specification for the complete schemas, linking rules, null policies, and independently calculated examples.

Canonical CSV inputs can be loaded with read_performance_csv; optional mapping and classification readers are also available at the package root.

Mapping CSV files are headerless and use one uniform form per file. Existing static files remain two columns (identifier,classification_identifier). Effective-dated files use four columns in the order from_date,thru_date,identifier, classification_identifier. For example:

2024-01-01,2024-01-31,ASSET,Equity
2024-02-01,2024-12-31,ASSET,Fixed Income

The dates are closed and inclusive. A source period for a mapped identifier must be contained in exactly one assignment; perfattr does not split a source period at a classification boundary.

Cash, fees, and financing

Cash receives no special treatment: supply it as an ordinary identifier, or map it to a Cash classification, with the weight and return chosen by the host accounting system. perfattr never invents cash or hides a residual in it.

A fee or financing charge without exposure can be supplied as a zero-weight row with an authoritative nonzero contribution and a null return. The contribution is preserved and included in the ordinary attribution and linking calculations; perfattr does not infer the row from its name or calculate the charge. Financing with an explicit exposure and return can instead be represented as an ordinary identifier.

Development

Create and activate a virtual environment:

python3 -m venv .venv
source .venv/bin/activate

Install the package and development dependencies:

python -m pip install --editable ".[dev]"

Run the initial checks:

python -m pytest
python -m pylint src/perfattr tests scripts
python -m pyright

Run the four roadmap performance workloads:

python scripts/benchmark_core.py --samples 5
python scripts/benchmark_core.py --samples 5 --input-form authoritative
python scripts/benchmark_preparation.py --samples 5

Pass --method three-effect, --method bhb-three-effect, or --method bhb-two-effect to benchmark_core.py to measure an opt-in calculation; the default remains --method two-effect.

Add --workload monthly_121260 --profile to inspect one workload's cumulative calculation-core call profile. The preparation benchmark compares static and effective-dated mappings through quarterly consolidation. The benchmark methodology and observations are recorded in docs/performance.md.

License

perfattr is distributed under the MIT License.

Download files

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

Source Distribution

perfattr-0.6.0a1.tar.gz (123.0 kB view details)

Uploaded Source

Built Distribution

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

perfattr-0.6.0a1-py3-none-any.whl (52.7 kB view details)

Uploaded Python 3

File details

Details for the file perfattr-0.6.0a1.tar.gz.

File metadata

  • Download URL: perfattr-0.6.0a1.tar.gz
  • Upload date:
  • Size: 123.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for perfattr-0.6.0a1.tar.gz
Algorithm Hash digest
SHA256 be502c3c95bec72f0c4571faabd59ef2fd67cfb9d207072fc5f1d364e55ae491
MD5 bb24ab4bce622b3fc18288b10b6836c7
BLAKE2b-256 b0cf4d95a7f35206b3df2dbe417d88b2b03ae94066eb120166c24449f55c1110

See more details on using hashes here.

Provenance

The following attestation bundles were made for perfattr-0.6.0a1.tar.gz:

Publisher: publish.yml on JohnDReynolds/perfattr

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

File details

Details for the file perfattr-0.6.0a1-py3-none-any.whl.

File metadata

  • Download URL: perfattr-0.6.0a1-py3-none-any.whl
  • Upload date:
  • Size: 52.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for perfattr-0.6.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 715523d7576b247cdf1a0be01b5350e484a862ea3be9752917390a3a5734a88d
MD5 80b0f7b7bf28c3cc1509a357082ed7e7
BLAKE2b-256 00b5f86c8d79e47b62564f053883102ce8606538acb06c549e22b55191bb7bf1

See more details on using hashes here.

Provenance

The following attestation bundles were made for perfattr-0.6.0a1-py3-none-any.whl:

Publisher: publish.yml on JohnDReynolds/perfattr

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

Release history Release notifications | RSS feed

This release

0.6.0a1 This release

2 files

0.3.0

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page