Build a leg from a schedule
You'll get a fixed leg and a floating leg from a confirmation's terms: the schedule's periods, a payment lag, the coupons over each period, and the side each leg is on. Nothing here needs a market; you value the legs later against whatever market you have.
Before you start
- helixio installed, in Rust or Python: see installing.
- The terms from the confirmation: effective and termination dates, frequency, calendar, payment lag, notional, and each leg's rate or index and day count.
- If the term isn't a whole number of periods, which stub rule the confirmation elects for the floating leg. The rules are listed on from a schedule to a leg.
Steps
- Generate the schedule with
dates.schedule(effective, termination, frequency, calendar). It measures periods back from the termination date, so a short period lands at the front. In Rust,Schedule::generatetakes the roll convention and the stub position as arguments. - Get one payment date per period with
schedule.paying(lag, calendar). In Rust the lag is aPaymentRule::business_days(lag, calendar)insideLegTerms. - Build each strip over the schedule:
legs.fixed_rate_legwith the fixed rate, andlegs.ibor_legwith the index and the stub rule asstub=. - Wrap each strip in a
Legwith its side,"Pay"or"Receive", and the key it's discounted on. In Rust the side is inLegTerms, and the strip builders return theLeg. - Read the coupons back to check the dates and amounts against the confirmation.
The comments in the program match these steps.
Rust
use chrono::NaiveDate;
use helixrs::{
AnyIndex, Direction, LegTerms, PaymentRule, Schedule, StubRule, calendar, fixed_rate_leg,
ibor_leg_with_stubs, index,
};
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 (effective, termination) = (date((2026, 11, 20)), date((2029, 8, 20)));
let (notional, fixed_rate) = (5_000_000.0, 0.0275);
// Semiannual periods on the TARGET calendar, measured back from the end, so the
// first period is the short one.
let target = calendar("EUTA")?;
let schedule = Schedule::generate(
effective,
termination,
"6M".parse()?,
target.as_ref(),
"ModFollowing".parse()?,
"None".parse()?,
"ShortInitial".parse()?,
false,
None,
None,
)?;
let paying = PaymentRule::business_days(2, target.clone());
// The fixed strip pays 2.75% on 30E/360; the floating strip reads EURIBOR 6M on
// ACT/360, its short first period at the index's own six-month window.
let terms = |day_count: &str, direction: Direction| -> Outcome<LegTerms> {
Ok(LegTerms {
notional,
day_count: day_count.parse()?,
currency: "EUR".parse()?,
direction,
payment: paying.clone(),
})
};
let Some(AnyIndex::Ibor(euribor)) = index("EUR-EURIBOR-6M") else {
panic!("a registered term index");
};
let fixed = fixed_rate_leg(&schedule, &terms("30E/360", Direction::Pay)?, fixed_rate);
let floating = ibor_leg_with_stubs(
&schedule,
&terms("ACT/360", Direction::Receive)?,
&euribor,
1.0,
0.0,
&StubRule::IndexTenor,
)?;
// Each fixed coupon: its accrual, its payment date and its amount.
for coupon in fixed.coupons() {
println!(
"{} {} pays {} {:.6} {:10.2}",
coupon.accrual_start().expect("an accrual start"),
coupon.accrual_end().expect("an accrual end"),
coupon.payment_date(),
coupon.year_fraction(),
coupon.amount().expect("a fixed amount")
);
}
println!("fixed {} coupons, {:?}", fixed.coupons().len(), fixed.direction());
println!("floating {} coupons, {:?}", floating.coupons().len(), floating.direction());
Ok(())
}Python
import datetime as dt
import helixio as hlx
EFFECTIVE, TERMINATION = dt.date(2026, 11, 20), dt.date(2029, 8, 20)
NOTIONAL, FIXED_RATE = 5_000_000.0, 0.0275
# Semiannual periods on the TARGET calendar, measured back from the end, so the
# first period is the short one.
target = hlx.Calendar("EUTA")
schedule = hlx.dates.schedule(EFFECTIVE, TERMINATION, "6M", target)
paying = schedule.paying(2, target)
# The fixed strip pays 2.75% on 30E/360; the floating strip reads EURIBOR 6M on
# ACT/360, its short first period at the index's own six-month window.
fixed = hlx.Leg(
hlx.legs.fixed_rate_leg(
schedule, paying, "EUR", notional=NOTIONAL, fixed_rate=FIXED_RATE, day_count="30E/360"
),
"Pay",
"EUR",
)
floating = hlx.Leg(
hlx.legs.ibor_leg(
schedule, paying, "EUR", notional=NOTIONAL, index="EUR-EURIBOR-6M", day_count="ACT/360",
stub="index_tenor",
),
"Receive",
"EUR",
)
# Each fixed coupon: its accrual, its payment date and its amount.
for coupon in fixed.coupons():
print(
f"{coupon.accrual_start()} {coupon.accrual_end()} pays {coupon.payment_date()}"
f" {coupon.year_fraction():.6f} {coupon.amount():10.2f}"
)
print(f"fixed {len(fixed.coupons())} coupons, {fixed.direction()}")
print(f"floating {len(floating.coupons())} coupons, {floating.direction()}")2026-11-20 2027-02-22 pays 2027-02-24 0.255556 35138.89
2027-02-22 2027-08-20 pays 2027-08-24 0.494444 67986.11
2027-08-20 2028-02-21 pays 2028-02-23 0.502778 69131.94
2028-02-21 2028-08-21 pays 2028-08-23 0.500000 68750.00
2028-08-21 2029-02-20 pays 2029-02-22 0.497222 68368.06
2029-02-20 2029-08-20 pays 2029-08-22 0.500000 68750.00
fixed 6 coupons, Pay
floating 6 coupons, Receive
Check it worked
- The first period is the short one, and its accrual fraction is under a half year.
- Each payment date is the period's end plus the lag in business days: here 20 February 2027 is a Saturday, so that period ends on Monday 22 February and pays on Wednesday 24 February.
- Each fixed amount is notional × rate × accrual fraction.
- Both legs have one coupon per period, on the side you gave them.
- To value the legs, call
leg.npv(market); a market missing a curve a leg needs refuses with the curve named.
Related
- From a schedule to a leg, the reference for periods, payment lags and stub rules.
- Legs and coupons, for every strip and coupon argument.
- Schedules, for the schedule generator itself.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.