How to Create a Candlestick Pattern Detector on TradingView
Translate visual candlestick rules into explicit, testable boolean logic.
Candlestick patterns encode trader psychology into OHLC geometry. This tutorial breaks down common formations, shows how to code them without ambiguity, and provides a Pine Script v5 indicator that labels Hammer, Bullish Engulfing, Doji, and Morning Star setups.
What Is a Candlestick Pattern Detector?
A candlestick pattern detector evaluates each bar’s open, high, low, and close—plus neighboring bars—to flag textbook formations such as Hammer, Engulfing, Doji, and Morning Star.
Unlike subjective chart reading, code forces you to define thresholds: minimum shadow ratios, body overlap, gap requirements, and trend context.
Detectors are educational and semi-systematic; they should be paired with trend, volume, and risk rules before live trading.
Why Traders Use This Indicator
- Automates repetitive visual scanning across many symbols.
- Clarifies exact definitions for backtesting and journaling.
- Speeds up alert creation when patterns match a playbook.
- Useful training wheels for learning price action vocabulary.
- Can feed higher-level scoring systems when combined with filters.
| Parameter | Default | Description |
|---|---|---|
| Body ratio (Doji) | ≤ 10% of range | Maximum body size relative to total range for a neutral Doji classification. |
| Lower wick ratio (Hammer) | ≥ 2× body | Hammer requires long lower shadow vs body with small upper shadow. |
| Engulfing overlap | Body engulfs prior body | Bullish engulfing: down candle then up candle whose body wraps prior body. |
| Morning star gap | Optional gap down/up | Classic three-bar star uses gaps; crypto may relax gap rules. |
How Pattern Logic Maps to OHLC
Define body = abs(close - open), range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low.
Doji: body ≤ small fraction of range (e.g., 10%).
Hammer: small body at top of range, lower wick large vs body, upper wick small.
Bullish engulfing: prior bearish body, current bullish body with open ≤ prior close and close ≥ prior open.
Morning star: long bearish leg, small-bodied star near lows, bullish third candle closing into first body.
Signal Interpretation
Treat patterns as hypotheses: Hammers near support in downtrends hint at rejection; Engulfing after a micro-down leg suggests aggressive counter bidding.
Context matters: the same Hammer at all-time highs carries different implications than one at a measured pullback.
Always define invalidation (e.g., close below Hammer low) when promoting a pattern to a trade idea.
Combining with Other Indicators
Layer volume or relative volume so patterns at liquidity spikes stand out.
Use moving averages or market structure (HH/HL) to filter only patterns that align with your regime.
The Hard Way: Writing Pine Script Manually
Challenges of Manual Coding
Encoding flexible gap rules for 24/7 markets vs equities.
Avoiding overlapping labels when multiple patterns fire on one bar.
Choosing inclusive thresholds that are not too rare or too noisy.
Handling doji-like bars inside complex patterns (Morning Star star bar).
Keeping code readable as pattern count grows past a handful.
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify — Candlestick pattern detector (educational)
//@version=5
indicator("Candlestick Pattern Detector", shorttitle="Patterns", overlay=true)
dojiBodyPct = input.float(10.0, "Doji max body % of range", minval=1, maxval=40) / 100.0
hammerWickMult = input.float(2.0, "Hammer lower wick ≥ body ×", minval=1, step=0.1)
hammerTopPct = input.float(25.0, "Hammer body in top % of range", minval=5, maxval=45) / 100.0
body = math.abs(close - open)
rng = math.max(high - low, syminfo.mintick)
upperW = high - math.max(open, close)
lowerW = math.min(open, close) - low
isDoji = rng > 0 and body / rng <= dojiBodyPct
// Hammer (bullish bias variant at bottom of move — simplified)
bodyTop = math.max(open, close)
bodyBot = math.min(open, close)
bodyInTop = (high - bodyTop) / rng <= hammerTopPct
hammer = body > 0 and lowerW >= body * hammerWickMult and upperW <= body * 0.6 and bodyInTop
// Bullish engulfing
prevBear = close[1] < open[1]
currBull = close > open
bullEngulf = prevBear and currBull and open <= close[1] and close >= open[1] and body > body[1]
// Morning star (relaxed gaps)
c1Bear = close[2] < open[2] and (open[2] - close[2]) > rng[2] * 0.45
c2Small = body[1] / rng[1] <= 0.35
c3Bull = close > open and close > (open[2] + close[2]) / 2
morningStar = c1Bear and c2Small and c3Bull and close[1] < close[2] and low[1] < low[2]
plotshape(isDoji, title="Doji", style=shape.cross, location=location.abovebar, size=size.tiny, color=color.new(color.gray, 0))
plotshape(hammer, title="Hammer", style=shape.triangleup, location=location.belowbar, size=size.small, color=color.new(color.teal, 0))
plotshape(bullEngulf, title="Bull engulf", style=shape.circle, location=location.belowbar, size=size.small, color=color.new(color.lime, 0))
plotshape(morningStar, title="Morning star", style=shape.diamond, location=location.belowbar, size=size.normal, color=color.new(color.aqua, 0))
// Bearish engulfing for symmetry in learning
prevBull = close[1] > open[1]
currBear = close < open
bearEngulf = prevBull and currBear and open >= close[1] and close <= open[1] and body > body[1]
plotshape(bearEngulf, title="Bear engulf", style=shape.circle, location=location.abovebar, size=size.small, color=color.new(color.red, 0))
// Label last signal to reduce clutter
var string lastPat = na
if hammer
lastPat := "Hammer"
if bullEngulf
lastPat := "Bull Engulfing"
if bearEngulf
lastPat := "Bear Engulfing"
if morningStar
lastPat := "Morning Star"
if isDoji
lastPat := "Doji"
var table patTable = table.new(position.top_right, 2, 3, border_width=1)
if barstate.islast
table.cell(patTable, 0, 0, "Last pattern", bgcolor=color.new(color.gray, 60), text_color=color.white)
table.cell(patTable, 1, 0, na(lastPat) ? "—" : lastPat, bgcolor=color.new(color.gray, 60), text_color=color.white)
table.cell(patTable, 0, 1, "Body/Range", bgcolor=color.new(color.gray, 70), text_color=color.white)
table.cell(patTable, 1, 1, str.tostring(body / rng * 100, "#.1") + "%", bgcolor=color.new(color.gray, 70), text_color=color.white)
table.cell(patTable, 0, 2, "Range", bgcolor=color.new(color.gray, 60), text_color=color.white)
table.cell(patTable, 1, 2, str.tostring(rng, format.mintick), bgcolor=color.new(color.gray, 60), text_color=color.white)
alertcondition(bullEngulf, title="Bullish engulfing", message="Bullish engulfing detected")
alertcondition(morningStar, title="Morning star", message="Morning star detected")
alertcondition(hammer, title="Hammer", message="Hammer detected")
alertcondition(isDoji, title="Doji", message="Doji detected")Maintenance Note: Pattern definitions are not universal—document your thresholds. When TradingView tweaks default syminfo.mintick behavior on certain CFDs, guard divisions by range with a floor tick as shown.
The Easy Way: Build with Pineify Visual Editor
What if you could create the same indicator without writing a single line of code? Pineify is a visual editor designed for TradingView users who want professional-grade indicators through an intuitive interface.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| Rule clarity | Easy to write ambiguous if-statements that disagree with textbooks. | Structured builders encourage explicit thresholds per pattern. |
| Scaling patterns | Each new pattern expands nested logic and retest burden. | Reusable pattern modules keep detectors maintainable. |
| Visualization | plotshape/label tuning is repetitive. | Preset styles reduce clashes and overlapping marks. |
| Alerts | Many alertcondition calls to maintain. | Batch alert wiring from pattern checklist. |
| Testing | Manual visual verification is slow. | Export consistent logic for replay and third-party review. |
Keeps pattern definitions documented alongside generated code.
Lowers friction when adding Evening Star or Harami variants.
Helps non-programmers iterate on thresholds safely.
Produces cleaner separation between detection and chart styling.
Speeds up multi-pattern scanners with shared OHLC helpers.
Step-by-Step Tutorial
Normalize OHLC helpers
Centralize body, range, and wick calculations used by every pattern.
Encode Doji threshold
Start with body-to-range percentage; tune per asset volatility.
Implement Hammer
Require wick dominance and body parked near range top.
Add Engulfing pairs
Check prior polarity then full body overlap conditions.
Build Morning Star
Walk three bars with star-bar small body and third-bar recovery.
De-duplicate labels
Prefer plotshape or a single label queue to avoid chart spam.
Publish alerts
Expose only patterns that your playbook actually trades.
Trading Strategies & Pro Tips
Support Hammer + trend filter
Require Hammer near rising 200-period MA and confirm with next-bar higher close.
Pro Tip: Add a minimum range filter to ignore micro-doji noise.
Engulfing continuation
Use bullish engulfing in uptrends as pullback resolution signals.
Morning star reversal
Pair Morning Star with RSI(14) below 35 for oversold context.
Pattern scoring
Assign points for simultaneous small patterns aligning with structure breaks.
Common Mistakes to Avoid
- Calling every long lower wick a Hammer without location context.
- Ignoring volume when patterns print on air-thin liquidity.
- Letting labels stack on the same bar without priority rules.
- Using contradictory thresholds so multiple exclusive patterns always fire.
- Assuming textbook gaps exist on assets that almost never gap.
Frequently Asked Questions
Scale pattern libraries with Pineify
- Add new formations without rewriting OHLC plumbing.
- Keep teams aligned on exact definitions and thresholds.
- Export consistent Pine for education, alerts, and research.
Disclaimer: Trading involves significant risk. The indicators, code, and strategies provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.