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
- • 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
- • 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
Anatomy of a Valid Pin Bar
Not every candle with a long wick is a valid Pin Bar. Here are the key characteristics:
Wick to Body Ratio
The wick (tail) should be at least 2-3x the length of the body. Ideally 3x or more.
Body Position
The body should be at one end of the candle. For bullish: body at top. For bearish: body at bottom.
Opposite Wick
The wick on the opposite side should be very small or non-existent (less than 10% of the main wick).
Location Matters
Pin Bars are most effective at key levels: support/resistance, supply/demand zones, or moving averages.
Protrusion
The wick should 'stick out' from surrounding candles — showing it tested beyond recent price action.
Common Mistake
Detecting Pin Bars with Python
Let's write Python code to automatically detect valid Pin Bar patterns in price data.
# 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:
🕯️ 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 | MODERATEPin Bars at Key Levels
Now let's enhance our detector to identify Pin Bars that form at significant price levels (like moving averages):
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:
Identify the Context
Is the Pin Bar at a key level? (S/R, S&D zone, MA, trendline). Without context, skip the trade.
Confirm the Pattern
Check wick:body ratio (≥2x). Ensure opposite wick is minimal. Look for protrusion from nearby candles.
Entry Method
Conservative: Enter on break of Pin Bar high/low. Aggressive: Enter at 50% retracement of the Pin Bar.
Stop Loss
Place stop loss just beyond the wick tip. This is the invalidation point — if price goes there, the pattern failed.
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:
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
Next Pattern
Hammer & Hanging Man
Coming Soon