0
$\begingroup$

I'm trying to perform a bootstrap of a yield curve from deposit rates, futures, and swaps, and the interpolation is "blowing up" for the futures maturities being off by two orders of magnitude (100x). I'm not sure what I'm doing wrong, if anything, or if there's an incorrect assumption I'm making somewhere. I know some of the code is superfluous, such as having start and maturity dates for the three types if I'm also including explicit periods, but I didn't have much chance to clean it up. I should also note that I tried changing future_rate_val to be simply the price instead of 100-price, but I got a similarly erroneous result, which is somewhat troubling in and of itself. Any help would be appreciated.

import QuantLib as ql
from pandas import DataFrame
import matplotlib.pyplot as plt
import csv

def get_spot_rates(yieldcurve, day_count, calendar=ql.UnitedStates(), months=121):
    spots = []
    tenors = []
    ref_date = yieldcurve.referenceDate()
    calc_date = ref_date
    for month in range(0, months):
        yrs = month/12.0
        d = calendar.advance(ref_date, ql.Period(month, ql.Months))
        compounding = ql.Compounded
        freq = ql.Semiannual
        zero_rate = yieldcurve.zeroRate(yrs, compounding, freq)
        tenors.append(yrs)
        eq_rate = zero_rate.equivalentRate(day_count,compounding,freq,calc_date,d).rate()
        spots.append(eq_rate*100)
    return DataFrame(list(zip(tenors, spots)),columns=["Maturities","Curve"],index=['']*len(tenors))

""" Deposit Rates """
depo_starts = [ql.Date(1,9,2016),ql.Date(2,9,2016),ql.Date(6,9,2016),ql.Date(6,9,2016),ql.Date(6,9,2016)]
depo_maturities = [ql.Date(2,9,2016),ql.Date(6,9,2016),ql.Date(6,10,2016),ql.Date(7,11,2016),ql.Date(6,12,2016)]

depo_periods = [ql.Period(1,ql.Days),ql.Period(2,ql.Days),ql.Period(1,ql.Months),ql.Period(2,ql.Months),ql.Period(3,ql.Months)]

depo_rates = [0.45, 0.48, 0.51765, 0.6328, 0.8176]

""" Futures """
future_starts = [ql.Date(21,9,2016),ql.Date(21,12,2016),ql.Date(15,3,2017),ql.Date(21,6,2017),ql.Date(20,9,2017),ql.Date(20,12,2017),ql.Date(21,3,2018),ql.Date(20,6,2018),ql.Date(19,9,2018),ql.Date(19,12,2018),ql.Date(20,3,2019),ql.Date(19,6,2019)]

future_maturities = [ql.Date(21,12,2016),ql.Date(21,3,2017),ql.Date(15,6,2017),ql.Date(21,9,2017),ql.Date(20,12,2017),ql.Date(20,3,2018),ql.Date(21,6,2018),ql.Date(20,9,2018),ql.Date(19,12,2018),ql.Date(19,3,2019),ql.Date(22,6,2019),ql.Date(19,9,2019)]
future_rates = [99.13,99.095,99.075,99.045,99.015,98.975,98.955,98.93,98.9,98.855,98.83,98.8]

""" Swaps """
swap_maturities = [ql.Date(8,9,2020),ql.Date(7,9,2021),ql.Date(6,9,2022),ql.Date(6,9,2023),ql.Date(6,9,2024),ql.Date(8,9,2025),ql.Date(8,9,2026),ql.Date(6,9,2028),ql.Date(8,9,1931),ql.Date(8,9,1936),ql.Date(6,9,1941),ql.Date(6,9,1946),ql.Date(6,9,1956),ql.Date(6,9,1966)]

swap_periods = [ql.Period(4,ql.Years),ql.Period(5,ql.Years),ql.Period(6,ql.Years),ql.Period(7,ql.Years),ql.Period(8,ql.Years),ql.Period(9,ql.Years),ql.Period(10,ql.Years),ql.Period(12,ql.Years),ql.Period(15,ql.Years),ql.Period(20,ql.Years),ql.Period(25,ql.Years),ql.Period(30,ql.Years),ql.Period(40,ql.Years),ql.Period(50,ql.Years)]

swap_rates = [1.06329999999999,1.12319999999999,1.1825,1.24150000000001,1.2975,1.349,1.3965,1.484,1.579,1.68250000000001,1.73049999999999,1.75749999999999,1.77579999999999,1.76000000000001]

""" Parameter Setup """
calc_date = ql.Date(1,9,2016)
ql.Settings.instance().evaluationDate = calc_date
calendar = ql.UnitedStates()
bussiness_convention = ql.ModifiedFollowing
day_count = ql.Actual360()
coupon_frequency = ql.Annual

""" DepositRateHelper """
depo_helpers = []
for i in range(len(depo_rates)):
    depo_helpers.append(ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(depo_rates[i]/100)),depo_periods[i],3,ql.TARGET(),ql.ModifiedFollowing,False,ql.Actual360()))

""" FuturesRateHelper """
# Convexity Adjustments
conv_adjust = [0,0.0001,0.0003,0.0007,0.0013,0.0027,0.005,0.0078,0.0112,0.0158,0.0211,0.0277]
convexity_quote = []
for i in range(len(conv_adjust)):
    convexity_quote.append(ql.SimpleQuote(conv_adjust[i]/100.0))

imm = ql.IMM.nextDate(calc_date)
futures_helpers = []
for i in range(len(future_rates)):
    future_rate_val = (100.0-future_rates[i])/100.0
    futures_helpers.append(ql.FuturesRateHelper(ql.QuoteHandle(ql.SimpleQuote(future_rate_val)),imm,ql.Euribor3M(),ql.QuoteHandle(convexity_quote[i])))
    imm = ql.IMM.nextDate(imm)


""" SwapRateHelper """
swap_helpers = []
for rate,tenor in list(zip(swap_rates,swap_periods)):
    swap_helpers.append(ql.SwapRateHelper(ql.QuoteHandle(ql.SimpleQuote(rate/100.0)),
    tenor, calendar,
    coupon_frequency, bussiness_convention,
    day_count,
    ql.Euribor3M()))

rate_helpers = depo_helpers + futures_helpers + swap_helpers

yc_linearzero = ql.PiecewiseLinearZero(calc_date,rate_helpers,day_count)
yc_cubiczero = ql.PiecewiseCubicZero(calc_date,rate_helpers,day_count)

max_maturity = 50*12

splz = get_spot_rates(yc_linearzero, day_count, months=max_maturity + 1)
spcz = get_spot_rates(yc_cubiczero, day_count, months=max_maturity + 1)

max_rate = swap_rates[-1]
min_rate = min(splz.Curve)
max_rate = max(splz.Curve)

"""Plotting"""
# plt.plot(splcd["Maturities"],splcd["Curve"], '.',label="LogCubicDiscount")
plt.plot(splz["Maturities"],splz["Curve"],'--', label="LinearZero")
plt.plot(spcz["Maturities"],spcz["Curve"],label="CubicZero")
plt.xlabel("Years", size=12)
plt.ylabel("Zero Rate", size=12)
plt.xlim(0,max_maturity/12.0)
plt.ylim([min_rate * 0.9,max_rate * 1.1])
plt.legend()

plt.show()

rows = zip(splz.Maturities,splz.Curve)

with open('LIBORBootstrap.csv','w',newline='') as f:
    writer = csv.writer(f)
    for row in rows:
        writer.writerow(row)

$\endgroup$

1 Answer 1

3
$\begingroup$

The FuturesRateHelper class knows that futures are quoted as 100-rate, so there's no need to convert the prices. You can just create them as

futures_helpers.append(ql.FuturesRateHelper(
    ql.QuoteHandle(ql.SimpleQuote(future_rates[i])),
    imm,ql.Euribor3M(),ql.QuoteHandle(convexity_quote[i])))

I admit that the name of the class can lead one astray by mentioning rates instead of prices. Also, the other helpers might suggest that the division by 100 is needed, but that is only because the rates are given as, say, 0.43% when they are really 0.0043.

$\endgroup$
1
  • $\begingroup$ Thanks Luigi I was hoping you'd chime in on this. It fixed the issue. $\endgroup$
    – JVP3122
    Commented Nov 14, 2016 at 2:23

Your Answer

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

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