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.