14.2. Risks#
Inflation risk
Interest rate fluctuations
Reinvestment risk
Rating, credit and defalut risks
…
14.2.1. Inflation risk#
Independently from any other mechanism, a change in inflation alters the real return of constant nominal yield bonds. Just as an example, if you buy a \(3\%\) nominal rate bond with expected inflation at \(2\%\), you’re aiming at \(1\%\) real return. If average inflation grows and remains constant at \(4\%\), you get a negative \(-1\%\) return.
Example 14.1 (Zero-coupon bond)
\(10\)-year \(r = 3\%\) nominal net yield bond, with expected inflation at target inflation \(i = i^* = 2\%\).
What’s the probability of getting at least the expected (and desired if you buy it?) real yield?
What’s the probability of getting negative real yield?
What’s the nominal net yield required to halve the probability of getting a real return lower than an expected \(1\%\) CAGR?
Show code cell source
%reset -f
import numpy as np
import matplotlib.pyplot as plt
#> Bond features
n_years = 10
r_nominal = .03
i_exp_1, i_sdev_1 = .02, .01
i_exp_2, i_sdev_2 = .02, .02
i_exp = [ i_exp_1, i_exp_2 ]
i_sdev = [ i_sdev_1, i_sdev_2 ]
#> Initial price
# r = (pend/pinit)**(1/years) - 1
bond_price_end = 100.
bond_price_ini = bond_price_end / ( 1. + r_nominal )**n_years
print(f"Final price: {bond_price_end: 6.3f}")
print(f"Initial price to get compound return r={r_nominal}: {bond_price_ini: 6.3f}")
#> Expected price index
pi_ini = 100.
pi_end = pi_ini * ( 1. + i_exp_1 )**n_years
#> N.realizations
n_reals = 10000
Final price: 100.000
Initial price to get compound return r=0.03: 74.409
Show code cell source
from functools import partial
#> Random number generator
i_rngs = [
partial(np.random.default_rng().normal, loc=i_exp_1, scale=i_sdev_1),
partial(np.random.default_rng().normal, loc=i_exp_2, scale=i_sdev_2),
]
#> Realizations of 1-year inflation
i_reals = [
i_rng(size=(n_years, n_reals)) for i_rng in i_rngs
]
#> Realization of price index: PI[n+1] = PI[n] * ( 1 + i[n] )
# ... np.cumprod(1 + i_reals, axis=0) * * pi_ini
#> Realization of real value of the investment
preal_reals = [
np.cumprod(1+r_nominal-i_real, axis=0) for i_real in i_reals
]
#> Geometric real returs after 10 years
r10_reals = [
( preal_real ** ( 1 / n_years ) - 1 ) * 100 for preal_real in preal_reals # Percentage
]
Show code cell source
# dbins = .20
# bins = np.arange(-3., 3, dbins) + 1.
# fig, ax = plt.subplots(2,1, figsize=(5,5))
# ax[0].hist(r10_reals[-1,:], bins=bins, density=True)
# # ax[0].plot([pi_end, pi_end], [0.,1.])
# ax[1].hist(r10_reals[-1,:], bins=bins, density=True, cumulative=True)
# # ax[1].plot([pi_end, pi_end], [0.,1.])
# ax[0].set_xlim(bins[0]-dbins, bins[-1]+dbins)
# ax[1].set_xlim(bins[0]-dbins, bins[-1]+dbins)
# # ax[0].set_ylim(0., .1)
# # ax[1].set_ylim(0., 1.)
# ax[0].set_title(f"Real CAGR after {n_years} years")
# ax[0].grid()
# ax[1].grid()
# ax[1].set_xlabel('Real CAGR')
Show code cell source
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.io as pio
pio.renderers.default = 'iframe' # plotly_mimetype+notebook' # or 'iframe'
dbins = .20
bins = np.arange(-3., 3, dbins) + 1.
# Create 2x1 subplots
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
subplot_titles=("Probability Density", "Cumulative Probability")
)
bin_centers = 0.5 * ( bins[:-1] + bins[1:] )
bin_widths = bins[1:] - bins[:-1]
for i_real, r10_real in enumerate(r10_reals):
values = r10_real[-1,:]
counts, _ = np.histogram(values, bins=bins)
probabilities = counts / counts.sum()
# Probability density histogram
fig.add_trace(
# go.Histogram(
# x=values,
# histnorm="probability density",
# nbinsx=20,
# name="PDF"
# ),
go.Bar(
x=bin_centers,
y=probabilities,
# width=bin_widths,
name=f"N({i_exp[i_real]}, {i_sdev[i_real]})",
marker_color=px.colors.qualitative.Plotly[i_real]
),
row=1,
col=1
)
# Cumulative probability histogram
fig.add_trace(
go.Histogram(
x=values,
histnorm="probability",
cumulative=dict(enabled=True),
# nbinsx=20,
showlegend=False,
xbins = dict(start=bins[0], end=bins[-1], size=dbins),
marker_color=px.colors.qualitative.Plotly[i_real]
),
row=2,
col=1
)
# Layout
fig.update_layout(
barmode='overlay',
height=600,
title="10Y Real CAGR",
bargap=0.1,
# showlegend=False
legend=dict(x=.99,y=.99,xanchor='right',yanchor='top')
)
fig.update_traces(opacity=.5)
fig.update_xaxes(title_text="CAGR, r", row=2, col=1)
fig.update_yaxes(title_text="p(r)", row=1, col=1)
fig.update_yaxes(title_text="P(r)", row=2, col=1)
fig.show()