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)
Answer
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.
No comments:
Post a Comment