use serde::{Deserialize, Serialize};
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Hash,
rkyv::Archive,
rkyv::Serialize,
rkyv::Deserialize,
)]
pub struct OddsX10000(pub u32);
pub const TICK_LADDER: [u32; 350] = generate_tick_ladder();
const fn generate_tick_ladder() -> [u32; 350] {
let mut ticks = [0u32; 350];
let mut idx = 0;
let mut price = 10100u32;
while price <= 20000 {
ticks[idx] = price;
idx += 1;
price += 100;
}
price = 20200;
while price <= 30000 {
ticks[idx] = price;
idx += 1;
price += 200;
}
price = 30500;
while price <= 40000 {
ticks[idx] = price;
idx += 1;
price += 500;
}
price = 41000;
while price <= 60000 {
ticks[idx] = price;
idx += 1;
price += 1000;
}
price = 62000;
while price <= 100000 {
ticks[idx] = price;
idx += 1;
price += 2000;
}
price = 105000;
while price <= 200000 {
ticks[idx] = price;
idx += 1;
price += 5000;
}
price = 210000;
while price <= 300000 {
ticks[idx] = price;
idx += 1;
price += 10000;
}
price = 320000;
while price <= 500000 {
ticks[idx] = price;
idx += 1;
price += 20000;
}
price = 550000;
while price <= 1000000 {
ticks[idx] = price;
idx += 1;
price += 50000;
}
price = 1100000;
while price <= 10000000 {
ticks[idx] = price;
idx += 1;
price += 100000;
}
ticks
}
impl OddsX10000 {
pub const MIN: u32 = 10_100;
pub const MAX: u32 = 10_000_000;
pub const fn new(value: u32) -> Self {
Self(value)
}
pub fn from_decimal(odds: f64) -> Option<Self> {
if !odds.is_finite() || odds < 0.0 {
return None;
}
let scaled = (odds * 10000.0).round();
if scaled < 0.0 || scaled > u32::MAX as f64 {
return None;
}
Some(Self(scaled as u32))
}
pub fn to_decimal(self) -> f64 {
self.0 as f64 / 10000.0
}
pub fn is_valid(self) -> bool {
self.0 >= Self::MIN
}
pub fn is_valid_tick(self) -> bool {
TICK_LADDER.binary_search(&self.0).is_ok()
}
pub fn floor_tick(self) -> Option<OddsX10000> {
if self.0 < Self::MIN {
return None;
}
if self.0 > Self::MAX {
return Some(OddsX10000(Self::MAX));
}
match TICK_LADDER.binary_search(&self.0) {
Ok(_) => Some(self), Err(idx) => {
if idx == 0 {
None
} else {
Some(OddsX10000(TICK_LADDER[idx - 1]))
}
}
}
}
pub fn ceil_tick(self) -> Option<OddsX10000> {
if self.0 > Self::MAX {
return None;
}
if self.0 < Self::MIN {
return Some(OddsX10000(Self::MIN));
}
match TICK_LADDER.binary_search(&self.0) {
Ok(_) => Some(self), Err(idx) => {
if idx >= TICK_LADDER.len() {
None
} else {
Some(OddsX10000(TICK_LADDER[idx]))
}
}
}
}
pub fn tick_up(self) -> Option<OddsX10000> {
match TICK_LADDER.binary_search(&self.0) {
Ok(idx) => {
if idx + 1 < TICK_LADDER.len() {
Some(OddsX10000(TICK_LADDER[idx + 1]))
} else {
None
}
}
Err(idx) => {
if idx < TICK_LADDER.len() {
Some(OddsX10000(TICK_LADDER[idx]))
} else {
None
}
}
}
}
pub fn tick_down(self) -> Option<OddsX10000> {
match TICK_LADDER.binary_search(&self.0) {
Ok(idx) => {
if idx > 0 {
Some(OddsX10000(TICK_LADDER[idx - 1]))
} else {
None
}
}
Err(idx) => {
if idx > 0 {
Some(OddsX10000(TICK_LADDER[idx - 1]))
} else {
None
}
}
}
}
pub fn tick_index(self) -> Option<usize> {
TICK_LADDER.binary_search(&self.0).ok()
}
pub fn ticks_between(self, other: OddsX10000) -> Option<i32> {
let self_idx = self.tick_index()?;
let other_idx = other.tick_index()?;
Some(other_idx as i32 - self_idx as i32)
}
pub fn from_tick_index(idx: usize) -> Option<OddsX10000> {
TICK_LADDER.get(idx).copied().map(OddsX10000)
}
}
impl std::fmt::Display for OddsX10000 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let whole = self.0 / 10_000;
let frac = self.0 % 10_000;
write!(f, "{}.{:04}", whole, frac)
}
}