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
- Set base_currency on the position (or its instrument definition) for every inverse instrument before fill handling.
- Use the instrument's defined base asset to populate base_currency when constructing the Position.
- 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
- Always populate base_currency from the instrument definition for inverse instruments.
- Validate instrument completeness (base, quote, settlement currencies) before creating positions.
- Check snapshot schemas when upgrading so base_currency is not dropped.
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
- Cannot calculate inverse points: open price is not positive
- Cannot calculate inverse points: close price is not positive
- position fill void exceeds known fragments for {}
- stale position fill void for {}
- duplicate position fill void for {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/26fadf91894d4b26.
Report an issue: GitHub.