nautechsystems/nautilus_trader · error

cannot decrease an empty bet position

Error message

cannot decrease an empty bet position

What it means

`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.

Source

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

            let total_stake = checked_add(checked_div(abs_self_exposure, self.price)?, bet.stake)?;
            checked_div(
                checked_add(abs_self_exposure, abs_bet_exposure)?,
                total_stake,
            )?
        } else {
            self.price
        };
        Ok((
            price,
            checked_add(self.exposure, bet_exposure)?,
            self.realized_pnl,
        ))
    }

    fn decreased_state(&self, bet: &Bet) -> anyhow::Result<(Decimal, Decimal, Decimal)> {
        let current_side = self
            .side()
            .ok_or_else(|| anyhow::anyhow!("cannot decrease an empty bet position"))?;
        let bet_exposure = bet.exposure_checked()?;
        let abs_bet_exposure = bet_exposure.abs();
        let abs_self_exposure = self.exposure.abs();

        match abs_bet_exposure.cmp(&abs_self_exposure) {
            std::cmp::Ordering::Less => {
                check_nonzero_denominator(self.price, "price")?;
                let decreasing_volume = checked_div(abs_bet_exposure, self.price)?;
                let decreasing_bet = Bet::new(self.price, decreasing_volume, current_side);
                let pnl = calc_bets_pnl_checked(&[bet.clone(), decreasing_bet])?;
                Ok((
                    self.price,
                    checked_add(self.exposure, bet_exposure)?,
                    checked_add(self.realized_pnl, pnl)?,
                ))
            }
            std::cmp::Ordering::Greater => Ok((
                bet.price,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Before applying a decrease, check `position.side().is_some()` and skip or no-op when the position is empty.
  2. Reconcile your event stream: ensure the decrease event actually corresponds to an open position (correct bet_id/order).
  3. If events may arrive out of order, buffer decreases until the matching increase is applied.
  4. Wrap the call in error handling and treat 'cannot decrease an empty bet position' as an idempotency signal rather than a crash.

Example fix

// before
position.add_bet_checked(&bet)?;

// after
if position.side().is_some() {
    position.add_bet_checked(&bet)?;
} else {
    // position already flat; ignore stale decrease
}
Defensive patterns

Strategy: try-catch

Validate before calling

if position.side().is_none() {
    // skip decrease on flat position
    return Ok(());
}

Type guard

fn is_open(position: &BetPosition) -> bool { position.side().is_some() }

Try / catch

match position.add_bet_checked(&bet) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("empty bet position") => { /* idempotent no-op */ },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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