Conventions and templates
This page lists how you resolve a template by its code, draft a quoted swap from it by tenor,
override one of its conventions, and read the conventions a swap will carry before you book it. A
template is a market's standing conventions for one kind of trade, under one code: USD-SOFR-OIS
holds the calendar, spot lag, frequencies, day counts, payment lag and floating index a dollar
SOFR swap carries.
Quick example
This resolves USD-SOFR-OIS, drafts a five-year and a 2Y2Y swap from it, and prints the
conventions a swap on it will carry.
use chrono::{Datelike, NaiveDate, Weekday};
use helixrs::statics::templates::registry;
use helixrs::{
AnyInstrument, AnyTemplate, CurveError, ForwardTenor, Period, add_period,
swap_builder_on_overnight_template, swap_builder_on_tenor,
};
fn date(year: i32, month: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
fn tenor(spelling: &str) -> ForwardTenor {
spelling.parse().expect("a tenor spelling")
}
fn period(spelling: &str) -> Period {
spelling.parse().expect("a period spelling")
}
/// A quoted swap on `template`, `spelling` from spot.
fn quoted_swap(
template: &AnyTemplate,
valuation: NaiveDate,
spelling: &str,
notional: f64,
) -> Result<AnyInstrument, CurveError> {
swap_builder_on_tenor(valuation, tenor(spelling), template, "USD-SOFR")?
.with_notional(notional)
.with_fixed_rate(0.0385)
.build()
}
/// The first accrual start and the last accrual end of the fixed leg.
fn accrual_span(instrument: &AnyInstrument) -> (NaiveDate, NaiveDate) {
let legs = instrument.legs().expect("a swap has legs");
let fixed = legs[0].coupons();
(
fixed[0].accrual_start().expect("an accruing coupon"),
fixed[fixed.len() - 1].accrual_end().expect("an accruing coupon"),
)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let valuation = date(2026, 9, 25); // a Friday
// One code resolves to the whole convention set for a USD SOFR overnight
// swap.
let sofr = registry::lookup("USD-SOFR-OIS").expect("a registered template");
let AnyTemplate::Ois(conventions) = &sofr else {
panic!("USD-SOFR-OIS resolves to an overnight swap template");
};
let spot = conventions.effective_date(valuation);
println!("code {}", conventions.name);
println!("family {}", sofr.family());
println!("spot lag {}", conventions.spot_lag);
println!("spot date {spot}");
// A quoted swap is asked for by tenor. "5Y" starts at spot; "2Y2Y" starts
// two years after spot and runs two more. The notional and rate are the
// only terms the caller states.
let five_year = quoted_swap(&sofr, valuation, "5Y", 100_000_000.0)?;
let forward = quoted_swap(&sofr, valuation, "2Y2Y", 100_000_000.0)?;
let (five_start, five_end) = accrual_span(&five_year);
let (forward_start, forward_end) = accrual_span(&forward);
println!("5Y {five_start} to {five_end}");
println!("2Y2Y {forward_start} to {forward_end}");
// Every convention a swap on a template will carry, read before anything is
// booked: the same builder that books the swap states its terms as a table.
let rows = swap_builder_on_overnight_template(date(2026, 10, 5), date(2031, 10, 5), "USD-SOFR-OIS")?
.conventions();
for (field, value) in &rows {
println!("{field:<24} {value}");
}
Ok(())
}import datetime as dt
import helixio as hlx
VALUATION = dt.date(2026, 9, 25) # a Friday
def accrual_span(instrument: hlx.Instrument) -> tuple[dt.date, dt.date]:
"""The first accrual start and the last accrual end of the fixed leg."""
fixed = instrument.legs()[0].coupons()
return fixed[0].accrual_start(), fixed[-1].accrual_end()
# One code resolves to the whole convention set for a USD SOFR overnight swap.
sofr = hlx.templates.get("USD-SOFR-OIS")
spot = sofr.spot_date(VALUATION)
print(f"code {sofr.code()}")
print(f"family {sofr.family()}")
print(f"spot lag {sofr.spot_lag()}")
print(f"spot date {spot}")
# A quoted swap is asked for by tenor. "5Y" starts at spot; "2Y2Y" starts two
# years after spot and runs two more. The notional and rate are the only terms
# the caller states.
five_year = sofr.vanilla_swap(VALUATION, "5Y", fixed_rate=0.0385, notional=100_000_000)
forward = sofr.vanilla_swap(VALUATION, "2Y2Y", fixed_rate=0.0385, notional=100_000_000)
five_start, five_end = accrual_span(five_year)
forward_start, forward_end = accrual_span(forward)
print(f"5Y {five_start} to {five_end}")
print(f"2Y2Y {forward_start} to {forward_end}")
# Every convention a swap on a template will carry, read before anything is
# booked: the same builder that books the swap states its terms as a table.
table = hlx.templates.swap_conventions(dt.date(2026, 10, 5), dt.date(2031, 10, 5), template="USD-SOFR-OIS")
for field, value in zip(table.to_dict()["field"], table.to_dict()["value"]):
print(f"{field:<24} {value}")code USD-SOFR-OIS
family OIS
spot lag 2D
spot date 2026-09-29
5Y 2026-09-29 to 2031-09-29
2Y2Y 2028-09-29 to 2030-09-30
family OvernightIndexedSwap
currency USD
index USD-SOFR
effective 2026-10-05
termination 2031-10-05
fixed_frequency 1Y
float_frequency 1Y
fixed_day_count ACT/360
float_day_count ACT/360
calendar USNY.SOFR
business_day_convention MODFOLLOWING
roll None
stub ShortFinal
payment_lag 2 BusinessDays FOLLOWING on USNY.SOFR
float_payment_lag 2 BusinessDays FOLLOWING on USNY.SOFR
accumulation Compounded
How it's priced
A trade is its template's conventions plus its own terms :
A quoted swap starts at spot: the trade date stepped by the spot lag on the template's calendar.
A forward tenor 2Y2Y starts two years after spot and runs two more. Why a market standardises
these conventions is covered in
how dates and conventions work.
API
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| a template, by code | template |
templates.get |
|
| spot | the trade date plus the spot lag | AnyTemplate::spot_date |
Template.spot_date |
| a trade drafted on a template | AnyTemplate::swap → ProductBuilder |
Template.swap → products.ProductBuilder |
|
| an override | a new template with one convention changed | the with_* methods of each template family |
Template.with_payment_lag and the other Template.with_* methods |
| as a table | the conventions a swap will carry | swap_builder_on_overnight_template(…).conventions() |
templates.swap_conventions |
| Argument | Type | Default | Meaning |
|---|---|---|---|
code |
str | required | templates.get(code): the registry code, such as "USD-SOFR-OIS" |
valuation |
date | required | Template.spot_date(valuation): the trade date spot is counted from |
trade_date |
date | required | Template.swap(trade_date): opens a ProductBuilder with every term defaulted, effective at spot, ten years, 10,000,000, receiving fixed |
effective |
date | required | Template.vanilla_swap: with a date termination, the first accrual date; with a tenor, the valuation date, and the swap starts at spot |
termination |
date or str | required | Template.vanilla_swap: an end date, or a tenor such as "5Y" or "2Y2Y" |
fixed_rate |
float | required | Template.vanilla_swap: the fixed rate |
notional |
float | required | Template.vanilla_swap: the notional |
pays_fixed |
bool | False |
Template.vanilla_swap: pay fixed instead of receiving it |
accrual_compounding |
AccrualCompounding, str or None |
None (the template's) |
Template.vanilla_swap: how the floating leg compounds |
days |
int | required | with_payment_lag(days), with_spot_lag(days): the new lag in business days |
roll_convention |
RollConvention or str |
required | with_roll_convention: a day of the month ("20"), "EOM" or "IMM" |
template, index |
str or None |
None |
templates.swap_conventions(effective, termination, *, template=, index=): name the template, or the index whose template is used |
Defaults and limits
- A template is a value. An override returns a new template and leaves the registered one unchanged, so you can use both side by side.
- Overrides are accepted only where a method reads them.
with_payment_lagworks for the overnight and vanilla swap families;with_spot_lagonly where a constructor reads the spot lag. - A template refuses a request its family can't honour. Asking a deposit template for a swap
raises
ValueErrornaming the code and the family. - An unknown code raises
ValueErrorlisting the codes that do resolve. The full list is in the template registry.
More examples
This program overrides the payment lag and the spot lag, and shows the refusals:
use chrono::{Datelike, NaiveDate, Weekday};
use helixrs::statics::templates::registry;
use helixrs::{
AnyInstrument, AnyTemplate, BusinessDayConvention, CurveError, ForwardTenor, Period,
RollConvention, add_period, swap_builder_on_overnight_template, swap_builder_on_tenor,
};
fn date(year: i32, month: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
fn tenor(spelling: &str) -> ForwardTenor {
spelling.parse().expect("a tenor spelling")
}
fn period(spelling: &str) -> Period {
spelling.parse().expect("a period spelling")
}
/// A quoted swap on `template`, `spelling` from spot.
fn quoted_swap(
template: &AnyTemplate,
valuation: NaiveDate,
spelling: &str,
notional: f64,
) -> Result<AnyInstrument, CurveError> {
swap_builder_on_tenor(valuation, tenor(spelling), template, "USD-SOFR")?
.with_notional(notional)
.with_fixed_rate(0.0385)
.build()
}
/// The first accrual start and the last accrual end of the fixed leg.
fn accrual_span(instrument: &AnyInstrument) -> (NaiveDate, NaiveDate) {
let legs = instrument.legs().expect("a swap has legs");
let fixed = legs[0].coupons();
(
fixed[0].accrual_start().expect("an accruing coupon"),
fixed[fixed.len() - 1].accrual_end().expect("an accruing coupon"),
)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let valuation = date(2026, 9, 25); // a Friday
let sofr = registry::lookup("USD-SOFR-OIS").expect("a registered template");
let AnyTemplate::Ois(conventions) = &sofr else {
panic!("USD-SOFR-OIS resolves to an overnight swap template");
};
let spot = conventions.effective_date(valuation);
let (five_start, five_end) = accrual_span("ed_swap(&sofr, valuation, "5Y", 100_000_000.0)?);
// `with_payment_lag` consumes a copy and returns the changed one, so both
// templates can be used side by side.
let same_day = AnyTemplate::from(conventions.clone().with_payment_lag(0));
let lagged_swap = quoted_swap(&sofr, valuation, "5Y", 100_000_000.0)?;
let same_day_swap = quoted_swap(&same_day, valuation, "5Y", 100_000_000.0)?;
let lagged_pays = lagged_swap.final_payment_date().expect("a last payment");
let same_day_pays = same_day_swap.final_payment_date().expect("a last payment");
println!("registered template pays {lagged_pays}");
println!("payment lag of zero pays {same_day_pays}");
// The spot lag is overridden the same way. With no lag the quoted swap
// starts on the trade date itself, a business day, and ends five years later.
let same_day_start = AnyTemplate::from(conventions.clone().with_spot_lag(0));
let start_swap = quoted_swap(&same_day_start, valuation, "5Y", 100_000_000.0)?;
println!("spot lag of zero starts {}", accrual_span(&start_swap).0);
// A template refuses a request its family cannot honour. Asked for a swap,
// a deposit template is refused rather than read as something it is not.
let deposit_code = "USD-SOFR-DEPO";
let deposit = registry::lookup(deposit_code).expect("a registered template");
let swap_refused = quoted_swap(&deposit, valuation, "5Y", 100_000_000.0).is_err();
// An unknown code resolves to nothing.
let unknown_refused = registry::lookup("USD-SOFR-SWAP").is_none();
// A swap asked for by an overnight template name refuses a vanilla code,
// and its refusal lists exactly the codes it accepts.
let refused = "EUR-EURIBOR-6M-IRS";
let overnight = match swap_builder_on_overnight_template(date(2026, 9, 29), date(2031, 9, 29), refused)
{
Err(CurveError::UnknownIndex(unknown)) => unknown.known.clone(),
_ => panic!("{refused} names a vanilla template and must be refused"),
};
let verdict = |refused: bool| if refused { "refused" } else { "accepted" };
println!("{deposit_code} asked for a swap {} ({} template)", verdict(swap_refused), deposit.family());
println!("USD-SOFR-SWAP asked for {} (not registered)", verdict(unknown_refused));
println!("overnight swap templates {}", overnight.len());
Ok(())
}import datetime as dt
import helixio as hlx
VALUATION = dt.date(2026, 9, 25) # a Friday
def accrual_span(instrument: hlx.Instrument) -> tuple[dt.date, dt.date]:
"""The first accrual start and the last accrual end of the fixed leg."""
fixed = instrument.legs()[0].coupons()
return fixed[0].accrual_start(), fixed[-1].accrual_end()
sofr = hlx.templates.get("USD-SOFR-OIS")
# `with_payment_lag` returns a new template, so both can be used side by side.
same_day = sofr.with_payment_lag(0)
lagged_swap = sofr.vanilla_swap(VALUATION, "5Y", fixed_rate=0.0385, notional=100_000_000)
same_day_swap = same_day.vanilla_swap(
VALUATION, "5Y", fixed_rate=0.0385, notional=100_000_000
)
lagged_pays = lagged_swap.final_payment_date()
same_day_pays = same_day_swap.final_payment_date()
print(f"registered template pays {lagged_pays}")
print(f"payment lag of zero pays {same_day_pays}")
# The spot lag is overridden the same way. With no lag the quoted swap starts
# on the trade date itself, a business day, and ends five years later.
same_day_start = sofr.with_spot_lag(0)
start_swap = same_day_start.vanilla_swap(VALUATION, "5Y", fixed_rate=0.0385, notional=100_000_000)
print(f"spot lag of zero starts {accrual_span(start_swap)[0]}")
# A template refuses a request its family cannot honour, and the refusal names
# the code and the family instead of returning something unchanged.
DEPOSIT = "USD-SOFR-DEPO"
deposit = hlx.templates.get(DEPOSIT)
try:
deposit.vanilla_swap(VALUATION, "5Y", fixed_rate=0.0385, notional=100_000_000)
swap_refused = False
except ValueError as refusal:
swap_refused = DEPOSIT in str(refusal) and deposit.family() in str(refusal)
# An unknown code is refused at lookup, and the refusal lists the codes that
# do resolve.
try:
hlx.templates.get("USD-SOFR-SWAP")
unknown_refused = False
except ValueError as refusal:
listed = str(refusal).split("known: ")[1].split(", ")
unknown_refused = "USD-SOFR-SWAP" not in listed
# The registry lists every template with its family. The overnight swap
# templates are the ones whose family is OIS.
listed = hlx.templates.catalogue()
overnight = [code for code, family in zip(listed["code"], listed["family"]) if family == "OIS"]
print(f"{DEPOSIT} asked for a swap {'refused' if swap_refused else 'accepted'} ({deposit.family()} template)")
print(f"USD-SOFR-SWAP asked for {'refused' if unknown_refused else 'accepted'} (not registered)")
print(f"overnight swap templates {len(overnight)}")registered template pays 2031-10-01
payment lag of zero pays 2031-09-29
spot lag of zero starts 2026-09-25
USD-SOFR-DEPO asked for a swap refused (deposit template)
USD-SOFR-SWAP asked for refused (not registered)
overnight swap templates 23
See also
- Source:
crates/helixrs/src/statics/templates/registry.rs,crates/helixrs/src/statics/templates/irs.rs,crates/helixrs/src/products.rs - Related pages:
- How dates and conventions work, for why templates exist and how a swap's dates follow from them.
- Your first trade, for a template used end to end.
- Calendars, for the spot lag's calendar.
- Day counts, for the day counts a template names.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.