Wednesday, 23 September 2015

Importing a yield curve into QuantLib with quantlib-python

The purpose of the exercise was to explore importing a predefined curve into QuantLib, and then to observe the output and check if we get what we expect. We use the ZeroCurve class (which the reference documentation says is "Term structure based on linear interpolation of zero yields class"). To import a zero yield curve into python one needs to create two aligned lists of dates and rates. By default the class will accept continuous rates, so anything different must be specified. In our case we are importing annual yields, so we set compoundingFrequency = ql.Annual




import pandas as pd
import numpy as np
import QuantLib as ql
import datetime as dt


calendar = ql.SouthAfrica()
bussiness_convention = ql.Following
day_count = ql.Actual365Fixed()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Annual
calc_date = ql.Date(18, 9, 2015) 
curve_date = dt.datetime(2015,9,18)
today = calc_date#ql.Date(8,ql.October, 2014)
#ql.Settings.instance().evaluationDate = calc_date

def to_datetime(d):
    return dt.datetime(d.year(),d.month(), d.dayOfMonth())
#the data from predefined curve

curve_data=pd.DataFrame(np.array([(0L, 1442534400000000000L, 1L, 0.05968943361108825, 'DAY', 1L),
       (1L, 1442534400000000000L, 3L, 0.06008360555190584, 'DAY', 3L),
       (2L, 1442534400000000000L, 7L, 0.061003962665558964, 'WEEK', 1L),
       (3L, 1442534400000000000L, 14L, 0.061629138095260494, 'WEEK', 2L),
       (4L, 1442534400000000000L, 21L, 0.06246539834717324, 'WEEK', 3L),
       (5L, 1442534400000000000L, 30L, 0.063080497435273, 'MONTH', 1L),
       (6L, 1442534400000000000L, 61L, 0.0640837249655406, 'MONTH', 2L),
       (7L, 1442534400000000000L, 91L, 0.06458932758091618, 'MONTH', 3L),
       (8L, 1442534400000000000L, 122L, 0.06460602877745236, 'MONTH', 4L),
       (9L, 1442534400000000000L, 153L, 0.06490097928752325, 'MONTH', 5L),
       (10L, 1442534400000000000L, 182L, 0.06562196294577327, 'MONTH', 6L),
       (11L, 1442534400000000000L, 213L, 0.06559317254771013, 'MONTH', 7L),
       (12L, 1442534400000000000L, 243L, 0.06615484763684432, 'MONTH', 8L),
       (13L, 1442534400000000000L, 274L, 0.06687832021278495, 'MONTH', 9L),
       (14L, 1442534400000000000L, 304L, 0.06683739890814766, 'MONTH', 10L),
       (15L, 1442534400000000000L, 335L, 0.06722706850014037, 'MONTH', 11L),
       (16L, 1442534400000000000L, 366L, 0.06799498188697228, 'MONTH', 12L),
       (17L, 1442534400000000000L, 457L, 0.06903328142506937, 'MONTH', 15L),
       (18L, 1442534400000000000L, 547L, 0.07012859614243028, 'MONTH', 18L),
       (19L, 1442534400000000000L, 639L, 0.0711421402251855, 'MONTH', 21L),
       (20L, 1442534400000000000L, 731L, 0.07215610320058774, 'MONTH', 24L),
       (21L, 1442534400000000000L, 822L, 0.07306913034489537, 'MONTH', 27L),
       (22L, 1442534400000000000L, 912L, 0.07390974864236854, 'MONTH', 30L),
       (23L, 1442534400000000000L, 1004L, 0.0746912071699184, 'MONTH', 33L),
       (24L, 1442534400000000000L, 1096L, 0.07543012070847732, 'MONTH', 36L),
       (25L, 1442534400000000000L, 1187L, 0.07612842034647893, 'MONTH', 39L),
       (26L, 1442534400000000000L, 1277L, 0.07679447188377297, 'MONTH', 42L),
       (27L, 1442534400000000000L, 1369L, 0.07744248361696626, 'MONTH', 45L),
       (28L, 1442534400000000000L, 1461L, 0.07805149575772585, 'MONTH', 48L),
       (29L, 1442534400000000000L, 1552L, 0.07862397558955214, 'MONTH', 51L),
       (30L, 1442534400000000000L, 1643L, 0.07917584728234361, 'MONTH', 54L),
       (31L, 1442534400000000000L, 1735L, 0.079709603821418, 'MONTH', 57L),
       (32L, 1442534400000000000L, 1827L, 0.08021643040257698, 'MONTH', 60L),
       (33L, 1442534400000000000L, 1918L, 0.08069118300778322, 'MONTH', 63L),
       (34L, 1442534400000000000L, 2008L, 0.08114184287539494, 'MONTH', 66L),
       (35L, 1442534400000000000L, 2100L, 0.08159085035229752, 'MONTH', 69L),
       (36L, 1442534400000000000L, 2192L, 0.08204424485907724, 'MONTH', 72L),
       (37L, 1442534400000000000L, 2283L, 0.08249064630693725, 'MONTH', 75L),
       (38L, 1442534400000000000L, 2373L, 0.08292334366174559, 'MONTH', 78L),
       (39L, 1442534400000000000L, 2465L, 0.08336529417751093, 'MONTH', 81L),
       (40L, 1442534400000000000L, 2557L, 0.0837551643848149, 'MONTH', 84L),
       (41L, 1442534400000000000L, 2648L, 0.08409983640404173, 'MONTH', 87L),
       (42L, 1442534400000000000L, 2738L, 0.08441573153982995, 'MONTH', 90L),
       (43L, 1442534400000000000L, 2830L, 0.08472115832909055, 'MONTH', 93L),
       (44L, 1442534400000000000L, 2922L, 0.08503216237171274, 'MONTH', 96L),
       (45L, 1442534400000000000L, 3013L, 0.08535856504021955, 'MONTH', 99L),
       (46L, 1442534400000000000L, 3104L, 0.08568946114081943, 'MONTH', 102L),
       (47L, 1442534400000000000L, 3196L, 0.08601312798025784, 'MONTH', 105L),
       (48L, 1442534400000000000L, 3288L, 0.08631201206897576, 'MONTH', 108L),
       (49L, 1442534400000000000L, 3379L, 0.08657921856932438, 'MONTH', 111L),
       (50L, 1442534400000000000L, 3469L, 0.08682568429113857, 'MONTH', 114L)], 
      dtype=[('index', '<i8'), ('valDate', '<M8[ns]'), ('tDays', '<i8'), ('SWAP', '<f8'), ('Period', 'O'), ('Periods', '<i8')]))

#We must make QuantLib date objects
dicPeriod={'DAY':ql.Days,'WEEK':ql.Weeks,'MONTH':ql.Months,'YEAR':ql.Years}
curve_data['qlvalDate']= curve_data.apply(lambda row :ql.DateParser.parseISO(pd.to_datetime(row['valDate']).strftime('%Y-%m-%d')),axis=1)
curve_data['qlQuote']= curve_data.apply(lambda row :ql.SimpleQuote(row['SWAP']),axis=1)
curve_data['qlDate']=curve_data.apply(lambda row :row['qlvalDate'] + ql.Period(int(row['Periods']),dicPeriod[row['Period']]),axis=1)
curve_data['Date']=curve_data.apply(lambda row :to_datetime(row['qlDate']),axis=1)

#Create Curve as a QuantLib object
zc = ql.ZeroCurve(curve_data.qlDate.values,curve_data.SWAP.values,ql.Actual365Fixed(),calendar, interpolation,compounding, compoundingFrequency)
# write back into the dataframe
curve_data['qlRates']=zc.zeroRates()
curve_data.set_index('Date')[['SWAP','qlRates']].plot()

The Plot

We can see that the QuantLib curve is below our input curve as it is a continuous rate. We can check this by running the following code, and we now see that they are the same.

curve_data['qlRates']=(np.exp(curve_data.qlRates)-1)
curve_data.set_index('Date')[['SWAP','qlRates']].plot()

Final Plot



3 comments:

  1. Thanks, Charles, I found this post helpful as a guideline for what I'm trying to do (import a yield curve from Bloomberg to QL). Two questions: 1) What are the 'valDate' and 'Periods' datatypes QL requires used for? 2) Why are you specifying the 'L' (in your 'index' input for example) if this is not C++?

    ReplyDelete
  2. Hi jr

    1.1) valDate is the date for which the yield curve is applicable
    1.2) Period is Weeks, months etc. for a certain number of these periods with reference the valDate in 1.1 above.
    2) "L" is just telling numpy that the text is a long int.

    Hope this helps,
    Charles

    ReplyDelete
  3. Hey There. I found your blog using msn. This is an extremely well written article. I’ll be sure to bookmark it and return to read more of your useful info. Thanks for the post. I’ll definitely comeback. https://python.engineering/python-os-path-realpath-method/

    ReplyDelete