Benchmark crypto perps order submission latency.
- Go 1.25+
uvfor live Hyperliquid/Lighter/Extended runs; Python 3 for Aster- Venue accounts funded and configured before using
--confirm-live
Long-running services should prefer direct Python interpreters over resident
uv run wrappers once dependencies are installed. Set PERPS_BENCH_PYTHON to
a shared Python command prefix; matching uv run ... python internal/venues/<venue>/... commands will execute through that prefix.
Generate local wallet material:
go run ./cmd/perps-bench accounts generate \
--venues hyperliquid,lighter \
--out .env.wallets.localPrint the setup checklist. This shows the public wallet identifiers you need for venue setup.
go run ./cmd/perps-bench accounts checklist \
--venues hyperliquid,lighter \
--env-file .env.wallets.localComplete the venue-side setup:
- Hyperliquid: register or approve the printed EVM wallet if needed, fund the
Hyperliquid account or agent wallet, then adjust the BTC order params in
examples/hyperliquid-builder.jsonif needed. - Lighter: use the printed Ethereum address for account creation/deposits,
generate an API key in Lighter, fill
LIGHTER_PRIVATE_KEY,LIGHTER_ACCOUNT_INDEX, andLIGHTER_API_KEY_INDEXin.env.wallets.local, optionally setLIGHTER_MAKER_PRIVATE_KEYandLIGHTER_MAKER_API_KEY_INDEXfor a maker-only key, then adjust the BTC order params inexamples/lighter-builder.jsonif needed.
Verify that required local environment is present:
go run ./cmd/perps-bench accounts check \
--venues hyperliquid,lighter \
--env-file .env.wallets.localRun a benchmark:
go run ./cmd/perps-bench run \
--config examples/hyperliquid-builder.json \
--env-file .env.wallets.local \
--confirm-liveSwap the config file for another configured venue, for example:
examples/lighter-builder.json
examples/aster-builder.json
examples/extended-builder.jsonThe starter configs use small post-only orders. Keep them small until account setup is confirmed. Cleanup runs after each measured submit, outside the latency window.
Post-only builder configs should derive price from the venue book instead of a
fixed BTC level. Set post_only_price_source to book_top to enable the warm
book-top stream, post_only_price_offset_bps for the maker-safe distance from
best bid/ask, and post_only_book_max_age_ms for the stale-book guard. The live
starter post-only configs use a 500 bps offset and fail request preparation if
the book is missing or stale.
Hyperliquid post-only configs can set post_only_price_source to
hyperliquid_l2book_http to fetch POST /info l2Book pricing instead of
opening another book WebSocket. Use post_only_price_refresh_ms to control the
cache TTL; the starter Hyperliquid configs refresh every five minutes.
For long-running taker sampling, use the venue-specific *-taker-builder.json
config and strict cleanup.
Use persistent WebSocket order submission whenever a venue has a verified official order-entry WebSocket path. Hyperliquid and Lighter single, batch, and taker configs use WebSocket for this reason. Aster and Extended currently use HTTPS for order submission with WebSocket account-feed confirmation because the verified official WebSocket docs cover market/private streams, not order-entry submission.
go run ./cmd/perps-bench accounts plan --venues hyperliquid,lighter
go run ./cmd/perps-bench accounts generate --venues hyperliquid,lighter --out .env.wallets.local
go run ./cmd/perps-bench accounts checklist --venues hyperliquid,lighter --env-file .env.wallets.local
go run ./cmd/perps-bench accounts check --venues hyperliquid,lighter --env-file .env.wallets.localSee docs/credentials.md for env-file details.
Write JSON/CSV results:
go run ./cmd/perps-bench run \
--config examples/hyperliquid-builder.json \
--env-file .env.wallets.local \
--confirm-live \
--output results/hyperliquid.json \
--csv results/hyperliquid.csvSummaries default to full response latency. To use TTFB:
--latency-mode ttfbTo measure private-stream confirmation instead of submit response:
--measurement-mode ws_confirmationCompare saved result files:
go run ./cmd/perps-bench compare-results \
results/hyperliquid.json \
results/lighter.jsonSome venues support order submission over both HTTPS and WebSocket. Compare them with:
go run ./cmd/perps-bench compare-transports \
--config examples/hyperliquid-builder.json \
--env-file .env.wallets.local \
--transports https,websocket \
--iterations 50 \
--warmups 5 \
--confirm-live \
--output results/hyperliquid-transports.jsonUnsupported transport/scenario combinations fail before the run starts.
Run a benchmark continuously into a local SQLite store:
go run ./cmd/perps-bench run-continuous \
--config examples/lighter-builder.json \
--env-file .env.wallets.local \
--transport websocket \
--rate 0.0166667 \
--chunk-iterations 1 \
--cleanup-mode strict \
--confirm-live \
--store data/bench.dbServe the read-only API:
go run ./cmd/perps-bench serve \
--store data/bench.db \
--listen 127.0.0.1:8080Print the collector/API service topology before wiring process supervision:
go run ./cmd/perps-bench service-topology \
--config examples/lighter-builder.json \
--env-file .env.wallets.local \
--exchange-tps-venue hyperliquid \
--validate-binary ./perps-bench \
--store data/bench.db \
--listen 127.0.0.1:8080Check a supervised deployment for failed services and stale accepted samples:
python3 scripts/service_watchdog.py --store data/bench.dbFor transient venue/API outages that trip systemd start limits, operators can explicitly reset and start failed benchmark services:
python3 scripts/service_watchdog.py --store data/bench.db --restart-failedExpose it publicly with a password:
export PERPS_BENCH_API_PASSWORD='choose-a-long-password'
go run ./cmd/perps-bench serve \
--store data/bench.db \
--listen 0.0.0.0:8080 \
--cors-origin ""Check the API with:
curl -u "bench:$PERPS_BENCH_API_PASSWORD" \
"http://YOUR_SERVER:8080/api/latest?window=5m"Start the read-only API:
go run ./cmd/perps-bench serve \
--store data/bench.db \
--listen 127.0.0.1:8080Start the dashboard:
cd frontend
npm install
npm run devOpen the URL printed by Vite, normally http://127.0.0.1:3000. The dashboard
keeps API credentials on the server side; they are not exposed to the browser.
For local development against an authenticated API, create frontend/.dev.vars:
PERPS_BENCH_API_URL=https://your-benchmark-api.example.com
PERPS_BENCH_API_USER=bench
PERPS_BENCH_API_PASSWORD=your-passwordDeploy the dashboard:
cd frontend
npx wrangler secret put PERPS_BENCH_API_URL --config wrangler.jsonc
npx wrangler secret put PERPS_BENCH_API_PASSWORD --config wrangler.jsonc
npm run deploy:stagingThe deployment flow uses staging as the local/default integration branch and
main as production. Pushes to staging deploy the separate Cloudflare Worker
perps-latency-dashboard-staging at
https://staging-latency.perps.trading; merges to main deploy production
perps-latency-dashboard at https://latency.perps.trading.
cd frontend
npm run deploy:staging
npm run deploy:productionAutodeploy can call the same npm scripts from the relevant branch. The Worker
runtime secrets PERPS_BENCH_API_URL and PERPS_BENCH_API_PASSWORD are set
directly in Cloudflare for each Worker environment.
Live runs require --confirm-live.
Fill-likely order profiles, including market, IOC, FOK, and explicit
non-post-only orders, require risk.allow_fill=true,
risk.neutralize_on_fill=true, and strict after-sample cleanup. Start with
post-only/maker-style orders while validating setup.
Hyperliquid, Lighter, Aster, Extended, and Pacifica support cleanup of
benchmark orders. Cleanup runs outside the measured latency window. At startup,
venues with authenticated account reads check for stale orders from the same
run_id; after the run, those venues reconcile that no submitted benchmark
orders remain open and the position did not change. Pacifica currently prepares
per-sample WebSocket cancel requests by client order ID. Use strict cleanup when
cleanup failures should fail the sample:
--cleanup --cleanup-mode strictEach live run also records a conservative network-floor estimate on every sample. The estimate prefers clean same-route observations in this order: WebSocket heartbeat RTT when the venue defines a safe heartbeat, real HTTP request TCP connect timing when a fresh connection is opened, then rolling low-percentile TCP connect timing to the same venue host and port as fallback. Raw latency remains the default dashboard view; use the dashboard's "Subtract network floor" toggle to inspect the optional network-adjusted view.
- Hyperliquid: HTTPS/WebSocket order submission, confirmation tracking, and cleanup.
- Lighter: HTTPS/WebSocket transaction submission, confirmation tracking, and cleanup.
- Aster: HTTPS order/batch submission, private WebSocket confirmation, and cleanup.
- Extended: HTTPS order submission, parallel single-order batch benchmark, private WebSocket confirmation, and cleanup.
- Pacifica: WebSocket order submission, native WebSocket batch benchmark, private WebSocket confirmation, and per-sample WebSocket cleanup.
- Missing env vars: run
accounts checkwith the same--env-fileflags you will use for the benchmark. - Wrong wallet or key: run
accounts printand compare the public identifiers with the venue UI. - Lighter account errors: confirm
LIGHTER_ACCOUNT_INDEX,LIGHTER_API_KEY_INDEX, andLIGHTER_PRIVATE_KEYmatch the active API key. If using maker-only mode, confirmLIGHTER_MAKER_API_KEY_INDEXis marked maker-only in Lighter. - Lighter runner already active: use a separate Lighter API key for each concurrent runner, or stop the existing process using the same key.
- Config rejected for transport: the venue does not support that transport/scenario pair in this tool.
- Config rejected for risk: keep the order post-only while validating setup.