The script
Six EMAs with lengths you can change, each teal while it rises and red while it falls, and a fill that turns green or red when all six are in order. Paste it into a new indicator in the Pine Editor, then save and add it to the chart.
//@version=6
indicator("EMA ribbon colored by slope", overlay = true)
src = input.source(close, "Source")
slopeLen = input.int(3, "Slope lookback (bars)", minval = 1)
upCol = input.color(color.teal, "Rising / falling colors", inline = "c")
downCol = input.color(color.red, "", inline = "c")
len1 = input.int(10, "Lengths 1 to 3", minval = 1, inline = "a")
len2 = input.int(20, "", minval = 1, inline = "a")
len3 = input.int(30, "", minval = 1, inline = "a")
len4 = input.int(40, "Lengths 4 to 6", minval = 1, inline = "b")
len5 = input.int(50, "", minval = 1, inline = "b")
len6 = input.int(60, "", minval = 1, inline = "b")
// Rising, falling or flat compared with lookback bars ago
slopeColor(float ma, int lookback, color up, color down) =>
ma > ma[lookback] ? up : ma < ma[lookback] ? down : color.gray
ma1 = ta.ema(src, len1)
ma2 = ta.ema(src, len2)
ma3 = ta.ema(src, len3)
ma4 = ta.ema(src, len4)
ma5 = ta.ema(src, len5)
ma6 = ta.ema(src, len6)
p1 = plot(ma1, "EMA 1", slopeColor(ma1, slopeLen, upCol, downCol), 2)
plot(ma2, "EMA 2", slopeColor(ma2, slopeLen, upCol, downCol))
plot(ma3, "EMA 3", slopeColor(ma3, slopeLen, upCol, downCol))
plot(ma4, "EMA 4", slopeColor(ma4, slopeLen, upCol, downCol))
plot(ma5, "EMA 5", slopeColor(ma5, slopeLen, upCol, downCol))
p6 = plot(ma6, "EMA 6", slopeColor(ma6, slopeLen, upCol, downCol), 2)
// Stacked: every average above the next slower one (or below, for a downtrend)
bool stackedUp = ma1 > ma2 and ma2 > ma3 and ma3 > ma4 and ma4 > ma5 and ma5 > ma6
bool stackedDown = ma1 < ma2 and ma2 < ma3 and ma3 < ma4 and ma4 < ma5 and ma5 < ma6
fill(p1, p6, stackedUp ? color.new(upCol, 85) : stackedDown ? color.new(downCol, 85) : color.new(color.gray, 94), "Ribbon")
alertcondition(stackedUp and not stackedUp[1], "Ribbon stacked up", "EMA ribbon stacked bullish on {{ticker}}")
alertcondition(stackedDown and not stackedDown[1], "Ribbon stacked down", "EMA ribbon stacked bearish on {{ticker}}")How it works
- Six separate
ta.ema()lines. Each average has its own call, run on every bar, with a length that comes from an input. That is whatta.ema()needs (see the pitfalls). slopeColor()compares the average with its valuelookbackbars ago. A lookback of 1 flips color on every small wiggle; 3 to 5 bars gives steadier colors.plot(ma1, "EMA 1", slopeColor(...), 2). The third argument ofplot()is a series color, so each line changes color bar by bar from a single call. The fourth is the line width.- Inputs on shared rows. Lengths 1–3 and 4–6 sit on two rows (
inline = "a"and"b"), with a title only on the first input of each row so they line up in the settings. - The fill uses the IDs of the fastest and slowest plots.
stackedUp[1]in the alerts makes them fire on the first stacked bar only, not on every bar of the trend.
Pitfalls
- Building the ribbon in a loop.
for i = 1 to 6withta.ema(src, i * 10)doesn't work: the length changes inside the loop, so it isn't the fixed lengthta.ema()needs (series int used but simple int expected), and ata.*call in a loop doesn't run once per bar (should be called on each calculation). One line per average is the way. - plot() in an if block. Changing color with
if risingthenplot(ma, color = color.teal)gives Cannot use plot in local scope. Compute the color and pass it to a singleplot(). - Colors on the live bar.
ma > ma[3]uses the live close, so a line can change color several times before the bar closes. Alerts fromalertcondition()should use "Once per bar close" in the alert dialog. - Plot count. Each
plot()counts toward the script's limit of 64 plots. Six lines and a fill are far from it, but a ribbon of dozens of averages with one plot per color would not be.
Variations
Color by slope strength, not just direction. Divide the slope by ATR so the scale doesn't depend on the price, then let color.from_gradient() fade from red to teal. Tune the -0.5 to 0.5 range to your market and timeframe.
//@version=6
indicator("EMA colored by slope strength", overlay = true)
len = input.int(50, "EMA length", minval = 1)
ma = ta.ema(close, len)
atr = ta.atr(14)
// Slope over 5 bars, in ATRs
float slope = (ma - ma[5]) / atr
color c = color.from_gradient(slope, -0.5, 0.5, color.red, color.teal)
plot(ma, "EMA", c, 3)
Questions
How do I change a plot's color based on a condition in Pine Script?
Pass a color expression as the color argument: plot(ma, color = ma > ma[1] ? color.teal : color.red). plot() accepts a color that changes on every bar, so one call is enough.
Can I make a moving average ribbon with a for loop?
Not with ta.ema(): it needs a fixed length and must run on every bar, and a loop gives it a different length on each pass. Write one ta.ema() line and one plot() per average.
What does a stacked ribbon mean?
All averages are in order of length: fastest on top in an uptrend, slowest on top in a downtrend. It describes the trend so far; it lags, and it doesn't say how long the trend will last.
Related pages
Every script on this page was checked with Edgoro Pine Check (Pine Script v6 rules). Pine Check is a static checker, not TradingView's compiler: TradingView compiles the script when you save it in the Pine Editor. The scripts are examples to learn from, not trading systems. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.