Strategy comparison

Portfolio vs. benchmark

Model a custom ETF blend, set weights, and compare lump sum investing against monthly DCA over any time horizon. Forecasts extend beyond available data using historical annualized returns.

Portfolio builder

Pick ETFs, set target weights, and compare lump sum vs. monthly DCA.

Portfolio tickers

IS3N.DESXR8.DEXSX6.DEIJHZPRR.DEXXSC.DE

Target weights

IS3N.DE19.5%

Max history: Fetching history...

SXR8.DE40.0%

Max history: Fetching history...

XSX6.DE19.5%

Max history: Fetching history...

IJH9.0%

Max history: Fetching history...

ZPRR.DE6.0%

Max history: Fetching history...

XXSC.DE6.0%

Max history: Fetching history...

Total100.0%

Benchmark

Benchmark ticker

SPY

Benchmark uses a single ticker and 100% weight by default.

Investment plan

Derived from 121 monthly contributions.

Forecast horizon

Dividends are modeled via adjusted close prices. Forecasts extend past performance using historical annualized returns.

Results

Run a comparison to see performance, returns, and risk metrics.

Metrics guide

Portfolio performance metrics

This page explains what each metric means, how it is computed in this app, and how to interpret it (especially for DCA). The implementation lives in lib/strategies/compare.ts.

Cheat sheet: which metric to use

  • Final value — what your account is worth at the end.
  • Total return % — simple profit vs. total contributed (good sanity-check).
  • Money-weighted return (IRR) — most common for evaluating a DCA plan (accounts for timing of deposits).
  • Time-weighted return (TWR) — most common for “fund/manager performance” (removes your cashflow timing).
  • Max drawdown / Volatility / Sharpe — risk and risk-adjusted performance.

What data we use

We fetch historical prices from Yahoo Finance and use adjusted close (adjClose). Adjusted close accounts for splits and dividend effects, so it’s a reasonable proxy for “dividends reinvested”.

Simulation runs on a daily timeline. If a ticker has no quote on a particular calendar day, we carry forward the last available price so all tickers share the same set of dates.

Practical caveat

ETFs often have multiple listings (different exchanges/currencies). If you pick the wrong symbol, history can be short or missing. Always verify the ticker is the one you intend.

How the simulation works (buy & hold)

Lump sum

On the start date, we invest the full amount and buy shares according to your target weights. After that, shares stay constant (no rebalancing).

shares[ticker] = (weight% × initialInvestment) / price(startDate) value[day]     = Σ shares[ticker] × price(day)

DCA (monthly contributions)

Once per month, on the same calendar day as the start date (or the last day of the month if that day doesn’t exist), we contribute monthlyInvestment and buy shares according to weights. Shares accumulate over time.

newShares = (weight% × monthlyInvestment) / price(contributionDate) shares   += newShares value[day]    = Σ shares[ticker] × price(day) invested[day] = invested[previous] + (monthlyInvestment on contribution day)

Why DCA “often looks worse” than lump sum

In a rising market, lump sum tends to win because all money is invested from day 1. With DCA, your exposure ramps up gradually — early contributions have less time in the market.

This doesn’t mean DCA is “wrong” — it’s simply a different risk/behavior profile. To compare fairly, this app derives lump sum from DCA so both invest the same total over the selected period.

The three return “families”

1) Simple return (profit vs. total invested)

Great for understanding “how much did I make?”. Not timing-aware for DCA.

2) Money-weighted return (IRR)

Best single “investor experience” rate for DCA because it uses exact contribution dates.

3) Time-weighted return (TWR)

Best for comparing the underlying investments without cashflow timing. This is how funds/portfolios are typically reported.

Metric definitions (with examples)

Total invested

Lump sum: the single initial contribution. DCA: sum of monthly contributions.

Final value

finalValue = Σ shares[ticker] × price(endDate)

Total return ($) and Total return %

totalReturn        = finalValue − totalInvested totalReturnPercent = (finalValue / totalInvested − 1) × 100

Example

totalInvested = 12,000 finalValue = 15,600 totalReturn = 3,600 totalReturn% = 30.0%

Annualized return (CAGR on total invested)

This is the constant yearly rate that turns totalInvested into finalValue over the whole period. It is easy to read, but for DCA it ignores when you invested.

years = (endDate − startDate) / 365.25 CAGR  = (finalValue / totalInvested)^(1/years) − 1

Example

totalInvested = 10,000 finalValue = 16,000 years = 5 CAGR ≈ (1.6)^(1/5) − 1 ≈ 9.90%/year

Money-weighted return (IRR / XIRR)

IRR is the single annual rate that makes the present value of all cashflows equal to zero. Contributions are negative, ending value is positive. This is the most common “what did I earn on my money?” metric for DCA.

Cashflows: contribution(s) → negative amounts finalValue      → positive amount on endDate Find r such that: Σ amount_i / (1 + r)^(years_i) = 0

Example (intuition)

If markets rise steadily, lump sum tends to have higher return than DCA because more money was invested earlier. IRR will usually land between “market CAGR” and the simple CAGR-on-total-invested number.

Example cashflows: 2020-01-01 -500 2020-02-01 -500 ... 2021-12-01 -500 2021-12-31 +15,600

Time-weighted return (TWR)

TWR removes cashflows before measuring returns. Think of it as: “If my portfolio was one unit and I never added/removed money, what would its growth rate be?”

For each day: cashflowToday = contribution made on that day (0 on most days) dailyR = (valueToday − cashflowToday) / valueYesterday − 1 Then compound: totalR = Π (1 + dailyR) − 1 TWR    = (1 + totalR)^(1/years) − 1

Worked example (2 periods)

Start: value = 1,000 Period 1 end: value = 1,100 → +10.0% Add cashflow: +900 → value becomes 2,000 (not a return) Period 2 end: value = 2,100 → +5.0% TWR total = (1.10 × 1.05) − 1 = 15.5%

Volatility (annualized)

Volatility is the typical day-to-day fluctuation (risk). We compute the standard deviation of daily returns and annualize it (≈ 252 trading days/year).

dailyR (lump sum) = valueToday / valueYesterday − 1 dailyR (DCA)      = (valueToday − cashflowToday) / valueYesterday − 1 volatility = std(dailyR) × sqrt(252)

Why DCA volatility can differ

Even after subtracting contributions from returns, the account is smaller early in DCA and the exposure ramps up over time. That changing exposure can make measured volatility differ from lump sum.

Alternative definitions exist (e.g., volatility of the underlying asset-mix return series). This app reports volatility of the strategy’s daily value path.

Max drawdown

The largest peak-to-trough percentage drop along the path.

drawdown(day) = (peakSoFar − value(day)) / peakSoFar maxDrawdown   = max over all days

Example

Values: 100 → 120 → 90 → 110 Peak = 120, trough = 90 Max drawdown = (120 − 90) / 120 = 25%

Sharpe ratio

Risk-adjusted return. We use a fixed 2% risk-free rate and compute:

sharpe = (annualizedReturn − 2%) / volatility

Example

annualizedReturn = 10% volatility = 15% Sharpe = (10% − 2%) / 15% = 0.53

Forecasting

If you enable forecasting, the simulation extends prices beyond the last historical date using the last ~10 years of annualized return for each ticker (or all available history if less). This is an extrapolation, not a prediction.

Why the chart shows only historical data

The chart intentionally displays history only. Projected end values are shown in the “Projected values” panel to keep the historical comparison readable.

Chart modes

Value

Actual account value over time. Best for seeing the “real money” path.

Invested

Cumulative cash contributed. Best for understanding capital deployment.

Normalized

Growth efficiency relative to capital contributed:normalized = value / invested.

Example: 1.25× means value is 25% above invested.0.90× means you are 10% below.

Tip: early in DCA, invested is still small, so normalized can move more sharply.

Annual returns by year table

The “Annual returns by year” table shows calendar-year returns based on historical data only (no forecast). For each year we take the first available value in that year and the last available value in that year:

yearReturn% = (lastValueInYear / firstValueInYear − 1) × 100

The “Portfolio” row is a buy-and-hold index built from your target weights (no monthly contributions), so it’s comparable to each symbol’s own annual returns.

Limitations

No taxes, fees, spreads, slippage, or transaction costs. Currency effects are ignored. Adjusted close approximates dividend reinvestment but still depends on the data quality of Yahoo Finance for the chosen symbol.