Methodology & Data Sources

What the engine does, where the numbers come from, and where the model breaks down.

The projection model

The core calculator is a year-by-year simulation. It takes five inputs — starting balance, current dividend yield, expected dividend growth rate (DGR), expected share price growth rate, and an optional periodic contribution — and walks forward one year at a time.

For each projected year, the engine:

  1. Grows the dividend per share by the DGR. If you started with a $4 dividend and DGR is 8%, year-2 dividend per share is $4.32.
  2. Grows the share price by the price-growth rate, compounded from the starting price.
  3. Computes annual dividend income = current share count × dividend per share.
  4. If DRIP is enabled, reinvests the year's dividends to buy more shares. The purchase price used is the simple average of the year's starting and ending share price — a standard mid-year-average approximation that avoids over-crediting either end of the price path.
  5. If periodic contributions are enabled, adds the contribution to share count at the same mid-year-average price.
  6. Updates share count, recomputes portfolio value (shares × end-of-year price), and adds the year's income to the cumulative dividend total.

The output is a table of year, share count, dividend per share, annual income, total dividends received to date, and portfolio value.

Why CAGR + calendar-year aggregation

Two design choices behind the default growth-rate values you see on this site:

CAGR, not arithmetic mean. The “annual share price growth” and “annual dividend growth” defaults use the compound annual growth rate (CAGR) — the smooth annual rate that, compounded over the window, produces the actual observed change. Some sources publish “average yearly returns” as a simple arithmetic mean of each year's percent change. For volatile assets, the arithmetic mean systematically overstates compound returns. Example: a stock that returns +100% in year 1 and −50% in year 2 has a CAGR of 0% (end value equals start value) but an arithmetic mean of +25%. Projecting 25 years forward with the arithmetic mean would predict portfolio growth that mathematically cannot occur. We use CAGR because the projection model on this site compounds returns each year — CAGR is the only rate that stays internally consistent with that compounding.

Calendar-year aggregation, not trailing 12 months (TTM). The dividend growth rate (DGR) is computed by summing every dividend paid during each complete calendar year (Jan 1 – Dec 31), then taking the CAGR across the most recent 5 such years. This matches how the SEC requires companies to disclose dividend history in their annual 10-K filings. An alternative — rolling 12-month windows — produces slightly different numbers depending on when each company raises its dividend during the year. Both approaches are defensible. We chose calendar-year aggregation because the resulting figures align directly with what companies publish in their official annual reports, making it easier for users to cross-check our numbers against source documents.

Yield calculations

On ticker pages, we display two yields and they mean different things:

  • Forward yield annualizes the most recent payout cadence. We take a window of the last 4–8 distributions (sized to the detected payout frequency), filter out one-time special dividends (any payment larger than 3× the median of the window), compute the arithmetic mean of what's left, multiply by the payout frequency, and divide by the current share price. This is the right yield to use for an ETF whose distribution policy has changed recently — it reflects what a buyer today should expect going forward.
  • Trailing yield is the sum of the last twelve months of distributions (again with one-time specials filtered out) divided by the current price. It's the conventional “TTM yield” that most data providers show, but with the specials filter applied so a one-off bonus payment doesn't inflate the headline number.

Payout frequency detection

We don't trust the per-row frequency field that comes back from Polygon — it can be stale or wrong, especially for option-income ETFs that switched from monthly to weekly distributions mid-life (MSTY and NVDY both did this). Instead, we compute the median day-gap between the last eight ex-dividend dates and map it to a frequency:

  • median ≤ 10 days → weekly (52 payments / year)
  • median ≤ 45 days → monthly (12)
  • median ≤ 100 days → quarterly (4)
  • median ≤ 200 days → semi-annually (2)
  • otherwise → annually (1)

The detected frequency is what drives both the calculator's “Income per period” KPI and the dividend-frequency dropdown default. We log a warning when detection disagrees with the registry's stated cadence so we notice when a fund changes its payout schedule.

5-year dividend growth rate (DGR)

The 5-year DGR is a compound annual growth rate computed from calendar-year dividend totals: we sum all distributions paid in year N, sum all distributions paid in year N+4, and compute the CAGR between them over four periods. We report 5-year DGR only when a ticker has at least five full calendar years of distribution history; otherwise the field is null and the ticker page falls back to a generic default with a clear note.

Before computing yearly totals, we split-adjust each historical dividend. For every stock split with an ex-date after a given dividend's ex-date, we divide that dividend amount by the split ratio. This produces a continuous, share-equivalent series so that a 2-for-1 split doesn't make pre-split dividends look artificially large.

5-year share price growth (SPG)

On ticker pages we also compute and display a 5-year share price CAGR, derived from Polygon's daily price bars over the trailing five-year window. The endpoint already returns split-adjusted closes, so no extra adjustment is needed here. We report SPG only when at least four years of price history exist; younger funds get null and the calculator falls back to a generic 5% default.

Note that this is a recent-regime CAGR, not a forecast. SCHD's reported SPG of ~4% reflects the 2021–2026 window specifically — longer-window references that include the 2014–2020 bull run come in higher.

Per-ticker data pipeline

All ticker data comes from the Polygon.io REST API. A script (npm run fetch:tickers) iterates through our ticker registry and pulls four endpoints for each one:

  • /v3/reference/dividends — the latest 28 distributions, enough to cover six years of monthly payers or seven years of quarterly payers.
  • /v3/reference/splits — the full split history, used to adjust historical dividends.
  • /v2/aggs/ticker/{X}/prev — the prior-day close, used as the reference price for yield calculation.
  • /v2/aggs/ticker/{X}/range/1/day/… — daily split-adjusted bars over the trailing five-year window, used to compute SPG.

The script writes a JSON snapshot per ticker into the repository. Live re-fetches only happen when we explicitly run the script and rebuild the site — never per visitor request. This keeps Polygon API costs flat and avoids leaking visitor information to a third-party API.

Data hygiene

Single-source data has known failure modes. We don't do multi-source voting (that would mean integrating yfinance and IBKR for what amounts to a calculator default value), but we do apply three correctness checks borrowed from a sister project that runs trading decisions on the same Polygon data:

  • Special dividend filter. Any payment greater than 3× the median of the others in the window is dropped before computing yield. Keeps a one-time bonus distribution from inflating the headline number. If filtering would remove every payment (variable-payer funds), we use the raw window unchanged.
  • Frequency drift detection. When the auto-detected cadence disagrees with what the registry expected, we log the discrepancy at fetch time and use the detected value. This caught MSTY and NVDY after they switched from monthly to weekly distributions.
  • Mean vs median divergence warning. When the arithmetic mean and median of recent payments diverge by more than 10%, we log it as a signal that the ticker may be regime-shifting. This is operational telemetry, not a correction — the displayed yield still uses the mean.

Data freshness

Every ticker JSON snapshot includes a fetchedAt timestamp that is surfaced on the ticker page. An automated job refreshes ticker data every U.S. weekday after the close (Mon–Fri 23:27 UTC, roughly 1–3 hours after Polygon publishes the EOD bars). Between refreshes, the displayed price will drift from the live market price and the trailing yield will drift with it. If a ticker's data is older than four calendar days, the live-data indicator on its page downgrades from green to amber. Treat the snapshot as a starting point, not a real-time quote.

Continuous validation

The numbers you see here are checked by an automated validation harness that runs on a schedule independent of the data-refresh job. The goal is to catch silent failures — stale data, algorithm regressions, source-vs-source divergence — before you do.

  • Daily internal checks (04:00 UTC, all 59 tickers). Five categories per ticker: data freshness (must be within 4 days), dividend-row shape (sorted, dedup'd, non-negative), derived yield re-computation against the registry (±0.1pp), day-over-day field drift (warn at >5% change), and two golden engine regressions (the projection engine and the SCHD inception-to-today backtest must produce locked outputs within 0.5%).
  • Weekly external cross-check (Sundays 04:00 UTC, top 10 tickers). Current share price and a forward yield computed from the trailing 12-month dividend mean are compared against Yahoo Finance — an independent upstream from Polygon. Price tolerance 5%; yield tolerance 15% (Yahoo's coverage of newer weekly cadences is sometimes incomplete and is skipped in that case rather than flagged false).
  • What happens on failure. Any failed check opens a public issue tagged validation on the project repository within minutes. The committed change to fix it ships under that issue. The full per-run report is indata/validation/latest.json (daily) and data/validation/external-latest.json (weekly) on the public repository, so the validation history is reproducible by anyone who wants to audit it.

Dividend projection models

Two models are available depending on the structure of the ticker.

Per-share model (default)

For traditional dividend-paying companies and ETFs of those companies (e.g., SCHD, PG, VOO), the per-share dividend amount is treated as an independent quantity that grows by the user-specified dividend growth rate (DGR). This reflects the economic reality that a company's dividend policy is decoupled from its short-term share price — share price and dividend amount can drift independently.

Yield-based model

For option-income, YieldMax single-name, and collar-strategy ETFs whose distributions are computed as a percentage of net asset value (NAV) (e.g., JEPI, MSTY, NUSI), the per-share dividend is derived from yield × NAV at each projection step. As NAV declines or rises, the dollar amount of each distribution scales proportionally. DGR under this model represents the annual growth rate of the yield itself (i.e., DGR=0% means the yield is maintained at its starting value across the projection).

This more accurately reflects the economic mechanics of these ETFs and produces converging projections for tickers with persistent NAV erosion (e.g., MSTY's historical -48.7% inception CAGR).

Which model applies to which ticker

The model is selected automatically by ticker classification. 14 tickers use the yield-based model: JEPI, JEPQ, SPYI, QQQI, DIVO, QYLD, HYLD, MSTY, NVDY, TSLY, ULTY, YMAG, YMAX, NUSI. The remaining 49 tickers use the per-share model.

Users cannot switch models manually; doing so for traditional dividend payers would produce identical results, and switching the model for option-income ETFs without rebasing the inputs would produce incoherent numbers. To explore alternative scenarios on any ticker page, adjust the DGR and SPG inputs directly.

Limitations and defaults

  • Default DGR for short-history tickers. Some funds (notably the YieldMax single-stock ETFs launched in 2024) don't have five full calendar years of distributions yet. For those, ticker pages default the DGR field to a generic 7%. This is a placeholder so the calculator has something to work with — it is not our forecast of those funds' future growth. Override it with whatever assumption you find defensible.
  • Constant growth assumptions. The model assumes DGR and price growth are constant over the projection horizon. Real dividends and prices are not. Dividend cuts, special distributions, return-of-capital reclassifications, and bear markets all violate the assumption.
  • Pre-tax projections. Every calculator except the tax calculator outputs pre-tax figures. Real after-tax outcomes depend on account type (taxable vs IRA vs 401(k)), state, and your marginal rate.

Tax model

The after-tax calculator applies the U.S. federal qualified-dividend brackets (0%, 15%, 20%) or ordinary-income rates, depending on the toggle. It is a simplified federal-only model. It does not account for state or local taxes, the Net Investment Income Tax (NIIT) on high earners, the Alternative Minimum Tax (AMT), return-of-capital classifications common to option-income ETFs (such as MSTY, ULTY, or covered-call funds), or wash-sale interactions. For real tax planning, talk to a CPA.

Corrections

If a number on the site looks wrong — a dividend amount, a split adjustment, a yield, a CAGR — email [email protected] with the ticker and the discrepancy. We will check it against Polygon's data and either fix the snapshot or document why our number differs.

Last updated: 2026-05-14. Earlier revisions covered the calculator math and the original three-endpoint pipeline; this revision adds the 5-year SPG calculation, the payout-frequency auto-detection logic, the daily-bars endpoint, and the data-hygiene checks (special-dividend filter, frequency drift, mean-vs-median divergence warning).