Contribution to Return¶
1. Calculation Name¶
Contribution to Return (CTR)
2. Description and Mathematical Formula¶
CTR attributes portfolio performance by multiplying each segment’s weight by its return. The excess contribution compares portfolio and benchmark contributions.
For segment \( i \):
\[ \text{CTR}^P_i = w^P_i \times r^P_i, \quad \text{CTR}^B_i = w^B_i \times r^B_i, \quad \text{Active}_i = \text{CTR}^P_i - \text{CTR}^B_i. \]
3. Input Sample Data¶
| Segment | Portfolio Weight | Portfolio Return | Benchmark Weight | Benchmark Return |
|---|---|---|---|---|
| Government | 35% | 2.10% | 40% | 1.80% |
| Credit | 30% | 4.50% | 25% | 3.80% |
| Mortgages | 15% | 3.20% | 20% | 3.00% |
| High Yield | 10% | 6.50% | 5% | 5.00% |
| Cash | 10% | 0.50% | 10% | 0.40% |
4. Mathematical Solution¶
- Portfolio contribution (bps):
Government \(= 0.35 \times 2.10\% = 0.735\%\)
Credit \(= 0.30 \times 4.50\% = 1.350\%\)
Mortgages \(= 0.15 \times 3.20\% = 0.480\%\)
High Yield \(= 0.10 \times 6.50\% = 0.650\%\)
Cash \(= 0.10 \times 0.50\% = 0.050\%\) - Benchmark contribution:
Government \(= 0.40 \times 1.80\% = 0.720\%\)
Credit \(= 0.25 \times 3.80\% = 0.950\%\)
Mortgages \(= 0.20 \times 3.00\% = 0.600\%\)
High Yield \(= 0.05 \times 5.00\% = 0.250\%\)
Cash \(= 0.10 \times 0.40\% = 0.040\%\) - Active contribution (bps):
Government \(= 0.015\%\)
Credit \(= 0.400\%\)
Mortgages \(= -0.120\%\)
High Yield \(= 0.400\%\)
Cash \(= 0.010\%\) - Totals:
Portfolio contribution \(= 3.265\%\)
Benchmark contribution \(= 2.560\%\)
Excess contribution \(= 0.705\%\)
5. Sample Python and R Code¶
import pandas as pd
data = pd.DataFrame(
{
"segment": ["Government", "Credit", "Mortgages", "High Yield", "Cash"],
"w_port": [0.35, 0.30, 0.15, 0.10, 0.10],
"r_port": [0.021, 0.045, 0.032, 0.065, 0.005],
"w_bench": [0.40, 0.25, 0.20, 0.05, 0.10],
"r_bench": [0.018, 0.038, 0.030, 0.050, 0.004],
}
)
data["ctr_port"] = data["w_port"] * data["r_port"]
data["ctr_bench"] = data["w_bench"] * data["r_bench"]
data["ctr_active"] = data["ctr_port"] - data["ctr_bench"]
totals = data[["ctr_port", "ctr_bench", "ctr_active"]].sum()
print(data)
print(totals)
library(dplyr)
data <- tibble::tibble(
segment = c("Government", "Credit", "Mortgages", "High Yield", "Cash"),
w_port = c(0.35, 0.30, 0.15, 0.10, 0.10),
r_port = c(0.021, 0.045, 0.032, 0.065, 0.005),
w_bench = c(0.40, 0.25, 0.20, 0.05, 0.10),
r_bench = c(0.018, 0.038, 0.030, 0.050, 0.004)
)
data <- data %>%
mutate(
ctr_port = w_port * r_port,
ctr_bench = w_bench * r_bench,
ctr_active = ctr_port - ctr_bench
)
totals <- summarise(data,
ctr_port = sum(ctr_port),
ctr_bench = sum(ctr_bench),
ctr_active = sum(ctr_active)
)
data
totals
6. Output Table¶
| Segment | Portfolio CTR | Benchmark CTR | Active (bps) |
|---|---|---|---|
| Government | 0.735% | 0.720% | +1.5 |
| Credit | 1.350% | 0.950% | +40.0 |
| Mortgages | 0.480% | 0.600% | -12.0 |
| High Yield | 0.650% | 0.250% | +40.0 |
| Cash | 0.050% | 0.040% | +1.0 |
| Total | 3.265% | 2.560% | +70.5 |
7. Conclusion¶
CTR quantifies how much each sleeve contributes to portfolio and benchmark performance. FinFacts uses this foundation for downstream attribution charts and sanity checks, making this template useful for QA notebooks and training decks.