nautechsystems/nautilus_trader · error · anyhow::Error

AX maintenance_margin_pct must be positive, was {maintenance

Error message

AX maintenance_margin_pct must be positive, was {maintenance_margin_pct}

What it means

parse_margin_rates requires maintenance_margin_pct to be strictly greater than zero before percent-to-rate conversion. A zero or negative maintenance percent — usually an unset field — aborts parsing of the instrument, even when the initial margin is valid.

Source

Thrown at crates/adapters/architect_ax/src/http/parse.rs:388

        Some(taker_fee),
        None,
        Some(info),
        ts_event,
        ts_init,
    );

    Ok(InstrumentAny::PerpetualContract(instrument))
}

fn parse_margin_rates(
    initial_margin_pct: Decimal,
    maintenance_margin_pct: Decimal,
) -> anyhow::Result<(Decimal, Decimal)> {
    anyhow::ensure!(
        initial_margin_pct > Decimal::ZERO,
        "AX initial_margin_pct must be positive, was {initial_margin_pct}"
    );
    anyhow::ensure!(
        maintenance_margin_pct > Decimal::ZERO,
        "AX maintenance_margin_pct must be positive, was {maintenance_margin_pct}"
    );
    anyhow::ensure!(
        maintenance_margin_pct <= initial_margin_pct,
        "AX maintenance_margin_pct {maintenance_margin_pct} exceeds initial_margin_pct {initial_margin_pct}"
    );

    Ok((
        margin_percent_to_rate(initial_margin_pct, "initial_margin_pct")?,
        margin_percent_to_rate(maintenance_margin_pct, "maintenance_margin_pct")?,
    ))
}

fn margin_percent_to_rate(value: Decimal, field: &str) -> anyhow::Result<Decimal> {
    let normalized = value.normalize();
    let scale = normalized.scale();
    anyhow::ensure!(

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Set a positive maintenance_margin_pct for the symbol in AX venue config
  2. Confirm the API field mapping for your version so the value is not silently defaulted to 0
  3. If the venue truly has no separate maintenance tier, set it equal to (not exceeding) the initial margin percent
  4. Reload instruments after the config fix

Example fix

// before (AX contract metadata)
"maintenance_margin_pct": "0"
// after
"maintenance_margin_pct": "0.5"
Defensive patterns

Strategy: try-catch

Validate before calling

// Vet margin fields before definitions reach the parser
fn maintenance_margin_ok(v: Decimal) -> bool { v > Decimal::ZERO }

if !maintenance_margin_ok(definition.maintenance_margin_pct) {
    anyhow::bail!("symbol {} has non-positive maintenance_margin_pct {}}", definition.symbol, definition.maintenance_margin_pct);
}

Try / catch

match parse_instrument(&definition, ts_event, ts_init) {
    Ok(instrument) => Ok(instrument),
    Err(e) if e.to_string().contains("maintenance_margin_pct must be positive") => {
        log::error!("symbol {} margin config incomplete (maintenance_margin_pct <= 0)", definition.symbol);
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: An instrument definition arrives with maintenance_margin_pct 0 (unconfigured listing) or negative, while initial_margin_pct may be fine; the load of that symbol fails inside parse_margin_rates.

Common situations: Margin config partially filled — initial set but maintenance left at default 0; API field renames causing zero-default deserialization; venues that do not distinguish maintenance margin (values omitted).

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/191375d81e8a0b74. Report an issue: GitHub.