Coding towards CFA (29) – Pricing Credit Default Swap with QuantLib

Coding towards CFA (29) – Pricing Credit Default Swap with QuantLib

In the previous blog, I manually crafted the Python code for pricing CDS without relying on third-party quant libraries. While this approach was useful for understanding the underlying algorithm, in practice, it’s preferable to use a mature, validated library for standardisation when available. In this blog post, I will revisit the CDS pricing exercise using QuantLib and explore its three built-in CDS pricing engines: IsdaCdsEngine, IntegralCdsEngine, and MidPointCdsEngine.

IsdaCdsEngine

The IsdaCdsEngine is designed to comply with the ISDA Standard Model for CDS pricing, which aims to provide standardized, consistent, and transparent methodologies for valuing CDS contracts in the global market. As the ISDA Standard Model is the widely accepted industry standard for CDS pricing and risk management, and is often the required method by regulators, it is the recommended and safer choice for CDS pricing in practice.

IntegralCdsEngine

The IntegralCdsEngine employs an integral-based approach to price CDS, integrating over potential default times and accounting for the evolution of credit spreads and discount factors over time. Unlike the IsdaCdsEngine, it does not rely on simplifying assumptions about default probabilities or the specific assumptions of the ISDA Standard Model, making it more flexible and capable of handling more complex default models.

MidPointCdsEngine

The MidPointCdsEngine uses a midpoint approximation to calculate the present value of the protection and premium legs, providing a quick and approximate valuation of CDS contracts. It is mainly used for quick, ad-hoc valuations and educational purposes.

Here is a function I put together for creating instances of these three pricing engines. As you can see, for all three engines, we need to provide the default probability term structure, the recovery rate, and the discounting rate term structure.

Now, let’s walk through the code to define these term structures and other required steps for CDS pricing using QuantLib.

First, we set the observed market data and the reference entity, including the CDS spread, recovery rate, and risk-free rate, and define the CDS contract terms, such as the maturity date, CDS coupon rate, payment frequency, etc.

We then create the SpreadCdsHelper instance and the PiecewiseFlatHazardRate instance, which represent the credit curve and will be provided for creating the CDS pricing engines.

Next, we create the CDS contract schedule and the CreditDefaultSwap instance using the provided CDS contract terms.

After calling the create_cds_pricing_engine to create one of the three pricing engines, we can set the engine to the CreditDefaultSwap instance and call the couponLegNPV, defaultLegNPV, upfrontNPV, and fairSpread methods to trigger the calculations.

Here are the outputs evaluated from these three pricing engines.

Full Code – QuantLib

import QuantLib as ql

def create_cds_pricing_engine(engine_type, credit_curve, recovery_rate, risk_free_curve):

    if engine_type == 'Integral':

        return ql.IntegralCdsEngine(
                ql.Period('1d'),
                ql.DefaultProbabilityTermStructureHandle(credit_curve), 
                recovery_rate, 
                ql.YieldTermStructureHandle(risk_free_curve)
            )

    elif engine_type == 'Midpoint':

        return ql.MidPointCdsEngine(
            ql.DefaultProbabilityTermStructureHandle(credit_curve), 
            recovery_rate, 
            ql.YieldTermStructureHandle(risk_free_curve)
        )

    elif engine_type == 'ISDA':
        return  ql.IsdaCdsEngine(
                ql.DefaultProbabilityTermStructureHandle(credit_curve), 
                recovery_rate, 
                ql.YieldTermStructureHandle(risk_free_curve)
        )
    else:
        raise Exception('No qualified engine type is specified.')


# Set the evaluation date
today = ql.Date(1, 1, 2025)
ql.Settings.instance().evaluationDate = today

# Define market data
recovery_rate = 0.40 
cds_spread = 0.01
risk_free_rate = ql.QuoteHandle(ql.SimpleQuote(0.035)) 

# Define the CDS terms
maturity_date = ql.Date(1, 1, 2030) 
cds_coupon = 0.01
frequency = ql.Quarterly
day_count = ql.Actual365Fixed()
calendar = ql.UnitedStates(ql.UnitedStates.NYSE)
payment_convention = ql.Following

# Set up the risk-free curve
risk_free_curve = ql.FlatForward(today, risk_free_rate, day_count,
                                         ql.Compounded, ql.Quarterly)

# create cds helpers and credit curve
cds_helpers = [
    ql.SpreadCdsHelper(
        ql.QuoteHandle(ql.SimpleQuote(cds_spread)),
        ql.Period(5, ql.Years),
        0,
        calendar,
        frequency,
        payment_convention,
        ql.DateGeneration.Forward,
        day_count,
        recovery_rate,
        ql.YieldTermStructureHandle(risk_free_curve),
    )
]
credit_curve = ql.PiecewiseFlatHazardRate(today, cds_helpers, day_count)

# Define the CDS schedule
cds_schedule = ql.MakeSchedule(
    today,
    maturity_date,
    ql.Period(frequency),
    frequency,
    calendar,
    payment_convention,
    payment_convention,
    ql.DateGeneration.Forward,
    False,
)

# Create CDS instance
cds = ql.CreditDefaultSwap(
    ql.Protection.Buyer,
    1000000,
    cds_coupon,
    cds_schedule,
    payment_convention,
    day_count
)

# apply Integral pricing engine
engine = create_cds_pricing_engine('Integral', credit_curve, 
                                   recovery_rate, risk_free_curve)  
cds.setPricingEngine(engine)
print("*******************************")
print("  Integral CDS Pricing Engine  ")
print("*******************************")
print(f"Premium Leg NPV: {round(cds.couponLegNPV(), 2)}")
print(f"Protection Leg NPV: {round(cds.defaultLegNPV(), 2)}")
print(f"Upfront Premium: {round(cds.upfrontNPV(), 2)}")
print(f"Fair Spread: {round(cds.fairSpread(), 2)}")


# apply midpoint pricing engine
engine = create_cds_pricing_engine('Midpoint', credit_curve, 
                                   recovery_rate, risk_free_curve)  
cds.setPricingEngine(engine)
print("*******************************")
print("  Midpoint CDS Pricing Engine  ")
print("*******************************")
print(f"Premium Leg NPV: {round(cds.couponLegNPV(), 2)}")
print(f"Protection Leg NPV: {round(cds.defaultLegNPV(), 2)}")
print(f"Upfront Premium: {round(cds.upfrontNPV(), 2)}")
print(f"Fair Spread: {round(cds.fairSpread(), 2)}")


# apply isda pricing engine
engine = create_cds_pricing_engine('ISDA', credit_curve, 
                                   recovery_rate, risk_free_curve)  
cds.setPricingEngine(engine)
print("*******************************")
print("  ISDA CDS Pricing Engine  ")
print("*******************************")
print(f"Premium Leg NPV: {round(cds.couponLegNPV(), 2)}")
print(f"Protection Leg NPV: {round(cds.defaultLegNPV(), 2)}")
print(f"Upfront Premium: {round(cds.upfrontNPV(), 2)}")
print(f"Fair Spread: {round(cds.fairSpread(), 2)}")

Leave a comment