Esc

    The API is under testing and may change before release.

    Legs and coupons

    This page lists what you need to build a leg on its own: the coupon and strip constructors, the Leg that holds them, and how a leg is valued. A coupon is one dated amount and the rule that fixes it; a leg is coupons in payment order, on one side of a trade, discounted on one key.

    Quick example

    This builds a receive-fixed EUR leg and a pay-EURIBOR leg over two years, values each, and reads the floating leg's value a second way, from its own cashflow table. The example reads its quotes from the desk sheets; load them once and it runs as shown.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{EUR_CLOSE, SOFR_CLOSE_DATE};
    use helixrs::{
        AnyIndex, CollateralSpec, Direction, Leg, LegTerms, MarketContext, PaymentRule, QuoteSheet,
        Schedule, YieldCurve, calendar, fixed_rate_leg, ibor_leg_on_index, index, leg_cashflow_records,
    };
    
    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")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let (start, end) = (date((2026, 10, 5)), date((2028, 10, 5)));
        let eur = CollateralSpec::Currency("EUR".parse()?);
    
        // The EUR recipe solves a €STR curve that discounts and a EURIBOR 3M curve that
        // projects.
        let sheet = QuoteSheet::from_rows(EUR_CLOSE.iter().copied())?;
        let market = MarketContext::as_of(close).with_recipe_quotes(&eur, &sheet)?;
        for (role, key) in market.contents() {
            println!("{role:<11} {key}");
        }
    
        // Two strips over two schedules on the TARGET calendar: annual fixed coupons at
        // 2.20% on 30E/360, and quarterly EURIBOR 3M coupons on ACT/360. Each leg says
        // its side and the key it is discounted on.
        let target = calendar("EUTA")?;
        let generate = |frequency: &str| -> Outcome<Schedule> {
            Ok(Schedule::generate(
                start,
                end,
                frequency.parse()?,
                target.as_ref(),
                "ModFollowing".parse()?,
                "None".parse()?,
                "ShortInitial".parse()?,
                false,
                None,
                None,
            )?)
        };
        let terms = |day_count: &str, direction: Direction| -> Outcome<LegTerms> {
            Ok(LegTerms {
                notional: 10_000_000.0,
                day_count: day_count.parse()?,
                currency: "EUR".parse()?,
                direction,
                payment: PaymentRule::none(),
            })
        };
        let Some(AnyIndex::Ibor(euribor)) = index("EUR-EURIBOR-3M") else {
            panic!("a registered term index");
        };
        let fixed: Leg = fixed_rate_leg(
            &generate("1Y")?,
            &terms("30E/360", Direction::Receive)?,
            0.0220,
        );
        let floating: Leg = ibor_leg_on_index(
            &generate("3M")?,
            &terms("ACT/360", Direction::Pay)?,
            &euribor,
            1.0,
            0.0,
        );
        println!(
            "fixed     {} coupons, {:?}",
            fixed.coupons().len(),
            fixed.direction()
        );
        println!(
            "floating  {} coupons, {:?}",
            floating.coupons().len(),
            floating.direction()
        );
    
        // Each leg is valued on its own. A floating coupon's amount is projected, and
        // the leg's own table carries each projection and its present value.
        let fixed_npv = fixed.npv_on(&market, &eur)?;
        let floating_npv = floating.npv_on(&market, &eur)?;
        let projections = floating.projections();
        let table = market.priced(&floating.risk_factors(&eur, &projections), |view| {
            Ok(leg_cashflow_records(&floating, &view.disc, view, &view.fixings, close)?)
        })?;
        let by_table: f64 = table["pv"].numbers().expect("numbers").iter().sum();
        println!("fixed leg     {fixed_npv:.2}");
        println!("floating leg  {floating_npv:.2}");
        println!("  by table    {by_table:.2}");
        println!("swap          {:.2}", fixed_npv + floating_npv);
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import EUR_CLOSE, SOFR_CLOSE_DATE
    
    START, END = dt.date(2026, 10, 5), dt.date(2028, 10, 5)
    
    # The EUR recipe solves a €STR curve that discounts and a EURIBOR 3M curve that
    # projects.
    market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("EUR", EUR_CLOSE)
    for role, key in market.contents():
        print(f"{role:<11} {key}")
    
    # Two strips over two schedules on the TARGET calendar: annual fixed coupons at
    # 2.20% on 30E/360, and quarterly EURIBOR 3M coupons on ACT/360. Each leg says
    # its side and the key it is discounted on.
    target = hlx.Calendar("EUTA")
    annual = hlx.dates.schedule(START, END, "1Y", target)
    quarterly = hlx.dates.schedule(START, END, "3M", target)
    fixed = hlx.Leg(
        hlx.legs.fixed_rate_leg(
            annual, annual.paying(0, target), "EUR", notional=10_000_000.0, fixed_rate=0.0220, day_count="30E/360"
        ),
        "Receive",
        "EUR",
    )
    floating = hlx.Leg(
        hlx.legs.ibor_leg(
            quarterly, quarterly.paying(0, target), "EUR", notional=10_000_000.0, index="EUR-EURIBOR-3M", day_count="ACT/360"
        ),
        "Pay",
        "EUR",
    )
    print(f"fixed     {len(fixed.coupons())} coupons, {fixed.direction()}")
    print(f"floating  {len(floating.coupons())} coupons, {floating.direction()}")
    
    # Each leg is valued on its own. A floating coupon's amount is projected, and
    # the leg's own table carries each projection and its present value.
    fixed_npv = fixed.npv(market)
    floating_npv = floating.npv(market)
    by_table = sum(floating.cashflow_records(market).to_dict()["pv"])
    print(f"fixed leg     {fixed_npv:.2f}")
    print(f"floating leg  {floating_npv:.2f}")
    print(f"  by table    {by_table:.2f}")
    print(f"swap          {fixed_npv + floating_npv:.2f}")
    discount    EUR
    projection  EUR-ESTR
    projection  EUR-EURIBOR-3M
    fixed     2 coupons, Receive
    floating  8 coupons, Pay
    fixed leg     426971.49
    floating leg  -419208.38
      by table    -419208.38
    swap          7763.12

    The floating leg's value is the sum of its table's present values. The floating leg is paid, so it is worth less than nothing.

    How it's priced

    A coupon ii pays Ni τi ciN_i\, \tau_i\, c_i on its payment date TiT_i: notional, accrual fraction and rate. A fixed coupon's rate is in its contract; a floating coupon's is the index's fixing, or its projection while the fixing is in the future. A leg on side ss (+1+1 to receive, −1-1 to pay), discounted on curve DD, is worth

    Vleg=s∑iNi τi ci D(Ti).V_{\text{leg}} = s \sum_i N_i\, \tau_i\, c_i\, D(T_i).

    The same sum built one coupon at a time is in how cashflows work.

    API

    Symbol Meaning Rust Python
    NiτiciN_i \tau_i c_i, fixed one fixed coupon FixedRateCoupon legs.fixed_rate_coupon
    fixed strip one fixed coupon per period fixed_rate_leg legs.fixed_rate_leg
    floating strip one IBOR coupon per period ibor_leg_on_index legs.ibor_leg
    ss the side Direction in LegTerms Leg(coupons, direction, discount)
    VlegV_{\text{leg}} the leg's value Leg::npv_on(&market, &discount) Leg.npv(market)
    needs what the leg reads Leg::projections, Leg::risk_factors Leg.market_data_needs
    the table the leg's cashflow rows leg_cashflow_records inside MarketContext::priced Leg.cashflow_records

    Leg(...):

    Argument Type Default Meaning
    coupons list[AnyCoupon] required the coupons, in payment order, as a strip or coupon constructor returns them
    direction str "Receive" "Receive" or "Pay"
    discount str "USD" the key the leg is discounted on: the currency of its cash collateral

    legs.fixed_rate_leg(...) and legs.ibor_leg(...):

    Argument Type Default Meaning
    schedule Schedule required the accrual periods, from dates.schedule
    payment_dates list[date] required one per period, from Schedule.paying
    currency str required the currency the coupons pay in
    notional float or list[float] required one notional, or one per period for an amortising strip
    fixed_rate float required (fixed) the contract rate
    index str required (IBOR) the term index, such as "EUR-EURIBOR-3M"
    day_count str required the accrual day count, such as "30E/360"
    gearing, spread float 1.0, 0.0 IBOR only: the coupon pays gearing × fixing + spread
    stub str or pair "own_accrual" IBOR only: how a short period reads its index; see from a schedule to a leg
    projection str "index_tenor" IBOR only: "accrual_period" projects every coupon over its own accrual instead

    legs.fixed_rate_coupon(currency, accrual_start, accrual_end, payment_date, *, notional, fixed_rate, day_count) builds one coupon; its arguments mean what they mean on the strip.

    Defaults and limits

    • Collateral: a leg doesn't carry its collateral; a trade does. A leg built on its own names the key it's discounted on, and in Rust Leg::npv_on takes it as an argument.
    • Indices: a leg's floating coupons may name any number of indices. The leg declares the first two as its projection and second_projection, and any further ones as its projection_basket, so a market missing one names it.
    • Reading a coupon: each coupon reports accrual_start, accrual_end, payment_date, year_fraction and, for a fixed coupon, amount.
    • Basis points by flow: a leg's sensitivity to one basis point on each flow is on cashflow records.
    • A zero-coupon fixed leg, which accrues into one payment, is on zero-coupon swap.

    See also

    • Source: crates/helixrs/src/cashflows/coupons.rs, crates/helixrs/src/cashflows/legs.rs, crates/helixio/src/legs.rs
    • Related pages:
      • How cashflows work, the ideas behind coupons and legs.
      • Build a leg from a schedule, step by step.
      • Cashflow records, the table every leg reports.

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