{"slug":"reproducible-backtest-quantjourney-bt","qid":"lookahead_by_year","label":"Same rule, prior-session signal against same-session signal, SPY by year","post_title":"Reproducible Backtest in Python, No API Key","post_url":"/blog/reproducible-backtest-quantjourney-bt#q-lookahead_by_year","columns":["year","next_bar_pct","same_bar_pct","gap_pp"],"rows":[{"year":2017,"next_bar_pct":16,"same_bar_pct":17.1,"gap_pp":1.1},{"year":2018,"next_bar_pct":6.1,"same_bar_pct":2.1,"gap_pp":3.9},{"year":2019,"next_bar_pct":7.6,"same_bar_pct":8.5,"gap_pp":1},{"year":2020,"next_bar_pct":17.4,"same_bar_pct":18.5,"gap_pp":1.1},{"year":2021,"next_bar_pct":18.7,"same_bar_pct":20.4,"gap_pp":1.7},{"year":2022,"next_bar_pct":-24.7,"same_bar_pct":-22.4,"gap_pp":2.3},{"year":2023,"next_bar_pct":9.4,"same_bar_pct":6.6,"gap_pp":2.8},{"year":2024,"next_bar_pct":13,"same_bar_pct":10.8,"gap_pp":2.2},{"year":2025,"next_bar_pct":10.4,"same_bar_pct":8.7,"gap_pp":1.6}],"shape":"ranking","sql":"WITH daily AS\n(\n    SELECT\n        toDate(toTimeZone(window_start, 'America/New_York')) AS d,\n        toFloat64(argMax(close, window_start))               AS px\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 05:00:00'\n      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60\n           + toMinute(toTimeZone(window_start, 'America/New_York'))) >= 570\n      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60\n           + toMinute(toTimeZone(window_start, 'America/New_York'))) < 960\n    GROUP BY d\n),\naveraged AS\n(\n    SELECT\n        d,\n        px,\n        avg(px) OVER (ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,\n        avg(px) OVER (ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,\n        row_number() OVER (ORDER BY d)                                      AS session_no\n    FROM daily\n),\npositioned AS\n(\n    SELECT\n        d,\n        px,\n        if(session_no >= 50 AND fast_ma > slow_ma, 1, 0) AS long_today,\n        lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)\n            OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,\n        lagInFrame(px, 1)\n            OVER (ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior\n    FROM averaged\n)\nSELECT\n    toYear(d)                                                                   AS year,\n    round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS next_bar_pct,\n    round((exp(sum(log(if(long_today = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS same_bar_pct,\n    round(abs(exp(sum(log(if(long_today = 1, px / px_prior, 1.0))))\n              - exp(sum(log(if(long_prior = 1, px / px_prior, 1.0))))) * 100, 1) AS gap_pp\nFROM positioned\nWHERE px_prior > 0\n  AND toYear(d) >= 2017\nGROUP BY year\nORDER BY year","computed_at":"2026-08-06T05:00:01.335531+00:00","elapsed":0.004709408}