Skip to content

Performance Attribution

Brinson decomposition splits portfolio active return into allocation and selection effects — answering "did we beat the benchmark by picking the right sectors or the right stocks?"

Functions

Function Description
brinson_attribution(...) Three-factor BHB: allocation + selection + interaction
two_factor_brinson(...) Two-factor: allocation + (selection inc. interaction)
information_ratio(rp, rb) Annualized IR = active return / tracking error
tracking_error(rp, rb) Annualized std of active returns

Brinson-Hood-Beebower Decomposition

Allocation_i  = (w_p,i - w_b,i) * (r_b,i - r_b)
Selection_i   = w_b,i * (r_p,i - r_b,i)
Interaction_i = (w_p,i - w_b,i) * (r_p,i - r_b,i)

Active = sum(Allocation) + sum(Selection) + sum(Interaction)

Example

from performance_attribution import brinson_attribution, information_ratio

res = brinson_attribution(wp, rp, wb, rb)
print(res['total_allocation'], res['total_selection'])

ir = information_ratio(daily_port, daily_bench)

Practical Notes

  • Allocation > 0: overweighted sectors that beat the benchmark.
  • Selection > 0: stocks within sectors outperformed sector index.
  • Interaction is small and often combined with selection (two-factor model).
  • IR > 0.5 is good; > 1.0 is exceptional.