hypebot-rs
Rust rewrite of a Hyperliquid quantitative trading bot — typed pipelines, serialized execution, runtime resilience.
Context
The original trading bot was a script — ergonomic for iteration, wrong for production. Once it started carrying real capital, every weakness of a script-style architecture showed up: untyped boundaries, concurrency you couldn’t reason about, state that lived in memory only, log output that wasn’t structured enough to debug a bad fill at 3am.
hypebot-rs is the rewrite — same trading logic, different foundation. Rust for strict types, async-first concurrency, and a runtime that fails loudly and recoverably instead of silently.
Approach
Concurrency model
The core abstraction is SymbolRunner: one async runner per trading symbol, each owning its own state machine. Multi-pair trading becomes “spawn N runners” instead of “interleave N state machines in one loop.” Tokio supervises them; failures in one symbol don’t take down the others.
Module split
Clean vertical modules so responsibilities can be reasoned about in isolation:
- config — typed, versioned, serde-validated at startup.
- signer — key management via
k256/alloy, isolated from anything that touches the network. - rest client —
reqwestwith retry + backoff policy separate from business logic. - ws client —
tokio-tungstenitestream handlers with explicit reconnect paths. - state — persisted to disk so crashes don’t lose position context.
- notifier — Discord integration for operational events.
Safety rails before performance
--dry-runmode that exercises the full pipeline without sending orders.cargo checkin CI so no landed commit breaks the type graph.tracingwith structured spans everywhere — the logs have to be usable post-incident, not just during development.
Performance optimization is deferred until correctness + observability are where they need to be.
Results & What I Learned
Current status: in development, internal use. cargo check passes; --dry-run works end-to-end; live trading is gated behind a manual flag I’m not flipping until the observability track is finished.
I am not reporting PnL. A bot’s PnL during development is the most misleading number you can publish — the selection effect alone makes it meaningless.
The rewrite taught me a specific thing: in Rust, the cost isn’t the syntax, it’s the decision work you can no longer avoid. Every ambiguity the Python version papered over (who owns this state, what does this return on partial failure, what’s the shape of this JSON really) becomes a compile error you have to resolve before the thing runs. That’s exactly what you want in a trading system.
Tech Stack & Links
Stack: Rust · Tokio · reqwest · tokio-tungstenite · serde · alloy · k256 · tracing
Links: GitHub