The latency benchmark
Guard adds about 3 ms per order; from Tokyo your order reaches Hyperliquid in about 5 ms. What that covers, how it was measured, and how to rerun it.
The result
Section titled “The result”Guard adds about 3 ms per order: 2.75 ms at the median, from your bot’s request reaching Guard to Guard’s answer, with your account judged from Hyperliquid’s live account stream and Guard’s decision journal on a local SSD. Run it in Tokyo, next to Hyperliquid, and your order reaches the exchange in about 5 ms.
Measured 7 Oct 2026 in Tokyo (AWS ap-northeast-1, a c7gd.medium), in paper mode against Hyperliquid testnet: a bot signs an order and posts it to Guard on the same machine, and Guard answers with what it would send (crates/zunder-guard/tests/latency_live.rs, 72 entries, one every 5 s).
| Path | p50 | p90 | p99 |
|---|---|---|---|
| Judged from the account stream, journal on local SSD | 2.75 ms | 2.89 ms | 4.54 ms |
| Judged from the account stream, journal on a network disk (gp3) | 4.97 ms | 5.33 ms | 7.14 ms |
| Judged after reading the account first (stream not current) | 21.2 ms | 56.0 ms | 56.9 ms |
Every decision is written to Guard’s journal and synced to disk before anything is sent: on the local SSD that takes 0.09 ms, on the network disk about 2 ms. The stream is current except within about 1 to 6 seconds after something changes on your account (an order, a fill, a funding payment, a transfer): Guard then reads before it judges.
Why Tokyo
Section titled “Why Tokyo”Hyperliquid answers from Tokyo. The trip there and back dominates everything Guard does:
| From | Round trip to Hyperliquid mainnet, median |
|---|---|
Tokyo (AWS ap-northeast-1, kept-alive connection, 7 Oct 2026) | 4.5 ms |
| Hong Kong (a Cloudflare probe) | about 61 ms |
| Denver, US (a probe) | about 133 ms |
Frankfurt (AWS eu-central-1, 59 requests, 6 Oct 2026) | 238 ms |
From Tokyo, a bot’s request reaches Hyperliquid through Guard at about 5 ms (2.75 ms plus half the round trip), and the venue’s answer is back at about 7 ms. An entry whose isolated leverage Guard sets first costs one more round trip. Run Guard next to your bot, both in Tokyo, with Guard’s data directory on a local SSD.
Guard’s own work: 0.24 ms
Section titled “Guard’s own work: 0.24 ms”Of the 3 ms, Guard’s own work on a request (no network, no disk) takes about 0.24 ms: 238 µs at the median and 244 µs at the 99th percentile, on Zunder’s build box in Frankfurt (16 vCPUs), release build, 20,000 ccxt-style market buys with an attached stop, 6 Oct 2026:
| Step | p50 | p99 | p99.9 |
|---|---|---|---|
| Decode the request and authenticate the client key (one signer recovery) | 87.8 µs | 92.5 µs | 97.4 µs |
| Judge (risk engine sizing, the attached stop, isolated leverage) | 5.1 µs | 5.4 µs | 9.2 µs |
| Re-sign with the API wallet key (two signatures: the leverage update and the order) | 145.1 µs | 150.2 µs | 164.0 µs |
| Total | 238.2 µs | 244.1 µs | 258.3 µs |
A bot that signs as mainnet costs a second recovery, about 80 µs more. An order on a position that is already open needs one signature, not two.
The judgement time the homepage shows is something else again: the risk engine alone (the WebAssembly build of zunder-risk), timed with performance.now() in your browser. It is not Guard’s latency.
What it measures
Section titled “What it measures”The 3 ms: crates/zunder-guard/tests/latency_live.rs, above. The 0.24 ms: the test latency_of_the_full_request_path in crates/zunder-guard-core/src/bench.rs, per round: a request as ccxt sends it (a market buy with a stop), decoded and checked against the client key’s signature, judged by the same code Guard runs (the risk engine sizes it, Guard attaches its stop and sets isolated leverage), then signed again with the API wallet key, as Guard forwards it.
What it does not measure
Section titled “What it does not measure”- The trip to Hyperliquid. Neither in the 3 ms nor in the 0.24 ms; nor Hyperliquid’s processing (the table under “Why Tokyo” gives the round trips).
- The network in the 0.24 ms. Neither the hop from your bot to Guard nor from Guard to Hyperliquid (the 3 ms includes the loopback hop).
- Reading the account. None when the account stream is current; otherwise three
inforequests in parallel, about one round trip to Hyperliquid (the table above shows both). - Sending. One round trip to Hyperliquid, two when isolated leverage is set first.
- Contention. A busy laptop is slower.
Component benchmarks
Section titled “Component benchmarks”Two smaller benchmarks measure parts of the path on their own. They are not Guard’s speed; the 3 ms above is.
| Component | p50 | p99 | Where |
|---|---|---|---|
The risk check alone (RiskEngine::size_entry) | 0.3 µs | not recorded | crates/zunder-exec/src/hyperliquid/signing.rs, latency_of_sizing_and_signing |
Building and signing one order in Zunder’s own executor (zunder-exec), without the risk check | 69 µs | 74 µs | the same test |
Measured on Zunder’s build box, an AWS Graviton c7g.4xlarge in eu-central-1, 20,000 rounds (commit 1f13430, 6 Oct 2026). The sizing inputs are Example 5. std::time::Instant around sub-microsecond work rounds, so the 0.3 µs is an order of magnitude, not a precise figure.
Rerun it
Section titled “Rerun it”From a checkout of the source, in release mode:
ZUNDER_GUARD_LATENCY=1 cargo test --release -p zunder-guard --test latency_live -- --ignored --nocapture # the 3 ms: paper mode against testnet's public data, no keycargo test --release -p zunder-guard-core latency_of_the_full_request_path -- --ignored --nocapture # Guard's own work, the 0.24 mscargo test --release -p zunder-exec latency_of_sizing_and_signing -- --ignored --nocapture # the component benchmarkNumbers differ by machine. Please report yours with the CPU and the commit.
This page as plain Markdown, for people and LLMs: /docs/methods/latency.md