use std::{convert::TryFrom, fmt};
use serde::{Deserialize, Serialize, de};
use super::{
SATS_PER_BTC,
error::QuantityValidationError,
leverage::Leverage,
margin::Margin,
price::{PercentageCapped, Price},
};
pub trait QuantityLike: crate::sealed::Sealed + Clone + Copy + PartialEq + Eq {
fn as_f64(&self) -> f64;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct OrderQuantity(u32);
#[deprecated(note = "use OrderQuantity")]
pub type Quantity = OrderQuantity;
impl OrderQuantity {
pub const MIN: Self = Self(1);
pub const MAX: Self = Self(500_000);
pub fn bounded<T>(value: T) -> Self
where
T: Into<f64>,
{
let as_f64: f64 = value.into();
let rounded = as_f64.round().max(0.0) as u32;
let clamped = rounded.clamp(Self::MIN.0, Self::MAX.0);
Self(clamped)
}
pub fn as_u32(&self) -> u32 {
self.0
}
pub fn as_u64(&self) -> u64 {
self.0 as u64
}
pub fn as_i64(&self) -> i64 {
self.0 as i64
}
pub fn as_f64(&self) -> f64 {
self.0 as f64
}
pub fn try_add(self, other: impl TryInto<i128>) -> Result<Self, QuantityValidationError> {
let other = other
.try_into()
.map_err(|_| QuantityValidationError::TooHigh { value: u128::MAX })?;
let sum = i128::from(self)
.checked_add(other)
.ok_or(QuantityValidationError::TooHigh { value: u128::MAX })?;
Self::try_from(sum)
}
pub fn try_sub(self, other: impl TryInto<i128>) -> Result<Self, QuantityValidationError> {
let other = other
.try_into()
.map_err(|_| QuantityValidationError::TooLow { value: i128::MIN })?;
let difference = i128::from(self)
.checked_sub(other)
.ok_or(QuantityValidationError::TooHigh { value: u128::MAX })?;
Self::try_from(difference)
}
pub fn try_calculate(
margin: Margin,
price: Price,
leverage: Leverage,
) -> Result<Self, QuantityValidationError> {
let qtd = margin.as_f64() * leverage.as_f64() * price.as_f64() / SATS_PER_BTC;
Self::try_from(qtd.floor() as u128)
}
#[deprecated(
note = "this helper is numerically ambiguous and will be removed in a future release"
)]
pub fn try_from_balance_perc(
balance: u64,
market_price: Price,
balance_perc: PercentageCapped,
) -> Result<Self, QuantityValidationError> {
let balance_usd = balance as f64 * market_price.as_f64() / SATS_PER_BTC;
let quantity_target = balance_usd * balance_perc.as_f64() / 100.0;
Self::try_from(quantity_target.floor())
}
}
impl crate::sealed::Sealed for OrderQuantity {}
impl QuantityLike for OrderQuantity {
fn as_f64(&self) -> f64 {
self.as_f64()
}
}
impl From<OrderQuantity> for u32 {
fn from(value: OrderQuantity) -> Self {
value.0
}
}
impl From<OrderQuantity> for u64 {
fn from(value: OrderQuantity) -> Self {
value.0 as u64
}
}
impl From<OrderQuantity> for u128 {
fn from(value: OrderQuantity) -> Self {
value.0 as u128
}
}
impl From<OrderQuantity> for i64 {
fn from(value: OrderQuantity) -> Self {
value.0 as i64
}
}
impl From<OrderQuantity> for i128 {
fn from(value: OrderQuantity) -> Self {
value.0 as i128
}
}
impl From<OrderQuantity> for f64 {
fn from(value: OrderQuantity) -> Self {
value.0 as f64
}
}
impl TryFrom<u8> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::try_from(value as u128)
}
}
impl TryFrom<u16> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Self::try_from(value as u128)
}
}
impl TryFrom<u32> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::try_from(value as u128)
}
}
impl TryFrom<u64> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Self::try_from(value as u128)
}
}
impl TryFrom<u128> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: u128) -> Result<Self, Self::Error> {
if value < Self::MIN.0 as u128 {
return Err(QuantityValidationError::TooLow {
value: value as i128,
});
}
if value > Self::MAX.0 as u128 {
return Err(QuantityValidationError::TooHigh { value });
}
Ok(OrderQuantity(value as u32))
}
}
impl TryFrom<usize> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Self::try_from(value as u128)
}
}
impl TryFrom<i8> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: i8) -> Result<Self, Self::Error> {
Self::try_from(value as i128)
}
}
impl TryFrom<i16> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
Self::try_from(value as i128)
}
}
impl TryFrom<i32> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(quantity: i32) -> Result<Self, Self::Error> {
Self::try_from(quantity as i128)
}
}
impl TryFrom<i64> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Self::try_from(value as i128)
}
}
impl TryFrom<i128> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: i128) -> Result<Self, Self::Error> {
if value < Self::MIN.0 as i128 {
return Err(QuantityValidationError::TooLow { value });
}
if value > Self::MAX.0 as i128 {
return Err(QuantityValidationError::TooHigh {
value: value as u128,
});
}
Ok(OrderQuantity(value as u32))
}
}
impl TryFrom<isize> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: isize) -> Result<Self, Self::Error> {
Self::try_from(value as i128)
}
}
impl TryFrom<f32> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::try_from(value as f64)
}
}
impl TryFrom<f64> for OrderQuantity {
type Error = QuantityValidationError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value.fract() != 0.0 {
return Err(QuantityValidationError::NotAnInteger { value });
}
Self::try_from(value as i128)
}
}
impl fmt::Display for OrderQuantity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for OrderQuantity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u32(self.0)
}
}
impl<'de> Deserialize<'de> for OrderQuantity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let quantity = u32::deserialize(deserializer)?;
OrderQuantity::try_from(quantity).map_err(|e| de::Error::custom(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_add_quantity() {
let base = OrderQuantity::try_from(1_000).unwrap();
let added = OrderQuantity::try_from(500).unwrap();
let total = base.try_add(added).unwrap();
assert_eq!(total, OrderQuantity::try_from(1_500).unwrap());
}
#[test]
fn test_try_add_quantity_fails_above_max() {
let error = OrderQuantity::MAX
.try_add(OrderQuantity::MIN)
.err()
.unwrap();
assert!(matches!(
error,
QuantityValidationError::TooHigh { value }
if value == (OrderQuantity::MAX.as_u64() + OrderQuantity::MIN.as_u64()) as u128
));
}
#[test]
fn test_try_sub_quantity() {
let base = OrderQuantity::try_from(1_000).unwrap();
let removed = OrderQuantity::try_from(500).unwrap();
let remaining = base.try_sub(removed).unwrap();
assert_eq!(remaining, OrderQuantity::try_from(500).unwrap());
}
#[test]
fn test_try_sub_quantity_fails_below_min() {
let error = OrderQuantity::MIN
.try_sub(OrderQuantity::MIN)
.err()
.unwrap();
assert!(matches!(
error,
QuantityValidationError::TooLow { value } if value == 0
));
let error = OrderQuantity::MIN
.try_sub(OrderQuantity::try_from(2).unwrap())
.err()
.unwrap();
assert!(matches!(
error,
QuantityValidationError::TooLow { value } if value == -1
));
}
#[test]
fn test_try_add_quantity_with_primitive_integer() {
let base = OrderQuantity::try_from(1_000).unwrap();
let total = base.try_add(500i32).unwrap();
assert_eq!(total, OrderQuantity::try_from(1_500).unwrap());
let total = base.try_add(500usize).unwrap();
assert_eq!(total, OrderQuantity::try_from(1_500).unwrap());
let total = base.try_add(500u128).unwrap();
assert_eq!(total, OrderQuantity::try_from(1_500).unwrap());
}
#[test]
fn test_try_sub_quantity_with_primitive_integer() {
let base = OrderQuantity::try_from(1_000).unwrap();
let remaining = base.try_sub(500i32).unwrap();
assert_eq!(remaining, OrderQuantity::try_from(500).unwrap());
let remaining = base.try_sub(500usize).unwrap();
assert_eq!(remaining, OrderQuantity::try_from(500).unwrap());
let remaining = base.try_sub(500u128).unwrap();
assert_eq!(remaining, OrderQuantity::try_from(500).unwrap());
}
#[test]
fn test_try_add_quantity_negative_operand_reduces_value() {
let base = OrderQuantity::try_from(1_000).unwrap();
let total = base.try_add(-500i64).unwrap();
assert_eq!(total, OrderQuantity::try_from(500).unwrap());
}
#[test]
fn test_try_add_quantity_negative_operand_fails_when_below_min() {
let error = OrderQuantity::try_from(500)
.unwrap()
.try_add(-501i64)
.err()
.unwrap();
assert!(matches!(
error,
QuantityValidationError::TooLow { value } if value == -1
));
}
#[test]
fn test_try_sub_quantity_negative_operand_increases_value() {
let base = OrderQuantity::try_from(1_000).unwrap();
let total = base.try_sub(-500i64).unwrap();
assert_eq!(total, OrderQuantity::try_from(1_500).unwrap());
}
#[test]
fn test_try_sub_quantity_large_negative_operand_reports_too_high() {
let error = OrderQuantity::MAX.try_sub(i128::MIN).err().unwrap();
assert!(matches!(
error,
QuantityValidationError::TooHigh { value } if value == u128::MAX
));
}
#[test]
fn test_try_add_quantity_overflow_reports_true_value() {
let error = OrderQuantity::MAX.try_add(10u32).err().unwrap();
assert!(matches!(
error,
QuantityValidationError::TooHigh { value }
if value == OrderQuantity::MAX.as_u64() as u128 + 10
));
}
#[test]
fn test_calculate_quantity() {
let margin = Margin::try_from(1_000).unwrap();
let price = Price::try_from(100_000).unwrap();
let leverage = Leverage::try_from(1.0).unwrap();
let quantity = OrderQuantity::try_calculate(margin, price, leverage).unwrap();
assert_eq!(quantity, OrderQuantity::MIN);
let margin = Margin::try_from(700).unwrap();
let price = Price::try_from(100_000).unwrap();
let leverage = Leverage::try_from(2.0).unwrap();
let quantity = OrderQuantity::try_calculate(margin, price, leverage).unwrap();
assert_eq!(quantity, OrderQuantity::MIN);
let margin = Margin::try_from(10).unwrap();
let price = Price::try_from(100_000).unwrap();
let leverage = Leverage::try_from(100.0).unwrap();
let quantity = OrderQuantity::try_calculate(margin, price, leverage).unwrap();
assert_eq!(quantity, OrderQuantity::MIN);
let margin = Margin::try_from(5_000_000).unwrap();
let price = Price::try_from(100_000).unwrap();
let leverage = Leverage::try_from(100.0).unwrap();
let quantity = OrderQuantity::try_calculate(margin, price, leverage).unwrap();
assert_eq!(quantity, OrderQuantity::MAX);
let margin = Margin::try_from(9).unwrap();
let price = Price::try_from(100_000).unwrap();
let leverage = Leverage::try_from(100.0).unwrap();
let quantity_validation_error = OrderQuantity::try_calculate(margin, price, leverage)
.err()
.unwrap();
assert!(matches!(
quantity_validation_error,
QuantityValidationError::TooLow { value: _ }
));
let margin = Margin::try_from(5_001_000).unwrap();
let price = Price::try_from(100_000).unwrap();
let leverage = Leverage::try_from(100.0).unwrap();
let quantity_validation_error = OrderQuantity::try_calculate(margin, price, leverage)
.err()
.unwrap();
assert!(matches!(
quantity_validation_error,
QuantityValidationError::TooHigh { value: _ }
));
}
}