oxarchive 0.1.1__tar.gz → 0.2.1__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.1.1 → oxarchive-0.2.1}/PKG-INFO +5 -19
- {oxarchive-0.1.1 → oxarchive-0.2.1}/README.md +4 -18
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/__init__.py +0 -4
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/client.py +0 -4
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/resources/__init__.py +0 -2
- oxarchive-0.2.1/oxarchive/types.py +408 -0
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/websocket.py +18 -19
- {oxarchive-0.1.1 → oxarchive-0.2.1}/pyproject.toml +1 -1
- oxarchive-0.1.1/oxarchive/resources/candles.py +0 -100
- oxarchive-0.1.1/oxarchive/types.py +0 -304
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/http.py +0 -0
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/resources/funding.py +0 -0
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/resources/instruments.py +0 -0
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/resources/openinterest.py +0 -0
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/resources/orderbook.py +0 -0
- {oxarchive-0.1.1 → oxarchive-0.2.1}/oxarchive/resources/trades.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oxarchive
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -140,20 +140,6 @@ trades = client.trades.list(
|
|
|
140
140
|
)
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
### Candles (OHLCV)
|
|
144
|
-
|
|
145
|
-
```python
|
|
146
|
-
# Get hourly candles
|
|
147
|
-
candles = client.candles.list(
|
|
148
|
-
"BTC",
|
|
149
|
-
interval="1h",
|
|
150
|
-
start="2024-01-01",
|
|
151
|
-
end="2024-01-02"
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
# Available intervals: '1m', '5m', '15m', '1h', '4h', '1d'
|
|
155
|
-
```
|
|
156
|
-
|
|
157
143
|
### Instruments
|
|
158
144
|
|
|
159
145
|
```python
|
|
@@ -272,8 +258,8 @@ async def main():
|
|
|
272
258
|
print(f"{ts}: {data['mid_price']}")
|
|
273
259
|
)
|
|
274
260
|
|
|
275
|
-
ws.on_replay_start(lambda ch, coin,
|
|
276
|
-
print(f"Starting replay
|
|
261
|
+
ws.on_replay_start(lambda ch, coin, start, end, speed:
|
|
262
|
+
print(f"Starting replay from {start} to {end} at {speed}x")
|
|
277
263
|
)
|
|
278
264
|
|
|
279
265
|
ws.on_replay_complete(lambda ch, coin, sent:
|
|
@@ -316,8 +302,8 @@ async def main():
|
|
|
316
302
|
all_data.extend([r.data for r in records])
|
|
317
303
|
)
|
|
318
304
|
|
|
319
|
-
ws.on_stream_progress(lambda
|
|
320
|
-
print(f"
|
|
305
|
+
ws.on_stream_progress(lambda snapshots_sent:
|
|
306
|
+
print(f"Sent: {snapshots_sent} snapshots")
|
|
321
307
|
)
|
|
322
308
|
|
|
323
309
|
ws.on_stream_complete(lambda ch, coin, sent:
|
|
@@ -104,20 +104,6 @@ trades = client.trades.list(
|
|
|
104
104
|
)
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
### Candles (OHLCV)
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
# Get hourly candles
|
|
111
|
-
candles = client.candles.list(
|
|
112
|
-
"BTC",
|
|
113
|
-
interval="1h",
|
|
114
|
-
start="2024-01-01",
|
|
115
|
-
end="2024-01-02"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
# Available intervals: '1m', '5m', '15m', '1h', '4h', '1d'
|
|
119
|
-
```
|
|
120
|
-
|
|
121
107
|
### Instruments
|
|
122
108
|
|
|
123
109
|
```python
|
|
@@ -236,8 +222,8 @@ async def main():
|
|
|
236
222
|
print(f"{ts}: {data['mid_price']}")
|
|
237
223
|
)
|
|
238
224
|
|
|
239
|
-
ws.on_replay_start(lambda ch, coin,
|
|
240
|
-
print(f"Starting replay
|
|
225
|
+
ws.on_replay_start(lambda ch, coin, start, end, speed:
|
|
226
|
+
print(f"Starting replay from {start} to {end} at {speed}x")
|
|
241
227
|
)
|
|
242
228
|
|
|
243
229
|
ws.on_replay_complete(lambda ch, coin, sent:
|
|
@@ -280,8 +266,8 @@ async def main():
|
|
|
280
266
|
all_data.extend([r.data for r in records])
|
|
281
267
|
)
|
|
282
268
|
|
|
283
|
-
ws.on_stream_progress(lambda
|
|
284
|
-
print(f"
|
|
269
|
+
ws.on_stream_progress(lambda snapshots_sent:
|
|
270
|
+
print(f"Sent: {snapshots_sent} snapshots")
|
|
285
271
|
)
|
|
286
272
|
|
|
287
273
|
ws.on_stream_complete(lambda ch, coin, sent:
|
|
@@ -20,8 +20,6 @@ from .client import Client
|
|
|
20
20
|
from .types import (
|
|
21
21
|
OrderBook,
|
|
22
22
|
Trade,
|
|
23
|
-
Candle,
|
|
24
|
-
CandleInterval,
|
|
25
23
|
Instrument,
|
|
26
24
|
FundingRate,
|
|
27
25
|
OpenInterest,
|
|
@@ -70,8 +68,6 @@ __all__ = [
|
|
|
70
68
|
# Types
|
|
71
69
|
"OrderBook",
|
|
72
70
|
"Trade",
|
|
73
|
-
"Candle",
|
|
74
|
-
"CandleInterval",
|
|
75
71
|
"Instrument",
|
|
76
72
|
"FundingRate",
|
|
77
73
|
"OpenInterest",
|
|
@@ -8,7 +8,6 @@ from .http import HttpClient
|
|
|
8
8
|
from .resources import (
|
|
9
9
|
OrderBookResource,
|
|
10
10
|
TradesResource,
|
|
11
|
-
CandlesResource,
|
|
12
11
|
InstrumentsResource,
|
|
13
12
|
FundingResource,
|
|
14
13
|
OpenInterestResource,
|
|
@@ -81,9 +80,6 @@ class Client:
|
|
|
81
80
|
self.trades = TradesResource(self._http)
|
|
82
81
|
"""Trade/fill history"""
|
|
83
82
|
|
|
84
|
-
self.candles = CandlesResource(self._http)
|
|
85
|
-
"""OHLCV candles"""
|
|
86
|
-
|
|
87
83
|
self.instruments = InstrumentsResource(self._http)
|
|
88
84
|
"""Trading instruments metadata"""
|
|
89
85
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from .orderbook import OrderBookResource
|
|
4
4
|
from .trades import TradesResource
|
|
5
|
-
from .candles import CandlesResource
|
|
6
5
|
from .instruments import InstrumentsResource
|
|
7
6
|
from .funding import FundingResource
|
|
8
7
|
from .openinterest import OpenInterestResource
|
|
@@ -10,7 +9,6 @@ from .openinterest import OpenInterestResource
|
|
|
10
9
|
__all__ = [
|
|
11
10
|
"OrderBookResource",
|
|
12
11
|
"TradesResource",
|
|
13
|
-
"CandlesResource",
|
|
14
12
|
"InstrumentsResource",
|
|
15
13
|
"FundingResource",
|
|
16
14
|
"OpenInterestResource",
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Type definitions for the 0xarchive SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Generic, Literal, Optional, TypeVar, Union
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# Base Types
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ApiMeta(BaseModel):
|
|
19
|
+
"""Response metadata."""
|
|
20
|
+
|
|
21
|
+
count: int
|
|
22
|
+
next_cursor: Optional[str] = None
|
|
23
|
+
request_id: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ApiResponse(BaseModel, Generic[T]):
|
|
27
|
+
"""Standard API response wrapper."""
|
|
28
|
+
|
|
29
|
+
success: bool
|
|
30
|
+
data: T
|
|
31
|
+
meta: ApiMeta
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# =============================================================================
|
|
35
|
+
# Order Book Types
|
|
36
|
+
# =============================================================================
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PriceLevel(BaseModel):
|
|
40
|
+
"""Single price level in the order book."""
|
|
41
|
+
|
|
42
|
+
px: str
|
|
43
|
+
"""Price at this level."""
|
|
44
|
+
|
|
45
|
+
sz: str
|
|
46
|
+
"""Total size at this price level."""
|
|
47
|
+
|
|
48
|
+
n: int
|
|
49
|
+
"""Number of orders at this level."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OrderBook(BaseModel):
|
|
53
|
+
"""L2 order book snapshot."""
|
|
54
|
+
|
|
55
|
+
coin: str
|
|
56
|
+
"""Trading pair symbol (e.g., BTC, ETH)."""
|
|
57
|
+
|
|
58
|
+
timestamp: datetime
|
|
59
|
+
"""Snapshot timestamp (UTC)."""
|
|
60
|
+
|
|
61
|
+
bids: list[PriceLevel]
|
|
62
|
+
"""Bid price levels (best bid first)."""
|
|
63
|
+
|
|
64
|
+
asks: list[PriceLevel]
|
|
65
|
+
"""Ask price levels (best ask first)."""
|
|
66
|
+
|
|
67
|
+
mid_price: Optional[str] = None
|
|
68
|
+
"""Mid price (best bid + best ask) / 2."""
|
|
69
|
+
|
|
70
|
+
spread: Optional[str] = None
|
|
71
|
+
"""Spread in absolute terms (best ask - best bid)."""
|
|
72
|
+
|
|
73
|
+
spread_bps: Optional[str] = None
|
|
74
|
+
"""Spread in basis points."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# =============================================================================
|
|
78
|
+
# Trade/Fill Types
|
|
79
|
+
# =============================================================================
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Trade(BaseModel):
|
|
83
|
+
"""Trade/fill record with full execution details."""
|
|
84
|
+
|
|
85
|
+
coin: str
|
|
86
|
+
"""Trading pair symbol."""
|
|
87
|
+
|
|
88
|
+
side: Literal["A", "B"]
|
|
89
|
+
"""Trade side: 'B' (buy) or 'A' (sell/ask)."""
|
|
90
|
+
|
|
91
|
+
price: str
|
|
92
|
+
"""Execution price."""
|
|
93
|
+
|
|
94
|
+
size: str
|
|
95
|
+
"""Trade size."""
|
|
96
|
+
|
|
97
|
+
timestamp: datetime
|
|
98
|
+
"""Execution timestamp (UTC)."""
|
|
99
|
+
|
|
100
|
+
tx_hash: Optional[str] = None
|
|
101
|
+
"""Blockchain transaction hash."""
|
|
102
|
+
|
|
103
|
+
trade_id: Optional[int] = None
|
|
104
|
+
"""Unique trade ID."""
|
|
105
|
+
|
|
106
|
+
order_id: Optional[int] = None
|
|
107
|
+
"""Associated order ID."""
|
|
108
|
+
|
|
109
|
+
crossed: Optional[bool] = None
|
|
110
|
+
"""True if taker (crossed the spread), false if maker."""
|
|
111
|
+
|
|
112
|
+
fee: Optional[str] = None
|
|
113
|
+
"""Trading fee amount."""
|
|
114
|
+
|
|
115
|
+
fee_token: Optional[str] = None
|
|
116
|
+
"""Fee denomination (e.g., USDC)."""
|
|
117
|
+
|
|
118
|
+
closed_pnl: Optional[str] = None
|
|
119
|
+
"""Realized PnL if closing a position."""
|
|
120
|
+
|
|
121
|
+
direction: Optional[Literal["Open Long", "Open Short", "Close Long", "Close Short"]] = None
|
|
122
|
+
"""Position direction."""
|
|
123
|
+
|
|
124
|
+
start_position: Optional[str] = None
|
|
125
|
+
"""Position size before this trade."""
|
|
126
|
+
|
|
127
|
+
source: Optional[Literal["s3", "ws", "api"]] = None
|
|
128
|
+
"""Data source."""
|
|
129
|
+
|
|
130
|
+
user_address: Optional[str] = None
|
|
131
|
+
"""User's wallet address."""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# =============================================================================
|
|
135
|
+
# Instrument Types
|
|
136
|
+
# =============================================================================
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Instrument(BaseModel):
|
|
140
|
+
"""Trading instrument specification."""
|
|
141
|
+
|
|
142
|
+
model_config = {"populate_by_name": True}
|
|
143
|
+
|
|
144
|
+
name: str
|
|
145
|
+
"""Instrument symbol (e.g., BTC)."""
|
|
146
|
+
|
|
147
|
+
sz_decimals: int = Field(alias="szDecimals")
|
|
148
|
+
"""Size decimal precision."""
|
|
149
|
+
|
|
150
|
+
max_leverage: Optional[int] = Field(default=None, alias="maxLeverage")
|
|
151
|
+
"""Maximum leverage allowed."""
|
|
152
|
+
|
|
153
|
+
only_isolated: Optional[bool] = Field(default=None, alias="onlyIsolated")
|
|
154
|
+
"""If true, only isolated margin mode is allowed."""
|
|
155
|
+
|
|
156
|
+
instrument_type: Optional[Literal["perp", "spot"]] = Field(default=None, alias="instrumentType")
|
|
157
|
+
"""Type of instrument."""
|
|
158
|
+
|
|
159
|
+
is_active: bool = Field(default=True, alias="isActive")
|
|
160
|
+
"""Whether the instrument is currently tradeable."""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# =============================================================================
|
|
164
|
+
# Funding Types
|
|
165
|
+
# =============================================================================
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class FundingRate(BaseModel):
|
|
169
|
+
"""Funding rate record."""
|
|
170
|
+
|
|
171
|
+
coin: str
|
|
172
|
+
"""Trading pair symbol."""
|
|
173
|
+
|
|
174
|
+
timestamp: datetime
|
|
175
|
+
"""Funding timestamp (UTC)."""
|
|
176
|
+
|
|
177
|
+
funding_rate: str
|
|
178
|
+
"""Funding rate as decimal (e.g., 0.0001 = 0.01%)."""
|
|
179
|
+
|
|
180
|
+
premium: Optional[str] = None
|
|
181
|
+
"""Premium component of funding rate."""
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# =============================================================================
|
|
185
|
+
# Open Interest Types
|
|
186
|
+
# =============================================================================
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class OpenInterest(BaseModel):
|
|
190
|
+
"""Open interest snapshot with market context."""
|
|
191
|
+
|
|
192
|
+
coin: str
|
|
193
|
+
"""Trading pair symbol."""
|
|
194
|
+
|
|
195
|
+
timestamp: datetime
|
|
196
|
+
"""Snapshot timestamp (UTC)."""
|
|
197
|
+
|
|
198
|
+
open_interest: str
|
|
199
|
+
"""Total open interest in contracts."""
|
|
200
|
+
|
|
201
|
+
mark_price: Optional[str] = None
|
|
202
|
+
"""Mark price used for liquidations."""
|
|
203
|
+
|
|
204
|
+
oracle_price: Optional[str] = None
|
|
205
|
+
"""Oracle price from external feed."""
|
|
206
|
+
|
|
207
|
+
day_ntl_volume: Optional[str] = None
|
|
208
|
+
"""24-hour notional volume."""
|
|
209
|
+
|
|
210
|
+
prev_day_price: Optional[str] = None
|
|
211
|
+
"""Price 24 hours ago."""
|
|
212
|
+
|
|
213
|
+
mid_price: Optional[str] = None
|
|
214
|
+
"""Current mid price."""
|
|
215
|
+
|
|
216
|
+
impact_bid_price: Optional[str] = None
|
|
217
|
+
"""Impact bid price for liquidations."""
|
|
218
|
+
|
|
219
|
+
impact_ask_price: Optional[str] = None
|
|
220
|
+
"""Impact ask price for liquidations."""
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# =============================================================================
|
|
224
|
+
# WebSocket Types
|
|
225
|
+
# =============================================================================
|
|
226
|
+
|
|
227
|
+
WsChannel = Literal["orderbook", "trades", "ticker", "all_tickers"]
|
|
228
|
+
"""Available WebSocket channels. Note: ticker/all_tickers are real-time only."""
|
|
229
|
+
|
|
230
|
+
WsConnectionState = Literal["connecting", "connected", "disconnected", "reconnecting"]
|
|
231
|
+
"""WebSocket connection state."""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class WsSubscribed(BaseModel):
|
|
235
|
+
"""Subscription confirmed from server."""
|
|
236
|
+
|
|
237
|
+
type: Literal["subscribed"]
|
|
238
|
+
channel: WsChannel
|
|
239
|
+
coin: Optional[str] = None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class WsUnsubscribed(BaseModel):
|
|
243
|
+
"""Unsubscription confirmed from server."""
|
|
244
|
+
|
|
245
|
+
type: Literal["unsubscribed"]
|
|
246
|
+
channel: WsChannel
|
|
247
|
+
coin: Optional[str] = None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class WsPong(BaseModel):
|
|
251
|
+
"""Pong response from server."""
|
|
252
|
+
|
|
253
|
+
type: Literal["pong"]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class WsError(BaseModel):
|
|
257
|
+
"""Error from server."""
|
|
258
|
+
|
|
259
|
+
type: Literal["error"]
|
|
260
|
+
message: str
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class WsData(BaseModel):
|
|
264
|
+
"""Real-time data message from server."""
|
|
265
|
+
|
|
266
|
+
type: Literal["data"]
|
|
267
|
+
channel: WsChannel
|
|
268
|
+
coin: str
|
|
269
|
+
data: dict[str, Any]
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# =============================================================================
|
|
273
|
+
# WebSocket Replay Types (Historical Replay Mode)
|
|
274
|
+
# =============================================================================
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class WsReplayStarted(BaseModel):
|
|
278
|
+
"""Replay started response."""
|
|
279
|
+
|
|
280
|
+
type: Literal["replay_started"]
|
|
281
|
+
channel: WsChannel
|
|
282
|
+
coin: str
|
|
283
|
+
start: int
|
|
284
|
+
"""Start timestamp in milliseconds."""
|
|
285
|
+
end: int
|
|
286
|
+
"""End timestamp in milliseconds."""
|
|
287
|
+
speed: float
|
|
288
|
+
"""Playback speed multiplier."""
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class WsReplayPaused(BaseModel):
|
|
292
|
+
"""Replay paused response."""
|
|
293
|
+
|
|
294
|
+
type: Literal["replay_paused"]
|
|
295
|
+
current_timestamp: int
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class WsReplayResumed(BaseModel):
|
|
299
|
+
"""Replay resumed response."""
|
|
300
|
+
|
|
301
|
+
type: Literal["replay_resumed"]
|
|
302
|
+
current_timestamp: int
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class WsReplayCompleted(BaseModel):
|
|
306
|
+
"""Replay completed response."""
|
|
307
|
+
|
|
308
|
+
type: Literal["replay_completed"]
|
|
309
|
+
channel: WsChannel
|
|
310
|
+
coin: str
|
|
311
|
+
snapshots_sent: int
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class WsReplayStopped(BaseModel):
|
|
315
|
+
"""Replay stopped response."""
|
|
316
|
+
|
|
317
|
+
type: Literal["replay_stopped"]
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class WsHistoricalData(BaseModel):
|
|
321
|
+
"""Historical data point (replay mode)."""
|
|
322
|
+
|
|
323
|
+
type: Literal["historical_data"]
|
|
324
|
+
channel: WsChannel
|
|
325
|
+
coin: str
|
|
326
|
+
timestamp: int
|
|
327
|
+
data: dict[str, Any]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# =============================================================================
|
|
331
|
+
# WebSocket Bulk Stream Types (Bulk Download Mode)
|
|
332
|
+
# =============================================================================
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class WsStreamStarted(BaseModel):
|
|
336
|
+
"""Stream started response."""
|
|
337
|
+
|
|
338
|
+
type: Literal["stream_started"]
|
|
339
|
+
channel: WsChannel
|
|
340
|
+
coin: str
|
|
341
|
+
start: int
|
|
342
|
+
"""Start timestamp in milliseconds."""
|
|
343
|
+
end: int
|
|
344
|
+
"""End timestamp in milliseconds."""
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class WsStreamProgress(BaseModel):
|
|
348
|
+
"""Stream progress response (sent every ~2 seconds)."""
|
|
349
|
+
|
|
350
|
+
type: Literal["stream_progress"]
|
|
351
|
+
snapshots_sent: int
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TimestampedRecord(BaseModel):
|
|
355
|
+
"""A record with timestamp for batched data."""
|
|
356
|
+
|
|
357
|
+
timestamp: int
|
|
358
|
+
data: dict[str, Any]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class WsHistoricalBatch(BaseModel):
|
|
362
|
+
"""Batch of historical data (bulk streaming)."""
|
|
363
|
+
|
|
364
|
+
type: Literal["historical_batch"]
|
|
365
|
+
channel: WsChannel
|
|
366
|
+
coin: str
|
|
367
|
+
data: list[TimestampedRecord]
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class WsStreamCompleted(BaseModel):
|
|
371
|
+
"""Stream completed response."""
|
|
372
|
+
|
|
373
|
+
type: Literal["stream_completed"]
|
|
374
|
+
channel: WsChannel
|
|
375
|
+
coin: str
|
|
376
|
+
snapshots_sent: int
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class WsStreamStopped(BaseModel):
|
|
380
|
+
"""Stream stopped response."""
|
|
381
|
+
|
|
382
|
+
type: Literal["stream_stopped"]
|
|
383
|
+
snapshots_sent: int
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# =============================================================================
|
|
387
|
+
# Error Types
|
|
388
|
+
# =============================================================================
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class OxArchiveError(Exception):
|
|
392
|
+
"""SDK error class."""
|
|
393
|
+
|
|
394
|
+
def __init__(self, message: str, code: int, request_id: Optional[str] = None):
|
|
395
|
+
super().__init__(message)
|
|
396
|
+
self.message = message
|
|
397
|
+
self.code = code
|
|
398
|
+
self.request_id = request_id
|
|
399
|
+
|
|
400
|
+
def __str__(self) -> str:
|
|
401
|
+
if self.request_id:
|
|
402
|
+
return f"[{self.code}] {self.message} (request_id: {self.request_id})"
|
|
403
|
+
return f"[{self.code}] {self.message}"
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# Type alias for timestamp parameters
|
|
407
|
+
Timestamp = Union[int, str, datetime]
|
|
408
|
+
"""Timestamp can be Unix ms (int), ISO string, or datetime object."""
|
|
@@ -33,6 +33,7 @@ from typing import Any, Callable, Optional, Set, Union
|
|
|
33
33
|
try:
|
|
34
34
|
import websockets
|
|
35
35
|
from websockets.client import WebSocketClientProtocol
|
|
36
|
+
from websockets.protocol import State as WsState
|
|
36
37
|
except ImportError:
|
|
37
38
|
raise ImportError(
|
|
38
39
|
"WebSocket support requires the 'websockets' package. "
|
|
@@ -108,14 +109,14 @@ ErrorHandler = Callable[[Exception], None]
|
|
|
108
109
|
|
|
109
110
|
# Replay handlers
|
|
110
111
|
HistoricalDataHandler = Callable[[str, int, dict], None]
|
|
111
|
-
ReplayStartHandler = Callable[[WsChannel, str, int, float], None]
|
|
112
|
-
ReplayCompleteHandler = Callable[[WsChannel, str, int], None]
|
|
112
|
+
ReplayStartHandler = Callable[[WsChannel, str, int, int, float], None] # channel, coin, start, end, speed
|
|
113
|
+
ReplayCompleteHandler = Callable[[WsChannel, str, int], None] # channel, coin, snapshots_sent
|
|
113
114
|
|
|
114
115
|
# Stream handlers
|
|
115
116
|
BatchHandler = Callable[[str, list[TimestampedRecord]], None]
|
|
116
|
-
StreamStartHandler = Callable[[WsChannel, str, int], None]
|
|
117
|
-
StreamProgressHandler = Callable[[int
|
|
118
|
-
StreamCompleteHandler = Callable[[WsChannel, str, int], None]
|
|
117
|
+
StreamStartHandler = Callable[[WsChannel, str, int, int], None] # channel, coin, start, end
|
|
118
|
+
StreamProgressHandler = Callable[[int], None] # snapshots_sent
|
|
119
|
+
StreamCompleteHandler = Callable[[WsChannel, str, int], None] # channel, coin, snapshots_sent
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
class OxArchiveWs:
|
|
@@ -164,7 +165,7 @@ class OxArchiveWs:
|
|
|
164
165
|
@property
|
|
165
166
|
def is_connected(self) -> bool:
|
|
166
167
|
"""Check if connected."""
|
|
167
|
-
return self._ws is not None and self._ws.
|
|
168
|
+
return self._ws is not None and self._ws.state == WsState.OPEN
|
|
168
169
|
|
|
169
170
|
async def connect(self) -> None:
|
|
170
171
|
"""Connect to the WebSocket server."""
|
|
@@ -421,14 +422,14 @@ class OxArchiveWs:
|
|
|
421
422
|
def on_replay_start(self, handler: ReplayStartHandler) -> None:
|
|
422
423
|
"""Set handler for replay started event.
|
|
423
424
|
|
|
424
|
-
Handler receives: (channel, coin,
|
|
425
|
+
Handler receives: (channel, coin, start, end, speed)
|
|
425
426
|
"""
|
|
426
427
|
self._on_replay_start = handler
|
|
427
428
|
|
|
428
429
|
def on_replay_complete(self, handler: ReplayCompleteHandler) -> None:
|
|
429
430
|
"""Set handler for replay completed event.
|
|
430
431
|
|
|
431
|
-
Handler receives: (channel, coin,
|
|
432
|
+
Handler receives: (channel, coin, snapshots_sent)
|
|
432
433
|
"""
|
|
433
434
|
self._on_replay_complete = handler
|
|
434
435
|
|
|
@@ -444,21 +445,21 @@ class OxArchiveWs:
|
|
|
444
445
|
def on_stream_start(self, handler: StreamStartHandler) -> None:
|
|
445
446
|
"""Set handler for stream started event.
|
|
446
447
|
|
|
447
|
-
Handler receives: (channel, coin,
|
|
448
|
+
Handler receives: (channel, coin, start, end)
|
|
448
449
|
"""
|
|
449
450
|
self._on_stream_start = handler
|
|
450
451
|
|
|
451
452
|
def on_stream_progress(self, handler: StreamProgressHandler) -> None:
|
|
452
453
|
"""Set handler for stream progress event.
|
|
453
454
|
|
|
454
|
-
Handler receives: (
|
|
455
|
+
Handler receives: (snapshots_sent)
|
|
455
456
|
"""
|
|
456
457
|
self._on_stream_progress = handler
|
|
457
458
|
|
|
458
459
|
def on_stream_complete(self, handler: StreamCompleteHandler) -> None:
|
|
459
460
|
"""Set handler for stream completed event.
|
|
460
461
|
|
|
461
|
-
Handler receives: (channel, coin,
|
|
462
|
+
Handler receives: (channel, coin, snapshots_sent)
|
|
462
463
|
"""
|
|
463
464
|
self._on_stream_complete = handler
|
|
464
465
|
|
|
@@ -585,30 +586,28 @@ class OxArchiveWs:
|
|
|
585
586
|
# Replay messages (Option B)
|
|
586
587
|
elif msg_type == "replay_started" and self._on_replay_start:
|
|
587
588
|
self._on_replay_start(
|
|
588
|
-
data["channel"], data["coin"], data["
|
|
589
|
+
data["channel"], data["coin"], data["start"], data["end"], data["speed"]
|
|
589
590
|
)
|
|
590
591
|
|
|
591
592
|
elif msg_type == "historical_data" and self._on_historical_data:
|
|
592
593
|
self._on_historical_data(data["coin"], data["timestamp"], data["data"])
|
|
593
594
|
|
|
594
595
|
elif msg_type == "replay_completed" and self._on_replay_complete:
|
|
595
|
-
self._on_replay_complete(data["channel"], data["coin"], data["
|
|
596
|
+
self._on_replay_complete(data["channel"], data["coin"], data["snapshots_sent"])
|
|
596
597
|
|
|
597
598
|
# Stream messages (Option D)
|
|
598
599
|
elif msg_type == "stream_started" and self._on_stream_start:
|
|
599
|
-
self._on_stream_start(data["channel"], data["coin"], data["
|
|
600
|
+
self._on_stream_start(data["channel"], data["coin"], data["start"], data["end"])
|
|
600
601
|
|
|
601
602
|
elif msg_type == "stream_progress" and self._on_stream_progress:
|
|
602
|
-
self._on_stream_progress(
|
|
603
|
-
data["records_sent"], data["total_records"], data["progress_pct"]
|
|
604
|
-
)
|
|
603
|
+
self._on_stream_progress(data["snapshots_sent"])
|
|
605
604
|
|
|
606
605
|
elif msg_type == "historical_batch" and self._on_batch:
|
|
607
|
-
records = [TimestampedRecord(**r) for r in data["
|
|
606
|
+
records = [TimestampedRecord(**r) for r in data["data"]]
|
|
608
607
|
self._on_batch(data["coin"], records)
|
|
609
608
|
|
|
610
609
|
elif msg_type == "stream_completed" and self._on_stream_complete:
|
|
611
|
-
self._on_stream_complete(data["channel"], data["coin"], data["
|
|
610
|
+
self._on_stream_complete(data["channel"], data["coin"], data["snapshots_sent"])
|
|
612
611
|
|
|
613
612
|
except Exception as e:
|
|
614
613
|
logger.error(f"Error handling message: {e}")
|
|
@@ -1,100 +0,0 @@
|
|
|
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"]]
|
|
@@ -1,304 +0,0 @@
|
|
|
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]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|