You open a script that has used an EMA for months, and you want to know what it would look like with a WMA instead. Or you inherited a strategy where the author wrote ta.rma and you cannot tell why. Or you simply pasted ta.sma(close, 20) into five places and now every experiment means five edits. Moving averages look like the simplest part of Pine Script, yet the choice between them quietly changes how fast a line reacts, how smooth it looks, and how the indicators built on top of it behave.
This guide covers the four moving averages you will meet most often in Pine Script v6: ta.sma, ta.ema, ta.rma, and ta.wma. You will see what each one computes, how the weights differ, how much each one lags, and which built-in indicators use which type. Then you will build a single script where the average type is an input, so swapping one for another is a dropdown change instead of a code change.
What a moving average is doing to your data
Every moving average in this family is a weighted average of recent values of a series, usually close. The only real difference between SMA, EMA, RMA, and WMA is how the weights are assigned. Equal weights give you the simple average. Weights that fall off geometrically give you the exponential family. Weights that fall off in a straight line give you the weighted average.
Two consequences follow from that, and they explain most of what you see on a chart. First, any average of past prices is behind the current price. That delay is a property of averaging, not a flaw in the function. Second, the shape of the weights decides how the delay feels: a line that gives the newest bar a large weight turns sooner, and a line that spreads the weight evenly stays calmer but turns later.
All four functions share the same call shape in v6: ta.sma(source, length), ta.ema(source, length), ta.rma(source, length), and ta.wma(source, length). That shared signature is what makes a switchable average easy to write. You can confirm the signatures in TradingView's Pine Script v6 reference.
SMA: equal weights over a fixed window
ta.sma(close, 20) adds up the last 20 closes and divides by 20. Each of those bars carries a weight of exactly 1/20, and the 21st bar back carries nothing. When a bar leaves the window, the line drops by that bar's contribution all at once, which is why an SMA can step sharply when a large old candle falls out of range.
That behavior is useful to know. An SMA is the most predictable average: you can compute it by hand, and a free moving average calculator will reproduce it. It is also the one that treats a bar from 19 bars ago exactly like the bar that just closed. If you want a line that describes the typical price over a fixed window, that is a feature. If you want a line that reacts to what just happened, it is a drawback.
EMA: geometric weights with a long tail
ta.ema(close, 20) uses a smoothing factor called alpha, computed as 2 / (length + 1). For length 20 that is about 0.0952. Each new value is alpha * source + (1 - alpha) * previous EMA. The newest bar gets a weight of alpha, the one before it gets alpha times (1 - alpha), and so on forever. No bar ever drops out completely, its weight just shrinks toward zero.
Two practical points come out of this. The EMA reacts to a new bar more strongly than the SMA does, because the newest bar carries a weight near 9.5 percent instead of 5 percent. And because the weights never reach zero, an EMA carries a faint memory of everything that came before. This is also why an EMA needs warm-up bars: the first values depend on how the calculation was seeded, and they only settle once enough history has passed through the recursion. If your chart is loaded with only a short stretch of bars, compare early EMA values with caution.
You can write the recursion yourself to see that nothing magic is happening:
//@version=6
indicator("EMA by hand", overlay = true)
length = input.int(20, "Length", minval = 1)
alpha = 2.0 / (length + 1)
var float manual = na
manual := na(manual) ? close : alpha * close + (1 - alpha) * manual
plot(manual, "Manual EMA", color.orange)
plot(ta.ema(close, length), "ta.ema", color.blue)The two lines start from different seeds, so they differ on the first bars. After a few multiples of the length they sit on top of each other. That convergence is the quickest way to convince yourself that the EMA is just a recursive weighted average and not a different kind of object.
RMA: the slow EMA that indicators quietly rely on
ta.rma is an exponential average with alpha equal to 1 / length. For length 20, alpha is 0.05 instead of 0.0952. The weights still decay geometrically, but more slowly, so the line is smoother and reacts later. Some people know it as Wilder's smoothing, after the author of RSI and ATR.
You rarely need to choose RMA for a plain price overlay, but you meet it constantly inside other indicators. ta.rsi smooths its gains and losses with an RMA, and ta.atr smooths true range with one too. That detail matters in two situations. If you rebuild RSI or ATR by hand and use ta.ema where TradingView uses ta.rma, your numbers will not match the built-in function. And if you want an EMA that behaves like an RMA of length n, you can use the identity that alpha 1/n equals alpha 2/(m+1) when m = 2n - 1. In other words, an RMA of length 14 behaves like an EMA of length 27. For more on how ATR itself is built, see the ATR in Pine Script guide, and for the oscillator that uses the same smoothing, see the RSI strategy guide.
WMA: linear weights that end at zero
ta.wma(close, 20) weights the newest bar by 20, the one before it by 19, down to 1 for the oldest bar in the window, and then divides by the sum of those weights. Unlike an EMA, a WMA has a hard window like an SMA. Unlike an SMA, the weights inside that window are not equal. The result is a line that reacts faster than an SMA of the same length, while still dropping old data completely after exactly length bars.
That hard cutoff means a WMA does not carry the infinite tail of an EMA, and it does not need the same kind of warm-up. It does inherit the SMA's habit of stepping when a bar leaves the window, though the step is much smaller because the oldest bar had the smallest weight.
How much each one lags, in numbers
A useful way to compare the four is the average age of the data inside each one, measured in bars. A value that is 0 bars old is the current bar. The textbook results for a length n are:
| Type | Weights | Average age of data (bars) | Length 20 |
|---|---|---|---|
| SMA | Equal over n bars | (n - 1) / 2 | 9.5 |
| EMA | Geometric, alpha = 2 / (n + 1) | (n - 1) / 2 | 9.5 |
| WMA | Linear, newest heaviest | (n - 1) / 3 | about 6.3 |
| RMA | Geometric, alpha = 1 / n | n - 1 | 19 |
Two things in that table surprise people. The first is that an SMA and an EMA of the same length have the same average age. The EMA is not "less laggy" in that sense. What differs is the shape: the EMA puts more weight on the newest bars and also keeps a long tail of old ones, so it responds quickly to a jump and then forgets it slowly. The second is that RMA lags about twice as much as an SMA of the same length, which is why an RSI of length 14 feels smoother than a plain 14-bar ratio.
These are idealized figures for a smooth series, not a promise about how a line will look on a particular chart. Treat them as a way to predict the ordering of the lines, which is WMA first, then SMA and EMA together, then RMA last. You can check that ordering yourself by plotting all four at once:
//@version=6
indicator("Four averages, one length", overlay = true)
length = input.int(20, "Length", minval = 1)
src = input.source(close, "Source")
plot(ta.sma(src, length), "SMA", color.new(color.gray, 0), 2)
plot(ta.ema(src, length), "EMA", color.new(color.blue, 0), 2)
plot(ta.rma(src, length), "RMA", color.new(color.purple, 0), 2)
plot(ta.wma(src, length), "WMA", color.new(color.orange, 0), 2)Load this on a trending stretch and then on a choppy one. In the trend, the WMA hugs price while the RMA trails furthest. In the chop, the same ordering shows up as how often each line is crossed by price. That behavior is the real cost of smoothness: a calmer line will change direction later.
One input, four averages
The tidy way to experiment is to make the type an input. The trick is where you call the ta.* functions. Calling them inside the branches of a conditional means they run only on some bars, and their internal history can then go out of step. Pine's compiler warns about exactly this. The reliable pattern is to compute all four on every bar, then pick one:
//@version=6
indicator("Selectable Moving Average", overlay = true)
maType = input.string("EMA", "MA type", options = ["SMA", "EMA", "RMA", "WMA"])
length = input.int(20, "Length", minval = 1)
src = input.source(close, "Source")
pickMa(float s, int len, string kind) =>
float smaVal = ta.sma(s, len)
float emaVal = ta.ema(s, len)
float rmaVal = ta.rma(s, len)
float wmaVal = ta.wma(s, len)
switch kind
"SMA" => smaVal
"EMA" => emaVal
"RMA" => rmaVal
"WMA" => wmaVal
=> na
ma = pickMa(src, length, maType)
plot(ma, "Moving average", color.blue, 2)Every call to ta.sma, ta.ema, ta.rma, and ta.wma runs on every bar, so each function sees a complete history, and the switch only chooses which finished value to hand back. The final => na branch is the default case that the switch expression needs when it returns a value. If you want to understand why this ordering matters, the execution model guide explains how Pine evaluates a script once per bar and why skipped calls cause trouble.
Wrapping the choice in a function has a second benefit: the type lives in one place. Every part of your script that needs an average calls pickMa, so a later change to the list of types, or to the default, is a one-line edit.
Using the switchable average in a crossover
A crossover of two averages is the most common place this pays off. With a function in place, both lines share one type input, and you can compare how the same pair behaves under each type without touching the logic:
//@version=6
indicator("MA Cross With Selectable Type", overlay = true)
maType = input.string("EMA", "MA type", options = ["SMA", "EMA", "RMA", "WMA"])
fastLen = input.int(10, "Fast length", minval = 1)
slowLen = input.int(30, "Slow length", minval = 2)
pickMa(float s, int len, string kind) =>
float smaVal = ta.sma(s, len)
float emaVal = ta.ema(s, len)
float rmaVal = ta.rma(s, len)
float wmaVal = ta.wma(s, len)
switch kind
"SMA" => smaVal
"EMA" => emaVal
"RMA" => rmaVal
"WMA" => wmaVal
=> na
fast = pickMa(close, fastLen, maType)
slow = pickMa(close, slowLen, maType)
crossUp = ta.crossover(fast, slow)
crossDown = ta.crossunder(fast, slow)
plot(fast, "Fast", color.green, 2)
plot(slow, "Slow", color.red, 2)
plotshape(crossUp, "Cross up", shape.triangleup, location.belowbar, color.green, size = size.small)
plotshape(crossDown, "Cross down", shape.triangledown, location.abovebar, color.red, size = size.small)
alertcondition(crossUp, "Fast crossed above slow", "Fast MA crossed above slow MA")
alertcondition(crossDown, "Fast crossed below slow", "Fast MA crossed below slow MA")Switch the dropdown from WMA to RMA and watch how the markers move. Faster types trigger earlier and more often, and slower types trigger later and less often. That trade between promptness and noise is the whole decision, and no type removes it. For a deeper treatment of crossover logic and its pitfalls, read the moving average crossover guide.
Choosing a type without fooling yourself
Because the four lines differ in how fast they react, it is easy to pick whichever one looks best on the stretch of chart you are staring at. That is a hindsight trap. A type that happened to fit one market phase tells you little about the next. A few habits keep the comparison honest.
Decide what you need the line to do before you look at it. If it is a baseline for "above or below the typical price", an SMA is the clearest. If it must respond quickly to a jump, an EMA or WMA fits. If it feeds an oscillator and you want to match a known formula, use the type that formula specifies, which is often RMA. Then compare lengths and types across several symbols and timeframes, not one screen. And keep the length and the type separate in your head: a 10-bar RMA and a 10-bar WMA are not interchangeable, and changing the type without changing the length changes the character of the line a lot.
One more detail catches people who work on higher timeframes. All four functions return na until they have enough bars, and an EMA or RMA also needs settling time after that. If an average looks wrong near the start of a chart, that is usually warm-up, not a bug. The guide to na values in Pine Script covers how na spreads through calculations, which helps when an average feeds a condition that then silently stops firing.
Swapping the type in a script you already have
Say you have a 300-line indicator that calls ta.ema(close, 21) in six places, and you want to try an RMA. By hand, that is six edits and a chance to miss one. The pattern above reduces it to one change, but you first have to refactor the six calls into a single function and a single input.
This is where an editor that changes only what you ask for helps. In PineScripter, you can ask for that refactor in a follow-up message such as "make the average type an input with SMA, EMA, RMA, and WMA options and use it everywhere the EMA is used". The follow-up edits only the affected lines instead of regenerating the file, and the Code Changes view shows each changed line so you can check that nothing else moved. The built-in linting engine checks the result for compile errors the same way TradingView does, and the AI fixes any it finds, so you are not carrying error messages back and forth. You can read more about this on the AI editor page.
Putting it together
The four averages are one idea with four weight shapes. SMA weighs a window equally. EMA weighs geometrically with alpha 2 / (n + 1) and never fully forgets. RMA weighs geometrically with alpha 1 / n, which makes it the smoothest and slowest, and is the average inside RSI and ATR. WMA weighs linearly inside a hard window, which makes it the quickest of the four for a given length. Compute all four every bar, choose with a switch, and keep the choice in one input, and you can test each one without rewriting your script.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.
Frequently asked questions
What is the difference between ta.ema and ta.rma in Pine Script?
Both are exponential averages. ta.ema uses alpha = 2 / (length + 1), while ta.rma uses alpha = 1 / length, so an RMA is smoother and slower. An RMA of length n behaves like an EMA of length 2n - 1.
Which moving average lags the least in Pine Script?
For the same length, ta.wma has the lowest average lag of the four, about (n - 1) / 3 bars, followed by ta.sma and ta.ema at (n - 1) / 2, and ta.rma at n - 1.
Why does my EMA look different from the one on another chart?
An EMA depends on its seed and on how many bars of history are loaded, so early values can differ. They converge after several multiples of the length.
Why compute all four averages before using switch?
ta functions keep internal history and should run on every bar. Calling them only inside a branch can leave that history incomplete and triggers a compiler warning, so compute all four and let the switch choose.
Which average do RSI and ATR use in Pine Script?
Both ta.rsi and ta.atr smooth with an RMA, so rebuilding them with ta.ema will not match the built-in values.
Describe your indicator in plain English and get Pine Script that compiles →
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.