use crate::markets::outcomes::book::{Book, Level};
use crate::markets::outcomes::px::{Mid, Px};
use crate::utils::Dec;
pub const WIDE_SPREAD: Px = cents(2);
pub const DEPTH_WINDOW: Px = cents(2);
pub const MIN_DEPTH: Dec = Dec::from_int(100);
const BOUNDARY: Px = cents(1);
const fn cents(count: u32) -> Px {
match Px::from_ticks(count * 1_000) {
Some(price) => price,
None => Px::ZERO,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Quality {
Settled,
Dead {
mid: Option<Mid>,
},
Thin {
bid: Px,
ask: Px,
spread: Px,
depth: Dec,
},
Firm {
mid: Mid,
spread: Px,
depth: Dec,
},
}
impl Quality {
#[must_use]
pub const fn probability(&self) -> Option<Mid> {
match self {
Quality::Firm { mid, .. } => Some(*mid),
_ => None,
}
}
#[must_use]
pub const fn depth(&self) -> Option<Dec> {
match self {
Quality::Firm { depth, .. } | Quality::Thin { depth, .. } => Some(*depth),
_ => None,
}
}
}
#[must_use]
pub fn depth_within(book: &Book, window: Px) -> Dec {
let near = |levels: &[Level]| {
let Some(touch) = levels.first().map(|level| level.px) else {
return Dec::ZERO;
};
levels
.iter()
.filter(|level| level.px.abs_diff(touch) <= window.ticks())
.map(|level| level.sz.notional(level.px))
.fold(Dec::ZERO, |sum, worth| sum.checked_add(worth).unwrap_or(sum))
};
let (bids, asks) = (near(&book.bids), near(&book.asks));
bids.checked_add(asks).unwrap_or(bids)
}
#[must_use]
pub fn assess(book: &Book, settled: bool) -> Quality {
if settled {
return Quality::Settled;
}
let (bid, ask) = (book.bids.first().map(|level| level.px), book.asks.first().map(|level| level.px));
let mid = bid.zip(ask).map(|(bid, ask)| midpoint(bid, ask));
let (Some(bid), Some(ask)) = (bid, ask) else {
return Quality::Dead { mid };
};
if bid <= BOUNDARY || ask >= BOUNDARY.complement() {
return Quality::Dead { mid };
}
let spread = Px::from_ticks(ask.ticks().saturating_sub(bid.ticks())).unwrap_or(Px::ZERO);
let depth = depth_within(book, DEPTH_WINDOW);
match spread > WIDE_SPREAD || depth < MIN_DEPTH {
true => Quality::Thin { bid, ask, spread, depth },
false => Quality::Firm { mid: midpoint(bid, ask), spread, depth },
}
}
const fn midpoint(bid: Px, ask: Px) -> Mid {
match Mid::from_units((Mid::of(bid).units() + Mid::of(ask).units()) / 2) {
Some(mid) => mid,
None => Mid::ONE,
}
}
#[cfg(test)]
#[path = "tests/quote.rs"]
mod tests;