nautechsystems/nautilus_trader · error

historical open interest request requires `period` metadata

Error message

historical open interest request requires `period` metadata

What it means

The Binance Futures data client rejects a `BinanceFuturesOpenInterestHist` custom-data request unless the DataType metadata contains a non-empty, trimmed string under the key `period`. The value maps directly to the mandatory `period` query parameter of Binance's open-interest-history endpoint (accepted values such as `5m`, `15m`, `30m`, `1h`, `1d`). The check runs in `required_period_metadata` before any HTTP call, so nothing is sent to Binance.

Source

Thrown at crates/adapters/binance/src/futures/data.rs:448

            .filter(|value| !value.is_empty())
        else {
            anyhow::bail!("custom data request requires `instrument_id` metadata");
        };

        InstrumentId::from_str(raw_instrument_id)
            .with_context(|| format!("invalid instrument_id metadata `{raw_instrument_id}`"))
    }

    fn required_period_metadata(data_type: &DataType) -> anyhow::Result<String> {
        let Some(period) = data_type
            .metadata()
            .as_ref()
            .and_then(|m| m.get("period"))
            .and_then(|v| v.as_str())
            .map(str::trim)
            .filter(|value| !value.is_empty())
        else {
            anyhow::bail!("historical open interest request requires `period` metadata");
        };

        Ok(period.to_string())
    }

    fn coinm_open_interest_hist_params(
        http: &BinanceFuturesHttpClient,
        instrument_id: &InstrumentId,
    ) -> anyhow::Result<(String, String)> {
        let symbol = format_binance_symbol(instrument_id);
        if let Some(pair) = symbol.strip_suffix("_PERP") {
            return Ok((pair.to_string(), "PERPETUAL".to_string()));
        }

        let cache = http.instruments_cache();
        let definition = cache
            .get(&Ustr::from(symbol.as_str()))
            .with_context(|| format!("missing COIN-M definition for {instrument_id}"))?;

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Add both required keys to the DataType metadata: {'instrument_id': 'BTCUSDT-PERP.BINANCE', 'period': '1h'}
  2. Use one of Binance's accepted period strings: 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d (an unlisted-but-nonempty string passes this check but will then fail at the Binance HTTP layer)
  3. Confirm you actually intended the historical variant; if you only want current open interest, request `BinanceFuturesOpenInterest` which takes no period

Example fix

# before
meta = {'instrument_id': 'BTCUSDT-PERP.BINANCE'}  # missing period
data_type = DataType(BinanceFuturesOpenInterestHist, metadata=meta)

# after
meta = {'instrument_id': 'BTCUSDT-PERP.BINANCE', 'period': '1h'}
data_type = DataType(BinanceFuturesOpenInterestHist, metadata=meta)
Defensive patterns

Strategy: validation

Validate before calling

VALID_OI_HIST_PERIODS = {'5m', '15m', '30m', '1h', '2h', '4h', '6h', '12h', '1d'}

def build_oi_hist_metadata(instrument_id: str, period: str | None) -> dict:
    period = (period or '').strip()
    if not period:
        raise ValueError('BinanceFuturesOpenInterestHist requires non-empty "period" metadata')
    if period not in VALID_OI_HIST_PERIODS:
        raise ValueError(f'period {period!r} not accepted by Binance openInterestHist; use {sorted(VALID_OI_HIST_PERIODS)}')
    return {'instrument_id': instrument_id, 'period': period}

Type guard

def has_valid_period_metadata(metadata: dict | None) -> bool:
    if not isinstance(metadata, dict):
        return False
    period = metadata.get('period')
    return isinstance(period, str) and period.strip() != ''

Try / catch

try:
    actor.request_custom_data(data_type, ...)
except Exception as e:
    if 'requires `period` metadata' in str(e):
        raise ValueError('Add {"period": "5m|15m|30m|1h|2h|4h|6h|12h|1d"} to the BinanceFuturesOpenInterestHist DataType metadata') from e
    raise

Prevention

When it happens

Trigger: Calling the custom-data request path with data type name `BinanceFuturesOpenInterestHist` and: no metadata at all, a metadata map missing the `period` key, `period` set to an empty string or whitespace-only string, or `period` set to a non-string JSON value (e.g. a number) so `as_str()` returns None. Note the current open interest type `BinanceFuturesOpenInterest` does NOT require period, only the Hist variant does.

Common situations: Copying a working current-open-interest request and only changing the type name to the Hist variant; building the metadata dict with a wrong key such as `interval` or `timeframe`; passing the period as a number of minutes (60) instead of the Binance string format (`1h`).

Related errors


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