oxarchive 0.2.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 +97 -0
- oxarchive/client.py +110 -0
- oxarchive/http.py +108 -0
- oxarchive/resources/__init__.py +15 -0
- oxarchive/resources/funding.py +113 -0
- oxarchive/resources/instruments.py +55 -0
- oxarchive/resources/openinterest.py +113 -0
- oxarchive/resources/orderbook.py +148 -0
- oxarchive/resources/trades.py +125 -0
- oxarchive/types.py +408 -0
- oxarchive/websocket.py +628 -0
- oxarchive-0.2.1.dist-info/METADATA +387 -0
- oxarchive-0.2.1.dist-info/RECORD +14 -0
- oxarchive-0.2.1.dist-info/WHEEL +4 -0
oxarchive/__init__.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oxarchive - Official Python SDK for 0xarchive
|
|
3
|
+
|
|
4
|
+
Hyperliquid Historical Data API.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oxarchive import Client
|
|
8
|
+
>>>
|
|
9
|
+
>>> client = Client(api_key="ox_your_api_key")
|
|
10
|
+
>>>
|
|
11
|
+
>>> # Get current order book
|
|
12
|
+
>>> orderbook = client.orderbook.get("BTC")
|
|
13
|
+
>>> print(f"BTC mid price: {orderbook.mid_price}")
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Get historical snapshots
|
|
16
|
+
>>> history = client.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .client import Client
|
|
20
|
+
from .types import (
|
|
21
|
+
OrderBook,
|
|
22
|
+
Trade,
|
|
23
|
+
Instrument,
|
|
24
|
+
FundingRate,
|
|
25
|
+
OpenInterest,
|
|
26
|
+
OxArchiveError,
|
|
27
|
+
# WebSocket types
|
|
28
|
+
WsChannel,
|
|
29
|
+
WsConnectionState,
|
|
30
|
+
WsSubscribed,
|
|
31
|
+
WsUnsubscribed,
|
|
32
|
+
WsPong,
|
|
33
|
+
WsError,
|
|
34
|
+
WsData,
|
|
35
|
+
# Replay types (Option B)
|
|
36
|
+
WsReplayStarted,
|
|
37
|
+
WsReplayPaused,
|
|
38
|
+
WsReplayResumed,
|
|
39
|
+
WsReplayCompleted,
|
|
40
|
+
WsReplayStopped,
|
|
41
|
+
WsHistoricalData,
|
|
42
|
+
# Stream types (Option D)
|
|
43
|
+
WsStreamStarted,
|
|
44
|
+
WsStreamProgress,
|
|
45
|
+
WsHistoricalBatch,
|
|
46
|
+
WsStreamCompleted,
|
|
47
|
+
WsStreamStopped,
|
|
48
|
+
TimestampedRecord,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# WebSocket client (optional import - requires websockets package)
|
|
52
|
+
try:
|
|
53
|
+
from .websocket import OxArchiveWs, WsOptions
|
|
54
|
+
_HAS_WEBSOCKET = True
|
|
55
|
+
except ImportError:
|
|
56
|
+
_HAS_WEBSOCKET = False
|
|
57
|
+
OxArchiveWs = None # type: ignore
|
|
58
|
+
WsOptions = None # type: ignore
|
|
59
|
+
|
|
60
|
+
__version__ = "0.1.0"
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
# Client
|
|
64
|
+
"Client",
|
|
65
|
+
# WebSocket Client
|
|
66
|
+
"OxArchiveWs",
|
|
67
|
+
"WsOptions",
|
|
68
|
+
# Types
|
|
69
|
+
"OrderBook",
|
|
70
|
+
"Trade",
|
|
71
|
+
"Instrument",
|
|
72
|
+
"FundingRate",
|
|
73
|
+
"OpenInterest",
|
|
74
|
+
"OxArchiveError",
|
|
75
|
+
# WebSocket Types
|
|
76
|
+
"WsChannel",
|
|
77
|
+
"WsConnectionState",
|
|
78
|
+
"WsSubscribed",
|
|
79
|
+
"WsUnsubscribed",
|
|
80
|
+
"WsPong",
|
|
81
|
+
"WsError",
|
|
82
|
+
"WsData",
|
|
83
|
+
# Replay Types (Option B)
|
|
84
|
+
"WsReplayStarted",
|
|
85
|
+
"WsReplayPaused",
|
|
86
|
+
"WsReplayResumed",
|
|
87
|
+
"WsReplayCompleted",
|
|
88
|
+
"WsReplayStopped",
|
|
89
|
+
"WsHistoricalData",
|
|
90
|
+
# Stream Types (Option D)
|
|
91
|
+
"WsStreamStarted",
|
|
92
|
+
"WsStreamProgress",
|
|
93
|
+
"WsHistoricalBatch",
|
|
94
|
+
"WsStreamCompleted",
|
|
95
|
+
"WsStreamStopped",
|
|
96
|
+
"TimestampedRecord",
|
|
97
|
+
]
|
oxarchive/client.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""0xarchive API client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .http import HttpClient
|
|
8
|
+
from .resources import (
|
|
9
|
+
OrderBookResource,
|
|
10
|
+
TradesResource,
|
|
11
|
+
InstrumentsResource,
|
|
12
|
+
FundingResource,
|
|
13
|
+
OpenInterestResource,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
DEFAULT_BASE_URL = "https://api.0xarchive.io"
|
|
17
|
+
DEFAULT_TIMEOUT = 30.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Client:
|
|
21
|
+
"""
|
|
22
|
+
0xarchive API client.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> from oxarchive import Client
|
|
26
|
+
>>>
|
|
27
|
+
>>> client = Client(api_key="ox_your_api_key")
|
|
28
|
+
>>>
|
|
29
|
+
>>> # Get current order book
|
|
30
|
+
>>> orderbook = client.orderbook.get("BTC")
|
|
31
|
+
>>> print(f"BTC mid price: {orderbook.mid_price}")
|
|
32
|
+
>>>
|
|
33
|
+
>>> # Get historical snapshots
|
|
34
|
+
>>> history = client.orderbook.history("ETH", start="2024-01-01", end="2024-01-02")
|
|
35
|
+
>>>
|
|
36
|
+
>>> # List all instruments
|
|
37
|
+
>>> instruments = client.instruments.list()
|
|
38
|
+
|
|
39
|
+
Async example:
|
|
40
|
+
>>> import asyncio
|
|
41
|
+
>>> from oxarchive import Client
|
|
42
|
+
>>>
|
|
43
|
+
>>> async def main():
|
|
44
|
+
... client = Client(api_key="ox_your_api_key")
|
|
45
|
+
... orderbook = await client.orderbook.aget("BTC")
|
|
46
|
+
... print(f"BTC mid price: {orderbook.mid_price}")
|
|
47
|
+
... await client.aclose()
|
|
48
|
+
>>>
|
|
49
|
+
>>> asyncio.run(main())
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
api_key: str,
|
|
55
|
+
*,
|
|
56
|
+
base_url: Optional[str] = None,
|
|
57
|
+
timeout: Optional[float] = None,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Create a new 0xarchive client.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
api_key: Your 0xarchive API key
|
|
64
|
+
base_url: Base URL for the API (defaults to https://api.0xarchive.io)
|
|
65
|
+
timeout: Request timeout in seconds (defaults to 30.0)
|
|
66
|
+
"""
|
|
67
|
+
if not api_key:
|
|
68
|
+
raise ValueError("API key is required. Get one at https://0xarchive.io/signup")
|
|
69
|
+
|
|
70
|
+
self._http = HttpClient(
|
|
71
|
+
base_url=base_url or DEFAULT_BASE_URL,
|
|
72
|
+
api_key=api_key,
|
|
73
|
+
timeout=timeout or DEFAULT_TIMEOUT,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Initialize resource namespaces
|
|
77
|
+
self.orderbook = OrderBookResource(self._http)
|
|
78
|
+
"""Order book data (L2 snapshots from April 2023)"""
|
|
79
|
+
|
|
80
|
+
self.trades = TradesResource(self._http)
|
|
81
|
+
"""Trade/fill history"""
|
|
82
|
+
|
|
83
|
+
self.instruments = InstrumentsResource(self._http)
|
|
84
|
+
"""Trading instruments metadata"""
|
|
85
|
+
|
|
86
|
+
self.funding = FundingResource(self._http)
|
|
87
|
+
"""Funding rates"""
|
|
88
|
+
|
|
89
|
+
self.open_interest = OpenInterestResource(self._http)
|
|
90
|
+
"""Open interest"""
|
|
91
|
+
|
|
92
|
+
def close(self) -> None:
|
|
93
|
+
"""Close the HTTP client and release resources."""
|
|
94
|
+
self._http.close()
|
|
95
|
+
|
|
96
|
+
async def aclose(self) -> None:
|
|
97
|
+
"""Close the async HTTP client and release resources."""
|
|
98
|
+
await self._http.aclose()
|
|
99
|
+
|
|
100
|
+
def __enter__(self) -> "Client":
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def __exit__(self, *args) -> None:
|
|
104
|
+
self.close()
|
|
105
|
+
|
|
106
|
+
async def __aenter__(self) -> "Client":
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
async def __aexit__(self, *args) -> None:
|
|
110
|
+
await self.aclose()
|
oxarchive/http.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""HTTP client for the 0xarchive API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional, TypeVar, Type
|
|
6
|
+
import httpx
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from .types import OxArchiveError
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T", bound=BaseModel)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HttpClient:
|
|
15
|
+
"""Internal HTTP client for making API requests."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, base_url: str, api_key: str, timeout: float):
|
|
18
|
+
self.base_url = base_url.rstrip("/")
|
|
19
|
+
self.api_key = api_key
|
|
20
|
+
self.timeout = timeout
|
|
21
|
+
self._client: Optional[httpx.Client] = None
|
|
22
|
+
self._async_client: Optional[httpx.AsyncClient] = None
|
|
23
|
+
|
|
24
|
+
def _get_headers(self) -> dict[str, str]:
|
|
25
|
+
return {
|
|
26
|
+
"X-API-Key": self.api_key,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def client(self) -> httpx.Client:
|
|
32
|
+
"""Get or create the sync HTTP client."""
|
|
33
|
+
if self._client is None:
|
|
34
|
+
self._client = httpx.Client(
|
|
35
|
+
base_url=self.base_url,
|
|
36
|
+
headers=self._get_headers(),
|
|
37
|
+
timeout=self.timeout,
|
|
38
|
+
)
|
|
39
|
+
return self._client
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def async_client(self) -> httpx.AsyncClient:
|
|
43
|
+
"""Get or create the async HTTP client."""
|
|
44
|
+
if self._async_client is None:
|
|
45
|
+
self._async_client = httpx.AsyncClient(
|
|
46
|
+
base_url=self.base_url,
|
|
47
|
+
headers=self._get_headers(),
|
|
48
|
+
timeout=self.timeout,
|
|
49
|
+
)
|
|
50
|
+
return self._async_client
|
|
51
|
+
|
|
52
|
+
def close(self) -> None:
|
|
53
|
+
"""Close the HTTP clients."""
|
|
54
|
+
if self._client is not None:
|
|
55
|
+
self._client.close()
|
|
56
|
+
self._client = None
|
|
57
|
+
if self._async_client is not None:
|
|
58
|
+
# Note: async client should be closed with await
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
async def aclose(self) -> None:
|
|
62
|
+
"""Close the async HTTP client."""
|
|
63
|
+
if self._async_client is not None:
|
|
64
|
+
await self._async_client.aclose()
|
|
65
|
+
self._async_client = None
|
|
66
|
+
|
|
67
|
+
def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
|
|
68
|
+
"""Handle the API response and raise errors if needed."""
|
|
69
|
+
try:
|
|
70
|
+
data = response.json()
|
|
71
|
+
except Exception:
|
|
72
|
+
raise OxArchiveError(
|
|
73
|
+
f"Invalid JSON response: {response.text[:200]}",
|
|
74
|
+
response.status_code,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if not response.is_success:
|
|
78
|
+
error_msg = data.get("error", f"Request failed with status {response.status_code}")
|
|
79
|
+
request_id = data.get("request_id")
|
|
80
|
+
raise OxArchiveError(error_msg, response.status_code, request_id)
|
|
81
|
+
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
def get(
|
|
85
|
+
self,
|
|
86
|
+
path: str,
|
|
87
|
+
params: Optional[dict[str, Any]] = None,
|
|
88
|
+
) -> dict[str, Any]:
|
|
89
|
+
"""Make a synchronous GET request."""
|
|
90
|
+
# Filter out None values from params
|
|
91
|
+
if params:
|
|
92
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
93
|
+
|
|
94
|
+
response = self.client.get(path, params=params)
|
|
95
|
+
return self._handle_response(response)
|
|
96
|
+
|
|
97
|
+
async def aget(
|
|
98
|
+
self,
|
|
99
|
+
path: str,
|
|
100
|
+
params: Optional[dict[str, Any]] = None,
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
|
+
"""Make an asynchronous GET request."""
|
|
103
|
+
# Filter out None values from params
|
|
104
|
+
if params:
|
|
105
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
106
|
+
|
|
107
|
+
response = await self.async_client.get(path, params=params)
|
|
108
|
+
return self._handle_response(response)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Resource modules."""
|
|
2
|
+
|
|
3
|
+
from .orderbook import OrderBookResource
|
|
4
|
+
from .trades import TradesResource
|
|
5
|
+
from .instruments import InstrumentsResource
|
|
6
|
+
from .funding import FundingResource
|
|
7
|
+
from .openinterest import OpenInterestResource
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"OrderBookResource",
|
|
11
|
+
"TradesResource",
|
|
12
|
+
"InstrumentsResource",
|
|
13
|
+
"FundingResource",
|
|
14
|
+
"OpenInterestResource",
|
|
15
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Funding rates 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 FundingRate, Timestamp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FundingResource:
|
|
13
|
+
"""
|
|
14
|
+
Funding rates API resource.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> # Get current funding rate
|
|
18
|
+
>>> current = client.funding.current("BTC")
|
|
19
|
+
>>>
|
|
20
|
+
>>> # Get funding rate history
|
|
21
|
+
>>> history = client.funding.history("ETH", start="2024-01-01", end="2024-01-07")
|
|
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 history(
|
|
44
|
+
self,
|
|
45
|
+
coin: str,
|
|
46
|
+
*,
|
|
47
|
+
start: Optional[Timestamp] = None,
|
|
48
|
+
end: Optional[Timestamp] = None,
|
|
49
|
+
limit: Optional[int] = None,
|
|
50
|
+
offset: Optional[int] = None,
|
|
51
|
+
) -> list[FundingRate]:
|
|
52
|
+
"""
|
|
53
|
+
Get funding rate history for a coin.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
coin: The coin symbol (e.g., 'BTC', 'ETH')
|
|
57
|
+
start: Start timestamp
|
|
58
|
+
end: End timestamp
|
|
59
|
+
limit: Maximum number of results
|
|
60
|
+
offset: Number of results to skip
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of funding rate records
|
|
64
|
+
"""
|
|
65
|
+
data = self._http.get(
|
|
66
|
+
f"/v1/funding/{coin.upper()}",
|
|
67
|
+
params={
|
|
68
|
+
"start": self._convert_timestamp(start),
|
|
69
|
+
"end": self._convert_timestamp(end),
|
|
70
|
+
"limit": limit,
|
|
71
|
+
"offset": offset,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
return [FundingRate.model_validate(item) for item in data["data"]]
|
|
75
|
+
|
|
76
|
+
async def ahistory(
|
|
77
|
+
self,
|
|
78
|
+
coin: str,
|
|
79
|
+
*,
|
|
80
|
+
start: Optional[Timestamp] = None,
|
|
81
|
+
end: Optional[Timestamp] = None,
|
|
82
|
+
limit: Optional[int] = None,
|
|
83
|
+
offset: Optional[int] = None,
|
|
84
|
+
) -> list[FundingRate]:
|
|
85
|
+
"""Async version of history()."""
|
|
86
|
+
data = await self._http.aget(
|
|
87
|
+
f"/v1/funding/{coin.upper()}",
|
|
88
|
+
params={
|
|
89
|
+
"start": self._convert_timestamp(start),
|
|
90
|
+
"end": self._convert_timestamp(end),
|
|
91
|
+
"limit": limit,
|
|
92
|
+
"offset": offset,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
return [FundingRate.model_validate(item) for item in data["data"]]
|
|
96
|
+
|
|
97
|
+
def current(self, coin: str) -> FundingRate:
|
|
98
|
+
"""
|
|
99
|
+
Get current funding rate for a coin.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
coin: The coin symbol (e.g., 'BTC', 'ETH')
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Current funding rate
|
|
106
|
+
"""
|
|
107
|
+
data = self._http.get(f"/v1/funding/{coin.upper()}/current")
|
|
108
|
+
return FundingRate.model_validate(data["data"])
|
|
109
|
+
|
|
110
|
+
async def acurrent(self, coin: str) -> FundingRate:
|
|
111
|
+
"""Async version of current()."""
|
|
112
|
+
data = await self._http.aget(f"/v1/funding/{coin.upper()}/current")
|
|
113
|
+
return FundingRate.model_validate(data["data"])
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Instruments API resource."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..http import HttpClient
|
|
6
|
+
from ..types import Instrument
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InstrumentsResource:
|
|
10
|
+
"""
|
|
11
|
+
Instruments API resource.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> # List all instruments
|
|
15
|
+
>>> instruments = client.instruments.list()
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Get specific instrument
|
|
18
|
+
>>> btc = client.instruments.get("BTC")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, http: HttpClient):
|
|
22
|
+
self._http = http
|
|
23
|
+
|
|
24
|
+
def list(self) -> list[Instrument]:
|
|
25
|
+
"""
|
|
26
|
+
List all available trading instruments.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of instruments
|
|
30
|
+
"""
|
|
31
|
+
data = self._http.get("/v1/instruments")
|
|
32
|
+
return [Instrument.model_validate(item) for item in data["data"]]
|
|
33
|
+
|
|
34
|
+
async def alist(self) -> list[Instrument]:
|
|
35
|
+
"""Async version of list()."""
|
|
36
|
+
data = await self._http.aget("/v1/instruments")
|
|
37
|
+
return [Instrument.model_validate(item) for item in data["data"]]
|
|
38
|
+
|
|
39
|
+
def get(self, coin: str) -> Instrument:
|
|
40
|
+
"""
|
|
41
|
+
Get a specific instrument by coin symbol.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
coin: The coin symbol (e.g., 'BTC', 'ETH')
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Instrument details
|
|
48
|
+
"""
|
|
49
|
+
data = self._http.get(f"/v1/instruments/{coin.upper()}")
|
|
50
|
+
return Instrument.model_validate(data["data"])
|
|
51
|
+
|
|
52
|
+
async def aget(self, coin: str) -> Instrument:
|
|
53
|
+
"""Async version of get()."""
|
|
54
|
+
data = await self._http.aget(f"/v1/instruments/{coin.upper()}")
|
|
55
|
+
return Instrument.model_validate(data["data"])
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Open interest 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 OpenInterest, Timestamp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenInterestResource:
|
|
13
|
+
"""
|
|
14
|
+
Open interest API resource.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> # Get current open interest
|
|
18
|
+
>>> current = client.open_interest.current("BTC")
|
|
19
|
+
>>>
|
|
20
|
+
>>> # Get open interest history
|
|
21
|
+
>>> history = client.open_interest.history("ETH", start="2024-01-01", end="2024-01-07")
|
|
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 history(
|
|
44
|
+
self,
|
|
45
|
+
coin: str,
|
|
46
|
+
*,
|
|
47
|
+
start: Optional[Timestamp] = None,
|
|
48
|
+
end: Optional[Timestamp] = None,
|
|
49
|
+
limit: Optional[int] = None,
|
|
50
|
+
offset: Optional[int] = None,
|
|
51
|
+
) -> list[OpenInterest]:
|
|
52
|
+
"""
|
|
53
|
+
Get open interest history for a coin.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
coin: The coin symbol (e.g., 'BTC', 'ETH')
|
|
57
|
+
start: Start timestamp
|
|
58
|
+
end: End timestamp
|
|
59
|
+
limit: Maximum number of results
|
|
60
|
+
offset: Number of results to skip
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of open interest records
|
|
64
|
+
"""
|
|
65
|
+
data = self._http.get(
|
|
66
|
+
f"/v1/openinterest/{coin.upper()}",
|
|
67
|
+
params={
|
|
68
|
+
"start": self._convert_timestamp(start),
|
|
69
|
+
"end": self._convert_timestamp(end),
|
|
70
|
+
"limit": limit,
|
|
71
|
+
"offset": offset,
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
return [OpenInterest.model_validate(item) for item in data["data"]]
|
|
75
|
+
|
|
76
|
+
async def ahistory(
|
|
77
|
+
self,
|
|
78
|
+
coin: str,
|
|
79
|
+
*,
|
|
80
|
+
start: Optional[Timestamp] = None,
|
|
81
|
+
end: Optional[Timestamp] = None,
|
|
82
|
+
limit: Optional[int] = None,
|
|
83
|
+
offset: Optional[int] = None,
|
|
84
|
+
) -> list[OpenInterest]:
|
|
85
|
+
"""Async version of history()."""
|
|
86
|
+
data = await self._http.aget(
|
|
87
|
+
f"/v1/openinterest/{coin.upper()}",
|
|
88
|
+
params={
|
|
89
|
+
"start": self._convert_timestamp(start),
|
|
90
|
+
"end": self._convert_timestamp(end),
|
|
91
|
+
"limit": limit,
|
|
92
|
+
"offset": offset,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
return [OpenInterest.model_validate(item) for item in data["data"]]
|
|
96
|
+
|
|
97
|
+
def current(self, coin: str) -> OpenInterest:
|
|
98
|
+
"""
|
|
99
|
+
Get current open interest for a coin.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
coin: The coin symbol (e.g., 'BTC', 'ETH')
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Current open interest
|
|
106
|
+
"""
|
|
107
|
+
data = self._http.get(f"/v1/openinterest/{coin.upper()}/current")
|
|
108
|
+
return OpenInterest.model_validate(data["data"])
|
|
109
|
+
|
|
110
|
+
async def acurrent(self, coin: str) -> OpenInterest:
|
|
111
|
+
"""Async version of current()."""
|
|
112
|
+
data = await self._http.aget(f"/v1/openinterest/{coin.upper()}/current")
|
|
113
|
+
return OpenInterest.model_validate(data["data"])
|