nautechsystems/nautilus_trader · warning

missing fee

Error message

missing fee

What it means

required_fee_amount normalizes an optional fee string from an OKX fill message: it trims whitespace and rejects None or empty strings with the terse 'missing fee' error before parsing the value as Decimal. OKX legitimately omits or blanks the fee field on some fills, so this conversion is strict by design — a fill without a fee string cannot produce a Commission.

Source

Thrown at crates/adapters/okx/src/common/parse.rs:445

/// OKX represents *charges* as positive numbers but they reduce the account
/// balance, hence the value is negated.
///
/// # Errors
///
/// Returns an error if the fee is missing or empty, cannot be parsed into
/// `Decimal`, or fails internal validation in [`Money::from_decimal`].
pub fn parse_fee(value: Option<&str>, currency: Currency) -> anyhow::Result<Money> {
    // OKX uses opposite sign convention: negative = cost, positive = rebate.
    // Negate to match Nautilus convention: positive = cost, negative = rebate.
    let decimal = required_fee_amount(value)?;
    Money::from_decimal(-decimal, currency).map_err(Into::into)
}

fn required_fee_amount(value: Option<&str>) -> anyhow::Result<Decimal> {
    let value = value
        .map(str::trim)
        .filter(|fee| !fee.is_empty())
        .ok_or_else(|| anyhow::anyhow!("missing fee"))?;
    Decimal::from_str(value).map_err(Into::into)
}

/// Parses OKX fee currency code, handling empty strings.
///
/// OKX sometimes returns empty fee currency codes.
/// When the fee currency is empty, defaults to USDT and logs a warning for non-zero fees.
pub fn parse_fee_currency(
    fee_ccy: &str,
    fee_amount: Decimal,
    context: impl FnOnce() -> String,
) -> Currency {
    let trimmed = fee_ccy.trim();
    if trimmed.is_empty() {
        if !fee_amount.is_zero() {
            let ctx = context();
            log::warn!(
                "Empty fee_ccy in {ctx} with non-zero fee={fee_amount}, using USDT as fallback"

View on GitHub (pinned to 18893faf8b)

Solutions

  1. If a missing fee should mean zero fee for your use case, treat None/empty as Decimal::ZERO before calling required_fee_amount (or pass Some("0") after defaulting).
  2. Check whether the fill is a spread fill or rebate case where OKX reports fee=0/empty, and handle it as zero with a warning instead of erroring.
  3. Log the raw fill JSON when this fires to confirm which fee field is blank and whether the adapter is reading the right field name for the current OKX API version.
  4. Update the adapter if OKX changed its fill-message schema (fee field renamed/moved).

Example fix

// before
let fee = required_fee_amount(fill.fee.as_deref())?;

// after
let fee = match fill.fee.as_deref().map(str::trim) {
    None | Some("") => Decimal::ZERO, // OKX omits fee on rebate/zero-fee fills
    Some(v) => required_fee_amount(Some(v))?,
};
Defensive patterns

Strategy: fallback

Validate before calling

fn fee_or_zero(fee: Option<&str>) -> Decimal {
    fee.map(str::trim)
        .filter(|f| !f.is_empty())
        .and_then(|f| Decimal::from_str(f).ok())
        .unwrap_or(Decimal::ZERO)
}

Try / catch

let fee = required_fee_amount(msg.fee.as_deref())
    .unwrap_or_else(|_| { log::debug!("OKX fill had no fee field; assuming 0"); Decimal::ZERO });

Prevention

When it happens

Trigger: parse_fee, parse_fill_report, or parse_spread_fill_report encounters a fill whose fee (feeCcy/fee string) is missing or empty after trimming, and the code path requires a fee amount to build the Commission for the fill report.

Common situations: Maker fills with zero/rebated fees reported as empty by OKX; spread fills where fee details aren't populated; API schema changes renaming fee fields so the parser reads a now-empty field; partial-liquidation fills omitting fee fields.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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