Skip to content

Latest commit

 

History

835 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cross-Exchange Perpetual Arbitrage Bot

A delta-neutral arbitrage system for decentralized perpetual futures, built from scratch in Rust.

The same perpetual contract trades on many exchanges, and their prices constantly drift apart. This bot watches several venues' order books at once, and the moment a price gap opens it captures the difference — going long on the cheap venue and short on the expensive one at the same instant. That leaves a market-neutral position, which it holds until the spread converges and then closes. Everything the strategy needs lives in this repository: the exchange WebSocket clients, the cryptographic order signing, the execution engine, the risk manager, the storage layer, a backtester, and a real-time web dashboard — with no trading framework underneath.

Real-time trading dashboard showing live venue balances and active and closed trades with fills, slippage, fees, funding, and PnL

The live dashboard — venue balances plus active and closed trades, each with its fills, slippage, fees, funding, and PnL. (click to enlarge)


Features

Trading

  • Delta-neutral by construction — each position is long one venue and short another in equal size, so it carries almost no market risk. The only exposure is the spread itself, and spreads converge.
  • Multi-venue and self-directing — connects to several exchanges at once, continuously scans every pair of them, and enters wherever the spread is widest, in either direction.
  • Parallel two-leg execution — both orders fire at the same instant to catch spreads that last only seconds, with automatic recovery when only one side fills.
  • Liquidation-aware risk control — a stop-loss anchored to each position's liquidation price (not an arbitrary percentage), per-exchange capital limits, and a daily-loss kill switch.
  • Built to survive a live venue — auto-reconnecting market data, staleness watchdogs, and detection of positions closed outside the bot (server-side stop-outs, liquidations, manual actions).

Operating it

  • Real-time dashboard — live positions, balances, spreads, per-leg latency, and full trade history. Most strategy parameters can be changed at runtime, no restart required.
  • Telegram bot — open / close / halt alerts, plus interactive status and trade queries.
  • Backtesting and optimization — replay recorded market data and search strategy parameters (grid / TPE / CMA-ES / funnel) on the exact same engine the live bot runs.

How it works

        cheap venue                        rich venue
   ┌────────────────────┐            ┌────────────────────┐
   │   BUY  →  go LONG   │ ◄───────► │   SELL  →  go SHORT │
   └────────────────────┘   equal   └────────────────────┘
              │               qty                │
              └────────── delta-neutral ─────────┘
                          (market risk ≈ 0)

   profit = entry_spread − exit_cost      (realized only on close)
  1. Detect. A spread detector reads both books tick-by-tick and computes the executable spread as a VWAP through the top levels — not just best bid/ask — so a signal reflects the price actually achievable for the intended size, after walking the book.
  2. Enter. When the net spread (after fees) clears the threshold, both legs fire in parallel — two concurrent tasks, one per venue — to capture the gap before it closes. Leg-risk recovery handles the case where only one side fills.
  3. Hold. The position is delta-neutral: a price move gains on one leg and loses the same on the other. The only exposure is the spread, which the strategy waits out.
  4. Exit. When captured PnL crosses the take-profit threshold, both legs unwind in parallel. A liquidation-anchored stop-loss guards each position and re-prices itself as the liquidation point drifts.

Strategy, execution flow, and risk logic in full: docs/DESIGN.md.


Architecture

        ┌──────────────────────────────────────────────┐
        │   WebSocket connectors  (one per exchange)    │
        │   order books  +  account / fill streams      │
        └───────────────────────┬──────────────────────┘
                                │  best levels (mpsc)
                                ▼
        ┌──────────────────────────────────────────────┐
        │                   ArbEngine                   │
        │  SpreadDetector → OpenSignal  → execute_open  │  parallel legs
        │  PositionTracker → CloseSignal → execute_close │  parallel legs
        │  stop-loss drift monitor · funding tracking   │
        └───────┬───────────────┬───────────────┬───────┘
                ▼               ▼               ▼
          RiskManager      SQLite store     Web dashboard
       capital · limits   trades · orders   SSE · Telegram
        kill switch       Parquet ticks     runtime config

Three Cargo crates — roughly 67k lines of Rust, plus another 30k of tests, many of them integration tests that exercise the live venue APIs:

Crate Responsibility
bot/ The arb_bot runtime — exchange connectors, strategy, parallel execution, risk manager, SQLite, order signing, web dashboard, Telegram
common/ Shared core — domain types, constants, PnL & stop-loss formulas, signal detection, the backtest simulator
optimizer/ Offline parameter search over recorded ticks

Module-level map and data flow: docs/ARCHITECTURE.md.


Under the hood

The parts that were the most interesting to build.

Order signing

Decentralized exchanges don't authenticate with an API key and a password — every order is a signed cryptographic message, and each venue uses a different scheme. Most have no Rust SDK, so the signing is implemented here directly:

Venue Architecture Order-signing scheme
Lighter Custom ZK-rollup L2 Schnorr over the ECgFp5 curve + Poseidon2 hash, on the Goldilocks field
Extended Off-chain CLOB + StarkNet settlement STARK-curve ECDSA + Pedersen hash (RFC-6979)
Zero1 (01.xyz) StarkNet perps, off-chain matching Ed25519 wallet key + ephemeral session keys, Protobuf wire format
Hyperliquid On-chain CLOB on a purpose-built L1 EIP-712 typed data (secp256k1) over MessagePack
Bybit Centralized exchange HMAC-SHA256 request authentication

Lighter needs the most from-scratch work: Goldilocks-field arithmetic, the GFp5 extension field, the ECgFp5 elliptic curve, the Poseidon2 sponge, and Schnorr signatures — 954 lines with no external crypto dependencies (bot/src/signing/ecgfp5.rs), since nothing off the shelf implements it.

Performance

Spreads on these venues last seconds, so the trading hot path is kept lean:

  • Parallel dual-leg execution — both orders leave the process at once, not one after the other, so the window between legs is bounded by the network rather than by the code.
  • fastwebsockets with SIMD frame unmasking and split read/write tasks; the read path is kept off select! because the frame reader isn't cancel-safe.
  • sonic-rs JSON (2.5–6.8× faster than serde_json), jemalloc, and rust_decimal instead of f64 so prices stay exact.
  • A single-threaded hot core — the position tracker is a plain Vec, not an Arc<Mutex<…>>, so the critical path stays lock-free; heavy work like serialization and DB writes is offloaded to background tasks.
  • Per-leg latency instrumentation splits every trade into four measured segments — wire latency, book age at decision, decision→send, and send→fill — shown live in the dashboard, which separates a slow venue from a slow decision.

Generic over N exchanges

Adding a venue doesn't touch the strategy. Every exchange plugs in behind two traits — ExchangeFactory (bootstrap: symbols, fees, WebSocket tasks) and ExchangeClient (place / close / stop-loss / positions) — and the detector scans all C(N, 2) pairs of enabled venues, taking the direction with the best spread. Exactly one function in the codebase names concrete exchanges (factory.rs); everything downstream is venue-agnostic, and the same generic core drives the live bot, the backtester, and the optimizer.

Backtesting with live parity

The bot records every tick it sees to Parquet, byte-identical to what the detector observed, so a replay reproduces the live view exactly. The simulator (common/src/sim.rs) shares its spread, VWAP, PnL, and stop-loss math with the production path — and byte-exact snapshot tests fail the build if a refactor makes the two diverge. On top of it runs a parameter optimizer with four search modes, parallelized with rayon.

Full-stack operator UI

The dashboard is a dependency-free vanilla-JS single-page app served by an Axum backend over server-sent events — 58 endpoints, Argon2-hashed logins, and role-based permissions. It renders live state and doubles as the control surface: most parameters are edited in place and persisted to a local override file, applied without a restart.


Tech stack

Chosen for speed and correctness on the trading path:

  • Async runtime — Tokio, with fastwebsockets (SIMD unmasking, raw frame access) for every market and account stream.
  • TLS / HTTPrustls on the aws-lc-rs backend, hyper for REST order placement.
  • Serializationsonic-rs for hot-path JSON, prost (Protobuf) and rmp-serde (MessagePack) for the venues that require them.
  • Numericsrust_decimal for all prices and sizes; no f64 where money is involved.
  • Storagerusqlite (bundled SQLite) as the single source of truth; parquet + arrow for tick capture.
  • Webaxum with server-sent events; a vanilla-JS single-page app.
  • Cryptostarknet-crypto, ed25519-dalek, alloy (EIP-712), and a from-scratch ECgFp5 / Poseidon2 stack.

Running it

Requires a Rust toolchain and SQLite, plus API credentials for at least one enabled venue in a local .env.

# Build and launch in clean mode (flattens any existing exchange positions first)
bash tools/bot_start.sh --clean --build

The dashboard comes up at http://127.0.0.1:3030. The initial admin password is generated on first run and written back to .env. Stop with bash tools/bot_stop.sh.

Startup modes, config files, and server setup: docs/LOCAL_SETUP.md.


Repository layout

bot/         runtime crate — connectors, strategy, execution, risk, storage, signing, web
common/      shared core — types, formulas, signal detection, backtest simulator
optimizer/   offline parameter search
config/      default.toml (defaults) · local.toml (runtime overrides) · connections.toml
db/          SQLite database
data/        Parquet tick history + funding data
tools/       start/stop scripts and tick utilities
docs/        design, architecture, and per-exchange notes

Documentation

Further reading: DESIGN.md (strategy, trade lifecycle, leg risk, liquidation math), ARCHITECTURE.md (modules, data flow, storage model), and PATTERNS.md (the project's async, atomic, and testing conventions).

About

Delta-neutral cross-exchange arbitrage for perpetual futures, in Rust — 5 venue connectors with custom order signing, async execution engine, risk manager, backtester and live web dashboard, built without a trading framework

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages