optionstratlib_core/constants.rs
1/******************************************************************************
2 Author: Joaquín Béjar García
3 Email: jb@taunais.com
4 Date: 11/8/24
5******************************************************************************/
6use positive::Positive;
7
8/// Days in a year, as `positive` defines it (365). Re-exported so that
9/// components reach it through core rather than by depending on `positive`
10/// directly (ADR-0001 D8).
11pub use positive::constants::DAYS_IN_A_YEAR;
12use rust_decimal::Decimal;
13use rust_decimal_macros::dec;
14use std::sync::LazyLock;
15
16/// Mathematical constant representing π (pi) with high precision using Decimal type.
17/// Used for circular calculations, angle conversions, and geometric computations.
18pub const PI: Decimal = dec!(3.1415926535897932384626433832);
19
20/// Represents zero as a 64-bit floating point number.
21/// Used as a baseline value for numerical comparisons and calculations.
22pub const ZERO: f64 = 0.0;
23
24/// Small decimal value used as a threshold for convergence tests and equality comparisons.
25/// Represents a general tolerance level for numerical algorithms.
26#[allow(dead_code)]
27pub const TOLERANCE: Decimal = dec!(1e-8);
28
29/// Extremely small decimal value used for high-precision calculations.
30/// Represents the smallest meaningful difference in numerical computations.
31pub const EPSILON: Decimal = dec!(1e-16);
32
33/// Minimum allowed volatility value as a `Positive` decimal.
34///
35/// Prevents numerical issues in financial calculations with near-zero volatility.
36/// Initialized once via `LazyLock`, like the six cells below it.
37///
38/// # Why the `Err` arm cannot fire
39///
40/// Each of these initialisers is a closure over nothing: its only input is a
41/// `dec!(…)` literal, expanded to a fixed mantissa and scale at compile time.
42/// [`Positive::new_decimal`] rejects exactly one thing, a value below the
43/// lower bound — zero without the `non-zero` feature, which this crate does
44/// not enable — and every literal here is strictly positive. There is no
45/// runtime value that reaches the `Err` arm, and no `Positive` constant to
46/// return in its place: `positive` keeps `from_decimal_const` crate-private,
47/// so a `const` alternative does not exist for values outside its own
48/// constant ladder. The alternative to aborting would be a stand-in like
49/// `Positive::ZERO`, which would silently make every annualisation divide by
50/// zero — worse than the abort it replaced.
51pub static MIN_VOLATILITY: LazyLock<Positive> = LazyLock::new(|| {
52 Positive::new_decimal(dec!(1e-16))
53 .unwrap_or_else(|e| unreachable!("MIN_VOLATILITY literal is positive and finite: {e}")) // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
54});
55
56/// Maximum allowed volatility value as a `Positive` decimal (100%).
57///
58/// Sets an upper bound for volatility inputs in financial models.
59pub const MAX_VOLATILITY: Positive = Positive::HUNDRED;
60
61/// Multiplier defining the lower bound for strike price ranges (98% of reference price).
62/// Used to establish the minimum strike price in option chains or pricing models.
63pub const STRIKE_PRICE_LOWER_BOUND_MULTIPLIER: f64 = 0.98;
64
65/// Multiplier defining the upper bound for strike price ranges (102% of reference price).
66/// Used to establish the maximum strike price in option chains or pricing models.
67pub const STRIKE_PRICE_UPPER_BOUND_MULTIPLIER: f64 = 1.02;
68
69/// Standard number of trading days in a year as a `Positive` decimal.
70///
71/// Used for business day-based financial calculations. See `MIN_VOLATILITY`
72/// for the `LazyLock` rationale.
73pub static TRADING_DAYS: LazyLock<Positive> = LazyLock::new(|| {
74 Positive::new_decimal(dec!(252.0))
75 .unwrap_or_else(|e| unreachable!("TRADING_DAYS literal is positive and finite: {e}")) // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
76});
77
78/// Standard number of trading hours in a market day as a `Positive` decimal.
79///
80/// Typically represents a standard U.S. market session (9:30 AM to 4:00 PM).
81pub static TRADING_HOURS: LazyLock<Positive> = LazyLock::new(|| {
82 Positive::new_decimal(dec!(6.5))
83 .unwrap_or_else(|e| unreachable!("TRADING_HOURS literal is positive and finite: {e}")) // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
84});
85
86/// Number of seconds in an hour as a `Positive` decimal value.
87///
88/// Used for time-based conversions and calculations.
89pub static SECONDS_PER_HOUR: LazyLock<Positive> = LazyLock::new(|| {
90 Positive::new_decimal(dec!(3600.0))
91 .unwrap_or_else(|e| unreachable!("SECONDS_PER_HOUR literal is positive and finite: {e}")) // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
92});
93
94/// Number of minutes in an hour as a `Positive` decimal value.
95///
96/// Aliased to `positive::constants::SIXTY`, which already exists upstream —
97/// no runtime initialization required.
98pub const MINUTES_PER_HOUR: Positive = positive::constants::SIXTY;
99
100/// Number of milliseconds in a second as a `Positive` decimal value.
101///
102/// Aliased to `positive::constants::THOUSAND`, which already exists upstream.
103pub const MILLISECONDS_PER_SECOND: Positive = positive::constants::THOUSAND;
104
105/// Number of microseconds in a second as a `Positive` decimal value.
106///
107/// No matching `positive::constants::*` entry for `1_000_000`, so the value
108/// is built once via `LazyLock`.
109pub static MICROSECONDS_PER_SECOND: LazyLock<Positive> = LazyLock::new(|| {
110 Positive::new_decimal(dec!(1_000_000.0)).unwrap_or_else(|e| {
111 unreachable!("MICROSECONDS_PER_SECOND literal is positive and finite: {e}") // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
112 })
113});
114
115/// Standard number of weeks in a year as a `Positive` decimal value.
116///
117/// Used for time-based financial calculations and annualization.
118pub static WEEKS_PER_YEAR: LazyLock<Positive> = LazyLock::new(|| {
119 Positive::new_decimal(dec!(52.0))
120 .unwrap_or_else(|e| unreachable!("WEEKS_PER_YEAR literal is positive and finite: {e}")) // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
121});
122
123/// Number of months in a year as a `Positive` decimal value.
124///
125/// Upstream `positive::constants` skips 11/12 in its integer ladder, so the
126/// value is built once via `LazyLock`.
127pub static MONTHS_PER_YEAR: LazyLock<Positive> = LazyLock::new(|| {
128 Positive::new_decimal(dec!(12.0))
129 .unwrap_or_else(|e| unreachable!("MONTHS_PER_YEAR literal is positive and finite: {e}")) // scan-banned: allow -- `dec!()` literal, `Err` arm has no reachable input; see MIN_VOLATILITY
130});
131
132/// Number of quarters in a year as a `Positive` decimal value.
133///
134/// Aliased to `positive::constants::FOUR`, which already exists upstream.
135pub const QUARTERS_PER_YEAR: Positive = positive::constants::FOUR;
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 /// Forces every `LazyLock` in this file.
142 ///
143 /// The `unreachable!` arms above are justified by the literals beside
144 /// them being strictly positive and finite, which is true of the literals
145 /// as written and of nothing else: `LazyLock` initialises at first
146 /// access, at run time, so an edit that makes one of these literals zero
147 /// or negative compiles cleanly and aborts on whichever request touches
148 /// it first. Unlike a `const` item, the compiler does not check
149 /// the premise.
150 ///
151 /// Dereferencing each one here moves that failure into the test suite,
152 /// where a bad literal is a red run rather than a dead worker. It is what
153 /// makes the markers honest rather than merely reviewed.
154 #[test]
155 fn test_every_lazy_constant_initialises() {
156 for (name, value) in [
157 ("MIN_VOLATILITY", *MIN_VOLATILITY),
158 ("TRADING_DAYS", *TRADING_DAYS),
159 ("TRADING_HOURS", *TRADING_HOURS),
160 ("SECONDS_PER_HOUR", *SECONDS_PER_HOUR),
161 ("MICROSECONDS_PER_SECOND", *MICROSECONDS_PER_SECOND),
162 ("WEEKS_PER_YEAR", *WEEKS_PER_YEAR),
163 ("MONTHS_PER_YEAR", *MONTHS_PER_YEAR),
164 ] {
165 assert!(
166 value > Positive::ZERO,
167 "{name} must be strictly positive, or its unreachable arm is reachable"
168 );
169 }
170 }
171}