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:
- Configuration: values the user can change in Settings.
- Calculation: time-series logic evaluated on every bar.
- 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.

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:
| Setting | Result | Typical use |
|---|---|---|
overlay = true | Output appears over the main price chart | Moving averages, Supertrend, support/resistance tools |
overlay = false | Output appears in a separate pane | MACD, 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
inputis selected by the user and remains fixed during one script run. - A source such as
closevaries on every bar and is aseries float. - A calculation such as
ta.ema(close, 12)is also aseries 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:
- The MACD line.
- The signal line.
- 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
histogramwhen the checkbox is enabled andnawhen it is disabled.nameans “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:
- Keep the usual , , and defaults and verify that MACD, Signal, Histogram, and Zero line appear in the indicator pane.
- 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.
- Restore it to , then increase the signal length from to . The signal line should become smoother and react more slowly to changes in MACD.
- Disable Show histogram. The two lines and zero line remain, but the columns disappear.
- Change the source from
closeto 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
| Symptom | Likely cause | Correction |
|---|---|---|
| Script appears over candles instead of below them | overlay is true | Use overlay = false for MACD |
Pine reports an assignment error around ta.macd() | The three returned values were not unpacked | Use [macdLine, signalLine, histogram] = ta.macd(...) |
| Changing a length seems to “rewrite” the past | The script reran across all bars | This is normal input behavior, not repainting |
| Histogram does not disappear when unchecked | The plot does not use the display toggle | Plot showHistogramInput ? histogram : na |
| A calculation behaves inconsistently after adding conditions | A time-series function was placed inside selective logic | Calculate 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