betex 0.35.0

Betfair / Prediction Market Exchange
Documentation
use super::{formulas, types::BookOrder};
use crate::{
    book::protocol::{
        command::{ReduceOrderCondition, ReduceOrderTarget},
        reject::RejectReason,
    },
    types::{Money, OddsX10000},
};

pub(crate) fn validate_reduce_order_condition(
    old: &BookOrder,
    condition: Option<&ReduceOrderCondition>,
) -> Result<(), RejectReason> {
    let Some(condition) = condition else {
        return Ok(());
    };
    if condition
        .expected_odds
        .is_some_and(|odds| odds != old.price)
        || condition
            .expected_stake
            .is_some_and(|stake| stake != old.stake)
        || condition
            .expected_matched_stake
            .is_some_and(|matched| matched != old.matched)
        || condition
            .expected_remaining_stake
            .is_some_and(|remaining| remaining != old.remaining())
    {
        return Err(RejectReason::OrderStateChanged);
    }
    Ok(())
}

pub(crate) fn reduce_order_target_remaining(
    old: &BookOrder,
    target: Option<ReduceOrderTarget>,
) -> Result<Money, RejectReason> {
    match target {
        None => Ok(old.remaining()),
        Some(ReduceOrderTarget::TotalStake(total_stake)) => {
            if total_stake.0 < 0 {
                return Err(RejectReason::InvalidStake);
            }
            if total_stake > old.stake {
                return Err(RejectReason::ExposureIncreaseNotAllowed);
            }
            Ok(total_stake.saturating_sub(old.matched).clamp_non_negative())
        }
        Some(ReduceOrderTarget::RemainingStake(remaining_stake)) => {
            if remaining_stake.0 < 0 {
                return Err(RejectReason::InvalidStake);
            }
            Ok(remaining_stake)
        }
    }
}

pub(crate) fn ensure_reduce_only(
    old: &BookOrder,
    new_price: OddsX10000,
    next_stake: Money,
) -> Result<(), RejectReason> {
    let old_remaining = old.remaining();
    if next_stake > old_remaining {
        return Err(RejectReason::ExposureIncreaseNotAllowed);
    }
    let current_reserve = formulas::exchange_order_reserve(old.info.side, old.price, old_remaining);
    let next_reserve = formulas::exchange_order_reserve(old.info.side, new_price, next_stake);
    if next_reserve > current_reserve {
        return Err(RejectReason::ExposureIncreaseNotAllowed);
    }
    Ok(())
}