Skip to main content

paft_money/
quantity.rs

1//! Full-precision quantity type for market sizes and volumes.
2
3use crate::decimal::{self, Decimal};
4use paft_decimal::{DecimalConstraintError, NonNegativeDecimal};
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::hash::{Hash, Hasher};
8
9#[cfg(feature = "dataframe")]
10use df_derive_macros::ToDataFrame;
11
12/// Full-precision non-negative quantity whose unit is supplied by surrounding context.
13///
14/// Use `QuantityAmount` for provider-agnostic sizes and volumes where the
15/// quantity unit may be shares, contracts, base units, quote units, lots, or a
16/// fractional venue-specific unit. Count-only fields with inherently integral
17/// semantics can keep using integer types.
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(transparent)]
20#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
21pub struct QuantityAmount {
22    #[cfg_attr(feature = "dataframe", df_derive(decimal(precision = 38, scale = 10)))]
23    amount: NonNegativeDecimal,
24}
25
26impl QuantityAmount {
27    /// Creates a contextual quantity amount from a non-negative decimal.
28    #[must_use]
29    pub const fn new(amount: NonNegativeDecimal) -> Self {
30        Self { amount }
31    }
32
33    /// Creates a contextual quantity amount from a decimal.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`DecimalConstraintError`] when `amount < 0`.
38    pub fn from_decimal(amount: Decimal) -> Result<Self, DecimalConstraintError> {
39        Ok(Self::new(NonNegativeDecimal::new(amount)?))
40    }
41
42    /// Returns the wrapped non-negative decimal by reference.
43    #[must_use]
44    pub const fn as_non_negative_decimal(&self) -> &NonNegativeDecimal {
45        &self.amount
46    }
47
48    /// Returns the wrapped decimal by reference.
49    #[must_use]
50    pub const fn as_decimal(&self) -> &Decimal {
51        self.amount.as_decimal()
52    }
53
54    /// Returns the wrapped non-negative decimal.
55    #[must_use]
56    #[cfg(not(feature = "bigdecimal"))]
57    pub const fn into_inner(self) -> NonNegativeDecimal {
58        self.amount
59    }
60
61    /// Returns the wrapped non-negative decimal.
62    #[must_use]
63    #[cfg(feature = "bigdecimal")]
64    pub fn into_inner(self) -> NonNegativeDecimal {
65        self.amount
66    }
67}
68
69impl AsRef<NonNegativeDecimal> for QuantityAmount {
70    fn as_ref(&self) -> &NonNegativeDecimal {
71        self.as_non_negative_decimal()
72    }
73}
74
75#[cfg(feature = "dataframe")]
76impl paft_utils::dataframe::Decimal128Encode for QuantityAmount {
77    fn try_to_i128_mantissa(&self, target_scale: u32) -> Option<i128> {
78        paft_utils::dataframe::Decimal128Encode::try_to_i128_mantissa(
79            self.as_non_negative_decimal(),
80            target_scale,
81        )
82    }
83}
84
85impl TryFrom<Decimal> for QuantityAmount {
86    type Error = DecimalConstraintError;
87
88    fn try_from(value: Decimal) -> Result<Self, Self::Error> {
89        Self::from_decimal(value)
90    }
91}
92
93impl From<NonNegativeDecimal> for QuantityAmount {
94    fn from(value: NonNegativeDecimal) -> Self {
95        Self::new(value)
96    }
97}
98
99impl From<QuantityAmount> for NonNegativeDecimal {
100    fn from(value: QuantityAmount) -> Self {
101        value.into_inner()
102    }
103}
104
105impl Hash for QuantityAmount {
106    fn hash<H: Hasher>(&self, state: &mut H) {
107        decimal::to_canonical_string(self.as_decimal()).hash(state);
108    }
109}
110
111impl fmt::Display for QuantityAmount {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        f.write_str(&decimal::to_canonical_string(self.as_decimal()))
114    }
115}