Esc

    The API is under testing and may change before release.

    Credit-linked swap

    This page lists everything you need to book and value a credit-linked swap: the function's arguments and defaults, the roles a leg can take, how it's priced, and an example you can run. A credit-linked swap is a set of legs whose cash depends on whether a reference entity survives. What each role means 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 books five annual 5% coupons on ten million as one leg against ACME. It values the leg as independent and as contingent on a 2% hazard, then contingent where default can't happen, then under each rule for the accrual a default interrupts.

    Rust
    use std::sync::Arc;
    
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AccrualOnDefault, AnyInstrument, CollateralSpec, CreditLinkedSwap, Currency, DayCountFraction,
        DefaultPaymentTime, Direction, FlatHazard, LegRole, LegTerms, MarketContext, PaymentRule,
        QuoteSheet, RoleLeg, Schedule, Survival, fixed_rate_leg,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    const ENTITY: &str = "ACME/SeniorUnSec/XR14/USD";
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let usd: Currency = "USD".parse()?;
        let rates = MarketContext::as_of(close).with_recipe_quotes(
            &CollateralSpec::Currency(usd),
            &QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?,
        )?;
        // The SOFR market with ACME on a flat hazard curve, recovering 40%.
        let credit = |hazard: f64| -> Outcome<MarketContext> {
            let curve = Survival::FlatHazard(FlatHazard::new(close, hazard, DayCountFraction::Act365Fixed)?);
            Ok(rates
                .clone()
                .with_credit_curve(ENTITY.parse()?, Arc::new(curve), Some(0.40)))
        };
        // Five annual 5% coupons on ten million, received.
        let schedule = Schedule::from_dates((0..6).map(|i| date((2026 + i, 10, 5))).collect())?;
        let terms = LegTerms {
            notional: 10_000_000.0,
            day_count: DayCountFraction::Thirty360,
            currency: usd,
            direction: Direction::Receive,
            payment: PaymentRule::none(),
        };
        let coupons = fixed_rate_leg(&schedule, &terms, 0.05);
        // The coupons as one leg of a credit-linked swap on ACME, in `role`.
        let swap = |role: LegRole, accrual: AccrualOnDefault| -> Outcome<AnyInstrument> {
            Ok(AnyInstrument::CreditLinkedSwap(Box::new(CreditLinkedSwap::new(
                ENTITY.parse()?,
                usd,
                CollateralSpec::Currency(usd),
                vec![RoleLeg::new(coupons.clone(), role, CollateralSpec::Currency(usd))],
                accrual,
                None,
                DefaultPaymentTime::AtDefault,
            )?)))
        };
    
        // On a 2% hazard, the independent leg is worth what it is without credit, and
        // the contingent leg less: each coupon is weighted by the chance ACME survives.
        let (risky, riskless) = (credit(0.02)?, credit(0.0)?);
        let independent = swap(LegRole::Independent, AccrualOnDefault::Halfway)?.npv(&risky)?;
        let contingent = swap(LegRole::Contingent, AccrualOnDefault::Halfway)?.npv(&risky)?;
        println!("independent  {independent:.2}");
        println!("contingent   {contingent:.2}");
    
        // Where default cannot happen, the contingent leg is the independent one.
        let certain = swap(LegRole::Contingent, AccrualOnDefault::Halfway)?.npv(&riskless)?;
        println!("contingent   {certain:.2}");
    
        // Settling a coupon's accrual to the default date, by the halfway rule, pays
        // part of the coupon a default interrupts; settling none pays nothing of it.
        let halfway = swap(LegRole::Contingent, AccrualOnDefault::Halfway)?.npv(&risky)?;
        let none = swap(LegRole::Contingent, AccrualOnDefault::None)?.npv(&risky)?;
        println!("halfway      {halfway:.2}");
        println!("none         {none:.2}");
        let worth = halfway - none;
        println!("accrual worth {worth:.2}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    ENTITY = "ACME/SeniorUnSec/XR14/USD"
    rates = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
    
    def credit(hazard):
        """The SOFR market with ACME on a flat hazard curve, recovering 40%."""
        return rates.with_credit_curve(ENTITY, hlx.curves.SurvivalCurve.flat_hazard(SOFR_CLOSE_DATE, hazard), 0.40)
    
    # Five annual 5% coupons on ten million, received.
    schedule = hlx.dates.Schedule.from_dates([dt.date(2026 + i, 10, 5) for i in range(6)])
    coupons = hlx.legs.fixed_rate_leg(
        schedule, schedule.dates()[1:], "USD", notional=10_000_000.0, fixed_rate=0.05, day_count="30/360"
    )
    
    def swap(role, **kw):
        """The coupons as one leg of a credit-linked swap on ACME, in `role`."""
        return hlx.products.credit_linked_swap(ENTITY, "USD", [(hlx.Leg(coupons, "Receive", "USD"), role)], **kw)
    
    # On a 2% hazard, the independent leg is worth what it is without credit, and
    # the contingent leg less: each coupon is weighted by the chance ACME survives.
    risky, riskless = credit(0.02), credit(0.0)
    independent = swap("independent").npv(risky)
    contingent = swap("contingent").npv(risky)
    print(f"independent  {independent:.2f}")
    print(f"contingent   {contingent:.2f}")
    
    # Where default cannot happen, the contingent leg is the independent one.
    certain = swap("contingent").npv(riskless)
    print(f"contingent   {certain:.2f}")
    
    # Settling a coupon's accrual to the default date, by the halfway rule, pays
    # part of the coupon a default interrupts; settling none pays nothing of it.
    halfway = swap("contingent", settles_accrual=True).npv(risky)
    none = swap("contingent", settles_accrual=False).npv(risky)
    print(f"halfway      {halfway:.2f}")
    print(f"none         {none:.2f}")
    worth = halfway - none
    print(f"accrual worth {worth:.2f}")
    independent  2179956.34
    contingent   2078431.05
    contingent   2179956.34
    halfway      2078431.05
    none         2057191.45
    accrual worth 21239.60

    Credit leaves the independent leg alone and lowers the contingent one. Where default can't happen, the two agree. Settling the interrupted coupon's accrual by the halfway rule is the default, and it is worth more than settling nothing.

    How it's priced

    An independent leg is valued as it is. A contingent leg's flow at tit_i is weighted by the survival probability Q(ti)Q(t_i). With accrual settled on default, the period in which default falls also pays half its coupon, weighted by the probability of default in the period, Q(ti−1)−Q(ti)Q(t_{i-1}) - Q(t_i). A default leg pays on default scaled by the loss 1−R1 - R, and a recovery leg scaled by RR.

    API

    Argument Type Default Meaning
    reference str required the entity's full identity, such as "ACME/SeniorUnSec/XR14/USD"
    currency str required the trade's currency
    legs list[tuple[Leg, LegRole or str]] required each leg as built, with its role: "independent", "contingent", "default" or "recovery"
    settles_accrual bool None, read as True True settles a contingent coupon's accrual to default by the halfway rule, False settles none
    accrual_on_default AccrualOnDefault or str None names the accrual rule instead, "proportional" among them; not with settles_accrual
    fixed_recovery float None a recovery the contract fixes; None reads the market's
    default_payment_time DefaultPaymentTime or str "at_default" when a default payment is made: "at_default", "at_period_end" or "at_maturity"

    In Rust the trade is CreditLinkedSwap::new(credit, currency, collateral, legs, accrual, recovery, payment_time), each leg a RoleLeg::new(leg, LegRole::Contingent, collateral).

    Symbol Meaning Rust Python
    the trade legs tagged by role against one entity CreditLinkedSwap::new products.credit_linked_swap
    the role how a leg depends on default RoleLeg::new(leg, LegRole::Contingent, ..) (leg, "contingent")
    QQ, RR the entity's survival and recovery MarketContext::with_credit_curve MarketContext.with_credit_curve
    VV its value AnyInstrument::npv Instrument.npv

    Defaults and limits

    • Accrual: give settles_accrual or accrual_on_default, not both. With neither, a contingent coupon settles its accrual by the halfway rule.
    • Currencies: a leg in another currency prices on the entity's curve in that currency and converts at spot.
    • Conventions: the accrual and recovery conventions a credit trade states are in the credit conventions catalog.
    • The legs: build each leg as on legs and coupons; the swap takes it as built, on its own discount key.

    See also

    • Source: crates/helixrs/src/instruments/credit_linked.rs
    • Related pages:
      • How credit products work, the ideas behind the roles.
      • Credit-linked note, for the funded form.
      • Credit default swap, for the protection a contingent leg sells.
      • Survival curves, for the entity's curve.

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