How It Works

A technical deep-dive into how ETF Stock Calculator fetches historical data, runs the day-by-day DCA simulation, handles dividends, and calculates your annualized return using IRR instead of simple CAGR.

#What This Tool Does

ETF Stock Calculator lets you backtest a dollar-cost averaging (DCA) strategy across up to 10 ETFs or stocks simultaneously, using real historical daily price data. You specify tickers, an initial investment amount, a recurring contribution and schedule, and a time period. The tool runs a full day-by-day simulation and shows you how each position would have grown, including the effect of dividend reinvestment.

The goal is an accurate, apples-to-apples comparison: not just “which went up more,” but “what would my actual account look like today if I had followed this exact plan?”

#Data Source

Historical price data is pulled live from Yahoo Finance on every “Run Comparison” click, using their chart() API at daily interval. For each ticker and date range we receive:

  • Open, high, low, and close prices for every trading day
  • Adjusted close price: the value used for all calculations
  • Dividend events: ex-dividend date and per-share payout amount
  • Share volume

If Yahoo Finance is temporarily unavailable, the API retries up to 5 times with exponential back-off (100 ms, 200 ms, 400 ms, 800 ms, 1.6 s) before surfacing an error in the results.

#Adjusted Close Price

All calculations use the adjusted close price, not the raw closing price. Yahoo Finance retroactively adjusts every historical price to account for two types of corporate events:

Stock splits

If a stock undergoes a 4-for-1 split, every price before the split date is divided by 4. Without this adjustment, the chart would show a false 75% price drop on split day. Adjusted close removes that artifact so the history looks continuous.

Dividend distributions

When a company pays a cash dividend, its stock price typically drops by the dividend amount on the ex-dividend date, since the cash left the company. Raw close reflects this drop; adjusted close factors it back out so that the price history represents total return rather than price-only return.

Using adjusted close means dividends are never double-counted. The price history already embeds the total-return effect. DRIP mode then adds the shares purchased by reinvesting dividends on top of that; non-DRIP mode accumulates the dividend cash as a separate balance alongside the position.

#The DCA Simulation

The simulation iterates over every trading day in the date range, in chronological order. Here is exactly what happens:

Initialization (day 0, the effective start date):
  shares_owned       = initial_investment / price[day_0]
  total_contributed  = initial_investment
  cash_dividends     = 0
  next_contribution  = day_0 + contribution_interval_days

─────────────────────────────────────────────────────

For each trading day in the dataset:

  1. Skip any day before effectiveStart or after endDate.

  2. Dividend check:
       if dividends_per_share > 0 and shares_owned > 0:
         amount = dividends_per_share × shares_owned
         if DRIP is ON:
           shares_owned += amount / price[today]   ← buy fractional shares
         else:
           cash_dividends += amount               ← hold as cash

  3. Contribution check:
       if today >= next_contribution and contribution > $0:
         shares_owned      += contribution / price[today]
         total_contributed += contribution
         next_contribution += contribution_interval_days

  4. Record snapshot:
       portfolio_value = (shares_owned × price[today]) + cash_dividends
       save { date, portfolio_value, shares_owned, total_contributed }

Contribution dates that fall on weekends or market holidays are executed on the nearest available trading day in the dataset. The code scans every data row and picks the one with the smallest absolute time difference from the target calendar date.

The loop runs over every actual trading day provided by Yahoo Finance, roughly 252 days per year. There is no interpolation or price averaging; every buy uses the exact adjusted close for that specific day.

#Dividend Reinvestment (DRIP)

When DRIP is on, dividend payments are immediately converted to additional fractional shares at that day’s adjusted close price. This mirrors how most brokerages implement automatic DRIP accounts: you never receive cash in hand; you simply own more shares after each ex-dividend date. Those additional shares then earn future dividends themselves, compounding the position over time.

When DRIP is off, dividends accumulate as a separate cash balance. The final portfolio value is the market value of your shares plus all accumulated dividend cash. This models a taxable brokerage account where you take dividends as income rather than reinvesting them.

The difference is most pronounced for high-dividend instruments (e.g. VYM, SCHD, BND) held over long periods. Reinvesting dividends grows the share base, which earns larger future dividends in an accelerating cycle. As a result, DRIP typically produces a meaningfully higher final value over a decade or more.

#Contribution Frequency

Each contribution frequency maps to a fixed-day interval. After every buy, the scheduler advances next_contribution by that interval. It does not jump to the first of the calendar month.

FrequencyContributions / yearExample (10 years)
Weekly52520
Monthly12120
Quarterly440
Annually110
Lump sumN/A0 (initial investment only)

The exact contribution count is round(years × frequency). For example, a 30-year monthly plan always produces exactly 360 contributions. Contributions are spaced evenly over the simulation period, and each one is executed on the nearest available trading day. This guarantee holds for both real tickers and the benchmark rate, so the two are always directly comparable.

#Annualized Return (IRR)

The “Ann. Return” figure in the results table is the Internal Rate of Return (IRR), a more accurate measure of annualized performance for DCA strategies than traditional CAGR.

Why not CAGR?

The classic annualized formula is:

CAGR = (final_value / total_contributed) ^ (1 / years) - 1

This is correct for a single lump sum invested at time zero, but it is misleading for DCA. With monthly contributions, your first dollar was in the market for 10 years while your last dollar was there for less than a month. CAGR ignores that timing entirely, which makes it overstate or understate the true return depending on market direction.

Concrete example: $500/month for exactly 12 months → $6,000 contributed, $6,900 final value.
CAGR = (6,900 ÷ 6,000)^(1∕1) − 1 = +15.0% / Y
But your average capital was only deployed for roughly 6 months.
IRR = +26.2% / Y, the correct annualized rate when you account for how long each dollar was actually at work.

What IRR is

IRR is the annualized discount rate r that makes the Net Present Value of all cash flows equal to zero:

NPV(r) = Σ [ CF_t / (1 + r)^t ] = 0

CF_t   cash flow at time t, measured in years from the simulation start
       Negative values = money you paid in (contributions)
       Positive values = money you received back (final portfolio value)
t      years elapsed since the simulation start date

Intuitively: IRR answers “at what constant annual rate would my money have grown if every dollar earned exactly that rate from the moment it was invested?”

How cash flows are modeled

Every contribution is recorded at the exact fractional year when it occurred. For a 10-year monthly DCA with a $10,000 initial investment and $500/month contributions:

t = 0.000 yr    CF = −$10,000   ← initial lump sum
t = 0.083 yr    CF = −$500      ← 1st monthly contribution  (1∕12 yr)
t = 0.167 yr    CF = −$500      ← 2nd monthly contribution
t = 0.250 yr    CF = −$500
...
t = 9.917 yr    CF = −$500      ← last contribution
t = 10.000 yr   CF = +$87,432   ← final portfolio value (closing inflow)

The final portfolio value enters as a large positive cash flow at the end. The solver then finds the single rate r that makes all those discounted flows sum to zero.

Newton-Raphson solver

We solve for rnumerically using Newton-Raphson iteration. Starting from an initial guess of r = 10%, each step refines the estimate using the NPV function and its derivative:

r_next = r − NPV(r) / NPV′(r)

NPV(r)  = Σ [  CF_t / (1 + r)^t       ]

NPV′(r) = Σ [ −t × CF_t / (1 + r)^(t+1) ]

The solver runs up to 300 iterations, stopping early when |NPV(r)| < 1e-7 (effectively zero). On each step r is clamped to [−99.99%, +10,000%] to prevent divergence. If the solver cannot converge (for example, if every contribution was lost), it returns 0%.

Lump-sum consistency check

For a pure lump sum with no periodic contributions, IRR reduces algebraically to the same value as CAGR, a useful consistency check:

Cash flows: −P at t = 0,  +FV at t = T

NPV(r) = −P + FV / (1 + r)^T = 0

Solving for r:
  (1 + r)^T = FV / P
  r = (FV / P)^(1/T) − 1   ← identical to the CAGR formula

#Inception Date Handling

If a ticker launched after your chosen start date (for example, you request 20 years of history but ARKK (ARK Innovation ETF) launched in 2014), the simulation detects this and adjusts automatically:

gap_days = earliest_available_date − requested_start_date  (in days)

if gap_days > 7:
  # 7-day buffer absorbs weekends and market holidays at the boundary
  effective_start      = earliest_available_date
  insufficientHistory  = true       ← flag shown in the results
else:
  effective_start      = requested_start_date

When flagged, the result card shows an “Insufficient history” notice with the actual inception date. All metrics (total return, annualized IRR, final value) reflect the shorter actual period, not the requested one. The comparison is still valid; just note that the newer ticker had less time to compound than the others.

#Chart Data

The raw simulation produces one data point per trading day, roughly 252 points per year, or 2,520 for a 10-year run. Sending all of those to the browser would be slow and visually indistinguishable from a thinned version at chart scale.

We reduce the dataset to at most 100 evenly spaced points before sending it to the browser:

step   = floor(total_points / 100)
output = every step-th point + always the final point

Example: 10-year simulation (2,520 raw points):
  step = floor(2520 / 100) = 25
  kept = points 0, 25, 50, 75, … + point 2519 (last)

The final data point is always preserved explicitly, so the endpoint and the final portfolio value shown on the chart are always exact. Only intermediate points are thinned.

#Chart Interactions

The portfolio value chart has two interactive features for exploring the contributions-vs-gains breakdown of your results.

Hover tooltip

Hovering anywhere on the chart in Value mode shows each ticker’s current portfolio value, plus a second line breaking that value into amount contributed and market gains:

Jan 2035
VOO: $245,000
  $70,000 contributed · $175,000 gains (+250%)
QQQ: $312,000
  $70,000 contributed · $242,000 gains (+346%)

The tooltip tracks all tickers simultaneously and forward-fills each series to the nearest available data point, so you always see a value for every ticker at any hover position. The gains breakdown is omitted in Return % mode since that view already expresses gains directly as a percentage.

Per-ticker breakdown panel

Click any ticker name in the chart legend to open a stacked area chart below the main chart showing just that ticker over the full simulation period. The bottom area (gray) represents cumulative contributions; the top area (ticker color) represents gains. As the simulation period lengthens, the gains layer typically grows to dwarf the contributions layer, the classic compounding wedge.

Click the same ticker again, or the “✕ close” button, to dismiss the panel. The breakdown is per-ticker by design: showing stacked contributions and gains for 10 tickers on a single chart would be unreadable, so the panel focuses on whichever one you want to inspect.

#Benchmark Rate

The + Add benchmark rate button in the Tickers panel lets you add a hypothetical fixed annual return as a reference line alongside real tickers. Common uses:

  • Compare VOO against a steady 7% target to see when the market beat or trailed your goal
  • Model a savings account, Treasury bond, or CD yield as an opportunity cost baseline
  • Test whether consistent contributions to a 'boring' 5% instrument beat a volatile single stock

How it is computed

Unlike real tickers, the benchmark is computed entirely in the browser. No network request is made. It uses true effective annual compounding, the same convention used to report real-ticker IRR. That means a 10% benchmark is directly comparable to a ticker that returned 10% annualized.

Exact contribution count:
  num_contribs = round(years × frequency)   e.g. 30yr monthly → 360 exactly

Contributions are spaced evenly over the simulation period;
the final contribution falls exactly at the end date.

For each contribution C deposited at time t_k:
  value at time t = C × (1 + r) ^ ((t − t_k) / YEAR)

Total portfolio value at any point t:
  V(t) = Σ  C_k × (1 + r)^((t − t_k) / YEAR)   for all k where t_k ≤ t

r    = annual rate as a decimal  (e.g. 0.07 for 7%)
YEAR = 365.25 days in milliseconds

This is exact discrete compounding at the true annual rate, the same measure used when reporting that a stock “returned 10% annualized.” Because the rate is fixed and known, there is no IRR solver needed: the annualized return column always shows exactly the rate you entered.

How it appears in results

The benchmark appears as a separate series labeled 7% Fixed (or whatever rate you entered) on the chart and in the results table, with its own color. It participates in the winner ranking just like any real ticker. The Details tab is hidden when no real tickers are included, since there are no fund fundamentals to display for a hypothetical rate.

The benchmark is a purely hypothetical instrument. It assumes the exact rate every year with no volatility, no fees, and no taxes. Those are conditions that no real investment guarantees, so it is most useful as a reference line, not a fair direct comparison.

#AI Interpretation

After each run, a structured summary of your simulation parameters and per-ticker results is sent to Claude (Anthropic) and the plain-English interpretation is streamed back and displayed below the chart. The prompt includes:

  • Initial investment, contribution amount and frequency, time period, DRIP setting
  • Per-ticker final value, total contributed, total return %, and annualized IRR
  • Whether any tickers had insufficient history (inception date caveat)

Claude is instructed to explain the results in plain English: what the numbers mean, how the instruments compared, and what factors likely drove differences (sector concentration, expense ratios, market conditions over the period, etc.). It is not instructed to give financial advice, and its output should not be treated as such.

The interpretation is generated once per run. It does not retain memory between runs and has no access to real-time market data beyond what is passed in the prompt.

#Limitations & Disclaimer

  • Data accuracyHistorical data comes from Yahoo Finance's unofficial API. While generally reliable for major ETFs and US stocks, it may occasionally have gaps, stale values, or errors for thinly traded instruments or international securities. We do not independently verify every data point.
  • Execution priceContributions are executed at the adjusted close price on the contribution date. In practice you would buy at some intraday price, possibly higher or lower. The difference is generally small over long simulations but can matter during high-volatility periods.
  • TaxesThe simulation does not model taxes. In a taxable account, dividend income is taxed in the year received, and capital gains rates vary by holding period. Results represent a pre-tax, idealized scenario. Tax-advantaged accounts (IRA, 401k) would match the simulation more closely.
  • Expense ratiosETF expense ratios are already baked into the historical NAV, and therefore the adjusted close price. No separate fee deduction is needed; the annual drag is implicit in the price data. For individual stocks there are no expense ratios.
  • Not financial adviceETF Stock Calculator is for educational and research purposes only. Past performance does not predict future results. Nothing on this site constitutes investment advice. Always consult a qualified financial advisor before making investment decisions.

Have an idea that would make this tool more useful? We'd love to hear it.