oxarchive 0.3.2__tar.gz → 0.3.4__tar.gz

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,78 @@
1
+ # Tests (internal only, not published)
2
+ tests/
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+
30
+ # PyInstaller
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py,cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Environments
57
+ .env
58
+ .venv
59
+ env/
60
+ venv/
61
+ ENV/
62
+ env.bak/
63
+ venv.bak/
64
+
65
+ # mypy
66
+ .mypy_cache/
67
+ .dmypy.json
68
+ dmypy.json
69
+
70
+ # IDE
71
+ .idea/
72
+ .vscode/
73
+ *.swp
74
+ *.swo
75
+
76
+ # OS
77
+ .DS_Store
78
+ Thumbs.db
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxarchive
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Official Python SDK for 0xarchive - Hyperliquid Historical Data API
5
5
  Project-URL: Homepage, https://0xarchive.io
6
6
  Project-URL: Documentation, https://0xarchive.io/docs/sdks
@@ -28,6 +28,7 @@ Requires-Dist: websockets>=12.0; extra == 'all'
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: mypy>=1.9.0; extra == 'dev'
30
30
  Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
31
32
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
32
33
  Requires-Dist: ruff>=0.4.0; extra == 'dev'
33
34
  Provides-Extra: websocket
@@ -44,6 +45,12 @@ Official Python SDK for [0xarchive](https://0xarchive.io) - Hyperliquid Historic
44
45
  pip install oxarchive
45
46
  ```
46
47
 
48
+ For WebSocket support:
49
+
50
+ ```bash
51
+ pip install oxarchive[websocket]
52
+ ```
53
+
47
54
  ## Quick Start
48
55
 
49
56
  ```python
@@ -96,13 +103,13 @@ async with Client(api_key="ox_your_api_key") as client:
96
103
 
97
104
  ```python
98
105
  client = Client(
99
- api_key="ox_your_api_key", # Required
100
- base_url="https://api.0xarchive.io", # Optional
101
- timeout=30.0, # Optional, request timeout in seconds
106
+ api_key="ox_your_api_key", # Required
107
+ base_url="https://api.0xarchive.io", # Optional
108
+ timeout=30.0, # Optional, request timeout in seconds (default: 30.0)
102
109
  )
103
110
  ```
104
111
 
105
- ## API Reference
112
+ ## REST API Reference
106
113
 
107
114
  ### Order Book
108
115
 
@@ -116,38 +123,63 @@ historical = client.orderbook.get("BTC", timestamp=1704067200000)
116
123
  # Get with limited depth
117
124
  shallow = client.orderbook.get("BTC", depth=10)
118
125
 
119
- # Get historical snapshots
126
+ # Get historical snapshots (start and end are required)
120
127
  history = client.orderbook.history(
121
128
  "BTC",
122
129
  start="2024-01-01",
123
130
  end="2024-01-02",
124
- limit=1000
131
+ limit=1000,
132
+ depth=20 # Price levels per side
125
133
  )
134
+
135
+ # Async versions
136
+ orderbook = await client.orderbook.aget("BTC")
137
+ history = await client.orderbook.ahistory("BTC", start=..., end=...)
126
138
  ```
127
139
 
128
140
  ### Trades
129
141
 
142
+ The trades API uses cursor-based pagination for efficient retrieval of large datasets.
143
+
130
144
  ```python
131
145
  # Get recent trades
132
146
  recent = client.trades.recent("BTC", limit=100)
133
147
 
134
- # Get trade history
135
- trades = client.trades.list(
136
- "ETH",
137
- start="2024-01-01",
138
- end="2024-01-02",
139
- side="buy" # Optional: filter by side
140
- )
148
+ # Get trade history with cursor-based pagination
149
+ result = client.trades.list("ETH", start="2024-01-01", end="2024-01-02", limit=1000)
150
+ trades = result.data
151
+
152
+ # Paginate through all results
153
+ while result.next_cursor:
154
+ result = client.trades.list(
155
+ "ETH",
156
+ start="2024-01-01",
157
+ end="2024-01-02",
158
+ cursor=result.next_cursor,
159
+ limit=1000
160
+ )
161
+ trades.extend(result.data)
162
+
163
+ # Filter by side
164
+ buys = client.trades.list("BTC", start=..., end=..., side="buy")
165
+
166
+ # Async versions
167
+ recent = await client.trades.arecent("BTC")
168
+ result = await client.trades.alist("ETH", start=..., end=...)
141
169
  ```
142
170
 
143
171
  ### Instruments
144
172
 
145
173
  ```python
146
- # List all instruments
174
+ # List all trading instruments
147
175
  instruments = client.instruments.list()
148
176
 
149
- # Get specific instrument
177
+ # Get specific instrument details
150
178
  btc = client.instruments.get("BTC")
179
+
180
+ # Async versions
181
+ instruments = await client.instruments.alist()
182
+ btc = await client.instruments.aget("BTC")
151
183
  ```
152
184
 
153
185
  ### Funding Rates
@@ -156,12 +188,16 @@ btc = client.instruments.get("BTC")
156
188
  # Get current funding rate
157
189
  current = client.funding.current("BTC")
158
190
 
159
- # Get funding rate history
191
+ # Get funding rate history (start is required)
160
192
  history = client.funding.history(
161
193
  "ETH",
162
194
  start="2024-01-01",
163
195
  end="2024-01-07"
164
196
  )
197
+
198
+ # Async versions
199
+ current = await client.funding.acurrent("BTC")
200
+ history = await client.funding.ahistory("ETH", start=..., end=...)
165
201
  ```
166
202
 
167
203
  ### Open Interest
@@ -170,40 +206,32 @@ history = client.funding.history(
170
206
  # Get current open interest
171
207
  current = client.open_interest.current("BTC")
172
208
 
173
- # Get open interest history
209
+ # Get open interest history (start is required)
174
210
  history = client.open_interest.history(
175
211
  "ETH",
176
212
  start="2024-01-01",
177
213
  end="2024-01-07"
178
214
  )
215
+
216
+ # Async versions
217
+ current = await client.open_interest.acurrent("BTC")
218
+ history = await client.open_interest.ahistory("ETH", start=..., end=...)
179
219
  ```
180
220
 
181
- ## Timestamp Formats
221
+ ## WebSocket Client
182
222
 
183
- The SDK accepts timestamps in multiple formats:
223
+ The WebSocket client supports three modes: real-time streaming, historical replay, and bulk streaming.
184
224
 
185
225
  ```python
186
- from datetime import datetime
187
-
188
- # Unix milliseconds
189
- client.orderbook.get("BTC", timestamp=1704067200000)
190
-
191
- # ISO string
192
- client.orderbook.history("BTC", start="2024-01-01", end="2024-01-02")
226
+ import asyncio
227
+ from oxarchive import OxArchiveWs, WsOptions
193
228
 
194
- # datetime object
195
- client.orderbook.history("BTC", start=datetime(2024, 1, 1), end=datetime(2024, 1, 2))
229
+ ws = OxArchiveWs(WsOptions(api_key="ox_your_api_key"))
196
230
  ```
197
231
 
198
- ## WebSocket Streaming
232
+ ### Real-time Streaming
199
233
 
200
- For real-time data, install with WebSocket support:
201
-
202
- ```bash
203
- pip install oxarchive[websocket]
204
- ```
205
-
206
- ### Basic Usage
234
+ Subscribe to live market data from Hyperliquid.
207
235
 
208
236
  ```python
209
237
  import asyncio
@@ -226,24 +254,26 @@ async def main():
226
254
  ws.subscribe_trades("BTC")
227
255
  ws.subscribe_all_tickers()
228
256
 
229
- # Handle orderbook updates
257
+ # Handle real-time data
230
258
  ws.on_orderbook(lambda coin, data: print(f"{coin}: {data.mid_price}"))
231
-
232
- # Handle trade updates
233
259
  ws.on_trades(lambda coin, trades: print(f"{coin}: {len(trades)} trades"))
234
260
 
235
261
  # Keep running
236
262
  await asyncio.sleep(60)
237
263
 
238
- # Disconnect
264
+ # Unsubscribe and disconnect
265
+ ws.unsubscribe_orderbook("ETH")
239
266
  await ws.disconnect()
240
267
 
241
268
  asyncio.run(main())
242
269
  ```
243
270
 
244
- ### Historical Replay (like Tardis.dev)
271
+ ### Historical Replay
245
272
 
246
- Replay historical data with timing preserved:
273
+ Replay historical data with timing preserved. Perfect for backtesting.
274
+
275
+ > **Important:** Replay data is delivered via `on_historical_data()`, NOT `on_trades()` or `on_orderbook()`.
276
+ > The real-time callbacks only receive live market data from subscriptions.
247
277
 
248
278
  ```python
249
279
  import asyncio
@@ -253,13 +283,14 @@ from oxarchive import OxArchiveWs, WsOptions
253
283
  async def main():
254
284
  ws = OxArchiveWs(WsOptions(api_key="ox_..."))
255
285
 
256
- # Handle replay data
286
+ # Handle replay data - this is where historical records arrive
257
287
  ws.on_historical_data(lambda coin, ts, data:
258
288
  print(f"{ts}: {data['mid_price']}")
259
289
  )
260
290
 
291
+ # Replay lifecycle events
261
292
  ws.on_replay_start(lambda ch, coin, start, end, speed:
262
- print(f"Starting replay from {start} to {end} at {speed}x")
293
+ print(f"Starting replay: {ch}/{coin} at {speed}x")
263
294
  )
264
295
 
265
296
  ws.on_replay_complete(lambda ch, coin, sent:
@@ -272,7 +303,8 @@ async def main():
272
303
  await ws.replay(
273
304
  "orderbook", "BTC",
274
305
  start=int(time.time() * 1000) - 86400000, # 24 hours ago
275
- speed=10
306
+ end=int(time.time() * 1000), # Optional
307
+ speed=10 # Optional, defaults to 1x
276
308
  )
277
309
 
278
310
  # Control playback
@@ -284,9 +316,9 @@ async def main():
284
316
  asyncio.run(main())
285
317
  ```
286
318
 
287
- ### Bulk Streaming (like Databento)
319
+ ### Bulk Streaming
288
320
 
289
- Fast bulk download for data pipelines:
321
+ Fast bulk download for data pipelines. Data arrives in batches without timing delays.
290
322
 
291
323
  ```python
292
324
  import asyncio
@@ -303,7 +335,7 @@ async def main():
303
335
  )
304
336
 
305
337
  ws.on_stream_progress(lambda snapshots_sent:
306
- print(f"Sent: {snapshots_sent} snapshots")
338
+ print(f"Progress: {snapshots_sent} snapshots")
307
339
  )
308
340
 
309
341
  ws.on_stream_complete(lambda ch, coin, sent:
@@ -317,25 +349,25 @@ async def main():
317
349
  "orderbook", "ETH",
318
350
  start=int(time.time() * 1000) - 3600000, # 1 hour ago
319
351
  end=int(time.time() * 1000),
320
- batch_size=1000
352
+ batch_size=1000 # Optional, defaults to 1000
321
353
  )
322
354
 
323
- # Stop stream if needed
355
+ # Stop if needed
324
356
  await ws.stream_stop()
325
357
 
326
358
  asyncio.run(main())
327
359
  ```
328
360
 
329
- ### Configuration
361
+ ### WebSocket Configuration
330
362
 
331
363
  ```python
332
364
  ws = OxArchiveWs(WsOptions(
333
365
  api_key="ox_your_api_key",
334
366
  ws_url="wss://api.0xarchive.io/ws", # Optional
335
- auto_reconnect=True, # Auto-reconnect on disconnect
336
- reconnect_delay=1.0, # Initial reconnect delay (seconds)
337
- max_reconnect_attempts=10, # Max reconnect attempts
338
- ping_interval=30.0, # Keep-alive ping interval (seconds)
367
+ auto_reconnect=True, # Auto-reconnect on disconnect (default: True)
368
+ reconnect_delay=1.0, # Initial reconnect delay in seconds (default: 1.0)
369
+ max_reconnect_attempts=10, # Max reconnect attempts (default: 10)
370
+ ping_interval=30.0, # Keep-alive ping interval in seconds (default: 30.0)
339
371
  ))
340
372
  ```
341
373
 
@@ -348,6 +380,27 @@ ws = OxArchiveWs(WsOptions(
348
380
  | `ticker` | Price and 24h volume | Yes |
349
381
  | `all_tickers` | All market tickers | No |
350
382
 
383
+ ## Timestamp Formats
384
+
385
+ The SDK accepts timestamps in multiple formats:
386
+
387
+ ```python
388
+ from datetime import datetime
389
+
390
+ # Unix milliseconds (int)
391
+ client.orderbook.get("BTC", timestamp=1704067200000)
392
+
393
+ # ISO string
394
+ client.orderbook.history("BTC", start="2024-01-01", end="2024-01-02")
395
+
396
+ # datetime object
397
+ client.orderbook.history(
398
+ "BTC",
399
+ start=datetime(2024, 1, 1),
400
+ end=datetime(2024, 1, 2)
401
+ )
402
+ ```
403
+
351
404
  ## Error Handling
352
405
 
353
406
  ```python
@@ -368,12 +421,15 @@ except OxArchiveError as e:
368
421
  Full type hint support with Pydantic models:
369
422
 
370
423
  ```python
371
- from oxarchive import Client, OrderBook, Trade
424
+ from oxarchive import Client
425
+ from oxarchive.types import OrderBook, Trade, Instrument, FundingRate, OpenInterest
426
+ from oxarchive.resources.trades import CursorResponse
372
427
 
373
428
  client = Client(api_key="ox_your_api_key")
374
429
 
375
430
  orderbook: OrderBook = client.orderbook.get("BTC")
376
431
  trades: list[Trade] = client.trades.recent("BTC")
432
+ result: CursorResponse = client.trades.list("BTC", start=..., end=...)
377
433
  ```
378
434
 
379
435
  ## Requirements
@@ -8,6 +8,12 @@ Official Python SDK for [0xarchive](https://0xarchive.io) - Hyperliquid Historic
8
8
  pip install oxarchive
9
9
  ```
10
10
 
11
+ For WebSocket support:
12
+
13
+ ```bash
14
+ pip install oxarchive[websocket]
15
+ ```
16
+
11
17
  ## Quick Start
12
18
 
13
19
  ```python
@@ -60,13 +66,13 @@ async with Client(api_key="ox_your_api_key") as client:
60
66
 
61
67
  ```python
62
68
  client = Client(
63
- api_key="ox_your_api_key", # Required
64
- base_url="https://api.0xarchive.io", # Optional
65
- timeout=30.0, # Optional, request timeout in seconds
69
+ api_key="ox_your_api_key", # Required
70
+ base_url="https://api.0xarchive.io", # Optional
71
+ timeout=30.0, # Optional, request timeout in seconds (default: 30.0)
66
72
  )
67
73
  ```
68
74
 
69
- ## API Reference
75
+ ## REST API Reference
70
76
 
71
77
  ### Order Book
72
78
 
@@ -80,38 +86,63 @@ historical = client.orderbook.get("BTC", timestamp=1704067200000)
80
86
  # Get with limited depth
81
87
  shallow = client.orderbook.get("BTC", depth=10)
82
88
 
83
- # Get historical snapshots
89
+ # Get historical snapshots (start and end are required)
84
90
  history = client.orderbook.history(
85
91
  "BTC",
86
92
  start="2024-01-01",
87
93
  end="2024-01-02",
88
- limit=1000
94
+ limit=1000,
95
+ depth=20 # Price levels per side
89
96
  )
97
+
98
+ # Async versions
99
+ orderbook = await client.orderbook.aget("BTC")
100
+ history = await client.orderbook.ahistory("BTC", start=..., end=...)
90
101
  ```
91
102
 
92
103
  ### Trades
93
104
 
105
+ The trades API uses cursor-based pagination for efficient retrieval of large datasets.
106
+
94
107
  ```python
95
108
  # Get recent trades
96
109
  recent = client.trades.recent("BTC", limit=100)
97
110
 
98
- # Get trade history
99
- trades = client.trades.list(
100
- "ETH",
101
- start="2024-01-01",
102
- end="2024-01-02",
103
- side="buy" # Optional: filter by side
104
- )
111
+ # Get trade history with cursor-based pagination
112
+ result = client.trades.list("ETH", start="2024-01-01", end="2024-01-02", limit=1000)
113
+ trades = result.data
114
+
115
+ # Paginate through all results
116
+ while result.next_cursor:
117
+ result = client.trades.list(
118
+ "ETH",
119
+ start="2024-01-01",
120
+ end="2024-01-02",
121
+ cursor=result.next_cursor,
122
+ limit=1000
123
+ )
124
+ trades.extend(result.data)
125
+
126
+ # Filter by side
127
+ buys = client.trades.list("BTC", start=..., end=..., side="buy")
128
+
129
+ # Async versions
130
+ recent = await client.trades.arecent("BTC")
131
+ result = await client.trades.alist("ETH", start=..., end=...)
105
132
  ```
106
133
 
107
134
  ### Instruments
108
135
 
109
136
  ```python
110
- # List all instruments
137
+ # List all trading instruments
111
138
  instruments = client.instruments.list()
112
139
 
113
- # Get specific instrument
140
+ # Get specific instrument details
114
141
  btc = client.instruments.get("BTC")
142
+
143
+ # Async versions
144
+ instruments = await client.instruments.alist()
145
+ btc = await client.instruments.aget("BTC")
115
146
  ```
116
147
 
117
148
  ### Funding Rates
@@ -120,12 +151,16 @@ btc = client.instruments.get("BTC")
120
151
  # Get current funding rate
121
152
  current = client.funding.current("BTC")
122
153
 
123
- # Get funding rate history
154
+ # Get funding rate history (start is required)
124
155
  history = client.funding.history(
125
156
  "ETH",
126
157
  start="2024-01-01",
127
158
  end="2024-01-07"
128
159
  )
160
+
161
+ # Async versions
162
+ current = await client.funding.acurrent("BTC")
163
+ history = await client.funding.ahistory("ETH", start=..., end=...)
129
164
  ```
130
165
 
131
166
  ### Open Interest
@@ -134,40 +169,32 @@ history = client.funding.history(
134
169
  # Get current open interest
135
170
  current = client.open_interest.current("BTC")
136
171
 
137
- # Get open interest history
172
+ # Get open interest history (start is required)
138
173
  history = client.open_interest.history(
139
174
  "ETH",
140
175
  start="2024-01-01",
141
176
  end="2024-01-07"
142
177
  )
178
+
179
+ # Async versions
180
+ current = await client.open_interest.acurrent("BTC")
181
+ history = await client.open_interest.ahistory("ETH", start=..., end=...)
143
182
  ```
144
183
 
145
- ## Timestamp Formats
184
+ ## WebSocket Client
146
185
 
147
- The SDK accepts timestamps in multiple formats:
186
+ The WebSocket client supports three modes: real-time streaming, historical replay, and bulk streaming.
148
187
 
149
188
  ```python
150
- from datetime import datetime
151
-
152
- # Unix milliseconds
153
- client.orderbook.get("BTC", timestamp=1704067200000)
154
-
155
- # ISO string
156
- client.orderbook.history("BTC", start="2024-01-01", end="2024-01-02")
189
+ import asyncio
190
+ from oxarchive import OxArchiveWs, WsOptions
157
191
 
158
- # datetime object
159
- client.orderbook.history("BTC", start=datetime(2024, 1, 1), end=datetime(2024, 1, 2))
192
+ ws = OxArchiveWs(WsOptions(api_key="ox_your_api_key"))
160
193
  ```
161
194
 
162
- ## WebSocket Streaming
195
+ ### Real-time Streaming
163
196
 
164
- For real-time data, install with WebSocket support:
165
-
166
- ```bash
167
- pip install oxarchive[websocket]
168
- ```
169
-
170
- ### Basic Usage
197
+ Subscribe to live market data from Hyperliquid.
171
198
 
172
199
  ```python
173
200
  import asyncio
@@ -190,24 +217,26 @@ async def main():
190
217
  ws.subscribe_trades("BTC")
191
218
  ws.subscribe_all_tickers()
192
219
 
193
- # Handle orderbook updates
220
+ # Handle real-time data
194
221
  ws.on_orderbook(lambda coin, data: print(f"{coin}: {data.mid_price}"))
195
-
196
- # Handle trade updates
197
222
  ws.on_trades(lambda coin, trades: print(f"{coin}: {len(trades)} trades"))
198
223
 
199
224
  # Keep running
200
225
  await asyncio.sleep(60)
201
226
 
202
- # Disconnect
227
+ # Unsubscribe and disconnect
228
+ ws.unsubscribe_orderbook("ETH")
203
229
  await ws.disconnect()
204
230
 
205
231
  asyncio.run(main())
206
232
  ```
207
233
 
208
- ### Historical Replay (like Tardis.dev)
234
+ ### Historical Replay
209
235
 
210
- Replay historical data with timing preserved:
236
+ Replay historical data with timing preserved. Perfect for backtesting.
237
+
238
+ > **Important:** Replay data is delivered via `on_historical_data()`, NOT `on_trades()` or `on_orderbook()`.
239
+ > The real-time callbacks only receive live market data from subscriptions.
211
240
 
212
241
  ```python
213
242
  import asyncio
@@ -217,13 +246,14 @@ from oxarchive import OxArchiveWs, WsOptions
217
246
  async def main():
218
247
  ws = OxArchiveWs(WsOptions(api_key="ox_..."))
219
248
 
220
- # Handle replay data
249
+ # Handle replay data - this is where historical records arrive
221
250
  ws.on_historical_data(lambda coin, ts, data:
222
251
  print(f"{ts}: {data['mid_price']}")
223
252
  )
224
253
 
254
+ # Replay lifecycle events
225
255
  ws.on_replay_start(lambda ch, coin, start, end, speed:
226
- print(f"Starting replay from {start} to {end} at {speed}x")
256
+ print(f"Starting replay: {ch}/{coin} at {speed}x")
227
257
  )
228
258
 
229
259
  ws.on_replay_complete(lambda ch, coin, sent:
@@ -236,7 +266,8 @@ async def main():
236
266
  await ws.replay(
237
267
  "orderbook", "BTC",
238
268
  start=int(time.time() * 1000) - 86400000, # 24 hours ago
239
- speed=10
269
+ end=int(time.time() * 1000), # Optional
270
+ speed=10 # Optional, defaults to 1x
240
271
  )
241
272
 
242
273
  # Control playback
@@ -248,9 +279,9 @@ async def main():
248
279
  asyncio.run(main())
249
280
  ```
250
281
 
251
- ### Bulk Streaming (like Databento)
282
+ ### Bulk Streaming
252
283
 
253
- Fast bulk download for data pipelines:
284
+ Fast bulk download for data pipelines. Data arrives in batches without timing delays.
254
285
 
255
286
  ```python
256
287
  import asyncio
@@ -267,7 +298,7 @@ async def main():
267
298
  )
268
299
 
269
300
  ws.on_stream_progress(lambda snapshots_sent:
270
- print(f"Sent: {snapshots_sent} snapshots")
301
+ print(f"Progress: {snapshots_sent} snapshots")
271
302
  )
272
303
 
273
304
  ws.on_stream_complete(lambda ch, coin, sent:
@@ -281,25 +312,25 @@ async def main():
281
312
  "orderbook", "ETH",
282
313
  start=int(time.time() * 1000) - 3600000, # 1 hour ago
283
314
  end=int(time.time() * 1000),
284
- batch_size=1000
315
+ batch_size=1000 # Optional, defaults to 1000
285
316
  )
286
317
 
287
- # Stop stream if needed
318
+ # Stop if needed
288
319
  await ws.stream_stop()
289
320
 
290
321
  asyncio.run(main())
291
322
  ```
292
323
 
293
- ### Configuration
324
+ ### WebSocket Configuration
294
325
 
295
326
  ```python
296
327
  ws = OxArchiveWs(WsOptions(
297
328
  api_key="ox_your_api_key",
298
329
  ws_url="wss://api.0xarchive.io/ws", # Optional
299
- auto_reconnect=True, # Auto-reconnect on disconnect
300
- reconnect_delay=1.0, # Initial reconnect delay (seconds)
301
- max_reconnect_attempts=10, # Max reconnect attempts
302
- ping_interval=30.0, # Keep-alive ping interval (seconds)
330
+ auto_reconnect=True, # Auto-reconnect on disconnect (default: True)
331
+ reconnect_delay=1.0, # Initial reconnect delay in seconds (default: 1.0)
332
+ max_reconnect_attempts=10, # Max reconnect attempts (default: 10)
333
+ ping_interval=30.0, # Keep-alive ping interval in seconds (default: 30.0)
303
334
  ))
304
335
  ```
305
336
 
@@ -312,6 +343,27 @@ ws = OxArchiveWs(WsOptions(
312
343
  | `ticker` | Price and 24h volume | Yes |
313
344
  | `all_tickers` | All market tickers | No |
314
345
 
346
+ ## Timestamp Formats
347
+
348
+ The SDK accepts timestamps in multiple formats:
349
+
350
+ ```python
351
+ from datetime import datetime
352
+
353
+ # Unix milliseconds (int)
354
+ client.orderbook.get("BTC", timestamp=1704067200000)
355
+
356
+ # ISO string
357
+ client.orderbook.history("BTC", start="2024-01-01", end="2024-01-02")
358
+
359
+ # datetime object
360
+ client.orderbook.history(
361
+ "BTC",
362
+ start=datetime(2024, 1, 1),
363
+ end=datetime(2024, 1, 2)
364
+ )
365
+ ```
366
+
315
367
  ## Error Handling
316
368
 
317
369
  ```python
@@ -332,12 +384,15 @@ except OxArchiveError as e:
332
384
  Full type hint support with Pydantic models:
333
385
 
334
386
  ```python
335
- from oxarchive import Client, OrderBook, Trade
387
+ from oxarchive import Client
388
+ from oxarchive.types import OrderBook, Trade, Instrument, FundingRate, OpenInterest
389
+ from oxarchive.resources.trades import CursorResponse
336
390
 
337
391
  client = Client(api_key="ox_your_api_key")
338
392
 
339
393
  orderbook: OrderBook = client.orderbook.get("BTC")
340
394
  trades: list[Trade] = client.trades.recent("BTC")
395
+ result: CursorResponse = client.trades.list("BTC", start=..., end=...)
341
396
  ```
342
397
 
343
398
  ## Requirements
@@ -57,7 +57,7 @@ except ImportError:
57
57
  OxArchiveWs = None # type: ignore
58
58
  WsOptions = None # type: ignore
59
59
 
60
- __version__ = "0.1.0"
60
+ __version__ = "0.3.4"
61
61
 
62
62
  __all__ = [
63
63
  # Client
@@ -50,13 +50,26 @@ class HttpClient:
50
50
  return self._async_client
51
51
 
52
52
  def close(self) -> None:
53
- """Close the HTTP clients."""
53
+ """Close the HTTP clients.
54
+
55
+ Note: This closes the sync client immediately. For the async client,
56
+ use aclose() instead, or call this from a sync context where you
57
+ don't need to await the async cleanup.
58
+ """
54
59
  if self._client is not None:
55
60
  self._client.close()
56
61
  self._client = None
57
62
  if self._async_client is not None:
58
- # Note: async client should be closed with await
59
- pass
63
+ # Close async client synchronously (will log warning but works)
64
+ # For proper cleanup, use aclose() in async contexts
65
+ try:
66
+ import asyncio
67
+ loop = asyncio.get_running_loop()
68
+ loop.create_task(self._async_client.aclose())
69
+ except RuntimeError:
70
+ # No running loop, close synchronously (httpx supports this)
71
+ self._async_client.close()
72
+ self._async_client = None
60
73
 
61
74
  async def aclose(self) -> None:
62
75
  """Close the async HTTP client."""
@@ -118,17 +118,23 @@ class Trade(BaseModel):
118
118
  closed_pnl: Optional[str] = None
119
119
  """Realized PnL if closing a position."""
120
120
 
121
- direction: Optional[Literal["Open Long", "Open Short", "Close Long", "Close Short"]] = None
122
- """Position direction."""
121
+ direction: Optional[str] = None
122
+ """Position direction (e.g., 'Open Long', 'Close Short', 'Long > Short')."""
123
123
 
124
124
  start_position: Optional[str] = None
125
125
  """Position size before this trade."""
126
126
 
127
- source: Optional[Literal["s3", "ws", "api"]] = None
128
- """Data source."""
127
+ source: Optional[Literal["s3", "ws", "api", "live"]] = None
128
+ """Data source: 's3' (historical), 'api' (REST backfill), 'ws' (websocket), 'live' (real-time ingestion)."""
129
129
 
130
130
  user_address: Optional[str] = None
131
- """User's wallet address."""
131
+ """User's wallet address (for fill-level data)."""
132
+
133
+ maker_address: Optional[str] = None
134
+ """Maker's wallet address (for market-level WebSocket trades)."""
135
+
136
+ taker_address: Optional[str] = None
137
+ """Taker's wallet address (for market-level WebSocket trades)."""
132
138
 
133
139
 
134
140
  # =============================================================================
@@ -123,8 +123,8 @@ StreamCompleteHandler = Callable[[WsChannel, str, int], None] # channel, coin,
123
123
  def _transform_trade(coin: str, raw: dict) -> Trade:
124
124
  """Transform raw Hyperliquid trade format to SDK Trade type.
125
125
 
126
- Raw format: { coin, side, px, sz, time, hash, tid }
127
- SDK format: { coin, side, price, size, timestamp, tx_hash, trade_id }
126
+ Raw WebSocket format: { coin, side, px, sz, time, hash, tid, users: [maker, taker] }
127
+ SDK format: { coin, side, price, size, timestamp, tx_hash, trade_id, maker_address, taker_address }
128
128
  """
129
129
  from datetime import datetime
130
130
 
@@ -136,6 +136,12 @@ def _transform_trade(coin: str, raw: dict) -> Trade:
136
136
  time_ms = raw.get("time")
137
137
  timestamp = datetime.utcfromtimestamp(time_ms / 1000).isoformat() + "Z" if time_ms else None
138
138
 
139
+ # Extract maker/taker addresses from users array
140
+ # WebSocket trades have: users: [maker_address, taker_address]
141
+ users = raw.get("users", [])
142
+ maker_address = users[0] if len(users) > 0 else None
143
+ taker_address = users[1] if len(users) > 1 else None
144
+
139
145
  return Trade(
140
146
  coin=raw.get("coin", coin),
141
147
  side=raw.get("side", "B"),
@@ -144,6 +150,10 @@ def _transform_trade(coin: str, raw: dict) -> Trade:
144
150
  timestamp=timestamp,
145
151
  tx_hash=raw.get("hash"),
146
152
  trade_id=raw.get("tid"),
153
+ maker_address=maker_address,
154
+ taker_address=taker_address,
155
+ # user_address is for fill-level data (REST API), not market-level WebSocket trades
156
+ user_address=raw.get("user_address"),
147
157
  )
148
158
 
149
159
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "oxarchive"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "Official Python SDK for 0xarchive - Hyperliquid Historical Data API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -38,6 +38,7 @@ websocket = [
38
38
  dev = [
39
39
  "pytest>=8.0.0",
40
40
  "pytest-asyncio>=0.23.0",
41
+ "pytest-cov>=4.0.0",
41
42
  "ruff>=0.4.0",
42
43
  "mypy>=1.9.0",
43
44
  ]
File without changes