Back to Learn
Intermediate18 min read

Pine Script for Custom Indicators

Pine Script is the language behind millions of custom indicators used by traders worldwide. Nydar's indicator engine supports 29 built-in functions with Pine Script-compatible syntax, letting you build custom indicators and run them against live data across 3 asset classes — crypto, stocks, and forex — without installing any software.

What Is Pine Script?

Pine Script is a domain-specific programming language designed exclusively for creating trading indicators and strategies. Concepts that take dozens of lines in Python — like calculating a moving average over the last 20 bars — take a single function call here. If you can write a spreadsheet formula, you can write Pine Script.

Why It Matters

Custom analysis. Combine and invent indicators that match your trading style.

Rapid prototyping. Test an idea in minutes — write, see the chart, tweak, iterate.

Portable skills. Pine Script concepts transfer directly to Nydar's custom indicator builder, which supports 29 functions and 11 ready-made formula examples.

Nydar integration: Build indicators in Pine Script, then adapt them for Nydar's formula builder — variable assignments, function calls like sma() and ema(), and arithmetic operators all transfer. See the Custom Indicators help page.

Pine Script Basics

Every script starts with a version and type declaration:

//@version=5
indicator("My Indicator", overlay=false)

myMA = ta.sma(close, 20)
plot(myMA, title="20 SMA", color=color.blue, linewidth=2)

Script Types

indicator() — visual study. strategy() — backtest-capable with entry/exit logic.

overlay

true — draws on price chart (MAs, bands). false — separate pane (RSI, MACD).

Data Types

int — integers
float — decimals
bool — true/false
string — text
color — rgba values
series — per-bar data

Series vs Simple

A simple value (like 14) is known at compile time. A series (like close) changes every bar. Some parameters require simple values. var initialises once and persists across bars; varip also persists across real-time ticks.

Built-in Variables

Price & Volume

open — opening price

high / low — bar extremes

close — closing price

volume — bar volume

Bar & Symbol

bar_index — bar index (0 = first)

time — UNIX timestamp (ms)

syminfo.ticker — symbol name

timeframe.period — chart timeframe

//@version=5
indicator("Bar Stats", overlay=false)
barRange = high - low
barBody  = math.abs(close - open)
bodyPct  = barBody / barRange * 100
plot(bodyPct, title="Body %", color=color.orange)
hline(50, "50%", color=color.gray, linestyle=hline.style_dashed)
bgcolor(bodyPct < 30 ? color.new(color.purple, 85) : na)

Plots body-to-range ratio. Bars below 30% are highlighted as indecision candles.

Essential Functions

ta.sma(source, length)

Simple Moving Average

ta.ema(source, length)

Exponential Moving Average

ta.rsi(source, length)

RSI oscillator (0-100)

ta.macd(src, fast, slow, sig)

Returns [macd, signal, histogram]

ta.crossover(a, b)

True when a crosses above b

ta.crossunder(a, b)

True when a crosses below b

ta.highest(source, length)

Highest value over N bars

ta.lowest(source, length)

Lowest value over N bars

math.abs(value)

Absolute value

math.round(value)

Round to nearest integer

//@version=5
indicator("RSI + MA Filter", overlay=false)
rsiVal    = ta.rsi(close, 14)
rsiSmooth = ta.sma(rsiVal, 5)
plot(rsiVal, "RSI", color=color.orange)
plot(rsiSmooth, "Smoothed", color=color.blue, linewidth=2)
hline(70, "OB", color=color.red)
hline(30, "OS", color=color.green)
bgcolor(ta.crossover(rsiVal, rsiSmooth) ? color.new(color.green, 90) : na)

Plotting & Visualization

Core Plot Functions

plot(series, title, color, linewidth, style) — line, histogram, columns, or area

plotshape(condition, style, location, color) — triangles, arrows, circles on signal bars

plotchar(condition, char, location, color) — single character markers

bgcolor(color) — background colour for the entire bar

barcolor(color) — change the price bar colour

hline(price, title, color) — fixed horizontal reference line

// Conditional coloring
maFast = ta.ema(close, 9)
maSlow = ta.ema(close, 21)
trendColor = maFast > maSlow ? color.green : color.red
plot(maFast, "Fast MA", color=trendColor, linewidth=2)

// Transparency: color.new(baseColor, 0=opaque to 100=invisible)
bgcolor(ta.rsi(close, 14) > 70 ? color.new(color.red, 90) :
        ta.rsi(close, 14) < 30 ? color.new(color.green, 90) : na)

Tip: Use na as a colour to hide a plot on specific bars — nothing renders when the colour is na.

Building Your First Indicator

A dual MA crossover: when the fast MA crosses above the slow, it signals bullish momentum. This is one of the most common technical analysis techniques. This walkthrough builds it step by step.

1. indicator("Dual MA Crossover", overlay=true) — MAs draw on price.

2. input.int() and input.source() — create a settings panel.

3. Two ta.ema() calls and two ta.crossover()/ta.crossunder() calls.

4. Colour-coded MAs, triangle shapes at crosses, and subtle background tint.

Complete Script

//@version=5
indicator("Dual MA Crossover", overlay=true)
fastLen = input.int(9, "Fast Length", minval=1)
slowLen = input.int(21, "Slow Length", minval=1)
src     = input.source(close, "Source")

fastMA = ta.ema(src, fastLen)
slowMA = ta.ema(src, slowLen)
bullCross = ta.crossover(fastMA, slowMA)
bearCross = ta.crossunder(fastMA, slowMA)

maColor = fastMA > slowMA ? color.green : color.red
plot(fastMA, "Fast MA", color=maColor, linewidth=2)
plot(slowMA, "Slow MA", color=color.gray, linewidth=1)
plotshape(bullCross, "Buy", shape.triangleup, location.belowbar, color.green, size=size.small)
plotshape(bearCross, "Sell", shape.triangledown, location.abovebar, color.red, size=size.small)
bgcolor(fastMA > slowMA ? color.new(color.green, 95) : color.new(color.red, 95))

Seventeen lines. Two MAs, crossover detection, colour-coded trend, and signal markers.

Conditional Logic & Alerts

if/else

var string trend = "Neutral"
if ta.ema(close, 9) > ta.ema(close, 21)
    trend := "Bullish"
else
    trend := "Bearish"

4-space indent. := reassigns; = declares.

Ternary & switch

col = close > open ? color.green : color.red

maLen = switch timeframe.period
    "1"  => 50
    "5"  => 40
    => 20  // default

alertcondition()

Registers conditions users can attach alerts to:

rsiVal = ta.rsi(close, 14)
alertcondition(rsiVal > 70, "RSI Overbought", "RSI above 70 on {{ticker}}")
alertcondition(rsiVal < 30, "RSI Oversold", "RSI below 30 on {{ticker}}")
alertcondition(ta.crossover(rsiVal, 50), "RSI Bull Cross", "Momentum bullish")

Placeholders: {{ticker}}, {{close}}, {{time}}.

Importing into Nydar

Nydar's Custom Indicator Builder uses a formula language that shares core concepts with Pine Script — variables, functions, arithmetic, and lookback references all work the same way.

1

Open Custom Indicators widget from the widget menu.

2

Translate your logic. No indicator()/plot() needed. Use result as output. Conditionals use iff().

3

Validate and preview on live data. Fix errors before saving.

4

Save and add to chart. Name it, save, toggle on from any chart widget.

Pine Script

fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
spread = fast - slow
plot(spread)

Nydar Formula

fast = ema(close, 9)
slow = ema(close, 21)
result = fast - slow

Full reference: 29 functions, syntax rules, and 11 examples at the Custom Indicators help page.

Common Patterns

Colour-Coded RSI Zones

//@version=5
indicator("Color RSI", overlay=false)
rsiVal = ta.rsi(close, 14)
rsiColor = rsiVal > 70 ? color.red : rsiVal < 30 ? color.green : color.gray
plot(rsiVal, "RSI", color=rsiColor, linewidth=2)

Volume Spike Detector

//@version=5
indicator("Volume Spike", overlay=true)
volMA   = ta.sma(volume, 20)
spike   = input.float(2.0, "Spike Multiplier", minval=1.0)
isSpike = volume > volMA * spike
barcolor(isSpike ? (close > open ? color.lime : color.fuchsia) : na)
alertcondition(isSpike, "Vol Spike", "Volume spike on {{ticker}}")

Multi-Timeframe Moving Average

//@version=5
indicator("HTF MA", overlay=true)
htf   = input.timeframe("D", "Higher Timeframe")
htfMA = request.security(syminfo.tickerid, htf, ta.sma(close, 50))
plot(htfMA, "HTF MA", color=color.orange, linewidth=2)

Dynamic Support & Resistance

//@version=5
indicator("Dynamic S/R", overlay=true)
lookback   = input.int(20, "Lookback", minval=5)
resistance = ta.highest(high, lookback)
support    = ta.lowest(low, lookback)
r = plot(resistance, "R", color=color.red)
s = plot(support, "S", color=color.green)
fill(r, s, color=color.new(color.blue, 95))

Tips & Pitfalls

Repainting

Historical signals that change after the bar closes. Causes: request.security() without lookahead=barmerge.lookahead_off, referencing close before bar close, or barstate.isrealtime in calculations. Perfect backtests, failing live.

Future Leak

lookahead=barmerge.lookahead_on gives you a higher-timeframe close before that bar finishes. Always use lookahead_off and verify with barstate.isconfirmed.

na Handling

na propagates — any operand being na makes the result na. Use nz(value, replacement) to substitute defaults. Common on the first N bars.

= vs :=

= declares a new variable. := reassigns. Using = inside an if block creates a local variable that vanishes — a silent logic bug.

Best Practices

Use input() for all parameters — expose every tunable value.

Test on multiple symbols and timeframes — what works on one asset may break on another.

Keep scripts focused — one indicator, one job. Combine simple indicators on your chart.

Comment your logic — explain why, not just what.

Key Takeaways

  • Pine Script is purpose-built for trading — single function calls replace dozens of lines.
  • Built-in variables (open, high, low, close, volume) give immediate access to price data.
  • The ta namespace provides SMA, EMA, RSI, MACD, and crossover detection out of the box.
  • Visualization is first-class — plot(), plotshape(), bgcolor() communicate analysis visually.
  • Watch for repainting — verify historical signals do not change after bars close.
  • Skills transfer to Nydar — adapt scripts for the Custom Indicator Builder directly.

Related Guides

Build Your Own Indicators

Use Nydar's Custom Indicator Builder to turn your Pine Script ideas into live dashboard indicators. No install needed.

Get Started Free

Further Reading

Written by Ryan Clinton

Founder of Nydar. Software engineer with experience at Cowen Inc (Wall Street) and on large-scale infrastructure projects. Built Nydar to make professional trading tools accessible to everyone.

Last reviewed: March 2026