What Is an SMA Moving Average? Pine Script v6 Guide to Simple Moving Averages

When analyzing price charts, raw candlestick data can appear noisy and difficult to interpret — the Simple Moving Average (SMA) is one of the most fundamental mathematical tools used to smooth that noise and reveal the underlying directional trend of a market.

1. What Is a Simple Moving Average (SMA)?

A Simple Moving Average is a statistical calculation that computes the arithmetic mean of a price series over a fixed number of periods. At each bar, the SMA sums the most recent N closing prices and divides by N, producing a single smoothed value that "rolls" forward with each new bar.

Mathematical Definition

For a window of length $N$, the SMA at time $t$ is defined as:

$$\text{SMA}_t = \frac{1}{N} \sum_{i=0}^{N-1} P_{t-i}$$

Where $P_{t-i}$ is the price at bar $t - i$. Every observation within the window receives an equal weight of $\frac{1}{N}$.

Numerical Example

Consider 5 consecutive closing prices: 10, 12, 11, 13, 14. The 5-period SMA is:

$$\text{SMA}_5 = \frac{10 + 12 + 11 + 13 + 14}{5} = \frac{60}{5} = 12.0$$

As the next bar arrives with price 15, the oldest value (10) is dropped and the window shifts:

$$\text{SMA}_5 = \frac{12 + 11 + 13 + 14 + 15}{5} = \frac{65}{5} = 13.0$$

2. Why Does SMA Smooth Price Data?

The smoothing effect of the SMA comes directly from averaging. A single anomalous price spike — caused by a news event or low-liquidity moment — contributes only $\frac{1}{N}$ of the total weight. As $N$ increases, the influence of any single bar diminishes further, producing a flatter, slower-moving line.

SMA Period (N) Responsiveness Smoothness Lag Common Use
5 Very High Low Minimal Short-term noise filter
20 Moderate Moderate Moderate Monthly trend reference
50 Low High Significant Medium-term trend
200 Very Low Very High Large Long-term trend baseline

3. SMA Warm-Up Period

Because the SMA requires a full window of N bars before it can produce a valid result, the function ta.sma() returns na for the first N − 1 bars. The first non-na value appears at bar_index == N - 1.

For example, a 20-period SMA will have na on bars 0 through 18, and its first valid value on bar 19.

graph TD A[Bar 0 arrives] --> B{bar_index >= N - 1?} B -- No --> C[ta.sma returns na] B -- Yes --> D[Sum last N closing prices] D --> E[Divide sum by N] E --> F[SMA value plotted on chart] F --> G[Next bar arrives] G --> H[Drop oldest price from window] H --> D C --> G

4. Implementing SMA in Pine Script v6

Pine Script v6 provides the built-in function ta.sma() to compute the Simple Moving Average. The length parameter accepts a series int, meaning it can be dynamically computed at runtime — a key advantage over functions like ta.ema() which require a simple int length.

Basic SMA Indicator

🔽 [Click to expand] View Full Pine Script Code — Basic SMA Indicator
//@version=6
indicator(title = "SMA Moving Average Demo", overlay = true)

// --- Inputs ---
// User-configurable SMA period; input.int returns input int, which satisfies simple int
int smaLength = input.int(defval = 20, title = "SMA Length", minval = 1, maxval = 500)

// --- Calculation ---
// ta.sma() accepts series int for length — dynamic lengths are fully supported
// Returns na for the first (smaLength - 1) bars
float smaValue = ta.sma(close, smaLength)

// --- Visualization ---
// Plot the SMA line on the main chart pane (overlay = true)
plot(
    series    = smaValue,
    title     = "SMA",
    color     = color.new(color.blue, 0),
    linewidth = 2,
    style     = plot.style_line
)

Multi-Period SMA Comparison

A common technique is to plot multiple SMAs with different periods simultaneously to observe how shorter and longer windows interact with price.

🔽 [Click to expand] View Full Pine Script Code — Multi-Period SMA
//@version=6
indicator(title = "Multi-Period SMA Comparison", overlay = true)

// --- Inputs ---
// Three independently configurable SMA periods
int fastLen   = input.int(defval = 10,  title = "Fast SMA Length",   minval = 1)
int medLen    = input.int(defval = 50,  title = "Medium SMA Length", minval = 1)
int slowLen   = input.int(defval = 200, title = "Slow SMA Length",   minval = 1)

// --- Calculations ---
// ta.sma() natively accepts series int — all three calls are fully legal
float fastSma = ta.sma(close, fastLen)
float medSma  = ta.sma(close, medLen)
float slowSma = ta.sma(close, slowLen)

// --- Visualization ---
// Fast SMA: responsive, plotted in green
plot(
    series    = fastSma,
    title     = "Fast SMA",
    color     = color.new(color.green, 0),
    linewidth = 1,
    style     = plot.style_line
)

// Medium SMA: balanced, plotted in orange
plot(
    series    = medSma,
    title     = "Medium SMA",
    color     = color.new(color.orange, 0),
    linewidth = 2,
    style     = plot.style_line
)

// Slow SMA: smooth trend baseline, plotted in red
plot(
    series    = slowSma,
    title     = "Slow SMA",
    color     = color.new(color.red, 0),
    linewidth = 2,
    style     = plot.style_line
)

5. SMA with Dynamic Length (Series Int)

One of the key technical advantages of ta.sma() in Pine Script v6 is that its length parameter accepts a series int. This means the window size can be computed dynamically on each bar — for example, derived from a volatility measure or another indicator output.

🔽 [Click to expand] View Full Pine Script Code — Dynamic Length SMA
//@version=6
indicator(title = "Dynamic Length SMA", overlay = true)

// --- Base length from user input ---
int baseLen = input.int(defval = 20, title = "Base SMA Length", minval = 2)

// --- Dynamic length: scale base length by a volatility proxy ---
// math.round() returns series float; int() cast produces series int
// ta.sma() accepts series int for length — this is fully legal
float volatilityRatio = ta.sma(high - low, baseLen) / ta.sma(close, baseLen)

// Guard against na on early bars and ensure minimum length of 2
int dynLen = na(volatilityRatio) ? baseLen : int(math.max(2.0, math.round(baseLen * (1.0 + volatilityRatio * 10.0))))

// --- Dynamic SMA calculation ---
// ta.sma() natively supports series int length — no compile error
float dynSma = ta.sma(close, dynLen)

// --- Static reference SMA for comparison ---
float staticSma = ta.sma(close, baseLen)

// --- Visualization ---
plot(dynSma,    title = "Dynamic SMA", color = color.new(color.purple, 0), linewidth = 2)
plot(staticSma, title = "Static SMA",  color = color.new(color.gray,   0), linewidth = 1)

6. SMA vs. Other Moving Averages — Key Differences

Understanding what makes the SMA unique requires comparing it to other common moving average types at the mathematical level.

Property SMA EMA WMA
Weight distribution Equal ($\frac{1}{N}$ each) Exponentially decaying Linearly decreasing
Oldest bar weight $\frac{1}{N}$ (same as newest) Near zero (never exactly 0) $\frac{1}{N(N+1)/2}$ (smallest)
Pine Script length qualifier series int ✅ simple int ⚠️ series int ✅
Warm-up bars N − 1 bars return na Starts from bar 0 (no na warm-up) N − 1 bars return na
Reaction to price spike Gradual (diluted by N) Fast (high recent weight) Moderate

Important Pine Script v6 note: ta.ema() requires a simple int length — passing a series int (such as a dynamically computed value) to ta.ema() will cause a compile error. ta.sma() does not have this restriction and accepts series int lengths natively.

7. Practical Considerations

The Lag Property

Because the SMA averages past prices, it always lags behind the current price. The lag is approximately $\frac{N-1}{2}$ bars. For a 20-period SMA, the center of mass of the window is roughly 9.5 bars in the past. This is a mathematical property, not a flaw — it is precisely what produces the smoothing effect.

$$\text{Approximate Lag} = \frac{N - 1}{2} \text{ bars}$$

Handling na Values

When using SMA output in further calculations, always guard against na during the warm-up period using na() checks or nz():

//@version=6
indicator("SMA na Guard Example", overlay = true)

int len = input.int(20, "Length")
float smaVal = ta.sma(close, len)

// Safe downstream use: replace na with 0.0 using nz()
float safeVal = nz(smaVal, 0.0)

// Or use na() to conditionally skip logic during warm-up
float result = na(smaVal) ? na : close - smaVal  // distance from price to SMA

plot(result, title = "Price - SMA", color = color.blue)

8. Conclusion

  • The Simple Moving Average computes the arithmetic mean of the last $N$ prices, assigning equal weight $\frac{1}{N}$ to every bar in the window. It returns na for the first $N - 1$ bars and produces its first valid value at bar_index == N - 1.
  • In Pine Script v6, ta.sma() uniquely accepts a series int for its length parameter, enabling fully dynamic window sizes computed at runtime — unlike ta.ema() which is restricted to simple int lengths.
  • The SMA's smoothing power and lag are both direct mathematical consequences of averaging: larger $N$ produces a smoother line with greater lag ($\approx \frac{N-1}{2}$ bars), while smaller $N$ tracks price more closely at the cost of retaining more noise.

Ideas for Further Development

  • SMA Ribbon: Plot 8–12 SMAs with incrementally increasing periods (e.g., 10, 20, 30, … 100) to create a visual "ribbon" that expands during trending markets and compresses during consolidation.
  • SMA Deviation Band: Compute the standard deviation of price around the SMA using ta.stdev() and plot upper/lower bands at ±1σ and ±2σ to quantify how far price has deviated from its mean — a foundation for mean-reversion analysis.

Related Posts

Comments

Popular posts from this blog

How to Build a Volume Indicator in Pine Script v6: Buying & Selling Pressure Analysis

Pine Script v6 plot() Function: Complete Guide to Lines, Histograms, and Circles

SuperTrend Indicator in Pine Script v6: Canonical Formula, Flip Logic, and Non-Repainting Signals