Esc

    The API is under testing and may change before release.

    Index CDS

    This page lists everything you need to find an index series and book and value an index CDS: the series functions, the contract's arguments and defaults, how it's priced, and an example you can run. An index CDS buys or sells protection on a standard basket of names, such as the 125 names of CDX.NA.IG, in one contract. Series, rolls and the index curve are 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 finds the CDX.NA.IG series on the run at the SOFR close, when it rolled and where its five-year contract matures. It then pins a 1.2% flat hazard for the index under the series' identity and values five years of protection at the family's coupon.

    Rust
    use chrono::{Datelike, NaiveDate};
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, CollateralSpec, CreditCurveId, CreditIndexFamily, DayCountFraction, FlatHazard,
        IndexCreditDefaultSwap, MarketContext, QuoteSheet, Survival,
    };
    
    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 FAMILY: &str = "CDX.NA.IG";
    const HAZARD: f64 = 0.012;
    const RECOVERY: f64 = 0.4;
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let family = FAMILY.parse::<CreditIndexFamily>()?.template();
    
        // The series on the run at the close, the date it rolled, and where its
        // five-year contract matures.
        let series = family.on_the_run(close);
        let rolled = family.series_roll(series);
        let maturity = family.series_maturity(series, 5);
        println!("series     {series}");
        println!("rolled     {rolled}");
        println!("5Y matures {maturity}");
    
        // The index curve is pinned under the series' identity, FAMILY.SERIESvVERSION.
        let identity = format!("{FAMILY}.{series}v1");
        let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
        let credit: CreditCurveId = identity.parse()?;
        let survival = Survival::FlatHazard(FlatHazard::new(close, HAZARD, DayCountFraction::Act365Fixed)?);
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?
            .with_credit_curve(credit, survival, Some(RECOVERY));
        let index = AnyInstrument::IndexCds(Box::new(
            IndexCreditDefaultSwap::standard(&family, close, series, 5, 10_000_000.0, true).with_version(1),
        ));
        let npv = index.npv(&market)?;
        let par = index.par_spread(&market)?;
        println!("credit     {identity}");
        println!("npv        {npv:.2}");
        println!("par spread {par:.6}");
        // The buyer pays the family's 100bp coupon for protection worth about 72bp, so
        // the contract has a negative value to the buyer.
    
        Ok(())
    }
    Python
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    FAMILY = "CDX.NA.IG"
    HAZARD, RECOVERY = 0.012, 0.4
    
    # The series on the run at the close, the date it rolled, and where its
    # five-year contract matures.
    series = hlx.credit.on_the_run(FAMILY, SOFR_CLOSE_DATE)
    rolled = hlx.credit.series_roll(FAMILY, series)
    maturity = hlx.credit.series_maturity(FAMILY, series, 5)
    print(f"series     {series}")
    print(f"rolled     {rolled}")
    print(f"5Y matures {maturity}")
    
    # The index curve is pinned under the series' identity, FAMILY.SERIESvVERSION.
    identity = f"{FAMILY}.{series}v1"
    market = (
        hlx.MarketContext(SOFR_CLOSE_DATE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_credit_curve(identity, hlx.curves.SurvivalCurve.flat_hazard(SOFR_CLOSE_DATE, HAZARD), recovery=RECOVERY)
    )
    index = hlx.products.index_credit_default_swap(FAMILY, SOFR_CLOSE_DATE, series, 5, notional=10_000_000.0)
    npv = index.npv(market)
    par = index.par_spread(market)
    print(f"credit     {identity}")
    print(f"npv        {npv:.2f}")
    print(f"par spread {par:.6f}")
    # The buyer pays the family's 100bp coupon for protection worth about 72bp, so
    # the contract has a negative value to the buyer.
    series     47
    rolled     2026-09-20
    5Y matures 2031-12-20
    credit     CDX.NA.IG.47v1
    npv        -129653.61
    par spread 0.007144

    The buyer pays the family's 100bp coupon for protection worth about 71bp, so the contract is worth less than nothing to the buyer.

    How it's priced

    For a family anchored at series aa rolling on rar_a, series kk rolls on

    rk=ra+6(k−a) months,r_k = r_a + 6(k - a) \text{ months},

    the series on the run at tt is the largest kk with rk≤tr_k \le t, and its nn-year contract matures three months after rkr_k plus nn years. The legs are those of a single-name CDS on the index curve, at the family's fixed coupon, on the notional times the index factor.

    API

    Argument Type Default Meaning
    family str required the index family, "CDX.NA.IG" or "ITRAXX.EUROPE"
    trade_date date required the trade date
    series int required the series, such as the one credit.on_the_run returns
    tenor_years int required whole years, matured three months after the series' roll plus the tenor
    notional float required the protected amount before the index factor
    buys_protection bool True False books the seller
    version int 1 the index version; each credit event moves the contract to the next
    index_factor float None the remaining notional fraction, in (0, 1], after names have defaulted
    upfront float None the upfront the buyer pays at settlement, as a decimal of the notional
    fixed_recovery float None a recovery the contract fixes; None reads the market's
    protection_start date None a forward start, as on the single-name contract

    The series functions take the family: credit.on_the_run(family, date), credit.series_roll(family, series) and credit.series_maturity(family, series, years). In Rust they are methods of CreditIndexTemplate, and the contract is IndexCreditDefaultSwap::standard.

    Symbol Meaning Rust Python
    on the run at tt the latest series rolled by tt CreditIndexTemplate::on_the_run credit.on_the_run
    rkr_k series kk's roll CreditIndexTemplate::series_roll credit.series_roll
    maturity three months after rkr_k, plus the tenor CreditIndexTemplate::series_maturity credit.series_maturity
    the index curve pinned under FAMILY.SERIESvVERSION MarketContext::with_credit_curve MarketContext.with_credit_curve
    the contract index CDS on a series IndexCreditDefaultSwap::standard products.index_credit_default_swap
    intrinsic the index against its constituents index_intrinsic credit.index_intrinsic
    skew hazard multipliers that reprice the index from its constituents index_skew credit.index_skew

    Defaults and limits

    • The coupon: an index contract always pays its family's standard coupon; there is no coupon argument. Each family's coupon and schedule are in the credit conventions catalog.
    • The curve: the contract values on the curve pinned under FAMILY.SERIESvVERSION, such as CDX.NA.IG.47v1. A new version after a credit event needs its own curve; see credit events.
    • The maturity is fixed by the series, so an off-the-run contract keeps the maturity it had when it was on the run.

    See also

    • Source: crates/helixrs/src/statics/templates/credit.rs, crates/helixrs/src/instruments/credit.rs
    • Related pages:
      • How credit products work, the ideas behind the index.
      • Credit default swap, for the legs.
      • CDO tranche, for slicing the index pool.
      • Credit events, for a default inside the index.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.