I'm trying to write a program to calculate the convexity of a bond. The bigger idea is, that if I have access to the actual price for each point in time, I should be able to calculate various features of the bond given that date. The convexity program is written as follows:
""" Calculate convexity of a bond """
def bond_convexity(price, par, T, coup, freq, dy=0.0001):
ytm = bond_ytm(price, par, T, coup, freq)
ytm_minus = ytm - dy
price_minus = bond_price(par, T, ytm_minus, coup, freq)
ytm_plus = ytm + dy
price_plus = bond_price(par, T, ytm_plus, coup, freq)
convexity = (price_minus+price_plus-2*price)/(price*dy**2)
return convexity
Where the yield to maturity and bond price functions are as follows:
"""Interpolate yield to maturity"""
def bond_ytm(price, par, coupdates, coup, freq=2, guess=0.05):
freq = float(freq)
MAT = coupdates[len(coupdates) - 1] #maturity date
timedif = list() #init
periods = list() #init
for i in range(0,len(coupdates)-1):
timedif.append( round((MAT - coupdates[i]).days/365,1) ) #collection of time to maturity - coupon date
periods.append(timedif[i]*freq)
coupon = coup/100.*par/freq
dt = [(x)/freq for x in periods]
ytm_func = ytm_func = lambda y : \
sum([coupon/(1+y/freq)**(freq*t) for t in dt]) + par/(1+y/freq)**\
(freq*max(dt)) - price
return optimize.newton(ytm_func, guess)
"""Price bond"""
def bond_price(par, coupdates, ytm, coup, freq=2):
freq = float(freq)
MAT = coupdates[len(coupdates) - 1] #maturity date
timedif = list() #init
periods = list() #init
for i in range(0,len(coupdates)-1):
timedif.append( round((MAT - coupdates[i]).days/365,1) ) #collection of time to maturity - coupon date
periods.append(timedif[i]*freq)
coupon = coup/100.*par/freq
dt = [(x)/freq for x in periods]
price = sum([coupon/(1+ytm/freq)**(freq*t) for t in dt]) + par/(1+ytm/freq)**(freq*max(dt))
return price
Now, the following data shows the upcoming coupon dates for the bond and today's date at the top. I've saved this variable as cdd:
cdd
Out[28]:
[datetime.datetime(2017, 10, 31, 15, 20, 0, 480212),
datetime.datetime(2018, 2, 1, 0, 0),
datetime.datetime(2018, 8, 1, 0, 0),
datetime.datetime(2019, 2, 1, 0, 0),
datetime.datetime(2019, 8, 1, 0, 0),
datetime.datetime(2020, 2, 1, 0, 0),
datetime.datetime(2020, 8, 1, 0, 0),
datetime.datetime(2021, 2, 1, 0, 0),
datetime.datetime(2021, 8, 1, 0, 0),
datetime.datetime(2022, 2, 1, 0, 0),
datetime.datetime(2022, 8, 1, 0, 0),
datetime.datetime(2023, 2, 1, 0, 0),
datetime.datetime(2023, 8, 1, 0, 0),
datetime.datetime(2024, 2, 1, 0, 0),
datetime.datetime(2024, 8, 1, 0, 0),
datetime.datetime(2025, 2, 1, 0, 0),
datetime.datetime(2025, 8, 1, 0, 0),
datetime.datetime(2026, 2, 1, 0, 0),
datetime.datetime(2026, 8, 1, 0, 0),
datetime.datetime(2027, 2, 1, 0, 0),
datetime.datetime(2027, 8, 1, 0, 0),
datetime.datetime(2028, 2, 1, 0, 0),
datetime.datetime(2028, 8, 1, 0, 0),
datetime.datetime(2029, 2, 1, 0, 0),
datetime.datetime(2029, 8, 1, 0, 0),
datetime.datetime(2030, 2, 1, 0, 0),
datetime.datetime(2030, 8, 1, 0, 0),
datetime.datetime(2031, 2, 1, 0, 0),
datetime.datetime(2031, 8, 1, 0, 0),
datetime.datetime(2032, 2, 1, 0, 0),
datetime.datetime(2032, 8, 1, 0, 0),
datetime.datetime(2033, 2, 1, 0, 0),
datetime.datetime(2033, 8, 1, 0, 0),
datetime.datetime(2034, 2, 1, 0, 0),
datetime.datetime(2034, 8, 1, 0, 0),
datetime.datetime(2035, 2, 1, 0, 0),
datetime.datetime(2035, 8, 1, 0, 0),
datetime.datetime(2036, 2, 1, 0, 0),
datetime.datetime(2036, 8, 1, 0, 0),
datetime.datetime(2037, 2, 1, 0, 0),
datetime.datetime(2037, 8, 1, 0, 0),
datetime.datetime(2038, 2, 1, 0, 0),
datetime.datetime(2038, 8, 1, 0, 0),
datetime.datetime(2039, 2, 1, 0, 0),
datetime.datetime(2039, 8, 1, 0, 0),
datetime.datetime(2040, 2, 1, 0, 0),
datetime.datetime(2040, 8, 1, 0, 0),
datetime.datetime(2041, 2, 1, 0, 0),
datetime.datetime(2041, 8, 1, 0, 0),
datetime.datetime(2042, 2, 1, 0, 0),
datetime.datetime(2042, 8, 1, 0, 0),
datetime.datetime(2043, 2, 1, 0, 0),
datetime.datetime(2043, 8, 1, 0, 0),
datetime.datetime(2044, 2, 1, 0, 0),
datetime.datetime(2044, 8, 1, 0, 0),
datetime.datetime(2045, 2, 1, 0, 0),
datetime.datetime(2045, 8, 1, 0, 0)]
However, if I run the program to calculate convexity for a bond priced at 112.057, par = 100, dates as cdd, coupon of 4.9, and semi annual frequency, I get the following solution:
bond_convexity(112.057, 100, cdd, 4.9, 2)
Out[32]: 351.98162487756656
Bloomberg is showing that the convexity should be approximately 3.467. What could I possibly be doing wrong? Any help is appreciated.