{"slug":"reproducible-backtest-quantjourney-bt","qid":"rule_across_names","label":"The same 20/50 rule on five liquid names, 2021 through 2025","post_title":"Reproducible Backtest in Python, No API Key","post_url":"/blog/reproducible-backtest-quantjourney-bt#q-rule_across_names","columns":["symbol","rule_pct","hold_pct","days_long_pct"],"rows":[{"symbol":"QQQ","rule_pct":40.2,"hold_pct":95.9,"days_long_pct":67},{"symbol":"AAPL","rule_pct":24.6,"hold_pct":105,"days_long_pct":59},{"symbol":"SPY","rule_pct":21.9,"hold_pct":82.4,"days_long_pct":71},{"symbol":"MSFT","rule_pct":12.5,"hold_pct":117.5,"days_long_pct":60},{"symbol":"KO","rule_pct":3.8,"hold_pct":27.6,"days_long_pct":59}],"shape":"ranking","sql":"WITH daily AS\n(\n    SELECT\n        ticker,\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 IN ('SPY', 'QQQ', 'AAPL', 'MSFT', 'KO')\n      AND window_start >= '2020-07-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 ticker, d\n),\naveraged AS\n(\n    SELECT\n        ticker,\n        d,\n        px,\n        avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS fast_ma,\n        avg(px) OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS slow_ma,\n        row_number() OVER (PARTITION BY ticker ORDER BY d)                                      AS session_no\n    FROM daily\n),\npositioned AS\n(\n    SELECT\n        ticker,\n        d,\n        px,\n        lagInFrame(if(session_no >= 50 AND fast_ma > slow_ma, 1, 0), 1)\n            OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS long_prior,\n        lagInFrame(px, 1)\n            OVER (PARTITION BY ticker ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS px_prior\n    FROM averaged\n)\nSELECT\n    ticker                                                                      AS symbol,\n    round((exp(sum(log(if(long_prior = 1, px / px_prior, 1.0)))) - 1) * 100, 1) AS rule_pct,\n    round((exp(sum(log(px / px_prior))) - 1) * 100, 1)                          AS hold_pct,\n    round(avg(long_prior) * 100, 0)                                             AS days_long_pct\nFROM positioned\nWHERE px_prior > 0\n  AND d >= toDate('2021-01-01')\nGROUP BY symbol\nORDER BY rule_pct DESC","computed_at":"2026-08-06T05:00:01.235453+00:00","elapsed":0.004403717}