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
- Ensure fills and position open prices are strictly positive decimals before PnL calculation; validate at ingestion.
- Check the market data source for zero/placeholder prices and filter them out or halt on them.
- Guard the call site: only compute PnL when position.avg_px_open > 0.0.
- 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
- Validate fill and position prices are strictly positive at ingestion.
- Never use 0.0 as a sentinel for missing prices; use Option.
- Add unit tests for inverse PnL with boundary prices.
- Monitor data feeds for zero-price events.
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
- Cannot calculate inverse points: close 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/acb978e7034e6032.
Report an issue: GitHub.