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.
| Parameter | Default | Description |
|---|---|---|
| Length | 20 / 50 / 200 | Number of bars in the window. Shorter lengths react faster; longer lengths emphasize major trends. |
| Source | close | Price series averaged (close, open, hl2, ohlc4, etc.). Close is standard for trend work. |
| MA type | EMA | Choose SMA for maximum smoothing or EMA when you need responsiveness to recent price. |
| Ribbon spacing | 8–12 bars between lines | When 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.
//@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.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| SMA vs EMA setup | Rewrite smoothing math when switching types. | Toggle MA type and lengths in the builder without editing formulas. |
| Crossover detection | Carefully handle `na` and off-by-one on the first valid bars. | Use generated conditions that mirror `ta.crossover` semantics. |
| Ribbon visualization | Duplicate plot code for each line and manage colors by hand. | Clone and parameterize plots from a single template block. |
| Alerts | Wire `alertcondition` and message strings manually. | Attach alerts to pre-built conditions with consistent naming. |
| Refactors | Easy 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
Create a new indicator project
Open Pineify and start an indicator that overlays on the price chart.
Add SMA and EMA series
Pick your source (typically close) and set lengths for SMA, fast EMA, and slow EMA.
Define crossover logic
Use bullish cross when fast EMA crosses above slow EMA, and bearish for the inverse.
Layer the ribbon
Add multiple EMA plots with stepped lengths and subtle transparency for a ribbon effect.
Style and validate
Match line weights to your chart theme and confirm values against TradingView built-ins.
Publish alerts
Connect alert conditions to crosses or trend state for notifications.
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.