Generate working TradingView Pine Script v6 indicators — plotting, drawing objects, tables, alerts, inputs and the plot/object limits that break scripts at scale. Provides validated scaffolds to start from rather than composing from memory. Use when asked to write, create or build an indicator, study, oscillator or overlay; to plot something on a chart; to add labels, lines, boxes or a table; or to add alerts to an indicator.
Installs into .claude/skills of the current project.
Are you the author of Pinescript Indicator?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/jpantsjoha-pinescript-indicator)
---
name: pinescript-indicator
description: "Generate working TradingView Pine Script v6 indicators — plotting, drawing objects, tables, alerts, inputs and the plot/object limits that break scripts at scale. Provides validated scaffolds to start from rather than composing from memory. Use when asked to write, create or build an indicator, study, oscillator or overlay; to plot something on a chart; to add labels, lines, boxes or a table; or to add alerts to an indicator."
license: MIT
metadata:
"pinescript-plugin/version": "0.5.0"
"pinescript-plugin/triggers": "write an indicator, create indicator, tradingview study, oscillator, overlay, plot on chart, add labels, draw lines, add a table, alertcondition, pine indicator"
"pinescript-plugin/pine-version": "v6"
---
# Writing Pine v6 indicators
## Start from a scaffold, not from memory
`references/scaffolds/` holds indicators that are validated in CI. Copy one and
modify it. Composing from recollection is how wrong parameter names get in.
| Scaffold | For |
|---|---|
| `overlay-indicator.pine` | Anything drawn on the price chart |
| `oscillator.pine` | Separate pane, bounded, with levels and fills |
| `drawing-objects.pine` | Lines, labels, boxes, tables, with correct cleanup |
**Validate before returning.** Call `validate_pine_script` on the finished script.
## Declaration
```pine
indicator("Title", shorttitle="TTL", overlay=true, max_labels_count=500)
```
`overlay=true` draws on price; `false` opens a separate pane. It cannot be changed
at runtime. Raise `max_lines_count` / `max_labels_count` / `max_boxes_count`
(default 50, max 500) **before** you hit the cap — exceeding it silently deletes
the oldest object.
## Inputs
```pine
length = input.int(14, "Length", minval=1, maxval=500)
source = input.source(close, "Source")
useAlt = input.bool(false, "Use alternative")
mode = input.string("Balanced", "Mode", options=["Fast", "Balanced", "Slow"])
tf = input.timeframe("", "Timeframe")
col = input.color(color.blue, "Line colour")
```
`input.int` returns `simple int`, which is what `ta.*` length parameters require —
a `series int` will not compile there. Every `input.*()` accepts `active=` (July
2025) to grey out a field conditionally.
## Plotting
```pine
plot(series, "Title", color=color.blue, linewidth=2, style=plot.style_line)
plotshape(cond, "Signal", style=shape.triangleup, location=location.belowbar, color=color.green, size=size.tiny)
plotchar(cond, "Mark", char="▲", location=location.abovebar, color=color.red)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dashed)
bgcolor(cond ? color.new(color.red, 90) : na)
```
`plotshape` takes **`style=`**, not `shape=`. `plotchar` takes **`char=`**.
Getting this wrong is the single most common indicator error.
`plot()` must be called at **global scope** — never inside `if`. To plot
conditionally, pass `na`:
```pine
plot(cond ? value : na, "Conditional")
```
**Limit: 64 plots per script**, counting `plot`, `plotshape`, `plotchar`,
`plotcandle`, `plotbar` and `hline`.
## Colour
```pine
color.new(color.red, 90) // 0 = opaque, 100 = invisible
color.rgb(255, 82, 82, 20)
c = value > 0 ? color.green : color.red
```
`transp=` was removed. Use `color.new()`.
## Drawing objects
Two valid call forms — both correct, neither is a mistake:
```pine
line.new(x1=bar_index[10], y1=low[10], x2=bar_index, y2=high) // coordinates
line.new(first_point=p1, second_point=p2) // chart.point
label.new(x=bar_index, y=high, text="hi", style=label.style_label_down)
box.new(left=bar_index[10], top=high, right=bar_index, bottom=low)
```
Parameter names differ per function: `label.new` takes `textalign`; `box.new` and
`table.cell` take `text_halign` / `text_valign`.
**Always clean up.** Objects persist across bars and count against the cap:
```pine
var line trend = na
if barstate.islast
if not na(trend)
line.delete(trend)
trend := line.new(x1=bar_index[20], y1=low[20], x2=bar_index, y2=high)
```
## Tables
Create once with `var`, populate on the last bar only — rebuilding every bar is a
common performance mistake:
```pine
var table t = table.new(position=position.top_right, columns=2, rows=3, bgcolor=color.new(color.black, 80))
if barstate.islast
table.cell(table_id=t, column=0, row=0, text="RSI", text_color=color.white, text_size=size.small)
```
## Alerts
```pine
alertcondition(ta.crossover(fast, slow), title="Cross Up", message="Fast crossed above slow, review sizing")
```
Exactly three parameters. Commas inside the message are fine — they are not
argument separators. The message is **static**: to include a runtime value use
`alert()` inside a condition instead.
## Higher timeframes without repainting
```pine
// REPAINTS — the current HTF bar is still forming
d = request.security(syminfo.tickerid, "D", close)
// STABLE — the previous, closed bar
d = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
```
Cache the call in a variable; never repeat it inline. **Limit: 40 `request.*`
calls per script.**
A **hard-coded external feed** is read with the viewer's data plan, not yours. A
viewer who cannot read it gets "Permission denied for symbol" and the whole script
halts. The validator raises this as **S10**, an info-level hint:
```pine
// STABLE — degrades to na for a viewer who cannot read the feed
spx = request.security("SP:SPX", "D", close[1], lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)
```
If the script is meaningless without that feed, keep the hard stop and say so with
`// pine-ignore: S10`.
## Checklist before returning an indicator
- [ ] `validate_pine_script` returns zero errors
- [ ] `//@version=6` present
- [ ] `plot()` calls at global scope, conditional via `na` not `if`
- [ ] Drawing objects deleted before reassignment
- [ ] Under 64 plots and 40 `request.*` calls
- [ ] `ta.*` calls unconditional
- [ ] `request.security` uses `[1]` or an explicit `lookahead`