nautechsystems/nautilus_trader · error

inverse position {} has no base currency

Error message

inverse position {} has no base currency

What it means

calculate_pnl_raw requires an inverse position to have a base_currency set, because inverse PnL is computed in the base currency via point valuation. A position constructed as inverse (is_inverse == true) with base_currency == None is internally inconsistent, so PnL calculation fails rather than returning a wrong value.

Source

Thrown at crates/model/src/position.rs:1175

    fn calculate_return(&self, avg_px_open: f64, avg_px_close: f64) -> anyhow::Result<f64> {
        // Prevent division by zero in return calculation
        if avg_px_open == 0.0 {
            anyhow::bail!(
                "Cannot calculate return: open price is zero (close price: {avg_px_close})"
            );
        }
        Ok(self.calculate_points(avg_px_open, avg_px_close) / avg_px_open)
    }

    fn calculate_pnl_raw(
        &self,
        avg_px_open: f64,
        avg_px_close: f64,
        quantity: f64,
    ) -> anyhow::Result<f64> {
        let quantity = quantity.min(self.signed_qty.abs());
        let result = if self.is_inverse {
            anyhow::ensure!(
                self.base_currency.is_some(),
                "inverse position {} has no base currency",
                self.instrument_id
            );
            let points = self.calculate_points_inverse(avg_px_open, avg_px_close)?;
            quantity * self.multiplier.as_f64() * points
        } else {
            quantity * self.multiplier.as_f64() * self.calculate_points(avg_px_open, avg_px_close)
        };
        Ok(result)
    }

    /// Calculates profit and loss from the given prices and quantity.
    ///
    /// # Errors
    ///
    /// Returns an error if inverse P&L cannot be calculated or the result cannot be represented as
    /// [`Money`].

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set base_currency on the position (or its instrument definition) for every inverse instrument before fill handling.
  2. Use the instrument's defined base asset to populate base_currency when constructing the Position.
  3. If the instrument is genuinely not inverse, construct the position with is_inverse = false so PnL uses the standard formula.

Example fix

// before
let position = Position::new(instrument_id, /* ... */ is_inverse: true, base_currency: None);
// after
let position = Position::new(
    instrument_id,
    /* ... */
    is_inverse: true,
    base_currency: Some(instrument.base_currency().expect("inverse instrument needs base")),
);
Defensive patterns

Strategy: validation

Validate before calling

if position.is_inverse && position.base_currency.is_none() {
    return Err(anyhow!("inverse position {} requires base currency", position.instrument_id));
}
let pnl = position.try_calculate_pnl(avg_px_open, avg_px_close, qty)?;

Type guard

fn pnl_ready(p: &Position) -> bool {
    !p.is_inverse || p.base_currency.is_some()
}

Try / catch

match position.try_calculate_pnl(open, close, qty) {
    Ok(pnl) => /* use pnl */,
    Err(e) if e.to_string().contains("no base currency") =>
        log::error!("configure base_currency for inverse position {}", position.instrument_id),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Creating a Position for an inverse instrument (e.g. crypto-margined futures like BTCUSD inverses) without supplying base_currency, then calling try_calculate_pnl / unrealized or realized PnL paths (handle_buy_order_fill, handle_sell_order_fill).

Common situations: Building positions programmatically where the instrument's base currency was not resolved (e.g. synthetic or custom instruments); loading a position from a snapshot serialized before base_currency existed or with it omitted; configuring inverse instruments with only quote/settlement currency defined.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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