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

  1. Use BetSide::Lay when constructing from a liability
  2. For Back bets, construct from stake (e.g. Bet::new or stake-based constructor) instead of liability
  3. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/53e2c7b192202256. Report an issue: GitHub.