betex 0.16.0

Betfair / Prediction Market Exchange
Documentation
//! Shared deterministic formulas used across book/engine paths.
//!
//! Rounding policy:
//! - maker exposure derived from taker stake uses integer division (floor toward zero).
//! - taker capacity derived from maker stake uses ceil so inverse implied fills can fully consume
//!   the maker remainder at the chosen trade tick.
//! - derived odds are floored to valid ladder ticks.

use crate::types::{Money, OddsX10000};

#[inline]
fn clamp_i128_to_i64(v: i128) -> i64 {
    v.clamp(i64::MIN as i128, i64::MAX as i128) as i64
}

/// Profit (excluding returned stake) for BACK/YES at decimal odds.
///
/// Formula: `stake * (odds - 1)`, with fixed-point odds (`x10000`).
#[inline]
pub fn backer_profit(stake: Money, odds: OddsX10000) -> Money {
    let stake_i = stake.0 as i128;
    let odds_i = odds.0 as i128;
    let profit = (stake_i * (odds_i - 10_000)) / 10_000;
    Money(clamp_i128_to_i64(profit))
}

/// Compute the implied odds for the opposite runner in a 2-runner market.
///
/// Formula: `o' = o / (o - 1)`, then floor to the nearest valid ladder tick.
#[inline]
pub fn derived_odds(odds: OddsX10000) -> OddsX10000 {
    let o = odds.0 as u64;
    if o <= 10_000 {
        return OddsX10000(OddsX10000::MAX);
    }
    let derived = (o * 10_000) / (o - 10_000);
    let raw = OddsX10000(derived.min(u32::MAX as u64) as u32);
    raw.floor_tick().unwrap_or(OddsX10000(OddsX10000::MAX))
}

/// Convert implied taker stake into consumed maker stake at `trade_price`.
///
/// Formula: `s_m = s_t * (a - 1)`.
#[inline]
pub fn implied_maker_stake_from_taker(taker_stake: Money, trade_price: OddsX10000) -> Money {
    let denom = trade_price.0 as i128 - 10_000;
    if taker_stake.0 <= 0 || denom <= 0 {
        return Money::zero();
    }
    let v = (taker_stake.0 as i128 * denom) / 10_000;
    Money(clamp_i128_to_i64(v))
}

/// Convert maker remaining stake into max implied taker capacity at `trade_price`.
///
/// Formula: `s_t_max = ceil(s_m * 10000 / (a - 1))`.
#[inline]
pub fn implied_taker_capacity_from_maker(maker_stake: Money, trade_price: OddsX10000) -> Money {
    let denom = trade_price.0 as i128 - 10_000;
    if maker_stake.0 <= 0 || denom <= 0 {
        return Money::zero();
    }
    let numerator = maker_stake.0 as i128 * 10_000;
    let v = (numerator + denom - 1) / denom;
    Money(clamp_i128_to_i64(v))
}