2
$\begingroup$

I'm looking into way to calculate forward bond yield using QuantLib. In Python QuantLib book I see an example for bond futures, where

futures = ql.FixedRateBondForward(calc_date, futures_maturity_date, 
ql.Position.Long, 0.0, settlement_days, day_count, calendar, business_convention,
ctd_bond, yield_curve_handle, yield_curve_handle)

implied_yield = futures.impliedYield(ctd_price/ctd_cf,
futures_price, calc_date, ql.Compounded, day_count).rate()

Is it correct to do something like this?

fwd= ql.FixedRateBondForward(calc_date, fwd_date, ql.Position.Long, 0.0,
settlement_days, day_count, calendar, business_convention, bond,
yield_curve_handle, yield_curve_handle)

fwd_price = fwd.cleanForwardPrice()
fwd_yield = fwd.impliedYield(bond_spot_price, fwd_price,
calc_date, ql.Compounded, day_count).rate()
```
$\endgroup$
4
$\begingroup$

The direct answer to your question is actually no, and here are some other ways to get a forward bond yield if all you want is the yield of a forward starting bond (I'm assuming it's a forward starting bond you want, ie, no intermediate cashflows)

import QuantLib as ql

today = ql.Date().todaysDate()
calendar = ql.NullCalendar()
dayCounter = ql.ActualActual()

dates = [today,  ql.Date(28,10,2021),  ql.Date(28,10,2022), ql.Date(28,10,2025)]
zeros = [0.01, 0.02, 0.03, 0.04]
crv = ql.LogLinearZeroCurve(dates, zeros, dayCounter, calendar)
yts = ql.YieldTermStructureHandle(crv)
engine = ql.DiscountingBondEngine(yts)

Defining a simple foward starting bond, you can get the bond yield from it's price (npv).

issueDate = today + ql.Period('2Y')
maturityDate = issueDate + ql.Period('2Y')

bond = ql.FixedRateBond(0, calendar, 100.0, issueDate, maturityDate, ql.Period('1Y'), [0.05], dayCounter)
bond.setPricingEngine(engine)

bondPrice = bond.NPV()
print(f"Bond Price: {bondPrice:.5f}")
bondYield = bond.bondYield(bondPrice, dayCounter, ql.Compounded, ql.Annual)
print(f"Bond Yield: {bondYield:.3%}")

Bond Price: 95.32379
Bond Yield: 3.689%

However, this will be the yield starting now and not the forward yield.

The approach you used:

fwd = ql.FixedRateBondForward(today, issueDate, ql.Position.Long, 100, 2, dayCounter, ql.TARGET(), ql.Following, bond, yts, yts)
fwdPrice = fwd.cleanForwardPrice()
fwdYield = fwd.impliedYield(bondPrice, fwdPrice, today, ql.Compounded, dayCounter).rate()
print(f"Fwd Yield: {fwdYield:.3%}")

Fwd Yield: 3.045%

Will also not give you the forward yield. According to QuantLib documentation, the impliedYield method gives:

"Simple yield calculation based on underlying spot and forward values, taking into account underlying income. When t>0, call with: underlyingSpotValue=spotValue(t), forwardValue=strikePrice, to get current yield. For a repo, if t=0, impliedYield should reproduce the spot repo rate. For FRA's, this should reproduce the relevant zero rate at the FRA's maturityDate"

So if you are feeding it the bondPrice and the forward bond price, you will basically get the zero rate. And in fact, since the forward bond price is just the compounded bond price:

print(fwdPrice)
print(bondPrice * crv.discount(issueDate)**-1)

101.21680137389713
101.21680137389713:

zeroRate = crv.zeroRate(issueDate, dayCounter, ql.Compounded).rate()
print(f"Zero Rate: {zeroRate:.3%}") 

Zero Rate: 3.045%

What you could do is build the cashflows of a forward bond:

cfs = ql.Leg([ql.AmortizingPayment(-100, issueDate)] + [*bond.cashflows()][:-1])
bond2 = ql.Bond(2, calendar, today, cfs)
bond2.setPricingEngine(engine)
for cf in bond2.cashflows():
    print(cf.date().ISO(), cf.amount())

2022-10-28 -100.0
2023-10-28 5.000000000000004
2024-10-28 5.002432816827618
2024-10-28 100.0

And get it's yield:

fwdYield = bond2.bondYield(bond2.NPV(), dayCounter, ql.Compounded, ql.Annual)
print(f"Fwd Yield: {fwdYield:.3%}")

Fwd Yield: 4.336%

If you don't know the coupon, you could just get the annually compounded forward from the curve:

fwdRate = crv.forwardRate(issueDate, maturityDate, dayCounter, ql.Compounded, ql.Annual).rate()
print(f"Fwd Rate: {fwdRate:.3%}")

Fwd Rate: 4.361%

Which would be more or less:

$$ fwd = \frac{DF_0 - DF_T}{\sum^T_{i=1} DF_i}$$

where i are cashflow dates and T is maturity date

dates = ql.MakeSchedule(issueDate, maturityDate, ql.Period('1Y'), )
dfs = [crv.discount(date) for date in dates]
fwdRate2 = (dfs[0]-dfs[-1])/ sum(dfs[1:])
print(f"Fwd Rate: {fwdRate2:.3%}")

Fwd Rate: 4.354%

$\endgroup$
4
  • $\begingroup$ Hi, thanks for the update. You've actually when through a lot of issues I had initially with moving settlement date and out of the box impliedYield approach. The crv.forwardRate I think only works for par bonds. $\endgroup$ – kismsu Oct 29 '20 at 8:18
  • $\begingroup$ Another comment, when I think of a bond forward, I assume I'll get in the future exactly the bond I'm looking at, not the bond with the same maturity. So if I have 10y bond and I want to get 1y forward on it, I should price 9y bond 1 year from now. Would you agree? $\endgroup$ – kismsu Oct 29 '20 at 8:26
  • 1
    $\begingroup$ Yes, the crv.forwardRate only works on par bonds as it's the rate by which the coupon rate and discounting rate are the same. And Yes you can think if it as a 9Y bond 1Y from now but take into account the cashflows in the meantime. Generically, any foward can be defined as $F_0 = (S_0 - I)e^{rT}$. Check out the John Hull book for an example (Ch 6.2) $\endgroup$ – David Duarte Oct 29 '20 at 9:21
  • $\begingroup$ On your comment on cash flow. fwdPrice = fwd.cleanForwardPrice() does not take coupon payments into account right? it is basically spot discounted forward. Is there any function which will adjust for that? $\endgroup$ – kismsu Oct 29 '20 at 10:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.