async-hyperliquid 1.1.0__tar.gz → 1.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.
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/CHANGELOG.md +11 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/PKG-INFO +2 -2
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/README.md +1 -1
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/README.md +24 -0
- async_hyperliquid-1.2.0/benchmarks/fast_cancel.py +178 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/orders.rst +5 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/orders.po +12 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/pyproject.toml +1 -1
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/client.py +28 -8
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/exchange.py +11 -1
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/exchange.py +2 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/LICENSE +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/__init__.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/hotpath.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/__init__.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/models.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/pacing.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/preflight.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/providers.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/reporting.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/results.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/runner.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live/workload.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/live_exchange.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/cancel-id-latency.png +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/cancel-id-latency.svg +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/report.json +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/samples.csv +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/signing.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/_static/language-switcher.css +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/_templates/sidebar/language-switcher.html +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/coin-name-mapping.md +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/conf.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/index.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/info-queries.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/lifecycle-reconciliation.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/markets.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/routing.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/howto/transfers-administration.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/index.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/introduction/index.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/introduction/installation.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/introduction/quickstart.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/coin-name-mapping.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/index.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/info-queries.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/lifecycle-reconciliation.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/markets.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/routing.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/transfers-administration.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/index.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/introduction/index.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/introduction/installation.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/introduction/quickstart.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/migration-0.5-to-1.0.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/about.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/benchmarks.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/index.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/license.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/async-hyperliquid.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/errors.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/exchange-client.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/index.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/info-client.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/response-types.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/types.po +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/migration-0.5-to-1.0.md +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/project/about.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/project/benchmarks.md +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/project/index.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/project/license.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/async-hyperliquid.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/errors.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/exchange-client.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/index.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/info-client.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/response-types.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/reference/types.rst +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/__init__.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/__init__.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/encoding.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/exchange.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/http.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/info.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/metadata.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/signing.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/constants.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/errors.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/info.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/py.typed +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/__init__.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/common.py +0 -0
- {async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/info.py +0 -0
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-11-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add `fast=True` to `cancel_order()`, `cancel_orders()`, `cancel_by_cloid()`, and
|
|
13
|
+
`cancel_orders_by_cloid()` to send Hyperliquid's fast-cancel `f` flag; the flag
|
|
14
|
+
is omitted when `fast=False`. The exchange rejects fast cancels of trigger
|
|
15
|
+
orders per status, so `fast` stays opt-in.
|
|
16
|
+
- Add testnet integration coverage for fast cancels and a diagnostic
|
|
17
|
+
`benchmarks/fast_cancel.py` comparing fast and non-fast cancel latency.
|
|
18
|
+
|
|
8
19
|
## [1.1.0] - 2026-09-14
|
|
9
20
|
|
|
10
21
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: async-hyperliquid
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Async Hyperliquid client using aiohttp
|
|
5
5
|
Keywords: dex,hyperliquid,async,aiohttp,trading,cryptocurrency,defi
|
|
6
6
|
Author: Yuki
|
|
@@ -36,7 +36,7 @@ Description-Content-Type: text/markdown
|
|
|
36
36
|
|
|
37
37
|
[](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml)
|
|
38
38
|
[](https://async-hyperliquid.readthedocs.io/en/latest/)
|
|
39
|
-
[](https://pypi.org/project/async-hyperliquid/)
|
|
40
40
|
|
|
41
41
|
`async-hyperliquid` is a typed, asynchronous Python client for the Hyperliquid
|
|
42
42
|
REST API. It provides credential-free market and account queries through
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml)
|
|
4
4
|
[](https://async-hyperliquid.readthedocs.io/en/latest/)
|
|
5
|
-
[](https://pypi.org/project/async-hyperliquid/)
|
|
6
6
|
|
|
7
7
|
`async-hyperliquid` is a typed, asynchronous Python client for the Hyperliquid
|
|
8
8
|
REST API. It provides credential-free market and account queries through
|
|
@@ -207,6 +207,30 @@ Artifacts: [sanitized JSON](results/20260805T192621Z/report.json), [sample CSV](
|
|
|
207
207
|
These measurements describe one testnet run, not exchange capacity. Shared-IP traffic, geography, testnet load, dependency versions, and network conditions can change results.
|
|
208
208
|
<!-- live-exchange-benchmark:detail:end -->
|
|
209
209
|
|
|
210
|
+
## Fast-cancel benchmark
|
|
211
|
+
|
|
212
|
+
`benchmarks/fast_cancel.py` compares single-order `cancel_order()` latency with
|
|
213
|
+
`fast=False` and `fast=True` on testnet. It uses the same `.env.local`
|
|
214
|
+
credentials and safety checks as the live Exchange benchmark (testnet only,
|
|
215
|
+
`HL_SK` must derive `HL_AK`, roles validated before any order).
|
|
216
|
+
|
|
217
|
+
Each round places `--orders` resting BTC ALO buys at `mid * 0.9` (about 12 USDC
|
|
218
|
+
each) in one batch, then cancels them sequentially, alternating the two methods;
|
|
219
|
+
the method that goes first swaps every round. Only the cancel request is timed,
|
|
220
|
+
pacing sleeps excluded. Any non-success cancel aborts the run, and remaining
|
|
221
|
+
orders are cancelled on exit.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
uv run --frozen --group benchmark python benchmarks/fast_cancel.py \
|
|
225
|
+
--rounds 30 --warmups 3 --orders 10
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The output is a plain-text table of n, mean, p50, p90, p99, min and max in ms.
|
|
229
|
+
Hyperliquid currently documents no effect for `fast` other than rejecting
|
|
230
|
+
trigger orders, so the two methods are expected to match today; the benchmark
|
|
231
|
+
is a baseline for the future mempool-priority upgrade. It is diagnostic only
|
|
232
|
+
and does not produce publishable artifacts.
|
|
233
|
+
|
|
210
234
|
## Signing benchmark
|
|
211
235
|
|
|
212
236
|
This benchmark compares Hyperliquid signing and order-payload construction in
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Compare single-order cancel latency with fast=False and fast=True on testnet.
|
|
2
|
+
|
|
3
|
+
Each round places ``--orders`` resting BTC ALO buys far below mid in one batch,
|
|
4
|
+
then cancels them one at a time, alternating fast=False / fast=True. The method
|
|
5
|
+
that goes first swaps every round. Only the cancel request itself is timed;
|
|
6
|
+
pacing sleeps are excluded. Any non-success cancel invalidates the run.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import asyncio
|
|
13
|
+
import math
|
|
14
|
+
import os
|
|
15
|
+
import statistics
|
|
16
|
+
import sys
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from time import perf_counter_ns
|
|
20
|
+
|
|
21
|
+
if __package__ in (None, ""):
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
23
|
+
|
|
24
|
+
from async_hyperliquid import AsyncHyperliquid
|
|
25
|
+
from async_hyperliquid.types import (
|
|
26
|
+
CancelOrder,
|
|
27
|
+
CancelOrderResponse,
|
|
28
|
+
Network,
|
|
29
|
+
PlaceOrderRequest,
|
|
30
|
+
TimeInForce,
|
|
31
|
+
limit_order_type,
|
|
32
|
+
)
|
|
33
|
+
from benchmarks.live.models import MIN_INTERVAL_NS, BenchmarkFailure
|
|
34
|
+
from benchmarks.live.pacing import WeightedPacer
|
|
35
|
+
from benchmarks.live.preflight import Credentials, validate_roles
|
|
36
|
+
|
|
37
|
+
COIN = "BTC"
|
|
38
|
+
NOTIONAL_USDC = 12
|
|
39
|
+
PRICE_RATIO = 0.9
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
43
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
44
|
+
parser.add_argument("--rounds", type=int, default=30)
|
|
45
|
+
parser.add_argument("--warmups", type=int, default=3)
|
|
46
|
+
parser.add_argument("--orders", type=int, default=10, help="even, per round")
|
|
47
|
+
parser.add_argument("--interval-ms", type=int, default=250)
|
|
48
|
+
args = parser.parse_args(argv)
|
|
49
|
+
if args.rounds < 1 or args.warmups < 0:
|
|
50
|
+
parser.error("--rounds must be positive and --warmups non-negative")
|
|
51
|
+
if args.orders < 2 or args.orders % 2:
|
|
52
|
+
parser.error("--orders must be an even number >= 2")
|
|
53
|
+
if args.interval_ms * 1_000_000 < MIN_INTERVAL_NS:
|
|
54
|
+
parser.error(f"--interval-ms must be at least {MIN_INTERVAL_NS // 1_000_000}")
|
|
55
|
+
return args
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def fast_plan(round_index: int, count: int) -> tuple[bool, ...]:
|
|
59
|
+
"""Alternate fast flags; the first method swaps every round for fairness."""
|
|
60
|
+
first = round_index % 2 == 1
|
|
61
|
+
return tuple(first if i % 2 == 0 else not first for i in range(count))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def summarize(samples_ns: Sequence[int]) -> dict[str, float]:
|
|
65
|
+
ms = sorted(sample / 1_000_000 for sample in samples_ns)
|
|
66
|
+
cuts = statistics.quantiles(ms, n=100, method="inclusive")
|
|
67
|
+
return {
|
|
68
|
+
"n": len(ms),
|
|
69
|
+
"mean": statistics.fmean(ms),
|
|
70
|
+
"p50": statistics.median(ms),
|
|
71
|
+
"p90": cuts[89],
|
|
72
|
+
"p99": cuts[98],
|
|
73
|
+
"min": ms[0],
|
|
74
|
+
"max": ms[-1],
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def format_table(samples: dict[bool, list[int]]) -> str:
|
|
79
|
+
columns = ("n", "mean", "p50", "p90", "p99", "min", "max")
|
|
80
|
+
lines = [f"{'method':<12}" + "".join(f"{name:>10}" for name in columns)]
|
|
81
|
+
for fast in (False, True):
|
|
82
|
+
stats = summarize(samples[fast])
|
|
83
|
+
cells = "".join(
|
|
84
|
+
f"{stats[name]:>10d}" if name == "n" else f"{stats[name]:>10.2f}"
|
|
85
|
+
for name in columns
|
|
86
|
+
)
|
|
87
|
+
lines.append(f"{f'fast={fast}':<12}" + cells)
|
|
88
|
+
return "\n".join(lines) + "\n(latency in ms)"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _cancelled(response: CancelOrderResponse) -> bool:
|
|
92
|
+
return response["status"] == "ok" and response["response"]["data"]["statuses"] == [
|
|
93
|
+
"success"
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def _place_round(
|
|
98
|
+
client: AsyncHyperliquid, count: int, size_decimals: int
|
|
99
|
+
) -> list[int]:
|
|
100
|
+
px = await client.info.mid_price(COIN) * PRICE_RATIO
|
|
101
|
+
step = 10**size_decimals
|
|
102
|
+
sz = math.ceil(NOTIONAL_USDC / px * step) / step
|
|
103
|
+
order: PlaceOrderRequest = {
|
|
104
|
+
"coin": COIN,
|
|
105
|
+
"is_buy": True,
|
|
106
|
+
"sz": sz,
|
|
107
|
+
"px": px,
|
|
108
|
+
"is_market": False,
|
|
109
|
+
"order_type": limit_order_type(TimeInForce.ALO),
|
|
110
|
+
}
|
|
111
|
+
response = await client.place_orders([order.copy() for _ in range(count)])
|
|
112
|
+
if response["status"] != "ok":
|
|
113
|
+
raise BenchmarkFailure(f"placement failed: {response['response']}")
|
|
114
|
+
oids: list[int] = []
|
|
115
|
+
for status in response["response"]["data"]["statuses"]:
|
|
116
|
+
resting = status.get("resting") if isinstance(status, dict) else None
|
|
117
|
+
if not isinstance(resting, dict):
|
|
118
|
+
raise BenchmarkFailure(f"order did not rest: {status!r}")
|
|
119
|
+
oids.append(resting["oid"])
|
|
120
|
+
return oids
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def _run(args: argparse.Namespace, credentials: Credentials) -> str:
|
|
124
|
+
pacer = WeightedPacer(interval_ns=args.interval_ms * 1_000_000)
|
|
125
|
+
samples: dict[bool, list[int]] = {False: [], True: []}
|
|
126
|
+
async with AsyncHyperliquid(
|
|
127
|
+
credentials.master_address,
|
|
128
|
+
credentials.signing_key,
|
|
129
|
+
vault_address=credentials.subaccount_address,
|
|
130
|
+
network=Network.TESTNET,
|
|
131
|
+
) as client:
|
|
132
|
+
await client.info.refresh_metadata()
|
|
133
|
+
validate_roles(
|
|
134
|
+
credentials.master_address,
|
|
135
|
+
await client.info.user_role(credentials.api_wallet_address),
|
|
136
|
+
await client.info.user_role(credentials.subaccount_address),
|
|
137
|
+
)
|
|
138
|
+
size_decimals = await client.info.size_decimals(COIN)
|
|
139
|
+
pending: list[int] = []
|
|
140
|
+
try:
|
|
141
|
+
for round_index in range(args.warmups + args.rounds):
|
|
142
|
+
await pacer.wait(weight=1)
|
|
143
|
+
pending = await _place_round(client, args.orders, size_decimals)
|
|
144
|
+
plan = fast_plan(round_index, len(pending))
|
|
145
|
+
for oid, fast in zip(tuple(pending), plan, strict=True):
|
|
146
|
+
await pacer.wait(weight=1)
|
|
147
|
+
start = perf_counter_ns()
|
|
148
|
+
response = await client.cancel_order(
|
|
149
|
+
CancelOrder(COIN, oid), fast=fast
|
|
150
|
+
)
|
|
151
|
+
elapsed = perf_counter_ns() - start
|
|
152
|
+
if not _cancelled(response):
|
|
153
|
+
raise BenchmarkFailure(f"cancel failed: {response!r}")
|
|
154
|
+
pending.remove(oid)
|
|
155
|
+
if round_index >= args.warmups:
|
|
156
|
+
samples[fast].append(elapsed)
|
|
157
|
+
finally:
|
|
158
|
+
if pending:
|
|
159
|
+
await client.cancel_orders(tuple(CancelOrder(COIN, o) for o in pending))
|
|
160
|
+
return format_table(samples)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
164
|
+
args = parse_args(argv)
|
|
165
|
+
from dotenv import load_dotenv
|
|
166
|
+
|
|
167
|
+
load_dotenv(Path(".env.local"), override=False)
|
|
168
|
+
try:
|
|
169
|
+
credentials = Credentials.from_environ(os.environ)
|
|
170
|
+
print(asyncio.run(_run(args, credentials)))
|
|
171
|
+
except BenchmarkFailure as error:
|
|
172
|
+
print(f"fast-cancel benchmark failed: {error}", file=sys.stderr)
|
|
173
|
+
return 1
|
|
174
|
+
return 0
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
raise SystemExit(main())
|
|
@@ -406,6 +406,11 @@ Use ``CancelOrder(coin, oid)`` with ``cancel_order()`` or ``cancel_orders()``.
|
|
|
406
406
|
Use ``CancelByCloid(coin, cloid)`` with ``cancel_by_cloid()`` or
|
|
407
407
|
``cancel_orders_by_cloid()``. Empty batches are rejected locally.
|
|
408
408
|
|
|
409
|
+
All four cancel methods accept ``fast=True``, which sends Hyperliquid's ``f``
|
|
410
|
+
flag. The exchange rejects fast cancels that target trigger orders, and a future
|
|
411
|
+
network upgrade will prioritize fast cancels in the mempool. With the default
|
|
412
|
+
``fast=False`` the flag is omitted from the signed action.
|
|
413
|
+
|
|
409
414
|
``ModifyOrderRequest`` identifies the order by integer order ID or ``Cloid`` and
|
|
410
415
|
contains the complete replacement order. ``modify_order()`` returns a default
|
|
411
416
|
acknowledgement; ``modify_orders()`` returns per-order statuses.
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/orders.po
RENAMED
|
@@ -288,6 +288,18 @@ msgstr ""
|
|
|
288
288
|
"搭配使用。将 ``CancelByCloid(coin, cloid)`` 与 ``cancel_by_cloid()`` 或 "
|
|
289
289
|
"``cancel_orders_by_cloid()`` 搭配使用。空批次会在本地被拒绝。"
|
|
290
290
|
|
|
291
|
+
#: ../../howto/orders.rst:409
|
|
292
|
+
msgid ""
|
|
293
|
+
"All four cancel methods accept ``fast=True``, which sends Hyperliquid's "
|
|
294
|
+
"``f`` flag. The exchange rejects fast cancels that target trigger orders,"
|
|
295
|
+
" and a future network upgrade will prioritize fast cancels in the "
|
|
296
|
+
"mempool. With the default ``fast=False`` the flag is omitted from the "
|
|
297
|
+
"signed action."
|
|
298
|
+
msgstr ""
|
|
299
|
+
"四个撤单方法都接受 ``fast=True``,用于发送 Hyperliquid 的 ``f`` 标志。"
|
|
300
|
+
"交易所会拒绝针对触发单(trigger order)的快速撤单;未来的网络升级会在 mempool "
|
|
301
|
+
"中优先处理快速撤单。默认 ``fast=False`` 时,签名的 action 中不包含该标志。"
|
|
302
|
+
|
|
291
303
|
#: ../../howto/orders.rst:409 2815403bb6ba4974a7f7a2680ee41f54
|
|
292
304
|
msgid ""
|
|
293
305
|
"``ModifyOrderRequest`` identifies the order by integer order ID or "
|
|
@@ -332,12 +332,22 @@ class AsyncHyperliquid:
|
|
|
332
332
|
)
|
|
333
333
|
|
|
334
334
|
async def cancel_order(
|
|
335
|
-
self,
|
|
335
|
+
self,
|
|
336
|
+
order: CancelOrder,
|
|
337
|
+
*,
|
|
338
|
+
fast: bool = False,
|
|
339
|
+
expires_after: int | None = None,
|
|
336
340
|
) -> CancelOrderResponse:
|
|
337
|
-
return await self.cancel_orders(
|
|
341
|
+
return await self.cancel_orders(
|
|
342
|
+
(order,), fast=fast, expires_after=expires_after
|
|
343
|
+
)
|
|
338
344
|
|
|
339
345
|
async def cancel_orders(
|
|
340
|
-
self,
|
|
346
|
+
self,
|
|
347
|
+
orders: Sequence[CancelOrder],
|
|
348
|
+
*,
|
|
349
|
+
fast: bool = False,
|
|
350
|
+
expires_after: int | None = None,
|
|
341
351
|
) -> CancelOrderResponse:
|
|
342
352
|
commands = tuple(orders)
|
|
343
353
|
if not commands:
|
|
@@ -350,16 +360,26 @@ class AsyncHyperliquid:
|
|
|
350
360
|
for order, market in zip(commands, markets, strict=True)
|
|
351
361
|
)
|
|
352
362
|
return await self._exchange._submit_cancels(
|
|
353
|
-
cancels, expires_after=expires_after
|
|
363
|
+
cancels, fast=fast, expires_after=expires_after
|
|
354
364
|
)
|
|
355
365
|
|
|
356
366
|
async def cancel_by_cloid(
|
|
357
|
-
self,
|
|
367
|
+
self,
|
|
368
|
+
order: CancelByCloid,
|
|
369
|
+
*,
|
|
370
|
+
fast: bool = False,
|
|
371
|
+
expires_after: int | None = None,
|
|
358
372
|
) -> CancelOrderResponse:
|
|
359
|
-
return await self.cancel_orders_by_cloid(
|
|
373
|
+
return await self.cancel_orders_by_cloid(
|
|
374
|
+
(order,), fast=fast, expires_after=expires_after
|
|
375
|
+
)
|
|
360
376
|
|
|
361
377
|
async def cancel_orders_by_cloid(
|
|
362
|
-
self,
|
|
378
|
+
self,
|
|
379
|
+
orders: Sequence[CancelByCloid],
|
|
380
|
+
*,
|
|
381
|
+
fast: bool = False,
|
|
382
|
+
expires_after: int | None = None,
|
|
363
383
|
) -> CancelOrderResponse:
|
|
364
384
|
commands = tuple(orders)
|
|
365
385
|
if not commands:
|
|
@@ -372,7 +392,7 @@ class AsyncHyperliquid:
|
|
|
372
392
|
for order, market in zip(commands, markets, strict=True)
|
|
373
393
|
)
|
|
374
394
|
return await self._exchange._submit_cloid_cancels(
|
|
375
|
-
cancels, expires_after=expires_after
|
|
395
|
+
cancels, fast=fast, expires_after=expires_after
|
|
376
396
|
)
|
|
377
397
|
|
|
378
398
|
@staticmethod
|
|
@@ -195,24 +195,34 @@ class ExchangeClient:
|
|
|
195
195
|
return await self._submit_action(action, "order", expires_after=expires_after)
|
|
196
196
|
|
|
197
197
|
async def _submit_cancels(
|
|
198
|
-
self,
|
|
198
|
+
self,
|
|
199
|
+
cancels: Sequence[EncodedCancel],
|
|
200
|
+
*,
|
|
201
|
+
fast: bool = False,
|
|
202
|
+
expires_after: int | None = None,
|
|
199
203
|
) -> CancelOrderResponse:
|
|
200
204
|
encoded = list(cancels)
|
|
201
205
|
if not encoded:
|
|
202
206
|
raise ValueError("orders must not be empty")
|
|
203
207
|
action = CancelAction(type="cancel", cancels=encoded)
|
|
208
|
+
if fast:
|
|
209
|
+
# Hyperliquid rejects actions hashed with f=false, so only send f=true.
|
|
210
|
+
action["f"] = True
|
|
204
211
|
return await self._submit_action(action, "cancel", expires_after=expires_after)
|
|
205
212
|
|
|
206
213
|
async def _submit_cloid_cancels(
|
|
207
214
|
self,
|
|
208
215
|
cancels: Sequence[EncodedCancelByCloid],
|
|
209
216
|
*,
|
|
217
|
+
fast: bool = False,
|
|
210
218
|
expires_after: int | None = None,
|
|
211
219
|
) -> CancelOrderResponse:
|
|
212
220
|
encoded = list(cancels)
|
|
213
221
|
if not encoded:
|
|
214
222
|
raise ValueError("orders must not be empty")
|
|
215
223
|
action = CancelByCloidAction(type="cancelByCloid", cancels=encoded)
|
|
224
|
+
if fast:
|
|
225
|
+
action["f"] = True
|
|
216
226
|
return await self._submit_action(action, "cancel", expires_after=expires_after)
|
|
217
227
|
|
|
218
228
|
async def _post_envelope(
|
|
@@ -144,6 +144,7 @@ class EncodedCancel(TypedDict):
|
|
|
144
144
|
class CancelAction(TypedDict):
|
|
145
145
|
type: Literal["cancel"]
|
|
146
146
|
cancels: list[EncodedCancel]
|
|
147
|
+
f: NotRequired[Literal[True]]
|
|
147
148
|
|
|
148
149
|
|
|
149
150
|
class EncodedCancelByCloid(TypedDict):
|
|
@@ -154,6 +155,7 @@ class EncodedCancelByCloid(TypedDict):
|
|
|
154
155
|
class CancelByCloidAction(TypedDict):
|
|
155
156
|
type: Literal["cancelByCloid"]
|
|
156
157
|
cancels: list[EncodedCancelByCloid]
|
|
158
|
+
f: NotRequired[Literal[True]]
|
|
157
159
|
|
|
158
160
|
|
|
159
161
|
class EncodedModify(TypedDict):
|
|
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
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/report.json
RENAMED
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/samples.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/_templates/sidebar/language-switcher.html
RENAMED
|
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
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/index.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/markets.po
RENAMED
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/howto/routing.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/about.po
RENAMED
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/index.po
RENAMED
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/project/license.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/index.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/docs/locale/zh_CN/LC_MESSAGES/reference/types.po
RENAMED
|
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
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/__init__.py
RENAMED
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/encoding.py
RENAMED
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/exchange.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/metadata.py
RENAMED
|
File without changes
|
{async_hyperliquid-1.1.0 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/signing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|