{"slug":"paper-trading-before-real-money","qid":"session_returns_by_year","label":"Five years of sessions: share closing above the open, typical move, and the extremes (SPY, 2021-2025)","post_title":"Paper Trading Before Real Money","post_url":"/blog/paper-trading-before-real-money#q-session_returns_by_year","columns":["year","sessions","pct_up_sessions","median_abs_move_pct","worst_session_pct","best_session_pct"],"rows":[{"year":"2021","sessions":252,"pct_up_sessions":54.8,"median_abs_move_pct":0.38,"worst_session_pct":-2.41,"best_session_pct":1.55},{"year":"2022","sessions":251,"pct_up_sessions":50.6,"median_abs_move_pct":0.86,"worst_session_pct":-3.35,"best_session_pct":4.8},{"year":"2023","sessions":250,"pct_up_sessions":58.4,"median_abs_move_pct":0.44,"worst_session_pct":-2.04,"best_session_pct":2.38},{"year":"2024","sessions":252,"pct_up_sessions":53.2,"median_abs_move_pct":0.35,"worst_session_pct":-2.93,"best_session_pct":1.56},{"year":"2025","sessions":250,"pct_up_sessions":52,"median_abs_move_pct":0.46,"worst_session_pct":-4.81,"best_session_pct":10.12}],"shape":"table","sql":"SELECT toString(y) AS year,\n       count() AS sessions,\n       round(100 * countIf(ret > 0) / count(), 1) AS pct_up_sessions,\n       round(quantileDeterministic(0.5)(abs(ret) * 100, cityHash64(d)), 2) AS median_abs_move_pct,\n       round(min(ret) * 100, 2) AS worst_session_pct,\n       round(max(ret) * 100, 2) AS best_session_pct\nFROM (\n    SELECT d,\n           toYear(d) AS y,\n           toFloat64(session_close) / toFloat64(session_open) - 1 AS ret\n    FROM (\n        SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,\n               argMin(open, window_start) AS session_open,\n               argMax(close, window_start) AS session_close\n        FROM global_markets.delayed_stocks_minute_aggs\n        WHERE ticker = 'SPY'\n          AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2021-01-01')\n          AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2025-12-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    )\n    WHERE session_open > 0\n)\nGROUP BY year\nORDER BY year","computed_at":"2026-08-22T04:18:11.108144+00:00","elapsed":2.59940656}