We already know two ways to smooth market data.
SMA
→ average a fixed window
EMA
→ keep a running average
→ give more weight to recent values
But another smoothing rule has quietly appeared in our indicator lessons.
It is inside RSI.
It will appear again when we build ATR.
Then it will appear again in Directional Movement and ADX.
RSI
\
True Range
↓
Wilder Smoothing
↓
ATR
↓
Directional Movement
↓
ADX
At this point, repeating the same formula inside every indicator would hide an important idea.
So we will stop and make the smoothing rule itself a reusable Learning Block.
1. What Is Wilder’s Smoothing?
J. Welles Wilder used a recursive averaging method in the indicator framework he published in New Concepts in Technical Trading Systems in 1978.
The method is often called:
Wilder's smoothing
Wilder average
RMA
→ Running Moving Average
SMMA
→ Smoothed Moving Average
Different platforms can use different names, but the central recursive idea is simple.
First create an initial average.
Then update that average using only:
previous average
+
new value
2. Start with a Simple Average
Suppose our period is:
N = 3
and the first three values are:
10, 13, 16
The first Wilder average is simply:
(10 + 13 + 16) / 3
= 13
So the recursive process needs a starting point.
first N values
→ ordinary arithmetic mean
→ first Wilder value
3. After the Seed, Do Not Recalculate the Whole Window
Now suppose the next value is:
25
Wilder's update can be written as:
New Average
=
(
Previous Average × (N - 1)
+
New Value
)
/
N
With our numbers:
(
13 × 2
+
25
)
/
3
=
17
The next input is:
22
Update again:
(
17 × 2
+
22
)
/
3
=
18.666667
Notice what we did not do.
We did not throw away the previous average and rebuild everything from scratch.
4. The Same Formula Has a More Revealing Form
We can rearrange the update:
New Average
=
Previous Average
+
(
New Value
-
Previous Average
)
/
N
This form makes the learning rate visible.
alpha
=
1 / N
So Wilder smoothing is a recursive exponential-style smoother with a smoothing factor of:
α = 1 / N
5. Compare Wilder Smoothing with a Standard EMA
The EMA we learned earlier commonly uses:
EMA alpha
=
2 / (N + 1)
Wilder smoothing uses:
Wilder alpha
=
1 / N
For a 14-period calculation:
EMA 14 alpha
=
2 / 15
≈ 0.133333
Wilder 14 alpha
=
1 / 14
≈ 0.071429
The Wilder value moves a smaller fraction toward each new observation.
So, with the same stated period, Wilder smoothing normally reacts more slowly than the common EMA formula.
6. Why People Sometimes Say Wilder 14 Is Similar to EMA 27
Ask which standard EMA period has the same alpha as Wilder 14.
2 / (M + 1)
=
1 / 14
Solve for M:
M
=
2 × 14 - 1
=
27
In general:
Wilder N
has the same alpha as
EMA (2N - 1)
That does not mean every Wilder implementation will exactly match every EMA implementation.
Initialization rules can differ.
But the equivalence helps us understand the smoothing speed.
7. Compare SMA, EMA, and Wilder with One Tiny Sequence
Use:
values
=
10, 13, 16, 25, 22
period
=
3
All three methods begin with the same seed in this lesson:
(10 + 13 + 16) / 3
=
13
At the next value, 25:
SMA 3
=
(13 + 16 + 25) / 3
=
18
EMA 3
=
13 + 0.5 × (25 - 13)
=
19
Wilder 3
=
13 + (1/3) × (25 - 13)
=
17
At the final value, 22:
SMA 3
=
(16 + 25 + 22) / 3
=
21
EMA 3
=
19 + 0.5 × (22 - 19)
=
20.5
Wilder 3
=
17 + (1/3) × (22 - 17)
≈
18.666667
The three methods are not interchangeable.
SMA
→ fixed window
EMA
→ recursive
→ alpha = 2 / (N + 1)
Wilder
→ recursive
→ alpha = 1 / N
8. Wilder Smoothing Has Memory
The previous average already contains information from older observations.
So when we write:
new average
=
old average
+
small adjustment
old information does not suddenly disappear.
Its influence fades gradually.
old observations
→ stored inside previous average
→ influence fades over time
new observation
→ enters with weight 1 / N
This is why the word recursive matters.
9. Build a Reusable Wilder Function
We want one function that can later accept:
price values
RSI gains
RSI losses
True Range
+DM
-DM
DX
Some of those series begin immediately.
Others, such as True Range, can begin with one missing value because the first row has no previous Close.
So our function allows leading None values
and begins the seed at the first valid observation.
def wilder_average(values, period):
result = [None] * len(values)
if period <= 0:
raise ValueError("period must be positive")
valid_start = next(
(
i
for i, value in enumerate(values)
if value is not None
),
None,
)
if valid_start is None:
return result
seed_end = valid_start + period - 1
if seed_end >= len(values):
return result
seed_values = values[
valid_start : seed_end + 1
]
if any(
value is None
for value in seed_values
):
raise ValueError(
"Expected a continuous block "
"of valid values for the Wilder seed."
)
seed = (
sum(float(value) for value in seed_values)
/ period
)
result[seed_end] = seed
alpha = 1.0 / period
for i in range(
seed_end + 1,
len(values),
):
current_value = values[i]
if current_value is None:
result[i] = None
continue
previous_average = result[i - 1]
if previous_average is None:
raise ValueError(
"Unexpected missing Wilder state."
)
result[i] = (
previous_average
+ alpha
* (
float(current_value)
- previous_average
)
)
return result
10. Read the Function as a Learning Flow
find first valid value
↓
collect N valid observations
↓
ordinary mean
↓
first Wilder value
↓
take next observation
↓
move 1/N of the distance
toward the new value
↓
repeat
Once this block is understood, later indicators become shorter.
11. The Same Function Can Handle True Range
Our True Range series begins like this:
None,
TR 1,
TR 2,
TR 3,
...
That first None is not an error.
The first bar has no previous Close inside the dataset.
The reusable function simply finds the first valid True Range and begins counting the smoothing period there.
True Range
→ wilder_average()
→ ATR
12. Why RSI Needs This Block
RSI first separates price changes into:
gains
losses
Those raw series are noisy.
Wilder's RSI smooths the gain and loss measurements before comparing them.
gains
→ Wilder smoothing
→ average gain
losses
→ Wilder smoothing
→ average loss
average gain / average loss
→ RS
→ RSI
If you want to revisit that indicator, see What Is RSI? Compare Recent Gains and Losses with Python.
13. Why ATR Needs This Block
True Range gives one movement measurement per bar.
ATR asks for a smoother view across time.
True Range
→ Wilder smoothing
→ Average True Range
That is why this lesson belongs naturally between True Range and ATR.
14. Directional Movement Reuses It Again
Directional Movement creates:
+DM
-DM
Those series are also smoothed before they are normalized by range.
+DM
→ Wilder smoothing
-DM
→ Wilder smoothing
True Range
→ Wilder smoothing
then
→ +DI / -DI
So the same averaging block becomes shared infrastructure.
15. ADX Reuses the Same Idea Yet Again
After +DI and -DI, we can calculate DX.
Then:
DX
→ Wilder smoothing
→ ADX
This is the reason we do not want a new smoothing function inside every article.
learn once
→ test once
→ freeze the block
→ reuse it
16. Initialization Is Part of the Definition
Recursive averages need a starting value.
Our educational implementation uses:
first Wilder value
=
simple mean of the first N valid observations
After that:
recursive Wilder update
Other software can use different warm-up or initialization conventions.
That can make the earliest values differ.
So when two libraries disagree near the beginning of a series, do not immediately assume one formula is wrong.
Check the seed first.
17. The Complete Python Program
This lesson introduces no new Python package.
We reuse FinanceDataReader for market data
and matplotlib for the chart.
Save the following file as:
phase3_wilder_smoothing.py
from pathlib import Path
from datetime import date, timedelta
import os
import FinanceDataReader as fdr
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
# ============================================================
# Phase 3 — Wilder's Smoothing
# Compare SMA, standard EMA, and Wilder Average
# ============================================================
# ------------------------------------------------------------
# 1. Settings
# ------------------------------------------------------------
symbol = "AAPL"
recent_trading_days = 140
smoothing_period = 14
bullish_color = "seagreen"
bearish_color = "firebrick"
# ------------------------------------------------------------
# 2. Working folder
# ------------------------------------------------------------
SCRIPT_DIR = Path(__file__).resolve().parent
os.chdir(SCRIPT_DIR)
print("Working folder:")
print(SCRIPT_DIR)
print()
# ------------------------------------------------------------
# 3. Simple moving average
# ------------------------------------------------------------
def simple_moving_average(values, period):
result = [None] * len(values)
if period <= 0:
raise ValueError("period must be positive")
if len(values) < period:
return result
for i in range(period - 1, len(values)):
window = values[i - period + 1 : i + 1]
if any(value is None for value in window):
continue
result[i] = (
sum(float(value) for value in window)
/ period
)
return result
# ------------------------------------------------------------
# 4. Standard EMA with the same SMA seed
# ------------------------------------------------------------
def ema_with_sma_seed(values, period):
result = [None] * len(values)
if period <= 0:
raise ValueError("period must be positive")
valid_start = next(
(
i
for i, value in enumerate(values)
if value is not None
),
None,
)
if valid_start is None:
return result
seed_end = valid_start + period - 1
if seed_end >= len(values):
return result
seed_values = values[
valid_start : seed_end + 1
]
if any(
value is None
for value in seed_values
):
raise ValueError(
"Expected a continuous block "
"of valid values for the EMA seed."
)
seed = (
sum(float(value) for value in seed_values)
/ period
)
result[seed_end] = seed
alpha = 2.0 / (period + 1.0)
for i in range(
seed_end + 1,
len(values),
):
current_value = values[i]
if current_value is None:
result[i] = None
continue
previous_average = result[i - 1]
if previous_average is None:
raise ValueError(
"Unexpected missing EMA state."
)
result[i] = (
previous_average
+ alpha
* (
float(current_value)
- previous_average
)
)
return result
# ------------------------------------------------------------
# 5. Wilder average
# ------------------------------------------------------------
def wilder_average(values, period):
result = [None] * len(values)
if period <= 0:
raise ValueError("period must be positive")
valid_start = next(
(
i
for i, value in enumerate(values)
if value is not None
),
None,
)
if valid_start is None:
return result
seed_end = valid_start + period - 1
if seed_end >= len(values):
return result
seed_values = values[
valid_start : seed_end + 1
]
if any(
value is None
for value in seed_values
):
raise ValueError(
"Expected a continuous block "
"of valid values for the Wilder seed."
)
seed = (
sum(float(value) for value in seed_values)
/ period
)
result[seed_end] = seed
alpha = 1.0 / period
for i in range(
seed_end + 1,
len(values),
):
current_value = values[i]
if current_value is None:
result[i] = None
continue
previous_average = result[i - 1]
if previous_average is None:
raise ValueError(
"Unexpected missing Wilder state."
)
result[i] = (
previous_average
+ alpha
* (
float(current_value)
- previous_average
)
)
return result
# ------------------------------------------------------------
# 6. Draw candlesticks
# ------------------------------------------------------------
def draw_candlesticks(
ax,
market_df,
body_width=0.62,
):
for x, (_, row) in enumerate(
market_df.iterrows()
):
open_price = float(row["Open"])
high_price = float(row["High"])
low_price = float(row["Low"])
close_price = float(row["Close"])
if close_price >= open_price:
candle_color = bullish_color
else:
candle_color = bearish_color
ax.vlines(
x,
low_price,
high_price,
color=candle_color,
linewidth=1.0,
)
body_bottom = min(
open_price,
close_price,
)
body_height = abs(
close_price
- open_price
)
if body_height == 0:
body_height = max(
high_price - low_price,
0.01,
) * 0.02
body = Rectangle(
(
x - body_width / 2.0,
body_bottom,
),
body_width,
body_height,
facecolor=candle_color,
edgecolor=candle_color,
linewidth=0.8,
)
ax.add_patch(body)
# ------------------------------------------------------------
# 7. Hand-calculation self-test
# ------------------------------------------------------------
toy_values = [
10.0,
13.0,
16.0,
25.0,
22.0,
]
toy_period = 3
toy_sma = simple_moving_average(
toy_values,
toy_period,
)
toy_ema = ema_with_sma_seed(
toy_values,
toy_period,
)
toy_wilder = wilder_average(
toy_values,
toy_period,
)
expected_wilder = [
None,
None,
13.0,
17.0,
18.666666666666668,
]
for actual, expected in zip(
toy_wilder,
expected_wilder,
):
if expected is None:
assert actual is None
else:
assert abs(
actual - expected
) < 1e-12
# A second test proves the same function can
# accept one leading None, as True Range does.
tr_like_values = [
None,
6.0,
4.0,
5.0,
8.0,
]
tr_like_wilder = wilder_average(
tr_like_values,
period=3,
)
assert tr_like_wilder[:3] == [
None,
None,
None,
]
assert abs(
tr_like_wilder[3] - 5.0
) < 1e-12
assert abs(
tr_like_wilder[4]
- 6.0
) < 1e-12
print("Self-test")
print("=========")
print()
print("Toy values:")
print(toy_values)
print()
print("SMA(3):")
print(toy_sma)
print()
print("EMA(3), alpha = 2 / (3 + 1):")
print(toy_ema)
print()
print("Wilder(3), alpha = 1 / 3:")
print(toy_wilder)
print()
print("Self-test: PASS")
print()
# ------------------------------------------------------------
# 8. Download recent market data
# ------------------------------------------------------------
today = date.today()
start_date = (
today
- timedelta(days=320)
).strftime("%Y-%m-%d")
end_date = today.strftime("%Y-%m-%d")
df = fdr.DataReader(
symbol,
start_date,
end_date,
)
df = df.tail(
recent_trading_days
).copy()
if len(df) < smoothing_period:
raise ValueError(
"Not enough market data "
"for the selected period."
)
# ------------------------------------------------------------
# 9. Build the three smoothers
# ------------------------------------------------------------
close_values = [
float(value)
for value in df["Close"]
]
sma_values = simple_moving_average(
values=close_values,
period=smoothing_period,
)
ema_values = ema_with_sma_seed(
values=close_values,
period=smoothing_period,
)
wilder_values = wilder_average(
values=close_values,
period=smoothing_period,
)
df["SMA"] = sma_values
df["EMA"] = ema_values
df["Wilder"] = wilder_values
# ------------------------------------------------------------
# 10. Print the latest comparison
# ------------------------------------------------------------
valid_df = df.dropna(
subset=[
"SMA",
"EMA",
"Wilder",
]
)
latest = valid_df.iloc[-1]
latest_date = valid_df.index[-1]
print("Latest comparison")
print("=================")
print()
print(
"Symbol:",
symbol,
)
print(
"Date:",
latest_date.strftime("%Y-%m-%d"),
)
print(
"Close:",
f'{latest["Close"]:.2f}',
)
print(
f"SMA({smoothing_period}):",
f'{latest["SMA"]:.2f}',
)
print(
f"EMA({smoothing_period}):",
f'{latest["EMA"]:.2f}',
)
print(
f"Wilder({smoothing_period}):",
f'{latest["Wilder"]:.2f}',
)
print()
print(
"Standard EMA alpha:",
f'{2.0 / (smoothing_period + 1.0):.6f}',
)
print(
"Wilder alpha:",
f'{1.0 / smoothing_period:.6f}',
)
print(
"EMA period with the same alpha "
"as Wilder period:",
2 * smoothing_period - 1,
)
# ------------------------------------------------------------
# 11. Plot candles and the three averages
# ------------------------------------------------------------
plot_df = df.tail(90).copy()
fig, ax = plt.subplots(
figsize=(12, 7),
)
draw_candlesticks(
ax,
plot_df,
)
x_values = list(
range(len(plot_df))
)
ax.plot(
x_values,
plot_df["SMA"],
label=f"SMA {smoothing_period}",
linewidth=1.4,
)
ax.plot(
x_values,
plot_df["EMA"],
label=f"EMA {smoothing_period}",
linewidth=1.4,
)
ax.plot(
x_values,
plot_df["Wilder"],
label=f"Wilder {smoothing_period}",
linewidth=1.6,
)
ax.set_title(
f"{symbol} — SMA vs EMA vs Wilder Smoothing"
)
ax.set_ylabel(
"Price"
)
ax.grid(
axis="y",
alpha=0.20,
)
ax.legend()
tick_step = max(
1,
len(plot_df) // 8,
)
tick_positions = list(
range(
0,
len(plot_df),
tick_step,
)
)
tick_labels = [
plot_df.index[i].strftime(
"%Y-%m-%d"
)
for i in tick_positions
]
ax.set_xticks(
tick_positions
)
ax.set_xticklabels(
tick_labels,
rotation=35,
ha="right",
)
fig.subplots_adjust(
left=0.09,
right=0.98,
top=0.92,
bottom=0.17,
)
# ------------------------------------------------------------
# 12. Save the chart
# ------------------------------------------------------------
output_file = (
SCRIPT_DIR
/ "wilder_smoothing_sma_ema_comparison.png"
)
fig.savefig(
output_file,
dpi=140,
)
print()
print("Chart saved:")
print(output_file)
plt.show()
plt.close(fig)
18. Run the Program
python phase3_wilder_smoothing.py
The program first checks the hand-calculation example.
It also checks that the same Wilder function works
when the input begins with one None, like True Range.
Then it downloads recent AAPL data and compares:
SMA 14
EMA 14
Wilder 14
The chart is saved as:
wilder_smoothing_sma_ema_comparison.png
Rising candles use seagreen.
Falling candles use firebrick.
19. What Should You Look for on the Chart?
Do not ask which line predicts price best.
That is not the learning question.
Instead ask:
Which line reacts fastest
after a sudden move?
Which line stays smoother?
How long does each line
remember the move?
Because the standard EMA has the larger alpha, it will usually react faster than Wilder smoothing when both use the same stated period.
20. Change One Thing Yourself
Start with:
smoothing_period = 14
Then try:
smoothing_period = 7
Ask:
Does Wilder smoothing react faster?
Does the gap between
EMA and Wilder change?
Then try:
smoothing_period = 28
Ask the opposite questions.
Do not search for the best period yet.
First understand what the parameter changes.
21. Wilder Smoothing Is Not a Trading Signal
A smoother is a transformation.
input series
→ smoothing rule
→ smoother series
It does not automatically say:
buy
sell
trend continues
trend reverses
Wilder smoothing becomes useful because later indicators use it to transform specific measurements.
Check Your Understanding
- Wilder smoothing begins with an arithmetic mean of the first N valid observations in our implementation.
- After the seed, the calculation is recursive.
- Wilder's smoothing factor is 1/N.
- A common EMA uses 2/(N+1), so it reacts faster than Wilder smoothing for the same stated period.
- Wilder N has the same alpha as a standard EMA with period 2N-1.
- Recursive smoothing keeps older information inside the previous average and lets its influence fade gradually.
- Initialization differences can create different early values between software implementations.
- The same Wilder averaging block can be reused in RSI, ATR, Directional Movement, and ADX.
- Wilder smoothing is a mathematical transformation, not a trading rule.
What You Just Learned
first N valid values
↓
simple average
↓
first Wilder value
↓
previous average
+
1/N of the gap
toward new value
↓
new Wilder value
↓
repeat
If one idea stays in your head after this lesson, let it be this:
Wilder smoothing is a reusable recursive averaging block. Learn it once, and RSI, ATR, DMI, and ADX become easier to understand.
Where Do We Go Next?
We already know how to calculate True Range.
We now know how Wilder smooths a sequence.
True Range
+
Wilder Smoothing
=
Average True Range
That is the next natural Building Block.
Sources
- J. Welles Wilder Jr., New Concepts in Technical Trading Systems, Trend Research, 1978 — Google Books bibliographic record.
- TA-Lib — Relative Strength Index (RSI).
- TA-Lib — Average True Range (ATR).
- TA-Lib — Average Directional Index (ADX).