Development of Pine Script Indicators (TradingView)
Pine Script is TradingView's own programming language for creating indicators and strategies right in browser. Looks simple but writing strategy that doesn't give false signals on historical data and actually trades profitably—non-trivial task.
Pine Script v5 Basics
Script Structure
//@version=5
indicator("My Indicator", shorttitle="MI", overlay=true, max_bars_back=500)
// Parameters (user-adjustable)
length = input.int(14, "Period", minval=1, maxval=200)
source = input.source(close, "Source")
show_signals = input.bool(true, "Show Signals")
// Calculations
ema_line = ta.ema(source, length)
// Visualization
plot(ema_line, "EMA", color=color.blue, linewidth=2)
Key difference from regular programming: Pine Script executes on each bar left to right. Variables are not scalars but series. close is not current price but array of all closes where close[0] = current bar, close[1] = previous.
Data Types and Series
// Numeric operations with offset
prev_close = close[1] // previous bar
two_bars_ago = close[2]
// Conditions on series
is_bullish = close > open // true/false for each bar
is_green_candle = close >= open
// Operations with series
rising = close > close[1] and close[1] > close[2] // three rising bars in a row
Technical Indicators
Advanced RSI with Divergence
//@version=5
indicator("RSI with Divergence", overlay=false)
rsi_length = input.int(14, "RSI Length")
rsi_overbought = input.int(70, "Overbought")
rsi_oversold = input.int(30, "Oversold")
div_lookback = input.int(15, "Divergence Lookback")
rsi = ta.rsi(close, rsi_length)
// Bullish divergence: price makes new low, RSI doesn't
price_lower_low = low < ta.lowest(low, div_lookback)[1]
rsi_higher_low = rsi > ta.lowest(rsi, div_lookback)[1]
bullish_div = price_lower_low and rsi_higher_low and rsi < rsi_oversold + 10
// Bearish divergence
price_higher_high = high > ta.highest(high, div_lookback)[1]
rsi_lower_high = rsi < ta.highest(rsi, div_lookback)[1]
bearish_div = price_higher_high and rsi_lower_high and rsi > rsi_overbought - 10
// Visualization
hline(rsi_overbought, "Overbought", color.new(color.red, 50))
hline(rsi_oversold, "Oversold", color.new(color.green, 50))
hline(50, "Midline", color.gray, linestyle=hline.style_dotted)
rsi_color = rsi >= rsi_overbought ? color.red : rsi <= rsi_oversold ? color.green : color.blue
plot(rsi, "RSI", color=rsi_color, linewidth=2)
// Divergence markers
plotshape(bullish_div, "Bullish Div", shape.triangleup, location.bottom,
color.new(color.green, 0), size=size.small)
plotshape(bearish_div, "Bearish Div", shape.triangledown, location.top,
color.new(color.red, 0), size=size.small)
Strategy with Backtesting
//@version=5
strategy("EMA Cross Strategy", overlay=true, initial_capital=10000,
commission_type=strategy.commission.percent, commission_value=0.1)
fast_ema = input.int(9, "Fast EMA")
slow_ema = input.int(21, "Slow EMA")
ema_fast = ta.ema(close, fast_ema)
ema_slow = ta.ema(close, slow_ema)
// Signals
long_signal = ta.crossover(ema_fast, ema_slow)
short_signal = ta.crossunder(ema_fast, ema_slow)
// Entry/exit
if long_signal
strategy.entry("Long", strategy.long)
if short_signal
strategy.close("Long")
// Visualization
plot(ema_fast, "Fast EMA", color.blue)
plot(ema_slow, "Slow EMA", color.orange)
bgcolor(long_signal ? color.new(color.green, 90) : na)
After writing strategy—"Strategy Tester" tab in TradingView shows all backtesting metrics: Net Profit, Profit Factor, Max Drawdown, Win Rate.
Working with Tables and Labels
// Table with metrics in corner of chart
var table metrics_table = table.new(position.top_right, 2, 5,
bgcolor=color.new(color.black, 70), border_width=1)
if barstate.islast // draw only on last bar
table.cell(metrics_table, 0, 0, "RSI", text_color=color.gray)
table.cell(metrics_table, 1, 0, str.tostring(math.round(rsi, 2)),
text_color=rsi > 70 ? color.red : rsi < 30 ? color.green : color.white)
// Labels on price chart
if long_signal
label.new(bar_index, low, "BUY\n" + str.tostring(close),
color=color.green, textcolor=color.white,
style=label.style_label_up, size=size.small)
Functions and Libraries
Pine Script v5 supports custom functions and import:
// Custom function
f_supertrend(factor, atr_period) =>
atr = ta.atr(atr_period)
upperband = hl2 + factor * atr
lowerband = hl2 - factor * atr
supertrend = 0.0
direction = 0
supertrend := close > nz(supertrend[1]) ? math.max(lowerband, nz(supertrend[1])) :
math.min(upperband, nz(supertrend[1]))
direction := close > supertrend ? 1 : -1
[supertrend, direction]
[st_line, st_dir] = f_supertrend(3.0, 10)
plot(st_line, "Supertrend", st_dir == 1 ? color.green : color.red, linewidth=2)
// Import library (v5)
import TradingView/ta/2 as tvta
Limitations and Best Practices
Pine Script Limitations:
- No external API access (only TradingView data)
- Maximum
max_bars_back= 500 for custom series - No real order management (strategies—backtesting only)
- No persistent storage between sessions
Best Practices:
- Use
varfor variables to preserve between bars -
barstate.islastfor calculations only on last bar (tables, labels) -
barstate.isconfirmedto avoid repainting on unconfirmed candle - Always add
nachecks:if not na(value) then...
Quality Pine Script indicator with 10,000+ likes on TradingView is powerful marketing tool and source of trust for author.







