#[derive(Debug, thiserror::Error)]
pub enum BacktestError {
#[error("invalid quantity: {0}")]
InvalidQuantity(u32),
#[error("crossed quote: bid {bid} > ask {ask}")]
CrossedQuote {
bid: u64,
ask: u64,
},
#[error("price {price} not aligned to tick {tick}")]
PriceNotTickAligned {
price: u64,
tick: u64,
},
#[error("data out of order: step {step} ts {ts} not strictly after previous {prev}")]
DataOutOfOrder {
step: u32,
ts: i64,
prev: i64,
},
#[error("tape too large: {limit} = {value} exceeds cap {cap}")]
TapeTooLarge {
limit: &'static str,
value: u64,
cap: u64,
},
#[error("config: {0}")]
Config(String),
#[error("data source io: {0}")]
DataIo(#[from] std::io::Error),
#[error("malformed data: {0}")]
Data(String),
#[error("chain conversion: {0}")]
Conversion(String),
#[error("simulator session: {0}")]
Session(String),
#[error("execution: {0}")]
Execution(String),
#[error("orderbook: {0}")]
OrderBook(String),
#[error("strategy: {0}")]
Strategy(String),
#[error("bundle write: {0}")]
Bundle(String),
#[error("arithmetic overflow")]
ArithmeticOverflow,
}
#[cfg(feature = "orderbook")]
impl From<option_chain_orderbook::Error> for BacktestError {
fn from(err: option_chain_orderbook::Error) -> Self {
Self::OrderBook(err.to_string())
}
}
#[must_use]
pub(crate) fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"non-string panic payload".to_string()
}
}
#[cfg(test)]
mod tests {
use super::BacktestError;
#[test]
fn test_error_messages_carry_offending_values() {
assert_eq!(
BacktestError::InvalidQuantity(0).to_string(),
"invalid quantity: 0"
);
assert_eq!(
BacktestError::CrossedQuote { bid: 105, ask: 100 }.to_string(),
"crossed quote: bid 105 > ask 100"
);
assert_eq!(
BacktestError::PriceNotTickAligned {
price: 101,
tick: 5
}
.to_string(),
"price 101 not aligned to tick 5"
);
assert_eq!(
BacktestError::DataOutOfOrder {
step: 3,
ts: 900,
prev: 1_000
}
.to_string(),
"data out of order: step 3 ts 900 not strictly after previous 1000"
);
assert_eq!(
BacktestError::TapeTooLarge {
limit: "max_steps",
value: 200,
cap: 100
}
.to_string(),
"tape too large: max_steps = 200 exceeds cap 100"
);
assert_eq!(
BacktestError::Config("initial capital must be positive, got 0".to_string())
.to_string(),
"config: initial capital must be positive, got 0"
);
assert_eq!(
BacktestError::ArithmeticOverflow.to_string(),
"arithmetic overflow"
);
}
#[test]
fn test_error_from_io_error_maps_to_data_io() {
let io = std::io::Error::new(std::io::ErrorKind::NotFound, "missing.parquet");
let err = BacktestError::from(io);
assert!(matches!(err, BacktestError::DataIo(_)));
assert!(err.to_string().starts_with("data source io: "));
}
}