3
$\begingroup$

I have been working on valuation of interest rate swaps using dual curve bootstrapping. And for this I use OISRateHelper to create a discount term structure using OIS rates. The entire code below :

import QuantLib as ql
ql.IndexManager.instance().clearHistories()

import math
import numpy as np
import pandas as pd
import datetime as dt

#### INPUTS
date0 = dt.datetime.strptime("2012-07-05", "%Y-%m-%d")
date1 = dt.datetime.strptime("2012-07-06", "%Y-%m-%d")

calculation_dates = [ql.Date(date0.day,date0.month,date0.year), ql.Date(date1.day,date1.month,date1.year)]
fwd_start = 0

ois_mat = [ql.Period(x) for x in ['1D', '2W', '1M', '2M', '3M', '4M', '5M', '6M',
                                  '7M', '8M', '9M', '10M', '11M', '12M', '18M', '2Y',
                                  '30M', '3Y', '4Y', '5Y', '6Y', '7Y', '8Y', '9Y', '10Y',
                                  '11Y', '12Y', '15Y', '20Y', '25Y', '30Y', '35Y', '40Y',
                                  '50Y']]

ois_rates0 = [0.331, 0.162, 0.1525, 0.138, 0.136, 0.134, 0.133, 0.132, 0.135, 0.133, 0.134, 0.133, 0.134, 0.135, 0.146, 0.168, 0.197, 0.263, 0.419, 0.622, 0.8364, 1.006, 1.1625, 1.302, 1.429, 1.544, 1.64, 1.839, 1.93, 1.964, 1.999, 2.0465, 2.097, 2.1675] 
ois_rates1 = [0.329, 0.145, 0.134, 0.129, 0.13, 0.1235, 0.125, 0.145, 0.126, 0.12, 0.127, 0.122, 0.123, 0.125, 0.141, 0.165, 0.192, 0.253, 0.402, 0.595, 0.795, 0.976, 1.131, 1.27, 1.4049, 1.517, 1.611, 1.811, 1.901, 1.94, 1.963, 2.0265, 2.091, 2.173]

ois_dict0 = dict(zip(ois_mat, [x/100 for x in ois_rates0]))
ois_dict1 = dict(zip(ois_mat, [x/100 for x in ois_rates1]))

list_ois_rates_dict = [ois_dict0, ois_dict1]

deposit_mat = [ql.Period(1, ql.Months), ql.Period(3, ql.Months), ql.Period(6, ql.Months)]#, ql.Period(12, ql.Months)]

deposit_rates0 = [0.00362, 0.00641, 0.0092]
deposit_rates1 = [0.00255, 0.00549, 0.00831]

deposit_dict0 = dict(zip(deposit_mat, deposit_rates0))
deposit_dict1 = dict(zip(deposit_mat, deposit_rates1))

list_deposit_rates_dict = [deposit_dict0, deposit_dict1]

swap_mats = [ql.Period(x, ql.Years) for x in [1,2,3,4,5,6,7,8,9,10,12,15,20,25,30]]
swap_rates0 = [0.00515, 0.00806, 0.00883, 0.01029, 0.01213, 0.0139, 0.01544, 0.01677, 0.01793, 0.01897, 0.02073, 0.02232, 0.02279, 0.02293, 0.02307]
swap_rates1 = [0.00465, 0.00744, 0.00802, 0.00931, 0.01104, 0.01288, 0.0145, 0.01591, 0.01713, 0.01824, 0.02006, 0.0217, 0.02229, 0.02246, 0.02263]

swap_dict0 = dict(zip(swap_mats, swap_rates0))
swap_dict1 = dict(zip(swap_mats, swap_rates1))

list_swap_curve_rates_dict =  [swap_dict0, swap_dict1]

libor_rates_dict = deposit_dict0.copy()
swap_rates_dict = swap_dict0.copy()
swap_libor_tenors = [ql.Period(x, ql.Months) for x in [6,6,6,6,6,6,6,6,6,6,6,6,6,6,6]]
settlement_days = 0
face_value = 100
fixed_day_count = ql.Thirty360()
float_day_count = ql.Actual360()
ois_day_count = ql.Actual360()

calendar = ql.TARGET()
list_fixed_coupon_frequency = [ql.Annual for x in range(len(swap_libor_tenors))]
currency = ql.EURCurrency()
spread = 0
business_convention = ql.ModifiedFollowing
date_generation = ql.DateGeneration.Forward
end_of_month = False




### CALCULATIONS

fixed_npv = []
float_npv = []
swap_npv = []
swap_fair_rate = []

ql.IndexManager.instance().clearHistories()
date_t0 = calculation_dates[0]
ql.Settings.instance().evaluationDate = date_t0

effective_start_date = calendar.advance(date_t0, settlement_days + fwd_start, ql.Days)

discount_term_structure = ql.RelinkableYieldTermStructureHandle()
forecast_term_structure = ql.RelinkableYieldTermStructureHandle()

oindex = ql.OvernightIndex("", settlement_days, currency, calendar, ois_day_count, discount_term_structure)

######################
t5 = dt.datetime.now()
######################

ois_quote_map = {}
ois_helpers_t0 = []
for r,m in zip(list_ois_rates_dict[0].values(), list_ois_rates_dict[0].keys()):
    quote = ql.SimpleQuote(r)
    helper= ql.OISRateHelper(settlement_days, m, ql.QuoteHandle(quote), oindex)
    ois_helpers_t0.append(helper)
    ois_quote_map[m]=quote

######################
t6 = dt.datetime.now()
######################

deposit_quote_map = {}
deposit_helpers_t0 = []
for  r,m in zip(list_deposit_rates_dict[0].values(), list_deposit_rates_dict[0].keys()):
    quote = ql.SimpleQuote(r)
    helper = ql.DepositRateHelper(ql.QuoteHandle(quote),m,settlement_days,calendar,
                                  business_convention,end_of_month,float_day_count)
    deposit_helpers_t0.append(helper)
    deposit_quote_map[m] = quote

swap_ibor_indices = [ ql.IborIndex("", x, settlement_days, currency, calendar, business_convention, False, float_day_count,
                                   forecast_term_structure) for x in swap_libor_tenors]

for sib in swap_ibor_indices:
    sib.addFixing(date_t0, libor_rates_dict[sib.tenor()])


swap_quote_map = {}
swap_helpers_t0 = []
for r,m, ibor_index,f  in zip(list_swap_curve_rates_dict[0].values(), list_swap_curve_rates_dict[0].keys(),
                                           swap_ibor_indices, list_fixed_coupon_frequency):
    quote = ql.SimpleQuote(r)
    helper = ql.SwapRateHelper(ql.QuoteHandle(quote),m,calendar,f,business_convention,fixed_day_count, ibor_index, 
                               ql.QuoteHandle(ql.SimpleQuote(0)),ql.Period(0, ql.Days), discount_term_structure,
                               settlement_days)

    swap_helpers_t0.append(helper)
    swap_quote_map[m] = quote

######################
t10 = dt.datetime.now()
######################


helpers_t0 = deposit_helpers_t0 + swap_helpers_t0

swap_curve_t0 = ql.PiecewiseLogCubicDiscount(settlement_days, calendar, helpers_t0, fixed_day_count)
discount_curve_t0 = ql.PiecewiseLogCubicDiscount(settlement_days, calendar, ois_helpers_t0, fixed_day_count)

swap_curve_t0.enableExtrapolation()
discount_curve_t0.enableExtrapolation()

discount_term_structure.linkTo(discount_curve_t0)
forecast_term_structure.linkTo(swap_curve_t0)

swap_engine = ql.DiscountingSwapEngine(discount_term_structure)

list_irs = []
for i in range(len(swap_mats)):
    swap_rate = list(swap_rates_dict.values())[i]
    fixed_tenor = ql.Period(list_fixed_coupon_frequency[i])
    float_tenor = swap_ibor_indices[i].tenor()
    fixed_schedule = ql.Schedule(effective_start_date, calendar.advance(effective_start_date, list(swap_rates_dict.keys())[i]),
                                 fixed_tenor, calendar,
                                 business_convention, business_convention,
                                 date_generation, end_of_month)

    float_schedule = ql.Schedule(effective_start_date, calendar.advance(effective_start_date, list(swap_rates_dict.keys())[i]),
                                 float_tenor, calendar,
                                 business_convention, business_convention,
                                 date_generation, end_of_month)

    irs_temp = ql.VanillaSwap(ql.VanillaSwap.Receiver, face_value, fixed_schedule, swap_rate, fixed_day_count,
                              float_schedule, swap_ibor_indices[i], spread, float_day_count)

    irs_temp.setPricingEngine(swap_engine)

    if fwd_start > 0:
        fair_swap_rate = irs_temp.fairRate()
        irs = ql.VanillaSwap(ql.VanillaSwap.Receiver, face_value, fixed_schedule, fair_swap_rate, fixed_day_count,
                             float_schedule, swap_ibor_indices[i], spread, float_day_count)
        irs.setPricingEngine(swap_engine)
        list_irs.append(irs)

    else:
        list_irs.append(irs_temp)



fixed_npv.append([x.fixedLegNPV() for x in list_irs])
float_npv.append([x.floatingLegNPV() for x in list_irs])
swap_npv.append([x.NPV() for x in list_irs])
swap_fair_rate.append([x.fairRate() for x in list_irs])



print("ois helpers on date0: " + str((t6 - t5)))
print("other helpers on date0: " + str((t10 - t6)))


######################
t19 = dt.datetime.now()
######################

tdelta = dt.timedelta()

if len(calculation_dates) > 1:
    for i in range(1, len(calculation_dates)):
        ######################
        t19_1 = dt.datetime.now()
        ######################  
        date_ti = calculation_dates[i]
        ql.Settings.instance().evaluationDate = date_ti
        ######################
        t19_2 = dt.datetime.now()
        ######################
        for k in list_ois_rates_dict[i].keys():
            ois_quote_map[k].setValue(list_ois_rates_dict[i][k])


        for k in list_deposit_rates_dict[i].keys():
            deposit_quote_map[k].setValue(list_deposit_rates_dict[i][k])

        for k in list_swap_curve_rates_dict[i].keys():
            swap_quote_map[k].setValue(list_swap_curve_rates_dict[i][k])

        fixed_npv.append([x.fixedLegNPV() for x in list_irs])
        float_npv.append([x.floatingLegNPV() for x in list_irs])
        swap_npv.append([x.NPV() for x in list_irs])
        swap_fair_rate.append([x.fairRate() for x in list_irs])


        ######################
        tdelta = tdelta + (t19_2 - t19_1)
        ######################        

######################
t20 = dt.datetime.now()
######################

print("Calculations for other dates: " + str((t20 - t19)))
print("Setting evaluation dates for other dates: " + str(tdelta))

I have calculated execution time for creating an instance of OISRateHelpers and compared them to execution time for creating instances of DepositRateHelpers and SwapRateHelpers.

ois helpers on date0: 0:00:00.299389
other helpers on date0: 0:00:00.000999

The output shows that there is a major difference between.

Also, when recalculating the swaps, on other dates (other than date0), it's the ql.Settings.instance().evaluationDate which takes up more time.

Calculations for other dates: 0:00:09.596271
Setting evaluation dates for other dates: 0:00:09.554356

From this thread, I have come to understand that setting new dates changes reference dates for all the instances declared before.

I am trying to understand :

  1. Why creating instances of OISRateHelpers takes longer than creating instances of SwapRateHelpers/DepositRateHelpers?

  2. The fact that changing evaluation dates is time consuming, is it possible that OISRateHelper is creating 'redundant' dependencies which require notifications for changes in evaluation dates as well? Thus making it time consuming when resetting evaluation dates in ql.Settings?

Thank you!

$\endgroup$

1 Answer 1

1
$\begingroup$

I sent the same question to the quantlib mailing list. And the solution was to set telescopicValueDates = True for OISRateHelpers.

OISRateHelper creates a schedule with lots of dates; this is not really necessary for the purpose of curve bootstrapping and you can avoid it by setting the parameter telescopicValueDates to true which will speed up things (without changing the resulting curve of course).

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.