oxarchive 0.1.0__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,101 @@
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
+ Candle,
24
+ CandleInterval,
25
+ Instrument,
26
+ FundingRate,
27
+ OpenInterest,
28
+ OxArchiveError,
29
+ # WebSocket types
30
+ WsChannel,
31
+ WsConnectionState,
32
+ WsSubscribed,
33
+ WsUnsubscribed,
34
+ WsPong,
35
+ WsError,
36
+ WsData,
37
+ # Replay types (Option B)
38
+ WsReplayStarted,
39
+ WsReplayPaused,
40
+ WsReplayResumed,
41
+ WsReplayCompleted,
42
+ WsReplayStopped,
43
+ WsHistoricalData,
44
+ # Stream types (Option D)
45
+ WsStreamStarted,
46
+ WsStreamProgress,
47
+ WsHistoricalBatch,
48
+ WsStreamCompleted,
49
+ WsStreamStopped,
50
+ TimestampedRecord,
51
+ )
52
+
53
+ # WebSocket client (optional import - requires websockets package)
54
+ try:
55
+ from .websocket import OxArchiveWs, WsOptions
56
+ _HAS_WEBSOCKET = True
57
+ except ImportError:
58
+ _HAS_WEBSOCKET = False
59
+ OxArchiveWs = None # type: ignore
60
+ WsOptions = None # type: ignore
61
+
62
+ __version__ = "0.1.0"
63
+
64
+ __all__ = [
65
+ # Client
66
+ "Client",
67
+ # WebSocket Client
68
+ "OxArchiveWs",
69
+ "WsOptions",
70
+ # Types
71
+ "OrderBook",
72
+ "Trade",
73
+ "Candle",
74
+ "CandleInterval",
75
+ "Instrument",
76
+ "FundingRate",
77
+ "OpenInterest",
78
+ "OxArchiveError",
79
+ # WebSocket Types
80
+ "WsChannel",
81
+ "WsConnectionState",
82
+ "WsSubscribed",
83
+ "WsUnsubscribed",
84
+ "WsPong",
85
+ "WsError",
86
+ "WsData",
87
+ # Replay Types (Option B)
88
+ "WsReplayStarted",
89
+ "WsReplayPaused",
90
+ "WsReplayResumed",
91
+ "WsReplayCompleted",
92
+ "WsReplayStopped",
93
+ "WsHistoricalData",
94
+ # Stream Types (Option D)
95
+ "WsStreamStarted",
96
+ "WsStreamProgress",
97
+ "WsHistoricalBatch",
98
+ "WsStreamCompleted",
99
+ "WsStreamStopped",
100
+ "TimestampedRecord",
101
+ ]
oxarchive/client.py ADDED
@@ -0,0 +1,114 @@
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
+ CandlesResource,
12
+ InstrumentsResource,
13
+ FundingResource,
14
+ OpenInterestResource,
15
+ )
16
+
17
+ DEFAULT_BASE_URL = "https://api.0xarchive.io"
18
+ DEFAULT_TIMEOUT = 30.0
19
+
20
+
21
+ class Client:
22
+ """
23
+ 0xarchive API client.
24
+
25
+ Example:
26
+ >>> from oxarchive import Client
27
+ >>>
28
+ >>> client = Client(api_key="ox_your_api_key")
29
+ >>>
30
+ >>> # Get current order book
31
+ >>> orderbook = client.orderbook.get("BTC")
32
+ >>> print(f"BTC mid price: {orderbook.mid_price}")
33
+ >>>
34
+ >>> # Get historical snapshots
35
+ >>> history = client.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
36
+ >>>
37
+ >>> # List all instruments
38
+ >>> instruments = client.instruments.list()
39
+
40
+ Async example:
41
+ >>> import asyncio
42
+ >>> from oxarchive import Client
43
+ >>>
44
+ >>> async def main():
45
+ ... client = Client(api_key="ox_your_api_key")
46
+ ... orderbook = await client.orderbook.aget("BTC")
47
+ ... print(f"BTC mid price: {orderbook.mid_price}")
48
+ ... await client.aclose()
49
+ >>>
50
+ >>> asyncio.run(main())
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ api_key: str,
56
+ *,
57
+ base_url: Optional[str] = None,
58
+ timeout: Optional[float] = None,
59
+ ):
60
+ """
61
+ Create a new 0xarchive client.
62
+
63
+ Args:
64
+ api_key: Your 0xarchive API key
65
+ base_url: Base URL for the API (defaults to https://api.0xarchive.io)
66
+ timeout: Request timeout in seconds (defaults to 30.0)
67
+ """
68
+ if not api_key:
69
+ raise ValueError("API key is required. Get one at https://0xarchive.io/signup")
70
+
71
+ self._http = HttpClient(
72
+ base_url=base_url or DEFAULT_BASE_URL,
73
+ api_key=api_key,
74
+ timeout=timeout or DEFAULT_TIMEOUT,
75
+ )
76
+
77
+ # Initialize resource namespaces
78
+ self.orderbook = OrderBookResource(self._http)
79
+ """Order book data (L2 snapshots from April 2023)"""
80
+
81
+ self.trades = TradesResource(self._http)
82
+ """Trade/fill history"""
83
+
84
+ self.candles = CandlesResource(self._http)
85
+ """OHLCV candles"""
86
+
87
+ self.instruments = InstrumentsResource(self._http)
88
+ """Trading instruments metadata"""
89
+
90
+ self.funding = FundingResource(self._http)
91
+ """Funding rates"""
92
+
93
+ self.open_interest = OpenInterestResource(self._http)
94
+ """Open interest"""
95
+
96
+ def close(self) -> None:
97
+ """Close the HTTP client and release resources."""
98
+ self._http.close()
99
+
100
+ async def aclose(self) -> None:
101
+ """Close the async HTTP client and release resources."""
102
+ await self._http.aclose()
103
+
104
+ def __enter__(self) -> "Client":
105
+ return self
106
+
107
+ def __exit__(self, *args) -> None:
108
+ self.close()
109
+
110
+ async def __aenter__(self) -> "Client":
111
+ return self
112
+
113
+ async def __aexit__(self, *args) -> None:
114
+ 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,17 @@
1
+ """Resource modules."""
2
+
3
+ from .orderbook import OrderBookResource
4
+ from .trades import TradesResource
5
+ from .candles import CandlesResource
6
+ from .instruments import InstrumentsResource
7
+ from .funding import FundingResource
8
+ from .openinterest import OpenInterestResource
9
+
10
+ __all__ = [
11
+ "OrderBookResource",
12
+ "TradesResource",
13
+ "CandlesResource",
14
+ "InstrumentsResource",
15
+ "FundingResource",
16
+ "OpenInterestResource",
17
+ ]
@@ -0,0 +1,100 @@
1
+ """Candles 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 Candle, CandleInterval, Timestamp
10
+
11
+
12
+ class CandlesResource:
13
+ """
14
+ Candles (OHLCV) API resource.
15
+
16
+ Example:
17
+ >>> # Get hourly candles
18
+ >>> candles = client.candles.list("BTC", interval="1h")
19
+ >>>
20
+ >>> # Get daily candles for a date range
21
+ >>> daily = client.candles.list("ETH", interval="1d", start="2024-01-01", end="2024-01-31")
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 list(
44
+ self,
45
+ coin: str,
46
+ *,
47
+ interval: Optional[CandleInterval] = None,
48
+ start: Optional[Timestamp] = None,
49
+ end: Optional[Timestamp] = None,
50
+ limit: Optional[int] = None,
51
+ offset: Optional[int] = None,
52
+ ) -> list[Candle]:
53
+ """
54
+ Get OHLCV candles for a coin.
55
+
56
+ Args:
57
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
58
+ interval: Candle interval ('1m', '5m', '15m', '1h', '4h', '1d')
59
+ start: Start timestamp
60
+ end: End timestamp
61
+ limit: Maximum number of results
62
+ offset: Number of results to skip
63
+
64
+ Returns:
65
+ List of candles
66
+ """
67
+ data = self._http.get(
68
+ f"/v1/candles/{coin.upper()}",
69
+ params={
70
+ "interval": interval,
71
+ "start": self._convert_timestamp(start),
72
+ "end": self._convert_timestamp(end),
73
+ "limit": limit,
74
+ "offset": offset,
75
+ },
76
+ )
77
+ return [Candle.model_validate(item) for item in data["data"]]
78
+
79
+ async def alist(
80
+ self,
81
+ coin: str,
82
+ *,
83
+ interval: Optional[CandleInterval] = None,
84
+ start: Optional[Timestamp] = None,
85
+ end: Optional[Timestamp] = None,
86
+ limit: Optional[int] = None,
87
+ offset: Optional[int] = None,
88
+ ) -> list[Candle]:
89
+ """Async version of list()."""
90
+ data = await self._http.aget(
91
+ f"/v1/candles/{coin.upper()}",
92
+ params={
93
+ "interval": interval,
94
+ "start": self._convert_timestamp(start),
95
+ "end": self._convert_timestamp(end),
96
+ "limit": limit,
97
+ "offset": offset,
98
+ },
99
+ )
100
+ return [Candle.model_validate(item) for item in data["data"]]
@@ -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"])