Reading a curve
This page lists the three readings a discount curve answers, a discount factor, a zero rate and a forward rate, and the setting that decides what day count the rates are formed on.
Quick example
This reads a curve marked from the SOFR close: factors and zero rates at three dates, a forward, and the same factors under an ACT/360 rate day count, which scales every rate by 360/365. The example reads its quotes from the desk sheets; load them once and it runs as shown.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
use helixrs::{
CollateralSpec, Curve, DayCountFraction, MarketContext, PiecewiseDiscount, QuoteSheet,
YieldCurve,
};
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 sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
let market = MarketContext::as_of(close)
.with_recipe_quotes(&CollateralSpec::Currency("USD".parse()?), &sheet)?;
let dates = [date((2027, 10, 1)), date((2031, 10, 1)), date((2036, 10, 1))];
// The SOFR curve, read at one, five and ten years.
let (curve, _) = market.curve(&"discount(USD)".parse()?).expect("the USD curve");
let view = curve.view::<f64>();
println!("rate day count {}", view.rate_day_count().code());
let mut factors = Vec::new();
for d in dates {
let factor = view.discount(d)?;
println!("{d} discount {factor:.10} zero {:.8}", view.zero_rate(d)?);
factors.push(factor);
}
// The forward between five and ten years: what the curve implies a rate over
// that window is, seen from today.
let forward = view.forward_rate(dates[1], dates[2])?;
println!("5Y-10Y forward {forward:.8}");
// The same factors, told to form rates on ACT/360: every factor is unchanged,
// and every zero rate is scaled by 360/365.
let pillars: Vec<(NaiveDate, f64)> = dates.iter().copied().zip(factors.iter().copied()).collect();
let act360 = Curve::Piecewise(
PiecewiseDiscount::from_discount_factors(close, &pillars)?
.with_rate_day_count(DayCountFraction::Act360),
);
let act360 = act360.view::<f64>();
println!("rate day count {}", act360.rate_day_count().code());
let ratio = act360.zero_rate(dates[1])? / view.zero_rate(dates[1])?;
println!("zero rate ratio {ratio:.10}");
Ok(())
}import datetime as dt
import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
DATES = [dt.date(2027, 10, 1), dt.date(2031, 10, 1), dt.date(2036, 10, 1)]
# The SOFR curve, read at one, five and ten years.
curve = market.curve("discount(USD)")
print(f"rate day count {curve.rate_day_count()}")
factors = [curve.discount(d) for d in DATES]
for d, factor in zip(DATES, factors):
print(f"{d} discount {factor:.10f} zero {curve.zero_rate(d):.8f}")
# The forward between five and ten years: what the curve implies a rate over
# that window is, seen from today.
forward = curve.forward_rate(DATES[1], DATES[2])
print(f"5Y-10Y forward {forward:.8f}")
# The same factors, told to form rates on ACT/360: every factor is unchanged,
# and every zero rate is scaled by 360/365.
act360 = hlx.curves.DiscountCurve(
SOFR_CLOSE_DATE, list(zip(DATES, factors)), rate_day_count="ACT/360"
)
print(f"rate day count {act360.rate_day_count()}")
ratio = act360.zero_rate(DATES[1]) / curve.zero_rate(DATES[1])
print(f"zero rate ratio {ratio:.10f}")rate day count ACT/365.FIXED
2027-10-01 discount 0.9574394995 zero 0.04349275
2031-10-01 discount 0.7916588736 zero 0.04669935
2036-10-01 discount 0.6184302261 zero 0.04801762
5Y-10Y forward 0.04933518
rate day count ACT/360
zero rate ratio 0.9863013699
How it's priced
With the year fraction on the curve's rate day count, the continuously compounded zero rate to and the forward between and are
Changing the rate day count from ACT/365 to ACT/360 changes no factor and scales every rate by . The rates rebuilt by hand from the factors are on how curves work.
API
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| the factor | YieldCurve::discount |
curves.DiscountCurve.discount |
|
| the zero rate | YieldCurve::zero_rate |
curves.DiscountCurve.zero_rate |
|
| the forward | YieldCurve::forward_rate |
curves.DiscountCurve.forward_rate |
|
| 's day count | the rate day count | YieldCurve::rate_day_count; set with PiecewiseDiscount::with_rate_day_count |
curves.DiscountCurve.rate_day_count; set with rate_day_count= |
| Argument | Type | Default | Meaning |
|---|---|---|---|
at |
date |
required | the date, for discount(at) and zero_rate(at) |
start, end |
date |
required | the window, for forward_rate(start, end) |
rate_day_count |
str or DayCountFraction |
"ACT/365.FIXED" |
set when the curve is built, see discount curves |
Defaults and limits
!!! warning "A curve's zero rate is not an index fixing"
The zero rate is continuously compounded on the curve's rate day count. The rate an index fixes
at is simple, on the index's own day count, over its own period; read that with
Index.forecast, as indices shows.
- Before the reference date
discountraisesValueError. - A factor is strictly between zero and one for a positive rate, and falls with time.
See also
- Source:
crates/helixrs/src/curves/discount.rs - Related pages:
- Discount curves, for building a curve.
- Day counts, for the year fractions.
- Indices, for the rate an index fixes at.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.