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?

Hide 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
Hide 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
]
Hide 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')
Hide 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()

14.3. #