Skip to content
All library documents

Simulating Hull–White Yield Curves from Short-Rate Paths

Article Quant Q&A · Author: Oamriotn

Summary

The document addresses how to obtain a simulated term structure from short-rate paths in the one-factor Hull–White model. Rather than providing formulas for calibrating theta and the A(t) and B(t) functions, the accepted answer proposes integrating each simulated short-rate path approximately to produce discount factors, then averaging those factors across paths to form a curve.

The example uses QuantLib to generate paths, numerically accumulate rates over time, and build a discount curve from the mean simulated discount factors. It compares that curve with the initial curve, reflecting the calibration property that the simulated average should reproduce the starting term structure. The example is a numerical illustration, not a derivation of the model functions, and its discretization and simulation choices affect accuracy; those details are not evaluated in the text.

Key ideas

  • Integrating a short-rate path over time gives an approximate discount factor along that path.
  • A simulated term structure can be estimated by averaging discount factors across paths.
  • The mean simulated curve should reproduce the model’s initial term structure.
  • The example constructs and compares discount curves numerically, without deriving theta or A(t) and B(t).

Tags

Full text
# Calibration of Theta, A(t) and B(t) of Hull White 1Factor model


# Calibration of Theta, A(t) and B(t) of Hull White 1Factor model












I’m simulating interest rates via the HullWhite One factor model. To simulate the short rate I’m using the code from the Quantlib Python Cookbook, chapter 15 and beyond (By Goutham Balaraman and Luigi Ballabio). The code can be found here:

The code generates the short rate. Unfortunately the code ends with having only the simulated short rate while my goal is to create a whole new term structure based on the simulated curve.

To create a term structure I need to calibrate theta based on the initial term structure. Besides that I also need to calculate A(t) and B(t) such that I’m able to create the term structure.

Questions: Does somebody have an algorithm to calculate theta, A(t) and B(t)?

## Answer by David Duarte (score 3)

https://quant.stackexchange.com/a/50729

You can check out here a blog post on simulating the yield term structure for the HullWhite model.

The basic idea is that once you have the paths for the short rate, you can simply integrate (approximately) the short rate throughout each path to obtain the discount factors.

The average of the simulations should match the initial term structure.

Here is an example based on that excellent blog post, with an improvement on execution time by vectorizing the integration.

```
import QuantLib as ql
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import copy 

nPaths = 500
years = 30

startDate = ql.Date(3, 12, 2018)
endDate = startDate + ql.Period(years, ql.Years)
tenor = ql.Period(1, ql.Days)
schedule = ql.MakeSchedule(startDate, endDate, tenor)

dates = [dt for dt in schedule]

times = [ql.Actual360().yearFraction(startDate, dt) for dt in dates]

curve = ql.YieldTermStructureHandle(ql.FlatForward(startDate, 0.04875825, ql.Actual365Fixed()))
reversionSpeed = 0.01
rateVolatility = 0.001
process = ql.HullWhiteProcess(curve, reversionSpeed, rateVolatility)

periods = (endDate - startDate) + 1
sequenceGenerator = ql.UniformRandomSequenceGenerator(periods, ql.UniformRandomGenerator())
gaussianSequenceGenerator = ql.GaussianRandomSequenceGenerator(sequenceGenerator)
pathGenerator = ql.GaussianPathGenerator(process, years, periods, gaussianSequenceGenerator, False)

paths = np.zeros(shape = (nPaths, periods))
for i in range(nPaths):
    path = pathGenerator.next().value()
    paths[i, :] = np.array([path[j] for j in range(periods)])
    
dfs = np.zeros(shape=paths.shape)
dt = years / len(schedule)
integral = 0
for j in range(periods):
    if j == 0:
        dfs[:, 0] = 1
        integral = copy.deepcopy(paths[:, 0])
    else:
        integral += paths[:, j]        
        dfs[:, j] += np.exp(-integral * dt)
    
simulatedCurve = ql.DiscountCurve(dates, dfs.mean(0), ql.Actual365Fixed(), ql.NullCalendar())
simulatedDfs = np.array([simulatedCurve.discount(dt) for dt in dates])
dfs_curve = np.array([curve.discount(dt) for dt in dates])

fig, ax = plt.subplots(1,2, figsize=(10,3))
plt.tight_layout()

ax[0].set_title('Discount Factors')
ax[0].plot(times, simulatedDfs, linestyle = 'dashed', label = 'simulated curve')
ax[0].plot(times, dfs_curve, linestyle = 'solid', label = 'original curve')
ax[0].legend()

ax[1].set_title('Paths')
ax[1].plot(paths.T, linewidth=0.5)
```

The output would be:

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.