Esc

    The API is under testing and may change before release.

    Pin a curve node past the last quote

    You'll hold a curve at a level you choose beyond its last swap, say a 40-year zero rate, and check that the swaps it was solved from still price to nothing.

    Before you start

    • helixio installed: see installing.
    • Your swap quotes, and the value you want the curve to read at the far date: a zero rate or a discount factor.

    Steps

    1. Make one par rung per swap quote with calibration.par_rungs.
    2. Make one more rung on the curve's own reading at the far date: Rung.priced(date, products.curve_pin(currency, date, "zero_rate"), value, 1.0). Use "discount_factor" to pin a factor instead.
    3. Solve the swaps and the pin together with calibration.curve, and file the curve in a market.
    4. Read the curve at the far date, and price the swaps back.

    The comments in the program match these steps.

    Rust
    use chrono::NaiveDate;
    use helixrs::{
        AnyInstrument, CollateralSpec, CurvePin, CurveReading, FixingStore, FxRates, Interpolation,
        MarketContext, Period, Rung, Tolerance, YieldCurve, calibrate, par_rungs,
    };
    
    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((2026, 10, 1));
        let far = date((2066, 10, 1));
        let usd = CollateralSpec::Currency("USD".parse()?);
    
        // Step 1: one par rung per swap quote, here five SOFR swaps out to thirty years.
        let tenors = ["1Y", "2Y", "5Y", "10Y", "30Y"].iter().map(|t| t.parse::<Period>()).collect::<Result<Vec<_>, _>>()?;
        let rates = [0.0441, 0.0452, 0.0468, 0.0481, 0.0476];
        let rungs = par_rungs("USD-SOFR-OIS", close, &tenors, &rates, 1.0)?;
    
        // Step 2: a priced rung on the curve's own 40-year zero rate, worth the 4.60%
        // you want the curve to read there.
        let pin = AnyInstrument::CurvePin(CurvePin::new("USD".parse()?, far, CurveReading::ZeroRate));
        let mut with_pin = rungs.clone();
        with_pin.push(Rung::priced(far, pin, 0.0460, 1.0));
    
        // Step 3: solve the swaps and the pin together, and file the curve.
        let (fixings, fx) = (FixingStore::new(), FxRates::default());
        let solved = calibrate(close, &with_pin, &fixings, &fx, Tolerance::default(), Interpolation::LogLinear)?;
        let parameters = solved.curve_value().parameter_count();
        let market = MarketContext::as_of(close)
            .with_discount(usd, solved.clone())
            .with_projection("USD-SOFR", solved);
    
        // Step 4: read the curve at the last swap and at the pin, and price the swaps back.
        let (curve, _) = market.curve(&"discount(USD)".parse()?).expect("the USD curve");
        let view = curve.view::<f64>();
        println!("pillars  {parameters}");
        let last = rungs[rungs.len() - 1].pillar;
        println!("zero rate at {last}  {:.6}", view.zero_rate(last)?);
        println!("zero rate at {far}  {:.6}", view.zero_rate(far)?);
        let mut worst: f64 = 0.0;
        for rung in &rungs {
            worst = worst.max(rung.instrument.npv(&market)?.abs());
        }
        println!("largest swap value  {worst:.2}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    
    CLOSE = dt.date(2026, 10, 1)
    FAR = dt.date(2066, 10, 1)
    calibration = hlx.calibration
    
    # Step 1: one par rung per swap quote, here five SOFR swaps out to thirty years.
    tenors = ["1Y", "2Y", "5Y", "10Y", "30Y"]
    rates = [0.0441, 0.0452, 0.0468, 0.0481, 0.0476]
    rungs = calibration.par_rungs("USD-SOFR-OIS", CLOSE, list(zip(tenors, rates)))
    
    # Step 2: a priced rung on the curve's own 40-year zero rate, worth the 4.60%
    # you want the curve to read there.
    pin = calibration.Rung.priced(FAR, hlx.products.curve_pin("USD", FAR, "zero_rate"), 0.0460, 1.0)
    
    # Step 3: solve the swaps and the pin together, and file the curve.
    solved = calibration.curve(CLOSE, list(rungs) + [pin])
    market = hlx.MarketContext(CLOSE).with_discount("USD", solved.curve()).with_projection("USD-SOFR", solved.curve())
    
    # Step 4: read the curve at the last swap and at the pin, and price the swaps back.
    curve = market.curve("discount(USD)")
    print(f"pillars  {solved.curve().parameter_count()}")
    print(f"zero rate at {rungs[-1].pillar()}  {curve.zero_rate(rungs[-1].pillar()):.6f}")
    print(f"zero rate at {FAR}  {curve.zero_rate(FAR):.6f}")
    worst = max(abs(rung.instrument().npv(market)) for rung in rungs)
    print(f"largest swap value  {worst:.2f}")
    pillars  6
    zero rate at 2056-10-10  0.047044
    zero rate at 2066-10-01  0.046000
    largest swap value  0.00

    Check it worked

    • The curve reads exactly the pinned value at the far date.
    • Every swap is still worth nothing: the pin sits past the last pillar, so it moves nothing before it.
    • If the solve refuses the pin, its date isn't after the pillar of the rung listed before it: pillars must rise in the order you pass the rungs. List the pin in date order and it can sit between quotes as well as past them.
    • Bootstrapping, the reference for the solve, its rungs and its tolerance.
    • Reading a curve, for zero rates and discount factors.
    • Build a curve from your own rungs, for rungs no recipe lists.

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