nautechsystems/nautilus_trader · error
Liability-based betting is only applicable for Lay side.
Error message
Liability-based betting is only applicable for Lay side.
What it means
Bet::from_liability_checked derives a stake from a liability, which is only mathematically defined for Lay bets (liability = stake * (odds - 1)). Passing any other BetSide is rejected because liability-based construction has no meaning for Back bets.
Source
Thrown at crates/model/src/data/bet.rs:123
/// or if the stake calculation overflows.
#[must_use]
pub fn from_liability(price: Decimal, liability: Decimal, side: BetSide) -> Self {
Self::from_liability_checked(price, liability, side).unwrap_or_else(|e| panic!("{e}"))
}
/// Creates a bet from a given liability.
///
/// # Errors
///
/// Returns an error if the side is not [`BetSide::Lay`], if `price` is not greater
/// than 1, or if the stake calculation overflows.
pub fn from_liability_checked(
price: Decimal,
liability: Decimal,
side: BetSide,
) -> anyhow::Result<Self> {
if side != BetSide::Lay {
anyhow::bail!("Liability-based betting is only applicable for Lay side.");
}
check_odds_gt_one(price)?;
let stake = checked_div(liability, checked_sub(price, Decimal::ONE)?)?;
Ok(Self::new(price, stake, side))
}
/// Returns the bet's exposure.
///
/// For BACK bets, exposure is positive; for LAY bets, it is negative.
///
/// # Panics
///
/// Panics if the calculation overflows.
#[must_use]
pub fn exposure(&self) -> Decimal {
self.exposure_checked().unwrap_or_else(|e| panic!("{e}"))
}View on GitHub (pinned to 18893faf8b)
Solutions
- Use BetSide::Lay when constructing from a liability
- For Back bets, construct from stake (e.g. Bet::new or stake-based constructor) instead of liability
- Fix side selection in the calling code/UI
Example fix
// before Bet::from_liability_checked(price, liability, BetSide::Back)?; // bails // after Bet::from_liability_checked(price, liability, BetSide::Lay)?; // or stake-based Bet for Back
Defensive patterns
Strategy: validation
Validate before calling
if side != BetSide::Lay {
return Err(anyhow!("liability construction requires Lay side"));
}
let bet = Bet::from_liability_checked(price, liability, side)?; Type guard
fn is_lay(side: BetSide) -> bool { side == BetSide::Lay } Try / catch
match Bet::from_liability_checked(price, liability, side) {
Err(e) if e.to_string().contains("only applicable for Lay") => { /* use stake-based Bet for Back */ }
other => other?,
} Prevention
- Use from_liability_checked only in Lay-side order logic
- Construct Back bets from stake directly
- Type-check side at the API boundary (exchange order placement)
When it happens
Trigger: Calling Bet::from_liability_checked with side == BetSide::Back (or anything other than Lay).
Common situations: Exchange (e.g. Betfair) integrations where users specify liabilities for both sides generically; UIs that apply the Lay liability model to Back bets by mistake.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- invalid probability: must be non-zero
- invalid OrderSide: must be Buy or Sell, was {side}
- {e}
- {e}
- {FAILED}: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/53e2c7b192202256.
Report an issue: GitHub.