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 artefact 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 — 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 — so 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.

FrequencyIntervalApprox. contributions / year
Weekly7 days~52
Monthly30 days~12
Quarterly91 days4
Annually365 days1
Lump sum0 (initial investment only)

Using a fixed interval rather than calendar months means contribution days drift slightly over multi-year simulations, but the total number of contributions per year stays correct. A monthly simulation over 10 years will always produce exactly 120 contributions plus the initial lump sum.

#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% / yr
But your average capital was only deployed for roughly 6 months.
IRR = +26.2% / yr — 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 modelled

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.

#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.