Edgoro
Indicator · Language features

Pine Script functions and methods: a v6 example

Write user-defined functions and methods in Pine Script v6: typed parameters, default values, tuples, a method on your own type, and the rules on scope.

Short answer

Declare a function at the top level with name(type param) => followed by an indented body: its value is the last line, and a tuple [a, b] returns several values. A method is a function whose first parameter is the object it works on, declared with the method keyword, so you can call it as object.name().

The script

A band function that returns a tuple, a one-line function with a default argument, and a Swing type with two methods. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.

Pine Script v6
//@version=6
indicator("Functions and methods", overlay = true)

len      = input.int(20, "Band length", minval = 2)
mult     = input.float(2.0, "Band width (x stdev)", minval = 0.1, step = 0.1)
swingLen = input.int(3, "Swing strength", minval = 1)

// A function that returns three values (a tuple)
bands(float src, simple int length, float k) =>
    float basis = ta.sma(src, length)
    float dev   = k * ta.stdev(src, length)
    [basis, basis + dev, basis - dev]

// One-line functions; decimals has a default value
pctFrom(float price, float ref) => ref != 0 ? (price - ref) / ref * 100 : na
roundTo(float x, int decimals = 2) => math.round(x, decimals)

// A type, and two methods that work on it
type Swing
    float price
    int   index

method pctTo(Swing s, float price) =>
    pctFrom(price, s.price)

method age(Swing s) =>
    na(s.index) ? na : bar_index - s.index

[basis, upper, lower] = bands(close, len, mult)

pl = ta.pivotlow(low, swingLen, swingLen)
var Swing lastLow = Swing.new(na, na)
if not na(pl)
    // Fields of an object can be changed anywhere with :=
    lastLow.price := pl
    lastLow.index := bar_index - swingLen

plot(basis, "Basis", color.gray)
plot(upper, "Upper band", color.teal)
plot(lower, "Lower band", color.teal)
plot(lastLow.price, "Last swing low", color.orange, style = plot.style_stepline)
plot(roundTo(pctFrom(close, basis)), "% from the basis", display = display.data_window)
plot(roundTo(lastLow.pctTo(close), 1), "% above the last swing low", display = display.data_window)
plot(lastLow.age(), "Bars since the swing low", display = display.data_window)

How it works

  • bands(float src, simple int length, float k) =>. Parameter types are optional in scripts (they're required in libraries), but they make mistakes show up at the call. simple int says the length must be fixed once the script starts, which is what ta.sma() and ta.stdev() are given here.
  • The tuple. The last line [basis, basis + dev, basis - dev] is the return value; [basis, upper, lower] = bands(...) unpacks it into three variables.
  • One-line functions put the body after =>. roundTo(x, decimals = 2) can be called with one argument, as in roundTo(pctFrom(close, basis)), or two.
  • type Swing declares an object with two fields; Swing.new(na, na) creates one. Declared with var, the same object lives across bars, and the if block updates its fields with :=.
  • Methods. method pctTo(Swing s, float price) takes the object as its first parameter, so it's called as lastLow.pctTo(close). It reuses pctFrom() rather than repeating the formula.
  • History per call. Each place a function with ta.* calls inside is called keeps its own history, so calling bands() twice with different sources gives two independent sets of bands.

Pitfalls

  • Changing a global variable. count := count + 1 inside a function gives Cannot modify global variable in function. Return the new value and assign it at the top level, or keep the state in an object's fields or an array, which a function may change.
  • ta. calls in a function called conditionally.* If bands() is only called inside an if, its ta.sma() sees only the bars where the block ran (should be called on each calculation). Call the function on every bar and use the result in the if.
  • A series where a fixed value is needed. Passing a length that changes from bar to bar to a function that gives it to ta.ema() fails with series int used but simple int expected. Declaring the parameter as simple int makes the error point at the call that passes it.
  • Plotting from a function. plot() and the other output calls can't be inside a function (Cannot use plot in local scope). Return the value and plot it at the top level.
  • Branches of different types. A function whose if or ternary returns a string in one branch and a number in the other doesn't compile (return types not compatible). Every branch must return the same type, with na allowed in place of any of them.

Variations

A method on a built-in type. Methods aren't limited to your own types: here pushCapped() adds a value to any array<float> and trims it to n elements, so a rolling window is one call.

//@version=6
indicator("Method on an array", overlay = false)
// Push a value and keep only the newest n
method pushCapped(array<float> a, float value, int n) =>
    a.push(value)
    if a.size() > n
        a.shift()
    a
var array<float> closes = array.new<float>()
closes.pushCapped(close, 50)
plot(closes.avg(), "Average of the last 50 closes", color.teal)

Share functions between scripts. Functions you reuse everywhere can live in a library that other scripts import; see the library example.

Questions

Can a Pine Script function return more than one value?

Yes: make the last line a tuple, such as [basis, upper, lower], and unpack it at the call with [b, u, l] = myFunction(). The number of variables must match the number of values returned.

What is the difference between a function and a method in Pine Script?

Only how they're declared and called. A method is declared with the method keyword, and its first parameter is the object it works on, so you call it with a dot: swing.age() instead of age(swing).

Can a Pine Script function keep a value from one bar to the next?

Yes: a var variable declared inside a function is initialized once and keeps its value, and each place the function is called has its own copy. That's how a function can count events or remember a level.

Every script on this page was checked with Edgoro Pine Check (Pine Script v6 rules). Pine Check is a static checker, not TradingView's compiler: TradingView compiles the script when you save it in the Pine Editor. The scripts are examples to learn from, not trading systems. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

Need a different version of this script?

Describe the change in plain English and Edgoro writes the Pine Script v6 for you, checked with Pine Check. For example:

“Write a reusable function that returns a moving average, an upper band and a lower band for any source, and plot it twice: on close and on hl2.”