Esc

    The API is under testing and may change before release.

    Structured notes

    This page lists what you need to price a structured note: the constructor, the price per 100 it reports, the spread a quote implies over the issuer's discounting, the issuer's credit, and an example you can run. A structured note is a bond whose coupons are a leg built to order, such as a floating, capped or CMS rate; the investor takes the issuer's credit along with it.

    Quick example

    The example reads its quotes from the desk sheets; load them once and it runs as shown.

    This wraps two years of quarterly EURIBOR 3M coupons on ten million, from 5 October 2026, as a note. It values the note as its coupons plus its discounted face, finds the spread a 97.00 quote implies and pins it to reprice the quote, and weights the note by its issuer's survival on a 2% hazard.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{EUR_CLOSE, SOFR_CLOSE_DATE};
    use helixrs::{
        AnyIndex, AnyInstrument, CollateralSpec, CreditCurveId, Curve, Direction, FlatHazard,
        FlatSpread, LegTerms, MarketContext, PaymentRule, QuoteSheet, RiskFactors, Schedule,
        StructuredNote, Survival, YieldCurve, calendar, ibor_leg_on_index, index, note_dirty_price,
        note_z_spread,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    const FACE: f64 = 10_000_000.0;
    const ISSUER: &str = "ACME/SeniorUnSec/MM14/EUR";
    
    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 eur = CollateralSpec::Currency("EUR".parse()?);
    
        // The coupons are an ordinary leg; the note receives it and redeems the face
        // on the last payment date.
        let sheet = QuoteSheet::from_rows(EUR_CLOSE.iter().copied())?;
        let market = MarketContext::as_of(close).with_recipe_quotes(&eur, &sheet)?;
        let target = calendar("EUTA")?;
        let quarterly = Schedule::generate(
            date((2026, 10, 5)),
            date((2028, 10, 5)),
            "3M".parse()?,
            target.as_ref(),
            "ModFollowing".parse()?,
            "None".parse()?,
            "ShortInitial".parse()?,
            false,
            None,
            None,
        )?;
        let terms = LegTerms {
            notional: FACE,
            day_count: "ACT/360".parse()?,
            currency: "EUR".parse()?,
            direction: Direction::Receive,
            payment: PaymentRule::none(),
        };
        let Some(AnyIndex::Ibor(euribor)) = index("EUR-EURIBOR-3M") else {
            panic!("a registered term index");
        };
        let leg = ibor_leg_on_index(&quarterly, &terms, &euribor, 1.0, 0.0);
        let lifted = market.lift::<f64>();
        let view = market.view(&lifted)?;
        let resolved = view.resolve(&RiskFactors::discounted_by(&eur).projecting("EUR-EURIBOR-3M"))?;
        let coupons: f64 = leg.npv::<f64, _>(&resolved.disc, &resolved, &resolved.fixings)?;
        let note = StructuredNote::new(&leg, FACE, eur.clone())?;
        let plain = AnyInstrument::StructuredNote(Box::new(note.clone()));
        let value = plain.npv(&market)?;
        let last_payment = leg
            .coupons()
            .iter()
            .map(|coupon| coupon.payment_date())
            .max()
            .expect("a coupon");
        let face_today = FACE * resolved.disc.discount(last_payment)?;
        let dirty = note_dirty_price(&plain, &market)?;
        println!("coupons           {coupons:.2}");
        println!("face, discounted  {face_today:.2}");
        println!("note              {value:.2}");
        println!("per 100           {dirty:.6}");
    
        // A quote below the risk-free value implies a spread on the issuer's
        // discounting. Pinned under the note's key, that spread gives the quote back.
        let quote = 97.0;
        let z = note_z_spread(&plain, &market, quote)?;
        let pinned = market
            .clone()
            .with_security_spread("ACME-NOTE", Curve::FlatSpread(FlatSpread::new(close, z)?));
        let marked = AnyInstrument::StructuredNote(Box::new(note.clone().spread_by("ACME-NOTE")));
        println!("quoted per 100    {quote:.2}");
        println!("z-spread          {:.4} bp", z * 10_000.0);
        println!("repriced per 100  {:.6}", note_dirty_price(&marked, &pinned)?);
    
        // Tagged to its issuer, the note is weighted by the issuer's survival and
        // recovers part of its face on default. With no default risk it is the plain
        // note; with some, it is worth less.
        let issuer: CreditCurveId = ISSUER.parse()?;
        let with_hazard = |hazard: f64| -> Outcome<MarketContext> {
            let curve = Survival::FlatHazard(FlatHazard::new(close, hazard, "ACT/365.FIXED".parse()?)?);
            Ok(market.clone().with_credit_curve(issuer.clone(), curve, Some(0.4)))
        };
        let tagged = AnyInstrument::StructuredNote(Box::new(note.issued_by(issuer.clone())));
        let riskless = tagged.npv(&with_hazard(0.0)?)?;
        let risky = tagged.npv(&with_hazard(0.02)?)?;
        println!("no default risk   {riskless:.2}");
        println!("hazard 2%         {risky:.2}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import EUR_CLOSE, SOFR_CLOSE_DATE
    
    FACE = 10_000_000.0
    ISSUER = "ACME/SeniorUnSec/MM14/EUR"
    
    # The coupons are an ordinary leg; the note receives it and redeems the face
    # on the last payment date.
    market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("EUR", EUR_CLOSE)
    target = hlx.Calendar("EUTA")
    quarterly = hlx.dates.schedule(dt.date(2026, 10, 5), dt.date(2028, 10, 5), "3M", target)
    paying = quarterly.paying(0, target)
    leg = hlx.Leg(
        hlx.legs.ibor_leg(quarterly, paying, "EUR", notional=FACE, index="EUR-EURIBOR-3M", day_count="ACT/360"),
        "Receive",
        "EUR",
    )
    note = hlx.products.structured_note(leg, face=FACE)
    value = note.npv(market)
    face_today = FACE * market.curve("discount(EUR)").discount(paying[-1])
    dirty = hlx.bonds.note_dirty_price(note, market)
    print(f"coupons           {leg.npv(market):.2f}")
    print(f"face, discounted  {face_today:.2f}")
    print(f"note              {value:.2f}")
    print(f"per 100           {dirty:.6f}")
    
    # A quote below the risk-free value implies a spread on the issuer's
    # discounting. Pinned under the note's key, that spread gives the quote back.
    quote = 97.0
    z = hlx.bonds.note_z_spread(note, market, quote)
    pinned = market.with_security_spread("ACME-NOTE", hlx.curves.SpreadCurve.flat(SOFR_CLOSE_DATE, z))
    marked = hlx.products.structured_note(leg, face=FACE, security_spread="ACME-NOTE")
    print(f"quoted per 100    {quote:.2f}")
    print(f"z-spread          {z * 10_000:.4f} bp")
    print(f"repriced per 100  {hlx.bonds.note_dirty_price(marked, pinned):.6f}")
    
    # Tagged to its issuer, the note is weighted by the issuer's survival and
    # recovers part of its face on default. With no default risk it is the plain
    # note; with some, it is worth less.
    def with_hazard(hazard):
        curve = hlx.curves.SurvivalCurve.flat_hazard(SOFR_CLOSE_DATE, hazard)
        return market.with_credit_curve(ISSUER, curve, 0.4)
    
    tagged = hlx.products.structured_note(leg, face=FACE, issuer=ISSUER)
    riskless = tagged.npv(with_hazard(0.0))
    risky = tagged.npv(with_hazard(0.02))
    print(f"no default risk   {riskless:.2f}")
    print(f"hazard 2%         {risky:.2f}")
    coupons           419208.38
    face, discounted  9603774.19
    note              10022982.57
    per 100           100.229826
    quoted per 100    97.00
    z-spread          165.6827 bp
    repriced per 100  97.000000
    no default risk   10022982.57
    hazard 2%         9788359.35

    The note is its coupons plus its discounted face. A 97.00 quote sits below that risk-free value, so it implies a positive spread on the issuer's discounting; pinned under the note's key, the spread gives the quote back. Tagged to its issuer, the note is worth the plain note with no default risk, and less on a 2% hazard.

    How it's priced

    With coupons worth LL on the market and face NN paid at the last payment date TT:

    V=L+N D(T),P100=100 VN.V = L + N\,D(T), \qquad P_{100} = 100\,\frac{V}{N}.

    A flat spread zz on the issuer's discounting scales each flow at time tt by e−zte^{-zt}, with the coupons still projected spread-free. Tagged to its issuer, each flow is weighted by the issuer's survival, and each period adds a recovery of face weighted by the chance of default in it. How a note's price carries its issuer is covered in how bonds work.

    API

    Argument Type Default Meaning
    leg Leg required the coupons, received; any leg helixio builds: fixed, floating, collared, CMS or CMS spread
    face float required the face redeemed on the last payment date
    issuer str None tags the issuer, so a credit curve and recovery value the note risky
    security_spread str None a key whose spread curve the note discounts under

    products.structured_note(leg, *, face, issuer, security_spread) builds it; in Rust, StructuredNote::new(&leg, face, collateral)?, with .issued_by(credit) and .spread_by(key).

    Symbol Meaning Rust Python
    LL the coupon leg Leg::npv Leg.npv
    VV the note AnyInstrument::npv on AnyInstrument::StructuredNote Instrument.npv
    P100P_{100} the note per 100 of face note_dirty_price(&note, &market)? bonds.note_dirty_price(note, market)
    zz the z-spread of a quote note_z_spread(&note, &market, quote)? bonds.note_z_spread(note, market, quote)

    Defaults and limits

    • The leg is received. A leg the holder would pay is refused.
    • Market inputs: whatever the leg needs (its discount and projection curves, and any fixings or vols), plus a credit curve and recovery when the note is tagged to an issuer, or a spread curve under its security_spread key.
    • The z-spread applies to the discounting only; the coupons are projected spread-free.

    See also

    • Source: crates/helixrs/src/instruments/structured.rs
    • Related pages:
      • How bonds work, for how a note carries its issuer.
      • Legs and coupons, for the coupons a note wraps.
      • Build a leg from a schedule, step by step.
      • Spread measures, for the same spreads on a fixed-rate bond.

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