nautechsystems/nautilus_trader · error
Tick size {tick_size} must be positive
Error message
Tick size {tick_size} must be positive What it means
tick_relative_price_bounds computes the tradeable price range [tick_size, 1 - tick_size] for a Polymarket outcome and requires a strictly positive tick size, since a non-positive tick would produce an invalid/degenerate price band.
Source
Thrown at crates/adapters/polymarket/src/http/parse.rs:317
.maybe_min_notional(bo.min_notional)
.max_price(max_price)
.min_price(min_price)
.margin_init(bo.margin_init)
.margin_maint(bo.margin_maint)
.maker_fee(bo.maker_fee)
.taker_fee(bo.taker_fee)
.maybe_info(bo.info.clone())
.ts_event(ts_event)
.ts_init(ts_init)
.build()?;
Ok(InstrumentAny::BinaryOption(rebuilt))
}
// Returns the tradeable price bounds `[tick_size, 1 - tick_size]` for a Polymarket outcome,
// mirroring the venue range enforced in `PolymarketOrderBuilder::validate_limit_price`.
pub(crate) fn tick_relative_price_bounds(tick_size: Decimal) -> anyhow::Result<(Price, Price)> {
anyhow::ensure!(
tick_size > Decimal::ZERO,
"Tick size {tick_size} must be positive"
);
let min_price = Price::from_decimal_dp(tick_size, POLYMARKET_PRICE_PRECISION)?;
let max_price = Price::from_decimal_dp(Decimal::ONE - tick_size, POLYMARKET_PRICE_PRECISION)?;
anyhow::ensure!(
min_price.as_decimal() == tick_size,
"Tick size {tick_size} is not exactly representable at Polymarket price precision {POLYMARKET_PRICE_PRECISION}"
);
Ok((min_price, max_price))
}
fn build_info_json(def: &PolymarketInstrumentDef) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"token_id".to_string(),View on GitHub (pinned to 18893faf8b)
Solutions
- Check the tick_size value in the market payload before rebuilding the instrument.
- Guard with an explicit tick_size > Decimal::ZERO check and skip the update if not positive.
- Re-fetch authoritative instrument definitions from the Gamma/CLOB REST API when the streamed tick size looks invalid.
Example fix
// before
rebuild_instrument_with_tick_size(&instrument, &msg.new_tick_size)?;
// after
if Decimal::from_str(&msg.new_tick_size)? <= Decimal::ZERO {
tracing::warn!("ignoring non-positive tick size {}", msg.new_tick_size);
return Ok(());
}
rebuild_instrument_with_tick_size(&instrument, &msg.new_tick_size)?; Defensive patterns
Strategy: validation
Validate before calling
let tick = parse_decimal_exact(&new_tick_size)?;
if tick <= Decimal::ZERO { bail!("tick size must be positive, got {tick}"); } Type guard
fn is_positive_tick(d: Decimal) -> bool { d > Decimal::ZERO } Try / catch
match tick_relative_price_bounds(tick_size) {
Ok((min, max)) => (min, max),
Err(e) => { warn!("invalid tick size {tick_size}: {e}"); return Ok(()); }
} Prevention
- Ignore market messages carrying zero or negative tick sizes (often pre-open placeholders).
- Assert tick_size > 0 in instrument definition fixtures.
- Keep a whitelist of valid Polymarket tick sizes.
When it happens
Trigger: Calling tick_relative_price_bounds (via create_instrument_from_def or rebuild_instrument_with_tick_size) with tick_size <= 0, e.g. Decimal::ZERO or a negative value parsed from market data.
Common situations: Venue sending tick_size "0" or "0.0" in a market message before the real value arrives, or a data bug producing negative tick sizes.
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
- Polymarket market BUY amount {} pUSD truncates to zero at {L
- invalid signed E18 integer for {field}: {value}
- AX minimum_order_size must be a positive whole number, was {
- AX initial_margin_pct must be positive, was {initial_margin_
- AX maintenance_margin_pct must be positive, was {maintenance
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/86d89b1c58305cb6.
Report an issue: GitHub.