oxarchive 0.4.6__py3-none-any.whl → 0.5.1__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.1"
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"]
oxarchive/websocket.py CHANGED
@@ -434,6 +434,7 @@ class OxArchiveWs:
434
434
  end: Optional[int] = None,
435
435
  speed: float = 1.0,
436
436
  granularity: Optional[str] = None,
437
+ interval: Optional[str] = None,
437
438
  ) -> None:
438
439
  """Start historical replay with timing preserved.
439
440
 
@@ -444,9 +445,11 @@ class OxArchiveWs:
444
445
  end: End timestamp (Unix ms, defaults to now)
445
446
  speed: Playback speed multiplier (1 = real-time, 10 = 10x faster)
446
447
  granularity: Data resolution for Lighter orderbook ('checkpoint', '30s', '10s', '1s', 'tick')
448
+ interval: Candle interval for candles channel ('1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w')
447
449
 
448
450
  Example:
449
451
  >>> await ws.replay("orderbook", "BTC", start=time.time()*1000 - 86400000, speed=10)
452
+ >>> await ws.replay("candles", "BTC", start=..., speed=10, interval="15m")
450
453
  """
451
454
  msg = {
452
455
  "op": "replay",
@@ -459,6 +462,8 @@ class OxArchiveWs:
459
462
  msg["end"] = end
460
463
  if granularity is not None:
461
464
  msg["granularity"] = granularity
465
+ if interval is not None:
466
+ msg["interval"] = interval
462
467
  await self._send(msg)
463
468
 
464
469
  async def replay_pause(self) -> None:
@@ -493,6 +498,7 @@ class OxArchiveWs:
493
498
  end: int,
494
499
  batch_size: int = 1000,
495
500
  granularity: Optional[str] = None,
501
+ interval: Optional[str] = None,
496
502
  ) -> None:
497
503
  """Start bulk streaming for fast data download.
498
504
 
@@ -503,9 +509,11 @@ class OxArchiveWs:
503
509
  end: End timestamp (Unix ms)
504
510
  batch_size: Records per batch message
505
511
  granularity: Data resolution for Lighter orderbook ('checkpoint', '30s', '10s', '1s', 'tick')
512
+ interval: Candle interval for candles channel ('1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w')
506
513
 
507
514
  Example:
508
515
  >>> await ws.stream("orderbook", "ETH", start=..., end=..., batch_size=1000)
516
+ >>> await ws.stream("candles", "BTC", start=..., end=..., interval="1h")
509
517
  """
510
518
  msg = {
511
519
  "op": "stream",
@@ -517,6 +525,8 @@ class OxArchiveWs:
517
525
  }
518
526
  if granularity is not None:
519
527
  msg["granularity"] = granularity
528
+ if interval is not None:
529
+ msg["interval"] = interval
520
530
  await self._send(msg)
521
531
 
522
532
  async def stream_stop(self) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxarchive
3
- Version: 0.4.6
3
+ Version: 0.5.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
@@ -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
 
@@ -0,0 +1,16 @@
1
+ oxarchive/__init__.py,sha256=8-68R4dD9xFfeUQ1yAuy1IadC10el1QRBNJOn3DXMfA,2738
2
+ oxarchive/client.py,sha256=XWQ_VEBQy3UIAnmZQ-Z_FyzXnvMA3FITwtinBOf3o-Y,4453
3
+ oxarchive/exchanges.py,sha256=ozw_eDGnzyb7D5HKMo2Hfl7-dIcVKSXJCyFlFIhiaos,2631
4
+ oxarchive/http.py,sha256=SY_o9Ag8ADo1HI3i3uAKW1xwkYjPE75gRAjnMsddAGs,4211
5
+ oxarchive/types.py,sha256=LX5Usgn6RgUoS6l48ufQW2zKjzH1C3xjlV5UbsEp2fY,14293
6
+ oxarchive/websocket.py,sha256=sS-kLDKv2qS77-61hXChRePjL_hz-URcALM9UW5zxXU,30609
7
+ oxarchive/resources/__init__.py,sha256=pFCP01qP6g9A9Dbzn-lGgF0i6x8GhGlwmwpOXGg3UWM,510
8
+ oxarchive/resources/candles.py,sha256=GI7-YSNFckEd1i49W9mlrLn1cl955sY8ki0u12TuLgw,4449
9
+ oxarchive/resources/funding.py,sha256=ybMWkpoccrkdwnd6W3oHgsaor7cBcA2nkYy4CbjmHUg,4485
10
+ oxarchive/resources/instruments.py,sha256=6q7rMdIaixXgFVXgwQsVd-YuO7RIXr1oGPT5jBsqI9A,3733
11
+ oxarchive/resources/openinterest.py,sha256=whwo60KFNLGwvVrDmDYYc-rMZr35Fcizb5Iil-DSvD8,4553
12
+ oxarchive/resources/orderbook.py,sha256=NzgKH45MBb2oAHyPmy6BSikaYyq0nM-8GFjur9LIRN8,6661
13
+ oxarchive/resources/trades.py,sha256=t4iicyi1waaHzC7Q_cC-c7O4yVx1vqS4eMH8F8_5hxk,5503
14
+ oxarchive-0.5.1.dist-info/METADATA,sha256=iR7PKuDuF89N-I5b1QqcNyZNKr-OI2tJXBirT1-tYTU,17974
15
+ oxarchive-0.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ oxarchive-0.5.1.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- oxarchive/__init__.py,sha256=z02RMa5pktqkvB4hOuzdeASD5j-qUUYebmkHL5UHSeE,2666
2
- oxarchive/client.py,sha256=XWQ_VEBQy3UIAnmZQ-Z_FyzXnvMA3FITwtinBOf3o-Y,4453
3
- oxarchive/exchanges.py,sha256=kk--Uh4g1U_tPfhJhYtZD_1zR6tJS5D-iaPnOGwWa9c,2425
4
- oxarchive/http.py,sha256=SY_o9Ag8ADo1HI3i3uAKW1xwkYjPE75gRAjnMsddAGs,4211
5
- oxarchive/types.py,sha256=YHy04TYtuEmJSHDMRh_9D9MVnhnXVc2rGagc45LwPiQ,13380
6
- oxarchive/websocket.py,sha256=sdKMJrmGeR_ApNP_c7TPJylc4DAkmrp1JXfpnRGynao,29985
7
- oxarchive/resources/__init__.py,sha256=ZGS3rLOfQFD665oJYMia_MxfTWlal7MXtx874DLG5EE,448
8
- oxarchive/resources/funding.py,sha256=ybMWkpoccrkdwnd6W3oHgsaor7cBcA2nkYy4CbjmHUg,4485
9
- oxarchive/resources/instruments.py,sha256=6q7rMdIaixXgFVXgwQsVd-YuO7RIXr1oGPT5jBsqI9A,3733
10
- oxarchive/resources/openinterest.py,sha256=whwo60KFNLGwvVrDmDYYc-rMZr35Fcizb5Iil-DSvD8,4553
11
- oxarchive/resources/orderbook.py,sha256=NzgKH45MBb2oAHyPmy6BSikaYyq0nM-8GFjur9LIRN8,6661
12
- 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,,