Performance Attribution
Core Concepts
Brinson-Fachler Attribution (Single Period)
The classic equity attribution model decomposes active return (portfolio return minus benchmark return) into three effects:
- Allocation effect: Value added by over/underweighting sectors relative to the benchmark
- A_i = (w_p,i - w_b,i) × (R_b,i - R_b)
- Rewards overweighting sectors that outperform the total benchmark
- Selection effect: Value added by picking better securities within each sector
- S_i = w_b,i × (R_p,i - R_b,i)
- Rewards outperforming the sector benchmark regardless of weight
- Interaction effect: Combined effect of both overweighting and outperforming (or vice versa)
- I_i = (w_p,i - w_b,i) × (R_p,i - R_b,i)
- Captures the joint benefit of overweighting a sector AND selecting better securities in it
- Total active return: R_p - R_b = Σ A_i + Σ S_i + Σ I_i
Where: w_p,i = portfolio weight in sector i, w_b,i = benchmark weight in sector i, R_p,i = portfolio return in sector i, R_b,i = benchmark return in sector i, R_b = total benchmark return.
Multi-Period Attribution
Single-period attribution does not compound across periods. Geometric linking methods are required:
- Carino method: Applies a smoothing factor to make arithmetic effects compound to the correct geometric total
- Menchero method: Uses a logarithmic approach for smoother decomposition
- GRAP (Geometric Return Attribution Program): Converts arithmetic effects to geometric equivalents
- Key principle: the sum of linked attribution effects must equal the total geometric active return over the full period
Factor-Based Attribution
Decomposes returns into exposures to systematic risk factors:
- Model: R_p = Σ β_k × F_k + α
- β_k = portfolio's exposure (loading) to factor k
- F_k = return of factor k during the period
- α = residual return unexplained by factors (true alpha)
- Common factors: Market (MKT), Size (SMB), Value (HML), Momentum (UMD), Quality (QMJ), Low Volatility (BAB)
- Factor contribution: β_k × F_k for each factor
- Active factor contribution: (β_p,k - β_b,k) × F_k
- The model chosen (Fama-French 3, Carhart 4, Fama-French 5, Barra, Axioma) affects results
Fixed-Income Attribution
Decomposes bond portfolio returns into component sources:
- Yield return (income): Coupon income accrued during the period (yield × time)
- Roll return: Price appreciation as bonds "roll down" the yield curve toward maturity
- Curve change return: Impact of parallel and non-parallel yield curve shifts
- Duration effect: -D × Δy (parallel shift)
- Curve reshaping: key rate duration contributions
- Spread change return: Impact of credit spread changes: -spread_duration × Δspread
- Credit/default return: Losses from defaults or credit events
- Residual: Unexplained return (convexity effects, model error)
Currency Attribution
For international portfolios, returns decompose into:
- Local return: Return of the asset in its local currency
- Currency return: Gain/loss from exchange rate movements
- Cross-product: Interaction between local return and currency return
- Total return (base currency): R_base ≈ R_local + R_currency + R_local × R_currency
- Hedged return: Local return + hedge cost (forward premium/discount)
- Attribution of active currency decisions: actual currency exposure vs benchmark currency exposure
Holdings-Based vs Returns-Based Attribution
- Holdings-based: Uses actual portfolio positions; more accurate but requires detailed holdings data at each evaluation point
- Returns-based (style analysis): Regresses portfolio returns against a set of style indices (e.g., Sharpe style analysis); less precise but requires only return series
- Transaction-based: Most accurate; accounts for intra-period trading by using actual transaction records
Key Formulas
| Formula |
Expression |
Use Case |
| Allocation effect (sector i) |
A_i = (w_p,i - w_b,i) × (R_b,i - R_b) |
Sector weighting decisions |
| Selection effect (sector i) |
S_i = w_b,i × (R_p,i - R_b,i) |
Security selection within sector |
| Interaction effect (sector i) |
I_i = (w_p,i - w_b,i) × (R_p,i - R_b,i) |
Joint allocation-selection effect |
| Total active return |
R_p - R_b = Σ(A_i + S_i + I_i) |
Sum of all effects equals active return |
| Factor return contribution |
C_k = β_k × F_k |
Return from factor k exposure |
| Duration effect |
ΔP/P ≈ -D × Δy |
Bond price change from yield shift |
| Currency return |
R_fx = (S_end - S_start) / S_start |
Exchange rate impact |
Worked Examples
Example 1: Brinson-Fachler equity attribution
Given: Two-sector portfolio (Tech and Healthcare). Portfolio: 35% Tech (returned 15%), 65% Healthcare (returned 8%). Benchmark: 25% Tech (returned 12%), 75% Healthcare (returned 6%). Total benchmark return: 0.25×12% + 0.75×6% = 7.5%.
Calculate: Allocation, selection, and interaction effects for each sector, and total active return.
Solution:
- Total portfolio return: 0.35×15% + 0.65×8% = 5.25% + 5.20% = 10.45%.
- Total active return: 10.45% - 7.50% = 2.95%.
- Tech allocation effect: (0.35 - 0.25) × (12% - 7.5%) = 0.10 × 4.5% = +0.45% (overweight a sector that beat the benchmark).
- Tech selection effect: 0.25 × (15% - 12%) = 0.25 × 3% = +0.75% (stock picks in Tech beat Tech benchmark).
- Tech interaction effect: (0.35 - 0.25) × (15% - 12%) = 0.10 × 3% = +0.30% (overweight AND outperformed).
- Healthcare allocation effect: (0.65 - 0.75) × (6% - 7.5%) = -0.10 × -1.5% = +0.15% (underweight a sector that lagged the benchmark).
- Healthcare selection effect: 0.75 × (8% - 6%) = 0.75 × 2% = +1.50% (stock picks in Healthcare beat Healthcare benchmark).
- Healthcare interaction effect: (0.65 - 0.75) × (8% - 6%) = -0.10 × 2% = -0.20% (underweight but outperformed — interaction is negative).
- Totals: Allocation = 0.45 + 0.15 = 0.60%. Selection = 0.75 + 1.50 = 2.25%. Interaction = 0.30 + (-0.20) = 0.10%. Sum = 0.60 + 2.25 + 0.10 = 2.95% ✓.
Example 2: Factor-based attribution
Given: A fund has factor loadings: β_mkt = 1.1, β_smb = 0.3, β_hml = -0.2. During the period: MKT = 5%, SMB = 2%, HML = -1%. Risk-free rate = 1%. Fund excess return = 7%.
Calculate: Factor contributions and alpha.
Solution:
- Market contribution: 1.1 × 5% = 5.50%.
- Size (SMB) contribution: 0.3 × 2% = 0.60%.
- Value (HML) contribution: -0.2 × (-1%) = +0.20%.
- Total factor-explained return: 5.50 + 0.60 + 0.20 = 6.30%.
- Alpha (residual): 7.00% - 6.30% = +0.70%.
- Interpretation: The fund's excess return of 7% is mostly explained by above-market beta (5.5%) and a small-cap tilt (0.6%). The negative value loading helped (+0.2%) as value underperformed. After accounting for all factors, the manager generated 0.70% of true alpha.
Common Pitfalls
- Interaction effect is hard to interpret — some attribution models fold it into allocation or selection, which changes reported results significantly
- Multi-period attribution requires geometric linking — simple arithmetic attribution does not compound correctly and residuals grow over time
- Returns-based attribution (style analysis) may not reflect actual holdings, especially for managers who trade actively or change style
- Factor attribution results depend heavily on the chosen factor model — different models yield different alpha estimates
- Currency attribution is often overlooked in international portfolios, hiding or inflating apparent skill
- Survivorship bias in manager evaluation: only surviving funds are analyzed, overstating average skill
- Confusing gross-of-fee and net-of-fee returns when comparing to benchmarks
- Using inappropriate benchmarks that do not match the portfolio's investment universe
Cross-References
- investment-policy (wealth-management plugin): Benchmark selection in IPS directly feeds performance attribution analysis
- tax-efficiency (wealth-management plugin): After-tax attribution requires adjusting returns for tax impact
- savings-goals (wealth-management plugin): Attribution helps assess whether investment strategy is on track to meet goals
- liquidity-management (wealth-management plugin): Cash drag from liquidity reserves affects portfolio-level attribution
- client-review-prep (advisory-practice plugin): attribution analysis highlights are key talking points in client review meetings
- tax-loss-harvesting (wealth-management plugin): tax alpha from TLH should be tracked and attributed separately
Running the script
Run with uv run scripts/performance_attribution.py (the PEP 723 header resolves numpy automatically) or with python3 scripts/performance_attribution.py after pip install numpy scipy. A bare run prints three demos: the Brinson-Fachler attribution from Worked Example 1, an OLS factor attribution on seeded synthetic data, and Carino multi-period linking. Use --verify to assert outputs match this skill's worked example numbers (exit code 0 on PASS) and --help for an overview of the classes. The file is primarily meant to be imported as a module (e.g., from performance_attribution import BrinsonFachler).
1---2name: performance-attribution3description: Decompose portfolio returns into explainable components to identify where value was added or lost. Use when the user asks about Brinson attribution, allocation vs selection effects, factor-based attribution, fixed-income attribution, or currency attribution. Also trigger when users mention 'what drove my returns', 'was it stock picking or sector bets', 'alpha decomposition', 'multi-period linking', 'interaction effect', 'active return breakdown', or ask why their portfolio outperformed or underperformed the benchmark.4---5
6# Performance Attribution
7
8## Core Concepts
9
10### Brinson-Fachler Attribution (Single Period)
11The classic equity attribution model decomposes active return (portfolio return minus benchmark return) into three effects:
12
13- **Allocation effect:** Value added by over/underweighting sectors relative to the benchmark
14 - A_i = (w_p,i - w_b,i) × (R_b,i - R_b)
15 - Rewards overweighting sectors that outperform the total benchmark
16- **Selection effect:** Value added by picking better securities within each sector
17 - S_i = w_b,i × (R_p,i - R_b,i)
18 - Rewards outperforming the sector benchmark regardless of weight
19- **Interaction effect:** Combined effect of both overweighting and outperforming (or vice versa)
20 - I_i = (w_p,i - w_b,i) × (R_p,i - R_b,i)
21 - Captures the joint benefit of overweighting a sector AND selecting better securities in it
22- **Total active return:** R_p - R_b = Σ A_i + Σ S_i + Σ I_i
23
24Where: w_p,i = portfolio weight in sector i, w_b,i = benchmark weight in sector i, R_p,i = portfolio return in sector i, R_b,i = benchmark return in sector i, R_b = total benchmark return.
25
26### Multi-Period Attribution
27Single-period attribution does not compound across periods. Geometric linking methods are required:
28
29- **Carino method:** Applies a smoothing factor to make arithmetic effects compound to the correct geometric total
30- **Menchero method:** Uses a logarithmic approach for smoother decomposition
31- **GRAP (Geometric Return Attribution Program):** Converts arithmetic effects to geometric equivalents
32- Key principle: the sum of linked attribution effects must equal the total geometric active return over the full period
33
34### Factor-Based Attribution
35Decomposes returns into exposures to systematic risk factors:
36
37- **Model:** R_p = Σ β_k × F_k + α
38 - β_k = portfolio's exposure (loading) to factor k
39 - F_k = return of factor k during the period
40 - α = residual return unexplained by factors (true alpha)
41- **Common factors:** Market (MKT), Size (SMB), Value (HML), Momentum (UMD), Quality (QMJ), Low Volatility (BAB)
42- **Factor contribution:** β_k × F_k for each factor
43- **Active factor contribution:** (β_p,k - β_b,k) × F_k
44- The model chosen (Fama-French 3, Carhart 4, Fama-French 5, Barra, Axioma) affects results
45
46### Fixed-Income Attribution
47Decomposes bond portfolio returns into component sources:
48
49- **Yield return (income):** Coupon income accrued during the period (yield × time)
50- **Roll return:** Price appreciation as bonds "roll down" the yield curve toward maturity
51- **Curve change return:** Impact of parallel and non-parallel yield curve shifts
52 - Duration effect: -D × Δy (parallel shift)
53 - Curve reshaping: key rate duration contributions
54- **Spread change return:** Impact of credit spread changes: -spread_duration × Δspread
55- **Credit/default return:** Losses from defaults or credit events
56- **Residual:** Unexplained return (convexity effects, model error)
57
58### Currency Attribution
59For international portfolios, returns decompose into:
60
61- **Local return:** Return of the asset in its local currency
62- **Currency return:** Gain/loss from exchange rate movements
63- **Cross-product:** Interaction between local return and currency return
64- **Total return (base currency):** R_base ≈ R_local + R_currency + R_local × R_currency
65- **Hedged return:** Local return + hedge cost (forward premium/discount)
66- Attribution of active currency decisions: actual currency exposure vs benchmark currency exposure
67
68### Holdings-Based vs Returns-Based Attribution
69- **Holdings-based:** Uses actual portfolio positions; more accurate but requires detailed holdings data at each evaluation point
70- **Returns-based (style analysis):** Regresses portfolio returns against a set of style indices (e.g., Sharpe style analysis); less precise but requires only return series
71- **Transaction-based:** Most accurate; accounts for intra-period trading by using actual transaction records
72
73## Key Formulas
74
75| Formula | Expression | Use Case |
76|---------|-----------|----------|
77| Allocation effect (sector i) | A_i = (w_p,i - w_b,i) × (R_b,i - R_b) | Sector weighting decisions |
78| Selection effect (sector i) | S_i = w_b,i × (R_p,i - R_b,i) | Security selection within sector |
79| Interaction effect (sector i) | I_i = (w_p,i - w_b,i) × (R_p,i - R_b,i) | Joint allocation-selection effect |
80| Total active return | R_p - R_b = Σ(A_i + S_i + I_i) | Sum of all effects equals active return |
81| Factor return contribution | C_k = β_k × F_k | Return from factor k exposure |
82| Duration effect | ΔP/P ≈ -D × Δy | Bond price change from yield shift |
83| Currency return | R_fx = (S_end - S_start) / S_start | Exchange rate impact |
84
85## Worked Examples
86
87### Example 1: Brinson-Fachler equity attribution
88**Given:** Two-sector portfolio (Tech and Healthcare). Portfolio: 35% Tech (returned 15%), 65% Healthcare (returned 8%). Benchmark: 25% Tech (returned 12%), 75% Healthcare (returned 6%). Total benchmark return: 0.25×12% + 0.75×6% = 7.5%.
89**Calculate:** Allocation, selection, and interaction effects for each sector, and total active return.
90**Solution:**
911. **Total portfolio return:** 0.35×15% + 0.65×8% = 5.25% + 5.20% = 10.45%.
922. **Total active return:** 10.45% - 7.50% = **2.95%**.
933. **Tech allocation effect:** (0.35 - 0.25) × (12% - 7.5%) = 0.10 × 4.5% = **+0.45%** (overweight a sector that beat the benchmark).
944. **Tech selection effect:** 0.25 × (15% - 12%) = 0.25 × 3% = **+0.75%** (stock picks in Tech beat Tech benchmark).
955. **Tech interaction effect:** (0.35 - 0.25) × (15% - 12%) = 0.10 × 3% = **+0.30%** (overweight AND outperformed).
966. **Healthcare allocation effect:** (0.65 - 0.75) × (6% - 7.5%) = -0.10 × -1.5% = **+0.15%** (underweight a sector that lagged the benchmark).
977. **Healthcare selection effect:** 0.75 × (8% - 6%) = 0.75 × 2% = **+1.50%** (stock picks in Healthcare beat Healthcare benchmark).
988. **Healthcare interaction effect:** (0.65 - 0.75) × (8% - 6%) = -0.10 × 2% = **-0.20%** (underweight but outperformed — interaction is negative).
999. **Totals:** Allocation = 0.45 + 0.15 = **0.60%**. Selection = 0.75 + 1.50 = **2.25%**. Interaction = 0.30 + (-0.20) = **0.10%**. Sum = 0.60 + 2.25 + 0.10 = **2.95%** ✓.
100
101### Example 2: Factor-based attribution
102**Given:** A fund has factor loadings: β_mkt = 1.1, β_smb = 0.3, β_hml = -0.2. During the period: MKT = 5%, SMB = 2%, HML = -1%. Risk-free rate = 1%. Fund excess return = 7%.
103**Calculate:** Factor contributions and alpha.
104**Solution:**
1051. **Market contribution:** 1.1 × 5% = **5.50%**.
1062. **Size (SMB) contribution:** 0.3 × 2% = **0.60%**.
1073. **Value (HML) contribution:** -0.2 × (-1%) = **+0.20%**.
1084. **Total factor-explained return:** 5.50 + 0.60 + 0.20 = **6.30%**.
1095. **Alpha (residual):** 7.00% - 6.30% = **+0.70%**.
1106. **Interpretation:** The fund's excess return of 7% is mostly explained by above-market beta (5.5%) and a small-cap tilt (0.6%). The negative value loading helped (+0.2%) as value underperformed. After accounting for all factors, the manager generated 0.70% of true alpha.
111
112## Common Pitfalls
113- Interaction effect is hard to interpret — some attribution models fold it into allocation or selection, which changes reported results significantly
114- Multi-period attribution requires geometric linking — simple arithmetic attribution does not compound correctly and residuals grow over time
115- Returns-based attribution (style analysis) may not reflect actual holdings, especially for managers who trade actively or change style
116- Factor attribution results depend heavily on the chosen factor model — different models yield different alpha estimates
117- Currency attribution is often overlooked in international portfolios, hiding or inflating apparent skill
118- Survivorship bias in manager evaluation: only surviving funds are analyzed, overstating average skill
119- Confusing gross-of-fee and net-of-fee returns when comparing to benchmarks
120- Using inappropriate benchmarks that do not match the portfolio's investment universe
121
122## Cross-References
123- **investment-policy** (wealth-management plugin): Benchmark selection in IPS directly feeds performance attribution analysis
124- **tax-efficiency** (wealth-management plugin): After-tax attribution requires adjusting returns for tax impact
125- **savings-goals** (wealth-management plugin): Attribution helps assess whether investment strategy is on track to meet goals
126- **liquidity-management** (wealth-management plugin): Cash drag from liquidity reserves affects portfolio-level attribution
127- **client-review-prep** (advisory-practice plugin): attribution analysis highlights are key talking points in client review meetings
128- **tax-loss-harvesting** (wealth-management plugin): tax alpha from TLH should be tracked and attributed separately
129
130## Running the script
131Run with `uv run scripts/performance_attribution.py` (the PEP 723 header resolves numpy automatically) or with `python3 scripts/performance_attribution.py` after `pip install numpy scipy`. A bare run prints three demos: the Brinson-Fachler attribution from Worked Example 1, an OLS factor attribution on seeded synthetic data, and Carino multi-period linking. Use `--verify` to assert outputs match this skill's worked example numbers (exit code 0 on PASS) and `--help` for an overview of the classes. The file is primarily meant to be imported as a module (e.g., `from performance_attribution import BrinsonFachler`).