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
- Verify the close/fill price passed to calculate_pnl_raw is a valid positive market price.
- Use Option/None semantics for 'not closed' instead of 0.0 sentinel values.
- Add ingestion-time validation rejecting non-positive prices in market data.
- 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
- Only compute PnL for positions with a real closing fill.
- Filter malformed ticks/quotes before they become fill prices.
- Keep close-price state as Option until filled.
- Test adapters against venues that emit 0.0 placeholders.
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
- Cannot calculate inverse points: open price is not positive
- inverse position {} has no base currency
- 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/c453eaea68aabe94.
Report an issue: GitHub.