betex 0.35.0

Betfair / Prediction Market Exchange
Documentation
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, Sub};

/// Integral amount type used by the engine/book for stake/size/remaining.
///
/// # Units (Quanta)
/// `Money` is an integer count of **quanta**, not cents.
///
/// - `Money(1)` is **$0.0001** (one ten-thousandth of a dollar).
/// - This makes probability-tick prediction markets with `MAX=10_000` exact:
///   `cost_quanta = qty_shares * price_ticks` (no division, no rounding).
///
/// The engine avoids floating point to keep matching deterministic.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    PartialOrd,
    Ord,
    Default,
    Hash,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct Money(pub i64);

impl Money {
    /// Quanta per USD (i.e. `Money(MONEY_SCALE_PER_USD)` == $1.00).
    pub const MONEY_SCALE_PER_USD: i64 = 10_000;

    /// Quanta per cent (i.e. `Money(MONEY_SCALE_PER_CENT)` == $0.01).
    pub const MONEY_SCALE_PER_CENT: i64 = Self::MONEY_SCALE_PER_USD / 100;

    pub fn zero() -> Self {
        Money(0)
    }
    pub fn is_positive(self) -> bool {
        self.0 > 0
    }
    pub fn saturating_sub(self, other: Money) -> Money {
        Money(self.0.saturating_sub(other.0))
    }
    pub fn saturating_add(self, other: Money) -> Money {
        Money(self.0.saturating_add(other.0))
    }
    pub fn clamp_non_negative(self) -> Money {
        Money(self.0.max(0))
    }

    /// Convert cents to `Money` quanta.
    pub fn from_cents(cents: i64) -> Self {
        Money(cents.saturating_mul(Self::MONEY_SCALE_PER_CENT))
    }

    /// Convert `Money` quanta to cents by truncation towards zero.
    pub fn to_cents_trunc(self) -> i64 {
        self.0 / Self::MONEY_SCALE_PER_CENT
    }
}

impl Add for Money {
    type Output = Money;
    fn add(self, rhs: Money) -> Money {
        match self.0.checked_add(rhs.0) {
            Some(v) => Money(v),
            None => {
                debug_assert!(false, "Money add overflow: {} + {}", self.0, rhs.0);
                Money(self.0.saturating_add(rhs.0))
            }
        }
    }
}

impl Sub for Money {
    type Output = Money;
    fn sub(self, rhs: Money) -> Money {
        match self.0.checked_sub(rhs.0) {
            Some(v) => Money(v),
            None => {
                debug_assert!(false, "Money sub overflow: {} - {}", self.0, rhs.0);
                Money(self.0.saturating_sub(rhs.0))
            }
        }
    }
}

impl fmt::Display for Money {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let abs = (self.0 as i128).abs();
        let whole = abs / Self::MONEY_SCALE_PER_USD as i128;
        let frac = abs % Self::MONEY_SCALE_PER_USD as i128;
        if self.0 < 0 {
            write!(f, "-{}.{:04}", whole, frac)
        } else {
            write!(f, "{}.{:04}", whole, frac)
        }
    }
}