Indicator TutorialPine Script v5

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.
ParameterDefaultDescription
Body ratio (Doji)≤ 10% of rangeMaximum body size relative to total range for a neutral Doji classification.
Lower wick ratio (Hammer)≥ 2× bodyHammer requires long lower shadow vs body with small upper shadow.
Engulfing overlapBody engulfs prior bodyBullish engulfing: down candle then up candle whose body wraps prior body.
Morning star gapOptional gap down/upClassic 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.

Pine Script v5
// 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.

FeatureManual Pine ScriptPineify Visual Editor
Rule clarityEasy to write ambiguous if-statements that disagree with textbooks.Structured builders encourage explicit thresholds per pattern.
Scaling patternsEach new pattern expands nested logic and retest burden.Reusable pattern modules keep detectors maintainable.
Visualizationplotshape/label tuning is repetitive.Preset styles reduce clashes and overlapping marks.
AlertsMany alertcondition calls to maintain.Batch alert wiring from pattern checklist.
TestingManual 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

1

Normalize OHLC helpers

Centralize body, range, and wick calculations used by every pattern.

2

Encode Doji threshold

Start with body-to-range percentage; tune per asset volatility.

3

Implement Hammer

Require wick dominance and body parked near range top.

4

Add Engulfing pairs

Check prior polarity then full body overlap conditions.

5

Build Morning Star

Walk three bars with star-bar small body and third-bar recovery.

6

De-duplicate labels

Prefer plotshape or a single label queue to avoid chart spam.

7

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.