{"record":{"id":"833d9525ec97be76","repo":"nautechsystems/nautilus_trader","slug":"cannot-decrease-an-empty-bet-position","errorCode":null,"errorMessage":"cannot decrease an empty bet position","messagePattern":"cannot decrease an empty bet position","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/data/bet.rs","lineNumber":468,"sourceCode":"            let total_stake = checked_add(checked_div(abs_self_exposure, self.price)?, bet.stake)?;\n            checked_div(\n                checked_add(abs_self_exposure, abs_bet_exposure)?,\n                total_stake,\n            )?\n        } else {\n            self.price\n        };\n        Ok((\n            price,\n            checked_add(self.exposure, bet_exposure)?,\n            self.realized_pnl,\n        ))\n    }\n\n    fn decreased_state(&self, bet: &Bet) -> anyhow::Result<(Decimal, Decimal, Decimal)> {\n        let current_side = self\n            .side()\n            .ok_or_else(|| anyhow::anyhow!(\"cannot decrease an empty bet position\"))?;\n        let bet_exposure = bet.exposure_checked()?;\n        let abs_bet_exposure = bet_exposure.abs();\n        let abs_self_exposure = self.exposure.abs();\n\n        match abs_bet_exposure.cmp(&abs_self_exposure) {\n            std::cmp::Ordering::Less => {\n                check_nonzero_denominator(self.price, \"price\")?;\n                let decreasing_volume = checked_div(abs_bet_exposure, self.price)?;\n                let decreasing_bet = Bet::new(self.price, decreasing_volume, current_side);\n                let pnl = calc_bets_pnl_checked(&[bet.clone(), decreasing_bet])?;\n                Ok((\n                    self.price,\n                    checked_add(self.exposure, bet_exposure)?,\n                    checked_add(self.realized_pnl, pnl)?,\n                ))\n            }\n            std::cmp::Ordering::Greater => Ok((\n                bet.price,","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/data/bet.rs#L450-L486","documentation":"`decreased_state` computes the resulting state when a bet position is reduced, but it can only do so when the current position has a side (long/short). If the position is empty (no side, exposure effectively flat), there is nothing to decrease, so the library raises this error instead of returning a meaningless state.","triggerScenarios":"Calling `add_bet_checked` (or otherwise driving `decreased_state`) with a decrease/offsetting bet while the current `BetPosition` has `side() == None` — i.e. the position is empty or already flat.","commonSituations":"Receiving a bet cancellation or hedge fill that arrives after the position was already closed; replaying events out of order so a decrease is applied to a flat position; double-processing a close in a betting-market integration.","solutions":["Before applying a decrease, check `position.side().is_some()` and skip or no-op when the position is empty.","Reconcile your event stream: ensure the decrease event actually corresponds to an open position (correct bet_id/order).","If events may arrive out of order, buffer decreases until the matching increase is applied.","Wrap the call in error handling and treat 'cannot decrease an empty bet position' as an idempotency signal rather than a crash."],"exampleFix":"// before\nposition.add_bet_checked(&bet)?;\n\n// after\nif position.side().is_some() {\n    position.add_bet_checked(&bet)?;\n} else {\n    // position already flat; ignore stale decrease\n}","handlingStrategy":"try-catch","validationCode":"if position.side().is_none() {\n    // skip decrease on flat position\n    return Ok(());\n}","typeGuard":"fn is_open(position: &BetPosition) -> bool { position.side().is_some() }","tryCatchPattern":"match position.add_bet_checked(&bet) {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"empty bet position\") => { /* idempotent no-op */ },\n    Err(e) => return Err(e),\n}","preventionTips":["Check position state before applying decrease/offset events.","Make event processing idempotent so duplicate closes are harmless.","Track bet lifecycle explicitly so stale cancellations are filtered."],"tags":["rust","betting","state"],"backgroundTag":"invalid-state-transition","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"}