4
$\begingroup$

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))
$\endgroup$

1 Answer 1

4
$\begingroup$

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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