---
title: "Pine Script: Indicators"
slug: "pine-script-indicators"
category: "coding"
tags: ["coding", "pine-script", "tradingview", "indicators", "alerts", "charting"]
status: "stable"
last_updated: 2026-08-14
summary: "Declare Pine v6 indicators with indicator(), configure inputs, plot series, and wire alertcondition for chart overlays."
related: ["[[coding/pine-script]]", "[[coding/pine-script-strategies]]", "[[coding/pine-script-libraries]]", "[[coding/pine-script-security]]", "[[coding/general-principles]]"]
---

## 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 [[coding/pine-script-strategies]] only when you need backtested PnL. Use [[coding/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.

```pine
//@version=6
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.

```pine
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.

```pine
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.

```pine
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.

```pine
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.

```pine
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 [[coding/pine-script-security]] for why bar-close firing matters for repainting.

## Related

- [[coding/pine-script]]
- [[coding/pine-script-strategies]]
- [[coding/pine-script-libraries]]
- [[coding/pine-script-security]]
- [[coding/general-principles]]
