nautechsystems/nautilus_trader · error · anyhow::Error

Cannot calculate inverse points: open price is not positive

Error message

Cannot calculate inverse points: open price is not positive or is too small ({avg_px_open})

What it means

Position PnL calculation for inverse instruments converts prices via reciprocal (1/px), which is undefined or numerically garbage for non-positive or denormal prices. calculate_points_inverse bails when avg_px_open is <= 0 or smaller than 1e-15 (just above f64 epsilon) before it can divide by the open price.

Source

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

                log::error!("Error calculating average close price: {e}");
                last_px
            })
    }

    fn calculate_points(&self, avg_px_open: f64, avg_px_close: f64) -> f64 {
        match self.side {
            PositionSide::Long => avg_px_close - avg_px_open,
            PositionSide::Short => avg_px_open - avg_px_close,
            PositionSide::Flat => 0.0,
        }
    }

    fn calculate_points_inverse(&self, avg_px_open: f64, avg_px_close: f64) -> anyhow::Result<f64> {
        // Epsilon at the limit of IEEE f64 precision before rounding errors (f64::EPSILON ≈ 2.22e-16)
        const EPSILON: f64 = 1e-15;

        if avg_px_open <= 0.0 || avg_px_open.abs() < EPSILON {
            anyhow::bail!(
                "Cannot calculate inverse points: open price is not positive or is too small ({avg_px_open})"
            );
        }

        if avg_px_close <= 0.0 || avg_px_close.abs() < EPSILON {
            anyhow::bail!(
                "Cannot calculate inverse points: close price is not positive or is too small ({avg_px_close})"
            );
        }

        let inverse_open = 1.0 / avg_px_open;
        let inverse_close = 1.0 / avg_px_close;
        let result = match self.side {
            PositionSide::Long => inverse_open - inverse_close,
            PositionSide::Short => inverse_close - inverse_open,
            PositionSide::Flat => 0.0,
        };
        Ok(result)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure fills and position open prices are strictly positive decimals before PnL calculation; validate at ingestion.
  2. Check the market data source for zero/placeholder prices and filter them out or halt on them.
  3. Guard the call site: only compute PnL when position.avg_px_open > 0.0.
  4. If prices legitimately approach zero, reject or rescale them upstream; 1e-15 is the library's hard floor.

Example fix

// before
let pnl = position.calculate_pnl_raw(instrument, avg_px_close).unwrap();
// after
if position.avg_px_open <= 0.0 {
    log::warn!("skipping PnL: open price {} invalid", position.avg_px_open);
    return;
}
let pnl = position.calculate_pnl_raw(instrument, avg_px_close).unwrap();
Defensive patterns

Strategy: validation

Validate before calling

// Rust: guard before calling calculate_pnl_raw
if position.avg_px_open <= 0.0 || position.avg_px_open.abs() < 1e-15 {
    log::warn!("skip inverse PnL: invalid open price {}", position.avg_px_open);
    return Ok(0.0);
}
let pnl = position.calculate_pnl_raw(&instrument, avg_px_close)?;

Type guard

fn valid_inverse_px(px: f64) -> bool { px > 0.0 && px.abs() >= 1e-15 }

Prevention

When it happens

Trigger: Calling position.calculate_pnl_raw (via calculate_points_inverse) with an inverse-instrument position whose stored avg_px_open is zero, negative, or below 1e-15 — typically from an uninitialized position, a fill recorded with px=0.0, or a bad data feed.

Common situations: Inverse contracts (e.g. BTCUSD coin-margined perps) fed zero prices from a stale/misconfigured market data source; positions built programmatically without setting an open price; data pipelines that null-coalesce missing prices to 0.0.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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