nautechsystems/nautilus_trader · error

No mark price returned from OKX

Error message

No mark price returned from OKX

What it means

Raised in `request_mark_price` when OKX returns HTTP success but an empty data array — no mark-price record exists for the requested instrument. The adapter requires exactly one record and treats the empty result as an error.

Source

Thrown at crates/adapters/okx/src/http/client.rs:2807

    pub async fn request_mark_price(
        &self,
        instrument_id: InstrumentId,
    ) -> anyhow::Result<MarkPriceUpdate> {
        let inst = self.instrument_from_cache(instrument_id.symbol.inner())?;
        let mut params = GetMarkPriceParamsBuilder::default();
        params.inst_type(okx_instrument_type(&inst)?);
        params.inst_id(instrument_id.symbol.inner());
        let params = params.build().map_err(|e| anyhow::anyhow!(e))?;

        let resp = self
            .inner
            .get_mark_price(params)
            .await
            .map_err(|e| anyhow::anyhow!(e))?;

        let raw = resp
            .first()
            .ok_or_else(|| anyhow::anyhow!("No mark price returned from OKX"))?;
        let ts_init = self.generate_ts_init();

        let mark_price =
            parse_mark_price_update(raw, instrument_id, inst.price_precision(), ts_init)
                .map_err(|e| anyhow::anyhow!(e))?;
        Ok(mark_price)
    }

    /// Requests the current price limits for the `instrument_id` from OKX.
    ///
    /// # Errors
    ///
    /// Returns an error if the HTTP request fails or no price limit is returned.
    pub async fn request_price_limit(
        &self,
        instrument_id: InstrumentId,
    ) -> anyhow::Result<OKXPriceLimit> {
        let mut params = GetPriceLimitParamsBuilder::default();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the instrument is currently listed and trading on OKX.
  2. Check the inst_type derived from the cached instrument matches the instrument (SWAP/FUTURES/OPTION vs SPOT).
  3. Guard the caller: skip or defer mark-price requests for expired/delisted instruments.
  4. Retry later if OKX is in maintenance; empty responses during downtime are transient.

Example fix

// before
let mark = client.request_mark_price(&expired_future_id).await?;
// after
if !instrument_is_active(&expired_future_id) { return Ok(None); }
let mark = client.request_mark_price(&expired_future_id).await?;
Defensive patterns

Strategy: validation

Validate before calling

if instrument.expiry_unix_nanos() <= clock.timestamp_ns() {
    // skip mark price for expired instruments
    return Ok(None);
}

Type guard

fn is_active(now_unix: i64, inst: &InstrumentAny) -> bool {
    inst.expiration().map_or(true, |exp| exp.as_unix_nanos() > now_unix)
}

Try / catch

match client.request_mark_price(&inst_id).await {
    Ok(mp) => mp,
    Err(e) if e.to_string().contains("No mark price returned") => {
        log::warn!("no OKX mark price for {inst_id}; skipping");
        Default::default()
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: Calling `request_mark_price` for an instrument_id whose inst_id/inst_type yields no rows on OKX: delisted instrument, expired derivative, spot pair without mark price, or wrong venue/symbol in config.

Common situations: Polling mark price for an expired future or settled option; symbol configured with the wrong venue suffix; querying before the instrument began trading; OKX temporarily returning empty data during maintenance.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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