A Simple Moving Average has one very simple rule.
every price inside the window
gets the same weight
That makes the SMA easy to understand.
But it also raises a new question:
Should an older price
matter as much as a recent price?
An Exponential Moving Average, or EMA, answers that question in a different way.
It gives more influence to recent prices and lets older information fade gradually.
1. Start with the Problem We Already Know
In the previous lessons, we built a Simple Moving Average from recent Close prices.
For a 5-day SMA:
Day 1 price → 20%
Day 2 price → 20%
Day 3 price → 20%
Day 4 price → 20%
Day 5 price → 20%
Every price has the same weight.
When the window moves forward, the oldest price disappears from the calculation completely.
inside the SMA window
→ equal weight
outside the SMA window
→ zero weight
EMA uses a different kind of memory.
2. EMA Lets Old Information Fade
Instead of giving every recent price exactly the same weight, EMA updates itself one price at a time.
new price
↓
gets fresh weight
+
previous EMA
↓
carries older information
The key idea is:
recent information
→ larger influence
older information
→ smaller and smaller influence
The old information does not suddenly disappear.
It fades.
3. The EMA Formula Is Smaller Than It Looks
We only need three things:
1. today's price
2. yesterday's EMA
3. one weight called alpha
The recursive formula is:
EMA_now
=
alpha * Price_now
+
(1 - alpha) * EMA_previous
Read that formula as a sentence.
new EMA
=
a piece of the new price
+
a piece of the previous EMA
The previous EMA already contains information from older prices.
That is how the past stays in the calculation without storing a fixed window of equal-weight prices.
4. What Is Alpha?
alpha controls how strongly the newest price changes the EMA.
large alpha
→ new price matters more
→ EMA reacts faster
small alpha
→ previous EMA matters more
→ EMA reacts more slowly
A common way to choose alpha is from a span:
alpha = 2 / (span + 1)
For example, if:
span = 3
then:
alpha
= 2 / (3 + 1)
= 0.5
So half of the update comes from the newest price, and half comes from the previous EMA.
The important relationship is simple:
larger span
→ smaller alpha
→ slower response
smaller span
→ larger alpha
→ faster response
5. Calculate a Tiny EMA by Hand
Before using real market data, let us calculate three prices by hand.
prices:
100
102
106
span = 3
alpha = 0.5
For this beginner example, use the first price as the first EMA.
EMA 1 = 100
Now move to the second price.
EMA 2
= 0.5 * 102
+ 0.5 * 100
= 101
Then move to the third price.
EMA 3
= 0.5 * 106
+ 0.5 * 101
= 103.5
Notice what happened.
We did not go back and average all three prices again.
We only needed:
current price
+
previous EMA
That is why the formula is called recursive.
6. Why Is It Called “Exponential”?
Each update keeps only part of the previous EMA.
Then the next update keeps only part of that value again.
recent price
→ strong influence
one step older
→ smaller influence
two steps older
→ smaller again
three steps older
→ smaller again
The influence falls repeatedly by the same ratio.
That repeated decay is the reason for the word exponential.
You do not need to memorize every historical weight.
The recursive update handles that memory for us.
7. Build EMA from Scratch with Python
The Python function can follow the hand calculation almost line by line.
def exponential_moving_average(close, span):
alpha = 2 / (span + 1)
ema_values = []
previous_ema = None
for price in close:
if previous_ema is None:
current_ema = price
else:
current_ema = (
alpha * price
+ (1 - alpha) * previous_ema
)
ema_values.append(current_ema)
previous_ema = current_ema
Look at the loop carefully.
read one price
↓
combine it with previous EMA
↓
store the new EMA
↓
use it in the next step
There is no technical-analysis library inside the calculation.
The formula you just learned is the code.
8. Full Runnable Python File
We reuse the same basic tools from earlier Alphesta lessons: FinanceDataReader for market data, pandas for the table, and matplotlib for the chart.
Save this file as:
phase3_06_ema_from_scratch.py
Then run:
python phase3_06_ema_from_scratch.py
from datetime import datetime, timedelta
from pathlib import Path
import FinanceDataReader as fdr
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import pandas as pd
# ------------------------------------------------------------
# User settings
# ------------------------------------------------------------
SYMBOL = "AAPL"
TRADING_DAYS = 90
EMA_SPAN = 20
OUTPUT_FILE = "ema_from_scratch_candles.png"
# ------------------------------------------------------------
# 1. Download recent OHLC data
# ------------------------------------------------------------
def download_recent_ohlc(symbol, trading_days):
end_date = datetime.now()
start_date = end_date - timedelta(days=220)
df = fdr.DataReader(
symbol,
start_date.strftime("%Y-%m-%d"),
end_date.strftime("%Y-%m-%d"),
)
required_columns = ["Open", "High", "Low", "Close"]
df = df.dropna(subset=required_columns).copy()
if len(df) < trading_days:
raise ValueError(
f"Only {len(df)} valid rows were downloaded. "
f"Need at least {trading_days}."
)
return df.tail(trading_days).copy()
# ------------------------------------------------------------
# 2. Build EMA from scratch
# ------------------------------------------------------------
def exponential_moving_average(close, span):
if span < 1:
raise ValueError("span must be at least 1")
alpha = 2 / (span + 1)
ema_values = []
previous_ema = None
for price in close.astype(float):
if previous_ema is None:
current_ema = price
else:
current_ema = (
alpha * price
+ (1 - alpha) * previous_ema
)
ema_values.append(current_ema)
previous_ema = current_ema
ema = pd.Series(
ema_values,
index=close.index,
name=f"EMA_{span}",
)
return ema, alpha
# ------------------------------------------------------------
# 3. Draw simple candlesticks without mplfinance
# ------------------------------------------------------------
def draw_candlesticks(ax, df):
candle_width = 0.6
for x, (_, row) in enumerate(df.iterrows()):
open_price = float(row["Open"])
high_price = float(row["High"])
low_price = float(row["Low"])
close_price = float(row["Close"])
ax.vlines(
x,
low_price,
high_price,
linewidth=1,
)
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.01,
)
face = "white" if close_price >= open_price else "black"
candle = Rectangle(
(x - candle_width / 2, body_bottom),
candle_width,
body_height,
facecolor=face,
edgecolor="black",
linewidth=1,
)
ax.add_patch(candle)
# ------------------------------------------------------------
# 4. Draw price + EMA
# ------------------------------------------------------------
def draw_chart(df, ema_column, span, output_file):
fig, ax = plt.subplots(figsize=(12, 7))
draw_candlesticks(ax, df)
x = list(range(len(df)))
ax.plot(
x,
df[ema_column],
linewidth=2,
label=f"EMA {span}",
)
tick_step = max(len(df) // 8, 1)
tick_positions = list(range(0, len(df), tick_step))
if tick_positions[-1] != len(df) - 1:
tick_positions.append(len(df) - 1)
tick_labels = [
df.index[i].strftime("%Y-%m-%d")
for i in tick_positions
]
ax.set_xticks(tick_positions)
ax.set_xticklabels(
tick_labels,
rotation=45,
ha="right",
)
ax.set_title(
f"{SYMBOL}: Candlesticks + EMA {span}"
)
ax.set_xlabel("Most recent available trading rows")
ax.set_ylabel("Price")
ax.legend()
fig.tight_layout()
fig.savefig(output_file, dpi=160)
plt.close(fig)
# ------------------------------------------------------------
# 5. Run the lesson
# ------------------------------------------------------------
def main():
df = download_recent_ohlc(
SYMBOL,
TRADING_DAYS,
)
ema, alpha = exponential_moving_average(
df["Close"],
EMA_SPAN,
)
ema_column = ema.name
df[ema_column] = ema
print(f"[SYMBOL] {SYMBOL}")
print(f"[ROWS] {len(df)}")
print(f"[EMA SPAN] {EMA_SPAN}")
print(f"[ALPHA] {alpha:.6f}")
print()
print("Most recent available rows:")
print(
df[["Close", ema_column]]
.tail(10)
.round(2)
.to_string()
)
draw_chart(
df,
ema_column,
EMA_SPAN,
OUTPUT_FILE,
)
output_path = Path(OUTPUT_FILE).resolve()
print()
print(f"[OUTPUT] {output_path}")
if __name__ == "__main__":
main()
The program prints the smoothing factor and the most recent available Close and EMA values.
It also creates:
ema_from_scratch_candles.png
That chart should contain:
candlesticks
+
EMA 20
9. Read the Chart in the Right Order
Do not look at the EMA line first.
Read the chart from the source data outward.
1. Look at the candles.
2. See what price has been doing.
3. Look at the EMA.
4. Ask how quickly the EMA followed the change.
Remember:
price
↓
EMA calculation
↓
EMA line
The EMA is a transformation of price.
It does not replace the price underneath it.
10. EMA Can React Faster, but It Still Lags
EMA gives more influence to recent prices.
That can make it respond more strongly to a new price move than a slower average.
But EMA is still calculated from prices that already exist.
price changes first
↓
EMA receives the new price
↓
EMA changes
So EMA does not know tomorrow's price.
More recent weight does not turn a moving average into a prediction machine.
11. What Does the Span Change?
In this lesson, the main parameter is:
EMA_SPAN = 20
The span changes alpha.
span
↓
alpha
↓
how strongly new price changes EMA
A shorter span usually makes the EMA move more closely with price.
A longer span usually makes the EMA smoother and slower.
We will compare that behavior more directly with SMA in the next lesson.
12. Change One Thing Yourself
Start with:
EMA_SPAN = 20
Then try:
EMA_SPAN = 10
Run the file again.
Then try:
EMA_SPAN = 50
Ask:
Which EMA stays closer to the candles?
Which EMA looks smoother?
Which EMA changes direction sooner?
What happened to alpha?
The goal is not to find the “best” span.
The goal is to connect the parameter to the behavior you can see.
change span
→ alpha changes
→ rerun
→ observe the chart
→ explain why
Check Your Understanding
You are ready to move on if you can explain these ideas in your own words:
- SMA gives equal weight to prices inside its window.
- EMA gives more influence to recent information and lets older influence fade.
alphacontrols how strongly the newest price changes the EMA.- A larger span gives a smaller alpha and usually a slower EMA.
- The recursive formula needs the current price and the previous EMA.
- EMA is still calculated from price data; it does not predict the future by itself.
What You Just Learned
Close price
↓
choose span
↓
alpha = 2 / (span + 1)
↓
current price + previous EMA
↓
new EMA
↓
repeat
If one idea stays in your head after this lesson, let it be this:
An EMA does not forget the past all at once. It lets the past fade while giving more influence to recent prices.
Now we can ask the natural next question:
If SMA and EMA use the same prices,
why do their lines react differently?