binance
無料Use binance-cli for Binance Spot, Futures (USD-S), and Convert. Requires auth.
日本語の概要は準備中です。原文の説明を表示しています。
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>.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
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>.
cargo add ccxt tokio --features tokio/full
cargo add ccxt-pro
cargo add ccxt-prediction
[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"] }
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.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(())
}
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 | 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.
| 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:
use ccxt::Binance as BinanceRest;
use ccxt_pro::Binance as BinanceWs;
Two settings matter in real programs and are easy to miss:
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.
use ccxt::Binance;
let mut exchange = Binance::new(None);
Config builderuse 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.
The core's fields are dynamic, and the wrapper derefs read-only, so exchange.verbose = true
does not compile. Use the setters — they chain:
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());
// 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)?;
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.
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:
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)
}
params argumentEvery 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:
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.
// 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.
Trading rules can be checked locally, before sending an order and without an extra request:
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();
let symbols: Vec<String> = exchange.symbols();
let currencies = exchange.currencies();
Market fields: id, symbol, base, quote, settle, base_id, quote_id, market_type,
spot, margin, swap, future, option, active, contract, linear, inverse, taker,
maker, limits, precision, raw.
let ticker = exchange.fetch_ticker("BTC/USDT", Params::none()).await?;
println!("{:?}", ticker.last); // last price
println!("{:?}", ticker.bid); // best bid
println!("{:?}", ticker.ask); // best ask
println!("{:?}", ticker.base_volume); // 24h volume
println!("{:?}", ticker.timestamp);
// Multiple tickers (if supported) -> HashMap<String, Ticker>
let tickers = exchange
.fetch_tickers(Some(vec!["BTC/USDT".into(), "ETH/USDT".into()]), Params::none())
.await?;
for (symbol, t) in &tickers {
println!("{symbol} {:?}", t.last);
}
Numeric fields are Option<f64> — a venue that does not publish a field yields None, not 0.0.
let book = exchange.fetch_order_book("BTC/USDT", Some(5), Params::none()).await?;
if let Some(bid) = book.bids.first() {
println!("top bid price={} amount={}", bid[0], bid[1]);
}
if let Some(ask) = book.asks.first() {
println!("top ask price={} amount={}", ask[0], ask[1]);
}
bids / asks are Vec<[f64; 2]> — [price, amount], best first. Pass None for full depth.
use ccxt::types::OHLCV; // = [f64; 6]
let candles: Vec<OHLCV> = exchange
.fetch_ohlcv("BTC/USDT", Some("1h"), None, Some(100), Params::none())
.await?;
for c in &candles {
println!("ts={} o={} h={} l={} c={} v={}", c[0], c[1], c[2], c[3], c[4], c[5]);
}
// Recent public trades
let trades = exchange.fetch_trades("BTC/USDT", None, Some(50), Params::none()).await?;
// Your trades (requires authentication)
let my_trades = exchange.fetch_my_trades(Some("BTC/USDT"), None, Some(50), Params::none()).await?;
let balance = exchange.fetch_balance(()).await?;
println!("{:?}", balance.free.get("USDT")); // available
println!("{:?}", balance.used.get("USDT")); // held in orders
println!("{:?}", balance.total.get("USDT")); // free + used
free / used / total are HashMap<String, f64> keyed by currency code; balance.info holds
the raw venue payload.
// Generic
let order = exchange
.create_order("BTC/USDT", "limit", "buy", 0.001, Some(50_000.0), Params::none())
.await?;
println!("{:?} {:?} {:?}", order.id, order.status, order.filled);
// Market orders take `None` for price
let order = exchange
.create_order("BTC/USDT", "market", "sell", 0.001, None, Params::none())
.await?;
// Convenience constructors
let o = exchange.create_limit_buy_order("BTC/USDT", 0.001, 50_000.0, Params::none()).await?;
let o = exchange.create_limit_sell_order("BTC/USDT", 0.001, 60_000.0, Params::none()).await?;
let o = exchange.create_market_buy_order("BTC/USDT", 0.001, Params::none()).await?;
let o = exchange.create_market_sell_order("BTC/USDT", 0.001, Params::none()).await?;
let o = exchange.create_market_buy_order_with_cost("BTC/USDT", 100.0, Params::none()).await?;
// Trigger / conditional
// symbol side amount price trigger
let o = exchange.create_stop_limit_order("BTC/USDT", "sell", 0.001, 47_900.0, 48_000.0, Params::none()).await?;
// symbol side amount trigger
let o = exchange.create_stop_market_order("BTC/USDT", "sell", 0.001, 48_000.0, Params::none()).await?;
// symbol type side amount price trigger
let o = exchange.create_trigger_order("BTC/USDT", "limit", "sell", 0.001, Some(47_900.0), Some(48_000.0), Params::none()).await?;
let open = exchange.fetch_open_orders(Some("BTC/USDT"), None, None, Params::none()).await?;
let closed = exchange.fetch_closed_orders(Some("BTC/USDT"), None, None, Params::none()).await?;
let all = exchange.fetch_orders(Some("BTC/USDT"), None, None, Params::none()).await?;
let one = exchange.fetch_order("12345", Some("BTC/USDT"), Params::none()).await?;
let edited = exchange
.edit_order("12345", "BTC/USDT", "limit", "buy", Some(0.002), Some(49_000.0), Params::none())
.await?;
let canceled = exchange.cancel_order("12345", Some("BTC/USDT"), Params::none()).await?;
let batch = exchange.cancel_orders(vec!["1".into(), "2".into()], Some("BTC/USDT"), Params::none()).await?;
let everything = exchange.cancel_all_orders(Some("BTC/USDT"), Params::none()).await?;
Order fields: id, client_order_id, symbol, timestamp, datetime, status
("open" | "closed" | "canceled" | "expired"), order_type, side, price, amount, filled,
remaining, cost, fee, raw. Note order_type — type is a Rust keyword.
let positions = exchange.fetch_positions(None, Params::none()).await?;
for p in &positions {
println!("{} {:?} contracts={:?} entry={:?} upnl={:?}",
p.symbol, p.side, p.contracts, p.entry_price, p.unrealized_pnl);
}
let one = exchange.fetch_position("BTC/USDT:USDT", Params::none()).await?;
let closed = exchange.close_position("BTC/USDT:USDT", Some("long"), Params::none()).await?;
All examples use ccxt_pro. Load markets once before watching.
use ccxt::Params;
use ccxt_pro::Binance;
let mut exchange = Binance::new(None);
exchange.try_load_markets(false).await?;
loop {
match exchange.watch_ticker("BTC/USDT", Params::none()).await {
Ok(t) => println!("{:?} {:?}", t.last, t.timestamp),
Err(e) => { eprintln!("[{}] {}", e.kind, e.message); break; }
}
}
loop {
let book = exchange.watch_order_book("BTC/USDT", Some(20), Params::none()).await?;
println!("{:?} {:?}", book.bids.first(), book.asks.first());
}
limit is best-effort — some venues only publish a full book, so expect more levels than requested.
loop {
let trades = exchange.watch_trades("BTC/USDT", None, Some(50), Params::none()).await?;
for t in &trades {
println!("{:?} {:?} {:?} {:?}", t.datetime, t.side, t.price, t.amount);
}
}
One update carries the batch of trades the venue published, not a single trade.
loop {
let candles = exchange.watch_ohlcv("BTC/USDT", Some("1m"), None, None, Params::none()).await?;
if let Some(c) = candles.last() {
println!("close={} volume={}", c[4], c[5]);
}
}
let symbols = vec!["BTC/USDT".to_string(), "ETH/USDT".to_string()];
let trades = exchange.watch_trades_for_symbols(symbols.clone(), None, None, Params::none()).await?;
let book = exchange.watch_order_book_for_symbols(symbols, Some(10), Params::none()).await?;
let tickers = exchange.watch_tickers(Some(vec!["BTC/USDT".into()]), Params::none()).await?;
These multiplex over a single WebSocket connection instead of opening one per symbol.
use ccxt::{Config, Params};
use ccxt_pro::Binance;
let mut exchange = Binance::with_config(Config::new().api_key("KEY").secret("SECRET"));
exchange.try_load_markets(false).await?;
loop {
let orders = exchange.watch_orders(Some("BTC/USDT"), None, None, Params::none()).await?;
for o in &orders {
println!("{:?} {:?} {:?}", o.id, o.status, o.filled);
}
}
let my_trades = exchange.watch_my_trades(Some("BTC/USDT"), None, None, Params::none()).await?;
let balance = exchange.watch_balance(Params::none()).await?;
let positions = exchange.watch_positions(None, None, None, Params::none()).await?;
watch_orders and watch_my_trades share one user-data stream and one authentication, so
subscribing to both costs a single connection.
A watch_* call needs &mut self, so two streams from the same instance cannot be awaited
concurrently. Either interleave them in one loop, or give each stream its own instance:
let mut a = ccxt_pro::Binance::new(None);
let mut b = ccxt_pro::Binance::new(None);
a.try_load_markets(false).await?;
b.try_load_markets(false).await?;
let (btc, eth) = tokio::join!(
a.watch_ticker("BTC/USDT", Params::none()),
b.watch_ticker("ETH/USDT", Params::none()),
);
For many symbols on one venue, prefer the *_for_symbols variants above — one connection, one task.
There is no typed close() and no typed un_watch_* yet. WebSocket clients live in a global
registry keyed by URL, so dropping the exchange value does not by itself disconnect. To force a
disconnect, drop the client for that URL:
ccxt::pro::ws_client::drop_client("wss://stream.binance.com:9443/ws");
For a process that streams until exit, doing nothing is fine.
Rust method names are the snake_case form of the unified CCXT names — fetchOHLCV is
fetch_ohlcv, createOrder is create_order, watchOrderBook is watch_order_book.
124 unified REST methods are available as typed wrappers (ccxt), and 25 watch_* methods
on top of those in ccxt-pro. Anything outside that list is still reachable untyped — see
Untyped escape hatch.
fetch_markets(params) — all marketsfetch_currencies(params) — all currenciesfetch_ticker(symbol, params) / fetch_tickers(symbols, params)fetch_spot_tickers(symbols, params) / fetch_contract_tickers(symbols, params)fetch_bids_asks(symbols, params) — best bid/ask for many symbolsfetch_mark_price(symbol, params) / fetch_mark_prices(symbols, params)fetch_order_book(symbol, limit, params) / fetch_order_books(symbols, limit, params)fetch_l3_order_book(symbol, limit, params)fetch_trades(symbol, since, limit, params)fetch_ohlcv(symbol, timeframe, since, limit, params)fetch_spot_ohlcv / fetch_contract_ohlcv / fetch_index_ohlcv / fetch_mark_ohlcv / fetch_premium_index_ohlcvfetch_time(params) — server timefetch_status(params) — exchange statusfetch_balance(params) 🔒fetch_free_balance / fetch_used_balance / fetch_total_balance / fetch_partial_balance 🔒fetch_ledger(code, since, limit, params) / fetch_ledger_entry(id, code, params) 🔒fetch_transactions / fetch_deposits / fetch_withdrawals / fetch_deposits_withdrawals 🔒fetch_borrow_interest(code, symbol, since, limit, params) 🔒fetch_cross_borrow_rate(code, params) / fetch_isolated_borrow_rate(symbol, params)Creating:
create_order(symbol, type, side, amount, price, params) 🔒create_limit_order / create_market_order 🔒create_limit_buy_order / create_limit_sell_order 🔒create_market_buy_order / create_market_sell_order 🔒create_market_order_with_cost / create_market_buy_order_with_cost / create_market_sell_order_with_cost 🔒create_stop_order / create_stop_limit_order / create_stop_market_order 🔒create_stop_loss_order / create_take_profit_order / create_trigger_order 🔒create_trailing_amount_order / create_trailing_percent_order 🔒create_post_only_order / create_reduce_only_order / create_twap_order 🔒create_order_with_take_profit_and_stop_loss(...) 🔒create_orders(orders, params) — batch 🔒Managing:
fetch_order(id, symbol, params) / fetch_order_with_client_order_id 🔒fetch_orders / fetch_open_orders / fetch_closed_orders / fetch_canceled_orders /
fetch_canceled_and_closed_orders 🔒fetch_order_status(id, symbol, params) / fetch_order_trades(id, symbol, since, limit, params) 🔒fetch_my_trades(symbol, since, limit, params) 🔒edit_order(id, symbol, type, side, amount, price, params) / edit_orders /
edit_order_with_client_order_id / edit_limit_order / edit_limit_buy_order / edit_limit_sell_order 🔒cancel_order(id, symbol, params) / cancel_orders(ids, symbol, params) /
cancel_all_orders(symbol, params) / cancel_orders_for_symbols(orders, params) /
cancel_order_with_client_order_id / cancel_orders_with_client_order_ids 🔒fetch_position(symbol, params) / fetch_positions(symbols, params) /
fetch_positions_for_symbol / fetch_positions_risk 🔒fetch_position_history / fetch_positions_history 🔒close_position(symbol, side, params) / close_all_positions(params) 🔒fetch_leverage(symbol, params) / fetch_leverages(symbols, params) /
fetch_market_leverage_tiers(symbol, params)fetch_margin_mode(symbol, params) / fetch_margin_modes(symbols, params)fetch_funding_rate(symbol, params) / fetch_funding_rates(symbols, params) /
fetch_funding_interval / fetch_funding_intervalsfetch_open_interest(symbol, params) / fetch_open_interests / fetch_open_interest_historyfetch_liquidations(symbol, since, limit, params) / fetch_my_liquidations 🔒fetch_greeks(symbol, params) / fetch_all_greeks(symbols, params) — optionsfetch_trading_fee(symbol, params) / fetch_trading_fees(params) 🔒fetch_deposit_address(code, params) / fetch_deposit_addresses(codes, params) /
fetch_deposit_addresses_by_network(code, params) / create_deposit_address(code, params) 🔒withdraw(code, amount, address, tag, params) 🔒transfer(code, amount, from_account, to_account, params) / fetch_transfer / fetch_transfers 🔒fetch_convert_currencies(params)ccxt-pro)Public:
watch_ticker(symbol, params) / watch_tickers(symbols, params)watch_bids_asks(symbols, params)watch_mark_price(symbol, params) / watch_mark_prices(symbols, params)watch_order_book(symbol, limit, params) / watch_order_book_for_symbols(symbols, limit, params)watch_trades(symbol, since, limit, params) / watch_trades_for_symbols(symbols, since, limit, params)watch_ohlcv(symbol, timeframe, since, limit, params)watch_funding_rate(symbol, params) / watch_funding_rates(symbols, params) / watch_funding_rates_for_symbolswatch_liquidations(symbol, since, limit, params) / watch_liquidations_for_symbolsPrivate 🔒:
watch_balance(params)watch_orders(symbol, since, limit, params) / watch_orders_for_symbolswatch_my_trades(symbol, since, limit, params) / watch_my_trades_for_symbolswatch_positions(symbols, since, limit, params) / watch_position / watch_position_for_symbolswatch_my_liquidations(symbol, since, limit, params) / watch_my_liquidations_for_symbols🔒 = requires API credentials.
Optional scalars are Option<T> and nullable string arguments are Option<&str>:
exchange.fetch_ohlcv("BTC/USDT", Some("1h"), None, Some(100), Params::none()).await?;
// timeframe since limit
exchange.fetch_open_orders(None, None, None, Params::none()).await?; // all symbols
Symbol lists are Option<Vec<String>> — None means "all":
exchange.fetch_tickers(None, Params::none()).await?;
exchange.fetch_positions(Some(vec!["BTC/USDT:USDT".into()]), Params::none()).await?;
Not every exchange implements every method. has is reachable through the wrapper's Deref:
if ccxt::safe_bool(&exchange.has, "fetchOHLCV", Some(false)).unwrap_or(false) {
let candles = exchange.fetch_ohlcv("BTC/USDT", Some("1h"), None, Some(100), Params::none()).await?;
}
The keys are the camelCase unified names (fetchOHLCV, createOrder), matching every other
CCXT binding. A ccxt_pro instance carries the watch* and *Ws keys (watchOrderBook,
createOrderWs) on its own has; a REST instance does not. Calling a method the venue does not
implement returns Err(NotSupported), so checking first is an optimisation, not a requirement.
use ccxt::{Binance, Config};
let mut exchange = Binance::with_config(
Config::new()
.api_key(&std::env::var("BINANCE_APIKEY").unwrap())
.secret(&std::env::var("BINANCE_SECRET").unwrap()),
);
Other credentials, when a venue needs them:
Config::new()
.api_key("...")
.secret("...")
.password("passphrase") // okx, kucoin, …
.uid("...") // kraken, …
.wallet_address("0x...") // hyperliquid, …
.private_key("0x...") // hyperliquid, dydx, …
.token("...");
Never hard-code credentials — read them from the environment or a secrets store.
match exchange.fetch_balance(()).await {
Ok(b) => println!("authenticated, {} currencies", b.total.len()),
Err(e) if e.is("AuthenticationError") => println!("invalid credentials: {}", e.message),
Err(e) => println!("[{}] {}", e.kind, e.message),
}
Every typed method returns Result<T, ExchangeError>. ExchangeError carries a kind (the leaf
class name) and a message, plus is() / is_a(), which walk the unified CCXT class hierarchy —
so one handler covers a whole family.
BaseError
├─ ExchangeError (non-recoverable — the request or account state is wrong)
│ ├─ AuthenticationError
│ │ ├─ PermissionDenied → AccountNotEnabled
│ │ └─ AccountSuspended
│ ├─ ArgumentsRequired
│ ├─ BadRequest → BadSymbol
│ ├─ OperationRejected (NoChange, MarginModeAlreadySet, MarketClosed, …)
│ ├─ InsufficientFunds
│ ├─ InvalidAddress → AddressPending
│ ├─ InvalidOrder
│ │ ├─ OrderNotFound
│ │ ├─ DuplicateOrderId
│ │ ├─ OrderNotFillable / OrderImmediatelyFillable
│ │ └─ ContractUnavailable
│ └─ NotSupported
└─ OperationFailed (transient — retry)
├─ NetworkError
│ ├─ RequestTimeout
│ ├─ RateLimitExceeded
│ ├─ DDoSProtection
│ ├─ ExchangeNotAvailable → OnMaintenance
│ └─ InvalidNonce → ChecksumError
├─ BadResponse → NullResponse
└─ CancelPending
match exchange.create_order("BTC/USDT", "limit", "buy", 0.001, Some(50_000.0), Params::none()).await {
Ok(order) => println!("{:?}", order.id),
Err(e) if e.is("InsufficientFunds") => println!("not enough balance"),
Err(e) if e.is("OrderNotFound") => println!("already filled or cancelled"),
Err(e) if e.is("InvalidOrder") => println!("bad parameters: {}", e.message),
Err(e) if e.is("AuthenticationError") => println!("check credentials"),
Err(e) if e.is("RateLimitExceeded") => println!("back off"),
Err(e) if e.is("NetworkError") => println!("transient: {}", e.kind),
Err(e) => println!("[{}] {}", e.kind, e.message),
}
Order the arms most-specific first. OrderNotFound.is("InvalidOrder") is true, so an
InvalidOrder arm placed above it swallows the more specific case.
Match on the hierarchy, never on message text — messages are venue-specific and change.
Retry only the OperationFailed subtree. Everything under ExchangeError is a bug in the request
or the account state and will fail again.
use std::time::Duration;
async fn fetch_with_retry(ex: &mut ccxt::Binance, symbol: &str) -> Result<ccxt::types::Ticker, ccxt::ExchangeError> {
let mut delay = Duration::from_millis(500);
for attempt in 0..5 {
match ex.fetch_ticker(symbol, Params::none()).await {
Ok(t) => return Ok(t),
Err(e) if e.is("NetworkError") && attempt < 4 => {
eprintln!("retry {} after [{}]", attempt + 1, e.kind);
tokio::time::sleep(delay).await;
delay *= 2;
}
Err(e) => return Err(e),
}
}
unreachable!()
}
load_marketsload_markets returns a Value and panics on failure. Use try_load_markets, which returns
Result<Vec<Market>, ExchangeError> — loading really can fail (a venue outage, or a venue whose
currency load is authenticated rejecting bad credentials).
if let Err(e) = exchange.try_load_markets(false).await {
eprintln!("cannot load markets: [{}] {}", e.kind, e.message);
return;
}
The built-in leaky-bucket limiter is on by default — requests are spaced by rateLimit
milliseconds, weighted per endpoint.
let mut exchange = Binance::with_config(Config::new().enable_rate_limit(true));
// or afterwards
exchange.set_enable_rate_limit(true);
exchange.set_rate_limit_ms(50);
Turn it off only if you throttle externally. Note that each exchange instance has its own limiter — several instances hitting the same venue with the same API key can still trip a ban, so share one instance per venue per key.
// at construction
let cfg = Config::new().set_str("httpsProxy", "http://127.0.0.1:8080");
let mut exchange = ccxt::Binance::with_config(cfg);
// or afterwards — set at most ONE of these; conflicting settings are rejected
exchange.set_http_proxy("http://user:pass@127.0.0.1:8080");
exchange.set_https_proxy("http://127.0.0.1:8080");
exchange.set_socks_proxy("socks5://127.0.0.1:1080");
WebSocket traffic uses a separate setting — the REST proxies do not apply to watch_*:
let mut ws = ccxt_pro::Binance::new(None);
ws.set_ws_proxy("http://127.0.0.1:8080"); // dialled with an HTTP CONNECT tunnel
// or: Config::new().set_str("wsProxy", "http://127.0.0.1:8080")
The typed layer covers the unified API. Three ways down to the dynamic layer when you need more:
1. raw on any returned struct — the full venue payload:
let ticker = exchange.fetch_ticker("BTC/USDT", Params::none()).await?;
let info = ccxt::get_value(&ticker.raw, &ccxt::Value::Str("info".to_string()));
let count = ccxt::safe_string(&info, "count", None);
Helpers re-exported at the crate root: get_value, safe_string, safe_number, safe_integer,
safe_bool.
2. Deref to the core — read-only access to dynamic fields (has, id, markets, urls,
options, rateLimit, timeout, …):
let supports_ws_orders = ccxt::safe_bool(&exchange.has, "createOrderWs", Some(false));
3. call_raw — dispatch any method on the core by name, including ones with no typed wrapper
(set_leverage, set_margin_mode, fetch_funding_rate_history, exchange-specific implicit
endpoints). Names are snake_case; the result is a Value:
use ccxt::{TypedExchange, Value};
let mut ex: Box<dyn TypedExchange> = Box::new(ccxt::Binance::new(None));
let result: Value = ex
.call_raw("set_leverage", vec![Value::Int(5), Value::Str("BTC/USDT:USDT".to_string())])
.await?;
call_raw is on TypedExchange, which every typed wrapper implements, so it also works on a
concrete Binance with the trait in scope.
CCXT supports prediction-market venues (Polymarket, Kalshi, Limitless, Myriad, Opinion, and the
prediction flavours of Hyperliquid and Binance) in the ccxt-prediction crate. They use the same
unified API, but prices are quoted 0–1 (USDC per outcome share) and the tradeable unit is an
outcome (a market's YES/NO token), not a regular market symbol.
use ccxt::Params;
use ccxt_prediction::{Kalshi, Polymarket, TypedExchange, TypedExchangeExt};
let mut ex = Polymarket::new(None);
ex.load_markets(false).await; // outcomes load automatically
// An outcome handle looks like 'TRUMP_OUT_PRESIDENT_2027:YES'
let handle = "TRUMP_OUT_PRESIDENT_2027:YES";
let ticker = ex.fetch_ticker(handle, Params::none()).await?;
let book = ex.fetch_order_book(handle, Some(10), Params::none()).await?;
// limit buy 5 YES shares @ 0.40 USDC (price is 0..1 per share)
let order = ex.create_order(handle, "limit", "buy", 5.0, Some(0.40), Params::none()).await?;
if let Some(id) = order.id {
ex.cancel_order(&id, Some(handle), Params::none()).await?;
}
Price/trade methods (fetch_ticker, fetch_order_book, fetch_ohlcv, fetch_trades,
create_order, cancel_order, …) take an outcome handle where a REST venue takes a symbol.
Event discovery (fetch_events, fetch_event, fetch_outcomes, fetch_outcome) has no typed
wrapper yet — reach it via call_raw:
let events = ex.call_raw("fetch_events", vec![ccxt::Value::Null]).await?;
Venues are also driven generically through ccxt_prediction's own TypedExchange /
TypedExchangeExt (distinct from the ones in ccxt).
Two things, either usable without the other. A client for the CCXT order-router service, which
holds live books across many venues and answers "what is the cheapest way to turn asset A into
asset B right now?", including bridges (SOL -> USDT -> BTC when no SOL/BTC market exists). And
an execution engine for plans you build yourself, which needs no router service and no API key.
It is not an exchange: it does not implement ExchangeBase, has no unified methods, and is
constructed directly.
use ccxt_base::order_router::{OrderRouter, RouterVenue};
use ccxt_base::Value;
let router = OrderRouter::new(&Value::Map(config))?;
let route = router.fetch_route("USDT", "BTC", &Value::Map(params)).await?; // exactly one of amountIn / amountOut
// execute takes the route directly: it builds the plan, loads each venue's markets and
// runs the safety check itself, refusing to place anything on a blocking violation
let report = router.execute(&route, &venues, &options).await?;
// want to see or change the plan first? the steps in between are public and PURE (no I/O):
// build_execution_plan(&route, ...) then check_execution_plan_safety(&plan, &markets, ...)
Rust differs from the other five ports in two places, both forced by the language:
Result<_, ExchangeError> where the others throw. The error's kind
carries the same class name, so err.is("NetworkError") asks the question the other ports ask of
an exception class.execute takes BTreeMap<String, Box<dyn RouterVenue>> rather than your exchange objects
directly. ExchangeBase's methods return impl Future, which is not object-safe, so a map of
exchanges cannot exist. RouterVenue is that map's element type, narrowed to the operations the
money path performs; implement it for whatever exchange type you hold.execute defaults to dry_run, and anything other than an explicit live flag forces
dry_run — a call that looks live but forgot the flag places nothing.
execute takes a plan, not a route, and never checks where the plan came from, so your own
strategy can supply its own trades and still get the notional cap, halt-and-reconcile between hops,
resting-order cleanup and the unwind plan. A step is one order on one venue; required fields are
exchangeId, symbol, side, amount, base, quote.
A live execute requires an identity and refuses without one — supply it as the plan's requestId
or as options.idempotencyKey. It keys an in-process ledger so a second execute of the same plan
is refused before any venue is contacted. The ledger is capped and evicts oldest-first, so the
guarantee is "recent duplicates are refused", not "duplicates are impossible", and it does not
survive a restart.
https://docs.ccxt.com/router/api. Every endpoint is public: there is no API key, no signup
and no login. The service rate-limits by client IP address instead.
The client still accepts an apiKey and still sends it as x-api-key when you pass one, so a
deployment that fronts the service with its own authentication keeps working. With no key the
header is omitted entirely rather than sent empty.
The full contract is published as OpenAPI 3.1 at https://docs.ccxt.com/router/openapi.yaml.
curl -O https://docs.ccxt.com/router/openapi.yaml and point codegen at it, import it into
Postman/Insomnia, or diff it between deploys. It is the authority on every field this client
reads; where the two disagree, the spec is right. Rendered prose version:
/router/docs and
/router/docs/api.
Free to use for now, up to the published rate limit — not a permanent commitment, so expect a
paid tier eventually. Your existing key is how that would be billed; nothing in the client changes.
Read the limit off the response headers (x-ratelimit-limit, x-ratelimit-remaining,
x-ratelimit-reset) rather than hardcoding a number. A 429 raises RateLimitExceeded with the
retry interval folded into the message.
A router that has restarted is alive long before it can price anything. Asked to route in that
window it refuses with 503 cache_cold, and the client raises ExchangeNotAvailable — a
retry, distinct from the ExchangeError that means something is actually wrong.
Holdings are POSTed, never put in a URL. fetch_route normally sends a GET, but when you pass
balances the client switches to POST /route and puts every parameter in the body: the service
scrubs holdings from its own logs, but a reverse proxy, an ALB, a CDN, browser history and a
Referer all see the full request line, and no in-process redaction reaches them.
fetch_route_with_balances does this for you.
Two flags the client verifies for you, because one silently lost in transit looks identical to
one never sent. balances: the service ignores them entirely if it predates the feature and
answers byte-identically, so fetch_route throws unless the router echoes balancesApplied (or
balanceEntryCount, which is how an empty wallet is confirmed) — pass requireBalancesApplied: false to opt out. requireFullFill: the one flag that fails open, so the client stamps what you
asked for and the safety check makes partial_fill blocking when you asked for a full fill and
did not get one.
An empty value is not an omitted one. Omit bridges and you get the default bridge set; send
bridges= and you have asked for no bridging at all. Same for exchanges= (no venues) and
balances= (you hold nothing). The client forwards an empty value rather than dropping it.
requestId is sent as the x-request-id header, so your log and the router's decision log can
be joined; the service mints one when absent.
| Method | Endpoint | Key? | Answers |
|---|---|---|---|
fetch_health() | /health | no | is the process alive — 200 from the first millisecond of boot |
fetch_readiness() | /ready | no | can it route yet: book counts, and how many are fresh |
fetch_version() | /version | yes | which commit is deployed |
fetch_symbols() | /symbols | yes | the symbols it holds a book for |
fetch_exchanges_status() | /exchanges/status | yes | per-venue connection health |
fetch_cached_order_book(exchange_id, symbol) | /orderbook/{exchange}/{symbol} | yes | the exact book a route was ranked on |
Gate deploys on readiness, not health — /health is 200 before a single websocket has connected.
fetch_readiness() does not raise when the answer is no: the service replies 503 carrying the same
body it returns on 200, and you need those counts to know why.
let readiness = router.fetch_readiness().await?;
if router.string_at(&readiness, "status", "") != "ready" {
println!(
"{} of {} books are fresh",
router.number_at(&readiness, "freshCount", 0.0),
router.number_at(&readiness, "bookCount", 0.0)
);
}
/metrics (Prometheus) has no client method — it answers text/plain and this class parses every
response as JSON.
Watching a route. watch_route holds a WebSocket open and calls your hook with each RouteResult as
the books move; return 'stop' to close cleanly and get the last route back. Every frame is
stamped exactly as fetch_route stamps its answer, so it can go straight into the plan builder. Three
endpoint rules differ from fetch_route: balances and balanceMode are refused (a socket outlives
the holdings it was opened with — refused client-side, before anything opens), includeQuotes
defaults to false, and refusals arrive as close codes rather than statuses — 1008 raises
BadRequest and 1013 raises ExchangeNotAvailable, the same classes the REST path uses. A hook
that throws stops the stream and reaches you, unlike execute's step hook.
set_on_stepThis is where Rust's API diverges most. The other five ports pass the hook in
options['onStep']; Rust installs it on the router. Value is a closed enum deriving Debug,
Clone and PartialEq and answering to_json, so it cannot carry a closure without redefining
what closure equality and serialisation mean at every Value site in the crate.
use std::sync::Arc;
router.set_on_step(Arc::new(|event: &Value| {
// return "halt" to stop the route; anything else continues
if router_str(event, "status") == "partial" { "halt".to_string() } else { String::new() }
}));
let report = router.execute(&plan, &venues, &options).await?;
router.clear_on_step();
OnStepHook is Arc<dyn Fn(&Value) -> String + Send + Sync>. The hook is called after each step
completes AND after its reconciliation, never mid-order, with an event carrying planId,
stepIndex, hopIndex, status, filledAmount, outAmount, attempt, reconciliation,
haltReason, stepsRemaining and more.
"halt" stops the route and sets haltReason to halted_by_on_step;
nothing it returns resumes a route the reconciliation already halted.catch_unwind, the failure is
recorded as on_step_hook_failed:panic, and execution continues as if the hook had no opinion —
losing the report would destroy the only account of orders already live. This does not hold
under panic = "abort", where no construct in any language would help.options.retryFailedSteps (default 0, retryDelayMs default 1000) re-places a step the venue
definitively rejected. An outcome_unknown step is never retried at any setting: it may already
be a live position, and re-placing it is the double-fill this class exists to prevent. The winning
attempt is reported as attempt. The router sets no client order id of its own — venues disagree on
length and charset, so whatever you pass in orderParams travels untouched.
// Wrong — symbol resolution fails
let mut ex = Binance::new(None);
let ticker = ex.fetch_ticker("BTC/USDT", Params::none()).await?; // BadSymbol
// Correct
let mut ex = Binance::new(None);
ex.try_load_markets(false).await?;
let ticker = ex.fetch_ticker("BTC/USDT", Params::none()).await?;
// Wrong — the wrapper derefs read-only, this does not compile
exchange.verbose = true;
exchange.apiKey = "...".into();
// Correct
exchange.set_verbose(true);
exchange.set_api_key("...");
load_markets where failure matters// Risky — panics on a venue outage or bad credentials
exchange.load_markets(false).await;
// Correct
exchange.try_load_markets(false).await?;
// Wrong — InvalidOrder swallows OrderNotFound
Err(e) if e.is("InvalidOrder") => ...,
Err(e) if e.is("OrderNotFound") => ..., // unreachable
// Correct — specific first
Err(e) if e.is("OrderNotFound") => ...,
Err(e) if e.is("InvalidOrder") => ...,
Option<f64> as a number// Wrong — `last` is Option<f64>, not f64
let value = ticker.last * amount;
// Correct — decide what a missing price means
let Some(last) = ticker.last else { return Err(...) };
let value = last * amount;
// Wrong — burns rate limit, seconds of latency
loop {
let t = exchange.fetch_ticker("BTC/USDT", Params::none()).await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
// Correct — one connection, push updates
let mut ws = ccxt_pro::Binance::new(None);
ws.try_load_markets(false).await?;
loop {
let t = ws.watch_ticker("BTC/USDT", Params::none()).await?;
}
"BTCUSDT" // wrong — no separator (that's the exchange-specific id)
"BTC-USDT" // wrong — dash separator
"btc/usdt" // wrong — lowercase
"BTC/USDT" // correct — unified spot symbol
"BTC/USDT:USDT" // correct — linear perpetual swap
"BTC/USD:BTC" // correct — inverse swap
A stack overflow inside a fetch_* call usually means the runtime's worker threads are on the
2 MB default. Build the runtime with .thread_stack_size(64 * 1024 * 1024) — see
Runtime setup.
Every unified method takes &mut self, so a single instance cannot be used from two tasks at once.
Wrap it in a tokio::sync::Mutex (which serialises the calls) or give each task its own instance.
error: no matching package named 'ccxt-pro' found
cargo add ccxt-pro — the WebSocket venues are a separate crate from ccxt.
cannot borrow as mutable on a watch_* call
Two watch_* calls from the same instance cannot be awaited concurrently. Use *_for_symbols, or a
second instance.
the trait bound ...: TypedExchangeExt is not satisfied
Bring both traits into scope: use ccxt::{TypedExchange, TypedExchangeExt}; (or the ccxt_pro /
ccxt_prediction pair for those crates).
no method named 'fetch_ticker' found for struct 'Box<dyn TypedExchange>'
Same cause — TypedExchangeExt is not imported.
Err(BadSymbol)
Markets were not loaded, or the symbol is not listed. Check exchange.symbols().
Err(NotSupported)
The venue does not implement that method, or the signer is not ported yet (see
Known limitations). Check exchange.has first.
Err(AuthenticationError)
Verify the key and secret, the key's permissions on the exchange, any IP allowlist, and that the
system clock is synced.
Err(InvalidNonce)
Sync the system clock; use one exchange instance per API key.
Err(RateLimitExceeded)
Leave enable_rate_limit on, and don't run several instances against one key.
Stack overflow Increase the worker-thread stack size — see Runtime setup.
A panic message printed but the call returned Ok/Err anyway
Expected: the core signals errors by panicking across an internal catch_unwind. Install the
no-op panic hook shown in Runtime setup; set CCXT_SHOW_PANICS=1 to see them
while debugging.
Long compile times
The generated crates are large. Drop debug info in dev builds and keep ccxt-pro out of the
dependency list unless you stream:
[profile.dev]
debug = 0
exchange.set_verbose(true); // logs every HTTP request and response to stderr
For a WebSocket venue, set_verbose(true) on the ccxt_pro instance logs the frames.
The Rust port is newer than the other bindings. Current gaps:
paradex, lighter zk-proofs, dydx protobuf transactions, apex StarkEx, curve25519).
They fail loudly with NotSupported rather than emitting an invalid signature, so those venues'
private endpoints are unusable; public endpoints work.un_watch_* and no close() — see
Closing connections.call_raw.u64::MAX fall back to f64; values up to
u64::MAX are preserved losslessly.まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Use binance-cli for Binance Spot, Futures (USD-S), and Convert. Requires auth.
日本語の概要は準備中です。原文の説明を表示しています。
Use when the user mentions connect/disconnect wallet, sign in, sign out, web3 wallet, wallet address, check balance, how much crypto do I have, send BNB/USDT/crypto, transfer tokens, swap tokens, buy/sell token, DEX trade, limit order, market order, cancel order, get a quote, transaction history, wallet settings, daily limit, slippage, MEV protection, supported chains, available networks, prediction market, predict.fun, YES/NO market, place a prediction, redeem winnings, claim payout, prediction portfolio, prediction PnL, x402 payment, HTTP 402 Payment Required, pay a known x402 API, check approvals, view token approvals, revoke approval, manage approvals, wallet approvals, authorization management, token authorization, DeFi protocols, DeFi position, DeFi portfolio, staking, liquidity pool, LP, yield farming, health factor, APY, TVL, DeFi investment, DeFi deposit, DeFi redeem, DeFi stake, DeFi unstake, add liquidity, remove liquidity, claim rewards, claim fees, or any on-chain wallet operation.
日本語の概要は準備中です。原文の説明を表示しています。
Use when users ask for World Cup or 世界杯 AI match predictions, WC assistant probabilities, World Cup news insights, master analysis, recomputing football match win rates with custom correction signals, or trading a related prediction market after reviewing the AI analysis.
日本語の概要は準備中です。原文の説明を表示しています。
Query Ondo tokenized US stock data on Binance Web3. Covers: supported stock token list, RWA metadata (company info, attestation reports), market and per-asset trading status (with corporate action codes for earnings, dividends, splits), real-time on-chain data (token price, holders, circulating supply, market cap), US stock fundamentals (P/E, dividend yield, 52-week range), and token K-Line/candlestick charts. Use this skill when users ask about: - Tokenized stock price, holders, or on-chain data for specific tickers - Whether a stock token is tradable, paused, or halted - Ondo RWA token list or which US stocks are available on-chain - Corporate actions affecting a token (dividends, stock splits, earnings halt) - Stock token K-Line or candlestick chart data - Comparing on-chain token price vs US stock price NOT for general crypto tokens (BTC, ETH, SOL, etc.) — use query-token-info for those.
日本語の概要は準備中です。原文の説明を表示しています。
CCXT command-line interface (ccxt-cli) for interacting with 100+ cryptocurrency exchanges directly from the terminal — no code required. Covers installing the CLI, calling any unified CCXT method (fetchTicker, fetchOHLCV, createOrder, fetchBalance), passing arguments and exchange-specific params, authenticating with API keys, sandbox/testnet mode, streaming live tickers and orderbooks over WebSocket, plotting OHLCV charts, and scripting with raw JSON output. Use when the user wants to query an exchange, test API credentials, place or inspect orders, or debug exchange requests from the command line or in shell scripts.
日本語の概要は準備中です。原文の説明を表示しています。
CCXT cryptocurrency exchange library for C# and .NET 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 .NET projects. Use when working with crypto exchanges in C# applications, trading systems, or financial software. Supports .NET Standard 2.0+.
日本語の概要は準備中です。原文の説明を表示しています。