Plotting Functions

Python has several plotting libraries. The three most widely used are:

We’ll use both Matplotlib and Seaborn throughout the course. Seaborn is built on top of Matplotlib, so they work together — importing Seaborn also improves the default appearance of Matplotlib plots. We import both along with NumPy.

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme()

Calling sns.set_theme() applies cleaner default styles to all subsequent plots, including Matplotlib ones.

Bond Prices and Interest Rates

Let’s reuse the NumPy function we defined earlier to value a semi-annual paying coupon bond.

def pv_bond_np(c, y, F, T):
    pv = (F * (c/2) / (y/2)) * (1 - 1 / (1 + (y/2))**(2*T)) + F / (1 + y/2)**(2*T)
    return np.round(pv, 2)

We will now generate a plot of bond prices for different interest rates. For this, ideally we would like to use many interest rates. In the previous lesson we typed the interest rates manually as a NumPy array. Luckily for us, NumPy has a built-in function called linspace that returns evenly spaced numbers over a specified interval.

The function below generates a NumPy array of 20 evenly spaced points ranging from 2% to 20%. We then store the array in a variable called rates.

rates = np.linspace(0.02, 0.20, 20)

We can now generate a NumPy array of bond prices with maturity 5 years and a coupon rate of 5% per year for each interest rate found in rates.

bond_prices_5 = pv_bond_np(0.05, rates, 1000, 5)

We can now generate a plot of bond prices vs. different interest rates.

plt.plot(100*rates, bond_prices_5)
plt.xlabel('Yield-to-maturity (%)')
plt.ylabel('Bond Price')
plt.title('Sensitivity of Bond Price to YTM')
plt.show()

A few notes on the code. We imported Matplotlib as plt, so all its functions are accessed via plt.. The function plot(x, y) draws a line. xlabel, ylabel, and title add axis labels and a title. plt.show() triggers the figure to render — required in Quarto to produce output.

The graph shows that bond prices are a decreasing function of interest rates. This makes sense since discounting at a higher rate reduces the present value of the cash flows. We can also see that the function is not linear but convex. Convexity is actually more pronounced on longer maturity bonds.

To see this visually, let’s compute prices for a 30-year bond with the same coupon rate, and plot them in the same graph as the 5-year bond prices.

bond_prices_30 = pv_bond_np(0.05, rates, 1000, 30)

To add an additional plot in our graph we just type plot again. And we can also add a legend to differentiate both plots.

plt.plot(100*rates, bond_prices_5, label='5 years')
plt.plot(100*rates, bond_prices_30, label='30 years')
plt.xlabel('Yield-to-maturity (%)')
plt.ylabel('Bond Price')
plt.legend()
plt.title('Sensitivity of Bond Price to YTM')
plt.show()

The picture shows that the 30-year bond plot is indeed more convex and has a more negative slope than the 5-year bond.

Seaborn

Seaborn is particularly useful for statistical plots. A common task in finance is visualizing the distribution of returns. Let’s simulate daily returns for a stock using a normal distribution and plot the result.

np.random.seed(42)
returns = np.random.normal(loc=0.001, scale=0.02, size=252)

Here loc is the mean daily return (0.1%) and scale is the daily standard deviation (2%), and size=252 gives us one year of trading days.

sns.histplot(returns, bins=30, kde=True)
plt.xlabel('Daily Return')
plt.ylabel('Count')
plt.title('Distribution of Simulated Daily Returns')
plt.show()

The kde=True argument adds a kernel density estimate — a smoothed curve that approximates the distribution. This is a common way to visualize return distributions and check whether they look approximately normal.

Seaborn is also useful for visualizing relationships between variables. For example, we can look at the relationship between two simulated stocks using a scatter plot.

np.random.seed(42)
stock1 = np.random.normal(0.001, 0.02, 252)
stock2 = 0.6 * stock1 + np.random.normal(0, 0.015, 252)

sns.scatterplot(x=stock1, y=stock2, alpha=0.5)
plt.xlabel('Stock 1 Return')
plt.ylabel('Stock 2 Return')
plt.title('Return Scatter Plot')
plt.show()

The two stocks are positively correlated by construction. The scatter plot makes this visible — when one stock has a positive day, the other tends to as well. Visualizing return co-movement is a first step toward understanding portfolio diversification.

Practice Problems

Problem 1 Generate a plot of \(f(x) = x^{2} - 3 x + 5\) for \(x \in [-5, 5]\) using 50 evenly spaced points for \(x\).

Solution

We start by defining the function \(f(x) = x^{2} - 3 x + 5\).

def f(x):
    y = x**2 - 3*x + 5
    return y

We then generate a NumPy array for \(x\) and compute the respective values for \(y = f(x)\).

x = np.linspace(-5, 5, 50)
y = f(x)

Finally we can plot the function.

plt.plot(x, y)
plt.show()