oxarchive 0.3.1__py3-none-any.whl → 0.3.3__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 +1 -1
- oxarchive/http.py +16 -3
- oxarchive/types.py +9 -3
- oxarchive/websocket.py +46 -1
- {oxarchive-0.3.1.dist-info → oxarchive-0.3.3.dist-info}/METADATA +2 -1
- {oxarchive-0.3.1.dist-info → oxarchive-0.3.3.dist-info}/RECORD +7 -7
- {oxarchive-0.3.1.dist-info → oxarchive-0.3.3.dist-info}/WHEEL +0 -0
oxarchive/__init__.py
CHANGED
oxarchive/http.py
CHANGED
|
@@ -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."""
|
oxarchive/types.py
CHANGED
|
@@ -124,11 +124,17 @@ class Trade(BaseModel):
|
|
|
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
|
# =============================================================================
|
oxarchive/websocket.py
CHANGED
|
@@ -120,6 +120,50 @@ StreamProgressHandler = Callable[[int], None] # snapshots_sent
|
|
|
120
120
|
StreamCompleteHandler = Callable[[WsChannel, str, int], None] # channel, coin, snapshots_sent
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
def _transform_trade(coin: str, raw: dict) -> Trade:
|
|
124
|
+
"""Transform raw Hyperliquid trade format to SDK Trade type.
|
|
125
|
+
|
|
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
|
+
"""
|
|
129
|
+
from datetime import datetime
|
|
130
|
+
|
|
131
|
+
# Check if already in SDK format (from REST API or historical replay)
|
|
132
|
+
if "price" in raw and "size" in raw:
|
|
133
|
+
return Trade(**raw)
|
|
134
|
+
|
|
135
|
+
# Transform from Hyperliquid raw format
|
|
136
|
+
time_ms = raw.get("time")
|
|
137
|
+
timestamp = datetime.utcfromtimestamp(time_ms / 1000).isoformat() + "Z" if time_ms else None
|
|
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
|
+
|
|
145
|
+
return Trade(
|
|
146
|
+
coin=raw.get("coin", coin),
|
|
147
|
+
side=raw.get("side", "B"),
|
|
148
|
+
price=str(raw.get("px", "0")),
|
|
149
|
+
size=str(raw.get("sz", "0")),
|
|
150
|
+
timestamp=timestamp,
|
|
151
|
+
tx_hash=raw.get("hash"),
|
|
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"),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _transform_trades(coin: str, raw_data: list) -> list[Trade]:
|
|
161
|
+
"""Transform a list of raw trades to SDK Trade types."""
|
|
162
|
+
if not isinstance(raw_data, list):
|
|
163
|
+
return [_transform_trade(coin, raw_data)]
|
|
164
|
+
return [_transform_trade(coin, t) for t in raw_data]
|
|
165
|
+
|
|
166
|
+
|
|
123
167
|
def _transform_orderbook(coin: str, raw: dict) -> OrderBook:
|
|
124
168
|
"""Transform raw Hyperliquid orderbook format to SDK OrderBook type.
|
|
125
169
|
|
|
@@ -642,7 +686,8 @@ class OxArchiveWs:
|
|
|
642
686
|
self._on_orderbook(coin, orderbook)
|
|
643
687
|
|
|
644
688
|
elif channel == "trades" and self._on_trades:
|
|
645
|
-
|
|
689
|
+
# Transform raw Hyperliquid format to SDK Trade type
|
|
690
|
+
trades = _transform_trades(coin, raw_data)
|
|
646
691
|
self._on_trades(coin, trades)
|
|
647
692
|
|
|
648
693
|
# Replay messages (Option B)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oxarchive
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
oxarchive/__init__.py,sha256=
|
|
1
|
+
oxarchive/__init__.py,sha256=9mUMJPsr1Yi--7G8ivg-_d8iWGmiynTNZXn3L-VgvKY,2181
|
|
2
2
|
oxarchive/client.py,sha256=3P0fvOcyM5BWppkVV4054NduDHKvRg-cWeluoGymmRk,3163
|
|
3
|
-
oxarchive/http.py,sha256=
|
|
4
|
-
oxarchive/types.py,sha256=
|
|
5
|
-
oxarchive/websocket.py,sha256=
|
|
3
|
+
oxarchive/http.py,sha256=SY_o9Ag8ADo1HI3i3uAKW1xwkYjPE75gRAjnMsddAGs,4211
|
|
4
|
+
oxarchive/types.py,sha256=bd08H1oS2SNAcb1zsiw8P9qgqYa2eFdc9Ue_MUMffz8,11034
|
|
5
|
+
oxarchive/websocket.py,sha256=hodeRIfY3sHrdD1DMexInnDvLXTtUzyXOrRM37Iyark,26941
|
|
6
6
|
oxarchive/resources/__init__.py,sha256=WQ4GYQ8p3L0D2Isk4IV4h1DRpvyZlt6tOF1t_CJr6ls,385
|
|
7
7
|
oxarchive/resources/funding.py,sha256=TXkZxodVQTVcVbzNG6SpMQAzf8AkLm2NYZJxnP4MNXw,3500
|
|
8
8
|
oxarchive/resources/instruments.py,sha256=flD1sH6x3P3CTqV1ZwkfwbranVacmhsHn5Dhr7lGQhM,1606
|
|
9
9
|
oxarchive/resources/openinterest.py,sha256=h13yLA72LpfryUf8IqF6W7uE4ObYY2Qbc-auv4LtPqc,3552
|
|
10
10
|
oxarchive/resources/orderbook.py,sha256=o_DTdpzKrZvHL9YXm8cGGUugPM8uUa6r9O_72r1ByV0,4557
|
|
11
11
|
oxarchive/resources/trades.py,sha256=XCi2rXA2hxaTt0KNlWw8f7W0hzAvNWyT7DaivMz_rHw,10012
|
|
12
|
-
oxarchive-0.3.
|
|
13
|
-
oxarchive-0.3.
|
|
14
|
-
oxarchive-0.3.
|
|
12
|
+
oxarchive-0.3.3.dist-info/METADATA,sha256=la3_AkxO_R3b5sP9EQbgaFAt4R5vRMmV_UtkwwEXhUQ,8798
|
|
13
|
+
oxarchive-0.3.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
oxarchive-0.3.3.dist-info/RECORD,,
|
|
File without changes
|