Technical Analysis in Pine Script: Build Professional Trading Indicators
Learn how to create professional technical indicators in Pine Script v6. Build custom trend detection, momentum analysis, and market regime indicators with advanced visualization techniques.
Indicators in Pine Script are the building blocks of technical analysis on TradingView. They transform raw price data into visual insights that help you make informed trading decisions. Unlike strategies that execute trades, indicators focus on analyzing and displaying market conditions through plots, colors, and overlays that make complex data easily understandable.
What Are Indicators & Why They Matter
Pine Script indicators are specialized scripts that analyze price action and market data to identify trading opportunities. They can overlay on price charts or display in separate panes, helping traders spot trends, reversals, and market conditions. With indicators, you can visualize everything from simple moving averages to complex market regime analyses.
Real-World Applications
Technical Analysis
- Create custom trend identification systems
- Build momentum and volatility measures
- Design support/resistance detection tools
- Implement volume analysis indicators
Market Regime Analysis
- Detect trending vs ranging markets
- Measure volatility conditions
- Analyze market breadth
- Monitor liquidity levels
Signal Generation
- Build multi-timeframe confirmation systems
- Create custom alert conditions
- Design trade entry/exit signals
- Develop risk management overlays
Basic Usage: Your First Indicator
Let's create a smart trend detection indicator that combines multiple technical factors:
//@version=6
indicator("Smart Trend Detector", overlay=true)
// Input Parameters
fast_length = input.int(13, "Fast EMA Length")
slow_length = input.int(21, "Slow EMA Length")
atr_length = input.int(14, "ATR Length")
vol_length = input.int(20, "Volume MA Length")
// Calculate Technical Components
fast_ema = ta.ema(close, fast_length)
slow_ema = ta.ema(close, slow_length)
atr = ta.atr(atr_length)
vol_ma = ta.sma(volume, vol_length)
// Trend Detection Logic
trend_strength = math.abs(fast_ema - slow_ema) / atr
is_trending = trend_strength > 0.5
vol_confirmed = volume > vol_ma
trend_up = fast_ema > slow_ema
// Color Logic for Visualization
trend_color = is_trending ? (trend_up ? color.new(color.green, 20) : color.new(color.red, 20)) : color.new(color.gray, 50)
// Plotting with Style
plot(fast_ema, "Fast EMA", color=trend_color, linewidth=2)
plot(slow_ema, "Slow EMA", color=color.new(color.blue, 50), linewidth=1)
// Add Background Coloring for Trend Strength
bgcolor(is_trending ? color.new(trend_color, 90) : na)
// Alerting Conditions
alertcondition( is_trending and trend_up and vol_confirmed, title="Strong Uptrend Detected", message="Price showing strong upward momentum with volume confirmation")
This basic indicator demonstrates several key concepts:
- Combining multiple technical factors
- Dynamic color visualization
- Background highlighting for emphasis
- Volume confirmation integration
- Built-in alert conditions
Common Patterns and Best Practices
Pattern 1: Multi-Timeframe Indicator
This example shows how to build an indicator that analyzes multiple timeframes:
//@version=6
indicator("MTF Momentum Scanner", overlay=false)
// Timeframe Inputs
htf = input.timeframe("240", "Higher Timeframe")
itf = input.timeframe("60", "Intermediate Timeframe")
momentum_length = input.int(14, "Momentum Length")
// Function to Calculate Momentum
getMomentum(timeframe) =>
sec_close = request.security(syminfo.tickerid, timeframe, close)
100 * (sec_close - sec_close[momentum_length]) / sec_close[momentum_length]
// Calculate Momentum for Each Timeframe
htf_mom = getMomentum(htf)
itf_mom = getMomentum(itf)
ctf_mom = getMomentum("") // Current timeframe
// Define Momentum Zones
strong_level = 3.0
weak_level = 1.0
// Function to Get Momentum State
getMomState(mom) =>
if mom > strong_level
1 // Strong up
else if mom < -strong_level
-1 // Strong down
else if math.abs(mom) < weak_level
0 // Neutral
else
0.5 * math.sign(mom) // Weak trend
// Calculate Combined Signal
htf_state = getMomState(htf_mom)
itf_state = getMomState(itf_mom)
ctf_state = getMomState(ctf_mom)
// Plotting with Color Gradients
c_strong_up = color.new(color.green, 20)
c_weak_up = color.new(color.green, 60)
c_neutral = color.new(color.gray, 40)
c_weak_down = color.new(color.red, 60)
c_strong_down = color.new(color.red, 20)
getStateColor(state) =>
state == 1 ? c_strong_up : state == 0.5 ? c_weak_up : state == 0 ? c_neutral : state == -0.5 ? c_weak_down : c_strong_down
// Create Momentum Plots
plot(htf_mom, "HTF Momentum", color=getStateColor(htf_state), linewidth=3)
plot(itf_mom, "ITF Momentum", color=getStateColor(itf_state), linewidth=2)
plot(ctf_mom, "CTF Momentum", color=getStateColor(ctf_state), linewidth=1)
// Add Reference Lines
hline(0, "Zero Line", color=color.new(color.white, 50))
hline(strong_level, "Upper Strong", color=color.new(color.green, 70))
hline(-strong_level, "Lower Strong", color=color.new(color.red, 70))
This advanced pattern shows:
- Multi-timeframe data handling
- Custom function implementation
- State management and classification
- Color gradient visualization
- Reference level plotting
Pattern 2: Adaptive Indicator with Market Context
Here's how to create an indicator that adapts to market conditions:
//@version=6
indicator("Adaptive RSI", overlay=false)
// Market Context Parameters
vol_length = input.int(20, "Volatility Length")
trend_length = input.int(50, "Trend Length")
rsi_length = input.int(14, "RSI Base Length")
// Market Context Calculation
volatility = ta.atr(vol_length) / ta.sma(close, vol_length) * 100
trend_strength = math.abs(ta.sma(close, trend_length) - close) / close * 100
// Adaptive RSI Length
calcAdaptiveLength() =>
int simple_length = rsi_length
simple_length
// Calculate Adaptive RSI
adaptive_length = calcAdaptiveLength()
adaptive_rsi = ta.rsi(close, adaptive_length)
// Dynamic Overbought/Oversold Levels
ob_level = input.float(70, "Overbought Level")
os_level = input.float(30, "Oversold Level")
// Color Logic
rsi_color = adaptive_rsi > ob_level ? color.new(color.red, 20) : adaptive_rsi < os_level ? color.new(color.green, 20) : color.new(color.blue, 40)
// Plotting
plot(adaptive_rsi, "Adaptive RSI", color=rsi_color, linewidth=2)
band1 = hline(ob_level, "Upper Band", color=color.new(color.red, 50))
band0 = hline(50, "Middle Band", color=color.new(color.gray, 50))
band2 = hline(os_level, "Lower Band", color=color.new(color.green, 50))
fill(band1, band0, color=color.new(color.red, 95))
fill(band0, band2, color=color.new(color.green, 95))
// Information Display
var table info = table.new(position.top_right, 2, 4)
if barstate.islast
table.cell(info, 0, 0, "Adaptive Length", bgcolor=color.new(color.gray, 90))
table.cell(info, 1, 0, str.tostring(adaptive_length, "#"), bgcolor=color.new(color.gray, 90))
table.cell(info, 0, 1, "Volatility", bgcolor=color.new(color.gray, 90))
table.cell(info, 1, 1, str.tostring(volatility, "#.##"), bgcolor=color.new(color.gray, 90))
table.cell(info, 0, 2, "Upper Band", bgcolor=color.new(color.gray, 90))
table.cell(info, 1, 2, str.tostring(ob_level, "#.#"), bgcolor=color.new(color.gray, 90))
table.cell(info, 0, 3, "Lower Band", bgcolor=color.new(color.gray, 90))
table.cell(info, 1, 3, str.tostring(os_level, "#.#"), bgcolor=color.new(color.gray, 90))
This sophisticated pattern demonstrates:
- Adaptive parameter calculation
- Dynamic level adjustment
- Market context integration
- Information table display
- Advanced visualization techniques
Performance Tips
Memory Management
- Use var keyword for persistent variables
- Clear unused plot series
- Minimize table updates
- Implement efficient data structures
Calculation Efficiency
- Cache repeated calculations
- Use built-in functions when possible
- Minimize request.security calls
- Implement smart repainting prevention
Visual Optimization
- Use appropriate transparency levels
- Implement clean color schemes
- Avoid overlapping elements
- Maintain chart readability
Common Issues and Solutions
Issue 1: Indicator Repainting
Problem: Indicator shows different values on historical vs real-time data
Solution:
// Implement repainting prevention
var float prev_value = na
var float curr_value = na
calcIndicatorValue() =>
// Your calculation logic here
value = ta.sma(close, 20)
value
if barstate.isconfirmed
prev_value := curr_value
curr_value := calcIndicatorValue()
plot(prev_value, "Non-Repainting Value")
Issue 2: Visual Clutter
Problem: Too many overlapping elements making the indicator hard to read
Solution:
// Implement smart visual management
show_extras = input.bool(false, "Show Additional Info")
transparency = input.int(50, "Base Transparency", minval=0, maxval=100)
// Main plot always visible
plot(main_value, "Main", linewidth=2)
// Secondary elements controlled by user
if show_extras
plot(extra_value1, "Extra 1", color=color.new(color.blue, transparency))
plot(extra_value2, "Extra 2", color=color.new(color.green, transparency))
// Dynamic label positioning
if barstate.islast and show_extras
label.new(
bar_index, high,
text="Extra Info",
style=label.style_label_down,
textcolor=color.white,
color=color.new(color.blue, 90)
)
Issue 3: Performance Impact
Problem: Indicator calculations slowing down chart performance
Solution:
// Implement calculation optimization
max_bars = 500
var values = array.new_float(0)
// Function to maintain efficient array size
manageCacheSize() =>
if array.size(values) > max_bars
array.shift(values)
// Calculate only what's needed
if barstate.isconfirmed
new_value = calcIndicatorValue()
array.push(values, new_value)
manageCacheSize()
// Plot using efficient data structure
plot(array.get(values, array.size(values) - 1))
Elevate your trading today!
Join TradeSage and get access to AI-powered Pinescript generator, intelligent trading assistant, and strategy optimizer. Start your journey to smarter trading today.