1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! The library-wide error type.
//!
//! Every fallible public operation returns [`RustyQLibError`], usually
//! through the [`Result`] alias. Variants are grouped by failure domain so
//! callers can match on the class of failure without parsing message text:
//! bad user input, an engine/product combination the library refuses to
//! price, a calibration that did not converge, a numerical breakdown inside
//! an otherwise valid computation, or malformed contract/market data.
use thiserror::Error;
/// Library-wide result alias.
pub type Result<T> = std::result::Result<T, RustyQLibError>;
/// The error type returned by all fallible RustyQLib operations.
#[derive(Debug, Clone, PartialEq, Error)]
pub enum RustyQLibError {
/// A user-supplied value is outside its valid domain (negative
/// volatility, maturity in the past, correlation outside [-1, 1], ...).
#[error("invalid input `{field}`: {reason}")]
InvalidInput {
/// The contract/market-data field or function argument at fault.
field: String,
/// Why the value was rejected.
reason: String,
},
/// The requested engine cannot price the requested product/model
/// combination (e.g. path-dependent payoffs on the binomial engine).
#[error("unsupported: {0}")]
UnsupportedEngine(String),
/// An iterative calibration or root search terminated without meeting
/// its convergence criterion.
#[error("calibration failed after {iterations} iterations (residual {residual:.6e}): {reason}")]
CalibrationFailed {
/// Iterations performed before giving up.
iterations: usize,
/// Final objective / residual value at termination.
residual: f64,
/// What was being calibrated and why it stopped.
reason: String,
},
/// A numerical method broke down on otherwise valid input (singular
/// matrix, bracketing failure, NaN in an intermediate, ...).
#[error("numerical error: {0}")]
NumericalError(String),
/// Contract or market data could not be parsed / deserialized.
#[error("parse error: {0}")]
ParseError(String),
/// A [`Market`](crate::core::market::Market) lookup found no datum at
/// the given key. `key` is the debug rendering of the typed key, e.g.
/// `Spot("ACME")`.
#[error("missing market data: {key}")]
MissingMarketData {
/// The key that had no entry.
key: String,
},
}
impl From<crate::core::vols::VolError> for RustyQLibError {
fn from(e: crate::core::vols::VolError) -> Self {
RustyQLibError::invalid_input("vol_surface", e.to_string())
}
}
impl From<crate::core::curves::CurveError> for RustyQLibError {
fn from(e: crate::core::curves::CurveError) -> Self {
RustyQLibError::invalid_input("discount_curve", e.to_string())
}
}
impl RustyQLibError {
/// Convenience constructor for [`RustyQLibError::InvalidInput`].
pub fn invalid_input(field: impl Into<String>, reason: impl Into<String>) -> Self {
RustyQLibError::InvalidInput { field: field.into(), reason: reason.into() }
}
}