Pine Script: Master Doji Candlestick Patterns

Decode market indecision and potential reversals with this comprehensive guide to detecting and strategizing with Doji patterns in TradingView's Pine Script.

Posted: Expertise: Intermediate/Advanced

Why Doji Patterns Matter in Pine Script

A Doji candlestick pattern is a crucial signal in technical analysis, characterized by a very small or non-existent real body, meaning the opening and closing prices are virtually the same. This indicates market indecision, where buyers and sellers are in a state of balance. While a single Doji candle alone may not be a definitive reversal signal, its appearance, especially after a prolonged trend or at key support/resistance levels, can indicate potential exhaustion of the current trend and a possible reversal. This makes Doji patterns invaluable for:

Understanding Various Doji Candlestick Patterns

While all Doji share the characteristic of a small body, their wick lengths and positions create different variations, each conveying a slightly different message:

Standard Doji

A cross shape, indicating perfect balance between buyers and sellers. Common in sideways markets.

Long-Legged Doji

Long upper and lower wicks, signifying strong indecision and volatility, but no clear direction.

Dragonfly Doji

Long lower wick, no upper wick. Price rejected lower levels. Bullish reversal signal if after downtrend.

Gravestone Doji

Long upper wick, no lower wick. Price rejected higher levels. Bearish reversal signal if after uptrend.

Four Price Doji

Open, high, low, and close are all the same. Extremely rare, indicating extreme indecision or very low liquidity.

Key Characteristics of ALL Doji:

Basic Doji Pattern Detection in Pine Script

Here's how to create a basic indicator to detect a standard Doji pattern in Pine Script v5. We'll define a "small body" as a percentage of the total candle range:

//@version=5
indicator("Doji Pattern Detector", overlay=true)

// User input for threshold to define a "small body"
bodyRatioThreshold = input.float(0.1, title="Doji Body Ratio Threshold (e.g., 0.1 for 10%)", minval=0.01, maxval=0.5)

// Calculate candle body size
bodySize = math.abs(close - open)

// Calculate total candle range
candleRange = high - low

// Check if it's a Doji (body size is very small relative to the candle's total range)
// Avoid division by zero if candleRange is 0 (occurs with Four Price Doji)
isDoji = if candleRange > 0
    bodySize / candleRange <= bodyRatioThreshold
else // Handle Four Price Doji case (open==high==low==close)
    true

// Plot signal on the chart
plotshape(isDoji, title="Doji Detected", location=location.belowbar, color=color.new(color.silver, 0), style=shape.circle, size=size.small)

// Alert condition (optional)
alertcondition(isDoji, title="Doji Detected", message="Doji Candlestick Pattern Detected!")

Detecting Specific Doji Types

We can refine the detection to identify specific Doji variations:

//@version=5
indicator("Specific Doji Patterns", overlay=true)

// Input for threshold to define a "small body"
bodyRatioThreshold = input.float(0.1, title="Doji Body Ratio Threshold (e.g., 0.1 for 10%)", minval=0.01, maxval=0.5)
// Input for wick ratio to define "long" or "short" wicks
wickRatioThreshold = input.float(0.4, title="Long Wick Ratio Threshold (e.g., 0.4 for 40%)", minval=0.1, maxval=0.9)


// Helper function to check for small body
isSmallBody(open_val, close_val, high_val, low_val) =>
    bodySize = math.abs(close_val - open_val)
    candleRange = high_val - low_val
    candleRange > 0 ? (bodySize / candleRange <= bodyRatioThreshold) : true // Handles Four Price Doji

// Calculate wick lengths
upperShadow = high - math.max(open, close)
lowerShadow = math.min(open, close) - low
totalRange = high - low

// --- Standard Doji ---
// Small body, relatively balanced upper and lower shadows (or overall short wicks)
isStandardDoji = isSmallBody(open, close, high, low) and
                 (upperShadow / totalRange > 0.1 and lowerShadow / totalRange > 0.1) // Some wicks
                 // More balanced wick check: math.abs(upperShadow - lowerShadow) / totalRange < 0.2

// --- Long-Legged Doji ---
// Small body, very long upper and lower shadows
isLongLeggedDoji = isSmallBody(open, close, high, low) and
                   upperShadow / totalRange >= wickRatioThreshold and
                   lowerShadow / totalRange >= wickRatioThreshold

// --- Dragonfly Doji ---
// Small body (or non-existent) at the top of the candle, long lower shadow, little to no upper shadow
isDragonflyDoji = isSmallBody(open, close, high, low) and
                  lowerShadow / totalRange >= wickRatioThreshold and
                  upperShadow / totalRange < bodyRatioThreshold * 0.5 // Upper shadow is tiny

// --- Gravestone Doji ---
// Small body (or non-existent) at the bottom of the candle, long upper shadow, little to no lower shadow
isGravestoneDoji = isSmallBody(open, close, high, low) and
                   upperShadow / totalRange >= wickRatioThreshold and
                   lowerShadow / totalRange < bodyRatioThreshold * 0.5 // Lower shadow is tiny

// --- Four Price Doji ---
// Open, high, low, close are all the same
isFourPriceDoji = (open == high) and (high == low) and (low == close)

// Plot shapes for detected patterns
plotshape(isStandardDoji,     title="Standard Doji",    location=location.belowbar, color=color.new(color.gray, 0), style=shape.circle, size=size.small, text="SD")
plotshape(isLongLeggedDoji,   title="Long-Legged Doji", location=location.belowbar, color=color.new(color.orange, 0), style=shape.circle, size=size.normal, text="LLD")
plotshape(isDragonflyDoji,    title="Dragonfly Doji",   location=location.belowbar, color=color.new(color.green, 0), style=shape.triangleup, size=size.normal, text="DFD")
plotshape(isGravestoneDoji,   title="Gravestone Doji",  location=location.abovebar, color=color.new(color.red, 0), style=shape.triangledown, size=size.normal, text="GSD")
plotshape(isFourPriceDoji,    title="Four Price Doji",  location=location.belowbar, color=color.new(color.purple, 0), style=shape.diamond, size=size.small, text="4PD")

// Alerts (optional)
alertcondition(isLongLeggedDoji, title="Long-Legged Doji Alert", message="Long-Legged Doji Detected!")
alertcondition(isDragonflyDoji, title="Dragonfly Doji Alert", message="Dragonfly Doji Detected!")
alertcondition(isGravestoneDoji, title="Gravestone Doji Alert", message="Gravestone Doji Detected!")

Advanced Doji Strategies

Doji patterns are powerful signals of indecision, but their true predictive value shines when combined with other indicators and market context. They act as "warnings" that a trend might be losing momentum, rather than direct entry signals.

1. Doji with Trend Confirmation (Using Moving Average)

//@version=5
strategy("Doji with Trend Reversal Confirmation", overlay=true)

// Input for Moving Average length
maLength = input(50, "MA Length (for trend)")
ma = ta.ema(close, maLength) // Using EMA for trend detection

// Doji Pattern Detection (using a general Doji definition for simplicity)
bodyRatioThreshold = 0.1
bodySize = math.abs(close - open)
candleRange = high - low
isDoji = if candleRange > 0
    bodySize / candleRange <= bodyRatioThreshold
else
    true

// Trend Context:
// Potential Bullish Reversal: Doji after a downtrend AND near MA support
isPotentialBullishReversal = isDoji and close < ma and close[1] < ma[1] and ma > ma[1] // price below MA, MA sloping down
// Potential Bearish Reversal: Doji after an uptrend AND near MA resistance
isPotentialBearishReversal = isDoji and close > ma and close[1] > ma[1] and ma < ma[1] // price above MA, MA sloping up

// Plot signals
plotshape(isPotentialBullishReversal, title="Doji (Bullish Reversal Context)", location=location.belowbar, color=color.new(color.blue, 0), style=shape.arrowup, size=size.normal)
plotshape(isPotentialBearishReversal, title="Doji (Bearish Reversal Context)", location=location.abovebar, color=color.new(color.purple, 0), style=shape.arrowdown, size=size.normal)

// Plot MA
plot(ma, "Trend MA", color.blue)

2. Doji with Volume Confirmation

//@version=5
indicator("Doji with Volume Confirmation", overlay=true)

// Doji Pattern Detection (from basic example)
bodyRatioThreshold = input.float(0.1, title="Doji Body Ratio Threshold", minval=0.01, maxval=0.5)
bodySize = math.abs(close - open)
candleRange = high - low
isDoji = if candleRange > 0
    bodySize / candleRange <= bodyRatioThreshold
else
    true

// Volume Confirmation:
// Volume on the Doji candle should be higher than average volume, indicating significant indecision.
avgVolume = ta.sma(volume, 20) // 20-period Simple Moving Average of Volume
isVolumeConfirmed = volume > avgVolume * 1.5 // Volume is 1.5x average

// Combined signal
confirmedDoji = isDoji and isVolumeConfirmed

// Plot confirmed signals
barcolor(confirmedDoji ? color.new(color.teal, 60) : na)

plotshape(confirmedDoji, title="Doji (Vol Confirmed)", location=location.belowbar, color=color.new(color.yellow, 0), style=shape.labeldown, size=size.small, text="Doji Vol")

// Optional: Plot volume for visual check
plot(volume, title="Volume", color=color.gray, style=plot.style_columns)
plot(avgVolume, title="Avg Volume", color=color.orange, style=plot.style_line)

3. Doji at Support/Resistance Zones

//@version=5
indicator("Doji at S/R Zones", overlay=true)

// User input for threshold to define a "small body"
bodyRatioThreshold = input.float(0.1, title="Doji Body Ratio Threshold", minval=0.01, maxval=0.5)

// Define sensitivity for S/R zone detection
srTolerance = input.float(0.005, title="S/R Zone Tolerance (as % of price)", minval=0.001, maxval=0.02)

// Calculate candle body size
bodySize = math.abs(close - open)
candleRange = high - low
isDoji = if candleRange > 0
    bodySize / candleRange <= bodyRatioThreshold
else
    true

// Simple S/R line detection (e.g., using previous swing highs/lows)
// This is a simplified example; real S/R can be complex.
lookback = input(20, "S/R Lookback Periods")
prevHigh = ta.highest(high[1], lookback)
prevLow = ta.lowest(low[1], lookback)

// Check if Doji is near Resistance
isNearResistance = isDoji and math.abs(high - prevHigh) / high <= srTolerance and close > open // Bullish close might be a breakout failure or test
// Check if Doji is near Support
isNearSupport = isDoji and math.abs(low - prevLow) / low <= srTolerance and close < open // Bearish close might be a breakdown failure or test

// Plot signals
plotshape(isNearResistance, title="Doji at Resistance", location=location.abovebar, color=color.new(color.red, 0), style=shape.square, size=size.normal, text="R-Doji")
plotshape(isNearSupport, title="Doji at Support", location=location.belowbar, color=color.new(color.green, 0), style=shape.square, size=size.normal, text="S-Doji")

// Plot S/R lines (for visual confirmation)
plot(prevHigh, "Previous High (Resistance)", color.red, linewidth=1, style=plot.style_stepline)
plot(prevLow, "Previous Low (Support)", color.green, linewidth=1, style=plot.style_stepline)

Optimizing Doji Pattern Performance

To maximize the effectiveness of Doji patterns in your Pine Script strategies:

Common Doji Pattern Pitfalls to Avoid

Warning: Doji patterns are about *indecision*, not necessarily direct reversal signals. Trading them in isolation or without proper context is a common mistake.

Conclusion

The Doji candlestick pattern is a foundational concept in technical analysis, offering invaluable insights into market indecision and potential turning points. While simple in appearance, its various forms (Standard, Long-Legged, Dragonfly, Gravestone, Four Price) each convey nuanced messages when interpreted within the correct market context. By implementing Doji detection in Pine Script, you can automate the identification of these critical moments, integrate them into broader trading strategies with confirmation from other indicators, and enhance your ability to anticipate shifts in market sentiment on TradingView. Remember, the Doji is a signal of pause; the subsequent price action confirms the new direction.