nautechsystems/nautilus_trader · error
fee exponent must be non-negative
Error message
fee exponent must be non-negative
What it means
instrument_fee_exponent extracts the fee exponent from the venue fee schedule JSON and requires it to be a numeric value >= 0. The exponent drives the fee curve rate; a negative exponent would produce nonsensical fee scaling, so the parser bails with "fee exponent must be non-negative".
Source
Thrown at crates/adapters/polymarket/src/execution/parse.rs:480
pub fn instrument_fee_exponent(instrument: &InstrumentAny) -> anyhow::Result<Decimal> {
let value = match instrument {
InstrumentAny::BinaryOption(bo) => {
bo.info.as_ref().and_then(|info| info.get("fee_schedule"))
}
_ => None,
};
let Some(schedule) = value else {
return Ok(Decimal::ONE);
};
let value = schedule
.get("exponent")
.context("fee schedule is missing exponent")?;
let exponent = match value {
serde_json::Value::String(value) => parse_decimal_exact(value)?,
serde_json::Value::Number(value) => parse_decimal_exact(&value.to_string())?,
_ => anyhow::bail!("fee exponent must be a decimal number or numeric string"),
};
anyhow::ensure!(
exponent >= Decimal::ZERO,
"fee exponent must be non-negative"
);
Ok(exponent)
}
/// Adjusts a market-BUY pUSD amount to fit within the user's pUSD balance once
/// platform and builder taker fees are deducted. Mirrors `adjust_market_buy_amount`
/// in `polymarket-rs-clob-client-v2`'s `clob/utilities.rs`.
///
/// Returns `amount` unchanged when the balance already covers `amount + fees`.
/// Otherwise solves for the principal that, with fees, exactly consumes the
/// balance, then truncates to `USDC_DECIMALS` (the on-chain pUSD scale).
///
/// The fee-curve step `(p * (1 - p))^exponent` is the only computation that
/// crosses into `f64`, matching the reference SDK so we agree with the
/// venue's authoritative match-time fee calculation regardless of whether
/// Polymarket ships a fractional exponent in the future.View on GitHub (pinned to 18893faf8b)
Solutions
- Check the fee schedule payload's exponent field and correct it to a non-negative value.
- Refresh the fee schedule from the venue (delete stale cached fee config and re-fetch).
- Confirm exponent conventions: if the schedule is meant to express 10^-n rates, store n as a non-negative integer.
Example fix
// before
{"exponent": "-2"} // negative -> rejected
// after
{"exponent": "2"} // non-negative fee-curve exponent Defensive patterns
Strategy: validation
Validate before calling
let exponent: Decimal = parse_decimal_exact(schedule["exponent"].as_str().unwrap_or_default())?;
if exponent < Decimal::ZERO { return Err("fee schedule exponent must be >= 0".into()); } Type guard
fn has_valid_fee_exponent(schedule: &serde_json::Value) -> bool {
schedule.get("exponent")
.and_then(|v| v.as_str().or_else(|| v.as_number().map(|n| n.to_string().into())).map(|s| s.parse::<Decimal>().map(|d| d >= Decimal::ZERO).unwrap_or(false)))
.unwrap_or(false)
} Try / catch
let exponent = match instrument_fee_exponent(&schedule) {
Ok(e) => e,
Err(e) => { tracing::warn!("bad fee schedule: {e:#}; refreshing from venue"); refresh_fee_schedule().await?; }
}; Prevention
- Re-fetch the fee schedule from the venue instead of trusting long-lived caches.
- Validate the whole fee schedule JSON schema once at load.
- Never hand-edit cached fee schedule files.
When it happens
Trigger: Parsing a Polymarket fee schedule whose exponent field is a negative number (string or JSON number), via calculate_commission, build_admitted_target_fill, build_fill_reports_from_trades, build_ws_taker_fill_report, or submit_market_order.
Common situations: Stale or hand-edited cached fee-schedule JSON; a venue fee-model change emitting a different exponent convention; test fixtures with negative exponent values.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Gamma {scope} filter '{key}' must be an unsigned integer: {e
- Gamma {scope} filter '{key}' must be a decimal number: {e}
- Gamma {scope} filter '{key}' values must be unsigned integer
- unrecognized side '{side}'
- quantity size={} cannot be represented with precision={}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/989b73302e423fca.
Report an issue: GitHub.