import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()Plotting Functions
Python has several plotting libraries. The three most widely used are:
- Matplotlib — the standard, with full control over every plot element
- Seaborn — built on Matplotlib, with better defaults for statistical plots
- Plotly — interactive charts for web output
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.
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 yWe 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()