Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ tests/*
!tests/test_webhooks.py
!tests/test_webhook_signature.py
!tests/test_surface_parity.py
!tests/test_api_contract.py
!tests/test_api_contract_live.py

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
216 changes: 190 additions & 26 deletions CHANGELOG.md

Large diffs are not rendered by default.

841 changes: 508 additions & 333 deletions README.md

Large diffs are not rendered by default.

54 changes: 47 additions & 7 deletions oxarchive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@

Historical Market Data API for two venues, Hyperliquid and Lighter. Lighter has
two deployments: mainnet and Robinhood Chain.
- Hyperliquid (perpetuals data from April 2023)
- Hyperliquid core perpetuals (order book from 2023-04-15)
- Hyperliquid HIP-3 builder perps under the Hyperliquid namespace at /v1/hyperliquid/hip3 and client.hyperliquid.hip3
- Hyperliquid HIP-4 outcome markets under the Hyperliquid namespace at /v1/hyperliquid/hip4 and client.hyperliquid.hip4
- Hyperliquid spot pairs under /v1/hyperliquid/spot and client.spot (trades and candles
from 2025-03-22, candle floor 2025-03-22T10:50:22Z, rest live from 2026-05-05)
from 2025-03-22; order book and L4 from 2026-05-05)
- Lighter mainnet at /v1/lighter and client.lighter
- Lighter on Robinhood Chain at /v1/rh-lighter and client.rh_lighter (USDG-quoted;
trades and liquidations from 2026-06-26; order book, open interest and
funding from 2026-08-22)
- Account positions on client.hyperliquid.positions, client.hyperliquid.hip3.positions,
client.lighter.positions and client.rh_lighter.positions
- Webhooks on client.webhooks, with WebhookVerifier to verify deliveries
- What each venue serves, per datatype, and from when, on client.capabilities()

Every request selects API version 2026-10-01 (the ``0xArchive-Version``
header, and ``version`` on WebSocket connections). Errors raise
OxArchiveError, whose ``error_code`` is one of ERROR_CODES.

Example:
>>> from oxarchive import Client
Expand All @@ -25,23 +30,28 @@
>>> hl_orderbook = client.hyperliquid.orderbook.get("BTC")
>>> print(f"BTC mid price: {hl_orderbook.mid_price}")
>>>
>>> # Lighter.xyz data
>>> # Lighter data
>>> lighter_orderbook = client.lighter.orderbook.get("BTC")
>>>
>>> # Lighter on Robinhood Chain
>>> rh_orderbook = client.rh_lighter.orderbook.get("AAPL-USDG")
>>>
>>> # Hyperliquid HIP-3 data
>>> hip3_orderbook = client.hyperliquid.hip3.orderbook.get("km:US500")
>>> hip3_orderbook = client.hyperliquid.hip3.orderbook.get("xyz:TSLA")
>>>
>>> # Hyperliquid spot data (dashed canonical symbols)
>>> spot_orderbook = client.spot.orderbook.get("HYPE-USDC")
>>>
>>> # Get historical snapshots
>>> history = client.hyperliquid.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
>>> # Order book history for the last hour
>>> from datetime import datetime, timedelta, timezone
>>> now = datetime.now(timezone.utc)
>>> history = client.hyperliquid.orderbook.history(
... "ETH", start=now - timedelta(hours=1), end=now
... )
"""

from .client import Client
from .http import API_VERSION, API_VERSION_HEADER
from .exchanges import (
HyperliquidClient,
Hip3Client,
Expand All @@ -51,6 +61,7 @@
SpotClient,
)
from .resources.orderbook import LighterGranularity
from .resources.trades import TradeSide
from .orderbook_reconstructor import (
OrderBookReconstructor,
OrderbookDelta,
Expand Down Expand Up @@ -112,6 +123,13 @@
OxArchiveError,
CursorResponse,
ResponseMeta,
# API contract: error codes, venues and capabilities
ErrorCode,
ERROR_CODES,
WEBSOCKET_ERROR_CODES,
Venue,
VENUES,
Capability,
# Cumulative volume delta, HIP-3 oracle, HIP-4 questions
CvdBucket,
CvdInterval,
Expand Down Expand Up @@ -210,18 +228,29 @@

# WebSocket client (optional import - requires websockets package)
try:
from .websocket import OxArchiveWs, WsOptions
from .websocket import (
WS_CHANNELS,
OxArchiveWs,
WsChannelSpec,
WsOptions,
decode_lighter_payload,
)
_HAS_WEBSOCKET = True
except ImportError:
_HAS_WEBSOCKET = False
OxArchiveWs = None # type: ignore
WsOptions = None # type: ignore
WsChannelSpec = None # type: ignore
WS_CHANNELS = None # type: ignore
decode_lighter_payload = None # type: ignore

__version__ = "1.12.0"

__all__ = [
# Client
"Client",
"API_VERSION",
"API_VERSION_HEADER",
# Exchange Clients
"HyperliquidClient",
"Hip3Client",
Expand All @@ -232,6 +261,9 @@
# WebSocket Client
"OxArchiveWs",
"WsOptions",
"WsChannelSpec",
"WS_CHANNELS",
"decode_lighter_payload",
# Orderbook Reconstructor (Lighter)
"OrderBookReconstructor",
"OrderbookDelta",
Expand Down Expand Up @@ -283,6 +315,14 @@
"OxArchiveError",
"CursorResponse",
"ResponseMeta",
# API Contract
"ErrorCode",
"ERROR_CODES",
"WEBSOCKET_ERROR_CODES",
"Venue",
"VENUES",
"Capability",
"TradeSide",
# Cumulative Volume Delta, HIP-3 Oracle, HIP-4 Questions
"CvdBucket",
"CvdInterval",
Expand Down
67 changes: 53 additions & 14 deletions oxarchive/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from .http import HttpClient
from .exchanges import HyperliquidClient, LighterClient, RhLighterClient, SpotClient
from .types import Capability
from .resources import (
OrderBookResource,
TradesResource,
Expand All @@ -31,11 +32,11 @@ class Client:
Two venues: Hyperliquid and Lighter. Lighter has two deployments: mainnet
and Robinhood Chain.

- `client.hyperliquid` - Hyperliquid perpetuals (April 2023+)
- `client.hyperliquid` - Hyperliquid perpetuals (order book from 2023-04-15)
- `client.hyperliquid.hip3` - Hyperliquid HIP-3 builder perps under the Hyperliquid namespace
- `client.hyperliquid.hip4` - Hyperliquid HIP-4 outcome markets under the Hyperliquid namespace
- `client.spot` - Hyperliquid spot pairs (trades and candles from 2025-03-22,
orderbook/L4/TWAP live from 2026-05-05; no funding, OI, or liquidations)
order book and L4 from 2026-05-05; no funding, OI, or liquidations)
- `client.lighter` - Lighter mainnet
- `client.rh_lighter` - Lighter on Robinhood Chain (USDG-quoted perps and
spot; trades and liquidations from 2026-06-26; order book, OI and
Expand All @@ -45,10 +46,16 @@ class Client:
`client.hyperliquid.hip3.positions`, `client.lighter.positions` and
`client.rh_lighter.positions`.

Cross-venue: `client.symbols` (the public symbol universe),
Cross-venue: `client.capabilities()` (what each venue serves, per
datatype), `client.symbols` (the public symbol universe),
`client.data_quality`, and `client.webhooks` (push delivery; verify
deliveries with :class:`~oxarchive.WebhookVerifier`).

Every request sends the API version this SDK is written against
(``0xArchive-Version: 2026-10-01``). Errors raise
:class:`~oxarchive.OxArchiveError`, whose ``error_code`` is the stable code
to branch on.

Example:
>>> from oxarchive import Client
>>>
Expand All @@ -58,7 +65,7 @@ class Client:
>>> hl_orderbook = client.hyperliquid.orderbook.get("BTC")
>>> print(f"BTC mid price: {hl_orderbook.mid_price}")
>>>
>>> # Lighter.xyz data
>>> # Lighter data
>>> lighter_orderbook = client.lighter.orderbook.get("BTC")
>>>
>>> # Lighter on Robinhood Chain
Expand All @@ -68,10 +75,14 @@ class Client:
>>> positions = client.hyperliquid.positions.get("0x...")
>>>
>>> # Hyperliquid HIP-3 data
>>> hip3_orderbook = client.hyperliquid.hip3.orderbook.get("km:US500")
>>> hip3_orderbook = client.hyperliquid.hip3.orderbook.get("xyz:TSLA")
>>>
>>> # Get historical snapshots
>>> history = client.hyperliquid.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
>>> # Order book history for the last hour
>>> from datetime import datetime, timedelta, timezone
>>> now = datetime.now(timezone.utc)
>>> history = client.hyperliquid.orderbook.history(
... "ETH", start=now - timedelta(hours=1), end=now
... )
>>>
>>> # List all instruments
>>> instruments = client.hyperliquid.instruments.list()
Expand Down Expand Up @@ -125,20 +136,24 @@ def __init__(

# Exchange-specific clients (recommended)
self.hyperliquid = HyperliquidClient(self._http)
"""Hyperliquid exchange data (orderbook, trades, funding, OI from April 2023)"""
"""Hyperliquid exchange data. Order book from 2023-04-15, trades from
2023-04-15 03:31 UTC, funding and open interest from 2023-05-20 02:50 UTC;
``client.capabilities()`` lists every dataset's first instant."""

self.spot = SpotClient(self._http)
"""Hyperliquid spot pairs. Trades and candles from 2025-03-22; orderbook, L4,
TWAP, and orders live from 2026-05-05. No funding, OI, or liquidations."""
"""Hyperliquid spot pairs. Trades and candles from 2025-03-22; order book
and L4 from 2026-05-05 (PURR-USDC L4 from 2026-03-11). No funding, OI,
or liquidations."""

self.lighter = LighterClient(self._http)
"""Lighter mainnet data. Trade history begins January 17, 2025;
exact starts vary by market and data type."""
"""Lighter mainnet data. Trades from 2025-01-17; exact starts vary by
market and data type (``client.symbols.list()``)."""

self.rh_lighter = RhLighterClient(self._http)
"""Lighter on Robinhood Chain data (``/v1/rh-lighter``). Trades and
liquidations from 2026-06-26 20:10:26 UTC; order book, open interest
and funding from 2026-08-22 18:43 UTC. Same resources as ``client.lighter``
liquidations from 2026-06-26 20:10:26 UTC; candles from 2026-06-26
20:10 UTC; order book, open interest and funding from 2026-08-22
18:43 UTC. Same resources as ``client.lighter``
except the L3 order book and the L1 account resolver."""

# Data quality monitoring (cross-exchange)
Expand Down Expand Up @@ -179,6 +194,30 @@ def __init__(
self.open_interest = OpenInterestResource(self._http, legacy_base)
"""[DEPRECATED] Use client.hyperliquid.open_interest instead"""

def capabilities(self) -> list[Capability]:
"""
List what every venue serves, one row per venue and datatype.

Each :class:`~oxarchive.Capability` names the REST routes and
WebSocket channels for the datatype, whether it streams live and
replays, the first served instant (``available_from``), its cadence,
the largest page and the accepted intervals. The route needs no API
key and costs no credits. Per-symbol coverage is on
``client.symbols.list()``.

Example:
>>> rows = client.capabilities()
>>> replayable = {c for row in rows if row.replay for c in row.ws_channels}
>>> [row.datatype for row in rows if row.venue == "spot"]
"""
data = self._http.get("/v1/capabilities")
return [Capability.model_validate(row) for row in data["data"]]

async def acapabilities(self) -> list[Capability]:
"""Async version of :meth:`capabilities`."""
data = await self._http.aget("/v1/capabilities")
return [Capability.model_validate(row) for row in data["data"]]

def close(self) -> None:
"""Close the HTTP client and release resources."""
self._http.close()
Expand Down
Loading
Loading