import QuantLib as ql
Term structures and their reference dates
Independently of their specific type (interest rates, volatility, inflation or whatnot) the term structures implemented in the library share the management of their reference date; in particular, they can be set up so that they track (or don’t track) the global evaluation date. This notebook shows an example of this.
Let’s first import the QuantLib module and set up the global evaluation date. You might want to take note of the date, since we’ll be moving it around later on.
= ql.Date(3, ql.October, 2024) ql.Settings.instance().evaluationDate
Specifying the reference date of a term structure
In not-too-accurate terms, the reference date of a term structure is where it begins; it can be the evaluation date, but you might also want it to start on the spot date, for instance. When we create a term structure, we have two possibilities to specify its reference date—even though some particular classes only allow one of them.
The first is to define it by means of a (possibly null) offset from the current evaluation date; e.g., “two business days after the evaluation date” to define it as the spot date, or “no business days” to define it as the evaluation date itself. I’ll do it here by building a simple flat interest-rate curve: note the 0
and TARGET()
arguments, specifying the number of days and the calendar used to determine business days.
= ql.SimpleQuote(0.05) r
= ql.FlatForward(0, ql.TARGET(), ql.QuoteHandle(r), ql.Actual360()) curve1
The second possibility is to specify the reference date explicitly; in the cell below I use a second overload of the FlatForward
class takes a given date instead of the reference days and calendar. By pass the same date we set as the current evaluation date (October 3rd) we’re creating a curve which is the same as the first—at least for the time being.
= ql.FlatForward(
curve2 3, ql.October, 2024), ql.QuoteHandle(r), ql.Actual360()
ql.Date( )
We can check that both curves have the same reference date…
print(curve1.referenceDate())
print(curve2.referenceDate())
October 3rd, 2024
October 3rd, 2024
…and return the same discount factors, whether we ask for a given time (for instance, \(t=5\))…
print(curve1.discount(5.0))
print(curve2.discount(5.0))
0.7788007830714049
0.7788007830714049
…or for a given date: for instance, 5 years from the reference date, which will be different from the above because the corresponding time is greater than 5 for the Actual/360 day count convention we passed to the curve.
print(curve1.discount(ql.Date(3, ql.October, 2029)))
print(curve2.discount(ql.Date(3, ql.October, 2029)))
0.7759935186328056
0.7759935186328056
Moving the evaluation date
To recap: we built the first curve specifying its reference date relative to the evaluation date, and the second curve specifying its reference date explicitly. Now, what happens if we change the evaluation date?
= ql.Date(19, ql.September, 2024) ql.Settings.instance().evaluationDate
As you might expect, the reference date of the first curve changes accordingly while the referencec date of the second curve doesn’t.
print(curve1.referenceDate())
print(curve2.referenceDate())
September 19th, 2024
October 3rd, 2024
And of course the discount factors have changed, too…
print(curve1.discount(5.0))
print(curve2.discount(5.0))
0.7788007830714049
0.7788007830714049
…but only if we look at them in the right way. The whole curve has moved back a couple of weeks, so if we ask for a given time (that is, a given time from the reference) we’ll get the same discount factor; in other words, we’re asking for the discount factor over five years after the reference date, and that remains the same for a rigid translation of the curve. If we ask for the discount factor at a given date, though, we’ll see the effect:
print(curve1.discount(ql.Date(3, ql.October, 2029)))
print(curve2.discount(ql.Date(3, ql.October, 2029)))
0.7744861083592839
0.7759935186328056
Notifications
Finally, we can see that the two curves behave differently also with respect to notifications. Let’s make two observers: each of them will output a message when they receive a notification.
def make_observer(i):
def say():
= "Observer %d notified" % i
s print("-" * len(s))
print(s)
print("-" * len(s))
return ql.Observer(say)
= make_observer(1)
obs1 = make_observer(2) obs2
And now, let’s connect each of them to one of the curves. The first observer will receive notifications from the curve that moves with the evaluation date, and the second observer will receive notifications from the curve that doesn’t move.
obs1.registerWith(curve1) obs2.registerWith(curve2)
To check that notifications work, we can change the value of the quoted rate that we used to build both curves. Both observers should print their messages, and in fact, they do so twice. (That’s because the curves inherit from two different base classes, each one doing some work when notified and then forwarding the notification.)
0.03) r.setValue(
-------------------
Observer 1 notified
-------------------
-------------------
Observer 1 notified
-------------------
-------------------
Observer 2 notified
-------------------
-------------------
Observer 2 notified
-------------------
Instead, we can see what happens when the evaluation date changes again:
= ql.Date(23, ql.September, 2024) ql.Settings.instance().evaluationDate
-------------------
Observer 1 notified
-------------------
As you can see, only the moving curve sent a notification. The other did not, since it was not modified by the change of evaluation date.