hyperliquid-cli 0.1.0__tar.gz → 0.1.1__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.
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/PKG-INFO +1 -1
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/pyproject.toml +1 -1
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/commands/act.py +1 -24
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/commands/review.py +1 -28
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/main.py +9 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/__snapshots__/test_review.ambr +0 -24
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/test_act.py +0 -8
- hyperliquid_cli-0.1.1/tests/test_review.py +34 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/uv.lock +1 -1
- hyperliquid_cli-0.1.0/tests/test_review.py +0 -70
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/.github/workflows/publish.yml +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/.gitignore +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/LICENSE +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/README.md +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/__init__.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/client.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/commands/__init__.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/commands/orient.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/commands/research.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/output.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/src/hl_cli/resolver.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/__init__.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/__snapshots__/test_act.ambr +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/__snapshots__/test_orient.ambr +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/__snapshots__/test_research.ambr +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/conftest.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/test_orient.py +0 -0
- {hyperliquid_cli-0.1.0 → hyperliquid_cli-0.1.1}/tests/test_research.py +0 -0
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
"""Act commands — order, cancel, cancel-all, leverage."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import sys
|
|
5
|
-
import time
|
|
6
|
-
from pathlib import Path
|
|
7
4
|
from typing import Annotated, Optional
|
|
8
5
|
|
|
9
6
|
import typer
|
|
10
7
|
|
|
11
8
|
from hl_cli.client import get_address, get_exchange
|
|
12
9
|
from hl_cli.main import app
|
|
13
|
-
from hl_cli.output import die,
|
|
10
|
+
from hl_cli.output import die, output
|
|
14
11
|
from hl_cli.resolver import get_resolver
|
|
15
12
|
|
|
16
13
|
SIDE_MAP = {"buy": True, "long": True, "sell": False, "short": False}
|
|
@@ -53,23 +50,6 @@ def _parse_order_response(resp, asset: str, side_str: str, size: str):
|
|
|
53
50
|
return {"status": "unknown", "raw": st}
|
|
54
51
|
|
|
55
52
|
|
|
56
|
-
def _log_trade(result: dict, order_type: str, price: str = ""):
|
|
57
|
-
"""Append trade record to data/trades.jsonl."""
|
|
58
|
-
trades_dir = Path("data")
|
|
59
|
-
trades_dir.mkdir(exist_ok=True)
|
|
60
|
-
record = {
|
|
61
|
-
"timestamp": format_timestamp(int(time.time() * 1000)),
|
|
62
|
-
"asset": result.get("asset", ""),
|
|
63
|
-
"side": result.get("side", ""),
|
|
64
|
-
"size": result.get("size", ""),
|
|
65
|
-
"price": result.get("avg_price", price),
|
|
66
|
-
"type": order_type,
|
|
67
|
-
"oid": result.get("oid", 0),
|
|
68
|
-
"status": result.get("status", ""),
|
|
69
|
-
}
|
|
70
|
-
with open(trades_dir / "trades.jsonl", "a") as f:
|
|
71
|
-
f.write(json.dumps(record, separators=(",", ":")) + "\n")
|
|
72
|
-
|
|
73
53
|
|
|
74
54
|
@app.command()
|
|
75
55
|
def order(
|
|
@@ -106,7 +86,6 @@ def order(
|
|
|
106
86
|
# Market order
|
|
107
87
|
resp = exchange.market_open(resolved, is_buy, sz, cloid=sdk_cloid)
|
|
108
88
|
result = _parse_order_response(resp, resolved, side_lower, size)
|
|
109
|
-
_log_trade(result, "market")
|
|
110
89
|
output(result, opts["fields"])
|
|
111
90
|
return
|
|
112
91
|
|
|
@@ -162,12 +141,10 @@ def order(
|
|
|
162
141
|
grouping="normalTpsl",
|
|
163
142
|
)
|
|
164
143
|
result = _parse_order_response(resp, resolved, side_lower, size)
|
|
165
|
-
_log_trade(result, "limit", price)
|
|
166
144
|
output(result, opts["fields"])
|
|
167
145
|
else:
|
|
168
146
|
resp = exchange.order(resolved, is_buy, sz, px, order_type, reduce_only, cloid=sdk_cloid)
|
|
169
147
|
result = _parse_order_response(resp, resolved, side_lower, size)
|
|
170
|
-
_log_trade(result, "limit", price)
|
|
171
148
|
output(result, opts["fields"])
|
|
172
149
|
|
|
173
150
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
"""Review commands — fills
|
|
1
|
+
"""Review commands — fills."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
|
-
from pathlib import Path
|
|
5
3
|
from typing import Annotated, Optional
|
|
6
4
|
|
|
7
5
|
import typer
|
|
@@ -57,28 +55,3 @@ def fills(
|
|
|
57
55
|
output(result, opts["fields"])
|
|
58
56
|
|
|
59
57
|
|
|
60
|
-
@app.command()
|
|
61
|
-
def trades(
|
|
62
|
-
ctx: typer.Context,
|
|
63
|
-
asset_name: Annotated[Optional[str], typer.Option("--asset", help="Filter by asset")] = None,
|
|
64
|
-
limit: Annotated[int, typer.Option("--limit", help="Number of trades")] = 20,
|
|
65
|
-
):
|
|
66
|
-
"""Local trade log (from data/trades.jsonl)."""
|
|
67
|
-
opts = ctx.obj
|
|
68
|
-
trades_file = Path("data/trades.jsonl")
|
|
69
|
-
|
|
70
|
-
if not trades_file.exists():
|
|
71
|
-
output([], opts["fields"])
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
result = []
|
|
75
|
-
for line in trades_file.read_text().strip().split("\n"):
|
|
76
|
-
if not line:
|
|
77
|
-
continue
|
|
78
|
-
trade = json.loads(line)
|
|
79
|
-
if asset_name and trade.get("asset") != asset_name:
|
|
80
|
-
continue
|
|
81
|
-
result.append(trade)
|
|
82
|
-
|
|
83
|
-
result = result[-limit:]
|
|
84
|
-
output(result, opts["fields"])
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
"""hl — Agent-first CLI for Hyperliquid trading."""
|
|
2
2
|
|
|
3
|
+
from importlib.metadata import version as pkg_version
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
import typer
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
def _version_callback(value: bool):
|
|
10
|
+
if value:
|
|
11
|
+
print(pkg_version("hyperliquid-cli"))
|
|
12
|
+
raise typer.Exit()
|
|
13
|
+
|
|
14
|
+
|
|
7
15
|
app = typer.Typer(
|
|
8
16
|
name="hl",
|
|
9
17
|
add_completion=False,
|
|
@@ -15,6 +23,7 @@ app = typer.Typer(
|
|
|
15
23
|
@app.callback()
|
|
16
24
|
def main(
|
|
17
25
|
ctx: typer.Context,
|
|
26
|
+
version: bool = typer.Option(False, "--version", callback=_version_callback, is_eager=True, help="Show version and exit"),
|
|
18
27
|
fields: Optional[str] = typer.Option(None, "--fields", help="Comma-separated field projection"),
|
|
19
28
|
verbose: bool = typer.Option(False, "--verbose", help="Debug info to stderr"),
|
|
20
29
|
testnet: bool = typer.Option(False, "--testnet", help="Use testnet endpoints"),
|
|
@@ -23,27 +23,3 @@
|
|
|
23
23
|
}),
|
|
24
24
|
])
|
|
25
25
|
# ---
|
|
26
|
-
# name: TestTrades.test_with_data
|
|
27
|
-
list([
|
|
28
|
-
dict({
|
|
29
|
-
'asset': 'BTC',
|
|
30
|
-
'oid': 200001,
|
|
31
|
-
'price': '69666.0',
|
|
32
|
-
'side': 'buy',
|
|
33
|
-
'size': '0.1',
|
|
34
|
-
'status': 'filled',
|
|
35
|
-
'timestamp': '2026-03-11T10:30:00Z',
|
|
36
|
-
'type': 'market',
|
|
37
|
-
}),
|
|
38
|
-
dict({
|
|
39
|
-
'asset': 'ETH',
|
|
40
|
-
'oid': 200002,
|
|
41
|
-
'price': '3300.0',
|
|
42
|
-
'side': 'sell',
|
|
43
|
-
'size': '1.0',
|
|
44
|
-
'status': 'resting',
|
|
45
|
-
'timestamp': '2026-03-11T10:31:00Z',
|
|
46
|
-
'type': 'limit',
|
|
47
|
-
}),
|
|
48
|
-
])
|
|
49
|
-
# ---
|
|
@@ -42,14 +42,6 @@ class TestOrder:
|
|
|
42
42
|
assert result.exit_code == 1
|
|
43
43
|
assert parse(result)["error"] == "input_error"
|
|
44
44
|
|
|
45
|
-
def test_trade_log_written(self, runner, patched, tmp_path):
|
|
46
|
-
runner.invoke(app, ["order", "BTC", "buy", "0.1", "--market"])
|
|
47
|
-
trades_file = tmp_path / "data" / "trades.jsonl"
|
|
48
|
-
assert trades_file.exists()
|
|
49
|
-
record = json.loads(trades_file.read_text().strip())
|
|
50
|
-
assert record["asset"] == "BTC"
|
|
51
|
-
assert record["type"] == "market"
|
|
52
|
-
|
|
53
45
|
def test_unknown_asset(self, runner, patched):
|
|
54
46
|
result = runner.invoke(app, ["order", "FAKECOIN", "buy", "0.1", "--market"])
|
|
55
47
|
assert result.exit_code == 4
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Tests for review commands — fills, trades."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from hl_cli.main import app
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse(result):
|
|
12
|
+
return json.loads(result.output)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestFills:
|
|
16
|
+
def test_basic(self, runner, patched, snapshot):
|
|
17
|
+
result = runner.invoke(app, ["fills"])
|
|
18
|
+
assert result.exit_code == 0
|
|
19
|
+
assert parse(result) == snapshot
|
|
20
|
+
|
|
21
|
+
def test_filter_asset(self, runner, patched):
|
|
22
|
+
result = runner.invoke(app, ["fills", "BTC"])
|
|
23
|
+
assert result.exit_code == 0
|
|
24
|
+
data = parse(result)
|
|
25
|
+
assert len(data) == 1
|
|
26
|
+
assert data[0]["asset"] == "BTC"
|
|
27
|
+
|
|
28
|
+
def test_limit(self, runner, patched):
|
|
29
|
+
result = runner.invoke(app, ["fills", "--limit", "1"])
|
|
30
|
+
assert result.exit_code == 0
|
|
31
|
+
data = parse(result)
|
|
32
|
+
assert len(data) == 1
|
|
33
|
+
|
|
34
|
+
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"""Tests for review commands — fills, trades."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
|
|
8
|
-
from hl_cli.main import app
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def parse(result):
|
|
12
|
-
return json.loads(result.output)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestFills:
|
|
16
|
-
def test_basic(self, runner, patched, snapshot):
|
|
17
|
-
result = runner.invoke(app, ["fills"])
|
|
18
|
-
assert result.exit_code == 0
|
|
19
|
-
assert parse(result) == snapshot
|
|
20
|
-
|
|
21
|
-
def test_filter_asset(self, runner, patched):
|
|
22
|
-
result = runner.invoke(app, ["fills", "BTC"])
|
|
23
|
-
assert result.exit_code == 0
|
|
24
|
-
data = parse(result)
|
|
25
|
-
assert len(data) == 1
|
|
26
|
-
assert data[0]["asset"] == "BTC"
|
|
27
|
-
|
|
28
|
-
def test_limit(self, runner, patched):
|
|
29
|
-
result = runner.invoke(app, ["fills", "--limit", "1"])
|
|
30
|
-
assert result.exit_code == 0
|
|
31
|
-
data = parse(result)
|
|
32
|
-
assert len(data) == 1
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class TestTrades:
|
|
36
|
-
@pytest.fixture(autouse=True)
|
|
37
|
-
def _use_tmp_dir(self, tmp_path):
|
|
38
|
-
original = os.getcwd()
|
|
39
|
-
os.chdir(tmp_path)
|
|
40
|
-
yield
|
|
41
|
-
os.chdir(original)
|
|
42
|
-
|
|
43
|
-
def test_empty(self, runner, patched):
|
|
44
|
-
result = runner.invoke(app, ["trades"])
|
|
45
|
-
assert result.exit_code == 0
|
|
46
|
-
assert parse(result) == []
|
|
47
|
-
|
|
48
|
-
def test_with_data(self, runner, patched, tmp_path, snapshot):
|
|
49
|
-
trades_dir = tmp_path / "data"
|
|
50
|
-
trades_dir.mkdir()
|
|
51
|
-
(trades_dir / "trades.jsonl").write_text(
|
|
52
|
-
'{"timestamp":"2026-03-11T10:30:00Z","asset":"BTC","side":"buy","size":"0.1","price":"69666.0","type":"market","oid":200001,"status":"filled"}\n'
|
|
53
|
-
'{"timestamp":"2026-03-11T10:31:00Z","asset":"ETH","side":"sell","size":"1.0","price":"3300.0","type":"limit","oid":200002,"status":"resting"}\n'
|
|
54
|
-
)
|
|
55
|
-
result = runner.invoke(app, ["trades"])
|
|
56
|
-
assert result.exit_code == 0
|
|
57
|
-
assert parse(result) == snapshot
|
|
58
|
-
|
|
59
|
-
def test_filter_asset(self, runner, patched, tmp_path):
|
|
60
|
-
trades_dir = tmp_path / "data"
|
|
61
|
-
trades_dir.mkdir()
|
|
62
|
-
(trades_dir / "trades.jsonl").write_text(
|
|
63
|
-
'{"timestamp":"2026-03-11T10:30:00Z","asset":"BTC","side":"buy","size":"0.1","price":"69666.0","type":"market","oid":200001,"status":"filled"}\n'
|
|
64
|
-
'{"timestamp":"2026-03-11T10:31:00Z","asset":"ETH","side":"sell","size":"1.0","price":"3300.0","type":"limit","oid":200002,"status":"resting"}\n'
|
|
65
|
-
)
|
|
66
|
-
result = runner.invoke(app, ["trades", "--asset", "BTC"])
|
|
67
|
-
assert result.exit_code == 0
|
|
68
|
-
data = parse(result)
|
|
69
|
-
assert len(data) == 1
|
|
70
|
-
assert data[0]["asset"] == "BTC"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|