CDS options
This page lists everything you need to book and value an option on a CDS: the function's arguments and defaults, the credit vol surface it reads, how it's priced, and an example you can run. A CDS option, or credit swaption, is the right to enter a CDS at a strike spread on a future date. A payer is the right to buy protection, a receiver the right to sell it; how they trade is covered in how credit products work.
Quick example
The example reads its rates from the desk sheets; load them once and it runs as shown.
This pins ACME's survival curve and a credit vol surface, books a payer and a receiver struck at 120bp into a five-year CDS starting on 20 March 2027, and reads back the forward spread and the risky annuity the model priced with.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
use helixrs::{
AnyInstrument, CdsOptionStyle, CollateralSpec, CreditCurveId, CreditDefaultSwap,
CreditDefaultSwapOption, CreditVolSurface, Currency, DayCountFraction, FlatHazard,
MarketContext, QuoteSheet, Survival, SwaptionKind, cds_option, cds_standard,
};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
const ACME: &str = "ACME/SeniorUnSec/XR14/USD";
const STRIKE: f64 = 0.0120;
fn main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let expiry = date((2027, 3, 20));
let usd: Currency = "USD".parse()?;
// Rates from the sheet, the name's survival curve, and its credit vol surface:
// the Black vol of the forward spread by expiry in years.
let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
let acme: CreditCurveId = ACME.parse()?;
let vols = CreditVolSurface::new(&[(0.5, 0.45), (1.0, 0.42)])?;
let market = MarketContext::as_of(close)
.with_recipe_quotes(&CollateralSpec::Currency(usd), &sheet)?
.with_credit_curve(
acme.clone(),
Survival::FlatHazard(FlatHazard::new(close, 0.02, DayCountFraction::Act365Fixed)?),
Some(0.4),
)
.with_credit_vols(acme, vols.clone());
// The option exercises into a five-year CDS traded on the expiry date.
let underlying = CreditDefaultSwap::standard(&cds_standard(), expiry, "ACME", usd, 5, 1.0, 0.0100, true);
let option = |kind| -> Outcome<AnyInstrument> {
Ok(AnyInstrument::CdsOption(Box::new(CreditDefaultSwapOption::new(
underlying.clone(),
expiry,
STRIKE,
kind,
false,
CdsOptionStyle::SingleName,
)?)))
};
let p = option(SwaptionKind::Payer)?.npv(&market)?;
let r = option(SwaptionKind::Receiver)?.npv(&market)?;
println!("payer {p:.8}");
println!("receiver {r:.8}");
// The Black formula returns the forward and the risky annuity it priced with.
let black = cds_option(&underlying, expiry, STRIKE, vols.vol(0.5), SwaptionKind::Payer, false, &market)?;
println!("forward {:.8}", black.forward);
println!("annuity {:.6}", black.annuity);
Ok(())
}import datetime as dt
import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
ACME = "ACME/SeniorUnSec/XR14/USD"
EXPIRY = dt.date(2027, 3, 20)
STRIKE = 0.0120
# Rates from the sheet, the name's survival curve, and its credit vol surface:
# the Black vol of the forward spread by expiry in years.
vols = hlx.vols.CreditVolSurface([(0.5, 0.45), (1.0, 0.42)])
market = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_recipe_quotes("USD", USD_SOFR_CLOSE)
.with_credit_curve(ACME, hlx.curves.SurvivalCurve.flat_hazard(SOFR_CLOSE_DATE, 0.02), recovery=0.4)
.with_credit_vols(ACME, vols)
)
# The option exercises into a five-year CDS traded on the expiry date.
underlying = hlx.products.credit_default_swap(EXPIRY, "ACME", "USD", 5, notional=1.0, coupon=0.0100)
payer = hlx.products.credit_default_swap_option(underlying, EXPIRY, strike=STRIKE, kind="payer")
receiver = hlx.products.credit_default_swap_option(underlying, EXPIRY, strike=STRIKE, kind="receiver")
p, r = payer.npv(market), receiver.npv(market)
print(f"payer {p:.8f}")
print(f"receiver {r:.8f}")
# The Black formula returns the forward and the risky annuity it priced with.
_, forward, annuity = hlx.credit.cds_option(
underlying, EXPIRY, strike=STRIKE, vol=vols.vol(0.5), kind="payer", market=market
)
print(f"forward {forward:.8f}")
print(f"annuity {annuity:.6f}")payer 0.00614337
receiver 0.00651450
forward 0.01191441
annuity 4.335979
The forward spread sits just under the strike, so the receiver is worth a little more than the payer. Their difference is the annuity times the forward less the strike, whatever the vol.
How it's priced
Let be the forward par spread of the CDS the option exercises into, its risky annuity from today, the strike, the vol and the time to expiry. Under Black's model on the forward spread, the knockout payer and receiver are
with . Subtracting gives put–call parity, .
API
| Argument | Type | Default | Meaning |
|---|---|---|---|
underlying |
Instrument |
required | the CDS the option exercises into, a single-name or index contract |
expiry |
date |
required | the exercise date |
strike |
float |
required | the strike spread, or a price under strike_type="price" |
kind |
SwaptionKind or str |
required | "payer", the right to buy protection, or "receiver", the right to sell it |
short |
bool |
False |
True books the seller of the option |
style |
CdsOptionStyle or str |
"single_name" |
single_name knocks out on a default before expiry; front_end_protection adds a payer's protection to expiry instead; index prices an index option with its exercise price |
strike_type |
CdsStrikeType or str |
"spread" |
spread, or price per unit notional under the index style |
The vol surface is vols.CreditVolSurface([(years, vol), ...]), the Black vol of the forward
spread by expiry in years, pinned with MarketContext.with_credit_vols(identity, surface). In Rust
the option is CreditDefaultSwapOption::new.
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| credit vol by expiry | CreditVolSurface::new |
vols.CreditVolSurface |
|
| pinned vols | under the name's identity | MarketContext::with_credit_vols |
MarketContext.with_credit_vols |
| the option | on a forward CDS | CreditDefaultSwapOption::new |
products.credit_default_swap_option |
| , | option values | AnyInstrument::npv |
Instrument.npv |
| Black's model, read out | cds_option |
credit.cds_option |
Defaults and limits
- The vols: a market must hold a credit vol surface under the underlying's identity; without it the valuation is refused. Between quoted expiries the surface is linear in total variance, and beyond them it is flat.
- Knockout: under the default
single_namestyle the option is worth nothing on a default before expiry. Index options use theindexstyle, since the index survives a default. - The underlying follows the standard terms in the credit conventions catalog; an index underlying is taken at its index factor.
See also
- Source:
crates/helixrs/src/market/pricers.rs,crates/helixrs/src/marketdata/credit_vol.rs - Related pages:
- How credit products work, the ideas behind the option.
- Credit default swap, for the contract the option exercises into.
- Index CDS, for an index underlying.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.