
Pine Script v6 introduces enums (enumerations) as a new data type that lets you define a fixed set of named values1. If you have used enums in other programming languages like Python, TypeScript, or C#, the concept is familiar: instead of passing raw strings or integers around your code, you declare a type with a specific list of allowed values, and the compiler enforces that only those values are used.
Enums solve a real problem in Pine Script development. Before v6, if you wanted a user to choose between three oscillator types, you would use input.string() with an options array of plain strings. Your code would then compare those strings in if or switch blocks, and a single typo would silently break the logic. Enums eliminate that risk entirely. The compiler catches invalid values at compile time, and input.enum() automatically generates dropdown menus from your enum fields2.
This guide covers everything you need to know about Pine Script enums: declaring them, using them in inputs and switch statements, storing them in collections, and building practical trading indicators with them.
You declare an enum using the enum keyword, followed by the enum name and its fields3. Each field represents one possible value (member) of the enum type. Fields can have optional string titles that provide human-readable descriptions3.
Here is the basic syntax:
[export ]enum <enumName>
<field_1>[ = <title_1>]
<field_2>[ = <title_2>]
...
<field_N>[ = <title_N>]
And here is a concrete example declaring a Signal enum with three fields:
//@enum An enumeration of named signal states.
//@field buy Represents a "Buy signal" state.
//@field sell Represents a "Sell signal" state.
//@field neutral Represents a "neutral" state.
enum Signal
buy = "Buy signal"
sell = "Sell signal"
neutral
Several things to note about this declaration:
Signal identifier is the enum name, which also serves as the unique type namebuy and sell fields have explicit string titles ("Buy signal" and "Sell signal")neutral field has no specified title, so its title defaults to the string representation of its name: "neutral"3//@enum and //@field annotations document the purpose of the enum and its members in the source code3Pine Script supports special comment annotations for enums that serve as inline documentation:
//@enum describes the overall purpose of the enum type//@field describes what each individual member representsThese annotations are optional but recommended for any enum that other developers will read or that you export from a library.
To access a member of an enum, use dot notation on the enum name3:
enumName.fieldName
You can assign enum members to variables, pass them to function parameters, and store them in user-defined type (UDT) fields. The compiler enforces that any variable declared with an enum type can only hold members of that specific enum or na3.
// The compiler infers the type as Signal from the assigned value.
mySignal = Signal.neutral
// Explicit type declaration is required when initializing with na.
Signal myOtherSignal = na
The first line works without a type annotation because the compiler infers the type from Signal.neutral. The second line requires the explicit Signal type keyword because na alone does not tell the compiler which enum type the variable should accept.
Enum members support the == and != comparison operators2. You can use these comparisons in conditional structures to build clean, type-safe logic:
if mySignal == Signal.buy
strategy.entry("Long", strategy.long)
else if mySignal == Signal.sell
strategy.close("Long")
The input.enum() function creates a dropdown input in the script's Settings/Inputs tab2. The dropdown options are automatically populated from the enum's fields, and the displayed text for each option is the field's title.
Here is the function signature2:
input.enum(defval, title, options, tooltip, inline, group, confirm, display, active) → input enum
And here is a practical example:
//@version=6
indicator("Enum Input Demo")
//@enum An enumeration of oscillator choices.
enum OscType
rsi = "Relative Strength Index"
mfi = "Money Flow Index"
cci = "Commodity Channel Index"
//@variable An enumerator of the OscType enum. Its type is "input OscType".
OscType oscInput = input.enum(OscType.rsi, "Oscillator type")
//@function Calculates one of three oscillators based on a selection value.
calcOscillator(float source, simple int length, OscType selection) =>
result = switch selection
OscType.rsi => ta.rsi(source, length)
OscType.mfi => ta.mfi(source, length)
OscType.cci => ta.cci(source, length)
plot(calcOscillator(close, 20, selection = oscInput))
The dropdown in the script's settings displays "Relative Strength Index", "Money Flow Index", and "Commodity Channel Index" as the available options. When the user selects an option, the corresponding enum member is assigned to oscInput, and the calcOscillator() function uses a switch statement to determine which technical indicator to calculate.
The selection parameter of calcOscillator() can only accept four possible values: OscType.rsi, OscType.mfi, OscType.cci, or na. Any other value would cause a compilation error3.
The optional options parameter lets you display a subset of enum fields in the dropdown instead of all of them2. This is useful when you want to expose only certain options to the user while keeping the full enum available for internal logic.
Enums and switch statements are a natural pair. The switch structure maps each enum member to a specific action or calculation, providing clean pattern matching with compile-time safety.
//@version=6
indicator("Enum Switch Demo", overlay = true)
//@enum Signal display mode options.
enum SignalType
long = "Only long signals"
short = "Only short signals"
both = "Long and short signals"
none
SignalType modeInput = input.enum(SignalType.both, "Signal mode")
float rsiValue = ta.rsi(close, 14)
bool longSignal = ta.crossover(rsiValue, 30)
bool shortSignal = ta.crossunder(rsiValue, 70)
//@variable Whether to show the current signal based on the mode.
bool showLong = switch modeInput
SignalType.long => longSignal
SignalType.both => longSignal
=> false
bool showShort = switch modeInput
SignalType.short => shortSignal
SignalType.both => shortSignal
=> false
plotshape(showLong, "Long", shape.triangleup, location.belowbar, color.green)
plotshape(showShort, "Short", shape.triangledown, location.abovebar, color.red)
The switch structure handles each possible value of the SignalType enum. The default branch (=> false) covers any unmatched cases, including na and the none member. This pattern is cleaner and safer than comparing strings, because the compiler will warn you if you reference an enum member that does not exist.
Pine Script collections (arrays, matrices, and maps) can store enum members, giving you strict control over the values they contain3.
To create an array of enum members, use the enum name in the type template:
enum FooBar
foo
bar
baz
//@variable An array that can only hold FooBar.foo, FooBar.bar, FooBar.baz, or na.
array<FooBar> fooBarArray = array.new<FooBar>()
The array enforces type safety. You cannot push a member of a different enum into this array, even if the field names are identical.
Enums are particularly powerful with maps, because unlike other non-fundamental types, scripts can declare maps with enum keys3. This gives you strict control over every possible key in the map.
Here is a map that uses Signal enum members as keys:
var map<Signal, float> signalCounters = map.new<Signal, float>()
This map can contain at most six key-value pairs: one for each of the five Signal fields plus a possible na key. Maps cannot contain duplicate keys, so this structure is inherently bounded and predictable.
Every enum field has a string title. If you provide an explicit title (using = "Title" syntax), that title is used. If you omit it, the title defaults to the string representation of the field name3.
You can retrieve a field's title at runtime using the str.tostring() function2. This is the only way to convert an enum member to a string for use in labels, tables, or other display logic.
//@version=6
indicator("Field Titles Demo")
enum Exchange
BINANCE
BITSTAMP
COINBASE
KRAKEN
enum Pair
BTCUSD
ETHUSD
SOLUSD
Exchange exchangeInput = input.enum(Exchange.BINANCE, "Exchange")
Pair pairInput = input.enum(Pair.BTCUSD, "Pair")
//@variable The exchange:symbol pair built from enum field titles.
simple string symbol = str.tostring(exchangeInput) + ":" + str.tostring(pairInput)
plot(request.security(symbol, timeframe.period, close), "Requested close", color.purple, 3)
In this example, none of the enum fields have explicit titles, so each field's title is its own name (BINANCE, BTCUSD, etc.). The script concatenates the titles to form a valid ticker identifier like "BINANCE:BTCUSD" for the request.security() call.
Important: The str.format() and log.*() functions cannot accept enum members directly2. To use a field's title in a formatting function, call str.tostring() on the field first, then pass the resulting string.
This example demonstrates a complete indicator that uses an enum with a map to track and count signal states across all chart bars. It combines many enum features: declaration with titles, switch-based logic, map storage with enum keys, and title retrieval for display.
//@version=6
indicator("Signal State Tracker", overlay = true)
//@enum An enumeration of named signal states.
enum Signal
strongBuy = "Strong buy"
buy = "Buy"
neutral = "Neutral"
sell = "Sell"
strongSell = "Strong sell"
int lengthInput = input.int(50, "Length", 2)
//@variable A map counting the number of bars with each signal state.
var map<Signal, float> signalCounters = map.new<Signal, float>()
//@variable A table displaying signal counts.
var table infoTable = table.new(position.top_right, 1, 1, chart.fg_color)
if barstate.isfirst
signalCounters.put(Signal.strongBuy, 0)
signalCounters.put(Signal.buy, 0)
signalCounters.put(Signal.neutral, 0)
signalCounters.put(Signal.sell, 0)
signalCounters.put(Signal.strongSell, 0)
infoTable.cell(0, 0, text_color = chart.bg_color,
text_halign = text.align_left, text_size = size.large)
float ema = ta.ema(close, lengthInput)
float rank = ta.percentrank(close, lengthInput)
//@variable The current signal state based on EMA and percent rank.
Signal signalState = switch
close > ema => rank > 70 ? Signal.strongBuy : rank > 50 ? Signal.buy : Signal.neutral
close < ema => rank < 30 ? Signal.strongSell : rank < 50 ? Signal.sell : Signal.neutral
=> Signal.neutral
// Increment the counter for the current signal state.
signalCounters.put(signalState, signalCounters.get(signalState) + 1)
// Display the signal counts on the last bar.
if barstate.islast
string tableText = ""
for [state, count] in signalCounters
tableText += str.tostring(state) + ": " + str.tostring(count) + "\n"
infoTable.cell_set_text(0, 0, str.trim(tableText))
This script works as follows:
Signal enum defines five possible states, each with a descriptive titlemap<Signal, float> stores running counts for each state, using enum members as keysswitch expression determines the current signal state from the EMA and percent rank calculationsstr.tostring() on each enum keyThe signalCounters map can hold at most six key-value pairs (five enum members plus na), giving you a predictable, bounded data structure.
This example builds a complete oscillator selection system where the user picks an oscillator type from a dropdown, and the script calculates and displays the selected indicator with overbought and oversold levels:
//@version=6
indicator("Multi-Oscillator Strategy")
//@enum An enumeration of oscillator choices with descriptive titles.
enum OscType
rsi = "Relative Strength Index"
mfi = "Money Flow Index"
cci = "Commodity Channel Index"
OscType oscInput = input.enum(OscType.rsi, "Oscillator")
int lengthInput = input.int(14, "Length", minval = 2)
float obLevel = input.float(70, "Overbought Level")
float osLevel = input.float(30, "Oversold Level")
//@function Calculates the selected oscillator.
calcOsc(float source, simple int length, OscType selection) =>
result = switch selection
OscType.rsi => ta.rsi(source, length)
OscType.mfi => ta.mfi(source, length)
OscType.cci => ta.cci(source, length)
float oscValue = calcOsc(close, lengthInput, oscInput)
// Plot the oscillator and reference levels.
plot(oscValue, str.tostring(oscInput), color.blue, 2)
hline(obLevel, "Overbought", color.red, hline.style_dashed)
hline(osLevel, "Oversold", color.green, hline.style_dashed)
// Color the background on overbought and oversold conditions.
bgcolor(oscValue > obLevel ? color.new(color.red, 90) : na)
bgcolor(oscValue < osLevel ? color.new(color.green, 90) : na)
// Plot buy and sell arrows.
bool buySignal = ta.crossover(oscValue, osLevel)
bool sellSignal = ta.crossunder(oscValue, obLevel)
plotshape(buySignal, "Buy", shape.triangleup, location.bottom, color.green)
plotshape(sellSignal, "Sell", shape.triangledown, location.top, color.red)
The enum approach provides several advantages over the older input.string() pattern:
switch statement maps directly to enum members, so a typo causes a compile error instead of a silent bugcalcOsc() function parameter is typed as OscType, so it is impossible to pass an invalid valuestr.tostring(oscInput) to automatically display the selected oscillator's full nameWhile enums are a powerful addition to Pine Script, there are a few constraints to be aware of.
Each declared enum represents a completely unique type3. Even if two enums have identical field names and titles, their members are incompatible. You cannot compare members of different enums or pass a member of one enum to a function expecting another.
enum OscType
rsi = "Relative Strength Index"
enum OscType2
rsi = "Relative Strength Index"
// This causes a compilation error: OscType2 is not OscType.
// calcOscillator(close, 20, selection = OscType2.rsi)
This is by design. The type safety that enums provide depends on each enum being its own distinct type.
Enum names must be unique and cannot match any built-in type names or namespaces3. For example, you cannot name an enum syminfo, ta, or polyline because those names conflict with built-in namespaces. However, capitalized variants like Syminfo or Ta are allowed, since Pine Script identifiers are case-sensitive.
// Works fine because "Syminfo" differs from the built-in "syminfo" namespace.
enum Syminfo
abcd
// These all cause compilation errors:
// enum syminfo -- matches built-in namespace
// enum ta -- matches built-in namespace
// enum polyline -- matches built-in namespace
All enum fields are const values3. They are defined at compile time and cannot be modified during script execution. An input.enum() call returns a value with the input qualifier, meaning it is set once when the user configures the script's settings and does not change across bars2.
Enum members do not support arithmetic or ordering operations. You cannot add, subtract, or compare them with < or >. The only supported comparison operators are == and !=2. If you need ordered comparisons, use a switch to map enum members to numeric values.
Enums bring type safety and readability to Pine Script v6 that was not possible in earlier versions1. They replace fragile string comparisons with compiler-enforced value sets, generate clean dropdown inputs automatically, and work seamlessly with switch statements, arrays, and maps.
The key takeaways for using enums effectively are:
input.enum() dropdowns self-documentingswitch statements to map enum members to calculations or actionsmap<EnumType, ValueType> for enum-keyed data structures with guaranteed bounded sizestr.tostring() to retrieve field titles for display in labels, tables, and log messagesWhether you are building oscillator selectors, signal state trackers, or multi-mode trading systems, enums give your Pine Script code the same kind of structured, type-safe design that professional developers expect from modern programming languages.
1 Pine Script Release Notes
2 Pine Script v6 Language Reference
3 Pine Script User Manual