#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Px(pub i64);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
pub struct Qty(pub i64);
impl Px {
pub const ZERO: Self = Self(0);
#[inline(always)]
pub const fn raw(v: i64) -> Self { Self(v) }
#[inline(always)]
pub const fn from_u64(v: u64) -> Self { Self(v as i64) }
#[inline]
pub fn from_float(v: f64) -> Self { Self((v * 1_000_000_000.0) as i64) }
#[inline(always)]
pub fn to_float(self) -> f64 { self.0 as f64 / 1_000_000_000.0 }
#[inline(always)]
pub const fn as_raw(self) -> i64 { self.0 }
#[inline(always)]
pub const fn as_u64(self) -> u64 { self.0 as u64 }
#[inline(always)]
pub fn mid(self, other: Px) -> Px { Px((self.0 + other.0) / 2) }
#[inline(always)]
pub fn spread_bps(self, bid: Px) -> i32 {
let mid = (self.0 + bid.0) / 2;
if mid == 0 { return i32::MAX; }
(((self.0 - bid.0) as i128 * 10_000) / mid as i128) as i32
}
#[inline(always)]
pub fn notional(self, qty: Qty) -> i64 {
((self.0 as i128 * qty.0 as i128) / 100_000_000) as i64
}
#[inline(always)]
pub fn offset_bps(self, bps: i32) -> Px {
Px(self.0 + ((self.0 as i128 * bps as i128) / 10_000) as i64)
}
}
impl Qty {
pub const ZERO: Self = Self(0);
#[inline(always)]
pub const fn raw(v: i64) -> Self { Self(v) }
#[inline]
pub fn from_float(v: f64) -> Self { Self((v * 100_000_000.0) as i64) }
#[inline(always)]
pub fn to_float(self) -> f64 { self.0 as f64 / 100_000_000.0 }
#[inline(always)]
pub const fn as_raw(self) -> i64 { self.0 }
#[inline(always)]
pub fn abs(self) -> Qty { Qty(self.0.abs()) }
#[inline(always)]
pub fn is_zero(self) -> bool { self.0 == 0 }
#[inline(always)]
pub fn is_long(self) -> bool { self.0 > 0 }
#[inline(always)]
pub fn is_short(self) -> bool { self.0 < 0 }
#[inline]
pub fn split(self, n: u32) -> Qty {
if n == 0 { return self; }
Qty(self.0 / n as i64)
}
}
impl core::fmt::Debug for Px {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Px(${:.4})", self.to_float())
}
}
impl core::fmt::Display for Px {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "${:.4}", self.to_float())
}
}
impl core::fmt::Debug for Qty {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Qty({:.8})", self.to_float())
}
}
impl core::fmt::Display for Qty {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:.8}", self.to_float())
}
}
impl From<i64> for Px {
#[inline(always)]
fn from(v: i64) -> Self { Self(v) }
}
impl From<u64> for Px {
#[inline(always)]
fn from(v: u64) -> Self { Self(v as i64) }
}
impl From<Px> for i64 {
#[inline(always)]
fn from(v: Px) -> Self { v.0 }
}
impl From<Px> for u64 {
#[inline(always)]
fn from(v: Px) -> Self { v.0 as u64 }
}
impl From<i64> for Qty {
#[inline(always)]
fn from(v: i64) -> Self { Self(v) }
}
impl From<Qty> for i64 {
#[inline(always)]
fn from(v: Qty) -> Self { v.0 }
}
impl core::ops::Add for Px {
type Output = Px;
#[inline(always)]
fn add(self, rhs: Px) -> Px { Px(self.0 + rhs.0) }
}
impl core::ops::Sub for Px {
type Output = Px;
#[inline(always)]
fn sub(self, rhs: Px) -> Px { Px(self.0 - rhs.0) }
}
impl core::ops::Neg for Px {
type Output = Px;
#[inline(always)]
fn neg(self) -> Px { Px(-self.0) }
}
impl core::ops::Add for Qty {
type Output = Qty;
#[inline(always)]
fn add(self, rhs: Qty) -> Qty { Qty(self.0 + rhs.0) }
}
impl core::ops::Sub for Qty {
type Output = Qty;
#[inline(always)]
fn sub(self, rhs: Qty) -> Qty { Qty(self.0 - rhs.0) }
}
impl core::ops::Neg for Qty {
type Output = Qty;
#[inline(always)]
fn neg(self) -> Qty { Qty(-self.0) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn px_from_float() {
let p = Px::from_float(50_000.50);
assert_eq!(p.0, 50_000_500_000_000);
assert!((p.to_float() - 50_000.50).abs() < 0.001);
}
#[test]
fn qty_from_float() {
let q = Qty::from_float(1.5);
assert_eq!(q.0, 150_000_000);
assert!((q.to_float() - 1.5).abs() < 0.001);
}
#[test]
fn px_notional() {
let px = Px::from_float(50_000.0);
let qty = Qty::from_float(2.0);
let notional = px.notional(qty);
assert_eq!(notional, 100_000_000_000_000);
}
#[test]
fn px_spread_bps() {
let ask = Px::raw(101_000_000_000);
let bid = Px::raw(99_000_000_000);
assert_eq!(ask.spread_bps(bid), 200);
}
#[test]
fn px_offset_bps() {
let p = Px::raw(100_000_000_000); let up = p.offset_bps(100); assert_eq!(up.0, 101_000_000_000);
}
#[test]
fn qty_split() {
let q = Qty::from_float(10.0);
let part = q.split(5);
assert_eq!(part, Qty::from_float(2.0));
}
#[test]
fn zero_cost_repr() {
assert_eq!(core::mem::size_of::<Px>(), core::mem::size_of::<i64>());
assert_eq!(core::mem::size_of::<Qty>(), core::mem::size_of::<i64>());
}
}