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.
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(())
}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 pays on its payment date : 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 ( to receive, to pay), discounted on curve , is worth
The same sum built one coupon at a time is in how cashflows work.
API
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| , 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 |
| the side | Direction in LegTerms |
Leg(coupons, direction, discount) |
|
| 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_ontakes it as an argument. - Indices: a leg's floating coupons may name any number of indices. The leg declares the
first two as its
projectionandsecond_projection, and any further ones as itsprojection_basket, so a market missing one names it. - Reading a coupon: each coupon reports
accrual_start,accrual_end,payment_date,year_fractionand, 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.