{"slug":"price-return-vs-total-return","qid":"horizon_gap","label":"Price return vs total return by holding period: S&P 500 tracker, windows ending July 31, 2026","post_title":"Price Return vs Total Return: The Real Gap","post_url":"/blog/price-return-vs-total-return#q-horizon_gap","columns":["horizon","price_return_pct","total_return_pct","dividend_points_pct"],"rows":[{"horizon":"1-year","price_return_pct":18.2,"total_return_pct":19.5,"dividend_points_pct":1.3},{"horizon":"3-year","price_return_pct":63.1,"total_return_pct":69.3,"dividend_points_pct":6.2},{"horizon":"5-year","price_return_pct":70.7,"total_return_pct":82.6,"dividend_points_pct":12},{"horizon":"10-year","price_return_pct":244.3,"total_return_pct":304.5,"dividend_points_pct":60.2},{"horizon":"20-year","price_return_pct":485,"total_return_pct":736.9,"dividend_points_pct":251.9}],"shape":"ranking","sql":"WITH daily AS (\n    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,\n           argMax(toFloat64(close), window_start) AS close\n    FROM global_markets.delayed_stocks_minute_aggs\n    WHERE ticker = 'SPY'\n      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2006-07-31')\n      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')\n      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60\n           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959\n    GROUP BY d\n),\nspans AS (\n    SELECT y AS years,\n           concat(toString(y), '-year') AS horizon,\n           subtractYears(toDate('2026-07-31'), y) AS start_date\n    FROM (SELECT arrayJoin([1, 3, 5, 10, 20]) AS y)\n),\ndivs AS (\n    SELECT dv.ex_dividend_date AS d,\n           log(1 + toFloat64(dv.cash_amount) / dl.close) AS log_growth\n    FROM global_markets.stocks_dividends AS dv\n    INNER JOIN daily AS dl ON dl.d = dv.ex_dividend_date\n    WHERE dv.ticker = 'SPY'\n      AND dv.cash_amount > 0\n      AND dv.ex_dividend_date <= toDate('2026-07-31')\n),\nendpoints AS (\n    SELECT s.horizon AS horizon,\n           s.years AS years,\n           argMin(dl.close, dl.d) AS start_px,\n           argMax(dl.close, dl.d) AS end_px\n    FROM spans AS s, daily AS dl\n    WHERE dl.d >= s.start_date\n    GROUP BY s.horizon, s.years\n),\nreinvest AS (\n    SELECT s.years AS years,\n           exp(sum(dvs.log_growth)) AS factor\n    FROM spans AS s, divs AS dvs\n    WHERE dvs.d > s.start_date\n    GROUP BY s.years\n)\nSELECT e.horizon AS horizon,\n       round(100 * (e.end_px / e.start_px - 1), 1) AS price_return_pct,\n       round(100 * (e.end_px / e.start_px * r.factor - 1), 1) AS total_return_pct,\n       round(100 * (e.end_px / e.start_px) * (r.factor - 1), 1) AS dividend_points_pct\nFROM endpoints AS e\nINNER JOIN reinvest AS r ON e.years = r.years\nORDER BY e.years","computed_at":"2026-08-03T11:31:10.093710+00:00","elapsed":13.366952111}