pub mod mid;
pub mod qty;
pub mod signed_qty;
pub use mid::{Mid, OBSERVED_PER_UNIT, OBSERVED_PLACES};
pub use qty::{BadQty, Qty};
pub use signed_qty::{BadSignedQty, SignedQty};
use std::{fmt, str::FromStr};
use crate::markets::outcomes::coin::PRICE_PLACES;
use crate::utils::{Dec, Round};
use serde::{Deserialize, Deserializer, de};
pub const TICKS_PER_UNIT: u32 = 100_000;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadPx {
#[error("`{0}` is not a decimal number")]
NotANumber(String),
#[error("price `{0}` is outside 0 to 1")]
OutOfRange(String),
#[error("price `{0}` is finer than the 0.00001 tick")]
TooFine(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Px(u32);
impl Px {
pub const ZERO: Px = Px(0);
pub const ONE: Px = Px(TICKS_PER_UNIT);
pub const MIN_QUOTE: Px = Px(1);
pub const MAX_QUOTE: Px = Px(TICKS_PER_UNIT - 1);
#[must_use]
pub const fn from_ticks(ticks: u32) -> Option<Px> {
match ticks <= TICKS_PER_UNIT {
true => Some(Px(ticks)),
false => None,
}
}
#[must_use]
pub const fn ticks(self) -> u32 {
self.0
}
#[must_use]
pub const fn complement(self) -> Px {
Px(TICKS_PER_UNIT - self.0)
}
#[must_use]
pub const fn is_zero(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn is_quotable(self) -> bool {
self.0 >= Px::MIN_QUOTE.0 && self.0 <= Px::MAX_QUOTE.0
}
#[must_use]
pub const fn clamp_to_quotable(self) -> Px {
if self.0 < Px::MIN_QUOTE.0 {
Px::MIN_QUOTE
} else if self.0 > Px::MAX_QUOTE.0 {
Px::MAX_QUOTE
} else {
self
}
}
#[must_use]
pub const fn midpoint(self, other: Px) -> Px {
Px((self.0 + other.0) / 2)
}
#[must_use]
pub const fn checked_add(self, other: Px) -> Option<Px> {
Px::from_ticks(self.0 + other.0)
}
#[must_use]
pub const fn abs_diff(self, other: Px) -> u32 {
self.0.abs_diff(other.0)
}
#[must_use]
pub const fn step(self, ticks: i32) -> Px {
let moved = self.0 as i64 + ticks as i64;
if moved < 0 {
Px::ZERO
} else if moved > TICKS_PER_UNIT as i64 {
Px::ONE
} else {
Px(moved as u32)
}
}
pub fn parse(text: &str) -> Result<Px, BadPx> {
let value = Dec::parse(text).map_err(|_| BadPx::NotANumber(text.to_owned()))?;
if value.is_negative() {
return Err(BadPx::NotANumber(text.to_owned()));
}
Px::exact(value).map_err(|why| why.naming(text))
}
pub fn from_dec(value: Dec) -> Result<Px, BadPx> {
Px::exact(value).map_err(|why| why.naming(&value.to_string()))
}
fn exact(value: Dec) -> Result<Px, BadPx> {
if value.places() > PRICE_PLACES {
return Err(BadPx::TooFine(String::new()));
}
Px::on_grid(value, Round::Down).ok_or(BadPx::OutOfRange(String::new()))
}
#[must_use]
pub fn on_grid(value: Dec, round: Round) -> Option<Px> {
let ticks = value.units_at(PRICE_PLACES, round)?;
u32::try_from(ticks).ok().and_then(Px::from_ticks)
}
#[must_use]
pub fn nearest(value: Dec) -> Option<Px> {
Px::on_grid(value, Round::Nearest)
}
#[must_use]
pub const fn to_dec(self) -> Dec {
Dec::lit::<PRICE_PLACES>(self.0 as i128)
}
#[must_use]
pub fn to_wire(self) -> String {
self.to_dec().to_wire()
}
}
impl BadPx {
fn naming(self, text: &str) -> BadPx {
let text = text.to_owned();
match self {
BadPx::NotANumber(_) => BadPx::NotANumber(text),
BadPx::OutOfRange(_) => BadPx::OutOfRange(text),
BadPx::TooFine(_) => BadPx::TooFine(text),
}
}
}
impl fmt::Display for Px {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_dec())
}
}
impl FromStr for Px {
type Err = BadPx;
fn from_str(text: &str) -> Result<Self, Self::Err> {
Px::parse(text)
}
}
impl<'de> Deserialize<'de> for Px {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
Px::parse(&text).map_err(de::Error::custom)
}
}
#[cfg(test)]
#[path = "tests/px.rs"]
mod tests;