nautechsystems/nautilus_trader · error

PolymarketFeeModel requires fee schedule exponent 1, was {}

Error message

PolymarketFeeModel requires fee schedule exponent 1, was {}

What it means

validate_schedule enforces that the market's FeeSchedule has exponent 1, the only exponent formula PolymarketFeeModel supports for its linear fee computation. A schedule with any other exponent (e.g. Polymarket's historical nonlinear exponent schedules) is rejected.

Source

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

            .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)
    }
}

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(())
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Only apply PolymarketFeeModel to markets whose fee schedule has exponent 1.
  2. Inspect the market's fee_schedule in instrument info and confirm what the CLOB API returns.
  3. Extend validate_schedule/commission math to support the observed exponent if that market type must be supported.
  4. Update stale fee schedule fixtures to exponent 1 if the data is simply outdated.

Example fix

// before
let schedule = FeeSchedule { exponent: Decimal::new(2, 0), .. };
// after
let schedule = FeeSchedule { exponent: Decimal::ONE, .. };
Defensive patterns

Strategy: validation

Validate before calling

if schedule.exponent != Decimal::ONE {
    return Err(format!("unsupported fee exponent {}", schedule.exponent));
}

Type guard

fn is_linear_schedule(s: &FeeSchedule) -> bool {
    s.exponent == Decimal::ONE && s.rate >= Decimal::ZERO && (Decimal::ZERO..=Decimal::ONE).contains(&s.rebate_rate)
}

Try / catch

match fee_model.get_commission(&order, qty, px, &instrument) {
    Ok(fee) => fee,
    Err(e) if e.to_string().contains("exponent 1") => {
        log::warn!("market uses nonlinear fee schedule; skipping fee model");
        Money::zero(instrument.quote_currency())
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A market's taker fee schedule (from instrument info) carries exponent != 1 when get_commission runs validate_schedule — e.g. markets whose fee schedule uses a nonlinear exponent.

Common situations: Trading markets with legacy or special fee schedules fetched from the CLOB API; fee schedule data drift where the API starts returning exponent 2 or 0.5; hard-coded test fixtures with wrong exponent values.

Related errors


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