{"slug":"bullish-candlestick-patterns","qid":"pattern_hit_rates","label":"pattern_hit_rates","post_title":"bullish-candlestick-patterns","post_url":"/blog/lang/pcm/bullish-candlestick-patterns#q-pattern_hit_rates","columns":["pattern","signal_count","next_day_up_pct","any_day_up_pct"],"rows":[{"pattern":"Hammer","signal_count":2100,"next_day_up_pct":54.5,"any_day_up_pct":52.8},{"pattern":"Bullish engulfing","signal_count":907,"next_day_up_pct":48.3,"any_day_up_pct":52.8},{"pattern":"Piercing line","signal_count":380,"next_day_up_pct":49.7,"any_day_up_pct":52.8}],"shape":"ranking","sql":"WITH marked AS\n(\n    SELECT\n        multiIf(\n            least(o, c) - l >= 2 * abs(c - o) AND h - greatest(o, c) <= 0.25 * (h - l),\n                'Hammer',\n            prev_c < prev_o AND c > o AND c >= prev_o AND o <= prev_c,\n                'Bullish engulfing',\n            prev_c < prev_o AND o < prev_c AND c < prev_o\n                AND c > prev_c + (prev_o - prev_c) / 2,\n                'Piercing line',\n            'Ordinary day')  AS pattern,\n        if(next_c > c, 1, 0) AS next_up\n    FROM\n    (\n        SELECT\n            toFloat64(open)               AS o,\n            toFloat64(high)               AS h,\n            toFloat64(low)                AS l,\n            toFloat64(close)              AS c,\n            lagInFrame(toFloat64(open))   OVER back AS prev_o,\n            lagInFrame(toFloat64(close))  OVER back AS prev_c,\n            leadInFrame(toFloat64(close)) OVER fwd  AS next_c\n        FROM global_markets.stocks_daily_aggs\n        WHERE ticker IN ('AAPL','MSFT','NVDA','JPM','JNJ','KO','PG','WMT','XOM','HD')\n          AND date >= '2016-01-01'\n          AND date <= '2025-12-31'\n        WINDOW\n            back AS (PARTITION BY ticker ORDER BY date\n                     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),\n            fwd  AS (PARTITION BY ticker ORDER BY date\n                     ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)\n    )\n    WHERE h > l AND prev_c > 0 AND next_c > 0\n)\nSELECT\n    m.pattern                      AS pattern,\n    count()                        AS signal_count,\n    round(100 * avg(m.next_up), 1) AS next_day_up_pct,\n    any(b.base_up_pct)             AS any_day_up_pct\nFROM marked AS m\nCROSS JOIN\n(\n    SELECT round(100 * avg(next_up), 1) AS base_up_pct\n    FROM marked\n) AS b\nWHERE m.pattern != 'Ordinary day'\nGROUP BY m.pattern\nORDER BY multiIf(m.pattern = 'Hammer', 1, m.pattern = 'Bullish engulfing', 2, 3)","computed_at":"2026-09-25T15:12:04.808747+00:00","elapsed":0.006125938}