I am dealing with fixed rate bonds. There is one particular bond, 34659UAC0, that caught my eye. This bond has a first coupon period of a whopping 5 years, followed by regular periods of 6 months. My current setup seems unable to deal with this properly. Here is minimal code to replicate this case, on QuantLib 1.32:
import QuantLib as ql
import pdb
import pandas as pd
import numpy as np
import os
# 34659UAC0
daycount = ql.Thirty360(ql.Thirty360.ISMA)
schedule = ql.Schedule(
ql.Date(1,7,2014), # effectiveDate
ql.Date(1,1,2043), # terminationDate
ql.Period(ql.Semiannual), # tenor
ql.UnitedStates(ql.UnitedStates.GovernmentBond), # calendar
ql.Unadjusted, # convention
ql.Unadjusted, # terminationDateConvention
ql.DateGeneration.Forward, # rule
False, # endOfMonth
ql.Date(1,7,2019) # firstDate
)
bond = ql.FixedRateBond(
0, # settlementDays
100.0, # faceAmount,
schedule, # schedule
[0.12], # coupons
daycount, # accrualDayCounter
ql.Unadjusted # paymentConvention
)
yield_ = bond.bondYield(
100.0, # clean_price
daycount, # dayCounter
ql.Compounded, # compounding
ql.Semiannual, # frequency
ql.Date(1,7,2014) # settlementDate
)
print(f"yield is {yield_:.3f}")
import matplotlib.pyplot as plt
# plot daily clean price from start to end
prices = pd.Series(dtype=float)
t = ql.Date(1,7,2014)
while True:
cp = bond.cleanPrice(
yield_, # yield
daycount, # dayCount
ql.Compounded, # compounding
ql.Semiannual, # frequency
t # settlementDate
)
prices[t] = cp
t += 1
if t == ql.Date(1,1,2043):
break
fig, axs = plt.subplots(1, 1, figsize=(8, 6))
x_ticks = np.arange(len(prices))
TICK_FREQ = 365 * 2
axs.plot(x_ticks, prices.to_numpy(), label="clean price")
axs.legend()
axs.set_xticks(x_ticks[::TICK_FREQ])
axs.set_xticklabels(prices.index[::TICK_FREQ], rotation=45, fontsize=6)
axs.set_title(f"clean prices")
fig.tight_layout()
fig.savefig(os.path.join(r"C:\data\my_data\tax", f"34659UAC0_cleanprice.png"), dpi=200)
plt.clf()
pdb.set_trace()
This code outputs the yield to be 10.8%, then proceeds to plot clean price on each day from start (the interest accrual date) to end (maturity date). However, one can notice that the bond is issued at par, so the YTM from start date should just be the coupon rate, 12%. Indeed, Bloomberg YAS page gives the correct YTM of 12%. How can I set up QuantLib to give the correct YTM and clean prices (they should just be always 100)?
Bond information:
Interest accrual date 2014-07-01
Maturity date: 2043-01-01
First coupon date: 2019-07-01
coupon rate: 12%, paid semiannualy
Update: The problem is with the frequency parameter in bondYield. It forces a constant frequency, and we need to find a way to specify it to be consistent with coupon payment days.
Update2: To clarify the problem, the behavior I want is different from conventional YTM. An example DimitriVulis has raised in the comments below is that for a bond with annual coupon rate of $c$ but only pays until maturity, what do we expect the YTM to be. If we are using annual compounding, then we formula is $1 = \frac{1 + cn}{(1 + ytm)^n}$. However, I need it to be $1 = \frac{1 + cn}{1 + ytm * n}$. The motivation for this can be demonstrated by the clean price graph for my original problem. Since I am dealing with bond taxation, I need a pull-to-par as the tax basis for the bond, and this curve must be monotonically increasing / decreasing. For a method called "constant yield method", the tax basis is equal to clean price on coupon dates, and equal to a linear interpolation in between coupon dates. Therefore, I need my clean price to be monotonic, and thus my yield to be consistent with coupon payment dates.
Mathematically, the YTM I want should be implied from the following formula: $\frac{issue price}{par price} = \frac{c_1}{1 + ytm * c_1 / c} + \frac{c_2}{(1 + ytm * c_1 / c)(1 + ytm * c_2 / c)} + ... + \frac{1 + c_n}{(1 + ytm * c_1 / c)...(1 + ytm * c_n / c)}$, where $c_1...c_n$ are actual coupon payments (taking into account the time after the previous payment) per unit value, and $c$ is annual coupon rate. Note that if the coupon payments are all $c/2$, then this is equivalent to a standard semiannually-compounded YTM.