nautechsystems/nautilus_trader · error · anyhow::Error

Cannot calculate inverse points: close price is not positive

Error message

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

What it means

The inverse-points PnL formula requires both open and close prices to be strictly positive (and at least 1e-15) because it takes reciprocals of each. calculate_points_inverse bails when avg_px_close is zero, negative, or below the epsilon floor.

Source

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

        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)
    }

    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!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the close/fill price passed to calculate_pnl_raw is a valid positive market price.
  2. Use Option/None semantics for 'not closed' instead of 0.0 sentinel values.
  3. Add ingestion-time validation rejecting non-positive prices in market data.
  4. Confirm you are not passing quantity or PnL where a price is expected.

Example fix

// before
let pnl = position.calculate_pnl_raw(&instrument, 0.0).unwrap();
// after
let pnl = position.calculate_pnl_raw(&instrument, last_close_price).unwrap(); // last_close_price > 0.0 verified
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify the close price before PnL
assert!(avg_px_close > 0.0 && avg_px_close.abs() >= 1e-15, "invalid close price {avg_px_close}");
let pnl = position.calculate_pnl_raw(&instrument, avg_px_close)?;

Type guard

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

Try / catch

match position.calculate_pnl_raw(&instrument, avg_px_close) {
    Ok(pnl) => pnl,
    Err(e) => { log::error!("pnl failed: {e}"); 0.0 },
}

Prevention

When it happens

Trigger: calculate_pnl_raw invoked with avg_px_close <= 0.0 or |avg_px_close| < 1e-15 on an inverse instrument — e.g. closing a position using a fill price of 0.0 or an unset close price.

Common situations: Downstream code passing 0.0 as the close price when the position is not yet closed; backtests with malformed tick data; adapters emitting 0.0 instead of Option::None for missing close prices.

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/c453eaea68aabe94. Report an issue: GitHub.