use crate::book::{BinaryPriceSize, BookMarketState, PriceSize};
use crate::types::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SnapshotPriceSelection {
Best,
LiquidityWeighted,
DistanceWeighted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct SnapshotPriceSelections {
pub best: Option<OddsX10000>,
pub liquidity_weighted: Option<OddsX10000>,
pub distance_weighted: Option<OddsX10000>,
}
impl SnapshotPriceSelections {
pub fn from_levels(levels: &[PriceSize]) -> Self {
Self {
best: select_price(levels, SnapshotPriceSelection::Best),
liquidity_weighted: select_price(levels, SnapshotPriceSelection::LiquidityWeighted),
distance_weighted: select_price(levels, SnapshotPriceSelection::DistanceWeighted),
}
}
pub fn get(self, mode: SnapshotPriceSelection) -> Option<OddsX10000> {
match mode {
SnapshotPriceSelection::Best => self.best,
SnapshotPriceSelection::LiquidityWeighted => self.liquidity_weighted,
SnapshotPriceSelection::DistanceWeighted => self.distance_weighted,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MarketCloseSnapshot {
pub batch_max_events: u16,
pub total_live_orders: u64,
pub cancelled_total: u64,
pub chunks_done: u32,
pub cursor_after: Option<OrderId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MarketSnapshot {
pub market_id: MarketId,
pub market_seq: u64,
pub market_model: MarketModel,
pub state: BookMarketState,
pub close: Option<MarketCloseSnapshot>,
pub runners: Vec<RunnerSnapshot>,
pub binary: Option<BinaryMarketSnapshot>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BinaryMarketSnapshot {
pub max_price_ticks: u16,
pub bids: Vec<BinaryPriceSize>,
pub asks: Vec<BinaryPriceSize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RunnerSnapshot {
pub runner_id: RunnerId,
pub available_to_back: Vec<PriceSize>,
pub available_to_lay: Vec<PriceSize>,
pub matched_volume: Money,
pub selected_to_back: SnapshotPriceSelections,
pub selected_to_lay: SnapshotPriceSelections,
}
impl MarketSnapshot {
pub fn runner(&self, runner_id: RunnerId) -> Option<&RunnerSnapshot> {
self.runners.iter().find(|r| r.runner_id == runner_id)
}
pub fn runner_count(&self) -> usize {
self.runners.len()
}
}
impl RunnerSnapshot {
pub fn best_back(&self) -> Option<&PriceSize> {
self.available_to_back.first()
}
pub fn best_lay(&self) -> Option<&PriceSize> {
self.available_to_lay.first()
}
pub fn select_back_price(&self, mode: SnapshotPriceSelection) -> Option<OddsX10000> {
self.selected_to_back
.get(mode)
.or_else(|| select_price(&self.available_to_back, mode))
}
pub fn select_lay_price(&self, mode: SnapshotPriceSelection) -> Option<OddsX10000> {
self.selected_to_lay
.get(mode)
.or_else(|| select_price(&self.available_to_lay, mode))
}
pub fn spread_ticks(&self) -> Option<u32> {
let back = self.best_back()?;
let lay = self.best_lay()?;
back.price
.ticks_between(lay.price)
.map(|t| t.unsigned_abs())
}
pub fn total_back_volume(&self) -> Money {
Money(self.available_to_back.iter().map(|p| p.size.0).sum())
}
pub fn total_lay_volume(&self) -> Money {
Money(self.available_to_lay.iter().map(|p| p.size.0).sum())
}
}
fn select_price(levels: &[PriceSize], mode: SnapshotPriceSelection) -> Option<OddsX10000> {
match mode {
SnapshotPriceSelection::Best => levels.first().map(|p| p.price),
SnapshotPriceSelection::LiquidityWeighted => {
weighted_price(levels, |_, level| level.size.0.max(0) as f64)
}
SnapshotPriceSelection::DistanceWeighted => {
let best = levels.first()?.price;
weighted_price(levels, |idx, level| {
let base = level
.price
.ticks_between(best)
.map(|d| d.unsigned_abs() as f64)
.unwrap_or(idx as f64);
1.0 / (base + 1.0)
})
}
}
}
fn weighted_price<F>(levels: &[PriceSize], mut weight_for: F) -> Option<OddsX10000>
where
F: FnMut(usize, &PriceSize) -> f64,
{
let mut weighted_sum = 0.0;
let mut weight_total = 0.0;
for (idx, level) in levels.iter().enumerate() {
let weight = weight_for(idx, level);
if !weight.is_finite() || weight <= 0.0 {
continue;
}
weighted_sum += level.price.to_decimal() * weight;
weight_total += weight;
}
if weight_total <= f64::EPSILON {
return levels.first().map(|p| p.price);
}
nearest_tick(weighted_sum / weight_total)
}
fn nearest_tick(decimal: f64) -> Option<OddsX10000> {
let raw = OddsX10000::from_decimal(decimal);
let floor = raw.floor_tick();
let ceil = raw.ceil_tick();
match (floor, ceil) {
(Some(f), Some(c)) => {
let f_diff = (decimal - f.to_decimal()).abs();
let c_diff = (c.to_decimal() - decimal).abs();
if f_diff <= c_diff { Some(f) } else { Some(c) }
}
(Some(f), None) => Some(f),
(None, Some(c)) => Some(c),
(None, None) => None,
}
}