Having trouble to get the right yield with fixedRateBond.bondYield in QuantLib

Viewed 460

I'm trying to figure out how to estimate bond yield using its price with QuantLib. There are a lot of different examples, but I could not still find the right solution.

I have a bond maturing in March 2039, which pays coupons every 182 days. I compare calculations made with QuantLib with data from Bloomberg and exchange.

Here is the code:

issueDate = ql.Date(19,6,2019) #05.06.2019
maturityDate = ql.Date(16,3,2039) #16.03.2039
cpn_freq = 2
tenor = ql.Period('26W') #ql.Period(ql.Semiannual)
calendar = ql.Russia()
businessConvention = ql.Following
#businessConvention = ql.Unadjusted
dateGeneration = ql.DateGeneration.Backward
monthEnd = False

schedule = ql.Schedule (issueDate, maturityDate, tenor, calendar, businessConvention, businessConvention,
                        dateGeneration, monthEnd)

dayCount = ql.Actual365Fixed()

#settings for bond
compounding = ql.Compounded
cleanPrice = 105.45
ytm  = 0.0728
couponRate = 0.077
coupons = [couponRate]
settlementDays = 1
faceValue = 100

fixedRateBond = ql.FixedRateBond(settlementDays, faceValue, schedule, coupons, dayCount)

for c in fixedRateBond.cashflows():
    print('%20s %12f' % (c.date(), c.amount()))

It produces similar results to Bloomberg (the same coupon dates and amounts - 3.839):

   October 9th, 2019     2.658082
     April 8th, 2020     3.839452
   October 7th, 2020     3.839452
     April 7th, 2021     3.839452
   October 6th, 2021     3.839452
     April 6th, 2022     3.839452
   October 5th, 2022     3.839452
     April 5th, 2023     3.839452
   October 4th, 2023     3.839452
     April 3rd, 2024     3.839452
   October 2nd, 2024     3.839452
     April 2nd, 2025     3.839452
   October 1st, 2025     3.839452
     April 1st, 2026     3.839452
September 30th, 2026     3.839452
    March 31st, 2027     3.839452
September 29th, 2027     3.839452
    March 29th, 2028     3.839452
September 27th, 2028     3.839452
    March 28th, 2029     3.839452
September 26th, 2029     3.839452
    March 27th, 2030     3.839452
September 25th, 2030     3.839452
    March 26th, 2031     3.839452
September 24th, 2031     3.839452
    March 24th, 2032     3.839452
September 22nd, 2032     3.839452
    March 23rd, 2033     3.839452
September 21st, 2033     3.839452
    March 22nd, 2034     3.839452
September 20th, 2034     3.839452
    March 21st, 2035     3.839452
September 19th, 2035     3.839452
    March 19th, 2036     3.839452
September 17th, 2036     3.839452
    March 18th, 2037     3.839452
September 16th, 2037     3.839452
    March 17th, 2038     3.839452
September 15th, 2038     3.839452
    March 16th, 2039     3.839452
    March 16th, 2039   100.000000

However, trying to calculate YTD gives the wrong number:

fixedRateBond.bondYield(cleanPrice, dayCount, ql.Compounded, ql.Semiannual) * 100

7.170206456184388

But it should be 7.28%. If I try instead to use:

fixedRateBond.bondYield(cleanPrice, dayCount, ql.Compounded, ql.Period('26W')) * 100

I got an error:

  Possible C/C++ prototypes are:
    Bond::yield(DayCounter const &,Compounding,Frequency,Real,Size)
    Bond::yield(DayCounter const &,Compounding,Frequency,Real)
    Bond::yield(DayCounter const &,Compounding,Frequency)
    Bond::yield(Real,DayCounter const &,Compounding,Frequency,Date const &,Real,Size)
    Bond::yield(Real,DayCounter const &,Compounding,Frequency,Date const &,Real)
    Bond::yield(Real,DayCounter const &,Compounding,Frequency,Date const &)
    Bond::yield(Real,DayCounter const &,Compounding,Frequency)

Update: It looks like clearPrice and dirtyPrice also produce wrong results if used with actual ytm. But at least there is a workaround (from the CookBook) using the curve to get the right figures for clearPrice and dirtyPrice. But I still struggle to get the right number for YTM.

bond_yield = fixedRateBond.bondYield(cleanPrice, dayCount, compounding,  ql.Semiannual )* 100
bond_dp = fixedRateBond.dirtyPrice(ytm, dayCount, ql.Compounded, ql.Semiannual)
bond_cp = fixedRateBond.cleanPrice(ytm, dayCount, ql.Compounded, ql.Semiannual)
bond_dur = ql.BondFunctions.duration(fixedRateBond,0.0731,dayCount, ql.Compounded,ql.Semiannual, ql.Duration.Modified)

print('Yield: ' + str(bond_yield)) #ql.Period('26W')
print('Dirty price: ' + str(bond_dp))
print('Clean price: ' + str(bond_cp))
print('Accrued amount: ' + str(fixedRateBond.accruedAmount()))
print('Day counter: ' + str(fixedRateBond.dayCounter()))
print('Settlement date: ' + str(fixedRateBond.settlementDate()))
print('Dutation: ' + str(bond_dur))

print('Using curve')

flat_curve = FlatForward(fixedRateBond.settlementDate(), ytm, dayCount, Compounded)
engine = DiscountingBondEngine(YieldTermStructureHandle(flat_curve))
fixedRateBond.setPricingEngine(engine)
P2 = fixedRateBond.dirtyPrice()
print('Dirty price: ' +str(P2))
print('Cleand price: '+str(fixedRateBond.cleanPrice()))
print('YTM:' + str(fixedRateBond.bondYield(dayCount, Compounded,2)))

Yield: 7.150867552757262
Dirty price: 106.15244992161799
Clean price: 104.12724444216592
Accrued amount: 2.0252054794520635
Day counter: Actual/365 (Fixed) day counter
Settlement date: July 12th, 2021
Duration: 9.550427694884139

Using curve:
Dirty price: 107.46228302550196
Clean price: 105.43707754604989
YTM:0.07152117539749295
1 Answers

The YTM of 7.28% that you have obtained from Bloomberg is probably quoted on an annual basis.

yld = fixedRateBond.bondYield(cleanPrice, dayCount, ql.Compounded, ql.Annual)
print(yld)

gives you the following:

0.07277290053367616

And this yield produces back the following clean price:

print(fixedRateBond.cleanPrice(yld, dayCount, ql.Compounded, ql.Annual))

105.44999787859736

Related