nautechsystems/nautilus_trader · error

callbackRate {rate}% out of Binance range [{min_rate}, {max_

Error message

callbackRate {rate}% out of Binance range [{min_rate}, {max_rate}]

What it means

For Binance trailing-stop orders the Nautilus trailing offset (basis points) is converted to Binance's callbackRate percent, which the exchange limits to [0.1%, 10.0%]. trailing_offset_to_callback_rate divides the offset by 100 and rejects anything outside that band; submit_order validates it before the order is sent so the order is denied locally rather than rejected by the API.

Source

Thrown at crates/adapters/binance/src/futures/conversions.rs:102

        Some(true)
    } else {
        None
    }
}

/// Converts a Nautilus trailing offset (percent) into a Binance `callbackRate` decimal.
///
/// # Errors
///
/// Returns an error if the computed rate is outside the Binance accepted range
/// `[0.1%, 10.0%]`.
pub(crate) fn trailing_offset_to_callback_rate(offset: Decimal) -> anyhow::Result<Decimal> {
    let rate = offset / rust_decimal::Decimal::ONE_HUNDRED;
    let min_rate = rust_decimal::Decimal::new(1, 1);
    let max_rate = rust_decimal::Decimal::new(100, 1);

    if rate < min_rate || rate > max_rate {
        anyhow::bail!("callbackRate {rate}% out of Binance range [{min_rate}, {max_rate}]");
    }

    Ok(rate)
}

/// Converts a Nautilus trailing offset (percent) into a Binance `callbackRate` string.
///
/// # Errors
///
/// Returns an error if the computed rate is outside the Binance accepted range.
pub(crate) fn trailing_offset_to_callback_rate_string(offset: Decimal) -> anyhow::Result<String> {
    let rate = trailing_offset_to_callback_rate(offset)?;
    Ok(format_callback_rate(rate))
}

/// Formats a `callbackRate` decimal for Binance request params.
///
/// Whole percents are rendered with a trailing `.0` to match Binance examples.

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Set trailing_offset between 10 and 1000 basis points (0.1% to 10%): e.g. 250 bps for a 2.5% callback.
  2. Double-check the unit conversion: percent * 100 = bps; 0.5% is 50 bps, not 5.
  3. Validate the offset before submission and clamp or reject in strategy code so the order never reaches the denial path.

Example fix

# before
order = OrderFactory.trailing_stop_market(
    trailing_offset=5,      # intended 0.5% but is 0.05%
    trailing_offset_type=TrailingOffsetType.BASIS_POINTS,
)

# after
order = OrderFactory.trailing_stop_market(
    trailing_offset=50,     # 0.5% = 50 bps (within 10..=1000)
    trailing_offset_type=TrailingOffsetType.BASIS_POINTS,
)
Defensive patterns

Strategy: try-catch

Validate before calling

MIN_BPS, MAX_BPS = 10, 1000  # Binance callbackRate 0.1%..10.0%

def callback_rate_bps_ok(offset) -> bool:
    return MIN_BPS <= offset <= MAX_BPS

assert callback_rate_bps_ok(order.trailing_offset)

Type guard

def is_valid_binance_callback_bps(offset) -> bool:
    return isinstance(offset, (int, float)) and 10 <= offset <= 1000

Try / catch

try:
    client.submit_order(order)
except Exception as e:
    if 'callbackRate' in str(e) and 'out of Binance range' in str(e):
        # clamp into [0.1%, 10.0%] or surface to risk checks; do NOT blind-retry
        log.warning('trailing offset out of Binance callback range: %s', e)
    else:
        raise

Prevention

When it happens

Trigger: Submitting a TRAILING_STOP_MARKET order on a Binance futures client with trailing_offset outside 10..=1000 bps — e.g. trailing_offset=5 (0.05%, below minimum), or trailing_offset=5000 (50%, above maximum), with trailing_offset_type=BASIS_POINTS.

Common situations: Porting stop distances from price units or percent into bps incorrectly (e.g. intending 0.5% and writing 5 bps); very tight trailing stops copied from spot scalping configs; not realizing Binance caps the callback rate at 10%.

Related errors


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