Translated from the Spanish original. Where they differ, the Spanish one holds.
- App: Horalis Growth · Reviewed: 3 October 2026
- Code (in
projects/wealth-lens/):src/lib/finance.ts(futureValueWithContributions,monthlyRate),src/lib/calculator.ts(valueAt,resultOf,yearlyPath),src/lib/investment.ts(resolveInvestment),src/lib/indexes.ts(the data's averages)
The question it answers
"If I have €X today and add €Y a month for N years, how much will I have?" It is the result's big number, the chart's curve, "What you put in" and "Growth".
The formula, in plain words
Each year the money grows by a fixed percentage, and what has grown grows again. Each contribution goes in at the end of its month and grows from then on. Everything is in today's euros: the percentage is the growth after the rise in prices, and the contribution rises with prices.
Exact: with g the real yearly growth, i = (1 + g)^(1/12) − 1 the equivalent monthly rate and m = 12·N months,
total = X·(1 + g)^N + Y·((1 + i)^m − 1) / i
"What you put in" = X + 12·N·Y; "Growth" = total − what you put in.
Where g comes from:
| Choice | g | How |
|---|---|---|
| An asset (US shares, German bonds, gold) | 7.54 %, 2.47 %, 1.04 % | The geometric average of its real yearly returns from 1988 to 2022 (the same years for all) |
| Savings | (1 + 1.5 %) / (1 + inflation) − 1: −0.49 % with the euro's 2 % | A fixed interest of 1.5 % minus the inflation of "Rising prices in" |
| A mix | The weighted average of its parts' g | See mixes |
| Own growth ("My %") | The one written; 5 % to start | See default value |
A "What if…?" of +1 % or −1 % adds or takes one point from g. "A bad first decade" replaces the first ten years with those of a real decade (see bad decade); after that, g again.
Assumptions
- Growth is constant: a typical year, every year. The ups and downs are told apart by the simulated futures.
- g is a geometric average: the growth that, repeated, gives what the data gave from end to end. That is why the big number is a "typical" result (close to the futures' median, somewhat below it: see point 6 of what is debatable), not the arithmetic average of the results, which would be higher.
- Contributions rise exactly with inflation and go in at the end of each month.
- No taxes, no fund or trading fees, no effect of exchange rates (the dollar indices are deflated with US inflation).
- The years from 1988 to 2022 stand for what may come.
Sources (dated)
- S&P 500: Robert J. Shiller, Yale, Online Data (
ie_data.xls), data to June 2023, real total return from January to January. - German bonds: the 10-year Bund at constant maturity (OECD / Bundesbank and Destatis), to December 2024.
- Gold: World Bank, Commodity Price Data (Pink Sheet), the December average of each year in USD, CC BY 4.0, deflated with the US CPI (Shiller to 2022, BLS after), to December 2024. Read from the public mirror
github.com/datasets/gold-prices(its prices from 1960 are the World Bank's), compiled on 9 October 2026. - Data compiled on 29 September 2026 (
packages/seed-kit/src/data/asset-returns/*.json, fieldcompiledOn). Common period 1988–2022: the longest that all of them cover.
Validation (tests)
src/lib/finance.test.ts(40): cases calculated by hand (€1,000 at 7 % for 10 years = €1,967.15), the equivalent monthly rate, contributions, edge cases.src/lib/properties.test.ts(22): properties that hold for any figure (the formulas undo each other; more years or more growth never give less).src/lib/calculator.test.ts(24): the result, the chart that ends at the result, "what you put in" and "growth".src/lib/indexes.test.ts(25): each real return recalculated from the original figures, the common period, the geometric averages.src/lib/investment.test.ts(17): from the choice to g, inflation and the conversions.src/lib/edge-cases.test.ts: amounts at zero, negative growth, 60 years, and growth at −50 %, 0 %, 70 % and 500 % over 1, 20 and 60 years: everything finite, checkable sums (0 %: what was put in; −50 %: half each year; 70 % and 500 %: 1.7²⁰ and 6²⁰ times) and no figure of more than twelve digits in a row.src/lib/realism.test.ts: when each warning comes out, the records in the data and their wording in English and Spanish.src/lib/validation.test.ts: a file with growth from −50 % to 500 % is read; outside that range, it is not.e2e/money.test.mts: at 360 px, 500 % is accepted and 501 % is not, the warning with its euros and nothing cut or off the screen.
Limits
- It is not a forecast: it is what would happen if each year were the data's typical year.
- 35 years of data are few to estimate an average: the standard error of the S&P 500's average is about 2.8 points (16.4 % / √35).
- An investor in euros does not receive the real return in dollars: the difference is the real euro/dollar exchange rate, which the app does not model.
- The comparison between assets uses the same years, but those years are not "neutral": they include the great rise of the nineties and two stock market crises.
The growth the person writes
The box of step 3 accepts from −50 % to 500 % a year, after the rise in prices (GROWTH_LIMITS in src/lib/validation.ts; also when reading a file). It is the person's number: the app does not correct it, but it says what the data have seen.
- 500 % is a technical limit, not a judgement. With 500 % for 60 years the figures reach about €10⁴⁷, far from what a browser number holds (about 10³⁰⁸), also in the highest futures. Above that, nothing would break at once, but it would make no sense to show it either.
- Huge figures are written in words from a thousand million, with three figures ("€1.23 billion", "1230 millones de euros", "4,4 billones de euros", as the Spanish press does), and as a power of ten from 10¹⁸ ("€4.02 × 10¹⁸"), in
packages/seed-kit/src/format.ts. On a phone, the big number, the cards and "What if…?" become one size smaller or move to one column so nothing is cut. - More than the data's best stretch of 20 years (13 %, S&P 500 1980–1999): "Very rare: the best 20 years in the data gave 13%."
- More than 50 % (
strongGrowthWarninginsrc/lib/assumptions.ts): a stronger warning, whole and with euros: "No index or big company has kept this up: an average of 70% for 20 years. At that pace, your €1,100 would be €44,706,545." The euros are today's money grown at that pace for the plan's years (1100 × 1.7²⁰), in today's euros, so the sum can be done by hand; with no money today, the monthly contribution.
Never a claim the data contradict. The first sentence is checked against the app's data (keptRecords in src/lib/realism.ts): the best average of each index (its real years, after the rise in prices) and of each big company of the stored prices (its calendar years or, if the plan is longer, its growth since 2016, before the rise in prices, which overstates it a little) over the plan's years, or over all its data if it has fewer. If any beat it, the warning names it: Nvidia rose 64 % a year from 2016 to 2026, so with 60 % over 20 years it says "only NVDA did it, and only for 10 years (2016–2026)", and over 3 years "very few have done it, such as TSLA (2019–2021)". The sentence of the request ("No index or big company has kept up an average of X % for N years") has 13 words; it was reordered to meet the plain language test's 12 per sentence, with the same words.
What is debatable
- "Average" can be read as an expectation. The app says "grows at its average"; the geometric average (S&P 500: 7.54 %) is lower than the arithmetic one (8.87 %). Showing the geometric one is the prudent choice and what brings the big number close to the median, but the word should be "typical", or the difference explained in How it works. Cost: a text.
- A short period, favourable to the US. The S&P 500 of 1988–2022 (7.54 %) beats its own average of 1928–2022 (6.58 %) and that of 1900–2025 (6.6 % according to UBS 2026). With the "S&P 500" chip, the result inherits a particularly good era. Cost: decide whether assets with a long history use all of it (it would break "the same years for all").
- Currency. The dollar indices are in US real terms; for someone saving in euros the exchange-rate effect is missing (neither hedged nor unhedged). Cost: a EUR/USD series and a decision of method.
- Different windows. The S&P 500 is measured from January to January (Shiller's monthly data) and the others from December to December: a month's lag between assets that the app compares and mixes. Cost: redo the S&P 500 series by calendar years.
- No costs. A cheap ETF costs 0.07–0.20 % a year, and taxes on dividends and capital gains can take more. The app says so ("leaves out taxes and most fees"), but the big number does not deduct it. Cost: a visible, editable fee assumption.
- Two models for the same thing. The big number uses months and contributions at the end of each month; the simulations, years with half the contribution at the start and half at the end. And the median of a sum of swinging contributions is larger than the sum of their medians. Result: with €1,100 and €100 a month for 20 years at 5 %, the big number is €43,499 and the median of the 1,000 futures, €46,410 (6.7 % more). The big number is the more prudent of the two, but the app does not explain it. Cost: decide which is "the" result and unify the step.
- Mixes that are rebalanced. The weighted average of geometric averages underestimates the growth of a rebalanced mix: a 60/40 of US shares and German bonds grows 5.51 % in the projection and 5.87 % rebalanced year by year with the same data (see mixes).
History
- 2026-10-03: first sheet (no change to the calculation).
- 2026-10-07: "Rising prices in" instead of "Prices in" for the country of the inflation. No change to the calculation.
- 2026-10-07 (2): step 3's growth goes from −50 % to 500 % (before, up to 50 %), with a stronger warning in euros above 50 %, and huge figures in words or powers of ten. No change to the calculation.
- 2026-10-09 (phase A2): world shares (MSCI World) and the Nasdaq-100 are removed. Their terms do not allow publishing their data, and there is no open official series of world shares with a long history (the OECD and the World Bank publish price indices of each country's stock market, but without dividends or a world average). Consequences:
- gold moves from the LBMA (restrictive terms) to the World Bank's Pink Sheet (CC BY 4.0): the 1988–2022 average from 1.06 % to 1.04 %;
- own growth ("My %") moves with the swing of US shares (16.4 %) instead of the World's (17.8 %);
- the 100 %, 80/20 and 60/40 templates use US shares;
- a file with World or Nasdaq-100 is read as US shares, with a warning. The starting 5 % still quotes UBS's world 5.2 % (see default value); phase B2 removes the preselected growth.