nautechsystems/nautilus_trader · error

Failed to convert balance

Error message

Failed to convert balance: {e}

What it means

parse_balance_allowance divides the raw USDC balance by USDC_SCALE and calls AccountBalance::from_total_and_locked; if the domain layer rejects the conversion (e.g. invalid money value for the currency), the error is wrapped as 'Failed to convert balance: {e}'. This surfaces a domain-constraint violation while turning a venue balance into a Nautilus AccountBalance.

Solutions

  1. Log and inspect the inner error and the raw balance value to see which domain constraint failed.
  2. Sanity-check balance_raw (reject negatives / implausible magnitudes) before calling parse_balance_allowance.
  3. Verify USDC_SCALE matches the API's current balance units (micro/macro USDC).

Example fix

// before
let balance = parse_balance_allowance(balance_raw, currency)?;
// after
anyhow::ensure!(balance_raw >= Decimal::ZERO, "negative balance_raw: {balance_raw}");
let balance = parse_balance_allowance(balance_raw, currency)?;
Defensive patterns

Strategy: try-catch

Validate before calling

if balance_raw < Decimal::ZERO { return Err(anyhow!("negative balance_raw: {balance_raw}")); }

Try / catch

match parse_balance_allowance(balance_raw, currency) {
    Ok(bal) => emit(bal),
    Err(e) => { log::error!("balance conversion failed: {e:#}; raw={balance_raw}"); /* skip this update, keep last good state */ }
}

Prevention

When it happens

Trigger: fetch_and_emit_account_state receives a balance_raw from the Polymarket API that, after division by USDC_SCALE, cannot form a valid AccountBalance — e.g. a negative balance or a value exceeding currency precision/limits.

Common situations: Venue returning an anomalous (negative or huge) balance during API incidents; a USDC_SCALE mismatch making values overflow the expected precision; a changed API response unit.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at crates/adapters/polymarket/src/execution/parse.rs:717

        filled_qty
    }
}

/// pUSD scale factor: the Polymarket API returns balances in micro-pUSD (10^6 units).
const USDC_SCALE: Decimal = Decimal::from_parts(1_000_000, 0, 0, false, 0);

/// Converts a raw micro-pUSD balance from the Polymarket API into an [`AccountBalance`].
///
/// The API returns balances as integer micro-pUSD (e.g. `20000000` = 20 pUSD).
/// This divides by 10^6 and constructs Money via `Money::from_decimal`, matching
/// the pattern used by dYdX, Deribit, OKX, and other adapters.
pub fn parse_balance_allowance(
    balance_raw: Decimal,
    currency: Currency,
) -> anyhow::Result<AccountBalance> {
    let balance_pusd = balance_raw / USDC_SCALE;
    AccountBalance::from_total_and_locked(balance_pusd, Decimal::ZERO, currency)
        .map_err(|e| anyhow::anyhow!("Failed to convert balance: {e}"))
}

/// Result of walking the order book to compute market order parameters.
#[derive(Debug)]
pub struct MarketPriceResult {
    /// The crossing price (worst level reached) for the signed CLOB order.
    pub crossing_price: Decimal,
    /// Expected base quantity (shares) computed by walking levels at actual prices.
    pub expected_base_qty: Decimal,
}

/// Calculates the market-crossing price and expected base quantity by walking the order book.
///
/// Sorts levels deterministically before walking:
/// - BUY (asks): ascending by price, best (lowest) ask first
/// - SELL (bids): descending by price, best (highest) bid first
///
/// This ensures correct results regardless of the CLOB API's response ordering.

View on GitHub (pinned to 18893faf8b)