Tutorial

Custom Types and Objects in Pine Script (UDTs)

If you are juggling three parallel arrays to track one thing, user-defined types will change how you write Pine Script. Here is how to model data cleanly with objects.

10 min read

There is a moment every intermediate Pine Script user hits: you are tracking several things about each of something, say the price, bar index, and strength of each pivot, and you end up with three separate arrays that all have to stay in perfect sync. Update one and forget another, and everything breaks. User-defined types fix this by letting you bundle related fields into a single object.

Defining a type

You declare a user-defined type with the type keyword and list its fields, each with a type and an optional default. Once defined, you create instances with Typename.new() and access fields with dot notation. It reads much like a struct or a simple class in other languages.

pine
//@version=6
indicator("A pivot object", overlay = true)

type Pivot
    float price
    int   barIndex
    bool  isHigh

// Create an instance
p = Pivot.new(price = high, barIndex = bar_index, isHigh = true)

// Access fields with dot notation
plotchar(p.isHigh and bar_index == p.barIndex, "pv", "P", location.abovebar)

Arrays of objects: the real payoff

The moment UDTs earn their keep is when you store them in an array. Instead of parallel arrays for price, bar index, and flag, you keep one array of Pivot objects. Everything about a pivot travels together, so there is no way for the fields to fall out of sync. Combined with var for persistence, covered in the var and varip guide, this is the clean way to build up history you control.

pine
//@version=6
indicator("Track recent pivots as objects", overlay = true)

type Pivot
    float price
    int   barIndex

var Pivot[] pivots = array.new<Pivot>()

ph = ta.pivothigh(5, 5)
if not na(ph)
    array.push(pivots, Pivot.new(price = ph, barIndex = bar_index[5]))
    if array.size(pivots) > 10
        array.shift(pivots)

// Read the most recent pivot's price
recent = array.size(pivots) > 0 ? array.get(pivots, array.size(pivots) - 1).price : na
plot(recent, "last pivot high")

Compare this to maintaining pivotPrices and pivotBars as two arrays indexed together. The object version cannot desynchronize, reads more clearly, and extends easily: add a field to the type and every stored pivot gains it. This is the same structural clarity we recommend when choosing collections in arrays, matrices, and maps.

Describe the data you want to model and PineScripter defines the type and wiring for you

Pairing types with maps

Objects pair especially well with maps. A map whose values are UDT instances lets you keep everything about a keyed entity in one place, for example one object per symbol in a scanner, looked up by ticker. This keeps a multi-symbol tool readable instead of spreading each symbol's data across several maps, and it connects directly to the scanner patterns in dynamic requests.

pine
//@version=6
indicator("Per-symbol state with a map of objects")

type SymState
    float lastClose
    float rsi

var map<string, SymState> states = map.new<string, SymState>()

// In a real scanner you would loop symbols and fill this
map.put(states, "AAPL", SymState.new(lastClose = 100.0, rsi = 55.0))

s = map.get(states, "AAPL")
plot(na(s) ? na : s.rsi, "AAPL RSI")

When to reach for a UDT

Use a user-defined type whenever an entity has more than one attribute that belongs together, and especially when you store many of them. Pivots, trade records, zones, per-symbol state: all are natural objects. For a single loose value, a UDT is overkill. The signal to reach for one is the parallel-array smell: the moment you have two or more arrays that must always be indexed in lockstep, an array of objects is the cleaner design.

Modeling data with types and objects is where Pine Script starts to feel like real software engineering, and it is also where generic AI tends to fall back on messy parallel arrays because it does not reach for the modern constructs. PineScripter writes idiomatic v6 with user-defined types where they make the code cleaner, and validates that it compiles. If you are building something stateful enough to need objects, you can model it cleanly at PineScripter without hand-writing all the boilerplate.


Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.