Create your own
Lesson illustration

Building a Pine Script Indicator with Inputs, Calculations, and Plots

Welcome back. In the previous lesson, you established the key mental model for Pine: a script evaluates bar by bar, price-derived values are usually series, and an open candle can still change. That matters today because we will create an indicator, not a trading strategy or a live signal generator: its calculations will be transparent, user-configurable, and plotted on the chart.

By the end of this lesson, you will have built a configurable MACD indicator in TradingView. It will use typed user inputs, perform a built-in technical calculation on every bar, and render three outputs in its own pane. This is the reusable pattern behind many custom chart tools.


1. An indicator has three layers

A maintainable Pine indicator usually separates three concerns:

  1. Configuration: values the user can change in Settings.
  2. Calculation: time-series logic evaluated on every bar.
  3. Rendering: plots or other visuals sent to the chart.

This separation is worth keeping even in very small scripts. It makes a script easier to inspect, change, and later convert into a rule-based tool without mixing interface decisions with calculation logic.

For this lesson, the calculation is MACD. MACD compares a fast exponential moving average with a slower one:

It also computes a signal line, which is an EMA of the MACD line:

The histogram is their difference:

These are descriptive momentum measurements, not instructions to buy or sell. In particular, a MACD crossover can occur in a trend, a range, or a volatile reversal; its chart context remains essential.

Read TradingView’s concise official walkthrough before building the expanded version below.

First indicator - Pine Script® primer

Read TradingView’s “First indicator” primer for the canonical MACD example. It introduces the Pine Editor, an indicator() declaration, EMA calculations, plot(), configurable inputs, and the three-value result returned by ta.macd().

In the sections “The Pine Editor”, “First version”, and “Second version”, read the walkthrough from the opening Pine Editor description through the final explanation of the second MACD version. Focus especially on why the variables on the left side of [macdLine, signalLine, histLine] are enclosed in square brackets, and on what TradingView does when an input changes.

The official example begins with fixed values such as 12 and 26, then replaces them with inputs. That is an important upgrade: a user should not need to edit source code merely to test a conventional MACD setting against a different one.

TradingView displays a Pine Editor MACD script below the chart and its indicator Settings dialog above it. The dialog exposes configurable Fast length and Slow length values, while the calculated MACD and signal lines appear in a separate pane.

2. The Pine building blocks

A Pine script begins with a version declaration and exactly one top-level script declaration. For an indicator, that declaration is indicator():

//@version=6
indicator("My indicator", overlay = false)

//@version=6 tells TradingView to compile the script using Pine Script version 6.

indicator() gives the script its name and determines how it behaves as a chart study. The most relevant display setting at this stage is overlay:

SettingResultTypical use
overlay = trueOutput appears over the main price chartMoving averages, Supertrend, support/resistance tools
overlay = falseOutput appears in a separate paneMACD, RSI, volume-style oscillators

MACD is normally best viewed in its own pane because it oscillates around zero and is measured in price units rather than being a price level itself.

Inputs are parameters, not bar data

An input creates a widget in the indicator’s Settings / Inputs tab:

fastLengthInput = input.int(12, "Fast EMA length", minval = 1)

This expression returns an input int. The default is 12; the label visible in the UI is "Fast EMA length"; and minval = 1 prevents an invalid zero or negative EMA length.

Recall the qualifier distinction from the previous lesson:

  • An input is selected by the user and remains fixed during one script run.
  • A source such as close varies on every bar and is a series float.
  • A calculation such as ta.ema(close, 12) is also a series float.

Changing an input does not alter only the current value. TradingView re-executes the script over all loaded chart bars using the new parameters. That is why the entire historical MACD plot changes when you edit a length.

A source input is particularly useful for indicator design:

sourceInput = input.source(close, "Source")

The default source is close, but the user can select another price-derived series, such as hlc3. Unlike an ordinary integer input, a source input provides a series because its selected value still differs from bar to bar.

ta.macd() returns three series

Many Pine functions return one value. ta.macd() returns three:

[macdLine, signalLine, histogram] = ta.macd(close, 12, 26, 9)

The square brackets are tuple destructuring. In order, the function returns:

  1. The MACD line.
  2. The signal line.
  3. The histogram.

Each is a series float, with one calculated value for each bar. The fast, slow, and signal lengths can all be input int values.

Finally, plot() turns a series into a visual output:

plot(macdLine, title = "MACD", color = color.blue)

It does not calculate MACD. It renders the macdLine values that you have already calculated.

A useful discipline is therefore: calculate first, plot second. Do not call historical functions only in occasional branches just because a plot is optional. Calculate on every bar, then hide a visual output with na when necessary.

For a visual walkthrough of the same pattern using an SMA, watch this segment. The SMA is simpler than MACD, but its structure is identical: input, calculation, then plot.

Pine Script Tutorial: Complete Beginner's Guide to TradingView

In “Pine Script Tutorial: Complete Beginner's Guide to TradingView” by The Trading Parrot, watch the moving-average and input sections. They provide a practical view of overlay, built-in technical-analysis functions, plot(), and the Settings panel.

Watch the SMA build to see a series calculated from price and plotted on the chart, paying attention to why an overlay indicator belongs on the main price panel. Then watch the input controls for the workflow of changing a parameter from indicator settings. Stop there; crossover signals are deliberately reserved for the next lesson.


3. Build a configurable MACD indicator

Open a chart in TradingView. A daily chart of a liquid US or UK share is suitable for a first test; a major cryptocurrency such as BTCUSD also works. The point is to validate the script’s mechanics, not to find a trade.

At the bottom of the chart, open Pine Editor, select New, and choose an indicator template or blank indicator. Replace its contents with this script:

//@version=6
indicator("Configurable MACD", shorttitle = "MACD Lab", overlay = false)

// Inputs
sourceInput = input.source(close, "Source", group = "Calculation")
fastLengthInput = input.int(12, "Fast EMA length", minval = 1, group = "Calculation")
slowLengthInput = input.int(26, "Slow EMA length", minval = 1, group = "Calculation")
signalLengthInput = input.int(9, "Signal length", minval = 1, group = "Calculation")

showHistogramInput = input.bool(true, "Show histogram", group = "Display")
macdColorInput = input.color(color.blue, "MACD line", group = "Display")
signalColorInput = input.color(color.orange, "Signal line", group = "Display")
positiveColorInput = input.color(color.teal, "Positive histogram", group = "Display")
negativeColorInput = input.color(color.red, "Negative histogram", group = "Display")

// Calculation
[macdLine, signalLine, histogram] = ta.macd(
     sourceInput,
     fastLengthInput,
     slowLengthInput,
     signalLengthInput
)

histogramColor = histogram >= 0 ? positiveColorInput : negativeColorInput

// Output
hline(0, "Zero line", color = color.gray)
plot(macdLine, title = "MACD", color = macdColorInput, linewidth = 2)
plot(signalLine, title = "Signal", color = signalColorInput, linewidth = 2)
plot(
     showHistogramInput ? histogram : na,
     title = "Histogram",
     style = plot.style_columns,
     color = histogramColor
)

Save the script, then select Add to chart. After later edits, use Update on chart.

Read the script from top to bottom

The first block creates the interface. The shared group = "Calculation" string places related fields under one heading in the Inputs tab; group = "Display" does the same for visual settings. Grouping does not affect calculation. It only makes the indicator easier to operate.

The minval = 1 constraints are basic validation. They prevent an EMA length of zero, but they cannot express every meaningful relationship between inputs. For example, slowLengthInput should normally be greater than fastLengthInput; Pine cannot use the changing fast input as a minval for the slow input, because minval itself requires a fixed value. At this stage, follow that conventional relationship manually.

The calculation block contains the only technical-analysis call:

[macdLine, signalLine, histogram] = ta.macd(...)

It runs on every bar, using the source series selected by the user. On early bars, there may not be enough history for the requested lookback lengths, so the output can be na. That is expected; the indicator begins plotting when enough data are available.

The final block contains only visual work:

  • hline() draws a constant horizontal zero reference.
  • The first two plot() calls draw the MACD and signal lines.
  • The third plot() draws columns for the histogram.
  • The ternary expression returns histogram when the checkbox is enabled and na when it is disabled. na means “not available,” so Pine leaves that bar unplotted.

Notice that hiding the histogram does not skip the ta.macd() calculation. The calculation stays consistent across all bars; only its rendering changes.


4. Validate it as a chart tool, not a signal machine

Use the indicator’s Settings dialog to make a few controlled changes:

  1. Keep the usual , , and defaults and verify that MACD, Signal, Histogram, and Zero line appear in the indicator pane.
  2. Change the fast EMA length from to . The MACD line should become more responsive and generally more variable because the fast average follows recent prices more closely.
  3. Restore it to , then increase the signal length from to . The signal line should become smoother and react more slowly to changes in MACD.
  4. Disable Show histogram. The two lines and zero line remain, but the columns disappear.
  5. Change the source from close to another available price source. This should change the indicator’s full historical calculation, not merely its final bar.

Use the Data Window or the values shown beside the indicator’s name to verify that the plotted series correspond to your selected parameters.

Two interpretation cautions are useful even at this early scripting stage:

  • Fast settings trade smoothness for sensitivity. A smaller fast length responds sooner, but it also reacts more to noisy fluctuations.
  • MACD magnitudes are not directly comparable across instruments. A value of on one share, a cryptocurrency, and a commodity product does not have the same economic meaning because their price scales and volatility differ. MACD is best read in relation to its own history and market structure.

Most importantly, this script deliberately has no entry labels, alerts, or orders. A plotted indicator lets you inspect a condition. A trading rule must additionally define when the condition is confirmed, how it is filtered by context, where it is invalidated, and how risk is controlled.

Common implementation mistakes

SymptomLikely causeCorrection
Script appears over candles instead of below themoverlay is trueUse overlay = false for MACD
Pine reports an assignment error around ta.macd()The three returned values were not unpackedUse [macdLine, signalLine, histogram] = ta.macd(...)
Changing a length seems to “rewrite” the pastThe script reran across all barsThis is normal input behavior, not repainting
Histogram does not disappear when uncheckedThe plot does not use the display togglePlot showHistogramInput ? histogram : na
A calculation behaves inconsistently after adding conditionsA time-series function was placed inside selective logicCalculate technical series every bar, then conditionally plot or use them

Key takeaways

A Pine indicator is most manageable when it has a clear configuration layer, calculation layer, and output layer. input.*() functions create Settings controls; price data and technical calculations are series evaluated bar by bar; and plot() renders those series.

Your MACD script uses input.source, input.int, input.bool, and input.color controls, unpacks the three series returned by ta.macd(), and plots lines and a conditional histogram in a separate pane. Changing an input re-executes the indicator over the available chart history, which is expected.

Next, you will build on this indicator structure by encoding a non-repainting rule-based signal with visual markers and alert conditions, while ensuring that any signal intended for bar-close decisions is confirmed rather than provisional.

Can't find a good explanation? Sign up and we'll make it for you

Sign up