Skip to content
All library documents

Matching QuantLib Z-Spread Compounding to Bond Pricing

Article Quant Q&A · Author: bjonen

Summary

This note explains why a bond’s clean price calculated with a z-spread may differ from a target market price even when the spread solver appears to converge. The example builds a fixed-rate bond and derives a spread with QuantLib’s BondFunctions.zSpread method, then reprices the bond using a ZeroSpreadedTermStructure. The resulting prices differ because the two calculations interpret the spread under different compounding conventions.

The accepted answer says ZeroSpreadedTermStructure uses continuous compounding by default. To align the repricing with the z-spread calculation, pass the same compounding convention and frequency when constructing the spreaded curve. The example illustrates a software configuration issue rather than a limitation of spread calibration. Its guidance depends on using consistent curve inputs and conventions; it does not discuss other sources of bond price differences, such as settlement, accrued interest, or day-count mismatches.

Key ideas

  • A z-spread calculation and a spreaded curve must use matching compounding assumptions.
  • ZeroSpreadedTermStructure defaults to continuously compounded spreads when no convention is supplied.
  • Passing the intended compounding convention and frequency can align the repriced clean price with the target.
  • Other bond pricing inputs can also affect price agreement but are not explored in the example.

Tags

Full text
# QuantLib: BondFunctions.zSpread does not match clean price "exactly"


# QuantLib: BondFunctions.zSpread does not match clean price "exactly"












I am using QuantLib to compute prices of fixed rate bonds in different scenarios. In the first step I would like to replicate the current market price by adjusting the yield curve with the zspread. Using the function BondFunctions.zSpread I get close (in the example below (97.85 versus a market price of 98) but I am wondering why we do not converge to the exact same price when altering the spread continuously. Am I missing something?

The following code adapts Simple QuantLib Bond Math

```
import pytest
import QuantLib as ql

from QuantLib import *

# Construct yield curve
calc_date = Date(1, 1, 2017)
Settings.instance().evaluationDate = calc_date

spot_dates = [Date(1,1,2017), Date(1,1,2018), Date(1,1,2027)]
# corrected!
# spot_rates = [0.0, 0.04, 0.04]
spot_rates = [0.04, 0.04, 0.04]

day_count = SimpleDayCounter()
calendar = NullCalendar()
interpolation = Linear()
compounding = Compounded
# corrected!
compounding_frequency = Annual
compounding_frequency = Semiannual
spot_curve = ZeroCurve(spot_dates, spot_rates, day_count, calendar,
                       interpolation, compounding,
                       compounding_frequency)

spot_curve_handle = YieldTermStructureHandle(spot_curve)

# Construct bond schedule
issue_date = Date(1, 1, 2017)
maturity_date = Date(1, 1, 2022)
tenor = Period(Semiannual)
calendar = NullCalendar()
business_convention = Unadjusted
date_generation = DateGeneration.Backward
month_end = False

schedule = Schedule(issue_date, maturity_date, tenor, calendar,
                    business_convention, business_convention,
                    date_generation, month_end)

# Create FixedRateBond Object

coupon_rate = 0.05
coupons = [coupon_rate]
settlement_days = 0
face_value = 100

fixed_rate_bond = FixedRateBond(settlement_days,
                            face_value,
                            schedule,
                            coupons,
                            day_count)

# Set Valuation engine
bond_engine = DiscountingBondEngine(spot_curve_handle)
fixed_rate_bond.setPricingEngine(bond_engine)

# Calculate present value
value = fixed_rate_bond.NPV()
assert value == pytest.approx(104.49, abs=1.e-2)

# fix a hypothetical market price
px = 98.

# compute the implied z spread
zspread = ql.BondFunctions.zSpread(fixed_rate_bond,
                                   px,
                                   spot_curve, day_count, compounding,
                                   compounding_frequency, calc_date, 1.e-16, 1000000, 0.)

def impl_clean_price(spread):
    spread1 = ql.SimpleQuote(spread)
    spread_handle1 = ql.QuoteHandle(spread1)
    ts_spreaded1 = ql.ZeroSpreadedTermStructure(spot_curve_handle,
                                                spread_handle1)
    ts_spreaded_handle1 = ql.YieldTermStructureHandle(ts_spreaded1)
    ycsin = ts_spreaded_handle1
    fixed_rate_bond = FixedRateBond(settlement_days,
                                    face_value,
                                    schedule,
                                    coupons,
                                    day_count)
    # Set Valuation engine
    bond_engine = DiscountingBondEngine(ycsin)
    fixed_rate_bond.setPricingEngine(bond_engine)
    value = fixed_rate_bond.cleanPrice()
    return value

# the two clean prices are 98 and 97.8517891975
print px
print impl_clean_price(zspread)
print abs(px-impl_clean_price(zspread))
```

## Answer by Luigi Ballabio (score 4, accepted)

https://quant.stackexchange.com/a/42794

If you don't supply the compounding convention to `ZeroSpreadedTermStructure` explicitly, it will consider the passed spread as continuously compounded and apply it to the base curve accordingly.

You'll need to instantiate the curve instead as:

```
    ts_spreaded1 = ql.ZeroSpreadedTermStructure(spot_curve_handle,
                                                spread_handle1,
                                                compounding,
                                                compounding_frequency)
```

This will tell the `ZeroSpreadedTermStructure` how to interpret the spread quote and will result in the correct bond price.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.