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.
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(())
}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
ValueErroroutsideHelixError, 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:
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(())
}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.