Skip to content
All library documents

Interpreting a QuantLib Z-Spread Comparison and Percentage Scaling

Article Quant Q&A · Author: Jose Pedro Melo

Summary

The document presents a question about whether changing when QuantLib’s evaluation date is set affects a bond z-spread calculation. The example constructs a yield curve and a fixed-rate bond, then calculates a present value and a z-spread in two test functions that set the evaluation date at different points. The displayed outputs appear identical, prompting the question about the curve reference date.

The accepted response attributes the apparent discrepancy to inconsistent spread reporting: one test multiplies the z-spread by 100, while the other does not. This is a unit conversion issue when expressing a decimal rate as a percentage, rather than evidence that the evaluation date changed the spread. The exchange provides no further investigation of QuantLib date handling, and the code and outputs alone do not establish whether other date or curve conventions could affect a different calculation.

Key ideas

  • The example compares bond z-spread calculations after setting QuantLib’s evaluation date at different points.
  • The accepted response identifies inconsistent multiplication by 100 as the source of the reported spread difference.
  • A decimal spread and a percentage spread use different scales and should be labeled consistently.
  • The answer does not investigate other possible effects of evaluation dates or curve conventions.

Tags

Full text
# TermStructure/Evaluation Dates in Quantlib


# TermStructure/Evaluation Dates in Quantlib












i'm puzzled by the way Quantlib handles the evaluation date in the yield term structure classes. I have the following code as example:

```
import QuantLib as ql
import pandas as pd

#Curve
def build_termstruc(curve_date):
    helpers = []
    handle = ql.YieldTermStructureHandle()
    tenors = [1,3,6,9,12,18,2,3,4,5,6,7,8,9,10,15,20]
    swap_rates = [2.75,2.875,2.99,3.12,3.25,3.5,3.61,3.795,3.925,4.025,4.115,4.195,4.265,4.335,4.4,4.555,4.68]

    fixing_days = 0
    calendar = ql.NullCalendar()
    settlement_days = 0
    day_counter = ql.Actual360()
    index = ql.OvernightIndex("index", settlement_days,ql.CLPCurrency(),calendar,day_counter)
    for i in range(len(tenors)):
        if i == 0:
            helpers += [ql.DepositRateHelper(ql.QuoteHandle(ql.SimpleQuote(swap_rates[i]/100)),
                            ql.Period(1,ql.Days),
                            fixing_days,
                            calendar,
                            ql.Unadjusted,
                            False,
                            ql.Actual360())]
            continue
        elif i < 6 and i > 0:
            period = ql.Period(tenors[i], ql.Months)
            frequency = ql.Months
        elif i >=6:
            period = ql.Period(tenors[i], ql.Years)
            frequency = ql.Years

        helpers += [ql.OISRateHelper(settlement_days,
                        period,
                        ql.QuoteHandle(ql.SimpleQuote(swap_rates[i]/100)),
                        index,
                        handle,
                        False,
                        0,
                        ql.Following,
                        frequency)]
    return ql.PiecewiseFlatForward(curve_date, helpers, ql.Actual360())
def build_bond():
    #Bond
    issue_date = ql.Date(1,6,2015)
    maturity_date = ql.Date(1,6,2020)
    calendar = ql.NullCalendar()
    coupon_day_count = ql.Unadjusted
    payment_convention = ql.Following
    date_generation = ql.DateGeneration.Forward
    month_end = False
    settlement_days = 0
    int_day_count = ql.Thirty360()
    notional = 100
    coupons = [4.5/100]
    #non static variables
    schedule = ql.Schedule (issue_date,
                            maturity_date,
                            ql.Period(ql.Semiannual),
                            calendar,
                            coupon_day_count,
                            payment_convention,
                            date_generation,
                            month_end)

    return ql.FixedRateBond(settlement_days, notional, schedule, coupons, int_day_count)

def test1():
    curve_date = ql.Date(14,11,2018)
    ql.Settings.instance().evaluationDate = curve_date
    ts = build_termstruc(curve_date)

    bond = build_bond()
    bond_IIR = ql.InterestRate(3.78/100, ql.Actual365Fixed(), ql.Compounded, ql.Annual)
    npv = ql.CashFlows.npv(bond.cashflows(), bond_IIR, False)
    spread = ql.CashFlows.zSpread(bond.cashflows(), npv, ts, ql.Actual360(), ql.Compounded, ql.Annual, True)*100
    print('NPV: ', npv,'Spread: ', spread)

def test2():
    curve_date = ql.Date(14,11,2018)
    ts = build_termstruc(curve_date)
    ql.Settings.instance().evaluationDate = curve_date

    bond = build_bond()
    bond_IIR = ql.InterestRate(3.78/100, ql.Actual365Fixed(), ql.Compounded, ql.Annual)

    npv = ql.CashFlows.npv(bond.cashflows(), bond_IIR, False)
    spread = ql.CashFlows.zSpread(bond.cashflows(), npv, ts, ql.Actual360(), ql.Compounded, ql.Annual, True)*100
    print('NPV: ', npv,'Spread: ', spread)

if __name__ == '__main__':
    test1()
    test2()
```

The idea of the code is that the first two methods build a term structure and a bond and the other two (test1 and test2) only change the position where the evaluationDate is set. The result is the following:

```
NPV:  103.15824069057247 Spread:  0.18307250901326033 
NPV:  103.15824069057247 Spread:  0.18307250901326033
```

I don't get why i'm getting different z-spreads if im setting the curve reference date.

## Answer by amdopt (score 4, accepted)

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

Your spread definitions are different. In Def Test2(), it is multiplied by 100. That's the cause of the decimal shift in the spread.

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.