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.
- oxarchive-0.3.4/.gitignore +78 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/PKG-INFO +113 -57
- {oxarchive-0.3.2 → oxarchive-0.3.4}/README.md +111 -56
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/__init__.py +1 -1
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/http.py +16 -3
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/types.py +11 -5
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/websocket.py +12 -2
- {oxarchive-0.3.2 → oxarchive-0.3.4}/pyproject.toml +2 -1
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/client.py +0 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/resources/__init__.py +0 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/resources/funding.py +0 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/resources/instruments.py +0 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/resources/openinterest.py +0 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/resources/orderbook.py +0 -0
- {oxarchive-0.3.2 → oxarchive-0.3.4}/oxarchive/resources/trades.py +0 -0
|
@@ -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.
|
|
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",
|
|
100
|
-
base_url="https://api.0xarchive.io",
|
|
101
|
-
timeout=30.0,
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
##
|
|
221
|
+
## WebSocket Client
|
|
182
222
|
|
|
183
|
-
The
|
|
223
|
+
The WebSocket client supports three modes: real-time streaming, historical replay, and bulk streaming.
|
|
184
224
|
|
|
185
225
|
```python
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
+
### Real-time Streaming
|
|
199
233
|
|
|
200
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
|
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,
|
|
336
|
-
reconnect_delay=1.0,
|
|
337
|
-
max_reconnect_attempts=10,
|
|
338
|
-
ping_interval=30.0,
|
|
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
|
|
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",
|
|
64
|
-
base_url="https://api.0xarchive.io",
|
|
65
|
-
timeout=30.0,
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
##
|
|
184
|
+
## WebSocket Client
|
|
146
185
|
|
|
147
|
-
The
|
|
186
|
+
The WebSocket client supports three modes: real-time streaming, historical replay, and bulk streaming.
|
|
148
187
|
|
|
149
188
|
```python
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
+
### Real-time Streaming
|
|
163
196
|
|
|
164
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
|
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,
|
|
300
|
-
reconnect_delay=1.0,
|
|
301
|
-
max_reconnect_attempts=10,
|
|
302
|
-
ping_interval=30.0,
|
|
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
|
|
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
|
|
@@ -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
|
-
#
|
|
59
|
-
|
|
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[
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|