regard-cli 0.2.0__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.
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v4
14
+ - run: uv run --extra test pytest -v
15
+
16
+ publish:
17
+ needs: test
18
+ runs-on: ubuntu-latest
19
+ environment: pypi
20
+ permissions:
21
+ id-token: write
22
+ contents: read
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: astral-sh/setup-uv@v4
26
+ - run: uv build
27
+ - uses: pypa/gh-action-pypi-publish@release/v1
28
+ with:
29
+ packages-dir: dist/
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ data/trades.jsonl
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tab55
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: regard-cli
3
+ Version: 0.2.0
4
+ Summary: AI trading partner for the unified speculator — Hyperliquid + Polymarket
5
+ Author: tab55
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: agent,cli,defi,hyperliquid,polymarket,regard,trading
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial :: Investment
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: hyperliquid-python-sdk>=0.22.0
21
+ Requires-Dist: typer>=0.9.0
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=8.0; extra == 'test'
24
+ Requires-Dist: syrupy>=4.0; extra == 'test'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # regard-cli
28
+
29
+ AI trading partner for the unified speculator. Multi-venue trading CLI consumed by the [regard-computer](https://github.com/akegaviar/tabtabtabTABtab) Claude Code skill.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ uv tool install regard-cli
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ # Read commands (no auth needed)
41
+ regard hl prices BTC ETH
42
+ regard hl book BTC --depth 10
43
+ regard hl assets TSLA
44
+ regard hl funding BTC --history
45
+ regard hl candles BTC 1h --limit 50
46
+
47
+ # Account commands (needs HYPERLIQUID_AGENT_KEY or --address)
48
+ regard hl status
49
+ regard hl balance
50
+ regard hl positions
51
+ regard hl orders
52
+ regard hl fills
53
+
54
+ # Write commands (needs HYPERLIQUID_AGENT_KEY)
55
+ regard hl order BTC buy 0.1 --market
56
+ regard hl order BTC buy 0.1 95000 --tp 100000 --sl 90000
57
+ regard hl cancel 77738308
58
+ regard hl cancel-all BTC
59
+ regard hl leverage BTC 10
60
+ ```
61
+
62
+ ## Auth
63
+
64
+ Set `HYPERLIQUID_AGENT_KEY` env var with your Hyperliquid agent wallet key (can trade, cannot withdraw). Master address auto-resolved via `userRole` API.
65
+
66
+ Read-only commands work without auth using `--address`.
67
+
68
+ ## Design
69
+
70
+ - Venue-first sub-commands (`regard hl`, `regard poly`, ...)
71
+ - Output envelope: `{"ok": true, "data": ..., "meta": {"timestamp": ...}}`
72
+ - Structured errors with type/code/message, recovery hints, retryable flag
73
+ - Exit codes: 0=success, 2=validation, 3=auth, 4=venue, 5=rate_limit, 6=network
74
+ - Field projection via `--fields`
75
+ - HIP-3 asset auto-resolution (e.g. `TSLA` → `xyz:TSLA`)
76
+ - CLI-first, MCP-ready — every command maps 1:1 to an MCP tool definition
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,54 @@
1
+ # regard-cli
2
+
3
+ AI trading partner for the unified speculator. Multi-venue trading CLI consumed by the [regard-computer](https://github.com/akegaviar/tabtabtabTABtab) Claude Code skill.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv tool install regard-cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Read commands (no auth needed)
15
+ regard hl prices BTC ETH
16
+ regard hl book BTC --depth 10
17
+ regard hl assets TSLA
18
+ regard hl funding BTC --history
19
+ regard hl candles BTC 1h --limit 50
20
+
21
+ # Account commands (needs HYPERLIQUID_AGENT_KEY or --address)
22
+ regard hl status
23
+ regard hl balance
24
+ regard hl positions
25
+ regard hl orders
26
+ regard hl fills
27
+
28
+ # Write commands (needs HYPERLIQUID_AGENT_KEY)
29
+ regard hl order BTC buy 0.1 --market
30
+ regard hl order BTC buy 0.1 95000 --tp 100000 --sl 90000
31
+ regard hl cancel 77738308
32
+ regard hl cancel-all BTC
33
+ regard hl leverage BTC 10
34
+ ```
35
+
36
+ ## Auth
37
+
38
+ Set `HYPERLIQUID_AGENT_KEY` env var with your Hyperliquid agent wallet key (can trade, cannot withdraw). Master address auto-resolved via `userRole` API.
39
+
40
+ Read-only commands work without auth using `--address`.
41
+
42
+ ## Design
43
+
44
+ - Venue-first sub-commands (`regard hl`, `regard poly`, ...)
45
+ - Output envelope: `{"ok": true, "data": ..., "meta": {"timestamp": ...}}`
46
+ - Structured errors with type/code/message, recovery hints, retryable flag
47
+ - Exit codes: 0=success, 2=validation, 3=auth, 4=venue, 5=rate_limit, 6=network
48
+ - Field projection via `--fields`
49
+ - HIP-3 asset auto-resolution (e.g. `TSLA` → `xyz:TSLA`)
50
+ - CLI-first, MCP-ready — every command maps 1:1 to an MCP tool definition
51
+
52
+ ## License
53
+
54
+ MIT
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "regard-cli"
3
+ version = "0.2.0"
4
+ description = "AI trading partner for the unified speculator — Hyperliquid + Polymarket"
5
+ requires-python = ">=3.10"
6
+ license = "MIT"
7
+ authors = [{ name = "tab55" }]
8
+ readme = "README.md"
9
+ keywords = ["regard", "trading", "cli", "agent", "hyperliquid", "polymarket", "defi"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Topic :: Office/Business :: Financial :: Investment",
21
+ ]
22
+ dependencies = [
23
+ "typer>=0.9.0",
24
+ "hyperliquid-python-sdk>=0.22.0",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ test = ["pytest>=8.0", "syrupy>=4.0"]
29
+
30
+ [project.scripts]
31
+ regard = "hl_cli.main:app"
32
+
33
+ [build-system]
34
+ requires = ["hatchling"]
35
+ build-backend = "hatchling.build"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/hl_cli"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,98 @@
1
+ """SDK client factory — creates Info and Exchange instances."""
2
+
3
+ import os
4
+
5
+ import eth_account
6
+ from hyperliquid.exchange import Exchange
7
+ from hyperliquid.info import Info
8
+ from hyperliquid.utils.constants import MAINNET_API_URL
9
+
10
+ from hl_cli.output import die
11
+
12
+ TESTNET_API_URL = "https://api.hyperliquid-testnet.xyz"
13
+
14
+
15
+ def _base_url(testnet: bool) -> str:
16
+ return TESTNET_API_URL if testnet else MAINNET_API_URL
17
+
18
+
19
+ def _get_key():
20
+ key = os.environ.get("HYPERLIQUID_AGENT_KEY") or os.environ.get("HYPERLIQUID_PRIVATE_KEY")
21
+ if not key:
22
+ die(
23
+ "auth_error", "NO_KEY",
24
+ "No trading key set",
25
+ hint="Set HYPERLIQUID_AGENT_KEY (agent wallet) or HYPERLIQUID_PRIVATE_KEY (master wallet)",
26
+ exit_code=3,
27
+ )
28
+ return key
29
+
30
+
31
+ def _resolve_master_address(info: Info, agent_address: str) -> str:
32
+ """Look up the master account for an agent wallet via userRole API."""
33
+ try:
34
+ result = info.post("/info", {"type": "userRole", "user": agent_address})
35
+ role = result.get("role")
36
+ if role == "agent":
37
+ return result["data"]["user"]
38
+ if role == "user":
39
+ return agent_address # key IS the master key
40
+ except Exception:
41
+ pass
42
+ return agent_address # fallback: assume key is master
43
+
44
+
45
+ def get_address(explicit: str = None, testnet: bool = False) -> str:
46
+ """Get account address for queries.
47
+
48
+ Priority: explicit arg > HYPERLIQUID_ACCOUNT_ADDRESS > auto-resolve via userRole API > derived from key.
49
+ """
50
+ if explicit:
51
+ return explicit
52
+ addr = os.environ.get("HYPERLIQUID_ACCOUNT_ADDRESS")
53
+ if addr:
54
+ return addr
55
+ key = os.environ.get("HYPERLIQUID_AGENT_KEY") or os.environ.get("HYPERLIQUID_PRIVATE_KEY")
56
+ if key:
57
+ agent_address = eth_account.Account.from_key(key).address
58
+ if os.environ.get("HYPERLIQUID_AGENT_KEY"):
59
+ info = Info(_base_url(testnet), skip_ws=True)
60
+ return _resolve_master_address(info, agent_address)
61
+ return agent_address
62
+ die(
63
+ "auth_error", "NO_ADDRESS",
64
+ "No address available",
65
+ hint="Set HYPERLIQUID_AGENT_KEY or use --address",
66
+ exit_code=3,
67
+ )
68
+
69
+
70
+ def get_info(testnet: bool = False) -> Info:
71
+ """Create Info client (default perps only, dexes loaded lazily by resolver)."""
72
+ return Info(_base_url(testnet), skip_ws=True)
73
+
74
+
75
+ def get_exchange(testnet: bool = False) -> Exchange:
76
+ """Create Exchange client.
77
+
78
+ Supports two modes:
79
+ - Agent mode: HYPERLIQUID_AGENT_KEY (signs) + HYPERLIQUID_ACCOUNT_ADDRESS (queries master account)
80
+ - Direct mode: HYPERLIQUID_PRIVATE_KEY (signs and queries same account)
81
+ """
82
+ key = _get_key()
83
+ wallet = eth_account.Account.from_key(key)
84
+ account_address = get_address()
85
+ return Exchange(wallet, _base_url(testnet), account_address=account_address)
86
+
87
+
88
+ def get_all_mids(info: Info) -> dict:
89
+ """Get all mid prices including HIP-3 deployer assets."""
90
+ mids = info.all_mids()
91
+ try:
92
+ dex_list = info.post("/info", {"type": "perpDexs"})
93
+ for dex_info in dex_list[1:]:
94
+ dex_mids = info.post("/info", {"type": "allMids", "dex": dex_info["name"]})
95
+ mids.update(dex_mids)
96
+ except Exception:
97
+ pass
98
+ return mids
File without changes
@@ -0,0 +1,254 @@
1
+ """Act commands — order, cancel, cancel-all, leverage."""
2
+
3
+ import sys
4
+ from typing import Annotated, Optional
5
+
6
+ import typer
7
+
8
+ from hl_cli.client import get_address, get_exchange
9
+ from hl_cli.main import hl_app
10
+ from hl_cli.output import die, output
11
+ from hl_cli.resolver import get_resolver
12
+
13
+ SIDE_MAP = {"buy": True, "long": True, "sell": False, "short": False}
14
+
15
+
16
+ def _parse_order_response(resp, asset: str, side_str: str, size: str):
17
+ """Parse SDK order response into CLI output format."""
18
+ if resp.get("status") == "err":
19
+ die("venue_error", "ORDER_REJECTED", resp.get("response", str(resp)), exit_code=4)
20
+
21
+ data = resp.get("response", {}).get("data", {})
22
+ statuses = data.get("statuses", [])
23
+ if not statuses:
24
+ die("venue_error", "ORDER_REJECTED", "No status in response", exit_code=4)
25
+
26
+ st = statuses[0]
27
+ if "error" in st:
28
+ die("venue_error", "ORDER_REJECTED", st["error"], exit_code=4)
29
+
30
+ if "filled" in st:
31
+ filled = st["filled"]
32
+ return {
33
+ "status": "filled",
34
+ "oid": filled.get("oid", 0),
35
+ "asset": asset,
36
+ "side": side_str,
37
+ "size": filled.get("totalSz", size),
38
+ "avg_price": filled.get("avgPx", ""),
39
+ }
40
+ elif "resting" in st:
41
+ resting = st["resting"]
42
+ return {
43
+ "status": "resting",
44
+ "oid": resting.get("oid", 0),
45
+ "asset": asset,
46
+ "side": side_str,
47
+ "size": size,
48
+ }
49
+ else:
50
+ return {"status": "unknown", "raw": st}
51
+
52
+
53
+
54
+ @hl_app.command()
55
+ def order(
56
+ ctx: typer.Context,
57
+ asset: Annotated[str, typer.Argument(help="Asset name")],
58
+ side: Annotated[str, typer.Argument(help="buy/long or sell/short")],
59
+ size: Annotated[str, typer.Argument(help="Amount in base asset")],
60
+ price: Annotated[Optional[str], typer.Argument(help="Limit price")] = None,
61
+ market: Annotated[bool, typer.Option("--market", help="Market order (IOC)")] = False,
62
+ tif: Annotated[Optional[str], typer.Option("--tif", help="gtc|ioc|alo")] = None,
63
+ reduce_only: Annotated[bool, typer.Option("--reduce-only", help="Reduce only")] = False,
64
+ tp: Annotated[Optional[str], typer.Option("--tp", help="Take profit trigger price")] = None,
65
+ sl: Annotated[Optional[str], typer.Option("--sl", help="Stop loss trigger price")] = None,
66
+ cloid: Annotated[Optional[str], typer.Option("--cloid", help="Client order ID (hex)")] = None,
67
+ ):
68
+ """Place an order."""
69
+ opts = ctx.obj
70
+
71
+ side_lower = side.lower()
72
+ if side_lower not in SIDE_MAP:
73
+ die("validation_error", "INVALID_SIDE", f"Invalid side: {side}", hint="Use buy/long or sell/short", param="side")
74
+
75
+ is_buy = SIDE_MAP[side_lower]
76
+ sz = float(size)
77
+
78
+ exchange = get_exchange(opts["testnet"])
79
+ resolver = get_resolver(exchange.info)
80
+ resolved = resolver.resolve(asset)
81
+
82
+ from hyperliquid.utils.types import Cloid as SdkCloid
83
+ sdk_cloid = SdkCloid.from_str(cloid) if cloid else None
84
+
85
+ if market or price is None:
86
+ # Market order
87
+ resp = exchange.market_open(resolved, is_buy, sz, cloid=sdk_cloid)
88
+ result = _parse_order_response(resp, resolved, side_lower, size)
89
+ output(result, opts["fields"])
90
+ return
91
+
92
+ # Limit order
93
+ px = float(price)
94
+ tif_val = (tif or "gtc").capitalize()
95
+ if tif_val == "Gtc":
96
+ tif_val = "Gtc"
97
+ elif tif_val == "Ioc":
98
+ tif_val = "Ioc"
99
+ elif tif_val == "Alo":
100
+ tif_val = "Alo"
101
+ order_type = {"limit": {"tif": tif_val}}
102
+
103
+ if tp or sl:
104
+ # Parent + TP/SL as grouped order
105
+ order_requests = [
106
+ {
107
+ "coin": resolved,
108
+ "is_buy": is_buy,
109
+ "sz": sz,
110
+ "limit_px": px,
111
+ "order_type": order_type,
112
+ "reduce_only": reduce_only,
113
+ "cloid": sdk_cloid,
114
+ }
115
+ ]
116
+ if tp:
117
+ tp_type = {"trigger": {"triggerPx": float(tp), "isMarket": True, "tpsl": "tp"}}
118
+ order_requests.append({
119
+ "coin": resolved,
120
+ "is_buy": not is_buy,
121
+ "sz": sz,
122
+ "limit_px": float(tp),
123
+ "order_type": tp_type,
124
+ "reduce_only": True,
125
+ })
126
+ if sl:
127
+ sl_type = {"trigger": {"triggerPx": float(sl), "isMarket": True, "tpsl": "sl"}}
128
+ order_requests.append({
129
+ "coin": resolved,
130
+ "is_buy": not is_buy,
131
+ "sz": sz,
132
+ "limit_px": float(sl),
133
+ "order_type": sl_type,
134
+ "reduce_only": True,
135
+ })
136
+
137
+ resp = exchange.bulk_orders(
138
+ [exchange._order_request_to_wire(r) for r in order_requests]
139
+ if hasattr(exchange, "_order_request_to_wire")
140
+ else order_requests,
141
+ grouping="normalTpsl",
142
+ )
143
+ result = _parse_order_response(resp, resolved, side_lower, size)
144
+ output(result, opts["fields"])
145
+ else:
146
+ resp = exchange.order(resolved, is_buy, sz, px, order_type, reduce_only, cloid=sdk_cloid)
147
+ result = _parse_order_response(resp, resolved, side_lower, size)
148
+ output(result, opts["fields"])
149
+
150
+
151
+ @hl_app.command()
152
+ def cancel(
153
+ ctx: typer.Context,
154
+ first: Annotated[str, typer.Argument(help="OID or asset name")],
155
+ second: Annotated[Optional[str], typer.Argument(help="OID (if first is asset)")] = None,
156
+ cloid: Annotated[Optional[str], typer.Option("--cloid", help="Cancel by client order ID")] = None,
157
+ asset_opt: Annotated[Optional[str], typer.Option("--asset", help="Asset for cloid cancel")] = None,
158
+ ):
159
+ """Cancel a specific order."""
160
+ opts = ctx.obj
161
+ exchange = get_exchange(opts["testnet"])
162
+
163
+ if cloid:
164
+ # Cancel by client order ID
165
+ if not asset_opt:
166
+ die("validation_error", "MISSING_ARGUMENT", "Must specify --asset with --cloid", hint="regard hl cancel --cloid 0x... --asset BTC")
167
+ resolver = get_resolver(exchange.info)
168
+ resolved = resolver.resolve(asset_opt)
169
+ from hyperliquid.utils.types import Cloid as SdkCloid
170
+ resp = exchange.cancel_by_cloid(resolved, SdkCloid.from_str(cloid))
171
+ output({"status": "cancelled", "asset": resolved, "cloid": cloid}, opts["fields"])
172
+ return
173
+
174
+ if second is not None:
175
+ # hl cancel BTC 77738308
176
+ resolver = get_resolver(exchange.info)
177
+ resolved = resolver.resolve(first)
178
+ oid = int(second)
179
+ exchange.cancel(resolved, oid)
180
+ output({"status": "cancelled", "oid": oid, "asset": resolved}, opts["fields"])
181
+ else:
182
+ # hl cancel 77738308 — auto-lookup asset from open orders
183
+ oid = int(first)
184
+ addr = get_address()
185
+ open_orders = exchange.info.open_orders(addr)
186
+ coin = None
187
+ for o in open_orders:
188
+ if o["oid"] == oid:
189
+ coin = o["coin"]
190
+ break
191
+ if not coin:
192
+ die("validation_error", "ORDER_NOT_FOUND", f"Order {oid} not found in open orders", hint=f"Pass asset explicitly: regard hl cancel BTC {oid}")
193
+ exchange.cancel(coin, oid)
194
+ output({"status": "cancelled", "oid": oid, "asset": coin}, opts["fields"])
195
+
196
+
197
+ @hl_app.command(name="cancel-all")
198
+ def cancel_all(
199
+ ctx: typer.Context,
200
+ asset: Annotated[Optional[str], typer.Argument(help="Cancel only this asset's orders")] = None,
201
+ ):
202
+ """Cancel all open orders."""
203
+ opts = ctx.obj
204
+ exchange = get_exchange(opts["testnet"])
205
+ addr = get_address()
206
+
207
+ open_orders = exchange.info.open_orders(addr)
208
+
209
+ if asset:
210
+ resolver = get_resolver(exchange.info)
211
+ resolved = resolver.resolve(asset)
212
+ open_orders = [o for o in open_orders if o["coin"] == resolved]
213
+
214
+ if not open_orders:
215
+ output({"cancelled": 0, "assets": []}, opts["fields"])
216
+ return
217
+
218
+ cancel_requests = [{"coin": o["coin"], "oid": o["oid"]} for o in open_orders]
219
+ exchange.bulk_cancel(cancel_requests)
220
+
221
+ assets_cancelled = sorted(set(o["coin"] for o in open_orders))
222
+ output({"cancelled": len(open_orders), "assets": assets_cancelled}, opts["fields"])
223
+
224
+
225
+ @hl_app.command()
226
+ def leverage(
227
+ ctx: typer.Context,
228
+ asset: Annotated[str, typer.Argument(help="Asset name")],
229
+ lev: Annotated[int, typer.Argument(help="Leverage (integer)")],
230
+ cross: Annotated[bool, typer.Option("--cross", help="Cross margin")] = False,
231
+ isolated: Annotated[bool, typer.Option("--isolated", help="Isolated margin")] = False,
232
+ ):
233
+ """Set leverage for an asset."""
234
+ opts = ctx.obj
235
+
236
+ if cross and isolated:
237
+ die("validation_error", "CONFLICTING_OPTIONS", "Cannot specify both --cross and --isolated")
238
+
239
+ exchange = get_exchange(opts["testnet"])
240
+ resolver = get_resolver(exchange.info)
241
+ resolved = resolver.resolve(asset)
242
+
243
+ is_cross = not isolated # default is cross
244
+ if is_cross and ":" in resolved:
245
+ print("Warning: HIP-3 assets support isolated margin only. Using isolated.", file=sys.stderr)
246
+ is_cross = False
247
+
248
+ exchange.update_leverage(lev, resolved, is_cross)
249
+
250
+ output({
251
+ "asset": resolved,
252
+ "leverage": lev,
253
+ "margin_mode": "cross" if is_cross else "isolated",
254
+ }, opts["fields"])