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.

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.

Appendix: 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)