nautechsystems/nautilus_trader · error

PolymarketFeeModel requires a fill price in [0, 1]

Error message

PolymarketFeeModel requires a fill price in [0, 1]

What it means

Polymarket fees are charged as fee_rate * shares * price, which is only defined for prices in [0, 1] (probability space). get_commission validates the fill price and bails if it falls outside that closed interval. An out-of-range fill price indicates corrupted pricing data or a wrong instrument/price precision.

Source

Thrown at crates/adapters/polymarket/src/models.rs:87

            }
        };

        let Some(schedule) = binary
            .info
            .as_ref()
            .and_then(|info| info.get("fee_schedule"))
            .map(|value| serde_json::from_value::<FeeSchedule>(value.clone()))
            .transpose()
            .context("invalid Polymarket fee schedule")?
        else {
            return Ok(Money::zero(instrument.quote_currency()));
        };

        validate_schedule(&schedule)?;

        let fill_price = fill_px.as_decimal();
        if !(Decimal::ZERO..=Decimal::ONE).contains(&fill_price) {
            anyhow::bail!("PolymarketFeeModel requires a fill price in [0, 1]");
        }

        let fee_equivalent = fill_quantity
            .as_decimal()
            .checked_mul(schedule.rate)
            .and_then(|value| value.checked_mul(fill_price))
            .and_then(|value| value.checked_mul(Decimal::ONE - fill_price))
            .context("commission calculation overflow")?;
        let commission = match liquidity_side {
            LiquiditySide::Maker => -fee_equivalent
                .checked_mul(schedule.rebate_rate)
                .context("commission calculation overflow")?
                .round_dp(5),
            LiquiditySide::Taker => fee_equivalent.round_dp(5),
            LiquiditySide::NoLiquiditySide => unreachable!(),
        };

        Money::from_decimal(commission, instrument.quote_currency()).map_err(Into::into)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Convert fill prices to Polymarket's 0–1 decimal scale before fee calculation (divide by 100 if in percent).
  2. Verify the Price was constructed with POLYMARKET_PRICE_PRECISION and the correct scale.
  3. Validate fill prices at ingestion (reject/clamp values outside [0,1]) before the engine computes commission.
  4. Check the data feed producing the fill price for precision bugs.

Example fix

// before
let fill_px = Price::from_raw(55000000000); // wrong scale
// after
let fill_px = Price::new(0.55.into(), POLYMARKET_PRICE_PRECISION)?;
Defensive patterns

Strategy: validation

Validate before calling

let d = fill_px.as_decimal();
if !(Decimal::ZERO..=Decimal::ONE).contains(&d) {
    return Err(format!("fill price {d} outside [0,1]"));
}

Type guard

fn is_valid_probability_price(px: &Price) -> bool {
    let d = px.as_decimal();
    d >= Decimal::ZERO && d <= Decimal::ONE
}

Try / catch

match fee_model.get_commission(&order, qty, px, &instrument) {
    Ok(fee) => fee,
    Err(e) if e.to_string().contains("fill price in [0, 1]") => {
        log::error!("invalid fill price {} — check data feed precision", px);
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_commission with a fill_px outside [0, 1] — e.g. a price in cents (55 instead of 0.55), a price built with the wrong precision, or a negative/garbage price from a bad data feed.

Common situations: Mixing prices from another venue/precision; synthetic test fills using 0–100 probability scale; corrupted fill reports from a broken data source.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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