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)