oxarchive 0.4.6__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
oxarchive/__init__.py CHANGED
@@ -31,6 +31,8 @@ from .types import (
31
31
  LighterInstrument,
32
32
  FundingRate,
33
33
  OpenInterest,
34
+ Candle,
35
+ CandleInterval,
34
36
  OxArchiveError,
35
37
  # WebSocket types
36
38
  WsChannel,
@@ -65,7 +67,7 @@ except ImportError:
65
67
  OxArchiveWs = None # type: ignore
66
68
  WsOptions = None # type: ignore
67
69
 
68
- __version__ = "0.4.6"
70
+ __version__ = "0.5.0"
69
71
 
70
72
  __all__ = [
71
73
  # Client
@@ -84,6 +86,8 @@ __all__ = [
84
86
  "LighterGranularity",
85
87
  "FundingRate",
86
88
  "OpenInterest",
89
+ "Candle",
90
+ "CandleInterval",
87
91
  "OxArchiveError",
88
92
  # WebSocket Types
89
93
  "WsChannel",
oxarchive/exchanges.py CHANGED
@@ -10,6 +10,7 @@ from .resources import (
10
10
  LighterInstrumentsResource,
11
11
  FundingResource,
12
12
  OpenInterestResource,
13
+ CandlesResource,
13
14
  )
14
15
 
15
16
 
@@ -44,6 +45,9 @@ class HyperliquidClient:
44
45
  self.open_interest = OpenInterestResource(http, base_path)
45
46
  """Open interest"""
46
47
 
48
+ self.candles = CandlesResource(http, base_path)
49
+ """OHLCV candle data"""
50
+
47
51
 
48
52
  class LighterClient:
49
53
  """
@@ -77,3 +81,6 @@ class LighterClient:
77
81
 
78
82
  self.open_interest = OpenInterestResource(http, base_path)
79
83
  """Open interest"""
84
+
85
+ self.candles = CandlesResource(http, base_path)
86
+ """OHLCV candle data"""
@@ -5,6 +5,7 @@ from .trades import TradesResource
5
5
  from .instruments import InstrumentsResource, LighterInstrumentsResource
6
6
  from .funding import FundingResource
7
7
  from .openinterest import OpenInterestResource
8
+ from .candles import CandlesResource
8
9
 
9
10
  __all__ = [
10
11
  "OrderBookResource",
@@ -13,4 +14,5 @@ __all__ = [
13
14
  "LighterInstrumentsResource",
14
15
  "FundingResource",
15
16
  "OpenInterestResource",
17
+ "CandlesResource",
16
18
  ]
@@ -0,0 +1,121 @@
1
+ """Candles (OHLCV) 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, CursorResponse, Timestamp
10
+
11
+
12
+ class CandlesResource:
13
+ """
14
+ Candles (OHLCV) API resource.
15
+
16
+ Example:
17
+ >>> # Get candle history
18
+ >>> result = client.candles.history("BTC", start=start, end=end, interval="1h")
19
+ >>> for candle in result.data:
20
+ ... print(f"{candle.timestamp}: O={candle.open} H={candle.high} L={candle.low} C={candle.close}")
21
+ >>>
22
+ >>> # Paginate through large datasets
23
+ >>> all_candles = result.data
24
+ >>> while result.next_cursor:
25
+ ... result = client.candles.history("BTC", start=start, end=end, cursor=result.next_cursor)
26
+ ... all_candles.extend(result.data)
27
+ """
28
+
29
+ def __init__(self, http: HttpClient, base_path: str = "/v1"):
30
+ self._http = http
31
+ self._base_path = base_path
32
+
33
+ def _convert_timestamp(self, ts: Optional[Timestamp]) -> Optional[int]:
34
+ """Convert timestamp to Unix milliseconds."""
35
+ if ts is None:
36
+ return None
37
+ if isinstance(ts, int):
38
+ return ts
39
+ if isinstance(ts, datetime):
40
+ return int(ts.timestamp() * 1000)
41
+ if isinstance(ts, str):
42
+ try:
43
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
44
+ return int(dt.timestamp() * 1000)
45
+ except ValueError:
46
+ return int(ts)
47
+ return None
48
+
49
+ def history(
50
+ self,
51
+ coin: str,
52
+ *,
53
+ start: Timestamp,
54
+ end: Timestamp,
55
+ interval: Optional[CandleInterval] = None,
56
+ cursor: Optional[Timestamp] = None,
57
+ limit: Optional[int] = None,
58
+ ) -> CursorResponse[list[Candle]]:
59
+ """
60
+ Get historical OHLCV candle data with cursor-based pagination.
61
+
62
+ Args:
63
+ coin: The coin symbol (e.g., 'BTC', 'ETH')
64
+ start: Start timestamp (required)
65
+ end: End timestamp (required)
66
+ interval: Candle interval (1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w). Default: 1h
67
+ cursor: Cursor from previous response's next_cursor
68
+ limit: Maximum number of results (default: 100, max: 1000)
69
+
70
+ Returns:
71
+ CursorResponse with candle records and next_cursor for pagination
72
+
73
+ Example:
74
+ >>> result = client.candles.history("BTC", start=start, end=end, interval="1h", limit=1000)
75
+ >>> candles = result.data
76
+ >>> while result.next_cursor:
77
+ ... result = client.candles.history(
78
+ ... "BTC", start=start, end=end, interval="1h", cursor=result.next_cursor, limit=1000
79
+ ... )
80
+ ... candles.extend(result.data)
81
+ """
82
+ data = self._http.get(
83
+ f"{self._base_path}/candles/{coin.upper()}",
84
+ params={
85
+ "start": self._convert_timestamp(start),
86
+ "end": self._convert_timestamp(end),
87
+ "interval": interval,
88
+ "cursor": self._convert_timestamp(cursor),
89
+ "limit": limit,
90
+ },
91
+ )
92
+ return CursorResponse(
93
+ data=[Candle.model_validate(item) for item in data["data"]],
94
+ next_cursor=data.get("meta", {}).get("next_cursor"),
95
+ )
96
+
97
+ async def ahistory(
98
+ self,
99
+ coin: str,
100
+ *,
101
+ start: Timestamp,
102
+ end: Timestamp,
103
+ interval: Optional[CandleInterval] = None,
104
+ cursor: Optional[Timestamp] = None,
105
+ limit: Optional[int] = None,
106
+ ) -> CursorResponse[list[Candle]]:
107
+ """Async version of history(). start and end are required."""
108
+ data = await self._http.aget(
109
+ f"{self._base_path}/candles/{coin.upper()}",
110
+ params={
111
+ "start": self._convert_timestamp(start),
112
+ "end": self._convert_timestamp(end),
113
+ "interval": interval,
114
+ "cursor": self._convert_timestamp(cursor),
115
+ "limit": limit,
116
+ },
117
+ )
118
+ return CursorResponse(
119
+ data=[Candle.model_validate(item) for item in data["data"]],
120
+ next_cursor=data.get("meta", {}).get("next_cursor"),
121
+ )
oxarchive/types.py CHANGED
@@ -270,11 +270,48 @@ class OpenInterest(BaseModel):
270
270
  """Impact ask price for liquidations."""
271
271
 
272
272
 
273
+ # =============================================================================
274
+ # Candle Types
275
+ # =============================================================================
276
+
277
+
278
+ CandleInterval = Literal["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w"]
279
+ """Candle interval for OHLCV data."""
280
+
281
+
282
+ class Candle(BaseModel):
283
+ """OHLCV candle data."""
284
+
285
+ timestamp: datetime
286
+ """Candle open timestamp (UTC)."""
287
+
288
+ open: float
289
+ """Opening price."""
290
+
291
+ high: float
292
+ """Highest price during the interval."""
293
+
294
+ low: float
295
+ """Lowest price during the interval."""
296
+
297
+ close: float
298
+ """Closing price."""
299
+
300
+ volume: float
301
+ """Total volume traded during the interval."""
302
+
303
+ quote_volume: Optional[float] = None
304
+ """Total quote volume (volume * price)."""
305
+
306
+ trade_count: Optional[int] = None
307
+ """Number of trades during the interval."""
308
+
309
+
273
310
  # =============================================================================
274
311
  # WebSocket Types
275
312
  # =============================================================================
276
313
 
277
- WsChannel = Literal["orderbook", "trades", "ticker", "all_tickers"]
314
+ WsChannel = Literal["orderbook", "trades", "candles", "ticker", "all_tickers"]
278
315
  """Available WebSocket channels. Note: ticker/all_tickers are real-time only."""
279
316
 
280
317
  WsConnectionState = Literal["connecting", "connected", "disconnected", "reconnecting"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxarchive
3
- Version: 0.4.6
3
+ Version: 0.5.0
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
@@ -317,6 +317,57 @@ current = await client.hyperliquid.open_interest.acurrent("BTC")
317
317
  history = await client.hyperliquid.open_interest.ahistory("ETH", start=..., end=...)
318
318
  ```
319
319
 
320
+ ### Candles (OHLCV)
321
+
322
+ Get historical OHLCV candle data aggregated from trades.
323
+
324
+ ```python
325
+ # Get candle history (start is required)
326
+ candles = client.hyperliquid.candles.history(
327
+ "BTC",
328
+ start="2024-01-01",
329
+ end="2024-01-02",
330
+ interval="1h", # 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w
331
+ limit=100
332
+ )
333
+
334
+ # Iterate through candles
335
+ for candle in candles.data:
336
+ print(f"{candle.timestamp}: O={candle.open} H={candle.high} L={candle.low} C={candle.close} V={candle.volume}")
337
+
338
+ # Cursor-based pagination for large datasets
339
+ result = client.hyperliquid.candles.history("BTC", start=..., end=..., interval="1m", limit=1000)
340
+ while result.next_cursor:
341
+ result = client.hyperliquid.candles.history(
342
+ "BTC", start=..., end=..., interval="1m",
343
+ cursor=result.next_cursor, limit=1000
344
+ )
345
+
346
+ # Lighter.xyz candles
347
+ lighter_candles = client.lighter.candles.history(
348
+ "BTC",
349
+ start="2024-01-01",
350
+ end="2024-01-02",
351
+ interval="15m"
352
+ )
353
+
354
+ # Async versions
355
+ candles = await client.hyperliquid.candles.ahistory("BTC", start=..., end=..., interval="1h")
356
+ ```
357
+
358
+ #### Available Intervals
359
+
360
+ | Interval | Description |
361
+ |----------|-------------|
362
+ | `1m` | 1 minute |
363
+ | `5m` | 5 minutes |
364
+ | `15m` | 15 minutes |
365
+ | `30m` | 30 minutes |
366
+ | `1h` | 1 hour (default) |
367
+ | `4h` | 4 hours |
368
+ | `1d` | 1 day |
369
+ | `1w` | 1 week |
370
+
320
371
  ### Legacy API (Deprecated)
321
372
 
322
373
  The following legacy methods are deprecated and will be removed in v2.0. They default to Hyperliquid data:
@@ -505,12 +556,43 @@ ws = OxArchiveWs(WsOptions(
505
556
 
506
557
  ### Available Channels
507
558
 
508
- | Channel | Description | Requires Coin |
509
- |---------|-------------|---------------|
510
- | `orderbook` | L2 order book updates | Yes |
511
- | `trades` | Trade/fill updates | Yes |
512
- | `ticker` | Price and 24h volume | Yes |
513
- | `all_tickers` | All market tickers | No |
559
+ | Channel | Description | Requires Coin | Historical Support |
560
+ |---------|-------------|---------------|-------------------|
561
+ | `orderbook` | L2 order book updates | Yes | Yes |
562
+ | `trades` | Trade/fill updates | Yes | Yes |
563
+ | `candles` | OHLCV candle data | Yes | Yes (replay/stream only) |
564
+ | `ticker` | Price and 24h volume | Yes | Real-time only |
565
+ | `all_tickers` | All market tickers | No | Real-time only |
566
+
567
+ #### Candle Replay/Stream
568
+
569
+ ```python
570
+ # Replay candles at 10x speed
571
+ await ws.replay(
572
+ "candles", "BTC",
573
+ start=int(time.time() * 1000) - 86400000,
574
+ end=int(time.time() * 1000),
575
+ speed=10,
576
+ interval="15m" # 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w
577
+ )
578
+
579
+ # Bulk stream candles
580
+ await ws.stream(
581
+ "candles", "ETH",
582
+ start=int(time.time() * 1000) - 3600000,
583
+ end=int(time.time() * 1000),
584
+ batch_size=1000,
585
+ interval="1h"
586
+ )
587
+
588
+ # Lighter.xyz candles
589
+ await ws.replay(
590
+ "lighter_candles", "BTC",
591
+ start=...,
592
+ speed=10,
593
+ interval="5m"
594
+ )
595
+ ```
514
596
 
515
597
  ## Timestamp Formats
516
598
 
@@ -1,15 +1,16 @@
1
- oxarchive/__init__.py,sha256=z02RMa5pktqkvB4hOuzdeASD5j-qUUYebmkHL5UHSeE,2666
1
+ oxarchive/__init__.py,sha256=aBWv7E8wXOkAzpkf110EKknGLqTcUFToTzaOwoJn7fc,2738
2
2
  oxarchive/client.py,sha256=XWQ_VEBQy3UIAnmZQ-Z_FyzXnvMA3FITwtinBOf3o-Y,4453
3
- oxarchive/exchanges.py,sha256=kk--Uh4g1U_tPfhJhYtZD_1zR6tJS5D-iaPnOGwWa9c,2425
3
+ oxarchive/exchanges.py,sha256=ozw_eDGnzyb7D5HKMo2Hfl7-dIcVKSXJCyFlFIhiaos,2631
4
4
  oxarchive/http.py,sha256=SY_o9Ag8ADo1HI3i3uAKW1xwkYjPE75gRAjnMsddAGs,4211
5
- oxarchive/types.py,sha256=YHy04TYtuEmJSHDMRh_9D9MVnhnXVc2rGagc45LwPiQ,13380
5
+ oxarchive/types.py,sha256=LX5Usgn6RgUoS6l48ufQW2zKjzH1C3xjlV5UbsEp2fY,14293
6
6
  oxarchive/websocket.py,sha256=sdKMJrmGeR_ApNP_c7TPJylc4DAkmrp1JXfpnRGynao,29985
7
- oxarchive/resources/__init__.py,sha256=ZGS3rLOfQFD665oJYMia_MxfTWlal7MXtx874DLG5EE,448
7
+ oxarchive/resources/__init__.py,sha256=pFCP01qP6g9A9Dbzn-lGgF0i6x8GhGlwmwpOXGg3UWM,510
8
+ oxarchive/resources/candles.py,sha256=GI7-YSNFckEd1i49W9mlrLn1cl955sY8ki0u12TuLgw,4449
8
9
  oxarchive/resources/funding.py,sha256=ybMWkpoccrkdwnd6W3oHgsaor7cBcA2nkYy4CbjmHUg,4485
9
10
  oxarchive/resources/instruments.py,sha256=6q7rMdIaixXgFVXgwQsVd-YuO7RIXr1oGPT5jBsqI9A,3733
10
11
  oxarchive/resources/openinterest.py,sha256=whwo60KFNLGwvVrDmDYYc-rMZr35Fcizb5Iil-DSvD8,4553
11
12
  oxarchive/resources/orderbook.py,sha256=NzgKH45MBb2oAHyPmy6BSikaYyq0nM-8GFjur9LIRN8,6661
12
13
  oxarchive/resources/trades.py,sha256=t4iicyi1waaHzC7Q_cC-c7O4yVx1vqS4eMH8F8_5hxk,5503
13
- oxarchive-0.4.6.dist-info/METADATA,sha256=NUz-9REdplj8blQwCyS95rTiEaiNs6qGiJW4J6HFtWU,15945
14
- oxarchive-0.4.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- oxarchive-0.4.6.dist-info/RECORD,,
14
+ oxarchive-0.5.0.dist-info/METADATA,sha256=9-HN2MR1BRnmavdrtsFgC9FH_PawOysBey3YzneIzwo,17974
15
+ oxarchive-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ oxarchive-0.5.0.dist-info/RECORD,,