Performance Attribution and Investment Alpha › Combined Attribution Example

Michael BrenndoerferDecember 17, 20253 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 50Performance Attribution and Investment Alpha › Combined Attribution Example
Reading section 6 of 10 · Combined Attribution Example · 3 minSection 6 of 10 · 3 min

Book contents

Section 6 of 10 · 3 min read

Performance Attribution and Investment Alpha › Combined Attribution ExampleOne section · Sections in this chapter6 / 10 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's bring together Brinson and factor-based attribution in a combined analysis that demonstrates how both frameworks complement each other.

In[32]:
Code
import numpy as np

# Comprehensive attribution for a hypothetical equity portfolio
# Using realistic sector and factor data

np.random.seed(123)

# Define sectors and their characteristics
sector_names = [
    "Technology",
    "Healthcare",
    "Financials",
    "Consumer Disc.",
    "Industrials",
]
n_sectors = len(sector_names)

# Portfolio construction
port_weights = np.array([0.35, 0.20, 0.15, 0.18, 0.12])
bench_weights = np.array(
    [0.28, 0.14, 0.13, 0.10, 0.11]
)  # Sum less than 1 (other sectors)
bench_weights = bench_weights / bench_weights.sum()  # Normalize

# Sector returns (one period)
bench_sector_returns = np.array([0.082, 0.045, 0.058, 0.035, 0.042])
port_sector_returns = np.array([0.095, 0.052, 0.048, 0.038, 0.055])
In[33]:
Code
# Brinson Attribution
total_port_ret = np.sum(port_weights * port_sector_returns)
total_bench_ret = np.sum(bench_weights * bench_sector_returns)
active_ret = total_port_ret - total_bench_ret

weight_diff = port_weights - bench_weights
return_diff = port_sector_returns - bench_sector_returns
sector_excess_ret = bench_sector_returns - total_bench_ret

alloc = weight_diff * sector_excess_ret
selec = bench_weights * return_diff
inter = weight_diff * return_diff
total_active = np.sum(alloc) + np.sum(selec) + np.sum(inter)
Out[34]:
Console
============================================================
COMPREHENSIVE PERFORMANCE ATTRIBUTION
============================================================

1. HOLDINGS-BASED (BRINSON) ATTRIBUTION
--------------------------------------------------

Total Portfolio Return:  6.43%
Total Benchmark Return:  5.91%
Active Return:           0.52%

Allocation Effect:   -0.14%
Selection Effect:    +0.66%
Interaction Effect:  -0.01%
Total:               +0.52%
In[35]:
Code
# Sector-level Brinson breakdown data
brinson_df = pd.DataFrame(
    {
        "Sector": sector_names,
        "Port Wgt": port_weights,
        "Bench Wgt": bench_weights,
        "Allocation": alloc,
        "Selection": selec,
        "Interaction": inter,
        "Total Contrib": alloc + selec + inter,
    }
)
Out[36]:
Console

Sector-Level Breakdown:
--------------------------------------------------

Technology:
  Weights: Port 35.0% vs Bench 36.8%
  Allocation: -0.04%, Selection: +0.48%

Healthcare:
  Weights: Port 20.0% vs Bench 18.4%
  Allocation: -0.02%, Selection: +0.13%

Financials:
  Weights: Port 15.0% vs Bench 17.1%
  Allocation: +0.00%, Selection: -0.17%

Consumer Disc.:
  Weights: Port 18.0% vs Bench 13.2%
  Allocation: -0.12%, Selection: +0.04%

Industrials:
  Weights: Port 12.0% vs Bench 14.5%
  Allocation: +0.04%, Selection: +0.19%
Out[37]:
Visualization
Grouped bar chart comparing allocation, selection, and interaction effects across Technology, Healthcare, Financials, Consumer Discretionary, and Industrials; Technology has the largest positive selection effect and Financials the largest negative selection effect.
Sector-level Brinson attribution for the comprehensive example. Technology is the primary positive driver because of selection despite a small negative allocation effect. Financials detracts through selection, while Industrials and Healthcare add positive selection contributions.

Interpreting Attribution Results

The Brinson analysis shows that the portfolio's {python} f"{total_active:.2%}" outperformance is driven by selection, which contributes {python} f"{np.sum(selec):+.2%}" overall. Technology provides the largest selection contribution ({python} f"{selec[0]:+.2%}"), followed by Industrials ({python} f"{selec[4]:+.2%}"), while Financials detracts ({python} f"{selec[2]:+.2%}"). Allocation subtracts {python} f"{np.sum(alloc):+.2%}" in aggregate, including a small negative Technology allocation effect of {python} f"{alloc[0]:+.2%}".

From a factor perspective, a manager might further analyze whether the Technology outperformance came from factor tilts within tech (e.g., overweighting growth-oriented tech stocks) or from stock selection orthogonal to known factors. This level of analysis requires combining holdings data with factor exposure data, a technique used by sophisticated institutional investors.