{"slug":"kelly-criterion-position-sizing","qid":"edge_by_year","label":"The same Kelly calculation on the S&P 500 tracker, year by year, 2016 through 2025","post_title":"Kelly Criterion Position Sizing, Measured","post_url":"/blog/kelly-criterion-position-sizing#q-edge_by_year","columns":["year","win_rate_pct","avg_gain_pct","avg_loss_pct","full_kelly_x"],"rows":[{"year":"2016","win_rate_pct":53.8,"avg_gain_pct":0.58,"avg_loss_pct":0.58,"full_kelly_x":13.7},{"year":"2017","win_rate_pct":56.8,"avg_gain_pct":0.33,"avg_loss_pct":0.27,"full_kelly_x":81.8},{"year":"2018","win_rate_pct":51.8,"avg_gain_pct":0.7,"avg_loss_pct":0.8,"full_kelly_x":-3.6},{"year":"2019","win_rate_pct":59.9,"avg_gain_pct":0.56,"avg_loss_pct":0.58,"full_kelly_x":32.1},{"year":"2020","win_rate_pct":57.5,"avg_gain_pct":1.25,"avg_loss_pct":1.49,"full_kelly_x":4.5},{"year":"2021","win_rate_pct":57.9,"avg_gain_pct":0.63,"avg_loss_pct":0.63,"full_kelly_x":24.8},{"year":"2022","win_rate_pct":43.4,"avg_gain_pct":1.29,"avg_loss_pct":1.12,"full_kelly_x":-5.1},{"year":"2023","win_rate_pct":56.8,"avg_gain_pct":0.65,"avg_loss_pct":0.65,"full_kelly_x":21.3},{"year":"2024","win_rate_pct":57.5,"avg_gain_pct":0.59,"avg_loss_pct":0.59,"full_kelly_x":24.6},{"year":"2025","win_rate_pct":57.8,"avg_gain_pct":0.69,"avg_loss_pct":0.79,"full_kelly_x":12.3}],"shape":"table","sql":"WITH daily AS (\n    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS dt,\n           argMax(toFloat64(close), window_start) AS c\n    FROM global_markets.delayed_stocks_minute_aggs\n    WHERE ticker = 'SPY'\n      AND window_start >= '2016-01-01 00:00:00'\n      AND window_start <  '2026-01-01 00:00:00'\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 dt\n),\nsteps AS (\n    SELECT dt, c,\n           lagInFrame(c) OVER (ORDER BY dt ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev\n    FROM daily\n),\nrets AS (\n    SELECT dt, c / prev - 1 AS ret\n    FROM steps\n    WHERE prev > 0 AND c != prev\n)\nSELECT toString(toYear(dt)) AS year,\n       round(100 * countIf(ret > 0) / count(), 1) AS win_rate_pct,\n       round(100 * avgIf(ret, ret > 0), 2) AS avg_gain_pct,\n       round(100 * abs(avgIf(ret, ret < 0)), 2) AS avg_loss_pct,\n       round(countIf(ret > 0) / count() / abs(avgIf(ret, ret < 0))\n             - countIf(ret < 0) / count() / avgIf(ret, ret > 0), 1) AS full_kelly_x\nFROM rets\nGROUP BY year\nHAVING countIf(ret > 0) > 0 AND countIf(ret < 0) > 0\nORDER BY year","computed_at":"2026-07-31T03:07:14.543184+00:00","elapsed":0.005992311}