nautechsystems/nautilus_trader · error

Polymarket fee rate must be greater than or equal to zero

Error message

Polymarket fee rate must be greater than or equal to zero

What it means

validate_schedule rejects fee schedules with a negative rate, since a fee rate below zero is meaningless for this model (it would turn commission into a payout). The rate comes from the market's fee schedule in the binary option's instrument info.

Source

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

                .round_dp(5),
            LiquiditySide::Taker => fee_equivalent.round_dp(5),
            LiquiditySide::NoLiquiditySide => unreachable!(),
        };

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

fn validate_schedule(schedule: &FeeSchedule) -> anyhow::Result<()> {
    if schedule.exponent != Decimal::ONE {
        anyhow::bail!(
            "PolymarketFeeModel requires fee schedule exponent 1, was {}",
            schedule.exponent
        );
    }

    if schedule.rate < Decimal::ZERO {
        anyhow::bail!("Polymarket fee rate must be greater than or equal to zero");
    }

    if !(Decimal::ZERO..=Decimal::ONE).contains(&schedule.rebate_rate) {
        anyhow::bail!("Polymarket rebate rate must be in [0, 1]");
    }

    if !schedule.taker_only {
        anyhow::bail!("PolymarketFeeModel requires a taker-only fee schedule");
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use nautilus_core::UnixNanos;
    use nautilus_execution::models::fee::{FeeModel, FeeModelHandle};
    use nautilus_model::{
        enums::{LiquiditySide, OrderSide, OrderType},

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the fee schedule source (instrument info) and correct the negative rate value.
  2. Validate fee schedule data at instrument-build time so bad schedules are rejected earlier.
  3. Check whether the API response was misparsed (e.g. a rebate misread as a rate).
  4. Fix test fixtures to use non-negative rates.

Example fix

// before
let schedule = FeeSchedule { rate: Decimal::new(-5, 3), .. };
// after
let schedule = FeeSchedule { rate: Decimal::new(5, 3), .. }; // 0.005
Defensive patterns

Strategy: validation

Validate before calling

if schedule.rate < Decimal::ZERO {
    return Err("negative fee rate in schedule".into());
}

Type guard

fn has_valid_rate(s: &FeeSchedule) -> bool {
    s.rate >= Decimal::ZERO
}

Try / catch

match fee_model.get_commission(&order, qty, px, &instrument) {
    Ok(fee) => fee,
    Err(e) if e.to_string().contains("greater than or equal to zero") => {
        log::error!("corrupt fee schedule (negative rate); refusing to trade");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: get_commission encounters a FeeSchedule whose rate < 0 — from a malformed CLOB API response, bad manual fixture, or sign error when constructing the schedule.

Common situations: Hand-written test fixtures with negative rates; API/schema changes altering how the rate is encoded; data pipelines mangling the sign.

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