async-hyperliquid 1.0.0rc1__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.0.0rc1 → async_hyperliquid-1.2.0}/CHANGELOG.md +45 -0
- async_hyperliquid-1.2.0/PKG-INFO +122 -0
- async_hyperliquid-1.2.0/README.md +88 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/README.md +30 -3
- async_hyperliquid-1.2.0/benchmarks/fast_cancel.py +178 -0
- async_hyperliquid-1.2.0/docs/_static/language-switcher.css +14 -0
- async_hyperliquid-1.2.0/docs/_templates/sidebar/language-switcher.html +15 -0
- async_hyperliquid-1.2.0/docs/conf.py +94 -0
- async_hyperliquid-1.2.0/docs/howto/index.rst +17 -0
- async_hyperliquid-1.2.0/docs/howto/info-queries.rst +89 -0
- async_hyperliquid-1.2.0/docs/howto/lifecycle-reconciliation.rst +66 -0
- async_hyperliquid-1.2.0/docs/howto/markets.rst +47 -0
- async_hyperliquid-1.2.0/docs/howto/orders.rst +450 -0
- async_hyperliquid-1.2.0/docs/howto/routing.rst +58 -0
- async_hyperliquid-1.2.0/docs/howto/transfers-administration.rst +282 -0
- async_hyperliquid-1.2.0/docs/index.rst +47 -0
- async_hyperliquid-1.2.0/docs/introduction/index.rst +39 -0
- async_hyperliquid-1.2.0/docs/introduction/installation.rst +28 -0
- async_hyperliquid-1.2.0/docs/introduction/quickstart.rst +119 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/coin-name-mapping.po +228 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/index.po +67 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/info-queries.po +134 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/lifecycle-reconciliation.po +148 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/markets.po +100 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/orders.po +381 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/routing.po +132 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/transfers-administration.po +346 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/index.po +55 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/introduction/index.po +109 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/introduction/installation.po +53 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/introduction/quickstart.po +84 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/migration-0.5-to-1.0.po +796 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/about.po +37 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/benchmarks.po +111 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/index.po +37 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/license.po +36 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/async-hyperliquid.po +90 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/errors.po +52 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/exchange-client.po +100 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/index.po +51 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/info-client.po +71 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/response-types.po +69 -0
- async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/types.po +79 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/docs/migration-0.5-to-1.0.md +6 -43
- async_hyperliquid-1.2.0/docs/project/about.rst +9 -0
- async_hyperliquid-1.2.0/docs/project/benchmarks.md +19 -0
- async_hyperliquid-1.2.0/docs/project/index.rst +17 -0
- async_hyperliquid-1.2.0/docs/project/license.rst +10 -0
- async_hyperliquid-1.2.0/docs/reference/async-hyperliquid.rst +38 -0
- async_hyperliquid-1.2.0/docs/reference/errors.rst +11 -0
- async_hyperliquid-1.2.0/docs/reference/exchange-client.rst +41 -0
- async_hyperliquid-1.2.0/docs/reference/index.rst +11 -0
- async_hyperliquid-1.2.0/docs/reference/info-client.rst +27 -0
- async_hyperliquid-1.2.0/docs/reference/response-types.rst +32 -0
- async_hyperliquid-1.2.0/docs/reference/types.rst +108 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/pyproject.toml +14 -5
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/client.py +28 -8
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/exchange.py +11 -1
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/info.py +10 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/exchange.py +2 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/info.py +9 -0
- async_hyperliquid-1.0.0rc1/PKG-INFO +0 -520
- async_hyperliquid-1.0.0rc1/README.md +0 -486
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/LICENSE +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/__init__.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/hotpath.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/__init__.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/models.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/pacing.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/preflight.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/providers.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/reporting.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/results.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/runner.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/workload.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live_exchange.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/cancel-id-latency.png +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/cancel-id-latency.svg +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/report.json +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/samples.csv +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/signing.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/docs/coin-name-mapping.md +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/__init__.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/__init__.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/encoding.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/exchange.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/http.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/info.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/metadata.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/signing.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/constants.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/errors.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/py.typed +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/__init__.py +0 -0
- {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/common.py +0 -0
|
@@ -5,6 +5,51 @@ 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
|
+
|
|
19
|
+
## [1.1.0] - 2026-09-14
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Add `InfoClient.extra_agents()` with typed API-wallet responses.
|
|
24
|
+
- Add `reversed=True` to `InfoClient.user_fills()` time-range queries to request
|
|
25
|
+
the newest fills first; `start_time` is required when enabled.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Cover ascending and mixed-order fill responses to detect unintended client-side
|
|
30
|
+
sorting, and verify reversed fill queries against complete reference windows.
|
|
31
|
+
|
|
32
|
+
## [1.0.0] - 2026-08-17
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Publish comprehensive Read the Docs documentation for installation, Info and
|
|
37
|
+
Exchange workflows, routing, market and asset mapping, reconciliation,
|
|
38
|
+
migration, project ownership, licensing, and the public API reference.
|
|
39
|
+
- Add a complete Simplified Chinese documentation build using Sphinx gettext,
|
|
40
|
+
while preserving Python identifiers, signatures, types, code, and autodoc
|
|
41
|
+
content in English.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Promote the 1.0 API from release candidate to stable without additional
|
|
46
|
+
runtime contract changes.
|
|
47
|
+
- Gate English and Simplified Chinese documentation in CI, release validation,
|
|
48
|
+
and source-distribution tests with warnings treated as errors.
|
|
49
|
+
- Publish GitHub Release notes from the exact matching CHANGELOG section and
|
|
50
|
+
verify the body before and after immutable release publication.
|
|
51
|
+
- Run artifact download actions on the Node 24 runtime.
|
|
52
|
+
|
|
8
53
|
## [1.0.0rc1] - 2026-08-04
|
|
9
54
|
|
|
10
55
|
### Added
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: async-hyperliquid
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Async Hyperliquid client using aiohttp
|
|
5
|
+
Keywords: dex,hyperliquid,async,aiohttp,trading,cryptocurrency,defi
|
|
6
|
+
Author: Yuki
|
|
7
|
+
Author-email: Yuki <yuqi.lyle@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
19
|
+
Classifier: Framework :: AsyncIO
|
|
20
|
+
Classifier: Framework :: aiohttp
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Dist: aiohttp>=3.14.3,<4.0.0
|
|
23
|
+
Requires-Dist: msgpack>=1.2.1,<2.0.0
|
|
24
|
+
Requires-Dist: eth-account>=0.13.7,<0.14.0
|
|
25
|
+
Requires-Dist: eth-utils>=5.3.1,<6.0.0
|
|
26
|
+
Requires-Dist: coincurve>=21.0.0
|
|
27
|
+
Requires-Python: >=3.12
|
|
28
|
+
Project-URL: Homepage, https://github.com/owlyfi/async-hyperliquid
|
|
29
|
+
Project-URL: Documentation, https://async-hyperliquid.readthedocs.io/en/latest/
|
|
30
|
+
Project-URL: Repository, https://github.com/owlyfi/async-hyperliquid
|
|
31
|
+
Project-URL: Issues, https://github.com/owlyfi/async-hyperliquid/issues
|
|
32
|
+
Project-URL: Changelog, https://github.com/owlyfi/async-hyperliquid/blob/main/CHANGELOG.md
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Async Hyperliquid
|
|
36
|
+
|
|
37
|
+
[](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml)
|
|
38
|
+
[](https://async-hyperliquid.readthedocs.io/en/latest/)
|
|
39
|
+
[](https://pypi.org/project/async-hyperliquid/)
|
|
40
|
+
|
|
41
|
+
`async-hyperliquid` is a typed, asynchronous Python client for the Hyperliquid
|
|
42
|
+
REST API. It provides credential-free market and account queries through
|
|
43
|
+
`InfoClient`, plus authenticated order workflows through `AsyncHyperliquid`.
|
|
44
|
+
The client uses `aiohttp`, keeps protocol-shaped responses as `TypedDict`, and
|
|
45
|
+
supports explicit mainnet and testnet routing.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install async-hyperliquid
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Read market data
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import asyncio
|
|
57
|
+
|
|
58
|
+
from async_hyperliquid import InfoClient
|
|
59
|
+
from async_hyperliquid.types import Network
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def main() -> None:
|
|
63
|
+
async with InfoClient(network=Network.MAINNET) as client:
|
|
64
|
+
print(await client.mid_price("BTC"))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Place a testnet order
|
|
71
|
+
|
|
72
|
+
Use a dedicated API-wallet key and start on testnet. This submits a signed
|
|
73
|
+
market order.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
import os
|
|
78
|
+
|
|
79
|
+
from async_hyperliquid import AsyncHyperliquid
|
|
80
|
+
from async_hyperliquid.types import Network
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def main() -> None:
|
|
84
|
+
async with AsyncHyperliquid(
|
|
85
|
+
os.environ["HL_ACCOUNT_ADDRESS"],
|
|
86
|
+
os.environ["HL_SIGNING_KEY"],
|
|
87
|
+
network=Network.TESTNET,
|
|
88
|
+
) as client:
|
|
89
|
+
result = await client.place_order("BTC", True, 0.001, 0, is_market=True)
|
|
90
|
+
if result["status"] == "err":
|
|
91
|
+
raise RuntimeError(result["response"])
|
|
92
|
+
status = result["response"]["data"]["statuses"][0]
|
|
93
|
+
if isinstance(status, dict) and "error" in status:
|
|
94
|
+
raise RuntimeError(status["error"])
|
|
95
|
+
print(status)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
asyncio.run(main())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Learn more in the [Read the Docs documentation](https://async-hyperliquid.readthedocs.io/en/latest/),
|
|
102
|
+
[API reference](https://async-hyperliquid.readthedocs.io/en/latest/reference/index.html),
|
|
103
|
+
[migration guide](https://async-hyperliquid.readthedocs.io/en/latest/migration-0.5-to-1.0.html),
|
|
104
|
+
[changelog](CHANGELOG.md), and [license](LICENSE).
|
|
105
|
+
|
|
106
|
+
## Benchmarks
|
|
107
|
+
|
|
108
|
+
The overall signing benchmark values below are from a parity-gated `1.0.0rc1`
|
|
109
|
+
local CPU reference run. They are not network latency or exchange throughput.
|
|
110
|
+
See the detailed [benchmarks/README.md](benchmarks/README.md) manual for the
|
|
111
|
+
full methodology, parity gate, and per-operation results.
|
|
112
|
+
|
|
113
|
+
<!-- signing-benchmark:overall:start -->
|
|
114
|
+
| Library | Geometric-mean throughput | Relative to SDK |
|
|
115
|
+
|---|---:|---:|
|
|
116
|
+
| async-hyperliquid | 24,641 ops/s | 1.460x |
|
|
117
|
+
| Official SDK | 16,874 ops/s | 1.000x |
|
|
118
|
+
| CCXT | 803 ops/s | 0.0476x |
|
|
119
|
+
|
|
120
|
+
The geometric mean gives each of the five operations equal weight, preventing
|
|
121
|
+
the faster `action_hash` workload from dominating the score.
|
|
122
|
+
<!-- signing-benchmark:overall:end -->
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Async Hyperliquid
|
|
2
|
+
|
|
3
|
+
[](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml)
|
|
4
|
+
[](https://async-hyperliquid.readthedocs.io/en/latest/)
|
|
5
|
+
[](https://pypi.org/project/async-hyperliquid/)
|
|
6
|
+
|
|
7
|
+
`async-hyperliquid` is a typed, asynchronous Python client for the Hyperliquid
|
|
8
|
+
REST API. It provides credential-free market and account queries through
|
|
9
|
+
`InfoClient`, plus authenticated order workflows through `AsyncHyperliquid`.
|
|
10
|
+
The client uses `aiohttp`, keeps protocol-shaped responses as `TypedDict`, and
|
|
11
|
+
supports explicit mainnet and testnet routing.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install async-hyperliquid
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Read market data
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import asyncio
|
|
23
|
+
|
|
24
|
+
from async_hyperliquid import InfoClient
|
|
25
|
+
from async_hyperliquid.types import Network
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def main() -> None:
|
|
29
|
+
async with InfoClient(network=Network.MAINNET) as client:
|
|
30
|
+
print(await client.mid_price("BTC"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
asyncio.run(main())
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Place a testnet order
|
|
37
|
+
|
|
38
|
+
Use a dedicated API-wallet key and start on testnet. This submits a signed
|
|
39
|
+
market order.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import asyncio
|
|
43
|
+
import os
|
|
44
|
+
|
|
45
|
+
from async_hyperliquid import AsyncHyperliquid
|
|
46
|
+
from async_hyperliquid.types import Network
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def main() -> None:
|
|
50
|
+
async with AsyncHyperliquid(
|
|
51
|
+
os.environ["HL_ACCOUNT_ADDRESS"],
|
|
52
|
+
os.environ["HL_SIGNING_KEY"],
|
|
53
|
+
network=Network.TESTNET,
|
|
54
|
+
) as client:
|
|
55
|
+
result = await client.place_order("BTC", True, 0.001, 0, is_market=True)
|
|
56
|
+
if result["status"] == "err":
|
|
57
|
+
raise RuntimeError(result["response"])
|
|
58
|
+
status = result["response"]["data"]["statuses"][0]
|
|
59
|
+
if isinstance(status, dict) and "error" in status:
|
|
60
|
+
raise RuntimeError(status["error"])
|
|
61
|
+
print(status)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Learn more in the [Read the Docs documentation](https://async-hyperliquid.readthedocs.io/en/latest/),
|
|
68
|
+
[API reference](https://async-hyperliquid.readthedocs.io/en/latest/reference/index.html),
|
|
69
|
+
[migration guide](https://async-hyperliquid.readthedocs.io/en/latest/migration-0.5-to-1.0.html),
|
|
70
|
+
[changelog](CHANGELOG.md), and [license](LICENSE).
|
|
71
|
+
|
|
72
|
+
## Benchmarks
|
|
73
|
+
|
|
74
|
+
The overall signing benchmark values below are from a parity-gated `1.0.0rc1`
|
|
75
|
+
local CPU reference run. They are not network latency or exchange throughput.
|
|
76
|
+
See the detailed [benchmarks/README.md](benchmarks/README.md) manual for the
|
|
77
|
+
full methodology, parity gate, and per-operation results.
|
|
78
|
+
|
|
79
|
+
<!-- signing-benchmark:overall:start -->
|
|
80
|
+
| Library | Geometric-mean throughput | Relative to SDK |
|
|
81
|
+
|---|---:|---:|
|
|
82
|
+
| async-hyperliquid | 24,641 ops/s | 1.460x |
|
|
83
|
+
| Official SDK | 16,874 ops/s | 1.000x |
|
|
84
|
+
| CCXT | 803 ops/s | 0.0476x |
|
|
85
|
+
|
|
86
|
+
The geometric mean gives each of the five operations equal weight, preventing
|
|
87
|
+
the faster `action_hash` workload from dominating the score.
|
|
88
|
+
<!-- signing-benchmark:overall:end -->
|
|
@@ -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
|
|
@@ -372,15 +396,18 @@ divided by the provider aggregate median, so higher is better.
|
|
|
372
396
|
The overall score gives each of the five operations equal weight. It is the
|
|
373
397
|
geometric mean of their aggregate throughputs:
|
|
374
398
|
|
|
399
|
+
<!-- signing-benchmark:overall:start -->
|
|
375
400
|
| Library | Geometric-mean throughput | Relative to SDK |
|
|
376
401
|
|---|---:|---:|
|
|
377
402
|
| async-hyperliquid | 24,641 ops/s | 1.460x |
|
|
378
403
|
| Official SDK | 16,874 ops/s | 1.000x |
|
|
379
404
|
| CCXT | 803 ops/s | 0.0476x |
|
|
380
405
|
|
|
381
|
-
The geometric mean
|
|
382
|
-
|
|
383
|
-
|
|
406
|
+
The geometric mean gives each of the five operations equal weight, preventing
|
|
407
|
+
the faster `action_hash` workload from dominating the score.
|
|
408
|
+
<!-- signing-benchmark:overall:end -->
|
|
409
|
+
|
|
410
|
+
The same five workloads, versions, and weighting must be used for comparisons.
|
|
384
411
|
|
|
385
412
|
## Correctness verification
|
|
386
413
|
|
|
@@ -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())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.sidebar-language-switch {
|
|
2
|
+
align-items: center;
|
|
3
|
+
display: flex;
|
|
4
|
+
font-size: 0.875rem;
|
|
5
|
+
gap: 0.5rem;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
margin: -0.5rem 0 0.75rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.sidebar-language-switch a[aria-current="page"] {
|
|
11
|
+
color: var(--color-foreground-primary);
|
|
12
|
+
font-weight: 600;
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<nav class="sidebar-language-switch" aria-label="Documentation language">
|
|
2
|
+
{% if documentation_is_hosted %}
|
|
3
|
+
<a href="https://async-hyperliquid.readthedocs.io/en/{{ documentation_version|e }}/{{ pagename|e }}.html"
|
|
4
|
+
lang="en"{% if documentation_language == "en" %} aria-current="page"{% endif %}>English</a>
|
|
5
|
+
<span aria-hidden="true">|</span>
|
|
6
|
+
<a href="https://async-hyperliquid.readthedocs.io/zh-cn/{{ documentation_version|e }}/{{ pagename|e }}.html"
|
|
7
|
+
lang="zh-CN"{% if documentation_language == "zh-cn" %} aria-current="page"{% endif %}>简体中文</a>
|
|
8
|
+
{% else %}
|
|
9
|
+
<a href="{{ documentation_root_prefix|e }}en/{{ pagename|e }}.html"
|
|
10
|
+
lang="en"{% if documentation_language == "en" %} aria-current="page"{% endif %}>English</a>
|
|
11
|
+
<span aria-hidden="true">|</span>
|
|
12
|
+
<a href="{{ documentation_root_prefix|e }}zh_CN/{{ pagename|e }}.html"
|
|
13
|
+
lang="zh-CN"{% if documentation_language == "zh-cn" %} aria-current="page"{% endif %}>简体中文</a>
|
|
14
|
+
{% endif %}
|
|
15
|
+
</nav>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from importlib.metadata import metadata, version as package_version
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
project = "async-hyperliquid"
|
|
7
|
+
release = package_version(project)
|
|
8
|
+
version = release
|
|
9
|
+
|
|
10
|
+
requires_python = metadata(project)["Requires-Python"]
|
|
11
|
+
minimum_python = requires_python.removeprefix(">=")
|
|
12
|
+
if minimum_python == requires_python:
|
|
13
|
+
raise RuntimeError(
|
|
14
|
+
"documentation expects a minimum-only Requires-Python package constraint"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
rst_prolog = "\n".join(
|
|
18
|
+
(
|
|
19
|
+
f".. |minimum-python| replace:: {minimum_python}",
|
|
20
|
+
f".. |requires-python| replace:: ``{requires_python}``",
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
extensions = [
|
|
25
|
+
"myst_parser",
|
|
26
|
+
"sphinx.ext.autodoc",
|
|
27
|
+
"sphinx_copybutton",
|
|
28
|
+
"sphinx_inline_tabs",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
locale_dirs = ["locale/"]
|
|
32
|
+
gettext_compact = False
|
|
33
|
+
gettext_uuid = True
|
|
34
|
+
|
|
35
|
+
exclude_patterns = ["_build"]
|
|
36
|
+
html_theme = "furo"
|
|
37
|
+
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
|
|
38
|
+
|
|
39
|
+
_HOSTED_TO_SPHINX_LANGUAGE = {"en": "en", "zh-cn": "zh_CN"}
|
|
40
|
+
_SPHINX_TO_HOSTED_LANGUAGE = {
|
|
41
|
+
value: key for key, value in _HOSTED_TO_SPHINX_LANGUAGE.items()
|
|
42
|
+
}
|
|
43
|
+
_READTHEDOCS_VERSION = os.environ.get("READTHEDOCS_VERSION", "latest")
|
|
44
|
+
_IS_READTHEDOCS = os.environ.get("READTHEDOCS") == "True"
|
|
45
|
+
if not re.fullmatch(r"[a-z0-9._-]+", _READTHEDOCS_VERSION, flags=re.ASCII):
|
|
46
|
+
raise RuntimeError(
|
|
47
|
+
"READTHEDOCS_VERSION must be a non-empty Read the Docs version slug"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
rtd_language = os.environ.get("READTHEDOCS_LANGUAGE")
|
|
51
|
+
if rtd_language is not None:
|
|
52
|
+
try:
|
|
53
|
+
language = _HOSTED_TO_SPHINX_LANGUAGE[rtd_language]
|
|
54
|
+
except KeyError as exc:
|
|
55
|
+
raise RuntimeError(
|
|
56
|
+
f"unsupported Read the Docs language: {rtd_language}"
|
|
57
|
+
) from exc
|
|
58
|
+
|
|
59
|
+
html_title = project
|
|
60
|
+
templates_path = ["_templates"]
|
|
61
|
+
html_static_path = ["_static"]
|
|
62
|
+
html_css_files = ["language-switcher.css"]
|
|
63
|
+
html_sidebars = {
|
|
64
|
+
"**": [
|
|
65
|
+
"sidebar/brand.html",
|
|
66
|
+
"sidebar/language-switcher.html",
|
|
67
|
+
"sidebar/search.html",
|
|
68
|
+
"sidebar/scroll-start.html",
|
|
69
|
+
"sidebar/navigation.html",
|
|
70
|
+
"sidebar/ethical-ads.html",
|
|
71
|
+
"sidebar/scroll-end.html",
|
|
72
|
+
"sidebar/variant-selector.html",
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _add_documentation_context(app, pagename, templatename, context, doctree):
|
|
78
|
+
del templatename, doctree
|
|
79
|
+
rendered_language = app.config.language or "en"
|
|
80
|
+
try:
|
|
81
|
+
context["documentation_language"] = _SPHINX_TO_HOSTED_LANGUAGE[
|
|
82
|
+
rendered_language
|
|
83
|
+
]
|
|
84
|
+
except KeyError as exc:
|
|
85
|
+
raise RuntimeError(
|
|
86
|
+
f"unsupported Sphinx documentation language: {rendered_language}"
|
|
87
|
+
) from exc
|
|
88
|
+
context["documentation_version"] = _READTHEDOCS_VERSION
|
|
89
|
+
context["documentation_is_hosted"] = _IS_READTHEDOCS
|
|
90
|
+
context["documentation_root_prefix"] = "../" * (pagename.count("/") + 1)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def setup(app):
|
|
94
|
+
app.connect("html-page-context", _add_documentation_context)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
How-to guides
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
Choose the guide for the outcome you need, then follow its safety and response
|
|
5
|
+
steps before copying an example:
|
|
6
|
+
|
|
7
|
+
* :doc:`info-queries` covers credential-free market and account reads.
|
|
8
|
+
* :doc:`markets` explains canonical coin names and metadata-derived IDs.
|
|
9
|
+
* :doc:`orders` covers order placement, triggers, TP/SL, TWAP, modification,
|
|
10
|
+
cancellation, and closing.
|
|
11
|
+
* :doc:`routing` explains main accounts, API wallets, subaccounts, vaults, and
|
|
12
|
+
endpoint trust boundaries.
|
|
13
|
+
* :doc:`transfers-administration` covers fund-moving operations with explicit
|
|
14
|
+
signed-action examples, plus paid, administrative, outcome, and validator
|
|
15
|
+
actions.
|
|
16
|
+
* :doc:`lifecycle-reconciliation` covers sessions, errors, nonce ownership, and
|
|
17
|
+
ambiguous signed outcomes.
|