Indicator TutorialPine Script 5

How to Create Moving Average Indicators on TradingView

Build professional SMA and EMA plots, crossover markers, and MA ribbons with Pine Script v5.

Moving averages are the backbone of technical analysis on TradingView. Whether you smooth noise with a Simple Moving Average (SMA) or react faster with an Exponential Moving Average (EMA), Pine Script makes it straightforward to compute, visualize, and extend these tools. This tutorial walks through the math, signal logic, and a complete overlay indicator you can paste into the Pine Editor and customize.

What is a moving average?

A moving average is a continuously updated average price over a fixed number of bars. It filters short-term volatility so you can see the underlying trend.

  • SMA (Simple Moving Average) gives equal weight to every bar in the lookback window. It is smooth and stable but reacts slowly to new information.
  • EMA (Exponential Moving Average) applies more weight to recent prices, so it hugs price more closely and turns faster after impulses.

On TradingView, ta.sma(source, length) and ta.ema(source, length) implement these formulas efficiently on any series (close, hl2, etc.).

Why Traders Use This Indicator

  • Define trend direction and filter range-bound chop.
  • Generate systematic entries and exits with MA crossovers or slope.
  • Stack multiple MAs to build ribbons that show expansion and contraction of trend strength.
  • Combine MAs with volume or volatility tools for context-aware signals.
ParameterDefaultDescription
Length20 / 50 / 200Number of bars in the window. Shorter lengths react faster; longer lengths emphasize major trends.
SourceclosePrice series averaged (close, open, hl2, ohlc4, etc.). Close is standard for trend work.
MA typeEMAChoose SMA for maximum smoothing or EMA when you need responsiveness to recent price.
Ribbon spacing8–12 bars between linesWhen plotting several EMAs, even spacing (e.g. 8, 16, 24…) creates a compact visual of trend alignment.

How moving averages are computed

For length n and source x:

SMA: SMA = (x[0] + x[1] + … + x[n-1]) / n

EMA: uses a smoothing factor α = 2 / (n + 1) and updates recursively so recent bars dominate: EMA = α·x + (1-α)·EMA[1].

In Pine Script v5 you do not need to hand-roll these loops—ta.sma() and ta.ema() match platform-standard definitions and handle na values consistently.

Signal Interpretation

Crossovers: When a fast MA crosses above a slow MA, traders often read it as bullish momentum alignment; a cross below suggests bearish alignment. Use ta.crossover() and ta.crossunder() for one-bar pulse signals.

Slope and distance: Steepening MAs and widening gap between price and a long MA can indicate strong trends; flat MAs overlapping price often precede range conditions.

Ribbons: When multiple EMAs fan out in order (short above longer in uptrends), trend conviction is higher; a tangled ribbon warns of consolidation.

Combining with Other Indicators

Pair MAs with RSI or Stochastic to avoid buying every bullish cross in overbought territory. Add ATR or Keltner channels for volatility-aware stops. Use volume or VWAP on intraday charts to confirm that MA breaks occur with participation.

The Hard Way: Writing Pine Script Manually

Challenges of Manual Coding

Implement a Hull MA or weighted MA and compare lag versus EMA on the same chart.

Add a filter so cross signals only fire when price is also above/below a 200-period SMA.

Build a ribbon of 5+ EMAs with gradient colors based on bullish or bearish stack order.

Expose session or timeframe inputs so the MA length auto-scales between 1D and 1H charts.

Pine Script 5
//@version=5
indicator("MA Suite: SMA, EMA, Crossovers & Ribbon", overlay=true, max_lines_count=500, max_labels_count=500)

// --- Inputs ---
grpCore = "Core MAs"
maSource = input.source(close, "Source", group=grpCore)
smaLen = input.int(50, "SMA length", minval=1, group=grpCore)
emaFastLen = input.int(21, "Fast EMA length", minval=1, group=grpCore)
emaSlowLen = input.int(55, "Slow EMA length", minval=1, group=grpCore)

grpRibbon = "Ribbon"
showRibbon = input.bool(true, "Show EMA ribbon", group=grpRibbon)
ribbonStart = input.int(8, "Ribbon start length", minval=2, group=grpRibbon)
ribbonStep = input.int(8, "Ribbon step", minval=1, group=grpRibbon)
ribbonCount = input.int(5, "Ribbon lines (max 6)", minval=2, maxval=6, group=grpRibbon)

grpSig = "Signals"
showCross = input.bool(true, "Show crossover shapes", group=grpSig)
showBg = input.bool(false, "Highlight trend background", group=grpSig)

// --- Core calculations ---
smaVal = ta.sma(maSource, smaLen)
emaFast = ta.ema(maSource, emaFastLen)
emaSlow = ta.ema(maSource, emaSlowLen)

bullCross = ta.crossover(emaFast, emaSlow)
bearCross = ta.crossunder(emaFast, emaSlow)

// --- Ribbon: explicit plots (Pine requires plot() outside loops) ---
rLen0 = ribbonStart + 0 * ribbonStep
rLen1 = ribbonStart + 1 * ribbonStep
rLen2 = ribbonStart + 2 * ribbonStep
rLen3 = ribbonStart + 3 * ribbonStep
rLen4 = ribbonStart + 4 * ribbonStep
rLen5 = ribbonStart + 5 * ribbonStep
r0 = ta.ema(maSource, rLen0)
r1 = ta.ema(maSource, rLen1)
r2 = ta.ema(maSource, rLen2)
r3 = ta.ema(maSource, rLen3)
r4 = ta.ema(maSource, rLen4)
r5 = ta.ema(maSource, rLen5)

// --- Plots: primary lines ---
plot(smaVal, title="SMA", color=color.new(color.teal, 0), linewidth=2)
plot(emaFast, title="Fast EMA", color=color.new(color.blue, 0), linewidth=2)
plot(emaSlow, title="Slow EMA", color=color.new(color.orange, 0), linewidth=2)

// --- Ribbon plots ---
plot(showRibbon and ribbonCount > 0 ? r0 : na, title="Ribbon 0", color=color.new(color.rgb(90, 200, 230), 45), linewidth=1)
plot(showRibbon and ribbonCount > 1 ? r1 : na, title="Ribbon 1", color=color.new(color.rgb(80, 185, 225), 42), linewidth=1)
plot(showRibbon and ribbonCount > 2 ? r2 : na, title="Ribbon 2", color=color.new(color.rgb(70, 170, 220), 40), linewidth=1)
plot(showRibbon and ribbonCount > 3 ? r3 : na, title="Ribbon 3", color=color.new(color.rgb(60, 155, 215), 38), linewidth=1)
plot(showRibbon and ribbonCount > 4 ? r4 : na, title="Ribbon 4", color=color.new(color.rgb(50, 140, 210), 36), linewidth=1)
plot(showRibbon and ribbonCount > 5 ? r5 : na, title="Ribbon 5", color=color.new(color.rgb(40, 125, 205), 34), linewidth=1)

// --- Crossover markers ---
plotshape(showCross and bullCross, title="Bull cross", style=shape.triangleup, location=location.belowbar, size=size.small, color=color.new(color.green, 0))
plotshape(showCross and bearCross, title="Bear cross", style=shape.triangledown, location=location.abovebar, size=size.small, color=color.new(color.red, 0))

// --- Trend background ---
trendBull = emaFast > emaSlow
trendBear = emaFast < emaSlow
bgcolor(showBg and trendBull ? color.new(color.green, 92) : na)
bgcolor(showBg and trendBear ? color.new(color.red, 92) : na)

// --- Alerts ---
alertcondition(bullCross, title="Bullish MA cross", message="Fast EMA crossed above slow EMA")
alertcondition(bearCross, title="Bearish MA cross", message="Fast EMA crossed below slow EMA")

// --- Summary table ---
var table info = table.new(position.top_right, 1, 4, border_width=1)
if barstate.islast
    table.cell(info, 0, 0, "SMA(" + str.tostring(smaLen) + ")", text_color=color.white, bgcolor=color.new(color.gray, 30))
    table.cell(info, 0, 1, str.tostring(smaVal, format.mintick), text_color=color.white)
    table.cell(info, 0, 2, "Fast - Slow", text_color=color.white, bgcolor=color.new(color.gray, 30))
    table.cell(info, 0, 3, str.tostring(emaFast - emaSlow, format.mintick), text_color=color.white)

Maintenance Note: If you extend the ribbon beyond six lines, add more explicit `plot()` calls rather than looping—TradingView requires fixed plot invocations. Consider moving shared logic into a `library()` if you reuse MA math across indicators.

The Easy Way: Build with Pineify Visual Editor

What if you could create the same indicator without writing a single line of code? Pineify is a visual editor designed for TradingView users who want professional-grade indicators through an intuitive interface.

FeatureManual Pine ScriptPineify Visual Editor
SMA vs EMA setupRewrite smoothing math when switching types.Toggle MA type and lengths in the builder without editing formulas.
Crossover detectionCarefully handle `na` and off-by-one on the first valid bars.Use generated conditions that mirror `ta.crossover` semantics.
Ribbon visualizationDuplicate plot code for each line and manage colors by hand.Clone and parameterize plots from a single template block.
AlertsWire `alertcondition` and message strings manually.Attach alerts to pre-built conditions with consistent naming.
RefactorsEasy to break overlays when extracting functions.Structured modules reduce copy-paste errors across indicators.

Faster iteration when tuning lengths across many symbols or timeframes.

Less boilerplate for plots, colors, and inputs than hand-written Pine.

Consistent patterns for alerts and tables as you scale to more MAs.

Easier onboarding for teammates who do not want to maintain raw Pine.

Step-by-Step Tutorial

1

Create a new indicator project

Open Pineify and start an indicator that overlays on the price chart.

2

Add SMA and EMA series

Pick your source (typically close) and set lengths for SMA, fast EMA, and slow EMA.

3

Define crossover logic

Use bullish cross when fast EMA crosses above slow EMA, and bearish for the inverse.

4

Layer the ribbon

Add multiple EMA plots with stepped lengths and subtle transparency for a ribbon effect.

5

Style and validate

Match line weights to your chart theme and confirm values against TradingView built-ins.

6

Publish alerts

Connect alert conditions to crosses or trend state for notifications.

7

Export Pine

Generate v5 code, paste into TradingView, and save the indicator to your library.

Trading Strategies & Pro Tips

Dual EMA trend follow

Go long on bullish cross when price is above a longer SMA filter; exit or flip on bearish cross.

Pro Tip: Add a minimum cross angle or ATR-based stop to reduce whipsaws in ranges.

Ribbon expansion breakout

Trade in the direction the ribbon fans out after a tight consolidation.

Mean reversion to the MA

In range regimes, fade extended moves back toward a 20–50 SMA with tight risk.

Triple-screen style filter

Use a higher-timeframe MA slope for bias and only take crosses on the trading timeframe in that direction.

Common Mistakes to Avoid

  • Using the same length on vastly different timeframes without rescaling expectations.
  • Ignoring that SMA and EMA levels will differ even with identical nominal length.
  • Over-plotting too many MAs without transparency, which obscures price action.
  • Treating every crossover as equal without volume or structure confirmation.
  • Forgetting that early bars have fewer samples, so values stabilize after `length` bars.

Frequently Asked Questions

Build your moving average indicator faster with Pineify

  • Generate Pine Script v5 from visual rules instead of typing every plot.
  • Experiment with SMA, EMA, and stacked ribbons in one workflow.
  • Ship alerts and clean styling without hand-maintaining long scripts.

Disclaimer: Trading involves significant risk. The indicators, code, and strategies provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.