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
- Add both required keys to the DataType metadata: {'instrument_id': 'BTCUSDT-PERP.BINANCE', 'period': '1h'}
- 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)
- 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
- Build all custom-data metadata through one helper that inserts instrument_id and period together
- Remember the asymmetry: current open interest needs no period, the Hist variant always does
- Spell the key exactly 'period' — 'interval'/'timeframe' silently fail this check
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
- Binance Futures custom data requires BINANCE venue instrumen
- custom data request requires `instrument_id` metadata
- expected a COIN-M definition for {instrument_id}
- historical BinanceBar requests require EXTERNAL aggregation
- historical BinanceBar requests require LAST price type
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/01c5f95ed869ed71.
Report an issue: GitHub.