{"record":{"id":"53e2c7b192202256","repo":"nautechsystems/nautilus_trader","slug":"liability-based-betting-is-only-applicable-for-lay","errorCode":null,"errorMessage":"Liability-based betting is only applicable for Lay side.","messagePattern":"Liability-based betting is only applicable for Lay side\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/data/bet.rs","lineNumber":123,"sourceCode":"    /// or if the stake calculation overflows.\n    #[must_use]\n    pub fn from_liability(price: Decimal, liability: Decimal, side: BetSide) -> Self {\n        Self::from_liability_checked(price, liability, side).unwrap_or_else(|e| panic!(\"{e}\"))\n    }\n\n    /// Creates a bet from a given liability.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the side is not [`BetSide::Lay`], if `price` is not greater\n    /// than 1, or if the stake calculation overflows.\n    pub fn from_liability_checked(\n        price: Decimal,\n        liability: Decimal,\n        side: BetSide,\n    ) -> anyhow::Result<Self> {\n        if side != BetSide::Lay {\n            anyhow::bail!(\"Liability-based betting is only applicable for Lay side.\");\n        }\n\n        check_odds_gt_one(price)?;\n        let stake = checked_div(liability, checked_sub(price, Decimal::ONE)?)?;\n        Ok(Self::new(price, stake, side))\n    }\n\n    /// Returns the bet's exposure.\n    ///\n    /// For BACK bets, exposure is positive; for LAY bets, it is negative.\n    ///\n    /// # Panics\n    ///\n    /// Panics if the calculation overflows.\n    #[must_use]\n    pub fn exposure(&self) -> Decimal {\n        self.exposure_checked().unwrap_or_else(|e| panic!(\"{e}\"))\n    }","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/data/bet.rs#L105-L141","documentation":"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.","triggerScenarios":"Calling Bet::from_liability_checked with side == BetSide::Back (or anything other than Lay).","commonSituations":"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.","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"],"exampleFix":"// before\nBet::from_liability_checked(price, liability, BetSide::Back)?; // bails\n// after\nBet::from_liability_checked(price, liability, BetSide::Lay)?; // or stake-based Bet for Back","handlingStrategy":"validation","validationCode":"if side != BetSide::Lay {\n    return Err(anyhow!(\"liability construction requires Lay side\"));\n}\nlet bet = Bet::from_liability_checked(price, liability, side)?;","typeGuard":"fn is_lay(side: BetSide) -> bool { side == BetSide::Lay }","tryCatchPattern":"match Bet::from_liability_checked(price, liability, side) {\n    Err(e) if e.to_string().contains(\"only applicable for Lay\") => { /* use stake-based Bet for Back */ }\n    other => other?,\n}","preventionTips":["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)"],"tags":["rust","betting","bets","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}