GET /stats/open-interest
Open interest, long and short reported separately
Open interest, long and short reported separately.
operationId: fetchOpenInterest
Not reachable through CCXT. Venue-wide as specified, and ccxt's unified method is per-symbol.
fetchOpenInterest(symbol)returns one market's open interest; this operation takes no parameters and returns every market plus a venue total, and ccxt 4.5.76 has no plural form (ex.has['fetchOpenInterests']is None, checked). Annotating itfetchOpenInterestwould put a CCXT badge over an operation an adapter cannot call that way — the concept matches, the contract does not. Revisit if a per-market variant is added here, or if ccxt gains a plural method; the operationId already anticipates the mapping.
Authentication: none — this operation is public.
Open interest per market and as a venue total, with the long and short sides reported separately rather than pre-summed.
Read this first: which number to publish. On a perpetual venue every long position is matched by an equal short, so long_oi == short_oi holds by construction and their sum is exactly TWICE the one-sided figure reference venues such as Hyperliquid and dYdX headline. Caption long_oi_quote (or short_oi_quote, which equals it). gross_oi_two_sided_quote is the sum, named so it cannot be mistaken for the one-sided figure — it is NOT the quantity the BFF's total_open_interest on GET /market-stats carries — that figure sums /admin/risk-summary's per-market totals, which are raw position sizes in BASE units with the mark never multiplied in. To reconcile per market, compare the BFF's open_interest against long_oi_base + short_oi_base. There is deliberately no venue-level counterpart, because base units do not add across markets.
Base versus quote. *_base is the size open in the market's BASE asset (BTC for BTC-USDX-PERP). Base units do NOT add across markets — 3 BTC and 50 ETH sum to nothing — so the venue-level figures are quote-only and *_base appears per market only. *_quote is that size priced into quote notional (USDX) at mark_price.
Where the mark price comes from, and when a quote figure is absent. mark_price_source names it on every response. The mark is this indexer's mirror of the engine's GET /markets/{market_id}/mark-price, refreshed roughly every second and withheld once it is older than 15 seconds rather than served frozen. A market whose mark is absent, stale, or whose notional overflows keeps its *_base figures and carries quote_error instead of *_quote; absence is never zero. If a market that CARRIES open interest could not be priced, the venue *_quote fields are omitted too and the top-level quote_error names the markets — a partial venue total is never presented as complete. The venue figures are also withheld when the venue fold itself overflows Decimal, and that message names no market because every row priced; read quote_error rather than inferring the cause. A market with NOTHING open is exempt from all of that: no mark can change a zero, so it prices to "0" while still carrying the quote_error that says why it has no mark, and it never withholds the venue figure. Withholding the headline number because an EMPTY market lost its mark would lose it for the one reason that provably cannot affect it.
Freshness, and the one thing no error field can tell you. This is an in-memory projection folded from the engine's position stream and repaired from the engine's authoritative snapshot on a background sweep. Shortly after the service is rebuilt the mirror can be partially hydrated, and the figure reads low; the fold cannot distinguish that from a genuinely quiet venue, so no error field reports it. The sharpest form is a venue figure of "0" in the first seconds after a restart, when listed markets already exist and no mark has landed yet: that is the correct answer for what the mirror holds and the wrong one for what the venue holds, and this response cannot tell you which you are reading. Do not alert on a drop to zero without checking as_of_ms and the per-market quote_error values. as_of_ms — the last event this indexer ingested, on its OWN clock — is what tells a fresh snapshot from a stalled fold. It is not the last time open interest changed.
Values are decimal STRINGS, not JSON numbers, so they are exact; parse with a decimal type, never a float. Every listed market gets an entry, carrying "0" on both sides when nothing is open, so "no open interest" is distinguishable from "market unknown"; a delisted market appears only while it still carries a position. network names the chain network this indexer serves and testnet is the same fact as a boolean, both derived from auth.network rather than asserted (ENG-12796); note restates the one-sided warning in prose.
Responses
Example
curl -X GET 'https://exchange.nexus.xyz/api/exchange/stats/open-interest'import requests
response = requests.get("https://exchange.nexus.xyz/api/exchange/stats/open-interest")
response.raise_for_status()
print(response.json())const response = await fetch("https://exchange.nexus.xyz/api/exchange/stats/open-interest", {
method: "GET",
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
console.log(await response.json());Generated from eng/apps/exchange/api/openapi.json (spec 0.9.36). Do not edit by hand — regenerate with python3 product/docs/tools/api-reference/generate.py. Hand-authored context lives in the Guides pages.
Last updated

