Nth-to-default
This page lists everything you need to book and value an nth-to-default basket: the function's arguments and defaults, the correlation it reads, how it's priced, and an example you can run. An nth-to-default swap buys protection on a small basket that pays only when the n-th name defaults. Why its spread sits where it does 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 three names on flat hazards of 2%, 1.5% and 1%, and a basket correlation of 30% under the
key TRIO. It reads each name's single-name par spread, then the first-to-default and
second-to-default spreads on the basket.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
use helixrs::{
AnyInstrument, CollateralSpec, CreditCurveId, CreditDefaultSwap, Currency, DayCountFraction,
FlatHazard, MarketContext, NthToDefaultSwap, QuoteSheet, Survival, 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 NAMES: [(&str, f64); 3] = [("ACME", 0.020), ("BETA", 0.015), ("GAMMA", 0.010)];
fn main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let usd: Currency = "USD".parse()?;
let template = cds_standard();
// A bare name reads on the template's standard tier and clause.
let identity = |name: &str| -> Outcome<CreditCurveId> { Ok(template.reference(name)?.in_currency(usd)) };
// Each name's survival curve under its identity, and the basket's correlation
// under a key the contract reads, so it carries a risk row of its own.
let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
let mut market = MarketContext::as_of(close).with_recipe_quotes(&CollateralSpec::Currency(usd), &sheet)?;
for (name, hazard) in NAMES {
market = market.with_credit_curve(
identity(name)?,
Survival::FlatHazard(FlatHazard::new(close, hazard, DayCountFraction::Act365Fixed)?),
Some(0.4),
);
}
let market = market.with_basket_correlation("TRIO", 0.30)?;
let mut singles = Vec::new();
for (name, _) in NAMES {
let cds = CreditDefaultSwap::standard(&template, close, name, usd, 5, 1.0, 0.01, true);
singles.push(AnyInstrument::Cds(cds).par_spread(&market)?);
}
for ((name, _), spread) in NAMES.iter().zip(&singles) {
println!("{name:<6} single {spread:.6}");
}
let basket = |n: usize| -> Outcome<f64> {
let entities = NAMES.iter().map(|(name, _)| identity(name)).collect::<Outcome<Vec<_>>>()?;
let swap = NthToDefaultSwap::standard(&template, close, entities, n, usd, 5, 10_000_000.0, 0.0300, 0.0, true)?
.on_market_correlation("TRIO");
Ok(AnyInstrument::NthToDefault(Box::new(swap)).par_spread(&market)?)
};
let first = basket(1)?;
let second = basket(2)?;
println!("first to default {first:.6}");
println!("second to default {second:.6}");
Ok(())
}import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
NAMES = {"ACME": 0.020, "BETA": 0.015, "GAMMA": 0.010}
# Each name's survival curve under its identity, and the basket's correlation
# under a key the contract reads, so it carries a risk row of its own.
market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
for name, hazard in NAMES.items():
market = market.with_credit_curve(
f"{name}/SeniorUnSec/XR14/USD",
hlx.curves.SurvivalCurve.flat_hazard(SOFR_CLOSE_DATE, hazard),
recovery=0.4,
)
market = market.with_basket_correlation("TRIO", 0.30)
singles = [
hlx.products.credit_default_swap(SOFR_CLOSE_DATE, name, "USD", 5, notional=1.0, coupon=0.01).par_spread(market)
for name in NAMES
]
for name, spread in zip(NAMES, singles):
print(f"{name:<6} single {spread:.6f}")
def basket(n):
return hlx.products.nth_to_default_swap(
SOFR_CLOSE_DATE, list(NAMES), n, "USD", 5,
notional=10_000_000.0, spread=0.0300, correlation_key="TRIO",
)
first = basket(1).par_spread(market)
second = basket(2).par_spread(market)
print(f"first to default {first:.6f}")
print(f"second to default {second:.6f}")ACME single 0.011907
BETA single 0.008930
GAMMA single 0.005953
first to default 0.024016
second to default 0.003617
The first-to-default spread lies between the widest single name's and the sum of all three, and the second-to-default is far cheaper.
How it's priced
Under the one-factor copula of the CDO tranche, each name defaults by with probability , and the names are independent given the common factor . The probability that fewer than have defaulted by ,
is the basket's survival curve, and the swap's legs are those of a CDS on it.
API
| Argument | Type | Default | Meaning |
|---|---|---|---|
trade_date |
date |
required | the trade date |
entities |
list[str] |
required | the names, each a bare entity on the standard tier and clause or a full identity |
n |
int |
required | which default pays: 1 for first-to-default |
currency |
str |
required | completes each bare entity's curve identity |
tenor_years |
int |
required | whole years of protection |
notional |
float |
required | the protected amount |
spread |
float |
required | the running spread the buyer pays |
correlation |
float |
None |
a pairwise correlation fixed in the contract, with no risk row |
correlation_key |
str |
None |
the market's basket correlation under that key, with a risk row |
buys_protection |
bool |
True |
False books the seller |
fixed_recovery |
float |
None |
a recovery the contract fixes; None reads each name's |
factor_dof, idiosyncratic_dof |
float |
None |
the double-t copula's degrees of freedom; give both or neither |
copula_dof_key |
str |
None |
a credit index id whose marked degrees of freedom the double-t reads |
A basket correlation is pinned with MarketContext.with_basket_correlation(key, correlation). In
Rust the basket is NthToDefaultSwap::standard, or on_market_correlation to read the market's.
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| the names' curves | each under its identity | MarketContext::with_credit_curve |
MarketContext.with_credit_curve |
| in the market | basket correlation under a key | MarketContext::with_basket_correlation |
MarketContext.with_basket_correlation |
| the basket | nth-to-default on | NthToDefaultSwap::standard |
products.nth_to_default_swap |
| fewer than defaults | basket_survival_probability |
credit.basket_survival_probability |
|
| par spread | legs equal | AnyInstrument::par_spread |
Instrument.par_spread |
Defaults and limits
- Correlation: give exactly one of
correlationandcorrelation_key. A correlation in the market carries a risk row of its own; one fixed in the contract doesn't. - The names' curves: the market must hold a survival curve for every name in the basket.
- Terms: the basket follows standard CDS terms; see the credit conventions catalog.
See also
- Source:
crates/helixrs/src/instruments/credit.rs,crates/helixrs/src/models/credit.rs - Related pages:
- How credit products work, the ideas behind the bounds.
- Credit default swap, for the single names.
- CDO tranche, for the same copula on an index pool.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.