Wednesday, October 30, 2019

QuantLib-Python: Note on ForwardCurve Construction

In this post, we will construct QuantLib ForwardCurve instance and investigate the resulting term structure of discount factors. Python program and Excel can be downloaded from my GitHub page. First, let us assume the following market data containing [A] one cash deposit and three interpolated rates from futures strip.


















After completing bootstrapping procedure, we should get [B] simple spot discount factors and [C] simple forward rates. Next, let us implement this scheme in QuantLib. Import QuantLib libraries and create relevant date objects. Then, set forward rates (from [C]), dates and create QuantLib forward curve instance.

import QuantLib as ql
today = ql.Date(30, 10, 2019)
ql.Settings.instance().evaluationDate = today  
settlementDate = ql.TARGET().advance(today, ql.Period(2, ql.Days))

rts = [0.03145, 0.03145, 0.0278373626373627, 0.0253076923076923, 0.0249373626373629]
dts = [settlementDate, ql.Date(1,2,2020), ql.Date(1,5,2020), ql.Date(1,8,2020), ql.Date(1,11,2020)]
c = ql.ForwardCurve(dts, rts, ql.Actual360(), ql.NullCalendar(), ql.BackwardFlat())
df = [c.discount(d) for d in dts]
print(df)

We get the following set of spot discount factors from the curve instance.
[1.0, 0.9919949898918935, 0.9851153255552918, 0.9787646299045335, 0.9725469122728971]
Note, that these are not what we expected to see. "The correct ones" are calculated in the column [B] in the example above. The issue here is, that when constructing ForwardCurve object, the given forward rates inputs are implicitly defined as continuously compounded rates. Assuming we would like to get the same discount factor term structure as in our example above in [B], we need to convert our forward rate inputs from simple to continuously compounded rates. Let us implement this in QuantLib. Convert simple rates to continuous rates. Then, reset forward rates and re-create QuantLib forward curve instance.

for i in range(len(rts) - 1):
    r_simple = ql.InterestRate(rts[i + 1], ql.Actual360(), ql.Simple, ql.Once)
    t = ql.Actual360().yearFraction(dts[i], dts[i + 1])
    r_continuous = r_simple.equivalentRate(ql.Continuous, ql.NoFrequency, t)
    rts[i + 1] = r_continuous.rate()
    
# set rate for the first node
rts[0] = rts[1]
c = ql.ForwardCurve(dts, rts, ql.Actual360(), ql.NullCalendar(), ql.BackwardFlat())
df = [c.discount(d) for d in dts]
print(df)

We get the following "correct" set of spot discount factors from the curve instance.
[1.0, 0.9920268596783518, 0.9851707210231435, 0.9788400520709886, 0.9726415228427708]
Inputs (dates and forward rates) for ForwardCurve object are defined in yellow box in column [E] below.
















ForwardCurve object interpolation scheme is backward flat. This means, that for the final period (from 1-Aug-20 to 1-Nov-20), forward rate of 2.48582% will be applied. Implicitly this means, that there is no practical use for the first given forward rate (at the node, where the date is 1-Nov-19). However, this rate has to be defined anyway.

Note, that QuantLib logic of constructing ForwardCurve object is completely correct. One should just be aware of the fact, that input forward rates given are handled implicitly as continuously compounded rates.

Finally, there are some other types of issues concerning forward rate interpolation, which might be worth of knowing when working with ForwardCurve object. These issues are fully explained in this video given by QuantLib lead developer Luigi Ballabio.

As always, thanks for reading this blog.
-Mike

2 comments:



  1. Greeting and Thanks from Sydney Australia. I haven't looked at Quantlib for a long time and now starting to use it with Python.
    Your blog site is extremely useful for someone who is getting back to Quantlib and starting with Python.
    Thanks for sharing the code.

    Thusitha

    ReplyDelete
  2. How do you get the correct first forward though? In my code it seems to skip the first simple forward all the time...

    ReplyDelete