Fund performance data explained: cumulative, calendar-year, annualised and rebased to 100
The four ways fund performance is reported, what each one hides, how to compute tracking difference, and how to chart fund vs benchmark from API data.Published 2 September 2026 · 3 min read · by FundFacts APIPerformance is the number everyone looks at first and the one most often misread. Factsheets present it four different ways, each answering a different question. This guide explains them, shows how they map to the data payload of FundFacts API, and gives you the code to chart a fund against its benchmark.
The four views
| View | API field | Question it answers |
|---|---|---|
| Cumulative | data.cumulativePerformance | "If I had invested at the start, how much would I be up today?" |
| Rebased to 100 | data.indexedPerformance | "Show me the growth curve of fund and benchmark side by side" |
| Calendar-year | data.calendarReturns | "How did it do in 2022 specifically?" |
| Annualised (trailing) | data.annualisedReturns | "What did it compound at per year over 3, 5, 10 years?" |
Cumulative and rebased series
cumulativePerformance is an array of { date, fund, benchmark } where the values are cumulative percentage returns from the first point. indexedPerformance.points is the same series expressed as growth of 100 — { date, fund, index } starting at 100 — with indexedPerformance.hasIndex telling you whether the benchmark line exists.
json{"indexedPerformance": {"hasIndex": true,"points": [{ "date": "2009-09", "fund": 100, "index": 100 },{ "date": "2026-08", "fund": 596.6, "index": 598.0 }]}}
Rebased series are what you want for a chart: both lines start at the same point, so the gap between them at any date is the cumulative relative performance.
Calendar-year returns
json{"calendarReturns": {"years": ["2021", "2022", "2023", "2024", "2025", "2026"],"fund": [21.9, -18.0, 23.9, 18.7, 21.2, 13.5],"benchmark": [null, null, null, null, null, null]}}
The arrays are aligned by index. The last year is year-to-date, and benchmark may be null when the document does not publish it. Calendar returns are the honest view of volatility: a fund that shows "+11 % annualised over 5 years" may hide a −18 % year, and this is where you see it.
Annualised returns and why they mislead
json{"annualisedReturns": [{ "label": "1 Year", "fund": 19.68, "index": 19.55 },{ "label": "3 Years p.a.", "fund": 17.11, "index": 19.78 },{ "label": "5 Years p.a.", "fund": 11.45, "index": 10.98 },{ "label": "10 Years p.a.", "fund": 12.82, "index": 12.90 },{ "label": "Since Inception", "fund": 11.01, "index": null }]}
Annualised (or "p.a.") returns are geometric averages: (end / start)^(1 / years) − 1. They are the right way to compare funds with different histories, but two things to remember:
- End-point sensitivity. A 3-year figure measured a month after a crash looks very different from one measured a month before it.
- They are not what an investor experienced unless the investment was a single lump sum at the start of the window.
Tracking difference in one line
For an index fund the key number is not the return, it is the gap to the index. With annualisedReturns it is trivial:
typescriptconst td = fund.data.annualisedReturns.map((r) => ({period: r.label,trackingDifference: r.fund != null && r.index != null ? +(r.fund - r.index).toFixed(2) : null,}));// [{ period: "1 Year", trackingDifference: 0.13 }, { period: "3 Years p.a.", trackingDifference: -2.67 }, ...]
Negative numbers roughly equal to the TER are expected; large deviations mean sampling, securities-lending income or a benchmark mismatch (a *net* return index versus *gross*, for example).
Charting fund vs benchmark
Because the series is already rebased, the SVG is short. Here is a dependency-free version:
tsxexport function GrowthChart({ points }: { points: { date: string; fund: number; index: number | null }[] }) {const W = 720, H = 280;const all = points.flatMap((p) => [p.fund, p.index ?? p.fund]);const min = Math.min(...all), max = Math.max(...all);const x = (i: number) => (i / (points.length - 1)) * W;const y = (v: number) => H - ((v - min) / (max - min)) * H;const path = (key: "fund" | "index") =>points.map((p, i) => (p[key] == null ? "" : `${i ? "L" : "M"}${x(i)},${y(p[key] as number)}`)).join(" ");return (<svg viewBox={`0 0 ${W} ${H}`} width="100%"><path d={path("index")} fill="none" stroke="#F27A1A" strokeDasharray="4 3" /><path d={path("fund")} fill="none" stroke="#1F5EFF" strokeWidth={2.5} /></svg>);}
The landing page shows this exact chart running on the live sample response.
Things to state next to any performance number
- Currency.
keyFacts.currency— a USD share class in a EUR portfolio adds FX return you did not choose. - Income treatment.
keyFacts.distribution— accumulating share classes reinvest dividends, so their growth curve is higher than a distributing class of the same fund. - As-of date.
data.dataAsOfand the envelope'sgeneratedAt. - Fees included. Published fund returns are net of ongoing charges but before any platform fee or entry charge.
Next
Read how fees eat into these numbers in TER and ongoing charges, or create a free key and pull the full series for one of your funds.