In the previous lesson, you built one candlestick from one row of OHLC data. Now we will do one new thing: repeat the same drawing logic for every row in a DataFrame.
one OHLC row
→ one candlestick
many OHLC rows
→ many candlesticks
→ one candlestick chart
We will still build the chart ourselves with matplotlib. No candlestick library will do the work for us yet.
What you will finish: a multi-day AAPL candlestick chart that appears on your screen and is also saved beside your Python file.
1. Start from the One-Candle Idea
In Phase 2-1, one candle needed only two drawing commands:
ax.vlines(...)
→ draw the wick from Low to High
ax.bar(...)
→ draw the body from Open to Close
We are not replacing that logic. We are going to repeat it.
row 0 → candle 0
row 1 → candle 1
row 2 → candle 2
...
row 19 → candle 19
That repeated work is exactly what a for loop is for.
2. Create a New Python File
Create:
candlestick_chart_from_scratch.py
As before, make the folder containing the Python file the working folder:
from pathlib import Path
import os
SCRIPT_DIR = Path(__file__).resolve().parent
os.chdir(SCRIPT_DIR)
This keeps the code and the generated PNG together.
3. Get Several Days of AAPL Data
import FinanceDataReader as fdr
import matplotlib.pyplot as plt
symbol = "AAPL"
start_date = "2025-01-02"
end_date = "2025-01-31"
df = fdr.DataReader(symbol, start_date, end_date)
Before drawing, look at the data:
print(df[["Open", "High", "Low", "Close"]].head())
Each row still contains the four prices needed to draw one candle.
4. The New Building Block: a for Loop
Here is the key line:
for i, (date, row) in enumerate(df.iterrows()):
df.iterrows()
iterrows() gives Python one DataFrame row at a time.
first row
second row
third row
...
For each row, date is the DataFrame index and row contains Open, High, Low, Close, Volume, and the other columns.
enumerate(...)
enumerate() adds a counting number:
0
1
2
3
...
We store that number in i. It becomes the x-position of each candle.
i = 0 → first candle
i = 1 → second candle
i = 2 → third candle
5. Read OHLC Inside the Loop
open_price = float(row["Open"])
high_price = float(row["High"])
low_price = float(row["Low"])
close_price = float(row["Close"])
Then use the same direction and body calculations as before:
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)
6. Draw Each Candle at Its Own x-Position
The important change is that x is now i.
ax.vlines(
x=i,
ymin=low_price,
ymax=high_price,
color=candle_color,
linewidth=1.5,
)
ax.bar(
x=i,
height=body_height,
bottom=body_bottom,
width=candle_width,
color=candle_color,
edgecolor=candle_color,
)
Because i changes every time the loop runs, each candle moves one position to the right.
7. Control the Space Between Candles
candle_width = 0.6
A smaller value makes thinner candle bodies. A larger value makes them wider. Keep 0.6 for now.
8. Put Dates on the x-Axis
If every date is printed, labels can overlap. So show one date every three candles:
date_tick_step = 3
tick_positions = list(range(0, len(df), date_tick_step))
This creates positions such as:
0, 3, 6, 9, 12, ...
Then convert those positions into readable dates:
tick_labels = [
df.index[i].strftime("%Y-%m-%d")
for i in tick_positions
]
strftime() changes a date into text. Here %Y is year, %m is month, and %d is day.
9. Make the Chart Easier to Read
A correct chart is not enough. The title, price numbers, and date labels also need to be readable.
figure_width = 12
figure_height = 7
title_fontsize = 20
label_fontsize = 14
tick_fontsize = 12
Whole chart size: figsize
fig, ax = plt.subplots(
figsize=(figure_width, figure_height)
)
figsize controls the size of the whole figure. A wider figure gives many candles more horizontal room.
Title size: fontsize
ax.set_title(
f"{symbol} Candlestick Chart",
fontsize=title_fontsize,
)
Axis-label size
ax.set_ylabel(
"Price",
fontsize=label_fontsize,
)
Date and price-number size
ax.tick_params(
axis="both",
labelsize=tick_fontsize,
)
figsize
→ size of the whole chart
fontsize
→ title or axis-label text size
tick_params(labelsize=...)
→ date and price-number text size
There is no single perfect font size. The best value depends on the figure size and the number of candles.
10. Complete Code
from pathlib import Path
import os
import FinanceDataReader as fdr
import matplotlib.pyplot as plt
SCRIPT_DIR = Path(__file__).resolve().parent
os.chdir(SCRIPT_DIR)
print("Working folder:")
print(SCRIPT_DIR)
print()
symbol = "AAPL"
start_date = "2025-01-02"
end_date = "2025-01-31"
candle_width = 0.6
figure_width = 12
figure_height = 7
title_fontsize = 20
label_fontsize = 14
tick_fontsize = 12
date_tick_step = 3
df = fdr.DataReader(symbol, start_date, end_date)
print("Rows downloaded:", len(df))
print()
print(df[["Open", "High", "Low", "Close"]].head())
print()
fig, ax = plt.subplots(
figsize=(figure_width, figure_height)
)
for i, (date, 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)
ax.vlines(
x=i,
ymin=low_price,
ymax=high_price,
color=candle_color,
linewidth=1.5,
)
ax.bar(
x=i,
height=body_height,
bottom=body_bottom,
width=candle_width,
color=candle_color,
edgecolor=candle_color,
)
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} Candlestick Chart",
fontsize=title_fontsize,
)
ax.set_ylabel(
"Price",
fontsize=label_fontsize,
)
ax.tick_params(
axis="both",
labelsize=tick_fontsize,
)
ax.grid(axis="y", alpha=0.25)
ax.set_xlim(-1, len(df))
plt.tight_layout()
output_file = (
SCRIPT_DIR
/ "candlestick_chart_from_scratch.png"
)
plt.savefig(
output_file,
dpi=160,
bbox_inches="tight",
)
print("Chart saved:")
print(output_file)
print()
print("A chart window will now open.")
print("Close the chart window when you are finished viewing it.")
plt.show()
11. Run the File
python candlestick_chart_from_scratch.py
The terminal should show the working folder, the number of downloaded rows, and the first few OHLC rows. Then the chart appears on screen.
The script also saves:
candlestick_chart_from_scratch.png
in the same folder as the Python file.
your-practice-folder/
├─ candlestick_chart_from_scratch.py
└─ candlestick_chart_from_scratch.png
12. Change One Thing Yourself
Find:
tick_fontsize = 12
Change it to:
tick_fontsize = 16
Run the script again. The candles and market data do not change. Only the date and price-number text becomes larger.
market data
→ controls what the chart says
display settings
→ control how clearly you can read it
What You Just Learned
one DataFrame row
→ one wick + one body
for loop
→ repeat for every row
different x-position
→ candles line up across time
date labels
→ x-axis becomes readable
font settings
→ chart becomes easier to read
A candlestick chart is simply the one-candle building block repeated across time.
Where Do We Go Next?
You can now build the shape of a candlestick chart yourself. The next question is more important than drawing: what does one candle actually tell us about what happened during that trading period?
In the next lesson, we will return to one candle and learn how Open, High, Low, and Close describe the movement between buyers and sellers without jumping straight to patterns or predictions.