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
- 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).
- 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.
- 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.
- 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
- Expect empty/absent fee fields on OKX rebate and spread fills and default them to zero deliberately.
- Log raw fill payloads once when a missing fee is first seen to confirm the schema.
- Pin the adapter version to the OKX API version in use and update both together.
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
- Unknown position currency '{pos_ccy}' for instrument {instru
- Invalid timeframe for `BarSpecification`, was {timeframe}
- Failed to create commission from fee: {e}
- Invalid funding_rate value: {e}
- Invalid funding_interval, cannot be negative
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9c82adadd2fd41f4.
Report an issue: GitHub.