Esc

    The API is under testing and may change before release.

    Step curves

    This page lists what you need to build a step curve: a discount curve whose pillars sit where the overnight rate changes, each tagged as a central-bank meeting, a turn, or an ordinary date. It covers the knot tags, the constructor's arguments, and how the forwards behave between knots.

    Quick example

    This builds a SOFR step curve from a policy path, prints the forward over each window, and checks the forward is flat within a window. 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::SOFR_CLOSE_DATE;
    use helixrs::{Curve, KnotKind, StepForward, 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 reference = date(SOFR_CLOSE_DATE);
    
        // The overnight rate the desk expects, window by window: unchanged to the
        // October decision, cut at October and December, a premium over the year end,
        // then held. Each knot is tagged with the event that starts there.
        let path = [
            (date((2026, 10, 29)), "meeting:FED", 0.0390),
            (date((2026, 12, 10)), "meeting:FED", 0.0365),
            (date((2026, 12, 31)), "turn", 0.0340),
            (date((2027, 1, 4)), "anon", 0.0440),
            (date((2027, 10, 1)), "anon", 0.0340),
        ];
        let (mut factors, mut factor, mut start) = (Vec::new(), 1.0_f64, reference);
        let mut knots = Vec::new();
        for (end, tag, rate) in path {
            factor *= (-rate * (end - start).num_days() as f64 / 365.0).exp();
            factors.push(factor);
            knots.push((end, KnotKind::from_tag(tag, end)?));
            start = end;
        }
        let step = StepForward::from_discount_factors(reference, &knots, &factors)?;
        let tags: Vec<String> = step.knot_kinds().iter().map(KnotKind::tag).collect();
        println!("knots  {}", tags.join(", "));
        let curve = Curve::Step(step);
        let view = curve.view::<f64>();
    
        // Between two knots the forward is the path's rate for that window.
        let mut start = reference;
        for (end, _, rate) in path {
            let forward = view.forward_rate(start, end)?;
            println!("{start} - {end}  {forward:.6}");
            start = end;
        }
    
        // Inside a window the forward is the same whichever part of it is read.
        let first = view.forward_rate(date((2026, 11, 2)), date((2026, 11, 9)))?;
        let second = view.forward_rate(date((2026, 11, 30)), date((2026, 12, 7)))?;
        println!("two weeks inside one window  {first:.8} {second:.8}");
    
        Ok(())
    }
    Python
    import datetime as dt
    import math
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE
    
    REFERENCE = SOFR_CLOSE_DATE
    
    # The overnight rate the desk expects, window by window: unchanged to the
    # October decision, cut at October and December, a premium over the year end,
    # then held. Each knot is tagged with the event that starts there.
    PATH = [
        (dt.date(2026, 10, 29), "meeting:FED", 0.0390),
        (dt.date(2026, 12, 10), "meeting:FED", 0.0365),
        (dt.date(2026, 12, 31), "turn", 0.0340),
        (dt.date(2027, 1, 4), "anon", 0.0440),
        (dt.date(2027, 10, 1), "anon", 0.0340),
    ]
    factors, factor, start = [], 1.0, REFERENCE
    for end, _, rate in PATH:
        factor *= math.exp(-rate * (end - start).days / 365.0)
        factors.append(factor)
        start = end
    curve = hlx.curves.DiscountCurve.step_forward(REFERENCE, [(d, tag) for d, tag, _ in PATH], factors)
    print(f"knots  {', '.join(curve.knot_tags())}")
    
    # Between two knots the forward is the path's rate for that window.
    start = REFERENCE
    for end, tag, rate in PATH:
        forward = curve.forward_rate(start, end)
        print(f"{start} - {end}  {forward:.6f}")
        start = end
    
    # Inside a window the forward is the same whichever part of it is read.
    first = curve.forward_rate(dt.date(2026, 11, 2), dt.date(2026, 11, 9))
    second = curve.forward_rate(dt.date(2026, 11, 30), dt.date(2026, 12, 7))
    print(f"two weeks inside one window  {first:.8f} {second:.8f}")
    knots  meeting:FED, meeting:FED, turn, anon, anon
    2026-10-01 - 2026-10-29  0.039000
    2026-10-29 - 2026-12-10  0.036500
    2026-12-10 - 2026-12-31  0.034000
    2026-12-31 - 2027-01-04  0.044000
    2027-01-04 - 2027-10-01  0.034000
    two weeks inside one window  0.03650000 0.03650000

    Each window's forward is the path's rate for that window.

    How it's priced

    Let the policy path set the overnight rate rkr_k over the window (Kk−1,Kk](K_{k-1}, K_k] between knots, with K0K_0 the reference date. Compounding continuously on ACT/365, the factor at each knot is

    D(Kk)=∏j≤ke−rj τ(Kj−1,Kj).D(K_k) = \prod_{j \le k} e^{-r_j\, \tau(K_{j-1}, K_j)}.

    The curve is log-linear between knots, so the forward over any part of a window is that window's rate:

    f(s,t)=rkfor Kk−1≤s<t≤Kk.f(s, t) = r_k \quad \text{for } K_{k-1} \le s < t \le K_k.

    Why the front end moves in steps, and what a turn is, is covered in how curves work.

    API

    Symbol Meaning Rust Python
    KkK_k with identity a knot (date, KnotKind::from_tag(tag, date)?) (date, tag) with tag "meeting:FED", "turn" or "anon"
    D(Kk)D(K_k) the curve from knots and factors StepForward::from_discount_factors, wrapped in Curve::Step curves.DiscountCurve.step_forward
    — each knot's identity StepForward::knot_kinds, read with KnotKind::tag curves.DiscountCurve.knot_tags
    f(s,t)f(s, t) the forward YieldCurve::forward_rate curves.DiscountCurve.forward_rate

    curves.DiscountCurve.step_forward(...):

    Argument Type Default Meaning
    reference date required the curve's reference date
    knots list[tuple[date, str]] required (date, tag) pairs; the tag is "meeting:FED" (any bank code), "turn" or "anon"
    discount_factors list[float] required one discount factor per knot

    Defaults and limits

    • Identity: risk read back from a step curve is keyed by each knot's tag, so you see your exposure to a meeting rather than to a date.
    • Where meeting knots land: on the dates the bank's meeting schedule gives; dates.meeting_dates and dates.turn_dates list them in Python.
    • Solved from quotes: a step curve solved from meeting quotes is shown in curve helpers.

    See also

    • Source: crates/helixrs/src/curves/step.rs, crates/helixrs/src/statics/meetings.rs
    • Related pages:
      • Meeting schedules, for where meeting knots land.
      • Curve helpers, for a step curve solved from quotes.
      • Interpolation, for log-linear's flat forwards.

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