- name
- ccxt-rust
- description
- CCXT cryptocurrency exchange library for Rust developers. Covers both REST API (standard) and WebSocket API (real-time). Helps install CCXT, connect to exchanges, fetch market data, place orders, stream live tickers/orderbooks, handle authentication, and manage errors in Rust projects. Use when working with crypto exchanges in Rust applications, trading bots, or low-latency services. Async (tokio), typed wrappers returning Result<T, ExchangeError>.
# CCXT for Rust
A comprehensive guide to using CCXT in Rust projects for cryptocurrency exchange integration.
Every exchange has a **typed wrapper** (`ccxt::Binance`, `ccxt::Kraken`, …) exposing the unified
CCXT API with native Rust return types — `Ticker`, `Order`, `OrderBook`, `Market` — instead of a
dynamic value. All methods are `async` and return `Result<T, ExchangeError>`.
## Installation
### REST API
```bash
cargo add ccxt tokio --features tokio/full
```
### WebSocket API (ccxt.pro)
```bash
cargo add ccxt-pro
```
### Prediction markets
```bash
cargo add ccxt-prediction
```
### Cargo.toml
```toml
[dependencies]
ccxt = "4.5.75" # REST exchanges (typed) — required
ccxt-pro = "4.5.75" # WebSocket (watch*) exchanges — only if you stream
ccxt-prediction = "4.5.75" # prediction markets — only if you trade them
tokio = { version = "1", features = ["full"] }
```
### Requirements
- Rust **stable**, edition 2021 or later.
- A **tokio** runtime — every unified method is `async`. There is no sync API.
- `ccxt` alone is enough for REST. Add `ccxt-pro` only when you need `watch_*`; it is a separate
crate so a REST-only build does not compile the whole WebSocket surface.
## Quick Start
### REST API
```rust
use ccxt::{Binance, Params};
#[tokio::main]
async fn main() -> Result<(), ccxt::ExchangeError> {
let mut exchange = Binance::new(None);
exchange.load_markets(false).await;
let ticker = exchange.fetch_ticker("BTC/USDT", Params::none()).await?;
println!("{} last={:?} bid={:?} ask={:?}", ticker.symbol, ticker.last, ticker.bid, ticker.ask);
Ok(())
}
```
### WebSocket API — real-time updates
```rust
use ccxt::Params;
use ccxt_pro::Binance;
#[tokio::main]
async fn main() -> Result<(), ccxt::ExchangeError> {
let mut exchange = Binance::new(None);
exchange.try_load_markets(false).await?;
loop {
let ticker = exchange.watch_ticker("BTC/USDT", Params::none()).await?;
println!("{:?}", ticker.last); // live updates
}
}
```
Each `watch_*` call resolves to **one** decoded update, so consuming a stream is just calling it in
a loop.
## Crate layout
| Crate | Contains | Use when |
|---|---|---|
| `ccxt` | Typed REST wrappers (`ccxt::Binance`, …), `Params`, `Config`, `types::*`, `TypedExchange`. Re-exports the whole engine at its root. | Always |
| `ccxt-pro` | Typed WebSocket wrappers (`ccxt_pro::Binance`, …) with `watch_*` | Streaming |
| `ccxt-prediction` | Typed prediction-market wrappers (`ccxt_prediction::Kalshi`, …) | Prediction markets |
| `ccxt-base` | The untyped engine (`Value`, HTTP, crypto, rate limiter, Cores, WS infra) | Rarely direct — `ccxt` re-exports it |
`ccxt` re-exports `ccxt-base` at its root, so `ccxt::Value`, `ccxt::runtime::…`,
`ccxt::exchanges::binance::BinanceCore` resolve alongside the typed `ccxt::Binance`.
Coverage today: **105** typed REST venues, **76** typed WebSocket venues, **7** prediction venues.
## REST vs WebSocket
| Feature | REST API | WebSocket API |
|---------|----------|---------------|
| **Use for** | One-time queries, placing orders | Real-time monitoring, live price feeds |
| **Crate** | `ccxt` | `ccxt-pro` |
| **Import** | `use ccxt::Binance;` | `use ccxt_pro::Binance;` |
| **Methods** | `fetch_*` (`fetch_ticker`, `fetch_order_book`) | `watch_*` (`watch_ticker`, `watch_order_book`) |
| **Speed** | Slower (HTTP request/response) | Faster (persistent connection) |
| **Rate limits** | Strict (1–2 req/sec) | More lenient (continuous stream) |
| **Best for** | Trading, account management | Price monitoring, arbitrage detection |
Both crates expose a type named `Binance`. When you use both in one file, alias one of them:
```rust
use ccxt::Binance as BinanceRest;
use ccxt_pro::Binance as BinanceWs;
```
## Runtime setup
Two settings matter in real programs and are easy to miss:
```rust
fn main() {
// 1. The transpiled core signals errors by panicking across an internal
// catch_unwind; the typed layer turns that back into `Result`. Silencing
// the default hook stops caught panics from printing to stderr.
// Set CCXT_SHOW_PANICS=1 to see them while debugging.
if std::env::var("CCXT_SHOW_PANICS").is_err() {
std::panic::set_hook(Box::new(|_| {}));
}
// 2. The generated exchange code is deeply nested — give worker threads a
// large stack. The default 2 MB can overflow on some venues.
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.thread_stack_size(64 * 1024 * 1024)
.enable_all()
.build()
.unwrap();
rt.block_on(run());
}
```
`#[tokio::main]` is fine for short examples and scripts; use the explicit builder for anything
long-running.
## Creating an Exchange Instance
### Public (no authentication)
```rust
use ccxt::Binance;
let mut exchange = Binance::new(None);
```
### Private (with credentials), via the `Config` builder
```rust
use ccxt::{Binance, Config, Params};
let mut exchange = Binance::with_config(
Config::new()
.api_key("YOUR_API_KEY")
.secret("YOUR_SECRET")
.enable_rate_limit(true) // on by default
.timeout_ms(10_000)
.option_str("defaultType", "swap")
.option("fetchMarkets", Params::new().with_strs("types", &["spot", "linear"])),
);
```
`Config` covers every credential (`api_key`, `secret`, `password`, `uid`, `wallet_address`,
`private_key`, `token`), plus `sandbox`, `verbose`, `enable_rate_limit`, `rate_limit_ms`,
`timeout_ms`, and arbitrary properties via `set_str` / `set_int` / `set_float` / `set_bool`.
`options` nests exactly as in the other bindings: `option_str`/`option_int`/`option_bool`/
`option_strs` for a flat key, `option(key, Params)` for a nested object, `options(Params)` for a
whole block. Repeated calls deep-merge rather than replace.
### Settings after construction
The core's fields are dynamic, and the wrapper derefs read-only, so `exchange.verbose = true`
does not compile. Use the setters — they chain:
```rust
let mut exchange = Binance::new(None);
exchange.set_api_key("YOUR_API_KEY");
exchange.set_secret("YOUR_SECRET");
exchange.set_verbose(true).set_timeout_ms(10_000);
exchange.set_enable_rate_limit(true);
exchange.set_options(Params::new().with_str("defaultType", "spot"));
println!("{} {} {}", exchange.id(), exchange.is_verbose(), exchange.is_sandbox_mode_enabled());
```
### Sandbox / testnet
```rust
// at construction
let mut a = Binance::with_config(Config::new().sandbox(true));
// or afterwards — returns Err(NotSupported) when the venue has no testnet
let mut b = Binance::new(None);
b.set_sandbox_mode(true)?;
```
### Choosing an exchange at runtime
`from_id` / `from_id_with_config` build any supported venue from its id and hand back a trait
object; the typed API stays available through `TypedExchangeExt`.
```rust
use ccxt::{from_id_with_config, Config, Params, TypedExchange, TypedExchangeExt};
for id in ["binance", "bybit", "okx"] {
let Some(mut exchange) = from_id_with_config(id, Config::new().enable_rate_limit(true)) else {
continue; // unknown id
};
exchange.load_markets(false).await;
let ticker = exchange.fetch_ticker("BTC/USDT", Params::none()).await?;
println!("{id} {:?}", ticker.last);
}
```
`Box<dyn TypedExchange>` works because `TypedExchange` is object-safe; the ergonomic typed methods
live on the blanket `TypedExchangeExt`, so **both traits must be in scope**. The same pair exists in
`ccxt_pro` (for `watch_*`) and `ccxt_prediction`.
Write generic code the same way:
```rust
async fn best_bid<E: TypedExchange + TypedExchangeExt>(ex: &mut E) -> Result<Option<f64>, ccxt::ExchangeError> {
Ok(ex.fetch_ticker("BTC/USDT", Params::none()).await?.bid)
}
```
## The `params` argument
Every unified method ends with `params` — the venue-specific knobs. On the typed layer that is a
`Params` builder over Rust primitives, never a dynamic value:
```rust
use ccxt::Params;
// nothing extra — these three are equivalent
exchange.fetch_ticker("BTC/USDT", Params::none()).await?;
exchange.fetch_ticker("BTC/USDT", ()).await?;
exchange.fetch_ticker("BTC/USDT", [("recvWindow", "5000")]).await?;
// venue-specific extras
exchange.create_order(
"BTC/USDT", "limit", "buy", 0.001, Some(50_000.0),
Params::new()
.with_str("clientOrderId", "my-order-1")
.with_bool("postOnly", true)
.with_str("timeInForce", "GTC")
.with_float("triggerPrice", 49_000.0)
.with_int("recvWindow", 5_000)
.with_strs("clientOrderIds", &["a", "b"]),
).await?;
```
Builders: `with_str`, `with_int`, `with_float`, `with_bool`, `with_strs`, `with_json`,
`with_params` (nested object). Entries keep insertion order, which some signing routines depend on.
Unified params (`clientOrderId`, `postOnly`, `timeInForce`, `reduceOnly`, `triggerPrice`, …) mean
the same thing on every venue — CCXT translates them into whatever the exchange wants on the wire.
## Common REST Operations
### Loading markets
```rust
// Untyped, panics on failure — fine for scripts
exchange.load_markets(false).await;
// Preferred: fallible and typed
let markets: Vec<ccxt::types::Market> = exchange.try_load_markets(false).await?;
```
Loading markets is required before any call that resolves a unified symbol.
### Market metadata
Trading rules can be checked locally, before sending an order and without an extra request:
```rust
let market = exchange.market("BTC/USDT")?; // Err(BadSymbol) when not listed
println!("{} {} active={}", market.symbol, market.market_type, market.active);
println!("min amount {:?} min cost {:?}", market.limits.amount.min, market.limits.cost.min);
println!("amount step {:?} price tick {:?}", market.precision.amount, market.precision.price);
let swaps: Vec<_> = exchange.markets().into_iter().filter(|m| m.swap && m.active).collect();
عرض على GitHub