Overview

An indicator is the correct script kind for any Pine Script that produces visual output on the chart without simulating trades. It is cheaper to compute than a strategy and loads faster. Use indicators for signals, overlays, oscillators, and study panels. Use pine-script-strategies only when you need backtested PnL. Use pine-script-libraries when you want to share logic across scripts.

Declare with indicator() and set scope once

The indicator() call at the top of the script sets the display name, overlay behavior, and resource limits. Set it correctly from the start; changing overlay changes how users have added the script to their charts.

//@version=5
indicator(
    title     = "Dual EMA Ribbon",
    shorttitle = "EMA Ribbon",
    overlay   = true,
    max_lines_count   = 50,
    max_labels_count  = 10,
    max_boxes_count   = 0
)

overlay=true draws on the price pane. overlay=false opens a new panel. Set max_lines_count, max_labels_count, and max_boxes_count to the minimum your script actually needs. TradingView silently drops the oldest objects when the limit is hit, producing confusing visual artifacts.

Define all inputs at the top

Inputs are the user-facing contract. Group them at the top so they are easy to find and hard to lose.

fastLen  = input.int(9,  title="Fast EMA", minval=1,  maxval=200)
slowLen  = input.int(21, title="Slow EMA", minval=1,  maxval=200)
srcPrice = input.source(close, title="Source")
showFill = input.bool(true, title="Show ribbon fill")

Use minval and maxval on every input.int and input.float to prevent users from entering values that break the script logic. Meaningful defaults prevent first-open confusion.

Compute with built-ins before reaching for manual loops

Pine’s built-in functions (ta.ema, ta.rsi, ta.atr, ta.highest, ta.lowest, ta.crossover) run in compiled Pine bytecode. A manual for loop recomputing the same series is slower and more error-prone.

fastEMA = ta.ema(srcPrice, fastLen)
slowEMA = ta.ema(srcPrice, slowLen)
spread  = fastEMA - slowEMA

When you must write a loop, declare loop variables with var if they need to survive to the next bar. Plain assignment inside a loop resets on every bar.

Plot every visible series explicitly

Every plot() call names the series and makes it accessible via the Data Window and Pine’s input.source in other scripts. Unnamed plots cannot be referenced by external scripts.

plot(fastEMA, title="Fast EMA", color=color.blue,  linewidth=1)
plot(slowEMA, title="Slow EMA", color=color.orange, linewidth=1)
 
// Conditional fill
p1 = plot(fastEMA, display=display.none)
p2 = plot(slowEMA, display=display.none)
fillColor = showFill
    ? (fastEMA > slowEMA ? color.new(color.green, 85) : color.new(color.red, 85))
    : na
fill(p1, p2, color=fillColor, title="Ribbon fill")

Use display=display.none for the phantom plots needed by fill(). They do not appear in the legend but give fill() its boundary series.

Use barcolor and bgcolor sparingly

barcolor changes price bar colors based on a condition. It is a strong visual signal; use it only when the condition is the primary focus of the script.

barcolor(fastEMA > slowEMA ? color.new(color.green, 50) : color.new(color.red, 50))

bgcolor tints the chart background. Both are attention-heavy; prefer fills or plots for secondary signals.

Wire alerts with alertcondition

alertcondition registers a named alert that users configure in the TradingView Alerts dialog. Define one per signal.

crossUp   = ta.crossover(fastEMA, slowEMA)
crossDown = ta.crossunder(fastEMA, slowEMA)
 
alertcondition(crossUp,   title="Bullish cross",  message="Fast EMA crossed above Slow EMA on {{ticker}} {{interval}}")
alertcondition(crossDown, title="Bearish cross",  message="Fast EMA crossed below Slow EMA on {{ticker}} {{interval}}")

{{ticker}} and {{interval}} are TradingView template variables that expand at alert fire time. A phone notification that includes symbol and timeframe is self-descriptive. For runtime alerts with dynamic messages, use alert() inside an if block with alert.freq_once_per_bar_close. See pine-script-security for why bar-close firing matters for repainting.