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>;