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.
Files changed (95) hide show
  1. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/CHANGELOG.md +45 -0
  2. async_hyperliquid-1.2.0/PKG-INFO +122 -0
  3. async_hyperliquid-1.2.0/README.md +88 -0
  4. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/README.md +30 -3
  5. async_hyperliquid-1.2.0/benchmarks/fast_cancel.py +178 -0
  6. async_hyperliquid-1.2.0/docs/_static/language-switcher.css +14 -0
  7. async_hyperliquid-1.2.0/docs/_templates/sidebar/language-switcher.html +15 -0
  8. async_hyperliquid-1.2.0/docs/conf.py +94 -0
  9. async_hyperliquid-1.2.0/docs/howto/index.rst +17 -0
  10. async_hyperliquid-1.2.0/docs/howto/info-queries.rst +89 -0
  11. async_hyperliquid-1.2.0/docs/howto/lifecycle-reconciliation.rst +66 -0
  12. async_hyperliquid-1.2.0/docs/howto/markets.rst +47 -0
  13. async_hyperliquid-1.2.0/docs/howto/orders.rst +450 -0
  14. async_hyperliquid-1.2.0/docs/howto/routing.rst +58 -0
  15. async_hyperliquid-1.2.0/docs/howto/transfers-administration.rst +282 -0
  16. async_hyperliquid-1.2.0/docs/index.rst +47 -0
  17. async_hyperliquid-1.2.0/docs/introduction/index.rst +39 -0
  18. async_hyperliquid-1.2.0/docs/introduction/installation.rst +28 -0
  19. async_hyperliquid-1.2.0/docs/introduction/quickstart.rst +119 -0
  20. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/coin-name-mapping.po +228 -0
  21. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/index.po +67 -0
  22. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/info-queries.po +134 -0
  23. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/lifecycle-reconciliation.po +148 -0
  24. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/markets.po +100 -0
  25. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/orders.po +381 -0
  26. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/routing.po +132 -0
  27. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/howto/transfers-administration.po +346 -0
  28. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/index.po +55 -0
  29. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/introduction/index.po +109 -0
  30. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/introduction/installation.po +53 -0
  31. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/introduction/quickstart.po +84 -0
  32. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/migration-0.5-to-1.0.po +796 -0
  33. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/about.po +37 -0
  34. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/benchmarks.po +111 -0
  35. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/index.po +37 -0
  36. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/project/license.po +36 -0
  37. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/async-hyperliquid.po +90 -0
  38. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/errors.po +52 -0
  39. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/exchange-client.po +100 -0
  40. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/index.po +51 -0
  41. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/info-client.po +71 -0
  42. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/response-types.po +69 -0
  43. async_hyperliquid-1.2.0/docs/locale/zh_CN/LC_MESSAGES/reference/types.po +79 -0
  44. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/docs/migration-0.5-to-1.0.md +6 -43
  45. async_hyperliquid-1.2.0/docs/project/about.rst +9 -0
  46. async_hyperliquid-1.2.0/docs/project/benchmarks.md +19 -0
  47. async_hyperliquid-1.2.0/docs/project/index.rst +17 -0
  48. async_hyperliquid-1.2.0/docs/project/license.rst +10 -0
  49. async_hyperliquid-1.2.0/docs/reference/async-hyperliquid.rst +38 -0
  50. async_hyperliquid-1.2.0/docs/reference/errors.rst +11 -0
  51. async_hyperliquid-1.2.0/docs/reference/exchange-client.rst +41 -0
  52. async_hyperliquid-1.2.0/docs/reference/index.rst +11 -0
  53. async_hyperliquid-1.2.0/docs/reference/info-client.rst +27 -0
  54. async_hyperliquid-1.2.0/docs/reference/response-types.rst +32 -0
  55. async_hyperliquid-1.2.0/docs/reference/types.rst +108 -0
  56. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/pyproject.toml +14 -5
  57. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/client.py +28 -8
  58. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/exchange.py +11 -1
  59. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/info.py +10 -0
  60. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/exchange.py +2 -0
  61. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/info.py +9 -0
  62. async_hyperliquid-1.0.0rc1/PKG-INFO +0 -520
  63. async_hyperliquid-1.0.0rc1/README.md +0 -486
  64. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/LICENSE +0 -0
  65. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/__init__.py +0 -0
  66. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/hotpath.py +0 -0
  67. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/__init__.py +0 -0
  68. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/models.py +0 -0
  69. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/pacing.py +0 -0
  70. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/preflight.py +0 -0
  71. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/providers.py +0 -0
  72. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/reporting.py +0 -0
  73. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/results.py +0 -0
  74. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/runner.py +0 -0
  75. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live/workload.py +0 -0
  76. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/live_exchange.py +0 -0
  77. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/cancel-id-latency.png +0 -0
  78. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/cancel-id-latency.svg +0 -0
  79. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/report.json +0 -0
  80. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/results/20260805T192621Z/samples.csv +0 -0
  81. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/benchmarks/signing.py +0 -0
  82. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/docs/coin-name-mapping.md +0 -0
  83. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/__init__.py +0 -0
  84. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/__init__.py +0 -0
  85. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/encoding.py +0 -0
  86. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/exchange.py +0 -0
  87. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/http.py +0 -0
  88. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/info.py +0 -0
  89. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/metadata.py +0 -0
  90. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/_internal/signing.py +0 -0
  91. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/constants.py +0 -0
  92. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/errors.py +0 -0
  93. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/py.typed +0 -0
  94. {async_hyperliquid-1.0.0rc1 → async_hyperliquid-1.2.0}/src/async_hyperliquid/types/__init__.py +0 -0
  95. {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
+ [![CI](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml/badge.svg)](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml)
38
+ [![Documentation Status](https://readthedocs.org/projects/async-hyperliquid/badge/?version=latest)](https://async-hyperliquid.readthedocs.io/en/latest/)
39
+ [![PyPI](https://img.shields.io/pypi/v/async-hyperliquid.svg?v=1.2.0)](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
+ [![CI](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml/badge.svg)](https://github.com/owlyfi/async-hyperliquid/actions/workflows/ci.yml)
4
+ [![Documentation Status](https://readthedocs.org/projects/async-hyperliquid/badge/?version=latest)](https://async-hyperliquid.readthedocs.io/en/latest/)
5
+ [![PyPI](https://img.shields.io/pypi/v/async-hyperliquid.svg?v=1.2.0)](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 prevents the much faster `action_hash` workload from
382
- dominating the result merely because it has a larger absolute ops/s value. The
383
- same five workloads, versions, and weighting must be used for comparisons.
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.