Time-Weighted Return¶
1. Calculation Name¶
Time-Weighted Rate of Return (TWR)
2. Description and Mathematical Formula¶
The time-weighted return neutralizes the impact of external cash flows by breaking the period into sub-periods bounded by flows and geometrically chaining the returns.
For sub-period \( k \):
- \( V_{k,0} \) = market value at the start of sub-period \( k \)
- \( V_{k,1} \) = market value just before the next external flow
- \( r_k = \frac{V_{k,1} - V_{k,0}}{V_{k,0}} \)
The TWR over \( n \) sub-periods is
\[ R_{\text{TWR}} = \prod_{k=1}^{n} (1 + r_k) - 1. \]
3. Input Sample Data¶
| Sub-period | Start Date | End Date | Start MV (USD) | End MV before Flow (USD) | Net Flow at End (USD) |
|---|---|---|---|---|---|
| 1 | 2024-01-01 | 2024-01-07 | 1,000,000 | 1,030,000 | +50,000 |
| 2 | 2024-01-07 | 2024-01-15 | 1,080,000 | 1,150,000 | -20,000 |
| 3 | 2024-01-15 | 2024-01-25 | 1,130,000 | 1,160,000 | +10,000 |
| 4 | 2024-01-25 | 2024-01-31 | 1,170,000 | 1,180,000 | 0 |
| Ending Value | 2024-01-31 | — | — | 1,180,000 | — |
4. Mathematical Solution¶
- Sub-period returns:
\( r_1 = (1{,}030{,}000 - 1{,}000{,}000) / 1{,}000{,}000 = 3.00\% \)
\( r_2 = (1{,}150{,}000 - 1{,}080{,}000) / 1{,}080{,}000 = 1.77\% \)
\( r_3 = (1{,}160{,}000 - 1{,}130{,}000) / 1{,}130{,}000 = 2.65\% \)
\( r_4 = (1{,}180{,}000 - 1{,}170{,}000) / 1{,}170{,}000 = 0.85\% \) - Chain link:
\( R_{\text{TWR}} = (1.0300) \times (1.0177) \times (1.0265) \times (1.0085) - 1 = 8.53\% \)
5. Sample Python and R Code¶
import pandas as pd
import numpy as np
data = pd.DataFrame(
{
"period": [1, 2, 3, 4],
"start_mv": [1_000_000, 1_080_000, 1_130_000, 1_170_000],
"end_mv": [1_030_000, 1_150_000, 1_160_000, 1_180_000],
}
)
data["return"] = (data["end_mv"] - data["start_mv"]) / data["start_mv"]
twrr = np.prod(1 + data["return"]) - 1
print(f"TWR: {twrr:.4%}")
library(dplyr)
data <- tibble::tibble(
period = 1:4,
start_mv = c(1000000, 1080000, 1130000, 1170000),
end_mv = c(1030000, 1150000, 1160000, 1180000)
)
data <- data %>%
mutate(ret = (end_mv - start_mv) / start_mv)
twrr <- prod(1 + data$ret) - 1
scales::percent(twrr, accuracy = 0.01)
6. Output Table¶
| Metric | Value |
|---|---|
| Time-Weighted Return | 8.53% |
| Number of Linked Periods | 4 |
| Product of (1 + r_k) | 1.0853 |
7. Conclusion¶
TWR isolates manager skill by neutralizing the impact of external cash flows. This template mirrors the FinFacts desktop output, making it easy to validate chain-linking and to embed the same logic into factsheets or LLM responses.