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.
@@ -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"])
@@ -0,0 +1,148 @@
1
+ """Order book API resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Optional, Union
7
+
8
+ from ..http import HttpClient
9
+ from ..types import OrderBook, Timestamp
10
+
11
+
12
+ class OrderBookResource:
13
+ """
14
+ Order book API resource.
15
+
16
+ Example:
17
+ >>> # Get current order book
18
+ >>> orderbook = client.orderbook.get("BTC")
19
+ >>>
20
+ >>> # Get order book at specific timestamp
21
+ >>> historical = client.orderbook.get("ETH", timestamp=1704067200000)
22
+ >>>
23
+ >>> # Get order book history
24
+ >>> history = client.orderbook.history("BTC", start="2024-01-01", end="2024-01-02")
25
+ """
26
+
27
+ def __init__(self, http: HttpClient):
28
+ self._http = http
29
+
30
+ def _convert_timestamp(self, ts: Optional[Timestamp]) -> Optional[int]:
31
+ """Convert timestamp to Unix milliseconds."""
32
+ if ts is None:
33
+ return None
34
+ if isinstance(ts, int):
35
+ return ts
36
+ if isinstance(ts, datetime):
37
+ return int(ts.timestamp() * 1000)
38
+ if isinstance(ts, str):
39
+ # Try parsing ISO format
40
+ try:
41
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
42
+ return int(dt.timestamp() * 1000)
43
+ except ValueError:
44
+ return int(ts)
45
+ return None
46
+
47
+ def get(
48
+ self,
49
+ coin: str,
50
+ *,
51
+ timestamp: Optional[Timestamp] = None,
52
+ depth: Optional[int] = None,
53
+ ) -> OrderBook:
54
+ """
55
+ Get order book snapshot for a coin.
56
+
57
+ Args:
58
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
59
+ timestamp: Optional timestamp to get historical snapshot
60
+ depth: Number of price levels to return per side
61
+
62
+ Returns:
63
+ Order book snapshot
64
+ """
65
+ data = self._http.get(
66
+ f"/v1/orderbook/{coin.upper()}",
67
+ params={
68
+ "timestamp": self._convert_timestamp(timestamp),
69
+ "depth": depth,
70
+ },
71
+ )
72
+ return OrderBook.model_validate(data["data"])
73
+
74
+ async def aget(
75
+ self,
76
+ coin: str,
77
+ *,
78
+ timestamp: Optional[Timestamp] = None,
79
+ depth: Optional[int] = None,
80
+ ) -> OrderBook:
81
+ """Async version of get()."""
82
+ data = await self._http.aget(
83
+ f"/v1/orderbook/{coin.upper()}",
84
+ params={
85
+ "timestamp": self._convert_timestamp(timestamp),
86
+ "depth": depth,
87
+ },
88
+ )
89
+ return OrderBook.model_validate(data["data"])
90
+
91
+ def history(
92
+ self,
93
+ coin: str,
94
+ *,
95
+ start: Optional[Timestamp] = None,
96
+ end: Optional[Timestamp] = None,
97
+ limit: Optional[int] = None,
98
+ offset: Optional[int] = None,
99
+ depth: Optional[int] = None,
100
+ ) -> list[OrderBook]:
101
+ """
102
+ Get historical order book snapshots.
103
+
104
+ Args:
105
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
106
+ start: Start timestamp
107
+ end: End timestamp
108
+ limit: Maximum number of results
109
+ offset: Number of results to skip
110
+ depth: Number of price levels per side
111
+
112
+ Returns:
113
+ List of order book snapshots
114
+ """
115
+ data = self._http.get(
116
+ f"/v1/orderbook/{coin.upper()}/history",
117
+ params={
118
+ "start": self._convert_timestamp(start),
119
+ "end": self._convert_timestamp(end),
120
+ "limit": limit,
121
+ "offset": offset,
122
+ "depth": depth,
123
+ },
124
+ )
125
+ return [OrderBook.model_validate(item) for item in data["data"]]
126
+
127
+ async def ahistory(
128
+ self,
129
+ coin: str,
130
+ *,
131
+ start: Optional[Timestamp] = None,
132
+ end: Optional[Timestamp] = None,
133
+ limit: Optional[int] = None,
134
+ offset: Optional[int] = None,
135
+ depth: Optional[int] = None,
136
+ ) -> list[OrderBook]:
137
+ """Async version of history()."""
138
+ data = await self._http.aget(
139
+ f"/v1/orderbook/{coin.upper()}/history",
140
+ params={
141
+ "start": self._convert_timestamp(start),
142
+ "end": self._convert_timestamp(end),
143
+ "limit": limit,
144
+ "offset": offset,
145
+ "depth": depth,
146
+ },
147
+ )
148
+ return [OrderBook.model_validate(item) for item in data["data"]]
@@ -0,0 +1,125 @@
1
+ """Trades API resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Literal, Optional
7
+
8
+ from ..http import HttpClient
9
+ from ..types import Trade, Timestamp
10
+
11
+
12
+ class TradesResource:
13
+ """
14
+ Trades API resource.
15
+
16
+ Example:
17
+ >>> # Get recent trades
18
+ >>> trades = client.trades.recent("BTC")
19
+ >>>
20
+ >>> # Get trade history with time range
21
+ >>> history = client.trades.list("ETH", start="2024-01-01", end="2024-01-02")
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
+ start: Optional[Timestamp] = None,
48
+ end: Optional[Timestamp] = None,
49
+ limit: Optional[int] = None,
50
+ offset: Optional[int] = None,
51
+ side: Optional[Literal["buy", "sell"]] = None,
52
+ ) -> list[Trade]:
53
+ """
54
+ Get trade history for a coin.
55
+
56
+ Args:
57
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
58
+ start: Start timestamp
59
+ end: End timestamp
60
+ limit: Maximum number of results
61
+ offset: Number of results to skip
62
+ side: Filter by trade side
63
+
64
+ Returns:
65
+ List of trades
66
+ """
67
+ data = self._http.get(
68
+ f"/v1/trades/{coin.upper()}",
69
+ params={
70
+ "start": self._convert_timestamp(start),
71
+ "end": self._convert_timestamp(end),
72
+ "limit": limit,
73
+ "offset": offset,
74
+ "side": side,
75
+ },
76
+ )
77
+ return [Trade.model_validate(item) for item in data["data"]]
78
+
79
+ async def alist(
80
+ self,
81
+ coin: str,
82
+ *,
83
+ start: Optional[Timestamp] = None,
84
+ end: Optional[Timestamp] = None,
85
+ limit: Optional[int] = None,
86
+ offset: Optional[int] = None,
87
+ side: Optional[Literal["buy", "sell"]] = None,
88
+ ) -> list[Trade]:
89
+ """Async version of list()."""
90
+ data = await self._http.aget(
91
+ f"/v1/trades/{coin.upper()}",
92
+ params={
93
+ "start": self._convert_timestamp(start),
94
+ "end": self._convert_timestamp(end),
95
+ "limit": limit,
96
+ "offset": offset,
97
+ "side": side,
98
+ },
99
+ )
100
+ return [Trade.model_validate(item) for item in data["data"]]
101
+
102
+ def recent(self, coin: str, limit: Optional[int] = None) -> list[Trade]:
103
+ """
104
+ Get most recent trades for a coin.
105
+
106
+ Args:
107
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
108
+ limit: Number of trades to return (default: 100)
109
+
110
+ Returns:
111
+ List of recent trades
112
+ """
113
+ data = self._http.get(
114
+ f"/v1/trades/{coin.upper()}/recent",
115
+ params={"limit": limit},
116
+ )
117
+ return [Trade.model_validate(item) for item in data["data"]]
118
+
119
+ async def arecent(self, coin: str, limit: Optional[int] = None) -> list[Trade]:
120
+ """Async version of recent()."""
121
+ data = await self._http.aget(
122
+ f"/v1/trades/{coin.upper()}/recent",
123
+ params={"limit": limit},
124
+ )
125
+ return [Trade.model_validate(item) for item in data["data"]]
oxarchive/types.py ADDED
@@ -0,0 +1,304 @@
1
+ """Type definitions for the 0xarchive SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Literal, Optional, Union
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ # =============================================================================
12
+ # Base Types
13
+ # =============================================================================
14
+
15
+
16
+ class ApiResponse(BaseModel):
17
+ """Standard API response wrapper."""
18
+
19
+ success: bool
20
+ count: int
21
+ request_id: str
22
+
23
+
24
+ # =============================================================================
25
+ # Order Book Types
26
+ # =============================================================================
27
+
28
+
29
+ class OrderBook(BaseModel):
30
+ """Order book snapshot."""
31
+
32
+ coin: str
33
+ timestamp: int
34
+ bids: list[tuple[str, str]]
35
+ asks: list[tuple[str, str]]
36
+ mid_price: str
37
+ spread: str
38
+ spread_bps: str
39
+
40
+
41
+ # =============================================================================
42
+ # Trade Types
43
+ # =============================================================================
44
+
45
+
46
+ class Trade(BaseModel):
47
+ """Trade/fill record."""
48
+
49
+ id: str
50
+ coin: str
51
+ side: Literal["buy", "sell"]
52
+ price: str
53
+ size: str
54
+ value: str
55
+ timestamp: int
56
+ trade_type: str
57
+
58
+
59
+ # =============================================================================
60
+ # Candle Types
61
+ # =============================================================================
62
+
63
+ CandleInterval = Literal["1m", "5m", "15m", "1h", "4h", "1d"]
64
+
65
+
66
+ class Candle(BaseModel):
67
+ """OHLCV candle."""
68
+
69
+ coin: str
70
+ interval: str
71
+ timestamp: int
72
+ open: str
73
+ high: str
74
+ low: str
75
+ close: str
76
+ volume: str
77
+ trades: int
78
+
79
+
80
+ # =============================================================================
81
+ # Instrument Types
82
+ # =============================================================================
83
+
84
+
85
+ class Instrument(BaseModel):
86
+ """Trading instrument metadata."""
87
+
88
+ coin: str
89
+ name: str
90
+ sz_decimals: int
91
+ max_leverage: int
92
+ only_isolated: bool
93
+ is_active: bool
94
+
95
+
96
+ # =============================================================================
97
+ # Funding Types
98
+ # =============================================================================
99
+
100
+
101
+ class FundingRate(BaseModel):
102
+ """Funding rate record."""
103
+
104
+ coin: str
105
+ funding_rate: str
106
+ premium: str
107
+ timestamp: int
108
+
109
+
110
+ # =============================================================================
111
+ # Open Interest Types
112
+ # =============================================================================
113
+
114
+
115
+ class OpenInterest(BaseModel):
116
+ """Open interest record."""
117
+
118
+ coin: str
119
+ open_interest: str
120
+ timestamp: int
121
+
122
+
123
+ # =============================================================================
124
+ # WebSocket Types
125
+ # =============================================================================
126
+
127
+ WsChannel = Literal["orderbook", "trades", "ticker", "all_tickers", "candles", "funding", "openinterest"]
128
+ WsConnectionState = Literal["connecting", "connected", "disconnected", "reconnecting"]
129
+
130
+
131
+ class WsSubscribed(BaseModel):
132
+ """Subscription confirmed from server."""
133
+
134
+ type: Literal["subscribed"]
135
+ channel: WsChannel
136
+ coin: Optional[str] = None
137
+
138
+
139
+ class WsUnsubscribed(BaseModel):
140
+ """Unsubscription confirmed from server."""
141
+
142
+ type: Literal["unsubscribed"]
143
+ channel: WsChannel
144
+ coin: Optional[str] = None
145
+
146
+
147
+ class WsPong(BaseModel):
148
+ """Pong response from server."""
149
+
150
+ type: Literal["pong"]
151
+
152
+
153
+ class WsError(BaseModel):
154
+ """Error from server."""
155
+
156
+ type: Literal["error"]
157
+ message: str
158
+
159
+
160
+ class WsData(BaseModel):
161
+ """Data message from server."""
162
+
163
+ type: Literal["data"]
164
+ channel: WsChannel
165
+ coin: str
166
+ data: dict
167
+
168
+
169
+ # =============================================================================
170
+ # WebSocket Replay Types (Option B - Like Tardis.dev)
171
+ # =============================================================================
172
+
173
+
174
+ class WsReplayStarted(BaseModel):
175
+ """Replay started response."""
176
+
177
+ type: Literal["replay_started"]
178
+ channel: WsChannel
179
+ coin: str
180
+ start: int
181
+ end: int
182
+ speed: float
183
+ total_records: int
184
+
185
+
186
+ class WsReplayPaused(BaseModel):
187
+ """Replay paused response."""
188
+
189
+ type: Literal["replay_paused"]
190
+ current_timestamp: int
191
+
192
+
193
+ class WsReplayResumed(BaseModel):
194
+ """Replay resumed response."""
195
+
196
+ type: Literal["replay_resumed"]
197
+ current_timestamp: int
198
+
199
+
200
+ class WsReplayCompleted(BaseModel):
201
+ """Replay completed response."""
202
+
203
+ type: Literal["replay_completed"]
204
+ channel: WsChannel
205
+ coin: str
206
+ records_sent: int
207
+
208
+
209
+ class WsReplayStopped(BaseModel):
210
+ """Replay stopped response."""
211
+
212
+ type: Literal["replay_stopped"]
213
+
214
+
215
+ class WsHistoricalData(BaseModel):
216
+ """Historical data point (replay mode)."""
217
+
218
+ type: Literal["historical_data"]
219
+ channel: WsChannel
220
+ coin: str
221
+ timestamp: int
222
+ data: dict
223
+
224
+
225
+ # =============================================================================
226
+ # WebSocket Bulk Stream Types (Option D - Like Databento)
227
+ # =============================================================================
228
+
229
+
230
+ class WsStreamStarted(BaseModel):
231
+ """Stream started response."""
232
+
233
+ type: Literal["stream_started"]
234
+ channel: WsChannel
235
+ coin: str
236
+ start: int
237
+ end: int
238
+ batch_size: int
239
+ total_records: int
240
+
241
+
242
+ class WsStreamProgress(BaseModel):
243
+ """Stream progress response."""
244
+
245
+ type: Literal["stream_progress"]
246
+ records_sent: int
247
+ total_records: int
248
+ progress_pct: float
249
+
250
+
251
+ class TimestampedRecord(BaseModel):
252
+ """A record with timestamp."""
253
+
254
+ timestamp: int
255
+ data: dict
256
+
257
+
258
+ class WsHistoricalBatch(BaseModel):
259
+ """Stream batch (bulk data)."""
260
+
261
+ type: Literal["historical_batch"]
262
+ channel: WsChannel
263
+ coin: str
264
+ batch_index: int
265
+ records: list[TimestampedRecord]
266
+
267
+
268
+ class WsStreamCompleted(BaseModel):
269
+ """Stream completed response."""
270
+
271
+ type: Literal["stream_completed"]
272
+ channel: WsChannel
273
+ coin: str
274
+ records_sent: int
275
+
276
+
277
+ class WsStreamStopped(BaseModel):
278
+ """Stream stopped response."""
279
+
280
+ type: Literal["stream_stopped"]
281
+
282
+
283
+ # =============================================================================
284
+ # Error Types
285
+ # =============================================================================
286
+
287
+
288
+ class OxArchiveError(Exception):
289
+ """SDK error class."""
290
+
291
+ def __init__(self, message: str, code: int, request_id: Optional[str] = None):
292
+ super().__init__(message)
293
+ self.message = message
294
+ self.code = code
295
+ self.request_id = request_id
296
+
297
+ def __str__(self) -> str:
298
+ if self.request_id:
299
+ return f"[{self.code}] {self.message} (request_id: {self.request_id})"
300
+ return f"[{self.code}] {self.message}"
301
+
302
+
303
+ # Type alias for timestamp parameters
304
+ Timestamp = Union[int, str, datetime]