Back to Candlestick Patterns
ReversalHigh Probability

Pin Bar
Pattern

The Pin Bar is one of the most powerful price action patterns. Its long wick shows clear rejection of a price level, signaling a potential reversal.

What is a Pin Bar?

A Pin Bar (short for "Pinocchio Bar") is a candlestick with a small body and a long wick (tail) that extends significantly beyond the body.

The long wick represents price rejection — price tried to move in one direction but was pushed back strongly. This rejection often leads to a reversal in the opposite direction.

Bullish Pin Bar

BODY
← Small body
← Long wick
  • • Long lower wick (tail pointing down)
  • • Small body at the top of the candle
  • • Shows rejection of lower prices
  • • Signal: Buy opportunity
  • • Best at: Support levels, demand zones

Bearish Pin Bar

BODY
← Long wick
← Small body
  • • Long upper wick (tail pointing up)
  • • Small body at the bottom of the candle
  • • Shows rejection of higher prices
  • • Signal: Sell opportunity
  • • Best at: Resistance levels, supply zones

Key Rule

The wick should be at least 2x the size of the body. The longer the wick relative to the body, the stronger the rejection signal.

Anatomy of a Valid Pin Bar

Not every candle with a long wick is a valid Pin Bar. Here are the key characteristics:

1

Wick to Body Ratio

The wick (tail) should be at least 2-3x the length of the body. Ideally 3x or more.

2

Body Position

The body should be at one end of the candle. For bullish: body at top. For bearish: body at bottom.

3

Opposite Wick

The wick on the opposite side should be very small or non-existent (less than 10% of the main wick).

4

Location Matters

Pin Bars are most effective at key levels: support/resistance, supply/demand zones, or moving averages.

5

Protrusion

The wick should 'stick out' from surrounding candles — showing it tested beyond recent price action.

Common Mistake

A Pin Bar in the middle of a range is weaker than one at a key level. Context is everything. Always check where the Pin Bar forms, not just how it looks.

Detecting Pin Bars with Python

Let's write Python code to automatically detect valid Pin Bar patterns in price data.

python|pin_bar_detector.py
# Install: pip install yfinance pandas numpy

import yfinance as yf
import pandas as pd
import numpy as np
from typing import List, Dict

def detect_pin_bars(
    ticker: str,
    period: str = "6mo",
    wick_body_ratio: float = 2.0,
    opposite_wick_threshold: float = 0.25
) -> tuple:
    """
    Detect Pin Bar patterns in OHLCV data.
    
    Parameters:
    - ticker: Stock symbol (e.g., "RELIANCE.NS")
    - period: Data period to analyze
    - wick_body_ratio: Min ratio of main wick to body (default: 2x)
    - opposite_wick_threshold: Max ratio of opposite wick to main wick
    
    Returns:
    - List of detected Pin Bars
    - DataFrame with price data
    """
    # Fetch data
    df = yf.download(ticker, period=period, interval="1d")
    
    pin_bars: List[Dict] = []
    
    for i in range(len(df)):
        row = df.iloc[i]
        
        open_price = row['Open']
        high = row['High']
        low = row['Low']
        close = row['Close']
        
        # Calculate body and wicks
        body = abs(close - open_price)
        upper_wick = high - max(open_price, close)
        lower_wick = min(open_price, close) - low
        
        # Avoid division by zero
        if body == 0:
            body = 0.001
        
        # Check for Bullish Pin Bar (long lower wick)
        if lower_wick >= body * wick_body_ratio:
            if upper_wick <= lower_wick * opposite_wick_threshold:
                pin_bars.append({
                    'date': df.index[i],
                    'type': 'BULLISH',
                    'open': round(open_price, 2),
                    'high': round(high, 2),
                    'low': round(low, 2),
                    'close': round(close, 2),
                    'body_size': round(body, 2),
                    'wick_size': round(lower_wick, 2),
                    'wick_ratio': round(lower_wick / body, 2),
                    'strength': 'STRONG' if lower_wick >= body * 3 else 'MODERATE'
                })
        
        # Check for Bearish Pin Bar (long upper wick)
        elif upper_wick >= body * wick_body_ratio:
            if lower_wick <= upper_wick * opposite_wick_threshold:
                pin_bars.append({
                    'date': df.index[i],
                    'type': 'BEARISH',
                    'open': round(open_price, 2),
                    'high': round(high, 2),
                    'low': round(low, 2),
                    'close': round(close, 2),
                    'body_size': round(body, 2),
                    'wick_size': round(upper_wick, 2),
                    'wick_ratio': round(upper_wick / body, 2),
                    'strength': 'STRONG' if upper_wick >= body * 3 else 'MODERATE'
                })
    
    return pin_bars, df


# Example usage
ticker = "RELIANCE.NS"
pin_bars, price_data = detect_pin_bars(ticker)

print(f"\n🕯️ Pin Bar Detection for {ticker}")
print("=" * 60)
print(f"Total Pin Bars Found: {len(pin_bars)}")

# Show recent Pin Bars
print(f"\n📊 Recent Pin Bars:")
for pb in pin_bars[-5:]:
    emoji = "🟢" if pb['type'] == 'BULLISH' else "🔴"
    print(f"  {emoji} {pb['date'].strftime('%Y-%m-%d')} | {pb['type']:8} | "
          f"Ratio: {pb['wick_ratio']}x | {pb['strength']}")

Here's the output you'll see:

output|Output
🕯️ Pin Bar Detection for RELIANCE.NS
============================================================
Total Pin Bars Found: 12

📊 Recent Pin Bars:
  🟢 2024-11-15 | BULLISH  | Ratio: 2.85x | MODERATE
  🔴 2024-11-22 | BEARISH  | Ratio: 3.41x | STRONG
  🟢 2024-12-03 | BULLISH  | Ratio: 2.12x | MODERATE
  🔴 2024-12-10 | BEARISH  | Ratio: 4.02x | STRONG
  🟢 2024-12-18 | BULLISH  | Ratio: 2.67x | MODERATE

Pin Bars at Key Levels

Now let's enhance our detector to identify Pin Bars that form at significant price levels (like moving averages):

python|pin_bar_at_levels.py
def detect_pin_bars_at_levels(
    ticker: str,
    period: str = "1y",
    ma_periods: list = [20, 50, 200]
) -> list:
    """
    Detect Pin Bars that form near moving average levels.
    These have higher probability of success.
    """
    # Fetch data
    df = yf.download(ticker, period=period, interval="1d")
    
    # Calculate moving averages
    for ma in ma_periods:
        df[f'MA_{ma}'] = df['Close'].rolling(window=ma).mean()
    
    # Get basic Pin Bars
    pin_bars, _ = detect_pin_bars(ticker, period)
    
    # Filter for Pin Bars near MAs
    enhanced_signals = []
    
    for pb in pin_bars:
        date = pb['date']
        if date not in df.index:
            continue
            
        row = df.loc[date]
        low, high = pb['low'], pb['high']
        
        # Check if wick touched any MA
        ma_touches = []
        for ma in ma_periods:
            ma_col = f'MA_{ma}'
            if pd.notna(row[ma_col]):
                ma_value = row[ma_col]
                
                # Check if wick reached the MA (within 0.5%)
                if low <= ma_value * 1.005 and high >= ma_value * 0.995:
                    ma_touches.append(ma)
        
        if ma_touches:
            pb['ma_confluence'] = ma_touches
            pb['signal_quality'] = 'HIGH' if len(ma_touches) > 1 else 'MEDIUM'
            enhanced_signals.append(pb)
    
    return enhanced_signals


# Example: Find high-quality Pin Bars
ticker = "RELIANCE.NS"
quality_signals = detect_pin_bars_at_levels(ticker)

print(f"\n⭐ High-Quality Pin Bar Signals for {ticker}")
print("=" * 60)

for signal in quality_signals[-5:]:
    emoji = "🟢" if signal['type'] == 'BULLISH' else "🔴"
    mas = ", ".join([f"MA{ma}" for ma in signal['ma_confluence']])
    print(f"{emoji} {signal['date'].strftime('%Y-%m-%d')} | {signal['type']:8} | "
          f"Near: {mas} | Quality: {signal['signal_quality']}")

Why This Matters

  • • Pin Bars at moving averages have institutional attention
  • • Multiple MA confluence = stronger signal
  • • 200 MA is particularly significant (institutional favorite)
  • • Combine with volume for even better confirmation

Trading the Pin Bar

Here's a systematic approach to trading Pin Bar patterns:

1

Identify the Context

Is the Pin Bar at a key level? (S/R, S&D zone, MA, trendline). Without context, skip the trade.

2

Confirm the Pattern

Check wick:body ratio (≥2x). Ensure opposite wick is minimal. Look for protrusion from nearby candles.

3

Entry Method

Conservative: Enter on break of Pin Bar high/low. Aggressive: Enter at 50% retracement of the Pin Bar.

4

Stop Loss

Place stop loss just beyond the wick tip. This is the invalidation point — if price goes there, the pattern failed.

5

Take Profit

Aim for 1:2 or 1:3 risk-reward. First target: next key level. Second target: 2x the wick size.

Trade Example: Bullish Pin Bar

Setup:

  • • Pin Bar forms at ₹2,800 (demand zone)
  • • Low: ₹2,750 | Close: ₹2,795
  • • Wick: 45 points | Body: 15 points (3x ratio)

Trade:

  • • Entry: ₹2,800 (break of high)
  • • Stop Loss: ₹2,745 (below wick)
  • • Target 1: ₹2,910 (1:2 RR)
  • • Target 2: ₹2,965 (1:3 RR)

Risk Management

  • Never risk more than 1-2% of your capital per trade
  • • Pin Bars fail ~30-40% of the time — always use a stop loss
  • • If the wick is re-tested and broken, exit immediately

Backtesting Pin Bar Strategy

Let's write a simple backtest to see how Pin Bar trades would have performed historically:

python|pin_bar_backtest.py
def backtest_pin_bars(ticker: str, period: str = "2y") -> dict:
    """
    Simple backtest of Pin Bar strategy.
    Entry: Break of Pin Bar high/low
    Stop: Beyond the wick
    Target: 2x risk (1:2 RR)
    """
    pin_bars, df = detect_pin_bars(ticker, period)
    
    results = {
        'total_trades': 0,
        'wins': 0,
        'losses': 0,
        'win_rate': 0,
        'trades': []
    }
    
    for pb in pin_bars:
        idx = df.index.get_loc(pb['date'])
        
        # Need at least 10 days after the Pin Bar to evaluate
        if idx + 10 >= len(df):
            continue
        
        results['total_trades'] += 1
        
        if pb['type'] == 'BULLISH':
            entry = pb['high']
            stop = pb['low'] - 1
            risk = entry - stop
            target = entry + (risk * 2)  # 1:2 RR
            
            # Check next 10 candles
            for j in range(1, 11):
                future = df.iloc[idx + j]
                if future['Low'] <= stop:
                    results['losses'] += 1
                    results['trades'].append({
                        'date': pb['date'], 'type': 'BULLISH',
                        'result': 'LOSS', 'exit': 'Stop Hit'
                    })
                    break
                elif future['High'] >= target:
                    results['wins'] += 1
                    results['trades'].append({
                        'date': pb['date'], 'type': 'BULLISH',
                        'result': 'WIN', 'exit': 'Target Hit'
                    })
                    break
        
        else:  # BEARISH
            entry = pb['low']
            stop = pb['high'] + 1
            risk = stop - entry
            target = entry - (risk * 2)  # 1:2 RR
            
            for j in range(1, 11):
                future = df.iloc[idx + j]
                if future['High'] >= stop:
                    results['losses'] += 1
                    results['trades'].append({
                        'date': pb['date'], 'type': 'BEARISH',
                        'result': 'LOSS', 'exit': 'Stop Hit'
                    })
                    break
                elif future['Low'] <= target:
                    results['wins'] += 1
                    results['trades'].append({
                        'date': pb['date'], 'type': 'BEARISH',
                        'result': 'WIN', 'exit': 'Target Hit'
                    })
                    break
    
    if results['total_trades'] > 0:
        results['win_rate'] = round(
            results['wins'] / results['total_trades'] * 100, 1
        )
    
    return results


# Run backtest
ticker = "RELIANCE.NS"
backtest = backtest_pin_bars(ticker)

print(f"\n📈 Pin Bar Backtest Results for {ticker}")
print("=" * 50)
print(f"Total Trades: {backtest['total_trades']}")
print(f"Wins: {backtest['wins']} | Losses: {backtest['losses']}")
print(f"Win Rate: {backtest['win_rate']}%")
print(f"\nWith 1:2 RR, you're profitable if win rate > 33%")

Improving the Backtest

To improve results, consider adding:

  • • Filter for Pin Bars at key levels only
  • • Volume confirmation (higher volume = stronger signal)
  • • Trend filter (trade only in direction of trend)
  • • Time-based exit if neither target nor stop is hit

Key Takeaways

  • Pin Bar = long wick + small body showing price rejection
  • Bullish Pin Bar: long lower wick (rejection of lower prices)
  • Bearish Pin Bar: long upper wick (rejection of higher prices)
  • Wick should be at least 2-3x the body size for validity
  • Context matters: Pin Bars at key levels are more reliable
  • Stop loss goes beyond the wick tip (invalidation point)
  • Aim for 1:2 or 1:3 risk-reward ratio on trades
All Patterns

Next Pattern

Hammer & Hanging Man

Coming Soon