Skip to content
All library documents

Repricing Upfront CDS Helpers After Changing the Evaluation Date

Article Quant Q&A · Author: bkhoor

Summary

This question describes a QuantLib issue that occurs when a pricing workflow changes the evaluation date to an earlier date after calibrating a hazard curve with UpfrontCdsHelper objects. The reported runtime error says the cash settlement date falls before the protection start date. The author suspects the helpers retain dates tied to the prior evaluation date and asks how to handle multiple CDS across dates in arbitrary order.

The example shows a curve calibrated from quoted upfront spreads, CDS repricing, and then an attempt to move the global evaluation date back by a year. It provides a reproducible setup and the error, but no answer or confirmed diagnosis. The document therefore identifies a date-dependency and observer-update problem rather than establishing a fix. It does not demonstrate that clearing helpers from memory is sufficient, nor discuss implications beyond the failed repricing; any recommended solution would require further investigation of QuantLib’s date handling and object lifecycle.

Key ideas

  • Upfront CDS helpers participate in a hazard curve calibration and may depend on evaluation-date state.
  • Changing the evaluation date backward can trigger a settlement-date versus protection-start-date error.
  • The example reprices CDS instruments and then moves the global evaluation date to an earlier date.
  • The document presents a problem report without a verified workaround or explanation of the underlying behavior.

Tags

Full text
# CDS Runtime Error: Issue changing evaluation date using UpfrontCdsHelper in Python


# CDS Runtime Error: Issue changing evaluation date using UpfrontCdsHelper in Python












If I run the below CDS pricing example (from https://github.com/lballabio/QuantLib-SWIG/blob/master/Python/examples/cds.py) using the UpfrontCdsHelper instead of the SpreadCdsHelper, I get the following error if I try to rerun for an earlier date relative to the initial date I used (e.g. rerun for May 15, 2006 after running for May 15, 2007):

“RuntimeError: could not notify one or more observers: The cash settlement date must not be before the protection start date.”

For my use case, I have to price multiple CDS over rolling dates and if the evaluation dates on which I price them are not in chronological order then I run into the above issue. The process I’m using to iterate through multiple CDS/dates should be agnostic to the order of dates.

It appears that the crux of the issue is that the helpers are still live with the previously set protection dates when I update the evaluation date to reprice for an earlier date. What exactly is going on and is there a strategic solution/best practice in python to handle this (e.g. clear helpers from memory)? Are there any related implications that I should be aware of when iterating through random permutation of multiple instruments and dates?

Thanks in advance.

```
import QuantLib as ql

calendar = ql.TARGET()

todaysDate = ql.Date(15, ql.May, 2007)#ql.Date(15, ql.May, 2006) #change date to a prior date to recreate issue
ql.Settings.instance().evaluationDate = todaysDate

risk_free_rate = ql.YieldTermStructureHandle(ql.FlatForward(todaysDate, 0.01, ql.Actual365Fixed()))

# ### CDS parameters
coupon = 0.01
recovery_rate = 0.5
quoted_spreads = [0.0150, 0.0150, 0.0150, 0.0150]
tenors = [ql.Period(3, ql.Months), ql.Period(6, ql.Months), ql.Period(1, ql.Years), ql.Period(2, ql.Years)]
maturities = [calendar.adjust(todaysDate + x, ql.Following) for x in tenors]

instruments = [
    ql.UpfrontCdsHelper(
        ql.QuoteHandle(ql.SimpleQuote(s)),
        coupon,
        tenor,
        0,
        calendar,
        ql.Quarterly,
        ql.Following,
        ql.DateGeneration.TwentiethIMM,
        ql.Actual365Fixed(),
        recovery_rate,
        risk_free_rate,
    )
    for s, tenor in zip(quoted_spreads, tenors)
]

hazard_curve = ql.PiecewiseFlatHazardRate(todaysDate, instruments, ql.Actual365Fixed())
print("Calibrated hazard rate values: ")
for x in hazard_curve.nodes():
    print("hazard rate on %s is %.7f" % x)

print("Some survival probability values: ")
print(
    "1Y survival probability: %.4g, \n\t\texpected %.4g"
    % (hazard_curve.survivalProbability(todaysDate + ql.Period("1Y")), 0.9704)
)
print(
    "2Y survival probability: %.4g, \n\t\texpected %.4g"
    % (hazard_curve.survivalProbability(todaysDate + ql.Period("2Y")), 0.9418)
)

# ### Reprice instruments

nominal = 1000000.0
probability = ql.DefaultProbabilityTermStructureHandle(hazard_curve)

# We'll create a cds for every maturity:

all_cds = []
for maturity, s in zip(maturities, quoted_spreads):
    schedule = ql.Schedule(
        todaysDate,
        maturity,
        ql.Period(ql.Quarterly),
        calendar,
        ql.Following,
        ql.Unadjusted,
        ql.DateGeneration.TwentiethIMM,
        False,
    )
    cds = ql.CreditDefaultSwap(ql.Protection.Seller, nominal, s, schedule, ql.Following, ql.Actual365Fixed())
    engine = ql.MidPointCdsEngine(probability, recovery_rate, risk_free_rate)
    cds.setPricingEngine(engine)
    all_cds.append(cds)

print("Repricing of quoted CDSs employed for calibration: ")
for cds, tenor in zip(all_cds, tenors):
    print("%s fair spread: %.7g" % (tenor, cds.fairSpread()))
    print("   NPV: %g" % cds.NPV())
    print("   default leg: %.7g" % cds.defaultLegNPV())
    print("   coupon leg: %.7g" % cds.couponLegNPV())
    print("")
    
#updating evaluation date fails
ql.Settings.instance().evaluationDate = todaysDate - ql.Period(12, ql.Months)
```

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.