Python Trading Toolbox: step up your charts with indicator subplots | by Stefano Basurto | Towards Data Science

After a several months-long hiatus, I can finally resume posting to the Trading Toolbox Series. We started this series by learning how to plot indicators (specifically: moving averages) on the top of a price chart. Moving averages belong to a wide group of indicators, called overlay indicators, that share the same scale as the price and can, therefore, be plotted on the same chart. Other technical indicators, however, do not have this advantage and we need to plot them on a separate area, sometimes called a subplot.
Here is an example of a stock chart with an indicator on a separate pane taken from Yahoo! Finance:
With this post, we want to explore how to create similar charts using Matplotlib. We also want to explore how to harness Matplotlib’s potential for customization and create original, publication-quality charts. To start with, we will learn how we can obtain that kind of subplot using Matplotlib. Then, we will apply that to plot our first technical indicator below the price, the Rate of Change (or ROC). We will then have a look at how to do that on an OHLC bar or Candlestick chart instead of a line chart.
At this stage we need to delve into some technical aspects of how Matplotlib works: That is how we can harness its multiple plots capabilities and craft publishing quality charts. All the code provided assumes that you are using Jupyter Notebook. If instead, you are using a more conventional text editor or the command line, you will need to add:
plt.show()
each time a chart is created in order to make it visible.
In the first two posts of this series we created our first financial price plots using the format:
plt.plot(dates, price, <additional parameters>)
You can see the first article on moving averages or the second on weighted and exponential moving averages.
When calling that method, matplotlib does a few things behind the scenes in order to create charts:
First, it creates an object called figure: this is the container where all of our charts are stored. A figure is created automatically and quietly, however, we can create it explicitly and access it when we need to pass some parameters, e.g. with the instruction: fig = plt.figure(figsize=(12,6))
On top of that, matplotlib creates an object called axes (do not confuse with axis): this object corresponds to a single subplot contained within the figure. Again, this action usually happens behind the scenes.
Within any figure, we can have multiple subplots (axes) arranged in a matrix:
When it comes to charts with multiple subplots, there are enough ways and methods available to make our head spin. We are going to pick just one: the .subplot() method will serve our purposes well. Through other tutorials, you may come across a method called .add_subplot(): the .subplot() method is a wrapper for .add_subplots() (that means it should make its use simpler). With the exception of a few details, their use is actually very similar.
Whenever we add a subplot to a figure we need to supply three parameters:
The number of rows in the chart matrix.
The number of columns.
The number of the specific subplot: you can note from the diagram above that the axes objects are numbered going left to right, then top to bottom.
Let us try to build a practical example of a generic 2x2 multiple plot chart:
Which gives us the following chart:
Now that we know how to create charts with multiple plots, we can apply our skills to plot an indicator at the bottom of a price chart. For this task, we are going to use an indicator known as Rate of Change (ROC). There are actually a few different definitions of ROC, and the one that we are going to employ for our example is based on the formula:
where lag can be any whole number greater than zero and represents the number of periods (on a daily chart: days) we are looking back to compare our price. E.g., when we compute the ROC of the daily price with a 9-day lag, we are simply looking at how much, in percentage, the price has gone up (or down) compared to 9 days ago. In this article we are not going to discuss how to interpret the ROC chart and use it for investment decisions: that should better have a dedicated article and we are going to do that in a future post.
We start by preparing our environment:
import pandas as pdimport numpy as npimport matplotlib.pyplot as plt# Required by pandas: registering  matplotlib date converterspd.plotting.register_matplotlib_converters()# If you are using Jupyter, use this to show the output images within the Notebook:%matplotlib inline
For this exercise, I downloaded from Yahoo! Finance daily prices for the Invesco QQQ Trust, an ETF that tracks the performance of the Nasdaq 100 Index. You can find here the CSV file that I am using. We can load our data and have a glimpse at it:
datafile = 'QQQ.csv'data = pd.read_csv(datafile, index_col = 'Date')# Converting the dates from string to datetime format:data.index = pd.to_datetime(data.index)data
Which looks like:
We can then compute a ROC series with a 9-day lag and add it as a column to our data frame:
lag = 9data['ROC'] = ( data['Adj Close'] / data['Adj Close'].shift(lag) -1 ) * 100data[['Adj Close', 'ROC']]
Which outputs:
To make our example charts easier to read, we use only a selection of our available data. Here is how we select the last 100 rows, corresponding to the 100 most recent trading days:
data_sel = data[-100:]dates = data_sel.indexprice = data_sel['Adj Close']roc = data_sel['ROC']
We are now ready to create our first multiple plot chart, with the price at the top and the ROC indicator at the bottom. We can note that, compared to a generic chart with subplots, our indicator has the same date and time (the horizontal axis) as the price chart:
fig = plt.figure(figsize=(12,10))# The price subplot:price_ax = plt.subplot(2,1,1)price_ax.plot(dates, price)# The ROC subplot shares the date axis with the price plot:roc_ax = plt.subplot(2,1,2, sharex=price_ax)roc_ax.plot(roc)# We can add titles to each of the subplots:price_ax.set_title("QQQ  - Adjusted Closing Price")roc_ax.set_title("9-Day ROC")
We have just plotted our first chart with price and ROC in separate areas: this chart does its job in the sense that it makes both price and indicator visible. It does not, however, do it in a very visually appealing way. To start with, price and ROC share the same time axis: there is no need to apply the date labels again on both charts. We can remove them from the top chart by using:
price_ax.get_xaxis().set_visible(False)
We can also remove the gap between the two subplots with:
fig.subplots_adjust(hspace=0)
It is also a good idea to add a horizontal line at the zero level of the ROC to make it more readable, as well as to add labels to both vertical axes.
fig = plt.figure(figsize=(12,10))price_ax = plt.subplot(2,1,1)price_ax.plot(dates, price, label="Adj Closing Price")price_ax.legend(loc="upper left")roc_ax = plt.subplot(2,1,2, sharex=price_ax)roc_ax.plot(roc, label="9-Day ROC", color="red")roc_ax.legend(loc="upper left")price_ax.set_title("QQQ Daily Price")# Removing the date labels and ticks from the price subplot:price_ax.get_xaxis().set_visible(False)# Removing the gap between the plots:fig.subplots_adjust(hspace=0)# Adding a horizontal line at the zero level in the ROC subplot:roc_ax.axhline(0, color = (.5, .5, .5), linestyle = '--', alpha = 0.5)# We can add labels to both vertical axis:price_ax.set_ylabel("Price ($)")roc_ax.set_ylabel("% ROC")
This chart looks already better. However, Matplotlib offers a much greater potential when it comes to creating professional-looking charts. Here are some examples of what we can do:
To enhance the readability of the ROC indicator, we can fill the areas between the plot and the horizontal line. The .fill_between() method will serve this purpose.
We can format the date labels to show, for example, only the name of the month in short form (e.g., Jan, Feb, ...).
We can use a percent format for the labels on the ROC vertical axis.
We can add a grid to both subplots and set a background color.
Increase the margins (padding) between the plot and the borders.
In order to maximize the chart’s Data-Ink Ratio, we can remove all the spines (the borders around the subplots) and the tick marks on the horizontal and vertical axis for both subplots.
We can also set the default size for all the fonts to a larger number, say 14.
That is quite a long list of improvements. Without getting lost too much in the details, the following code should provide a good example:
Which gives:
This chart provides a taster of what we can achieve by manipulating the default Matplotlib parameters. Of course, we can always achieve some visual improvements by applying an existing style sheet as we have done in the first article of this series, e.g. with:
plt.style.use('fivethirtyeight')
When using indicator subplots, most of the time we want the price section to take a larger area than the indicator section in the chart. To achieve this we need to manipulate the invisible grid that Matplotlib uses to place the subplots. We can do so using the GridSpec function. This is another very powerful feature of Matplotlib. I will just provide a brief example of how it can be used to control the height ratio between our two subplots:
Which outputs:
As a side note, you may notice how this chart retained 14 as the font size set from the previous chart’s code. This happens because any changes to the rcParams Matplotlib parameters are permanent (until we restart the system). If we need to reset them we can use:
plt.style.use('default')
and add %matplotlib inline if we are using Jupyter Notebook.
In all of the previous examples, we charted the price as a line plot. Line plots are a good way to visualize prices when we have only one data point (in this case, the closing price) for each period of trading. Quite often, with financial price series, we want to use OHLC bar charts or Candlestick charts: those charts can show all the prices that summarize the daily trading activity (Open, High, Low, Close) instead of just Close.
To plot OHLC bar charts and candlestick charts in Matplotlib we need to use the mplfinance library. As I mentioned in the previous post of this series, mplfinance development has gathered new momentum and things are rapidly evolving. Therefore, we will deal only cursorily on how to use it to create our charts.
Mplfinance offers two methods to create subplots within charts and add an indicator:
With the External Axes Method creating charts is more or less similar to what we have done so far. Mplfinance takes care of drawing the OHLC or candlesticks charts. We can then pass an Axes object with our indicator as a separate subplot.
The Panels Method is even easier to use than pure Matplotlib code: mplfinance takes control of all the plotting and styling operations for us. To manipulate the visual aspects of the chart we can apply existing styles or create our own style.
The External Axes Method was released less than a week ago from the time I am writing this. I am still looking forward to making use of it to find out what potential it can offer.
As a taster of what we can do using the mplfinance Panel Method, we can plot a candlestick chart with the volume in a separate pane:
import mplfinance as mpfmpf.plot(data_sel, type='candle',  style='yahoo', title="QQQ Daily Price", volume=True)
We can add our ROC indicator in a separate subplot too:
# We create an additional plot placing it on the third panelroc_plot = mpf.make_addplot(roc, panel=2, ylabel='ROC')#We pass the additional plot using the addplot parametermpf.plot(data_sel, type='candle',  style='yahoo', addplot=roc_plot, title="QQQ Daily Price", volume=True)
There are several packages out there that make it possible to create financial charts using Python and pandas. In particular, plotly stands out for its capability to create good looking interactive charts. Matplotlib, on the other hand, may not produce the best charts straight out of the box (look at seaborn if you need that) however, it has a huge customization potential that makes it possible to create static charts that can stand out in a professional publication.
That is why I believe it is well worth to get the grips on tweaking the properties of matplotlib. The future developments of mplfinance will make those possibilities even more appealing.