Basics of OHLC charts with Python’s Matplotlib | by Thiago Carvalho | Towards Data Science

With a history going back to the 18th century, Open-High-Low-Close (OHLC) charts are among the most popular financial analysis tools, typically used to illustrate stock prices over time.
In this article, we’ll see how to build an OHLC chart with Matplotlib from scratch, explore the advantages and limitations of this visualization, and get a look at a more straightforward approach with mplfinance.
The chart is composed of a series of vertical lines that pack four critical variables about the price; The minimum, maximum, initial, and ending values over time, commonly measured in hours, days, or weeks.
OHLC is very similar to Candlestick charts as they both display the same information and are used to illustrate the price over time. Usually of a stock, currency, bond, commodity, and others.
They slightly differ in how they display the data; OHLC open price is always on the stick's left side and the close price on the right.
Candlesticks don’t have markers on the left or right. They have a box.
The box's filling represents the price's direction. Usually, a filled or red box means the price went down (Bearish market), so the open price is the rectangle's top part.
An empty or green box means the opposite (Bullish market), and the top part of the box is the closing price.
Let’s start building our OHLC chart. First, we’ll import the required libraries.
import pandas as pdimport matplotlib.pyplot as pltimport numpy as npimport math
The data in this example comes from a Kaggle dataset titled S&P 500 stock data.
df = pd.read_csv('../data/all_stocks_5yr.csv')df.head()
It’s an extensive dataset, and we won’t be plotting all this data at once, so before we start, let’s select a smaller subset.
# filter Apple stocksdf_apple = df[df['Name'] == 'AAPL'].copy()# convert date column from string to datedf_apple['date'] = pd.to_datetime(df_apple['date']) # filter records after 2017df_apple = df_apple[df_apple['date'].dt.year > 2017] df_apple.reset_index(inplace=True)
Now let’s plot the sticks. They should extend from the lowest to the highest price.
Instead of using the date as our x, we’ll create a NumPy array for it. That array will go from 0 to the length of our data frame. It’s easier to manipulate a numerical sequence, which will help position the sticks and the markers for open/close prices.
To draw the lines, we’ll iterate through our data frame, plotting a line for each record of data.
x = np.arange(0,len(df_apple))fig, ax = plt.subplots(1, figsize=(12,6))for idx, val in df_apple.iterrows():    plt.plot([x[idx], x[idx]], [val['low'], val['high']])plt.show()
Awesome, now we can add the markers.
x = np.arange(0,len(df_apple))fig, ax = plt.subplots(1, figsize=(12,6))for idx, val in df_apple.iterrows():    # high/low lines    plt.plot([x[idx], x[idx]],              [val['low'], val['high']],              color='black')    # open marker    plt.plot([x[idx], x[idx]-0.1],              [val['open'], val['open']],              color='black')    # close marker    plt.plot([x[idx], x[idx]+0.1],              [val['close'], val['close']],              color='black')plt.show()
There it is! It’s pretty easy to plot an OHLC chart with Matplotlib. Unlike candlesticks, you don’t need color or different fillings in the symbols to understand the visualization.
In its simplest form, this chart is readable and relatively straightforward.
They are not necessary but can take our visualization to another level.
We’ll add a variable with green color at the beginning of the loop; then we’ll add a condition to check if the open price was higher than the closing price; when true, we’ll change the color to red.
x = np.arange(0,len(df_apple))fig, ax = plt.subplots(1, figsize=(12,6))for idx, val in df_apple.iterrows():    color = '#2CA453'    if val['open'] > val['close']: color= '#F04730'    plt.plot([x[idx], x[idx]],              [val['low'], val['high']],              color=color)    plt.plot([x[idx], x[idx]-0.1],              [val['open'], val['open']],              color=color)    plt.plot([x[idx], x[idx]+0.1],              [val['close'], val['close']],              color=color)plt.show()
Adding color to our OHLC charts makes the past trends way more visible and our visualization more insightful.
Our visualization already looks great, but the x-axis is useless as it is.
Using a numerical sequence as our x helped drawing the lines and markers, but we can’t tell the dates like this.
We’ll use x to position our ticks and the dates as the labels. We also need to consider that if we print every date, our x-axis will be unreadable, so we’ll skip some values when plotting.
x = np.arange(0,len(df_apple))fig, ax = plt.subplots(1, figsize=(12,6))for idx, val in df_apple.iterrows():    color = '#2CA453'    if val['open'] > val['close']: color= '#F04730'    plt.plot([x[idx], x[idx]],              [val['low'], val['high']],              color=color)    plt.plot([x[idx], x[idx]-0.1],              [val['open'], val['open']],              color=color)    plt.plot([x[idx], x[idx]+0.1],              [val['close'], val['close']],              color=color)    # ticksplt.xticks(x[::3], df_apple.date.dt.date[::3])plt.show()
That’s great! Now we can add some styling to our visualization to make it more appealing.
x = np.arange(0,len(df_apple))fig, ax = plt.subplots(1, figsize=(12,6))for idx, val in df_apple.iterrows():    color = '#2CA453'    if val['open'] > val['close']: color= '#F04730'    plt.plot([x[idx], x[idx]], [val['low'], val['high']], color=color)    plt.plot([x[idx], x[idx]-0.1], [val['open'], val['open']], color=color)    plt.plot([x[idx], x[idx]+0.1], [val['close'], val['close']], color=color)    # ticksplt.xticks(x[::3], df_apple.date.dt.date[::3])ax.set_xticks(x, minor=True)# labelsplt.ylabel('USD')# gridax.xaxis.grid(color='black', linestyle='dashed', which='both', alpha=0.1)# remove spinesax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)# titleplt.title('Apple Stock Price', loc='left', fontsize=20)plt.show()
OHLC charts are a great starting point for analysis since they give us a decent overview on which we can build upon.
You can plot some predicted prices, confidence intervals, moving averages, trades volume, and many more variables and statistics to complement your visualization.
Building our visualization from scratch with Matplotlib gives us a lot of freedom.
x = np.arange(0,len(df_apple))fig, (ax, ax2) = plt.subplots(2, figsize=(12,8), gridspec_kw={'height_ratios': [4, 1]})for idx, val in df_apple.iterrows():    color = '#2CA453'    if val['open'] > val['close']: color= '#F04730'    ax.plot([x[idx], x[idx]], [val['low'], val['high']], color=color)    ax.plot([x[idx], x[idx]-0.1], [val['open'], val['open']], color=color)    ax.plot([x[idx], x[idx]+0.1], [val['close'], val['close']], color=color)    # ticks top plotax2.set_xticks(x[::3])ax2.set_xticklabels(df_apple.date.dt.date[::3])ax.set_xticks(x, minor=True)# labelsax.set_ylabel('USD')ax2.set_ylabel('Volume')# gridax.xaxis.grid(color='black', linestyle='dashed', which='both', alpha=0.1)ax2.set_axisbelow(True)ax2.yaxis.grid(color='black', linestyle='dashed', which='both', alpha=0.1)# remove spinesax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['top'].set_visible(False)ax2.spines['right'].set_visible(False)ax2.spines['left'].set_visible(False)# plot volumeax2.bar(x, df_apple['volume'], color='lightgrey')# get max volume + 10%mx = df_apple['volume'].max()*1.1# define tick locations - 0 to max in 4 stepsyticks_ax2 = np.arange(0, mx+1, mx/4)# create labels for ticks. Replace 1.000.000 by 'mi'yticks_labels_ax2 = ['{:.2f} mi'.format(i/1000000) for i in yticks_ax2]ax2.yaxis.tick_right() # Move ticks to the left side# plot y ticks / skip first and last values (0 and max)plt.yticks(yticks_ax2[1:-1], yticks_labels_ax2[1:-1])plt.ylim(0,mx) # titleax.set_title('Apple Stock Price\n', loc='left', fontsize=20)# no spacing between the subplotsplt.subplots_adjust(wspace=0, hspace=0)plt.show()
At the beginning of the article, I mentioned an easier way to plot OHLC charts. mplfinance is an excellent pack of utilities for financial visualizations.
pip install --upgrade mplfinance
Let’s take a look at how easy it is to use it.
The data frame should contain the fields: Open, Close, High, Low, and Volume. It also should have a DateTime index.
Our data has the proper fields, so we only need to change the index.
import mplfinance as mpfdf_google = df[df['Name'] == 'GOOGL'].copy()df_google['date'] = pd.to_datetime(df_google['date'])df_google = df_google[df_google['date'] > pd.to_datetime('2017-12-31')]df_google = df_google.set_index('date')mpf.plot(df_google)
Just like that, we have our OHLC chart!
We can also add lines for moving average and visualize the volume with a single line of code.
mpf.plot(df_google,type='candle',mav=(3, 5),volume=True, title='Google')
Matplotlib Finance utilities are easier to use than drawing the chart from scratch, but they are not easily customizable.
For most quick analysis, you need a functional and readable chart, and mplfinance will be more than enough.
For other more specific constraints, when you need to add a particular component to your visualization or customize the style to follow some design standards, mplfinance may fall short.
Those are the cases where drawing the chart from scratch with Matplotlib pays off. We can easily add, modify, or remove any part of the visualization, making it a great tool to have under your belt.
Thanks for reading my article. I hope you enjoyed it!
Check out more Python DataViz tutorials.
References:mplfinance;mplfinance — Styles and Customizations;
Matplotlib — Subplots Gridspec; Matplotlib —x-ticks, y-ticks; Matplotlib — Ticks right; Matplotlib — Spines; Matplotlib — Gridlines, W3;Matplotlib — Lines;Matplotlib — Bars;