mod error;
mod exchanges;
pub use error::Error;
pub use exchanges::{
binance::{
binance_inverse::BinanceInverseRestClient, binance_linear::BinanceLinearRestClient,
binance_option::BinanceOptionRestClient, binance_spot::BinanceSpotRestClient,
},
bitfinex::BitfinexRestClient,
bitget::*,
bithumb::*,
bitmex::BitmexRestClient,
bitstamp::BitstampRestClient,
bitz::*,
bybit::BybitRestClient,
coinbase_pro::CoinbaseProRestClient,
deribit::DeribitRestClient,
dydx::dydx_swap::DydxSwapRestClient,
ftx::FtxRestClient,
gate::*,
huobi::{
huobi_future::HuobiFutureRestClient, huobi_inverse_swap::HuobiInverseSwapRestClient,
huobi_linear_swap::HuobiLinearSwapRestClient, huobi_option::HuobiOptionRestClient,
huobi_spot::HuobiSpotRestClient,
},
kraken::{kraken_futures::KrakenFuturesRestClient, kraken_spot::KrakenSpotRestClient},
kucoin::*,
mexc::{mexc_spot::MexcSpotRestClient, mexc_swap::MexcSwapRestClient},
okx::OkxRestClient,
zb::*,
zbg::*,
};
use crypto_market_type::MarketType;
use error::Result;
use log::*;
use std::time::{Duration, SystemTime};
fn fetch_l2_snapshot_internal(
exchange: &str,
market_type: MarketType,
symbol: &str,
) -> Result<String> {
let ret = match exchange {
"binance" => exchanges::binance::fetch_l2_snapshot(market_type, symbol),
"bitfinex" => exchanges::bitfinex::BitfinexRestClient::fetch_l2_snapshot(symbol),
"bitget" => exchanges::bitget::fetch_l2_snapshot(market_type, symbol),
"bithumb" => exchanges::bithumb::BithumbRestClient::fetch_l2_snapshot(symbol),
"bitmex" => exchanges::bitmex::BitmexRestClient::fetch_l2_snapshot(symbol),
"bitstamp" => exchanges::bitstamp::BitstampRestClient::fetch_l2_snapshot(symbol),
"bitz" => exchanges::bitz::fetch_l2_snapshot(market_type, symbol),
"bybit" => exchanges::bybit::BybitRestClient::fetch_l2_snapshot(symbol),
"coinbase_pro" => exchanges::coinbase_pro::CoinbaseProRestClient::fetch_l2_snapshot(symbol),
"deribit" => exchanges::deribit::DeribitRestClient::fetch_l2_snapshot(symbol),
"dydx" => exchanges::dydx::fetch_l2_snapshot(market_type, symbol),
"ftx" => exchanges::ftx::FtxRestClient::fetch_l2_snapshot(symbol),
"gate" => exchanges::gate::fetch_l2_snapshot(market_type, symbol),
"huobi" => exchanges::huobi::fetch_l2_snapshot(market_type, symbol),
"kraken" => exchanges::kraken::fetch_l2_snapshot(market_type, symbol),
"kucoin" => exchanges::kucoin::fetch_l2_snapshot(market_type, symbol),
"mexc" => exchanges::mexc::fetch_l2_snapshot(market_type, symbol),
"okx" => exchanges::okx::OkxRestClient::fetch_l2_snapshot(symbol),
"zb" => exchanges::zb::fetch_l2_snapshot(market_type, symbol),
"zbg" => exchanges::zbg::fetch_l2_snapshot(market_type, symbol),
_ => panic!("Unknown exchange {exchange}"),
};
match ret {
Ok(s) => Ok(s.trim().to_string()),
Err(_) => ret,
}
}
pub fn fetch_l3_snapshot_internal(
exchange: &str,
market_type: MarketType,
symbol: &str,
) -> Result<String> {
let ret = match exchange {
"bitfinex" => exchanges::bitfinex::BitfinexRestClient::fetch_l3_snapshot(symbol),
"bitstamp" => exchanges::bitstamp::BitstampRestClient::fetch_l3_snapshot(symbol),
"coinbase_pro" => exchanges::coinbase_pro::CoinbaseProRestClient::fetch_l3_snapshot(symbol),
"kucoin" => exchanges::kucoin::fetch_l3_snapshot(market_type, symbol),
_ => panic!("{exchange} {market_type} does NOT provide level3 orderbook data"),
};
match ret {
Ok(s) => Ok(s.trim().to_string()),
Err(_) => ret,
}
}
pub fn fetch_open_interest(
exchange: &str,
market_type: MarketType,
symbol: Option<&str>,
) -> Result<String> {
let ret = match exchange {
"binance" => exchanges::binance::fetch_open_interest(market_type, symbol.unwrap()),
"bitget" => exchanges::bitget::fetch_open_interest(market_type, symbol.unwrap()),
"bybit" => exchanges::bybit::BybitRestClient::fetch_open_interest(symbol.unwrap()),
"bitz" => exchanges::bitz::fetch_open_interest(market_type, symbol),
"deribit" => exchanges::deribit::DeribitRestClient::fetch_open_interest(symbol),
"dydx" => exchanges::dydx::fetch_open_interest(market_type),
"ftx" => exchanges::ftx::FtxRestClient::fetch_open_interest(),
"gate" => exchanges::gate::fetch_open_interest(market_type, symbol.unwrap()),
"huobi" => exchanges::huobi::fetch_open_interest(market_type, symbol),
"kucoin" => exchanges::kucoin::fetch_open_interest(market_type),
"okx" => exchanges::okx::OkxRestClient::fetch_open_interest(market_type, symbol),
"zbg" => exchanges::zbg::fetch_open_interest(market_type, symbol.unwrap()),
_ => panic!("{exchange} does NOT have open interest RESTful API"),
};
match ret {
Ok(s) => Ok(s.trim().to_string()),
Err(_) => ret,
}
}
pub fn fetch_long_short_ratio(
exchange: &str,
market_type: MarketType,
symbol: &str,
) -> Result<String> {
let ret = match exchange {
"bybit" => exchanges::bybit::BybitRestClient::fetch_long_short_ratio(symbol),
_ => panic!("{exchange} {market_type} does NOT provide level3 orderbook data"),
};
match ret {
Ok(s) => Ok(s.trim().to_string()),
Err(_) => ret,
}
}
pub fn fetch_l2_snapshot(
exchange: &str,
market_type: MarketType,
symbol: &str,
retry: Option<u64>,
) -> Result<String> {
retriable(exchange, market_type, symbol, fetch_l2_snapshot_internal, retry)
}
pub fn fetch_l3_snapshot(
exchange: &str,
market_type: MarketType,
symbol: &str,
retry: Option<u64>,
) -> Result<String> {
retriable(exchange, market_type, symbol, fetch_l3_snapshot_internal, retry)
}
fn retriable(
exchange: &str,
market_type: MarketType,
symbol: &str,
crawl_func: fn(&str, MarketType, &str) -> Result<String>,
retry: Option<u64>,
) -> Result<String> {
let retry_count = {
let count = retry.unwrap_or(1);
if count == 0 { u64::MAX } else { count }
};
if retry_count == 1 {
return crawl_func(exchange, market_type, symbol);
}
let mut backoff_factor = 0;
let cooldown_time = Duration::from_secs(2);
for _ in 0..retry_count {
let resp = crawl_func(exchange, market_type, symbol);
match resp {
Ok(msg) => return Ok(msg),
Err(err) => {
let current_timestamp =
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis()
as u64;
warn!(
"{} {} {} {} {}, error: {}, back off for {} milliseconds",
current_timestamp,
backoff_factor,
exchange,
market_type,
symbol,
err,
(backoff_factor * cooldown_time).as_millis()
);
std::thread::sleep(backoff_factor * cooldown_time);
if err.0.contains("429") {
backoff_factor += 1;
} else {
backoff_factor *= 2;
}
}
}
}
Err(Error(format!(
"Failed {exchange} {market_type} {symbol} after retrying {retry_count} times"
)))
}