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
//! Validated basis-points value object.
//!
//! Replaces raw `Decimal` fields like `slippage_bps`, `commission_bps`, etc.
//! with a type that guarantees the value is in [0, 10000].
//! 1 basis point = 0.01% = 0.0001 as a fraction.
use std::fmt;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::Fraction;
/// A basis-points value validated to the range [0, 10000].
///
/// 10000 bps = 100% = 1.0 as a fraction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Bps(Decimal);
/// Error for invalid basis-points construction.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BpsError {
/// Value is outside the valid [0, 10000] range.
#[error("basis points {0} out of range [0, 10000]")]
OutOfRange(Decimal),
}
impl Bps {
/// Create a new basis-points value, validating that the value is in [0, 10000].
pub fn new(value: Decimal) -> Result<Self, BpsError> {
if value < Decimal::ZERO || value > Decimal::from(10000) {
return Err(BpsError::OutOfRange(value));
}
Ok(Self(value))
}
/// Create basis points from a trusted integer value.
///
/// # Panics
///
/// Panics if `value > 10000`.
pub fn from_trusted(value: u32) -> Self {
assert!(value <= 10000, "from_trusted: {value} > 10000");
Self(Decimal::from(value))
}
/// The raw basis-points value (0-10000).
pub fn value(&self) -> Decimal {
self.0
}
/// Convert to a [`Fraction`] in [0.0, 1.0].
///
/// 100 bps → 0.01, 10000 bps → 1.0.
pub fn as_fraction(&self) -> Decimal {
self.0 / Decimal::from(10000)
}
/// Convert to a validated [`Fraction`] value.
pub fn to_fraction(&self) -> Fraction {
// Safety: Bps is [0, 10000], so / 10000 is always [0, 1].
Fraction(self.0 / Decimal::from(10000))
}
/// The zero basis points.
pub fn zero() -> Self {
Self(Decimal::ZERO)
}
}
impl fmt::Display for Bps {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}bps", self.0.normalize())
}
}
#[cfg(test)]
#[path = "bps_tests.rs"]
mod tests;