nautechsystems/nautilus_trader · error

Price must be greater than 1.0 for lay liability calculation

Error message

Price must be greater than 1.0 for lay liability calculation, was {price}

What it means

check_odds_gt_one rejects odds of 1.0 or below when constructing a bet from a lay liability. Lay liability is computed via 1/odds arithmetic, which is only meaningful (and non-negative/finite) for odds strictly greater than 1.0. The guard bails with the offending price embedded in the message.

Source

Thrown at crates/model/src/data/bet.rs:752

///
/// The side is also inverted (BUY becomes SELL and vice versa).
///
/// # Errors
///
/// Returns an error if `probability` is 1.0 or its inverse is zero.
pub fn inverse_probability_to_bet(
    probability: Decimal,
    volume: Decimal,
    side: OrderSide,
) -> anyhow::Result<Bet> {
    check_probability_invertible(probability)?;
    let inverse_probability = checked_sub(Decimal::ONE, probability)?;
    probability_to_bet(inverse_probability, volume, side.opposite())
}

fn check_odds_gt_one(price: Decimal) -> anyhow::Result<()> {
    if price <= Decimal::ONE {
        anyhow::bail!("Price must be greater than 1.0 for lay liability calculation, was {price}");
    }
    Ok(())
}

fn check_nonzero_denominator(value: Decimal, name: &str) -> anyhow::Result<()> {
    if value.is_zero() {
        anyhow::bail!("invalid {name}: must be non-zero")
    }
    Ok(())
}

fn checked_add(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_add(rhs)
        .ok_or_else(|| anyhow::anyhow!("Decimal overflow adding {lhs} and {rhs}"))
}

fn checked_sub(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {
    lhs.checked_sub(rhs)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Pass decimal odds strictly greater than 1.0; validate the value before constructing the bet.
  2. Check whether a probability is being passed where decimal odds are required and convert (odds = 1/probability) first.
  3. Reject or skip records with odds <= 1.0 at ingestion instead of passing them into from_liability_checked.

Example fix

// before
let bet = Bet::from_liability_checked(Decimal::ZERO, liability)?; // odds <= 1.0
// after
anyhow::ensure!(odds > Decimal::ONE, "lay odds must be > 1.0, got {odds}");
let bet = Bet::from_liability_checked(odds, liability)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
use rust_decimal::Decimal;
fn valid_lay_odds(p: Decimal) -> bool { p > Decimal::ONE }
// Python
# assert price > 1.0, f"lay odds must be > 1.0, got {price}"

Try / catch

// Rust
match Bet::from_liability_checked(price, liability) {
    Ok(b) => b,
    Err(e) if e.to_string().starts_with("Price must be greater than 1.0") => skip_record(price),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling Bet::from_liability_checked (or its internal check_odds_gt_one) with price <= Decimal::ONE, e.g. odds of exactly 1.0, 0.0, or a negative value.

Common situations: Feeds delivering pre-race odds of 1.0 as a placeholder; unit errors where probability (0-1) is passed where decimal odds (>=1) are expected; uninitialized/zero price fields.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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