How to Compare a Short and Long Moving Average with Candlesticks in Python

Recent AAPL candlesticks with a five-day and twenty-day simple moving average in Python

In Phase 3-1, you built one moving average from scratch. That gave you the basic idea.

Now we take the next step: compare two moving averages on the same chart.

This time, we will not hide the price chart. We will keep the candlesticks visible and place both moving averages on top.

candlesticks
+ moving average 1
+ moving average 2

That is more realistic and more educational than plotting only the Close line.

1. Why Use Two Moving Averages?

One moving average answers one question:

What is the average of the most recent N prices?

Two moving averages let you compare two speeds.

short window
→ reacts faster

long window
→ reacts more slowly

That is the main idea of this lesson.

2. The Default Windows

We will use:

moving_average_1_days = 5
moving_average_2_days = 20

The 5-day average responds faster because it uses fewer prices. The 20-day average is smoother because it uses more prices.

3. Keep the Candlestick Chart Visible

We are still working with OHLC data:

Open
High
Low
Close

So instead of replacing the chart with only a Close line, this lesson draws candles first.

price data
→ candlesticks
→ short moving average
→ long moving average

This helps you see how the indicator sits on top of price.

4. Full Runnable Python File

Save this as:

phase3_02_compare_short_long_moving_averages.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-2
# Compare a Short and Long Moving Average with Python
# Candlesticks + Moving Average 1 + Moving Average 2
# ============================================================


# ------------------------------------------------------------
# 1. Settings
# ------------------------------------------------------------

symbol = "AAPL"
recent_trading_days = 60
moving_average_1_days = 5
moving_average_2_days = 20


# ------------------------------------------------------------
# 2. Build a Simple Moving Average from scratch
# ------------------------------------------------------------

def simple_moving_average(values, window):
    if window <= 0:
        raise ValueError("window must be greater than 0.")

    if len(values) < window:
        raise ValueError(
            "Not enough values for the selected moving-average window."
        )

    averages = [None] * (window - 1)

    for i in range(window - 1, len(values)):
        start = i - window + 1
        recent_values = values[start:i + 1]
        average = sum(recent_values) / window
        averages.append(average)

    return averages


# ------------------------------------------------------------
# 3. Draw candlesticks from OHLC data
# ------------------------------------------------------------

def draw_candlesticks(ax, data, candle_width=0.65):
    x_positions = list(range(len(data)))

    for x, (_, row) in zip(x_positions, data.iterrows()):
        open_price = float(row["Open"])
        high_price = float(row["High"])
        low_price = float(row["Low"])
        close_price = float(row["Close"])

        is_rising = close_price >= open_price
        body_color = "#2E8B57" if is_rising else "#C0392B"

        ax.vlines(
            x,
            low_price,
            high_price,
            linewidth=1.1,
            alpha=0.95,
        )

        body_bottom = min(open_price, close_price)
        body_height = abs(close_price - open_price)

        if body_height == 0:
            body_height = 0.15

        candle_body = Rectangle(
            (x - candle_width / 2, body_bottom),
            candle_width,
            body_height,
            linewidth=1.0,
            edgecolor=body_color,
            facecolor=body_color,
            alpha=0.85,
        )
        ax.add_patch(candle_body)

    return x_positions


# ------------------------------------------------------------
# 4. Set the working folder
# ------------------------------------------------------------

SCRIPT_DIR = Path(__file__).resolve().parent
os.chdir(SCRIPT_DIR)


# ------------------------------------------------------------
# 5. Download recent market data
# ------------------------------------------------------------

end_date = date.today() + timedelta(days=1)
start_date = date.today() - timedelta(days=180)

df = fdr.DataReader(
    symbol,
    start_date.strftime("%Y-%m-%d"),
    end_date.strftime("%Y-%m-%d"),
)

if df.empty:
    raise ValueError("No market data was returned.")

df = df.tail(recent_trading_days).copy()

print("First available date:", df.index[0].strftime("%Y-%m-%d"))
print("Most recent available date:", df.index[-1].strftime("%Y-%m-%d"))
print()


# ------------------------------------------------------------
# 6. Calculate two moving averages
# ------------------------------------------------------------

close_prices = df["Close"].astype(float).tolist()

ma_1_values = simple_moving_average(
    values=close_prices,
    window=moving_average_1_days,
)

ma_2_values = simple_moving_average(
    values=close_prices,
    window=moving_average_2_days,
)

df[f"SMA_{moving_average_1_days}"] = ma_1_values
df[f"SMA_{moving_average_2_days}"] = ma_2_values


# ------------------------------------------------------------
# 7. Print a small result table
# ------------------------------------------------------------

print("Recent OHLC and moving-average data:")
print(
    df[
        [
            "Open",
            "High",
            "Low",
            "Close",
            f"SMA_{moving_average_1_days}",
            f"SMA_{moving_average_2_days}",
        ]
    ].tail(10)
)
print()


# ------------------------------------------------------------
# 8. Explain the meaning in simple words
# ------------------------------------------------------------

print("Interpretation:")
print(
    f"- SMA {moving_average_1_days} reacts faster because it uses fewer prices."
)
print(
    f"- SMA {moving_average_2_days} is smoother because it uses more prices."
)
print(
    "- Candlesticks show daily OHLC price structure."
)
print(
    "- The two moving averages help you compare short-term and long-term price behavior."
)
print()


# ------------------------------------------------------------
# 9. Draw candlesticks + two moving averages
# ------------------------------------------------------------

figure_width = 12
figure_height = 7
figure_dpi = 100

fig, ax = plt.subplots(
    figsize=(figure_width, figure_height),
    dpi=figure_dpi,
)

x_positions = draw_candlesticks(ax, df)

ax.plot(
    x_positions,
    df[f"SMA_{moving_average_1_days}"],
    linewidth=2.0,
    label=f"SMA {moving_average_1_days}",
)

ax.plot(
    x_positions,
    df[f"SMA_{moving_average_2_days}"],
    linewidth=2.3,
    label=f"SMA {moving_average_2_days}",
)

ax.set_title(
    f"{symbol} — Candlesticks with "
    f"{moving_average_1_days}-Day and {moving_average_2_days}-Day Moving Averages",
    fontsize=17,
)

ax.set_xlabel("Date", fontsize=13)
ax.set_ylabel("Price", fontsize=13)
ax.tick_params(axis="both", labelsize=10)
ax.grid(alpha=0.25)
ax.legend(fontsize=11)

step = max(1, len(df) // 8)
tick_positions = x_positions[::step]
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=30,
    ha="right",
)

fig.subplots_adjust(
    left=0.09,
    right=0.98,
    top=0.90,
    bottom=0.18,
)

output_file = SCRIPT_DIR / "short_long_moving_averages_candles.png"

fig.savefig(
    output_file,
    dpi=120,
    bbox_inches="tight",
)

print("Saved:", output_file)

plt.show()
plt.close(fig)

5. What Will the Script Show?

You should see three things together:

  • candlesticks,
  • a short moving average,
  • a long moving average.

The short line should follow price more closely. The longer line should look smoother.

6. What Does That Mean?

The moving averages are not new market data. They are transformations of the Close prices.

Close prices
→ average over a window
→ moving-average line

A smaller window reacts faster. A bigger window reacts more slowly.

Keeping the candles visible helps you see that relationship clearly.

7. Try One Small Experiment

Change:

moving_average_1_days = 5
moving_average_2_days = 20

to:

moving_average_1_days = 10
moving_average_2_days = 30

Then run the same file again.

Ask yourself:

Which line reacts faster?
Which line is smoother?
How much of the candle noise disappears?

Check Your Understanding

  • You can explain why two moving averages are useful to compare.
  • You understand that the short moving average reacts faster.
  • You understand that the long moving average is smoother.
  • You can explain why keeping candlesticks visible is educational.
  • You ran the full file and created short_long_moving_averages_candles.png.

What You Just Built

OHLC market data
→ candlesticks
→ short moving average
→ long moving average
→ one chart for direct comparison

This is a stronger chart than a Close-only line because it keeps the real price structure visible.

In the next lesson, we can ask a natural follow-up question: What does it mean when the short moving average crosses the long moving average?


Official references: FinanceDataReader and matplotlib.