Visualizing Historical Stock Prices and Volume from Scratch

Trifunovic Uros
Analytics Vidhya
Published in
7 min readMar 23, 2021
Photo by M. B. M. on Unsplash

One of the main investing principles is that past performance is not an indicator of future performance. However, it is good to look at the historical price and volume charts to get a sense of the range a stock has been trading in, notice trends and patterns, and locate the price levels at which investors are particularly active.

The article provides a step-by-step guide to visualizing historical stock prices and volume using Jupyter Notebook and Python’s requests, pandas, and matplotlib libraries. Check out the detailed instructions for installing Jupyter Notebook and using the listed libraries by clicking on them.

By the time you finish reading the article, you will know how to:

  • Get historical stock data using a web API
  • Prepare the data for the analysis
  • Visualize price, volume, and moving averages

First, we import the required libraries and assign them the standard aliases.

import requests
import pandas as pd
import matplotlib.pyplot as plt

We begin by writing a function that takes a symbol as an input and returns a dictionary of stock information like date, closing price, and volume. To get the data using the Alpha Vantage API, you need to get a free API key. It is straightforward, and you can do it in no time by filling out a couple of fields on this link.

The breakdown of the function is as follows:

  • First, we pass the link to the Alpha Vantage website and store it as “url.”
  • Second, we create the “params” dictionary that we will pass as part of our request to get the API's data. The dictionary specifies that we are interested in all the historical daily stock prices for a given symbol. Remember to change the value of the “apikey” variable to your API key.
  • Now that we have the URL and the parameters in place, we request the Alpha Vantage data using the API. We also initialize an empty dictionary to store the data that we conveniently call “stock_dict.”
  • Our request returns a response that we iterate through using a “for” loop. The loop skips the metadata that we are not interested in and focuses on the historical information. For every record in the response, the loop updates the “stock_dict” dictionary accordingly.
  • Once the loop completes, the function returns a dictionary with the stock information we requested.
def get_daily_stock_info(symbol):
url = 'https://www.alphavantage.co/query?'
params = {
'function' : 'TIME_SERIES_DAILY_ADJUSTED',
'symbol' : symbol,
'outputsize' : 'full',
'apikey' : 'YOUR API KEY'
}
response = requests.get(url, params=params)
stock_dict = {'Date': [], 'Close': [], 'Volume': []}
for key,value in response.json().items():
if key != 'Meta Data':
stock_dict['Date'] = list(value.keys())
for info in value.values():
stock_dict['Close'].append(info['4. close'])
stock_dict['Volume'].append(info['6. volume'])

return(stock_dict)

Then, we let the user type in the ticker of interest. The “input ” function returns a string by default, and the upper method ensures we store the ticker value in the industry-standard capital letters.

symbol = input('Input a ticker:\n').upper()
stock_df = pd.DataFrame(get_daily_stock_info(symbol))

Next, we call our function, pass the symbol as a parameter, and store the result in a pandas DataFrame. As a result, the data will be nicely formatted in a table-like frame that resembles an Excel spreadsheet.

The first five rows of the stock_df DataFrame

At this point, the problem is that our data is not of the appropriate data type as all the columns are of type “object.” We adjust the problem with the below line of code. The “copy=False” parameter makes the transformation on the original data frame rather than making a copy of it and changing it there.

stock_df = stock_df.astype({'Date': 'datetime64[ns]', 'Close': 'float', 'Volume': 'int'}, copy=False)

We confirm our data is in the correct format and ready for visualization.

The output of stock_df.dtypes

Plotting multiple charts might lead to repeating some of the steps for each plot. Therefore, it is good to store some of the steps in a dictionary or create a function. Below, we store the colors we will be using for our plots and formatting for tickers and titles in a few variables. Colorhexa is a decent option for finding the hex codes for the colors you like. Pick your favorites, store them in a dictionary, and use them in your visuals.

colors = {'red': '#ff207c', 'grey': '#42535b', 'blue': '#207cff', 'orange': '#ffa320', 'green': '#00ec8b'}config_ticks = {'size': 14, 'color': colors['grey'], 'labelcolor': colors['grey']}config_title = {'size': 18, 'color': colors['grey'], 'ha': 'left', 'va': 'baseline'}

Now we are ready to visualize the historical price and volume stock data.

def get_charts(stock_data):
plt.rc('figure', figsize=(15, 10))

fig, axes = plt.subplots(2, 1,
gridspec_kw={'height_ratios': [3, 1]})
fig.tight_layout(pad=3)

date = stock_data['Date']
close = stock_data['Close']
vol = stock_data['Volume']

plot_price = axes[0]
plot_price.plot(date, close, color=colors['blue'],
linewidth=2, label='Price')

plot_vol = axes[1]
plot_vol.bar(date, vol, width=15, color='darkgrey')

The function above takes our stock DataFrame as an input and plots stock price and volume data.

  • First, it creates a figure to place the plots. It adjusts the figure size and sets the price chart's height to be three times that of the volume chart.
  • Second, it extracts the date, closing price, and volume data from the stock DataFrame and stores them in variables for further reference.
  • Third, it creates a price plot with dates on the x-axis and closing prices on the y-axis. Additionally, it adjusts the line color and size.
  • Fourth, it creates a bar plot with dates on the x-axis and volume on the y-axis with grey bars.

Since historical prices and volume start with the earliest date on the left to the most recent one on the right, it makes sense to move the y axis label position to the right. Furthermore, we make the price chart more legible by adding light grey gridlines and adjusting its ticks, labels, and color. Then, we repeat the process for the volume chart while making the necessary changes to the labels and axis.

def get_charts(stock_data):
...
plot_price.yaxis.tick_right()
plot_price.tick_params(axis='both', **config_ticks)
plot_price.set_ylabel('Price (in USD)', fontsize=14)
plot_price.yaxis.set_label_position("right")
plot_price.yaxis.label.set_color(colors['grey'])
plot_price.grid(axis='y', color='gainsboro',
linestyle='-', linewidth=0.5)
plot_price.set_axisbelow(True)

As a result, we get the chart below.

The left and top borders are unnecessary, and we remove them to make the visual cleaner. Since we do so for a couple of charts, we create a function that takes care of border formatting. The function will allow us to format the borders on any future plots easily. Additionally, we might want to make any changes in only one place rather than multiple times with every single chart.

def format_borders(plot):
plot.spines['top'].set_visible(False)
plot.spines['left'].set_visible(False)
plot.spines['left'].set_color(colors['grey'])
plot.spines['bottom'].set_color(colors['grey'])

Then, we add the “format_borders” function calls to the “get_charts” function.

def get_charts(stock_data):
...
format_borders(plot_price)
format_borders(plot_vol)

Now that the charts are cleaner, we title them by calling the “suptitle” method on the earlier-defined figure object and set its formatting.

fig.suptitle(symbol + ' Price and Volume', size=36, color=colors['grey'], x=0.24, y=1.10)

Additionally, it is helpful to see at first glance what was the closing price and the number of shares traded on the last trading day. We extract that information from “stock_df ”and use it as a title for the price chart.

def get_prev_day_info(plot):
previous_close='$' + str("{:,}".format(stock_df['Close'][0]))
previous_volume=str("{:,}".format(stock_df['Volume'][0]))
previous_date=str(stock_df['Date'][0].date())
plot.set_title(
'Closing price on ' + previous_date + ': ' +
previous_close + '\nShares traded on ' + previous_date +
': ' + previous_volume, fontdict=config_title, loc='left'
)
def get_charts(stock_data):
...
get_prev_day_info(plot_price)

These adjustments give us get the following chart.

From here, the possibilities are endless. For example, we could plot any technical indicator we are interested in or zoom into the specific period. Moving averages smooth out the sharp increases or decreases, with 50, 100, and 200-day averages used most frequently. We add a function to calculate these for us and add them to the price plot.

def plot_ma(plot, x, y):
mov_avg = {
'MA (50)': {'Range': 50, 'Color': colors['orange']},
'MA (100)': {'Range': 100, 'Color': colors['green']},
'MA (200)': {'Range': 200, 'Color': colors['red']}
}

for ma, ma_info in mov_avg.items():
plot.plot(
x, y.rolling(ma_info['Range']).mean(),
color=ma_info['Color'], label=ma, linewidth=2, ls='--'
)
def get_charts(stock_data):
...
plot_ma(plot_price, date, close)

Finally, we add a legend to the price chart to tell a line showing the price from the 50, 100, and 200-day moving averages.

def format_legend(plot):
plot_legend = plot.legend(loc='upper left',
bbox_to_anchor= (-0.005, 0.95), fontsize=16)
for text in plot_legend.get_texts():
text.set_color(colors['grey'])
def get_charts(stock_data):
...
format_legend(plot_price)

As a result, we get our final chart that visualizes historical prices, volume, and moving averages for a stock.

The code used to generate the chart is available on the following link. Play around with it, read the documentation, and get creative. Explore different technical indicators and try adding them to the visuals in a way that works for you the best.

Feel free to reach out with any questions and comments on LinkedIn.

Thank you for reading.

--

--

Trifunovic Uros
Analytics Vidhya

MSBA Data Analytics student at Zicklin School of Business