{"record":{"id":"01c5f95ed869ed71","repo":"nautechsystems/nautilus_trader","slug":"historical-open-interest-request-requires-period","errorCode":null,"errorMessage":"historical open interest request requires `period` metadata","messagePattern":"historical open interest request requires `period` metadata","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/binance/src/futures/data.rs","lineNumber":448,"sourceCode":"            .filter(|value| !value.is_empty())\n        else {\n            anyhow::bail!(\"custom data request requires `instrument_id` metadata\");\n        };\n\n        InstrumentId::from_str(raw_instrument_id)\n            .with_context(|| format!(\"invalid instrument_id metadata `{raw_instrument_id}`\"))\n    }\n\n    fn required_period_metadata(data_type: &DataType) -> anyhow::Result<String> {\n        let Some(period) = data_type\n            .metadata()\n            .as_ref()\n            .and_then(|m| m.get(\"period\"))\n            .and_then(|v| v.as_str())\n            .map(str::trim)\n            .filter(|value| !value.is_empty())\n        else {\n            anyhow::bail!(\"historical open interest request requires `period` metadata\");\n        };\n\n        Ok(period.to_string())\n    }\n\n    fn coinm_open_interest_hist_params(\n        http: &BinanceFuturesHttpClient,\n        instrument_id: &InstrumentId,\n    ) -> anyhow::Result<(String, String)> {\n        let symbol = format_binance_symbol(instrument_id);\n        if let Some(pair) = symbol.strip_suffix(\"_PERP\") {\n            return Ok((pair.to_string(), \"PERPETUAL\".to_string()));\n        }\n\n        let cache = http.instruments_cache();\n        let definition = cache\n            .get(&Ustr::from(symbol.as_str()))\n            .with_context(|| format!(\"missing COIN-M definition for {instrument_id}\"))?;","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/binance/src/futures/data.rs#L430-L466","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`).","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"],"exampleFix":"# before\nmeta = {'instrument_id': 'BTCUSDT-PERP.BINANCE'}  # missing period\ndata_type = DataType(BinanceFuturesOpenInterestHist, metadata=meta)\n\n# after\nmeta = {'instrument_id': 'BTCUSDT-PERP.BINANCE', 'period': '1h'}\ndata_type = DataType(BinanceFuturesOpenInterestHist, metadata=meta)","handlingStrategy":"validation","validationCode":"VALID_OI_HIST_PERIODS = {'5m', '15m', '30m', '1h', '2h', '4h', '6h', '12h', '1d'}\n\ndef build_oi_hist_metadata(instrument_id: str, period: str | None) -> dict:\n    period = (period or '').strip()\n    if not period:\n        raise ValueError('BinanceFuturesOpenInterestHist requires non-empty \"period\" metadata')\n    if period not in VALID_OI_HIST_PERIODS:\n        raise ValueError(f'period {period!r} not accepted by Binance openInterestHist; use {sorted(VALID_OI_HIST_PERIODS)}')\n    return {'instrument_id': instrument_id, 'period': period}","typeGuard":"def has_valid_period_metadata(metadata: dict | None) -> bool:\n    if not isinstance(metadata, dict):\n        return False\n    period = metadata.get('period')\n    return isinstance(period, str) and period.strip() != ''","tryCatchPattern":"try:\n    actor.request_custom_data(data_type, ...)\nexcept Exception as e:\n    if 'requires `period` metadata' in str(e):\n        raise ValueError('Add {\"period\": \"5m|15m|30m|1h|2h|4h|6h|12h|1d\"} to the BinanceFuturesOpenInterestHist DataType metadata') from e\n    raise","preventionTips":["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"],"tags":["binance","futures","open-interest","metadata","request-validation"],"backgroundTag":"missing-request-parameter","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}