Skip to main content

wickra_core/
error.rs

1//! Error types used across `wickra-core`.
2
3use thiserror::Error;
4
5/// Largest window length any indicator will accept.
6///
7/// Nothing in the maths needs a bound, but the allocation does: a constructor
8/// sizes its buffers from the period, so `Ema::new(usize::MAX)` aborts with a
9/// capacity overflow and `Ema::new(1_000_000_000)` reserves eight gigabytes
10/// before the caller sees anything go wrong. Bindings make that easy to reach
11/// by accident — a mistyped literal, a period read from a config file.
12///
13/// `1 << 24` is 16777216: a single `f64` buffer of that length is 128 MiB, which
14/// is far past any real window while leaving `period` arithmetic such as
15/// `6 * period - 5` nowhere near overflowing. Exceeding it is reported as
16/// [`Error::InvalidPeriod`] rather than a panic.
17pub const MAX_PERIOD: usize = 1 << 24;
18
19/// Message carried by [`Error::InvalidPeriod`] when a period exceeds
20/// [`MAX_PERIOD`]. Deliberately does not repeat the number, so the two cannot
21/// drift apart.
22pub(crate) const PERIOD_ABOVE_MAX: &str =
23    "period exceeds the maximum supported window length (see MAX_PERIOD)";
24
25/// Errors that can occur when constructing or operating on an indicator.
26///
27/// Marked `#[non_exhaustive]`: the set of validation failures grows as the
28/// catalogue does, so downstream code must carry a wildcard arm and a new
29/// variant stays a minor-version change rather than a breaking one.
30#[derive(Debug, Clone, PartialEq, Eq, Error)]
31#[non_exhaustive]
32pub enum Error {
33    /// A period (window length) must be at least one.
34    #[error("period must be greater than zero")]
35    PeriodZero,
36
37    /// A specific minimum period requirement was not met (e.g. MACD needs slow > fast).
38    #[error("invalid period: {message}")]
39    InvalidPeriod { message: &'static str },
40
41    /// A non-finite value (NaN or infinity) was passed where a finite price was expected.
42    #[error("input value must be finite (got NaN or infinity)")]
43    NonFiniteInput,
44
45    /// A candle whose components do not form a valid bar (e.g. high < low) was provided.
46    #[error("invalid candle: {message}")]
47    InvalidCandle { message: &'static str },
48
49    /// A tick whose components do not satisfy the tick invariants (e.g. negative
50    /// volume) was provided. Ticks are a different concept from candles and
51    /// surface as their own variant so consumers of a tick-stream pipeline
52    /// can match on a semantically-correct error instead of `InvalidCandle`.
53    #[error("invalid tick: {message}")]
54    InvalidTick { message: &'static str },
55
56    /// A multiplier or factor must be strictly positive.
57    #[error("multiplier must be greater than zero")]
58    NonPositiveMultiplier,
59
60    /// An order-book snapshot whose levels do not satisfy the book invariants
61    /// (e.g. a crossed book, non-finite price, negative size, or mis-sorted
62    /// levels) was provided. Order books are a microstructure input distinct
63    /// from candles and ticks, so they surface as their own variant.
64    #[error("invalid order book: {message}")]
65    InvalidOrderBook { message: &'static str },
66
67    /// A trade whose components do not satisfy the trade invariants (e.g.
68    /// non-finite price or negative size) was provided.
69    #[error("invalid trade: {message}")]
70    InvalidTrade { message: &'static str },
71
72    /// A derivatives tick whose components do not satisfy the tick invariants
73    /// (e.g. a non-positive price, a non-finite funding rate, or a negative
74    /// size/volume/liquidation) was provided. Derivatives ticks (funding /
75    /// open-interest / liquidation feeds) are a perpetual-futures input
76    /// distinct from candles, order books and trades, so they surface as their
77    /// own variant.
78    #[error("invalid derivatives tick: {message}")]
79    InvalidDerivatives { message: &'static str },
80
81    /// A market-breadth cross-section whose members do not satisfy the
82    /// cross-section invariants (an empty universe, a non-finite change, or a
83    /// negative / non-finite volume) was provided. A cross-section is a
84    /// breadth input distinct from candles, ticks, order books and trades, so
85    /// it surfaces as its own variant.
86    #[error("invalid cross-section: {message}")]
87    InvalidCrossSection { message: &'static str },
88
89    /// A real-valued configuration parameter was outside its admissible range
90    /// (e.g. a non-positive standard-deviation multiplier, or a Kalman filter
91    /// covariance that is not strictly positive). This is the floating-point
92    /// analogue of [`Error::InvalidPeriod`], which only covers integer windows.
93    #[error("invalid parameter: {message}")]
94    InvalidParameter { message: &'static str },
95}
96
97/// Convenience alias for `Result<T, wickra_core::Error>`.
98pub type Result<T> = core::result::Result<T, Error>;