How to Read Rising and Falling Price with Candles in Python

Recent AAPL candlestick chart with High and Low lines for reading rising and falling price

One candlestick tells you what happened during one time interval. But markets do not stop after one candle.

The next useful question is: how is the current candle positioned compared with the candle before it?

We still will not add an indicator. Instead, we will compare only two things: High and Low.

current High vs previous High
current Low  vs previous Low

From those two comparisons, we can begin describing whether price is moving upward, downward, or in a mixed way.

1. Do Not Start with an Indicator

A moving average, MACD, RSI, or another indicator can be useful later. But every indicator is calculated from price data.

So first, learn to see the structure in price itself.

price first
→ structure second
→ indicators later

In this lesson, the price structure comes directly from candle Highs and Lows.

2. Use Recent AAPL Data

As in the previous lesson, the date range is created when the script runs.

from datetime import date, timedelta

today = date.today()

start_date = (
    today - timedelta(days=60)
).strftime("%Y-%m-%d")

end_date = today.strftime("%Y-%m-%d")

Then we keep the most recent 20 trading days:

recent_trading_days = 20

df = df.tail(
    recent_trading_days
)

The exact dates will change over time. That is intentional.

You are practicing on recent market data rather than reproducing an old picture forever.

3. Price Structure Needs at Least Two Candles

One candle can tell you its own Open, High, Low, and Close. But it cannot be called a higher high or lower low by itself.

For that, you need a comparison.

previous candle
      ↓
compare
      ↓
current candle

That is why our loop starts from row 1 instead of row 0:

for i in range(1, len(df)):

Row 0 has no earlier row inside our selected data to compare with.

4. Select the Current and Previous Candles

Inside the loop:

current = df.iloc[i]

previous = df.iloc[i - 1]

Read this literally.

i
→ current row

i - 1
→ one row before it

If i = 5:

current  = df.iloc[5]
previous = df.iloc[4]

Python can now compare the same columns from two neighboring trading days.

5. What Is a Higher High?

Read the two High prices:

current_high = float(
    current["High"]
)

previous_high = float(
    previous["High"]
)

Then compare them:

current_high > previous_high

If this is true, the current candle reached a higher price than the previous candle did.

current High
    ↑

previous High

We can describe that as a Higher High.

6. What Is a Higher Low?

Now compare the Lows:

current_low > previous_low

If true, the lowest price of the current candle is also above the lowest price of the previous candle.

current Low
    ↑

previous Low

We call that a Higher Low.

7. A Simple Rising Structure

Suppose both conditions are true:

current High > previous High

and

current Low > previous Low

Both the top and bottom of the candle's price range moved upward.

In this beginner lesson, we will call that:

Rising structure

The code is:

if (
    current_high > previous_high
    and current_low > previous_low
):
    structure = "Rising structure"

This is a description of two neighboring candles. It is not yet a complete definition of an uptrend.

8. A Simple Falling Structure

Reverse the comparison:

current High < previous High

and

current Low < previous Low

Now both the upper and lower boundaries moved downward.

Falling structure

In Python:

elif (
    current_high < previous_high
    and current_low < previous_low
):
    structure = "Falling structure"

Again, this describes one local comparison. A larger trend requires more context.

9. What If High and Low Disagree?

Markets often do not fit a clean two-word description.

For example:

Higher High
+
Lower Low

The current candle expanded beyond both sides of the previous candle.

Or:

Lower High
+
Higher Low

The current range contracted inside the previous range.

For now, we group these cases as:

Mixed structure

This is useful because it prevents us from forcing every candle into "up" or "down."

10. Print the Comparisons Before Looking at the Chart

The script prints a line for each comparison:

date
| Higher High or Lower/Equal High
| Higher Low or Lower/Equal Low
| Rising / Falling / Mixed structure

This follows the same Alphesta habit from the previous lesson:

numbers
→ comparison
→ chart
→ interpretation

11. Draw the Candles from Scratch Again

We keep using the wick-and-body logic from the earlier lessons.

ax.vlines(...)
→ wick

ax.bar(...)
→ body

That repetition is intentional. The code should become familiar rather than magical.

12. Add a High Line and a Low Line

To make the structure easier to see, collect all recent High values:

high_values = [
    float(value)
    for value in df["High"]
]

And the Low values:

low_values = [
    float(value)
    for value in df["Low"]
]

Then draw them:

ax.plot(
    x_positions,
    high_values,
    marker="o",
    label="High",
)

ax.plot(
    x_positions,
    low_values,
    marker="o",
    label="Low",
)

These lines do not create a technical indicator. They simply connect values that already exist in the OHLC table.

They help your eye answer:

Are recent Highs moving up?

Are recent Lows moving up?

Are both moving down?

Or are they disagreeing?

13. Keep the Chart Readable

This chart contains more information than the one-candle figure, so we use a wider figure and larger text.

figure_width = 14
figure_height = 8

title_fontsize = 22
label_fontsize = 15
tick_fontsize = 13
annotation_fontsize = 11

The figure becomes easier to read when many dates are shown.

We also show only every second date label:

date_tick_step = 2

If the dates still feel crowded, try:

date_tick_step = 3

14. Complete Code

from pathlib import Path
from datetime import date, timedelta
import os

import FinanceDataReader as fdr
import matplotlib.pyplot as plt

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

symbol = "AAPL"

today = date.today()

start_date = (
    today - timedelta(days=60)
).strftime("%Y-%m-%d")

end_date = today.strftime("%Y-%m-%d")

recent_trading_days = 20

candle_width = 0.6

figure_width = 14
figure_height = 8
title_fontsize = 22
label_fontsize = 15
tick_fontsize = 13
annotation_fontsize = 11

date_tick_step = 2

df = fdr.DataReader(
    symbol,
    start_date,
    end_date,
)

df = df.tail(
    recent_trading_days
)

if len(df) < 2:
    raise ValueError(
        "At least two trading days are needed "
        "to compare price structure."
    )

structure_rows = []

for i in range(1, len(df)):

    current = df.iloc[i]
    previous = df.iloc[i - 1]

    current_date = (
        df.index[i]
        .strftime("%Y-%m-%d")
    )

    current_high = float(
        current["High"]
    )

    current_low = float(
        current["Low"]
    )

    previous_high = float(
        previous["High"]
    )

    previous_low = float(
        previous["Low"]
    )

    high_relation = (
        "Higher High"
        if current_high > previous_high
        else "Lower/Equal High"
    )

    low_relation = (
        "Higher Low"
        if current_low > previous_low
        else "Lower/Equal Low"
    )

    if (
        current_high > previous_high
        and current_low > previous_low
    ):
        structure = "Rising structure"

    elif (
        current_high < previous_high
        and current_low < previous_low
    ):
        structure = "Falling structure"

    else:
        structure = "Mixed structure"

    structure_rows.append(
        (
            current_date,
            high_relation,
            low_relation,
            structure,
        )
    )

for row in structure_rows:
    print(
        f"{row[0]} | "
        f"{row[1]} | "
        f"{row[2]} | "
        f"{row[3]}"
    )

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

for i, (date_index, 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"]
    )

    is_up_candle = (
        close_price >= open_price
    )

    candle_color = (
        "green"
        if is_up_candle
        else "red"
    )

    body_bottom = min(
        open_price,
        close_price,
    )

    body_height = abs(
        close_price - open_price
    )

    visible_body_height = (
        body_height
        if body_height > 0
        else max(
            (high_price - low_price) * 0.01,
            0.01,
        )
    )

    ax.vlines(
        x=i,
        ymin=low_price,
        ymax=high_price,
        color=candle_color,
        linewidth=1.6,
    )

    ax.bar(
        x=i,
        height=visible_body_height,
        bottom=body_bottom,
        width=candle_width,
        color=candle_color,
        edgecolor=candle_color,
    )

high_values = [
    float(value)
    for value in df["High"]
]

low_values = [
    float(value)
    for value in df["Low"]
]

x_positions = list(
    range(len(df))
)

ax.plot(
    x_positions,
    high_values,
    marker="o",
    linewidth=1.2,
    label="High",
)

ax.plot(
    x_positions,
    low_values,
    marker="o",
    linewidth=1.2,
    label="Low",
)

latest_current = df.iloc[-1]
latest_previous = df.iloc[-2]

latest_high = float(
    latest_current["High"]
)

latest_low = float(
    latest_current["Low"]
)

previous_high = float(
    latest_previous["High"]
)

previous_low = float(
    latest_previous["Low"]
)

if (
    latest_high > previous_high
    and latest_low > previous_low
):
    latest_structure = (
        "Rising structure"
    )

elif (
    latest_high < previous_high
    and latest_low < previous_low
):
    latest_structure = (
        "Falling structure"
    )

else:
    latest_structure = (
        "Mixed structure"
    )

ax.text(
    0.02,
    0.97,
    (
        "Latest comparison: "
        f"{latest_structure}"
    ),
    transform=ax.transAxes,
    va="top",
    fontsize=annotation_fontsize,
    bbox=dict(
        boxstyle="round,pad=0.5",
        alpha=0.08,
    ),
)

tick_positions = list(
    range(
        0,
        len(df),
        date_tick_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=45,
    ha="right",
)

ax.set_title(
    (
        f"{symbol} — "
        "Reading Rising and Falling Price"
    ),
    fontsize=title_fontsize,
)

ax.set_ylabel(
    "Price",
    fontsize=label_fontsize,
)

ax.tick_params(
    axis="both",
    labelsize=tick_fontsize,
)

ax.legend(
    fontsize=annotation_fontsize,
)

ax.grid(
    axis="y",
    alpha=0.20,
)

ax.set_xlim(
    -1,
    len(df),
)

plt.tight_layout()

output_file = (
    SCRIPT_DIR
    / "rising_falling_price_with_candles.png"
)

plt.savefig(
    output_file,
    dpi=160,
    bbox_inches="tight",
)

print()
print("Chart saved:")
print(output_file)

plt.show()

15. Change the Window Yourself

Find:

recent_trading_days = 20

Change it to:

recent_trading_days = 10

Run the script again.

Ask yourself:

Does the recent structure
look clearer or less clear?

Do the Highs and Lows
still appear to move together?

Did your interpretation change
because you changed the viewing window?

This is an important lesson: the same market can look different depending on how much history you display.

What You Just Learned

Higher High + Higher Low
→ simple rising structure

Lower High + Lower Low
→ simple falling structure

High and Low disagree
→ mixed structure

These are basic price relationships, not trading signals.

You also saw that we can describe price movement directly from OHLC data before calculating a single technical indicator.

Where Do We Go Next?

Now you know how to read one candle and how to compare neighboring candles.

The next step is to examine a few famous candlestick shapes, but we will treat them as testable descriptions of price behavior, not as guaranteed predictions.

We will begin with the hammer.