oxarchive 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
oxarchive/__init__.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ oxarchive - Official Python SDK for 0xarchive
3
+
4
+ Hyperliquid Historical Data API.
5
+
6
+ Example:
7
+ >>> from oxarchive import Client
8
+ >>>
9
+ >>> client = Client(api_key="ox_your_api_key")
10
+ >>>
11
+ >>> # Get current order book
12
+ >>> orderbook = client.orderbook.get("BTC")
13
+ >>> print(f"BTC mid price: {orderbook.mid_price}")
14
+ >>>
15
+ >>> # Get historical snapshots
16
+ >>> history = client.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
17
+ """
18
+
19
+ from .client import Client
20
+ from .types import (
21
+ OrderBook,
22
+ Trade,
23
+ Instrument,
24
+ FundingRate,
25
+ OpenInterest,
26
+ OxArchiveError,
27
+ # WebSocket types
28
+ WsChannel,
29
+ WsConnectionState,
30
+ WsSubscribed,
31
+ WsUnsubscribed,
32
+ WsPong,
33
+ WsError,
34
+ WsData,
35
+ # Replay types (Option B)
36
+ WsReplayStarted,
37
+ WsReplayPaused,
38
+ WsReplayResumed,
39
+ WsReplayCompleted,
40
+ WsReplayStopped,
41
+ WsHistoricalData,
42
+ # Stream types (Option D)
43
+ WsStreamStarted,
44
+ WsStreamProgress,
45
+ WsHistoricalBatch,
46
+ WsStreamCompleted,
47
+ WsStreamStopped,
48
+ TimestampedRecord,
49
+ )
50
+
51
+ # WebSocket client (optional import - requires websockets package)
52
+ try:
53
+ from .websocket import OxArchiveWs, WsOptions
54
+ _HAS_WEBSOCKET = True
55
+ except ImportError:
56
+ _HAS_WEBSOCKET = False
57
+ OxArchiveWs = None # type: ignore
58
+ WsOptions = None # type: ignore
59
+
60
+ __version__ = "0.1.0"
61
+
62
+ __all__ = [
63
+ # Client
64
+ "Client",
65
+ # WebSocket Client
66
+ "OxArchiveWs",
67
+ "WsOptions",
68
+ # Types
69
+ "OrderBook",
70
+ "Trade",
71
+ "Instrument",
72
+ "FundingRate",
73
+ "OpenInterest",
74
+ "OxArchiveError",
75
+ # WebSocket Types
76
+ "WsChannel",
77
+ "WsConnectionState",
78
+ "WsSubscribed",
79
+ "WsUnsubscribed",
80
+ "WsPong",
81
+ "WsError",
82
+ "WsData",
83
+ # Replay Types (Option B)
84
+ "WsReplayStarted",
85
+ "WsReplayPaused",
86
+ "WsReplayResumed",
87
+ "WsReplayCompleted",
88
+ "WsReplayStopped",
89
+ "WsHistoricalData",
90
+ # Stream Types (Option D)
91
+ "WsStreamStarted",
92
+ "WsStreamProgress",
93
+ "WsHistoricalBatch",
94
+ "WsStreamCompleted",
95
+ "WsStreamStopped",
96
+ "TimestampedRecord",
97
+ ]
oxarchive/client.py ADDED
@@ -0,0 +1,110 @@
1
+ """0xarchive API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ from .http import HttpClient
8
+ from .resources import (
9
+ OrderBookResource,
10
+ TradesResource,
11
+ InstrumentsResource,
12
+ FundingResource,
13
+ OpenInterestResource,
14
+ )
15
+
16
+ DEFAULT_BASE_URL = "https://api.0xarchive.io"
17
+ DEFAULT_TIMEOUT = 30.0
18
+
19
+
20
+ class Client:
21
+ """
22
+ 0xarchive API client.
23
+
24
+ Example:
25
+ >>> from oxarchive import Client
26
+ >>>
27
+ >>> client = Client(api_key="ox_your_api_key")
28
+ >>>
29
+ >>> # Get current order book
30
+ >>> orderbook = client.orderbook.get("BTC")
31
+ >>> print(f"BTC mid price: {orderbook.mid_price}")
32
+ >>>
33
+ >>> # Get historical snapshots
34
+ >>> history = client.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
35
+ >>>
36
+ >>> # List all instruments
37
+ >>> instruments = client.instruments.list()
38
+
39
+ Async example:
40
+ >>> import asyncio
41
+ >>> from oxarchive import Client
42
+ >>>
43
+ >>> async def main():
44
+ ... client = Client(api_key="ox_your_api_key")
45
+ ... orderbook = await client.orderbook.aget("BTC")
46
+ ... print(f"BTC mid price: {orderbook.mid_price}")
47
+ ... await client.aclose()
48
+ >>>
49
+ >>> asyncio.run(main())
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ api_key: str,
55
+ *,
56
+ base_url: Optional[str] = None,
57
+ timeout: Optional[float] = None,
58
+ ):
59
+ """
60
+ Create a new 0xarchive client.
61
+
62
+ Args:
63
+ api_key: Your 0xarchive API key
64
+ base_url: Base URL for the API (defaults to https://api.0xarchive.io)
65
+ timeout: Request timeout in seconds (defaults to 30.0)
66
+ """
67
+ if not api_key:
68
+ raise ValueError("API key is required. Get one at https://0xarchive.io/signup")
69
+
70
+ self._http = HttpClient(
71
+ base_url=base_url or DEFAULT_BASE_URL,
72
+ api_key=api_key,
73
+ timeout=timeout or DEFAULT_TIMEOUT,
74
+ )
75
+
76
+ # Initialize resource namespaces
77
+ self.orderbook = OrderBookResource(self._http)
78
+ """Order book data (L2 snapshots from April 2023)"""
79
+
80
+ self.trades = TradesResource(self._http)
81
+ """Trade/fill history"""
82
+
83
+ self.instruments = InstrumentsResource(self._http)
84
+ """Trading instruments metadata"""
85
+
86
+ self.funding = FundingResource(self._http)
87
+ """Funding rates"""
88
+
89
+ self.open_interest = OpenInterestResource(self._http)
90
+ """Open interest"""
91
+
92
+ def close(self) -> None:
93
+ """Close the HTTP client and release resources."""
94
+ self._http.close()
95
+
96
+ async def aclose(self) -> None:
97
+ """Close the async HTTP client and release resources."""
98
+ await self._http.aclose()
99
+
100
+ def __enter__(self) -> "Client":
101
+ return self
102
+
103
+ def __exit__(self, *args) -> None:
104
+ self.close()
105
+
106
+ async def __aenter__(self) -> "Client":
107
+ return self
108
+
109
+ async def __aexit__(self, *args) -> None:
110
+ await self.aclose()
oxarchive/http.py ADDED
@@ -0,0 +1,108 @@
1
+ """HTTP client for the 0xarchive API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional, TypeVar, Type
6
+ import httpx
7
+ from pydantic import BaseModel
8
+
9
+ from .types import OxArchiveError
10
+
11
+ T = TypeVar("T", bound=BaseModel)
12
+
13
+
14
+ class HttpClient:
15
+ """Internal HTTP client for making API requests."""
16
+
17
+ def __init__(self, base_url: str, api_key: str, timeout: float):
18
+ self.base_url = base_url.rstrip("/")
19
+ self.api_key = api_key
20
+ self.timeout = timeout
21
+ self._client: Optional[httpx.Client] = None
22
+ self._async_client: Optional[httpx.AsyncClient] = None
23
+
24
+ def _get_headers(self) -> dict[str, str]:
25
+ return {
26
+ "X-API-Key": self.api_key,
27
+ "Content-Type": "application/json",
28
+ }
29
+
30
+ @property
31
+ def client(self) -> httpx.Client:
32
+ """Get or create the sync HTTP client."""
33
+ if self._client is None:
34
+ self._client = httpx.Client(
35
+ base_url=self.base_url,
36
+ headers=self._get_headers(),
37
+ timeout=self.timeout,
38
+ )
39
+ return self._client
40
+
41
+ @property
42
+ def async_client(self) -> httpx.AsyncClient:
43
+ """Get or create the async HTTP client."""
44
+ if self._async_client is None:
45
+ self._async_client = httpx.AsyncClient(
46
+ base_url=self.base_url,
47
+ headers=self._get_headers(),
48
+ timeout=self.timeout,
49
+ )
50
+ return self._async_client
51
+
52
+ def close(self) -> None:
53
+ """Close the HTTP clients."""
54
+ if self._client is not None:
55
+ self._client.close()
56
+ self._client = None
57
+ if self._async_client is not None:
58
+ # Note: async client should be closed with await
59
+ pass
60
+
61
+ async def aclose(self) -> None:
62
+ """Close the async HTTP client."""
63
+ if self._async_client is not None:
64
+ await self._async_client.aclose()
65
+ self._async_client = None
66
+
67
+ def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
68
+ """Handle the API response and raise errors if needed."""
69
+ try:
70
+ data = response.json()
71
+ except Exception:
72
+ raise OxArchiveError(
73
+ f"Invalid JSON response: {response.text[:200]}",
74
+ response.status_code,
75
+ )
76
+
77
+ if not response.is_success:
78
+ error_msg = data.get("error", f"Request failed with status {response.status_code}")
79
+ request_id = data.get("request_id")
80
+ raise OxArchiveError(error_msg, response.status_code, request_id)
81
+
82
+ return data
83
+
84
+ def get(
85
+ self,
86
+ path: str,
87
+ params: Optional[dict[str, Any]] = None,
88
+ ) -> dict[str, Any]:
89
+ """Make a synchronous GET request."""
90
+ # Filter out None values from params
91
+ if params:
92
+ params = {k: v for k, v in params.items() if v is not None}
93
+
94
+ response = self.client.get(path, params=params)
95
+ return self._handle_response(response)
96
+
97
+ async def aget(
98
+ self,
99
+ path: str,
100
+ params: Optional[dict[str, Any]] = None,
101
+ ) -> dict[str, Any]:
102
+ """Make an asynchronous GET request."""
103
+ # Filter out None values from params
104
+ if params:
105
+ params = {k: v for k, v in params.items() if v is not None}
106
+
107
+ response = await self.async_client.get(path, params=params)
108
+ return self._handle_response(response)
@@ -0,0 +1,15 @@
1
+ """Resource modules."""
2
+
3
+ from .orderbook import OrderBookResource
4
+ from .trades import TradesResource
5
+ from .instruments import InstrumentsResource
6
+ from .funding import FundingResource
7
+ from .openinterest import OpenInterestResource
8
+
9
+ __all__ = [
10
+ "OrderBookResource",
11
+ "TradesResource",
12
+ "InstrumentsResource",
13
+ "FundingResource",
14
+ "OpenInterestResource",
15
+ ]
@@ -0,0 +1,113 @@
1
+ """Funding rates API resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Optional
7
+
8
+ from ..http import HttpClient
9
+ from ..types import FundingRate, Timestamp
10
+
11
+
12
+ class FundingResource:
13
+ """
14
+ Funding rates API resource.
15
+
16
+ Example:
17
+ >>> # Get current funding rate
18
+ >>> current = client.funding.current("BTC")
19
+ >>>
20
+ >>> # Get funding rate history
21
+ >>> history = client.funding.history("ETH", start="2024-01-01", end="2024-01-07")
22
+ """
23
+
24
+ def __init__(self, http: HttpClient):
25
+ self._http = http
26
+
27
+ def _convert_timestamp(self, ts: Optional[Timestamp]) -> Optional[int]:
28
+ """Convert timestamp to Unix milliseconds."""
29
+ if ts is None:
30
+ return None
31
+ if isinstance(ts, int):
32
+ return ts
33
+ if isinstance(ts, datetime):
34
+ return int(ts.timestamp() * 1000)
35
+ if isinstance(ts, str):
36
+ try:
37
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
38
+ return int(dt.timestamp() * 1000)
39
+ except ValueError:
40
+ return int(ts)
41
+ return None
42
+
43
+ def history(
44
+ self,
45
+ coin: str,
46
+ *,
47
+ start: Optional[Timestamp] = None,
48
+ end: Optional[Timestamp] = None,
49
+ limit: Optional[int] = None,
50
+ offset: Optional[int] = None,
51
+ ) -> list[FundingRate]:
52
+ """
53
+ Get funding rate history for a coin.
54
+
55
+ Args:
56
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
57
+ start: Start timestamp
58
+ end: End timestamp
59
+ limit: Maximum number of results
60
+ offset: Number of results to skip
61
+
62
+ Returns:
63
+ List of funding rate records
64
+ """
65
+ data = self._http.get(
66
+ f"/v1/funding/{coin.upper()}",
67
+ params={
68
+ "start": self._convert_timestamp(start),
69
+ "end": self._convert_timestamp(end),
70
+ "limit": limit,
71
+ "offset": offset,
72
+ },
73
+ )
74
+ return [FundingRate.model_validate(item) for item in data["data"]]
75
+
76
+ async def ahistory(
77
+ self,
78
+ coin: str,
79
+ *,
80
+ start: Optional[Timestamp] = None,
81
+ end: Optional[Timestamp] = None,
82
+ limit: Optional[int] = None,
83
+ offset: Optional[int] = None,
84
+ ) -> list[FundingRate]:
85
+ """Async version of history()."""
86
+ data = await self._http.aget(
87
+ f"/v1/funding/{coin.upper()}",
88
+ params={
89
+ "start": self._convert_timestamp(start),
90
+ "end": self._convert_timestamp(end),
91
+ "limit": limit,
92
+ "offset": offset,
93
+ },
94
+ )
95
+ return [FundingRate.model_validate(item) for item in data["data"]]
96
+
97
+ def current(self, coin: str) -> FundingRate:
98
+ """
99
+ Get current funding rate for a coin.
100
+
101
+ Args:
102
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
103
+
104
+ Returns:
105
+ Current funding rate
106
+ """
107
+ data = self._http.get(f"/v1/funding/{coin.upper()}/current")
108
+ return FundingRate.model_validate(data["data"])
109
+
110
+ async def acurrent(self, coin: str) -> FundingRate:
111
+ """Async version of current()."""
112
+ data = await self._http.aget(f"/v1/funding/{coin.upper()}/current")
113
+ return FundingRate.model_validate(data["data"])
@@ -0,0 +1,55 @@
1
+ """Instruments API resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..http import HttpClient
6
+ from ..types import Instrument
7
+
8
+
9
+ class InstrumentsResource:
10
+ """
11
+ Instruments API resource.
12
+
13
+ Example:
14
+ >>> # List all instruments
15
+ >>> instruments = client.instruments.list()
16
+ >>>
17
+ >>> # Get specific instrument
18
+ >>> btc = client.instruments.get("BTC")
19
+ """
20
+
21
+ def __init__(self, http: HttpClient):
22
+ self._http = http
23
+
24
+ def list(self) -> list[Instrument]:
25
+ """
26
+ List all available trading instruments.
27
+
28
+ Returns:
29
+ List of instruments
30
+ """
31
+ data = self._http.get("/v1/instruments")
32
+ return [Instrument.model_validate(item) for item in data["data"]]
33
+
34
+ async def alist(self) -> list[Instrument]:
35
+ """Async version of list()."""
36
+ data = await self._http.aget("/v1/instruments")
37
+ return [Instrument.model_validate(item) for item in data["data"]]
38
+
39
+ def get(self, coin: str) -> Instrument:
40
+ """
41
+ Get a specific instrument by coin symbol.
42
+
43
+ Args:
44
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
45
+
46
+ Returns:
47
+ Instrument details
48
+ """
49
+ data = self._http.get(f"/v1/instruments/{coin.upper()}")
50
+ return Instrument.model_validate(data["data"])
51
+
52
+ async def aget(self, coin: str) -> Instrument:
53
+ """Async version of get()."""
54
+ data = await self._http.aget(f"/v1/instruments/{coin.upper()}")
55
+ return Instrument.model_validate(data["data"])
@@ -0,0 +1,113 @@
1
+ """Open interest API resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Optional
7
+
8
+ from ..http import HttpClient
9
+ from ..types import OpenInterest, Timestamp
10
+
11
+
12
+ class OpenInterestResource:
13
+ """
14
+ Open interest API resource.
15
+
16
+ Example:
17
+ >>> # Get current open interest
18
+ >>> current = client.open_interest.current("BTC")
19
+ >>>
20
+ >>> # Get open interest history
21
+ >>> history = client.open_interest.history("ETH", start="2024-01-01", end="2024-01-07")
22
+ """
23
+
24
+ def __init__(self, http: HttpClient):
25
+ self._http = http
26
+
27
+ def _convert_timestamp(self, ts: Optional[Timestamp]) -> Optional[int]:
28
+ """Convert timestamp to Unix milliseconds."""
29
+ if ts is None:
30
+ return None
31
+ if isinstance(ts, int):
32
+ return ts
33
+ if isinstance(ts, datetime):
34
+ return int(ts.timestamp() * 1000)
35
+ if isinstance(ts, str):
36
+ try:
37
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
38
+ return int(dt.timestamp() * 1000)
39
+ except ValueError:
40
+ return int(ts)
41
+ return None
42
+
43
+ def history(
44
+ self,
45
+ coin: str,
46
+ *,
47
+ start: Optional[Timestamp] = None,
48
+ end: Optional[Timestamp] = None,
49
+ limit: Optional[int] = None,
50
+ offset: Optional[int] = None,
51
+ ) -> list[OpenInterest]:
52
+ """
53
+ Get open interest history for a coin.
54
+
55
+ Args:
56
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
57
+ start: Start timestamp
58
+ end: End timestamp
59
+ limit: Maximum number of results
60
+ offset: Number of results to skip
61
+
62
+ Returns:
63
+ List of open interest records
64
+ """
65
+ data = self._http.get(
66
+ f"/v1/openinterest/{coin.upper()}",
67
+ params={
68
+ "start": self._convert_timestamp(start),
69
+ "end": self._convert_timestamp(end),
70
+ "limit": limit,
71
+ "offset": offset,
72
+ },
73
+ )
74
+ return [OpenInterest.model_validate(item) for item in data["data"]]
75
+
76
+ async def ahistory(
77
+ self,
78
+ coin: str,
79
+ *,
80
+ start: Optional[Timestamp] = None,
81
+ end: Optional[Timestamp] = None,
82
+ limit: Optional[int] = None,
83
+ offset: Optional[int] = None,
84
+ ) -> list[OpenInterest]:
85
+ """Async version of history()."""
86
+ data = await self._http.aget(
87
+ f"/v1/openinterest/{coin.upper()}",
88
+ params={
89
+ "start": self._convert_timestamp(start),
90
+ "end": self._convert_timestamp(end),
91
+ "limit": limit,
92
+ "offset": offset,
93
+ },
94
+ )
95
+ return [OpenInterest.model_validate(item) for item in data["data"]]
96
+
97
+ def current(self, coin: str) -> OpenInterest:
98
+ """
99
+ Get current open interest for a coin.
100
+
101
+ Args:
102
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
103
+
104
+ Returns:
105
+ Current open interest
106
+ """
107
+ data = self._http.get(f"/v1/openinterest/{coin.upper()}/current")
108
+ return OpenInterest.model_validate(data["data"])
109
+
110
+ async def acurrent(self, coin: str) -> OpenInterest:
111
+ """Async version of current()."""
112
+ data = await self._http.aget(f"/v1/openinterest/{coin.upper()}/current")
113
+ return OpenInterest.model_validate(data["data"])