{"slug":"bootstrapping-backtest-confidence-intervals","qid":"horizon_spread","label":"Measured Sharpe dispersion across non-overlapping SPY windows, 2006 to 2025","post_title":"Bootstrapping Backtest Confidence Bands","post_url":"/blog/bootstrapping-backtest-confidence-intervals#q-horizon_spread","columns":["horizon","block_count","sharpe_p05","sharpe_p50","sharpe_p95","sharpe_band_width"],"rows":[{"horizon":"21 sessions","block_count":238,"sharpe_p05":-3.37,"sharpe_p50":1.47,"sharpe_p95":6.97,"sharpe_band_width":10.34},{"horizon":"63 sessions","block_count":78,"sharpe_p05":-1.82,"sharpe_p50":1.27,"sharpe_p95":4.18,"sharpe_band_width":6},{"horizon":"126 sessions","block_count":38,"sharpe_p05":-1,"sharpe_p50":1.16,"sharpe_p95":2.87,"sharpe_band_width":3.86},{"horizon":"252 sessions","block_count":18,"sharpe_p05":-0.7,"sharpe_p50":0.92,"sharpe_p95":2.22,"sharpe_band_width":2.92},{"horizon":"504 sessions","block_count":8,"sharpe_p05":-0.09,"sharpe_p50":0.54,"sharpe_p95":1.58,"sharpe_band_width":1.67}],"shape":"table","sql":"WITH daily AS\n(\n    SELECT\n        date,\n        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)\n            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret\n    FROM global_markets.stocks_daily_aggs\n    WHERE ticker = 'SPY'\n      AND date >= '2005-12-01'\n      AND date <  '2026-01-01'\n),\nindexed AS\n(\n    SELECT\n        ret,\n        row_number() OVER (ORDER BY date) AS i\n    FROM daily\n    WHERE date >= '2006-01-01'\n      AND ret IS NOT NULL\n),\nblocks AS\n(\n    SELECT\n        w,\n        intDiv(i, w)                                            AS blk,\n        count()                                                 AS n,\n        avg(ret) / nullIf(stddevSamp(ret), 0) * sqrt(252)        AS sharpe\n    FROM indexed\n    CROSS JOIN (SELECT arrayJoin([21, 63, 126, 252, 504]) AS w) AS ws\n    GROUP BY w, blk\n    HAVING n = w\n)\nSELECT\n    concat(toString(w), ' sessions')                          AS horizon,\n    count()                                                   AS block_count,\n    round(quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_p05,\n    round(quantileDeterministic(0.50)(sharpe, blk), 2)        AS sharpe_p50,\n    round(quantileDeterministic(0.95)(sharpe, blk), 2)        AS sharpe_p95,\n    round(quantileDeterministic(0.95)(sharpe, blk)\n        - quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_band_width\nFROM blocks\nWHERE sharpe IS NOT NULL\nGROUP BY w\nORDER BY w","computed_at":"2026-08-14T16:02:32.939908+00:00","elapsed":0.058247948}