Rust Examples
Generic Typed Order
Generic Typed Order — CCXT Rust code example.
use ccxt::types::{Market, Order, Ticker};
use ccxt::{from_id_with_config, Config, ExchangeError, Params, TypedExchange, TypedExchangeExt};
const EXCHANGES: &[&str] = &["binance", "bybit"];
const SYMBOL: &str = "BTC/USDT";
const MAX_NOTIONAL_USD: f64 = 25.0;
fn creds(id: &str) -> Option<(String, String)> {
let up = id.to_uppercase();
match (
std::env::var(format!("{up}_APIKEY")),
std::env::var(format!("{up}_SECRET")),
) {
(Ok(k), Ok(s)) if !k.is_empty() && !s.is_empty() => Some((k, s)),
_ => None,
}
}
fn config_for(id: &str) -> Config {
let mut cfg = Config::new();
if let Some((k, s)) = creds(id) {
cfg = cfg.api_key(&k).secret(&s);
if std::env::var("CCXT_LIVE").is_err() {
cfg = cfg.sandbox(true);
}
}
cfg
}
fn resting_price(last: f64) -> f64 {
(last * 0.90).floor()
}
fn order_amount(m: &Market, price: f64) -> f64 {
let min_amount = m.limits.amount.min.unwrap_or(0.0001);
let min_cost = m.limits.cost.min.unwrap_or(5.0);
let step = m.precision.amount.filter(|s| *s > 0.0).unwrap_or(0.0);
let mut amount = f64::max(min_amount, min_cost / price);
if step > 0.0 {
amount = ((amount / step) + 1e-9).ceil() * step;
for _ in 0..8 {
if amount * price >= min_cost && amount >= min_amount {
break;
}
amount += step;
}
}
amount
}
fn fmt_err(e: &ExchangeError) -> String {
let msg: String = e
.message
.lines()
.next()
.unwrap_or("")
.chars()
.take(90)
.collect();
format!("[{}] {msg}", e.kind)
}
async fn trade_on(id: &str, place: bool) {
println!("\n═══ {id} ═══");
let mut ex: Box<dyn TypedExchange> = match from_id_with_config(id, config_for(id)) {
Some(e) => e,
None => {
println!(" no typed wrapper for {id}");
return;
}
};
ex.load_markets(false).await;
let markets = ex.markets();
if markets.is_empty() {
println!(" markets did not load");
return;
}
println!(" markets {}", markets.len());
let market: Market = match ex.market(SYMBOL) {
Ok(m) => m,
Err(e) => {
println!(" market {}", fmt_err(&e));
return;
}
};
let ticker: Ticker = match ex.fetch_ticker(SYMBOL, Params::none()).await {
Ok(t) => t,
Err(e) => {
println!(" ticker {}", fmt_err(&e));
return;
}
};
let last = ticker.last.unwrap_or(0.0);
if last <= 0.0 {
println!(" ticker no last price");
return;
}
let price = resting_price(last);
let amount = order_amount(&market, price);
let notional = amount * price;
println!(
" {SYMBOL:<12} last={last:.2} bid={price:.2} amount={amount:.8} notional={notional:.2}"
);
println!(
" limits amount>={:.8} cost>={} step={:.8}",
market.limits.amount.min.unwrap_or(0.0),
market.limits.cost.min.unwrap_or(0.0),
market.precision.amount.unwrap_or(0.0),
);
if creds(id).is_none() {
println!(
" order skipped, no {}_APIKEY/{}_SECRET",
id.to_uppercase(),
id.to_uppercase()
);
return;
}
if notional >= MAX_NOTIONAL_USD {
println!(" order skipped, {notional:.2} USD >= {MAX_NOTIONAL_USD:.0} cap");
return;
}
if !place {
println!(" order skipped, set CCXT_PLACE_REAL=1 to place it");
return;
}
let params = Params::new().with_str("clientOrderId", &format!("ccxt-generic-{id}"));
let created: Order = match ex
.create_order(SYMBOL, "limit", "buy", amount, Some(price), params)
.await
{
Ok(o) => o,
Err(e) => {
println!(" create {}", fmt_err(&e));
return;
}
};
println!(
" created id={} status={} {:?} @ {:?}",
created.id.clone().unwrap_or_default(),
created.status.clone().unwrap_or_default(),
created.amount,
created.price,
);
let id_to_cancel = created.id.clone().unwrap_or_default();
match ex
.cancel_order(&id_to_cancel, Some(SYMBOL), Params::none())
.await
{
Ok(c) => println!(" cancelled status={}", c.status.unwrap_or_default()),
Err(e) => println!(" cancel {} CHECK {SYMBOL} MANUALLY", fmt_err(&e)),
}
}
async fn run() {
let ids: Vec<String> = match std::env::args().nth(1) {
Some(a) => a
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect(),
None => EXCHANGES.iter().map(|s| s.to_string()).collect(),
};
let place = std::env::var("CCXT_PLACE_REAL").is_ok();
println!("generic_typed_order");
println!(" exchanges {ids:?}");
println!(" symbol {SYMBOL}");
println!(" cap {MAX_NOTIONAL_USD:.0} USD/order");
println!(
" mode {}",
if place {
"PLACES ORDERS (CCXT_PLACE_REAL=1)"
} else {
"inspect only"
}
);
for id in &ids {
trade_on(id, place).await;
}
}
fn main() {
if std::env::var("CCXT_SHOW_PANICS").is_err() {
std::panic::set_hook(Box::new(|_| {}));
}
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());
}