nautechsystems/nautilus_trader · error
{e}
Error message
{e} What it means
`Bet::from_stake_or_liability` builds a Back bet directly from a stake, but for a Lay bet it derives the stake from a liability via `from_liability_checked`. The panicking wrapper surfaces that checked failure when `price` (decimal odds) is not greater than 1.0 ('Price must be greater than 1.0 for lay liability calculation, was {price}') or when `liability / (price - 1)` overflows. The Back branch can never panic.
Source
Thrown at crates/model/src/data/bet.rs:75
}
/// Returns the bet's side.
#[must_use]
pub fn side(&self) -> BetSide {
self.side
}
/// Creates a bet from a stake or liability depending on the bet side.
///
/// For `BetSide::Back` this calls [`Self::from_stake`] and for
/// `BetSide::Lay` it calls [`Self::from_liability`].
///
/// # Panics
///
/// Panics if `side` is [`BetSide::Lay`] and [`Self::from_liability`] panics.
#[must_use]
pub fn from_stake_or_liability(price: Decimal, volume: Decimal, side: BetSide) -> Self {
Self::from_stake_or_liability_checked(price, volume, side).unwrap_or_else(|e| panic!("{e}"))
}
/// Creates a bet from a stake or liability depending on the bet side.
///
/// # Errors
///
/// Returns an error if `side` is [`BetSide::Lay`] and [`Self::from_liability_checked`] fails.
pub fn from_stake_or_liability_checked(
price: Decimal,
volume: Decimal,
side: BetSide,
) -> anyhow::Result<Self> {
match side {
BetSide::Back => Ok(Self::from_stake(price, volume, side)),
BetSide::Lay => Self::from_liability_checked(price, volume, side),
}
}
View on GitHub (pinned to 2114cf6f76)
Solutions
- Pass decimal odds strictly greater than 1.0 as price on the Lay path, converting probabilities with 1/p first.
- Switch to `from_stake_or_liability_checked` and handle the Result instead of the panicking wrapper.
- Reject or quarantine market data with price <= 1.0 before constructing bets.
- If the message mentions Decimal overflow, check for corrupted or mis-scaled volume fields.
Example fix
// before: probability used directly as odds on the lay path
let bet = Bet::from_stake_or_liability(prob, volume, BetSide::Lay); // panics: price must be > 1.0
// after: convert probability to decimal odds, use the checked API
let odds = Decimal::ONE.checked_div(prob).context('zero probability')?;
let bet = Bet::from_stake_or_liability_checked(odds, volume, BetSide::Lay)?; Defensive patterns
Strategy: validation
Validate before calling
fn can_build_from_volume(price: Decimal, side: BetSide) -> bool {
match side {
BetSide::Back => true,
BetSide::Lay => price > Decimal::ONE,
}
} Try / catch
let bet = Bet::from_stake_or_liability_checked(odds, volume, side)
.with_context(|| format('cannot build {side} bet at {odds} from volume {volume}'))?; Prevention
- Convert probabilities to decimal odds (1/p) at ingest.
- Reject lay-side prices <= 1.0 before bet construction.
- Unit-test boundary prices exactly 1.0 and 0.99.
When it happens
Trigger: Calling `from_stake_or_liability(price, volume, BetSide::Lay)` with price <= 1.0 — price exactly 1.0 (the divisor price-1 would be zero) or a probability like 0.35 passed where decimal odds are expected — or with liability magnitudes that overflow Decimal in the division.
Common situations: Betting/prediction-market adapters converting exchange messages: probability (0..1) fed instead of decimal odds, feed prices of exactly 1.0 for near-certain outcomes, placeholder values in unit tests, or mis-scaled volumes (raw token base units).
Related errors
- height must be positive, was {self.height}
- instrument_id is required for historical orders
- invalid Binance Futures order-book depth; valid values are {
- {e}
- `slippage_bps` {slippage_bps} exceeds `max_slippage_bps` {ma
AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21).
Data as JSON: /api/errors/5cf8bbfa96fbb54a.
Report an issue: GitHub.