Investment Decision Lab  /  Methodology

Methodology & Data Sources

Full transparency: this app is intentionally rule-based and deterministic. All computations run in your browser; the ETF reference data comes from a curated catalog maintained in a database. Every assumption, formula and source is documented below. Last reviewed Q3 2026.

Badges: All math in your browser · No live prices in the model

Building a portfolio, step by step

The steps the engine takes to build a portfolio, in order. Editable values can be changed live in the app — they are stored locally and override the defaults everywhere in the app.

  1. Asset Allocation — how much goes into equity, cash, bonds, gold and satellites.
  2. Benchmark — the neutral starting split of equity across regions.
  3. Capital market assumptions — expected returns and volatilities for the Sharpe tilt.
  4. Correlation matrix — how strongly the asset classes move together (normal and in a crisis); feeds every risk figure.
  5. Risk-free rate — the hurdle in the Sharpe tilt and the return on cash.
  6. Regional equity weights — home bias, horizon and theme tilts, the cap per region.
  7. ETF catalog — which fund implements each bucket.
  8. Concentration triggers — checks on the finished portfolio.

1. Asset Allocation: equity, cash, bonds and satellites

The first step decides how much goes into equity, cash, bonds, gold and satellites. These rules are fixed in the code; steps 2, 3, 5 and 6 then split the equity share across regions. Satellites come out of the equity share: listed real estate 6% and crypto 1–3% by risk profile (none for Low), each if selected; a thematic tilt of 3–5% is also taken out of equity but counts as part of the equity sleeve. If together they are larger than the equity share, the excess is taken from bonds. Everything that isn't equity is the defensive share, which cash and bonds split as follows.

  • Equity cap: the target equity share is capped by risk appetite: Low ≤ 40%, Moderate ≤ 70%, High ≤ 90%, Very High ≤ 100%.
  • Cash, the liquidity reserve: (10 − horizon in years) × 1.5 percentage points, plus a 7-point buffer for Low risk appetite, kept between 2% and 20% and never more than the defensive share, so it drops below 2% when that share is smaller. A shorter horizon means money may be needed during a downturn, so it gets more cash.
  • Bonds, the stabiliser: take the rest of the defensive share. If commodities are included and the profile isn't Low, 15% of the bonds, at most 5 percentage points, moves into gold. With an ETF budget of 5 or fewer, gold can fold back into bonds.

Examples: 1 year, Low, 30% equity: 20% cash and 50% bonds (13.5 + 7 = 20.5, capped at 20). 10 years, Moderate, 60% equity: 2% cash and 38% bonds (the 2% floor applies).

Small ETF budget: with 5 ETFs or fewer, only as many of real estate, crypto and gold are kept as the budget allows after bonds and two equity funds (number of ETFs − 3); the smallest are dropped first. Dropped real estate and crypto go to equity, dropped gold goes back to bonds. If that still leaves too many buckets, the equity regions are merged into a global and a home-market equity fund.

2. Benchmark (MSCI ACWI Proxy)

All relative metrics (beta, alpha, tracking error and TE-contribution) are computed against a six-region MSCI ACWI proxy: Equity US, Equity Europe, Equity UK, Equity Switzerland, Equity Japan and Equity Emerging Markets. The default weights approximate the MSCI ACWI free-float market-cap breakdown; the operator can change them centrally, and you can adjust them in the app (stored in your browser).

Source: MSCI ACWI index fact sheet (approximate free-float market cap weights); last reviewed Q2 2026.

3. Capital Market Assumptions (CMAs)

Rule-based, forward-looking estimates of expected returns (μ) and volatilities (σ) for each asset class. The engine starts from built-in seed assumptions, overlays the operator's defaults from the catalog database, then applies a published consensus layer (long-run CMAs from major institutions) where one is maintained, and finally applies your own overrides. All layers are transparent and documented. Your overrides are stored in your browser and persist across sessions.

Asset classes covered: Equity US, Equity Europe, Equity UK, Equity Switzerland, Equity Japan, Equity Emerging Markets, Fixed Income Investment Grade, Fixed Income High Yield, Fixed Income Emerging Markets, Real Estate (REITs), Commodities, Gold, Digital Assets (Crypto), and Cash.

Source: Internal rule-based seed assumptions and the operator's defaults; a consensus layer from major asset managers' published long-run CMA reports applies where one is maintained. Illustrative only, not a forecast.

Primary sources (public):

Published forecasts compared (% p.a.)
Asset classApp default (26 Oct 2026)Vanguard · 10 y, USD (30 Jun 2026)BlackRock · 10 y (30 Jun 2026)Robeco · 5 y 2027–31, USD / EUR (Sep 2026)Schroders · 30 y, local currency (Jan 2026)AQR · 5–10 y, local real (31 Dec 2025)
US Equity7.30 USD5.28.97 USD–6.73.9
Europe Equity6.60 EUR–6.17 EUR (incl. UK)–6.3 (ex UK)5.0 (Eurozone)
UK Equity6.80 GBP–7.33 GBP–6.64.9
Swiss Equity6.00 CHF–4.38 CHF–6.0–
Japan Equity5.50 JPY–5.69 JPY–5.34.9
EM Equity8.503.08.80 USD8.75 / 8.007.55.1
Global Bonds4.50 USD5.0 (ex US, hedged)4.51 USD (hedged)5.25 / 4.00 (govt, hedged)–2.0 (global agg)
Inflation-Linked Bonds4.90 USD3.9–4.9 (US TIPS)5.2 USD (US TIPS)–––
Cash3.50 USD3.53.66 USD4.25 / 3.004.0 USD · 2.6 EUR · 1.3 CHF1.3 (US)
Gold4.50 USD–––––
Broad Commodities5.50 USD4.7–6.7––––
Listed Real Estate6.80 USD4.4 (US)7.43 USD7.75 / 7.006.8 (global)–

Read on 14 Oct 2026, from the linked documents only; “–” means the source shows no figure for that class, or it is not read yet (inflation-linked bonds, broad commodities). A bracket says what the source's figure covers (bonds: Vanguard ex US, Robeco developed-market government bonds, both hedged). All nominal except AQR. Vanguard: compound, median (50th percentile); for US TIPS and commodities Vanguard shows only the range, which the table gives as such (read on 26 Oct 2026, the page gives no as-of date, as was BlackRock's US TIPS). BlackRock: compound, central estimate in the currency given; the 10-year column of the Excel file on the linked page (sheet “Starting point”, as of 30 Jun 2026), except US TIPS: from the chart on the linked page, whose tooltip gives no horizon or date; bonds: Global Aggregate hedged in USD. Robeco: compound; developed-market equities only as a whole, not by region. Schroders: compound or arithmetic is not stated. AQR: local real, compound (p. 3; figures from Exhibits A1, A2, 7); Europe as the Eurozone, bonds as the Bloomberg Global Aggregate; no figure for Switzerland, gold (only a commodity basket) or listed real estate (only US private real estate). App default: the shipped default g, in the asset class's currency (EM: mixed, none shown; cash: the USD risk-free rate); the app itself shows the g in force, with consensus and your own values. Horizons and currencies differ, so the figures compare only roughly. J.P. Morgan is left out because its figures need a login.

4. Correlation Matrix (normal & crisis) (v4.21 · Oct 2026)

Pairwise long-run correlations ρᵢⱼ between asset classes, in two tables: "Normal" (default) and "Crisis" (the correlation-regime switch, see Tail Realism). Every diversification effect in the tool comes from the off-diagonal cells being below 1.0. They move no weights, but they set how the risks of the building blocks combine in the portfolio.

Both tables are editable: the operator sets defaults for everyone in the console, and you can override single pairs for yourself in the app. Only a valid (positive semi-definite) matrix is applied.

Source of the built-in tables: Empirical estimates from typical 2000–2024 regime data (MSCI, Bloomberg, FTSE Russell index series).

5. Risk-Free Rate (per base currency)

Each base currency (USD, EUR, GBP, CHF) keeps its own money-market rate because USD T-Bills, EUR ESTR, GBP SONIA and CHF SARON diverge meaningfully. The risk-free rate feeds the Sharpe ratio, Jensen's alpha and the cash expected-return row in the CMA table. All four rates are editable and stored locally.

Defaults: Calibrated to current central bank policy rates; last reviewed Q2 2026.

6. Regional Equity Weights: Home Bias & Tilts

The construction engine uses a per-currency home-bias multiplier to tilt the equity allocation towards the investor's home region. For example, a EUR-base investor's Europe allocation is scaled up relative to its market-cap weight. The multiplier defaults vary by currency (USD: 1.5×, EUR: 1.8×, GBP: 1.8×, CHF: 2.0×) and can be overridden locally.

The construction algorithm starts from the benchmark (step 2), tilts each region by its Sharpe ratio from the capital market assumptions and the risk-free rate (steps 3 and 5), applies these home-bias multipliers and the horizon and theme tilts, and caps each region at 65% of equity. How much goes into equity overall, and how cash and bonds are sized, is in step 1.

7. ETF Catalog & Overrides

Real UCITS ETFs (plus ETPs for crypto and physical gold) from issuers including 21Shares, Amundi, ARK, Bitwise, CoinShares, CSIF, First Trust, Global X, HSBC, Invesco, iShares, JPMorgan, L&G, SPDR (State Street), Swisscanto, UBS, VanEck, Vanguard, WisdomTree, Xetra-Gold and Xtrackers — with ISIN, per-exchange tickers, domicile, replication method, distribution policy, fund currency, TER, fund size, inception date and a short editorial rationale for why the fund was picked.

Hybrid maintenance mode: values that move regularly are refreshed automatically from justETF (TER, AUM, inception date, distribution and replication — weekly; per-exchange listings — daily; look-through breakdowns and top-10 holdings — monthly). Values that represent editorial decisions (which fund to use per asset class, the default exchange, the hand-written selection rationale, and the hedge-currency mapping) stay curated by hand in the catalog database; no automatic run overwrites them.

Source: Issuer official factsheets (for curated fields) and justETF (for automatically refreshed fields; public, indicative).

8. Concentration Triggers (v2.4 · Jul 2026)

Configurable thresholds that trigger a concentration warning when a single asset class, region or ETF exceeds a set percentage of the portfolio. Default triggers are calibrated to standard diversification best practice and can be adjusted in the app (stored in your browser). Warnings appear in the Build tab alongside the allocation.

How results are calculated

Assumptions, routings and models behind every reported metric — read-only documentation.

Look-Through Routing (v3.1 · Oct 2026)

ETFs like iShares Core MSCI Europe (IE00B4K48X80) are not a homogeneous "Europe" position from the risk engine's perspective: roughly 20% is UK and 14% is Switzerland, with the rest in continental EU. The look-through logic uses each ETF's look-through profile from the catalog database (refreshed monthly from justETF, curated where needed) and routes each allocation row, weighted, onto the correct country buckets — before Vol, Beta, Tracking Error, Alpha, TE-contribution and the correlation matrix are computed.

Where the routing applies: allocation pie chart, risk & performance metrics (Vol, Beta, TE, Alpha, Sharpe), TE-contribution table, efficient frontier marker, correlation matrix, Monte Carlo simulation, and the country-level world map in the look-through panel.

Theme positions (since October 2026): for return and risk a technology, healthcare, clean-energy or cybersecurity fund counts as its own class, Thematic Equity — otherwise a technology tilt would just be more US equity. Under the Historical return basis it takes the measured figures of the theme fund actually held. The pie chart, world map and exposure views still show it by country.

The world map colours only countries that actually carry a non-zero share, with fill intensity scaling with the per-country share — not the region total. New in v2.2: country-accurate look-through rendering.

Manual ETF Entry — Live Preview & Look-Through (v2.0 · May 2026)

Users can type any ISIN to fetch live look-through data and preview the ETF before adding it to the portfolio. The live preview shows the same geo and sector breakdown as a curated entry, so users can verify exposure before committing. Rate-limited requests are automatically retried with exponential back-off; a status indicator shows when a fetch is in progress or failed.

Currency Hedging — what the toggle actually does (v2.8 · Oct 2026)

When the full-hedge toggle is on, the engine switches to hedged share classes (where the catalog has them), adds a flat +15 bps to the TER of every hedgeable sleeve in the fee estimate, and applies the σ rule: every foreign equity bucket loses 3 percentage points of volatility (emerging markets 2 pp, floor 5 %), the bond bucket 1 pp (floor 1 %, non-USD base). The bond-only hedge (on by default) applies the bond part alone. Expected returns are never changed by a hedge.

Since October 2026 the Monte Carlo simulation and the Risk & Performance metrics (σₚ, Sharpe, max drawdown, beta, tracking error, efficient frontier) read the same hedge-adjusted σ, so the two cards agree in every hedge setting; the benchmark keeps its unhedged σ. In Explain there is no toggle: the cuts follow the hedged share classes actually held, scaled by their hedged part.

Withholding-Tax Drag (v1.5 · Apr 2026)

Dividend withholding taxes reduce the effective return of equity and real-estate ETFs depending on the fund's domicile, the underlying index geography, and the investor's base currency / tax treaty position. The engine applies a per-asset-class WHT drag to the expected return for relevant asset classes.

WHT rates reflect typical UCITS fund domicile arrangements (Ireland, Luxembourg) and standard treaty rates. They are conservative estimates: actual investor-level recoveries through treaty reclaims are not modelled and would improve returns beyond what is shown.

Formula: E[Rₚ]ₙₑₜ = Σᵢ wᵢ · (μᵢ − whtᵢ)

Monte Carlo Simulation (v4.5 · Oct 2026)

The simulation runs 20,000 independent paths by default (adjustable, 200–20,000) over the user's chosen investment horizon (1 to 40 whole years). Annual returns follow log-normal distributions (default) or a Student-t distribution with 5 degrees of freedom (Tail Realism mode); since October 2026 both models share one path set — every path's standard-normal draws are the Gauss shocks, and Student-t maps the same draws onto the t-distribution by probability rank. The portfolio is first collapsed into a single μ and σₚ using the full covariance matrix — diversification is already embedded in σₚ before paths are drawn. The app's Methodology page walks through one path of the default profile step by step, both models side by side.

Reported metrics: median terminal value, P10 / P90 range, probability of loss, CVaR(95) and CVaR(99) at horizon, and path-based realized Max Drawdown (median path worst-case and, as the bad case, the 10th percentile under the P10–P90 band or the 5th under P5–P95). Since October 2026 the card also shows what the paths realized: the average CAGR of all paths, the yearly swing measured in the paths (it matches σ up to sampling noise), and how much the average return over the whole horizon differs from path to path (about σ / √years).

Paths in the chart (October 2026): a "Show 50 paths" switch draws 50 whole paths (a fixed random sample) faintly behind the fan, the deepest drawdown among them in red.

Path-based Max Drawdown (new in v1.4): for each simulated path the worst peak-to-trough loss along the path is computed as min(Vₜ / maxₛ≤ₜ Vₛ − 1). The median and the bad case (P10 or P5, following the band) are reported across all paths.

Recovery (October 2026): per path the longest time under water within the horizon — from a peak (the start counts as one) to the first year back at or above it, in whole years — for the median and the bad case (worst 10 % or 5 %, following the band); "≥ N years" where that path is still under water at the horizon. Also the share of paths still below their peak at the horizon and the share above the starting value after 1, 3, 5 and 10 years within the horizon and at the horizon.

Tail Realism (v2.8 · Oct 2026)

Two optional toggles let the operator calibrate default assumptions more pessimistically without changing the reported median or expected return. Both default to off.

1. Crisis-Σ (correlation regime): replaces the long-run correlation matrix with a crisis-calibrated matrix based on 2008 and March 2020 data. In crises, equity-equity pairs jump to 0.85–0.95, equity↔bonds rises to +0.30 (flight-to-quality breaks down), and only gold and cash remain reliable diversifiers. All risk metrics (σ, β, TE, Sharpe, alpha, MDD, frontier) and the Monte Carlo simulation recompute against this matrix simultaneously. Calibrated from AQR/Bridgewater stress studies.

2. Student-t tail model (df=5): replaces the annual Gaussian shock with a Student-t distribution at 5 degrees of freedom (standard in the academic literature, e.g. Cont 2001), mapped from the same draw as the Gauss shock (t = T⁻¹(Φ(z)) · √((df−2)/df)), so a bad year stays a bad year and the tails worsen on the fixed seed too. The median and P10/P90 barely change; CVaR(99) and Path-MDD-P05 worsen measurably (about 10–20% relative on the default profile); CVaR(95) moves less and usually, not always, worsens. The worst 1% of the default 20,000 paths is 200 paths — expect about ±0.6 points of seed-to-seed noise (about ±2 points at 2,000 paths).

Stress Test Scenarios

Six crises replayed on the portfolio: the dot-com crash (24 Mar 2000 – 9 Oct 2002), the Global Financial Crisis (9 Oct 2007 – 9 Mar 2009), the 2011 euro crisis (29 Apr – 3 Oct 2011), the SNB's franc shock (14 – 16 Jan 2015), the COVID crash (19 Feb – 23 Mar 2020) and the 2022 rate shock (3 Jan – 12 Oct 2022). Each window runs from the S&P 500's closing peak to its closing trough (the franc shock: the close before the SNB's decision to the second close after it), and every asset and currency is measured over those days. Each position's move is translated into the base currency; the part held in share classes hedged into it keeps its local return; with look-through, equity funds are shocked by their countries. The stress test is deterministic and independent of the Crisis-Σ toggle.

Own scenario: below the crisis cards the user sets up a hypothetical, immediate shock (a move per asset class, a change in interest rates and credit spreads, and currency moves against the dollar). It runs through the same translation and hedges. Bonds move by − duration × rate change − spread duration × spread change, with one duration pair for all bond buckets (all are global aggregate funds), set in the admin console. Stored in the browser and in the settings export.

Proxies: distribution-adjusted closes (total return) of USD-listed funds (SPY, EZU, EWU, EWL, EWJ, EEM, AGG, GLD), IWDP (London, price return) and bitcoin; for 2000–02 the EURO STOXX 50 net return index, US index funds for emerging markets, bonds and REITs, and the gold price; daily FX closes.

Formulas

Expected Return
E[Rₚ] = Σᵢ wᵢ · μᵢ
Volatility
σₚ = √(Σᵢ Σⱼ wᵢ wⱼ σᵢ σⱼ ρᵢⱼ)
σᵢ: the hedge-adjusted σ (Currency Hedging section) — in the Monte Carlo and on the metrics tile alike; the benchmark uses the CMA σ.
Sharpe Ratio
(E[Rₚ] − Rf) / σₚ
Beta vs benchmark
βₚ = Cov(Rₚ, R_b) / Var(R_b)
Alpha (Jensen)
αₚ = E[Rₚ] − [Rf + βₚ · (E[R_b] − Rf)]
Tracking Error
TE = √(σₚ² + σ_b² − 2·Cov(Rₚ, R_b))
Max Drawdown (heuristic — Risk & Performance tile)
MDD ≈ −min(0.85, (1.8 + 1.4 · equityShare) · σₚ)
Max Drawdown (path-based — MC tile)
MDDₚₐₜₕ = minₜ (Vₜ / maxₛ≤ₜ Vₛ − 1); reported = quantileₚ for p ∈ {0.50, 0.10 (P10–P90) or 0.05 (P5–P95)}
Longest time under water (MC tile)
Uₚₐₜₕ = max over spells (y − k): peak at k, first Vᵧ ≥ peak at y; open at horizon T: T − k; reported = quantile_q for q ∈ {0.50, 0.90 or 0.95}
WHT-net Expected Return
E[Rₚ]ₙₑₜ = Σᵢ wᵢ · (μᵢ − whtᵢ)

Reference & context

Background material: how the app is built, its explicit limitations and the Excel calculation snapshot.

How the app is built (v2.6 · Sep 2026)

In short: the tech stack. A React and TypeScript web app (built with Vite, styled with Tailwind CSS, charts with Recharts) that runs every calculation — and the Excel, PDF and PowerPoint exports — in your browser. Vercel serves it and runs three small Node.js / Express server functions: the admin console's interface, on-demand look-ups of single funds on justETF by ISIN, and prices for the market-data views (from EODHD). The catalog is a PostgreSQL database at Supabase: the app reads it through Supabase's public interface, which allows reading only, and changes are written by one shared library that checks every value. The scheduled refresh runs are GitHub Actions workflows; the code is one pnpm monorepo, tested with Vitest and Playwright.

New in September 2026: all ETF data lives in one catalog database, the scheduled automatic runs write to it directly and check every value, and the operator's corrections are protected from those runs.

  • Everything is calculated in your browser. Portfolio construction, Monte Carlo simulation, look-through and fees run on your device. No account is needed. Your ETF choice per bucket is remembered by the fund's ISIN; if the operator later takes that fund out of the bucket, the app shows the bucket's default ETF instead and tells you. At start the app loads the catalog from the catalog database at Supabase — these requests carry none of your inputs, but like any web request they show Supabase your IP address. Everything else the page needs — program code, font and world map — comes from this website itself. The site also tells your browser to load nothing from other addresses, except from the catalog database (Content-Security-Policy). Beyond that — and apart from the page-view count described at the end of this paragraph — the only things the app sends to a server are ISINs (for fund data; opening, importing or loading a portfolio looks up all of its funds outside the catalog) and, in the market-data views unlocked with a passphrase, that passphrase plus exchange tickers and currency pairs (for prices and exchange rates into your base currency). The server can see which funds, tickers and currency pairs are requested — in the market-data views that also reveals your base currency and preferred exchange — but never weights or amounts; those and your other settings leave your device only when you pass them on yourself. Page views are counted by Vercel Web Analytics, through this website's own address: the page's address (path only — never a query string, never anything from your portfolio), the referring page, browser and operating system (each with version) and device type, and an approximate location derived from your IP address (country, possibly region and city) — without cookies and without a lasting profile: Vercel tells visitors apart for one day by a hash computed from the request, then resets it. Views of the admin console are not counted.
  • One catalog for all ETF data. Funds, buckets, look-through breakdowns, look-through aliases (funds that take over a base fund's profile), currency-hedged classes and the default assumptions live in one database. The app loads the current state at every start and falls back to its built-in copy; the “Data freshness” box under the “ETF Implementation” table (Build and Compare tabs) shows which of the two is in use.
  • Where the data comes from. justETF's public fund pages, read by automatic runs on a fixed schedule — listings daily, core data weekly, look-through monthly. Every value is checked before it is stored: a fund that fails the check keeps its previous values, and a run in which more than half of the funds fail stores no core data, listings or look-through data. Every change is logged.
  • What is maintained by hand. Bucket assignments, default exchanges, descriptions written by hand, the currency split of hedged share classes, aliases and the default assumptions are maintained by the operator; no automatic run overwrites them. Core data such as TER comes from justETF; a value the operator corrects can be protected from the runs. Descriptions and look-through data taken from justETF keep being refreshed.

What this app does NOT do

  • No live market data in the calculations: returns, volatilities and correlations are fixed assumptions, never current prices, NAVs or yields. The optional market-data and backtest views — unlocked with a passphrase — show delayed prices on request, for information only; they never feed the model.
  • No personalised investment advice — ignores taxes, liquidity needs, estate situation, true personal risk capacity.
  • No guarantee that listed ETFs are available or suitable in your jurisdiction.
  • No dynamic allocation, no rebalancing tracking, no cash-flow modelling.
  • No inflation model (apart from the WHT drag; see Withholding-Tax Drag section).

Excel snapshot — see how everything is calculated (v2.8 · Oct 2026)

An .xlsx file, produced at every build by the engine itself (the same functions the app runs), lays out every calculation this app performs. It is not a frozen picture of results but a working computation model: every derived cell is a live Excel formula, so you can change the yellow input cells or the assumptions in the file and watch the allocation, the look-through exposures and every metric recompute.

The file contains eleven sheets: Allocation, Rules (the asset-class rules as live formulas), CMA Assumptions, Correlations, Look-through Exposures (with the "σ effective" column — the hedge-adjusted σ the Monte Carlo and the Risk & Performance metrics share), Look-through Geo, Routing, _Covariance (three blocks: portfolio, benchmark, cross term), Risk & Performance, Parameters and Country map.

The default profile (CHF / High risk / 10 yr / 60% equity / 10 ETFs / Gold ✓ / REITs ✗ / Crypto ✗ / full currency hedging off, bond FX hedge on) is an auditable worked example of the full calculation pipeline; the yellow cells on the Parameters sheet let you vary it.