When developing indicators and strategies in TradingView’s Pine Script, providing configurable inputs is crucial for flexibility and usability. As scripts grow in complexity, the number of inputs can become substantial, leading to a cluttered settings dialog that is difficult for users to navigate. This is where groups come into play.
What are Groups and Why Use Them?
In Pine Script, groups provide a mechanism to logically organize input options within the script’s settings dialog. Instead of presenting a flat, potentially long list of inputs, you can bundle related parameters under distinct headings or ‘groups’.
The primary purpose of groups is to improve the user experience for anyone interacting with your script’s settings. They bring structure and order to the configuration process, making it easier to find specific parameters and understand how different settings relate to one another.
Benefits of Using Groups for Inputs
Implementing groups offers several key benefits:
- Improved Readability: The settings dialog becomes much cleaner and easier to scan.
- Enhanced Usability: Users can quickly locate the parameters they need to adjust, reducing frustration.
- Logical Structure: You can arrange inputs based on their function (e.g., ‘Moving Average Settings’, ‘RSI Parameters’, ‘Strategy Entry Conditions’).
- Maintainability: For developers, organizing inputs in the code often leads to better-structured and more maintainable scripts.
- Reduced Clutter: Complex scripts with dozens of inputs become manageable.
By using groups effectively, you transform a potentially overwhelming list of options into a well-organized configuration interface.
Basic Syntax for Defining a Group
Grouping inputs in Pine Script is straightforward. Each input function (input.int, input.float, input.bool, input.string, input.color, input.time, input.session, input.symbol, input.resolution, input.src) accepts an optional group argument.
This argument expects a string value which will be used as the name of the group header in the script’s settings dialog.
//@version=5
indicator("Basic Group Example", shortname="BGE")
// Input 1 for Group A
input.int(defval=10, title="Period A", group="Settings Group A")
// Input 2 for Group A
input.float(defval=2.0, title="Multiplier A", group="Settings Group A")
// Input 1 for Group B
input.bool(defval=true, title="Enable B", group="Settings Group B")
// Input 2 for Group B
input.string(defval="Label", title="Text B", group="Settings Group B")
// Inputs without a group appear outside of any group
input.int(defval=20, title="Ungrouped Period")
plot(close)
In this basic example, inputs with group="Settings Group A" will appear together under that header, and inputs with group="Settings Group B" will appear under the ‘Settings Group B’ header. Inputs without the group argument will be listed at the top, before any defined groups.
Creating and Configuring Input Groups
Once you understand the basic syntax, you can refine how groups are created and how inputs within them behave.
Defining Input Options within a Group
To include an input option within a group, you simply pass the desired group name string to the group argument of the input function call. All input function calls that use the exact same string for the group argument will have their corresponding input controls placed under that group’s heading in the settings.
It’s important to ensure consistency in the group name string. A typo or slight variation will result in a new, separate group being created.
Controlling the Order and Appearance of Inputs in Groups
The order in which inputs appear within a specific group is determined by the order in which their corresponding input.* functions are called within your Pine Script code. The first input function called with a specific group string will result in its control appearing first under that group’s header, the second call will appear second, and so on.
Pine Script handles the visual appearance of the group header itself. You just provide the name via the group argument for the first input you want in that group, and Pine Script creates the header. Subsequent inputs using the same group name are placed beneath it.
Using group Argument in input.int, input.float, input.string, etc.
The group argument is available across almost all input.* functions. This allows you to group any type of parameter:
input.int(): For integer values (periods, counts).input.float(): For floating-point values (multipliers, thresholds).input.bool(): For boolean flags (enable/disable features).input.string(): For text inputs (labels, identifiers).input.color(): For color selections.input.time(): For time inputs.input.session(): For trading session inputs.input.symbol(): For symbol inputs.input.resolution(): For resolution inputs.input.src(): For source series inputs (e.g., close, hl2).
The usage is identical for all: input.type(defval=..., title="...", group="My Group Name").
Advanced Grouping Techniques
Beyond simple flat grouping, you can leverage naming conventions and conditional logic to create more sophisticated input configurations.
Nesting Groups for Complex Input Structures
While Pine Script doesn’t support true nested visual containers like a tree structure, you can create the appearance of nesting by using hierarchical names for your groups, separated by a slash (/).
For example, you might have a main group called “Strategy Parameters”. Within that, you could have subgroups for “Entry Conditions” and “Exit Conditions”. You would name the groups like this:
group="Strategy Parameters/Entry Conditions"group="Strategy Parameters/Exit Conditions"
In the settings dialog, Pine Script will create distinct group headers for “Strategy Parameters/Entry Conditions” and “Strategy Parameters/Exit Conditions”. This naming convention helps users understand the hierarchical relationship between the settings, even though they are presented as separate top-level groups.
//@version=5
indicator("Nested Group Example", shortname="NGE")
input.int(defval=14, title="Length", group="RSI Settings")
input.float(defval=70.0, title="Overbought", group="RSI Settings")
input.float(defval=30.0, title="Oversold", group="RSI Settings")
input.int(defval=20, title="Short Period", group="Moving Averages/Simple MAs")
input.int(defval=50, title="Long Period", group="Moving Averages/Simple MAs")
input.int(defval=9, title="Signal Period", group="Moving Averages/Exponential MAs")
plot(close) // Placeholder plot
In this example, inputs under “RSI Settings” form one logical block, while inputs under “Moving Averages/Simple MAs” and “Moving Averages/Exponential MAs” use the slash notation to suggest a relationship between the MA types.
Conditional Group Display (show_if)
The show_if argument allows you to make an input’s visibility dependent on the value of a preceding boolean input. To make an entire group appear or disappear based on a condition, you need to apply the same show_if condition to every single input within that group.
This is a common pattern for enabling/disabling features within a script. You might have a boolean input like “Enable Stop Loss” and then group all stop-loss related parameters (type, percentage, points) under a “Stop Loss Settings” group, applying show_if=enableStopLoss to each parameter in that group.
//@version=5
strategy("Conditional Group Example", shortname="CGE")
enableStopLoss = input.bool(defval=false, title="Enable Stop Loss")
stopLossType = input.string(
defval="Percentage",
title="Type",
options=["Percentage", "Points"],
group="Stop Loss Settings",
show_if=enableStopLoss
)
stopLossValue = input.float(
defval=1.0,
title="Value (% or Pips)",
group="Stop Loss Settings",
show_if=enableStopLoss
)
// --- Example Usage (Simplified) ---
longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))
if longCondition
strategy.entry("Buy", strategy.long)
// Apply stop loss conditionally based on the input
if enableStopLoss
if stopLossType == "Percentage"
strategy.exit("Exit Buy", from_entry="Buy", stop=strategy.position_avg_price * (1 - stopLossValue / 100))
else if stopLossType == "Points"
strategy.exit("Exit Buy", from_entry="Buy", stop=strategy.position_avg_price - stopLossValue * syminfo.mintick)
In this strategy, the “Stop Loss Settings” group will only appear in the settings dialog if the “Enable Stop Loss” checkbox is checked. Remember to apply show_if to all inputs within the group.
Using Groups for Strategy Settings
Strategies often have multiple distinct components: entry rules, exit rules (take profit, stop loss), time filters, position sizing, etc. Groups are exceptionally useful for segmenting these parameters.
- Entry Settings Group: Contains parameters for the entry signal logic (e.g., indicator periods, crossover thresholds).
- Exit Settings Group: Contains parameters for various exit conditions (e.g., Take Profit %, Stop Loss points, trailing stop distance).
- Time Management Group: Includes inputs for session filters or date ranges.
- Risk Management Group: Parameters for position size calculation or maximum drawdown limits.
Organizing your strategy inputs this way makes it vastly easier for users (or yourself during optimization) to configure and understand the strategy’s behavior.
//@version=5
strategy("Grouped Strategy Settings", shortname="GSS")
// --- Entry Settings ---
emaShortPeriod = input.int(defval=12, title="Short EMA Period", group="Entry Settings")
emaLongPeriod = input.int(defval=26, title="Long EMA Period", group="Entry Settings")
// --- Exit Settings ---
useTakeProfit = input.bool(defval=false, title="Enable Take Profit", group="Exit Settings")
takeProfitPct = input.float(defval=2.0, title="Take Profit (%)", group="Exit Settings", show_if=useTakeProfit)
useStopLoss = input.bool(defval=true, title="Enable Stop Loss", group="Exit Settings")
stopLossPct = input.float(defval=1.0, title="Stop Loss (%)", group="Exit Settings", show_if=useStopLoss)
// --- Time Settings ---
useSessionFilter = input.bool(defval=false, title="Enable Session Filter", group="Time Settings")
tradingSession = input.session(defval="0930-1600", title="Session", group="Time Settings", show_if=useSessionFilter)
// --- Strategy Logic (Simplified) ---
shortEMA = ta.ema(close, emaShortPeriod)
longEMA = ta.ema(close, emaLongPeriod)
longCondition = ta.crossover(shortEMA, longEMA)
shortCondition = ta.crossunder(shortEMA, longEMA)
if longCondition
strategy.entry("Long", strategy.long)
if shortCondition
strategy.entry("Short", strategy.short)
// Exit logic
if useTakeProfit
strategy.exit("TP Long", from_entry="Long", profit=strategy.position_avg_price * (takeProfitPct / 100) / syminfo.mintick)
strategy.exit("TP Short", from_entry="Short", profit=strategy.position_avg_price * (takeProfitPct / 100) / syminfo.mintick)
if useStopLoss
strategy.exit("SL Long", from_entry="Long", stop=strategy.position_avg_price * (1 - stopLossPct / 100))
strategy.exit("SL Short", from_entry="Short", stop=strategy.position_avg_price * (1 + stopLossPct / 100))
// Session filter (apply to entries, exits often need to check regardless of session)
var bool inSession = true
if useSessionFilter
inSession := not na(time(tradingSession))
if not inSession
strategy.cancel_all()
strategy.close_all()
This example clearly separates the parameters for different aspects of the strategy, making the settings panel highly navigable.
Practical Examples of Using Groups
Let’s look at how you might apply grouping to common technical analysis components.
Grouping Moving Average Settings
A script using multiple moving averages can quickly accumulate parameters for periods, types (SMA, EMA, etc.), sources, and colors. Grouping them is essential.
//@version=5
indicator("MA Group Example", shortname="MAG", overlay=true)
// --- Short MA Settings ---
shortMAPeriod = input.int(defval=20, title="Period", group="Short Moving Average")
shortMASource = input.src(defval=close, title="Source", group="Short Moving Average")
shortMAType = input.string(defval="SMA", title="Type", options=["SMA", "EMA"], group="Short Moving Average")
shortMAColor = input.color(defval=color.blue, title="Color", group="Short Moving Average")
// --- Long MA Settings ---
longMAPeriod = input.int(defval=50, title="Period", group="Long Moving Average")
longMASource = input.src(defval=close, title="Source", group="Long Moving Average")
longMAType = input.string(defval="SMA", title="Type", options=["SMA", "EMA"], group="Long Moving Average")
longMAColor = input.color(defval=color.red, title="Color", group="Long Moving Average")
// --- MA Calculation Logic ---
shortMA = shortMAType == "SMA" ? ta.sma(shortMASource, shortMAPeriod) : ta.ema(shortMASource, shortMAPeriod)
longMA = longMAType == "SMA" ? ta.sma(longMASource, longMAPeriod) : ta.ema(longMASource, longMAPeriod)
// --- Plotting ---
plot(shortMA, color=shortMAColor, title="Short MA")
plot(longMA, color=longMAColor, title="Long MA")
Here, all parameters related to the short MA are in one group, and long MA parameters in another. This makes it very clear which settings control which line on the chart.
Grouping RSI Parameters
The Relative Strength Index (RSI) has core parameters (length, source) and often includes levels (overbought, oversold) with their own styling options. Grouping these improves the organization of the RSI settings.
//@version=5
indicator("RSI Group Example", shortname="RSIG", overlay=false)
// --- RSI Calculation Settings ---
RSIlength = input.int(defval=14, title="Length", group="RSI Calculation")
RSIsrc = input.src(defval=close, title="Source", group="RSI Calculation")
// --- RSI Levels Settings ---
showLevels = input.bool(defval=true, title="Show Levels", group="RSI Levels")
overboughtLevel = input.float(defval=70.0, title="Overbought Level", group="RSI Levels", show_if=showLevels)
oversoldLevel = input.float(defval=30.0, title="Oversold Level", group="RSI Levels", show_if=showLevels)
// --- Plotting/Styling Settings ---
RSIcolor = input.color(defval=color.purple, title="RSI Line Color", group="RSI Appearance")
levelColor = input.color(defval=color.new(color.gray, 50), title="Level Line Color", group="RSI Appearance", show_if=showLevels)
// --- RSI Logic ---
RSI = ta.rsi(RSIsrc, RSIlength)
// --- Plotting ---
plot(RSI, color=RSIcolor, title="RSI")
// Plot levels conditionally
hline(overboughtLevel, title="Overbought", color=levelColor, show_first=showLevels)
hline(oversoldLevel, title="Oversold", color=levelColor, show_first=showLevels)
This example separates calculation, level, and appearance settings into logical groups, with the level settings and their appearance controls conditionally displayed via show_if.
Combining Groups for a Complete Trading System Configuration
Building upon the previous examples, a slightly more complex system might combine multiple indicators and include additional logic like trend filtering. Groups allow you to manage inputs for each component effectively.
//@version=5
strategy("Combined Group Strategy", shortname="CGS", overlay=true)
// --- EMA Filter Settings ---
useEmaFilter = input.bool(defval=true, title="Enable EMA Filter", group="EMA Filter")
emaFilterPeriod = input.int(defval=200, title="Period", group="EMA Filter", show_if=useEmaFilter)
emaFilterSrc = input.src(defval=close, title="Source", group="EMA Filter", show_if=useEmaFilter)
// --- RSI Entry Settings ---
useRsiEntry = input.bool(defval=true, title="Enable RSI Entry", group="RSI Entry")
rsiEntryPeriod = input.int(defval=14, title="Period", group="RSI Entry", show_if=useRsiEntry)
rsiEntrySrc = input.src(defval=close, title="Source", group="RSI Entry", show_if=useRsiEntry)
rsiBuyLevel = input.float(defval=40.0, title="Buy Level", group="RSI Entry", show_if=useRsiEntry)
rsiSellLevel = input.float(defval=60.0, title="Sell Level", group="RSI Entry", show_if=useRsiEntry)
// --- Strategy Exit Settings ---
usePctExit = input.bool(defval=true, title="Enable Percentage Exit", group="Strategy Exits")
tpPct = input.float(defval=3.0, title="Take Profit (%)", group="Strategy Exits", show_if=usePctExit)
slPct = input.float(defval=1.5, title="Stop Loss (%)", group="Strategy Exits", show_if=usePctExit)
// --- Logic ---
emaFilter = ta.ema(emaFilterSrc, emaFilterPeriod)
rsi = ta.rsi(rsiEntrySrc, rsiEntryPeriod)
longCondition = false
shortCondition = false
// Apply EMA filter if enabled
if useEmaFilter
longCondition := close > emaFilter
shortCondition := close < emaFilter
else
// If no EMA filter, conditions are always potentially met based on RSI
longCondition := true
shortCondition := true
// Apply RSI entry if enabled and filter allows
if useRsiEntry
longCondition := longCondition and ta.crossover(rsi, rsiBuyLevel)
shortCondition := shortCondition and ta.crossunder(rsi, rsiSellLevel)
else
// If no RSI entry, disable entries unless another logic is added
longCondition := false
shortCondition := false
// Entry Orders
if longCondition
strategy.entry("Long", strategy.long)
if shortCondition
strategy.entry("Short", strategy.short)
// Exit Orders (Apply if enabled)
if usePctExit
strategy.exit("TP/SL Long", from_entry="Long", profit=strategy.position_avg_price * (tpPct / 100) / syminfo.mintick, stop=strategy.position_avg_price * (1 - slPct / 100))
strategy.exit("TP/SL Short", from_entry="Short", profit=strategy.position_avg_price * (tpPct / 100) / syminfo.mintick, stop=strategy.position_avg_price * (1 + slPct / 100))
This strategy demonstrates how to use groups to organize parameters for different logic modules (EMA filter, RSI entry) and exits. Each module has its own enable/disable boolean input that controls the visibility of its parameters using show_if.
Best Practices and Considerations
Using groups effectively requires more than just adding the group argument. Following best practices ensures your script remains clean and user-friendly.
Naming Conventions for Groups
Choose clear, descriptive names for your groups. The group name should immediately convey the purpose of the inputs it contains. Avoid generic names like “Settings 1”, “Parameters”, etc. Use names like “EMA Settings”, “Entry Conditions”, “Risk Management”, “Appearance Options”.
Using the hierarchical naming convention (Parent/Child) can further improve clarity for complex structures, but don’t over-complicate it for simple scripts.
Keeping Groups Organized and Readable
Define your inputs sequentially in your code, grouping related inputs together physically in the script source. This reinforces the logical grouping and makes your code easier to read and maintain. Place all inputs for a group one after another, before moving on to the inputs for the next group.
Add comments in your code to denote the start of each input group section. This adds another layer of organization.
// --- Moving Average Inputs ---
maPeriod = input.int(..., group="Moving Average Settings")
maSource = input.src(..., group="Moving Average Settings")
// --- RSI Inputs ---
rsiPeriod = input.int(..., group="RSI Settings")
rsiLevel = input.float(..., group="RSI Settings")
// ... etc.
Avoiding Common Pitfalls with Groups
- Typo in Group Name: Ensure the group name string is identical for all inputs you want in the same group. A simple typo (
"My Group"vs"My Gurp") will create two separate groups. - Over-Grouping: Don’t create a new group for every single input or for very small sets of unrelated inputs. Groups should bundle related parameters. Too many groups can be just as overwhelming as no groups.
- Inconsistent
show_if: If usingshow_ifto hide a group, remember to apply the condition to every input in that group. Missing one will result in that single input remaining visible when the others are hidden. - Order Mismatch: The order in the settings panel follows the code order. If you define inputs for Group A, then Group B, then more for Group A, they will appear as: Group A (first inputs), Group B (all inputs), Group A (later inputs). Define all inputs for a group consecutively for predictable ordering.
By paying attention to these details, you can effectively use groups to create clean, intuitive, and professional-looking input settings for your Pine Script indicators and strategies.
Using groups is a fundamental skill for developing production-ready scripts on TradingView. It significantly enhances the usability and maintainability of your work, making your tools more accessible and enjoyable for yourself and others.