Esc

    The API is under testing and may change before release.

    Errors

    This page lists every class of refusal the library raises, which layer each belongs to, and what a refusal tells you, so you can handle the failures a run can live with and stop on the ones it can't. When the library can't answer, it refuses, and the refusal's class names the layer that gave up: pricing, risk, calibration or market data.

    Quick example

    The example reads its quotes from the desk sheets; load them once and it runs as shown. It triggers one refusal in each of three layers, then reads what a missing market input names.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, Book, CalibrationError, CollateralSpec, FixingStore, FxRates, Interpolation,
        MarketContext, MarketDataError, PricingError, QuoteSheet, Termination, Tolerance, calibrate,
        template,
    };
    
    fn date(year: i32, month: u32, day: u32) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    /// The named class Python raises for a pricing refusal, decided as it decides it.
    fn pricing_class(error: &PricingError) -> &'static str {
        if error.missing_input().is_some() {
            "MissingMarketInputError"
        } else if error.is_unsolved() {
            "SolveError"
        } else {
            "PricingError"
        }
    }
    
    fn market_data_class(error: &MarketDataError) -> &'static str {
        match error {
            MarketDataError::MissingFixing { .. } => "MissingFixingError",
            MarketDataError::MissingFxRate { .. } => "MissingFxRateError",
            _ => "MarketDataError",
        }
    }
    
    fn calibration_class(error: &CalibrationError) -> &'static str {
        if error.is_unconverged() { "ConvergenceError" } else { "CalibrationError" }
    }
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let close = date(SOFR_CLOSE_DATE.0, SOFR_CLOSE_DATE.1, SOFR_CLOSE_DATE.2);
        let sheet = QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?;
        let usd = CollateralSpec::Currency("USD".parse()?);
        let market = MarketContext::as_of(close).with_recipe_quotes(&usd, &sheet)?;
        let swap = |code: &str, traded: NaiveDate, rate: f64| -> AnyInstrument {
            template(code)
                .expect("a registered template")
                .swap(traded)
                .expect("a trade date")
                .with_maturity("5Y".parse::<Termination>().expect("a tenor"))
                .expect("a maturity")
                .with_notional(1e6)
                .with_fixed_rate(rate)
                .build()
                .expect("a complete trade")
        };
        let seasoned = swap("USD-SOFR-OIS", date(2026, 9, 24), 0.0466);
        let euro = swap("EUR-ESTR-OIS", close, 0.0200);
        let booked = |trade_id: &str, instrument: &AnyInstrument| {
            Book::new().add(trade_id, instrument.clone(), 1.0).attach_market(market.clone())
        };
    
        // One failure from each layer. The market holds the USD curve and no prints.
        let reset = booked("SEASONED", &seasoned).npv(close).expect_err("a reset is missing");
        let fixing = market.fixing("USD-SOFR", date(2026, 9, 1)).expect_err("nothing was published");
        let ladder = calibrate(
            close,
            &[],
            &FixingStore::default(),
            &FxRates::new(),
            Tolerance::default(),
            Interpolation::LogLinear,
        )
        .expect_err("a ladder needs a rung");
        let rows = [
            ("a seasoned swap's reset", pricing_class(&reset), "PricingError"),
            ("an unpublished fixing", market_data_class(&fixing), "MarketDataError"),
            ("a ladder with no rung", calibration_class(&ladder), "CalibrationError"),
        ];
        let mut classes: Vec<&str> = rows.iter().map(|(_, class, _)| *class).collect();
        classes.dedup();
        for (what, class, layer) in rows {
            println!("{what:<24} {class:<24} under {layer}");
        }
    
        // A missing market input names what is missing in the spelling the market lists
        // its contents in, and the trade it was missing for.
        let missing = booked("ESTR-5Y", &euro).npv(close).expect_err("no EUR curve");
        let (kind, key) = missing.missing_input().expect("a missing input");
        let message = missing.to_string();
        println!("kind   {kind}");
        println!("key    {key}");
        println!("trade  {}", missing.trade().expect("a trade"));
        println!("first  {}", message.split(';').next().expect("a first clause"));
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    LAYERS = (hlx.PricingError, hlx.RiskError, hlx.CalibrationError, hlx.MarketDataError)
    market = hlx.MarketContext(SOFR_CLOSE_DATE).with_recipe_quotes("USD", USD_SOFR_CLOSE)
    sofr, estr = hlx.templates.get("USD-SOFR-OIS"), hlx.templates.get("EUR-ESTR-OIS")
    seasoned = sofr.swap(dt.date(2026, 9, 24)).with_maturity("5Y").with_notional(1e6).with_fixed_rate(0.0466).build()
    euro = estr.swap(SOFR_CLOSE_DATE).with_maturity("5Y").with_notional(1e6).with_fixed_rate(0.0200).build()
    
    def refusal(attempt) -> hlx.HelixError:
        """The error a failing call raises. A call that succeeds is itself a failure."""
        try:
            attempt()
        except hlx.HelixError as error:
            return error
        raise AssertionError("the call was expected to be refused")
    
    def layer_of(error: hlx.HelixError) -> str:
        return next(layer.__name__ for layer in LAYERS if isinstance(error, layer))
    
    def booked(trade_id: str, instrument: hlx.Instrument) -> hlx.Book:
        book = hlx.Book().add(trade_id, instrument, 1.0)
        book.attach_market(market)
        return book
    
    # One failure from each layer. The market holds the USD curve and no prints.
    attempts = [
        ("a seasoned swap's reset", lambda: booked("SEASONED", seasoned).npv(as_of=SOFR_CLOSE_DATE)),
        ("an unpublished fixing", lambda: market.fixing("USD-SOFR", dt.date(2026, 9, 1))),
        ("a ladder with no rung", lambda: hlx.calibration.curve(SOFR_CLOSE_DATE, [])),
    ]
    errors = [refusal(attempt) for _, attempt in attempts]
    for (what, _), error in zip(attempts, errors):
        print(f"{what:<24} {type(error).__name__:<24} under {layer_of(error)}")
    
    # A missing market input names what is missing in the spelling the market lists
    # its contents in, and the trade it was missing for.
    missing = refusal(lambda: booked("ESTR-5Y", euro).npv(as_of=SOFR_CLOSE_DATE))
    print(f"kind   {missing.kind}")
    print(f"key    {missing.key}")
    print(f"trade  {missing.trade}")
    print(f"first  {str(missing).split(';')[0]}")
    a seasoned swap's reset  MissingMarketInputError  under PricingError
    an unpublished fixing    MissingFixingError       under MarketDataError
    a ladder with no rung    CalibrationError         under CalibrationError
    kind   discount
    key    EUR
    trade  ESTR-5Y
    first  trade ESTR-5Y: no curve resolved under the key discount(EUR)

    API

    In Python every class sits under one root, HelixError:

    Layer Named refusals under it Raised when
    PricingError MissingMarketInputError, SolveError a valuation found no curve, surface or print; a quote solve found no root
    RiskError NoLadderError a par basis was asked of an input no calibration ladder has a row for
    CalibrationError ConvergenceError a calibration or fit ran out of iterations
    MarketDataError MissingFixingError, MissingFxRateError, SnapshotError, SourceError a lookup asked for a value never supplied; a source failed or left gaps

    CurveError is a further layer, for a curve failure that isn't the caller's input. In Rust each layer returns its own error type, and each type carries the predicate the Python class is chosen by: PricingError::missing_input, PricingError::is_unsolved, RiskError::lacks_ladder, CalibrationError::is_unconverged, and the MarketDataError variants.

    Symbol Meaning Rust Python
    a layer the type that refused PricingError, RiskError, CalibrationError, MarketDataError PricingError, RiskError, CalibrationError, MarketDataError
    a missing input kind and key PricingError::missing_input MissingMarketInputError with kind and key
    the trade the book trade it came from PricingError::trade MissingMarketInputError with trade
    a missing print a fixing never published MarketDataError::MissingFixing MissingFixingError
    a refused spelling a closed vocabulary the parse error, with input and accepted ValueError

    What a MissingMarketInputError carries:

    Argument Type Default Meaning
    kind str always set what kind of input was missing, as MarketContext.contents() lists it, such as "discount" or "fixing"; Rust returns it from PricingError::missing_input
    key str always set which one, such as "EUR" or "EUR-EURIBOR-6M@2026-01-02"
    trade str or None None outside a book the book trade it was missing for; Rust: PricingError::trade

    Defaults and limits

    • A wrong input isn't a layer's refusal. Pillars out of order or a spelling outside a closed vocabulary raise a plain ValueError outside HelixError, before any layer runs. Its message lists what the vocabulary accepts.
    • A refusal that wraps another carries it as its cause.
    • Messages start with the trade when the refusal came from a book: trade ESTR-5Y: ….
    • A book that may partly fail: to value what you can and keep a list of what was refused, see value a book and collect what failed.

    This program shows a misspelled argument refused, with the codes the vocabulary accepts:

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{AnyInstrument, Book, CollateralSpec, DayCountFraction, MarketContext, QuoteSheet, Termination, template};
    
    fn date(year: i32, month: u32, day: u32) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        // A spelling outside a closed vocabulary is refused before any layer runs, and
        // the refusal lists what the vocabulary accepts.
        let spelling_refusal = "ACT/364".parse::<DayCountFraction>().unwrap_err();
        let accepted = spelling_refusal.accepted();
        println!(
            "refused    {}",
            spelling_refusal.to_string().split(';').next().expect("a first clause")
        );
        let round_trips = accepted
            .iter()
            .filter(|code| code.parse::<DayCountFraction>().is_ok_and(|parsed| parsed.code() == **code))
            .count();
        println!("accepted   {} codes, {round_trips} of them parse back to themselves", accepted.len());
    
        Ok(())
    }
    Python
    import helixio as hlx
    
    # A spelling outside a closed vocabulary is refused before any layer runs, and
    # the message lists what the vocabulary accepts.
    try:
        hlx.dates.DayCountFraction("ACT/364")
        spelling_refusal = None
    except ValueError as error:
        spelling_refusal = error
    accepted = str(spelling_refusal).split("expected one of: ")[1].split(", ")
    print(f"refused    {str(spelling_refusal).split(';')[0]}")
    round_trips = sum(hlx.dates.DayCountFraction(code).code() == code for code in accepted)
    print(f"accepted   {len(accepted)} codes, {round_trips} of them parse back to themselves")
    refused    unknown day count "ACT/364"
    accepted   12 codes, 12 of them parse back to themselves

    See also

    • Source: crates/helixrs/src/market/context.rs, crates/helixrs/src/marketdata.rs, crates/helixio/src/lib.rs
    • Related pages:
      • How dates and conventions work, for why a run tells refusals apart.
      • Value a book and collect what failed.
      • Data sources, for SnapshotError.
      • Indices, for the fixing rule behind a missing print.
      • Vocabularies, for closed vocabularies.

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