A figure, an Axes, a line
A chart is the quickest way to see that a price series is clean, trending or broken. In matplotlib a Figure is the whole canvas and an Axes is one chart on it, with its lines, labels and legend. The object-oriented way is fig, ax = plt.subplots() followed by calls on ax: ax.plot(x, y) draws a line, ax.set_title, ax.set_xlabel and ax.set_ylabel name the chart. Drawing on an explicit Axes, and returning it, makes a plotting function easy to reuse and to test.
import matplotlib
matplotlib.use("Agg") # draw without a window (no screen needed)
import matplotlib.pyplot as plt
import pandas as pd
prices = pd.Series([100.0, 102, 101, 105, 107, 106, 110], index=pd.bdate_range("2026-03-02", periods=7))
fig, ax = plt.subplots()
ax.plot(prices.index, prices.values, label="AAPL")
ax.set_title("Closing price")
ax.set_xlabel("Date")
ax.set_ylabel("Price (USD)")
print(ax.get_title(), "|", ax.get_xlabel(), "|", ax.get_ylabel()) # Closing price | Date | Price (USD)
print(len(ax.lines), ax.lines[0].get_ydata().tolist()) # 1 [100.0, 102.0, 101.0, 105.0, 107.0, 106.0, 110.0]Labels and the legend
With several lines on one chart, each needs a name. Pass label="..." to ax.plot and call ax.legend() once at the end: the legend lists the labels in the order the lines were drawn. A chart without labels, a title and units is a picture, not a result. Lines also have style: linestyle="--" makes a dashed line, handy for an average that should not hide the prices, and color, linewidth and marker refine it further.
ma = prices.rolling(3).mean()
ax.plot(ma.index, ma.values, label="MA3", linestyle="--")
ax.legend()
print([line.get_label() for line in ax.lines]) # ['AAPL', 'MA3']
print([text.get_text() for text in ax.get_legend().get_texts()]) # ['AAPL', 'MA3']
print(ax.lines[1].get_linestyle(), ax.lines[1].get_ydata().tolist()[:3]) # -- [nan, nan, 101.0] NaN points are simply not drawnReference lines, and charts you cannot see
A horizontal line marks a level such as a strike, a stop or the average: ax.axhline(105, linestyle="--") draws it across the whole chart (axvline does the same vertically). Matplotlib can also draw with no window at all: with the Agg backend, fig.savefig("chart.png") writes the chart to a file, which is how reports and servers produce charts. Because everything you draw stays on the Axes object, a program (or a test) can read it back: lines with ax.lines, their data with get_ydata(), labels with get_label(), the title with get_title().
ax.axhline(105, linestyle=":", label="level")
level_line = ax.lines[-1]
print(list(level_line.get_ydata()), level_line.get_linestyle(), level_line.get_label()) # [105, 105] : level
fig.savefig("prices.png") # writes the chart to a file; no window, no plt.show() needed
plt.close(fig) # release the figure when you are done with itdef plot_prices(prices):
fig, ax = plt.subplots()
ax.plot(prices.index, prices.values)
ax.set_title("Closing price")
return ax # the caller can add lines, save the chart or inspect it
ax = plot_prices(prices)
print(len(ax.lines), ax.get_title()) # 1 Closing price
plt.close("all")plt.subplots() keeps its figure in memory until you call plt.close(fig). In a loop that draws hundreds of charts, or in a server, forgetting this leaks memory and eventually triggers a warning. Create the Figure inside the function, return what the caller needs and let the caller close it.fig, ax = plt.subplots()thenax.plot(x, y); name the chart withset_title,set_xlabelandset_ylabel.- Give each line a
label=and callax.legend();linestyle="--"draws a dashed line;ax.axhline(y)a horizontal one. - Draw on an Axes and return it:
ax.lines,get_ydata()andget_label()read the chart back; usesavefigandplt.closeinstead ofshowin programs.
Next up: candlestick charts, drawing open, high, low and close for each day.
Lab · what's graded
- Draw the price line with
plot_prices25 pts - Compare two series on one chart with
plot_comparison25 pts - Add a dashed moving average with
plot_with_average25 pts - Mark a price level with
plot_with_level25 pts