{"record":{"id":"b2a4130612e20336","repo":"nautechsystems/nautilus_trader","slug":"invalid-probability-must-be-non-zero","errorCode":null,"errorMessage":"invalid probability: must be non-zero","messagePattern":"invalid probability: must be non-zero","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/data/bet.rs","lineNumber":694,"sourceCode":"/// Calculates the combined profit and loss for a slice of bets.\n///\n/// # Errors\n///\n/// Returns an error if a payoff or the running total overflows.\npub fn calc_bets_pnl_checked(bets: &[Bet]) -> anyhow::Result<Decimal> {\n    bets.iter().try_fold(Decimal::ZERO, |acc, bet| {\n        checked_add(acc, bet.outcome_win_payoff_checked()?)\n    })\n}\n\n/// Checks that `probability` is non-zero.\n///\n/// # Errors\n///\n/// Returns an error if `probability` is zero.\npub fn check_probability_non_zero(probability: Decimal) -> anyhow::Result<()> {\n    if probability.is_zero() {\n        anyhow::bail!(\"invalid probability: must be non-zero\")\n    }\n    Ok(())\n}\n\n/// Checks that `probability` is invertible (not equal to 1.0).\n///\n/// # Errors\n///\n/// Returns an error if `probability` is 1.0.\npub fn check_probability_invertible(probability: Decimal) -> anyhow::Result<()> {\n    if probability == Decimal::ONE {\n        anyhow::bail!(\"invalid probability: must not be 1.0 (inverse would be zero)\")\n    }\n    Ok(())\n}\n\n/// Converts a probability and volume into a Bet.\n///","sourceCodeStart":676,"sourceCodeEnd":712,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/data/bet.rs#L676-L712","documentation":"check_probability_non_zero rejects a probability of exactly zero because converting probability to odds requires dividing by it (odds = 1/probability), which is undefined for zero. The library exposes it as a public checked helper.","triggerScenarios":"Calling check_probability_non_zero (directly or via probability_to_bet) with probability == Decimal::ZERO.","commonSituations":"Probabilities parsed from empty/placeholder data defaulting to 0; normalization bugs where all weights are zero leading to a zero probability; users mistaking zero for 'impossible event, skip' when they should skip the conversion themselves.","solutions":["Validate probability > 0 (or > 1) before calling probability_to_bet","Skip conversion for zero-probability outcomes at the caller level","Fix upstream normalization so probabilities are never zero"],"exampleFix":"// before\nlet bet = probability_to_bet(Decimal::ZERO, side)?; // bails\n// after\nlet p = Decimal::new(25, 2); // 0.25\nanyhow::ensure!(!p.is_zero(), \"skip zero-probability outcome\");\nlet bet = probability_to_bet(p, side)?;","handlingStrategy":"validation","validationCode":"if probability.is_zero() { return Ok(None); } // or skip the outcome\nlet bet = probability_to_bet(probability, side)?;","typeGuard":"fn is_usable_probability(p: Decimal) -> bool { !p.is_zero() }","tryCatchPattern":"match probability_to_bet(probability, side) {\n    Err(e) if e.to_string().contains(\"must be non-zero\") => { /* skip zero-probability outcome */ }\n    other => other?,\n}","preventionTips":["Filter zero-probability outcomes before conversion","Check normalization code so probabilities sum to 1 with no zeros from empty weights","Call check_probability_non_zero early in input parsing"],"tags":["rust","betting","probability","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}