liquidtrading-python 0.1.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.
@@ -0,0 +1,296 @@
1
+ """Frozen dataclasses matching server response shapes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+
9
+ def _to_float(value: object) -> float:
10
+ return float(value)
11
+
12
+
13
+ def _to_float_or_none(value: object) -> float | None:
14
+ return float(value) if value is not None else None
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Market:
19
+ symbol: str
20
+ ticker: str
21
+ exchange: str
22
+ max_leverage: int | None
23
+ sz_decimals: int | None
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Ticker:
28
+ symbol: str
29
+ mark_price: float
30
+ volume_24h: float | None
31
+ change_24h: float | None
32
+ funding_rate: float | None
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class OrderbookLevel:
37
+ price: float
38
+ size: float
39
+ count: int
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class Orderbook:
44
+ symbol: str
45
+ bids: tuple[OrderbookLevel, ...]
46
+ asks: tuple[OrderbookLevel, ...]
47
+ timestamp: int | None
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class Candle:
52
+ timestamp: int
53
+ open: float
54
+ high: float
55
+ low: float
56
+ close: float
57
+ volume: float
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class Account:
62
+ equity: float
63
+ margin_used: float
64
+ available_balance: float
65
+ account_value: float
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class Position:
70
+ symbol: str
71
+ side: str
72
+ size: float
73
+ entry_price: float
74
+ leverage: float
75
+ unrealized_pnl: float
76
+ mark_price: float | None
77
+ liquidation_price: float | None
78
+ margin_used: float | None
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class Order:
83
+ order_id: str | None
84
+ symbol: str
85
+ side: str
86
+ type: str
87
+ size: float
88
+ price: float | None
89
+ leverage: int
90
+ status: str
91
+ exchange: str
92
+ tp: float | None
93
+ sl: float | None
94
+ reduce_only: bool
95
+ created_at: str | None
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class OpenOrder:
100
+ order_id: str
101
+ symbol: str
102
+ side: str
103
+ size: float
104
+ price: float
105
+ timestamp: int
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class CloseResult:
110
+ symbol: str
111
+ status: str
112
+ message: str
113
+
114
+
115
+ @dataclass(frozen=True)
116
+ class TpSlResult:
117
+ tp: dict[str, Any] | None
118
+ sl: dict[str, Any] | None
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class MarginResult:
123
+ symbol: str
124
+ margin_adjusted: float
125
+
126
+
127
+ @dataclass(frozen=True)
128
+ class LeverageResult:
129
+ symbol: str
130
+ leverage: int
131
+ is_cross: bool
132
+
133
+
134
+ @dataclass(frozen=True)
135
+ class CrossMargin:
136
+ account_value: float
137
+ total_margin_used: float
138
+ total_ntl_pos: float
139
+
140
+
141
+ @dataclass(frozen=True)
142
+ class Balance:
143
+ exchange: str
144
+ equity: float
145
+ available_balance: float
146
+ margin_used: float
147
+ cross_margin: CrossMargin | None
148
+
149
+
150
+ # --- Factory functions ---
151
+
152
+
153
+ def _market_from_dict(d: dict[str, Any]) -> Market:
154
+ return Market(
155
+ symbol=d["symbol"],
156
+ ticker=d["ticker"],
157
+ exchange=d["exchange"],
158
+ max_leverage=d.get("max_leverage"),
159
+ sz_decimals=d.get("sz_decimals"),
160
+ )
161
+
162
+
163
+ def _ticker_from_dict(d: dict[str, Any]) -> Ticker:
164
+ return Ticker(
165
+ symbol=d["symbol"],
166
+ mark_price=_to_float(d["mark_price"]),
167
+ volume_24h=_to_float_or_none(d.get("volume_24h")),
168
+ change_24h=_to_float_or_none(d.get("change_24h")),
169
+ funding_rate=_to_float_or_none(d.get("funding_rate")),
170
+ )
171
+
172
+
173
+ def _orderbook_level_from_dict(d: dict[str, Any]) -> OrderbookLevel:
174
+ return OrderbookLevel(
175
+ price=_to_float(d["price"]),
176
+ size=_to_float(d["size"]),
177
+ count=d.get("count", 1),
178
+ )
179
+
180
+
181
+ def _orderbook_from_dict(d: dict[str, Any]) -> Orderbook:
182
+ return Orderbook(
183
+ symbol=d["symbol"],
184
+ bids=tuple(_orderbook_level_from_dict(b) for b in d["bids"]),
185
+ asks=tuple(_orderbook_level_from_dict(a) for a in d["asks"]),
186
+ timestamp=d.get("timestamp"),
187
+ )
188
+
189
+
190
+ def _candle_from_dict(d: dict[str, Any]) -> Candle:
191
+ return Candle(
192
+ timestamp=d["timestamp"],
193
+ open=_to_float(d["open"]),
194
+ high=_to_float(d["high"]),
195
+ low=_to_float(d["low"]),
196
+ close=_to_float(d["close"]),
197
+ volume=_to_float(d["volume"]),
198
+ )
199
+
200
+
201
+ def _account_from_dict(d: dict[str, Any]) -> Account:
202
+ return Account(
203
+ equity=_to_float(d["equity"]),
204
+ margin_used=_to_float(d["margin_used"]),
205
+ available_balance=_to_float(d["available_balance"]),
206
+ account_value=_to_float(d["account_value"]),
207
+ )
208
+
209
+
210
+ def _position_from_dict(d: dict[str, Any]) -> Position:
211
+ return Position(
212
+ symbol=d["symbol"],
213
+ side=d["side"],
214
+ size=_to_float(d["size"]),
215
+ entry_price=_to_float(d["entry_price"]),
216
+ leverage=_to_float(d["leverage"]),
217
+ unrealized_pnl=_to_float(d["unrealized_pnl"]),
218
+ mark_price=_to_float_or_none(d.get("mark_price")),
219
+ liquidation_price=_to_float_or_none(d.get("liquidation_price")),
220
+ margin_used=_to_float_or_none(d.get("margin_used")),
221
+ )
222
+
223
+
224
+ def _order_from_dict(d: dict[str, Any]) -> Order:
225
+ return Order(
226
+ order_id=d.get("order_id"),
227
+ symbol=d["symbol"],
228
+ side=d["side"],
229
+ type=d.get("type", "unknown"),
230
+ size=d.get("size", 0.0),
231
+ price=d.get("price"),
232
+ leverage=d.get("leverage", 1),
233
+ status=d.get("status", "unknown"),
234
+ exchange=d.get("exchange", "hyperliquid"),
235
+ tp=d.get("tp"),
236
+ sl=d.get("sl"),
237
+ reduce_only=d.get("reduce_only", False),
238
+ created_at=d.get("created_at"),
239
+ )
240
+
241
+
242
+ def _open_order_from_dict(d: dict[str, Any]) -> OpenOrder:
243
+ return OpenOrder(
244
+ order_id=d["order_id"],
245
+ symbol=d["symbol"],
246
+ side=d["side"],
247
+ size=_to_float(d["size"]),
248
+ price=_to_float(d["price"]),
249
+ timestamp=d["timestamp"],
250
+ )
251
+
252
+
253
+ def _close_result_from_dict(d: dict[str, Any]) -> CloseResult:
254
+ return CloseResult(
255
+ symbol=d["symbol"],
256
+ status=d["status"],
257
+ message=d["message"],
258
+ )
259
+
260
+
261
+ def _tp_sl_result_from_dict(d: dict[str, Any]) -> TpSlResult:
262
+ return TpSlResult(tp=d.get("tp"), sl=d.get("sl"))
263
+
264
+
265
+ def _margin_result_from_dict(d: dict[str, Any]) -> MarginResult:
266
+ return MarginResult(
267
+ symbol=d["symbol"],
268
+ margin_adjusted=d["margin_adjusted"],
269
+ )
270
+
271
+
272
+ def _leverage_result_from_dict(d: dict[str, Any]) -> LeverageResult:
273
+ return LeverageResult(
274
+ symbol=d["symbol"],
275
+ leverage=d["leverage"],
276
+ is_cross=d["is_cross"],
277
+ )
278
+
279
+
280
+ def _cross_margin_from_dict(d: dict[str, Any]) -> CrossMargin:
281
+ return CrossMargin(
282
+ account_value=_to_float(d["account_value"]),
283
+ total_margin_used=_to_float(d["total_margin_used"]),
284
+ total_ntl_pos=_to_float(d["total_ntl_pos"]),
285
+ )
286
+
287
+
288
+ def _balance_from_dict(d: dict[str, Any]) -> Balance:
289
+ cm = d.get("cross_margin")
290
+ return Balance(
291
+ exchange=d["exchange"],
292
+ equity=_to_float(d["equity"]),
293
+ available_balance=_to_float(d["available_balance"]),
294
+ margin_used=_to_float(d["margin_used"]),
295
+ cross_margin=_cross_margin_from_dict(cm) if isinstance(cm, dict) else None,
296
+ )
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: liquidtrading-python
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Liquid trading API
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: httpx>=0.25.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=7.0; extra == 'dev'
10
+ Requires-Dist: respx>=0.21; extra == 'dev'
11
+ Requires-Dist: ruff>=0.4; extra == 'dev'
12
+ Description-Content-Type: text/markdown
13
+
14
+ # liquidtrading-python
15
+
16
+ Official Python SDK for the Liquid trading API.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install liquidtrading-python
22
+ ```
23
+
24
+ ```bash
25
+ uv add liquidtrading-python
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```python
31
+ from liquidtrading import LiquidClient
32
+
33
+ client = LiquidClient(api_key="lq_...", api_secret="sk_...")
34
+ ticker = client.get_ticker("BTC-PERP")
35
+ print(f"BTC: {ticker.mark_price}")
36
+ ```
37
+
38
+ Legacy compatibility import:
39
+
40
+ ```python
41
+ from liquid import Client
42
+ ```
43
+
44
+ ## Authentication
45
+
46
+ ```python
47
+ client = LiquidClient(api_key="lq_...", api_secret="sk_...")
48
+ ```
49
+
50
+ Hosted Liquid API requests currently require credentials:
51
+
52
+ ```python
53
+ client = LiquidClient(api_key="lq_...", api_secret="sk_...")
54
+ markets = client.get_markets()
55
+ ```
56
+
57
+ Quick connectivity check after install:
58
+
59
+ ```bash
60
+ liquidtrading-demo --base-url http://localhost:8001 --api-key lq_... --api-secret sk_...
61
+ ```
62
+
63
+ If you also want to query open orders:
64
+
65
+ ```bash
66
+ liquidtrading-demo --base-url http://localhost:8001 --api-key lq_... --api-secret sk_... --include-open-orders
67
+ ```
68
+
69
+ Easiest local repo smoke test:
70
+
71
+ ```bash
72
+ bash scripts/run_smoke_demo.sh --base-url http://localhost:8001 --api-key lq_... --api-secret sk_...
73
+ ```
74
+
75
+ Alternative local repo smoke test:
76
+
77
+ ```bash
78
+ uv run python examples/smoke_demo.py --base-url http://localhost:8001 --api-key lq_... --api-secret sk_...
79
+ ```
80
+
81
+ Publishing guide:
82
+
83
+ ```text
84
+ See PUBLISHING.md
85
+ ```
86
+
87
+ ## Methods
88
+
89
+ ### Market Data (public)
90
+
91
+ ```python
92
+ client.get_markets() # list[dict]
93
+ client.get_ticker("BTC-PERP") # Ticker
94
+ client.get_orderbook("BTC-PERP", depth=20) # Orderbook
95
+ client.get_candles("BTC-PERP", interval="1h", limit=100) # list[Candle]
96
+ ```
97
+
98
+ ### Account (read scope)
99
+
100
+ ```python
101
+ client.get_account() # Account
102
+ client.get_balances() # Balance
103
+ client.get_positions() # list[Position]
104
+ ```
105
+
106
+ ### Orders (trade scope)
107
+
108
+ ```python
109
+ client.place_order("BTC-PERP", "buy", size=100, leverage=10) # Order
110
+ client.place_order("ETH-PERP", "sell", type="limit", size=500, leverage=5,
111
+ price=4000, tp=3800, sl=4200, time_in_force="gtc") # Order
112
+ client.get_open_orders() # list[OpenOrder]
113
+ client.get_order("order-id") # Order
114
+ client.cancel_order("order-id") # bool
115
+ client.cancel_all_orders() # int
116
+ ```
117
+
118
+ ### Positions (trade scope)
119
+
120
+ ```python
121
+ client.close_position("BTC-PERP") # CloseResult
122
+ client.set_tp_sl("BTC-PERP", tp=70000, sl=60000) # TpSlResult
123
+ client.update_leverage("BTC-PERP", leverage=20) # LeverageResult
124
+ client.update_margin("BTC-PERP", amount=50) # MarginResult
125
+ ```
126
+
127
+ ## Error Handling
128
+
129
+ ```python
130
+ from liquidtrading import (
131
+ LiquidClient,
132
+ AuthenticationError,
133
+ InvalidSignatureError,
134
+ InvalidApiKeyError,
135
+ RateLimitError,
136
+ InsufficientBalanceError,
137
+ InsufficientScopeError,
138
+ )
139
+
140
+ try:
141
+ order = client.place_order("BTC-PERP", "buy", size=100, leverage=10)
142
+ except InvalidApiKeyError:
143
+ print("API key is invalid or revoked")
144
+ except InvalidSignatureError:
145
+ print("Check your API secret")
146
+ except InsufficientScopeError:
147
+ print("API key needs trade permissions")
148
+ except RateLimitError as e:
149
+ print(f"Rate limited, retry after {e.retry_after}s")
150
+ except InsufficientBalanceError:
151
+ print("Not enough balance")
152
+ except AuthenticationError:
153
+ print("Other auth error")
154
+ ```
155
+
156
+ All errors inherit from `LiquidError`. Auth errors: `AuthenticationError`, `InvalidApiKeyError`, `InvalidSignatureError`, `ExpiredTimestampError`, `ReplayedNonceError`. Permission errors: `ForbiddenError`, `IpForbiddenError`, `InsufficientScopeError`. Others: `RateLimitError`, `ValidationError`, `InsufficientBalanceError`, `OrderRejectedError`, `SymbolNotFoundError`, `TimeoutError`, `ConnectionError`, `ServiceUnavailableError`.
157
+
158
+ Client-side validation raises `ValueError` before the request is sent.
159
+
160
+ ## Rate Limits
161
+
162
+ | Tier | Requests/sec | Orders/sec |
163
+ |--------|-------------|------------|
164
+ | free | 10 | 2 |
165
+ | trader | 30 | 10 |
166
+ | pro | 100 | 50 |
167
+
168
+ Enable retries for transient 429s (GET only, exponential backoff):
169
+
170
+ ```python
171
+ client = LiquidClient(api_key="lq_...", api_secret="sk_...", max_retries=3)
172
+ ```
173
+
174
+ ## MCP Server
175
+
176
+ The MCP server lives in the sibling `../liquidtrading-mcp/` repo and depends on this SDK as a published package.
177
+
178
+ ## Requirements
179
+
180
+ Python 3.9+ · httpx >= 0.25.0
@@ -0,0 +1,13 @@
1
+ liquid/__init__.py,sha256=VrOpzbJXU4xLhMFb5oZEOhWf_3mimkNl0pgVlOXvPxU,759
2
+ liquidtrading/__init__.py,sha256=eshLeLSS6b_8uDR25zGm5FzAydzX5ickmyMR1AG7E_I,1879
3
+ liquidtrading/auth.py,sha256=c8gAJzk7_XtcJu4-KbKh9J8MG9slfKcbB74PGpwE1sc,5230
4
+ liquidtrading/client.py,sha256=Lz4R4IWKolKC3eX0JpbqgeVS-xxymXQMIT8XpJZ0aAM,15422
5
+ liquidtrading/constants.py,sha256=7se1j7GeR9x-okSbOaX3a3TOEWTZQ9C9-GH1TjuGs2o,437
6
+ liquidtrading/demo.py,sha256=9aCyF6JiW00GrutKMdSfKwFwCfjq52wbSX-qpWkGA1Y,2617
7
+ liquidtrading/errors.py,sha256=AxzklcSbtW1vftI-cOWs-iCd3PKZ7tnD4rzAtfM6mAk,5867
8
+ liquidtrading/http.py,sha256=Dl7UrdQuUI1hF2XDxKi-hSre8rLWLf7VpYySEe0ehwQ,7732
9
+ liquidtrading/models.py,sha256=HRLllqMDxRtOrKQzfPtq7Oj4AcqlfgcDWbhYcMjZV7s,6869
10
+ liquidtrading_python-0.1.0.dist-info/METADATA,sha256=TVo4hsZncLFLcg-IBVwoX1GUmEss5DKV03nKHy7y9X0,4997
11
+ liquidtrading_python-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
12
+ liquidtrading_python-0.1.0.dist-info/entry_points.txt,sha256=QUYkeYHRfA5rnyEwIXWA1wkd_abBBNyFTfP3gDlb7sQ,101
13
+ liquidtrading_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ liquid-demo = liquidtrading.demo:main
3
+ liquidtrading-demo = liquidtrading.demo:main