crypto_rest_client/
lib.rs

1mod error;
2mod exchanges;
3
4pub use error::Error;
5pub use exchanges::{
6    binance::{
7        binance_inverse::BinanceInverseRestClient, binance_linear::BinanceLinearRestClient,
8        binance_option::BinanceOptionRestClient, binance_spot::BinanceSpotRestClient,
9    },
10    bitfinex::BitfinexRestClient,
11    bitget::*,
12    bithumb::*,
13    bitmex::BitmexRestClient,
14    bitstamp::BitstampRestClient,
15    bitz::*,
16    bybit::BybitRestClient,
17    coinbase_pro::CoinbaseProRestClient,
18    deribit::DeribitRestClient,
19    dydx::dydx_swap::DydxSwapRestClient,
20    ftx::FtxRestClient,
21    gate::*,
22    huobi::{
23        huobi_future::HuobiFutureRestClient, huobi_inverse_swap::HuobiInverseSwapRestClient,
24        huobi_linear_swap::HuobiLinearSwapRestClient, huobi_option::HuobiOptionRestClient,
25        huobi_spot::HuobiSpotRestClient,
26    },
27    kraken::{kraken_futures::KrakenFuturesRestClient, kraken_spot::KrakenSpotRestClient},
28    kucoin::*,
29    mexc::{mexc_spot::MexcSpotRestClient, mexc_swap::MexcSwapRestClient},
30    okx::OkxRestClient,
31    zb::*,
32    zbg::*,
33};
34
35use crypto_market_type::MarketType;
36use error::Result;
37use log::*;
38use std::time::{Duration, SystemTime};
39
40fn fetch_l2_snapshot_internal(
41    exchange: &str,
42    market_type: MarketType,
43    symbol: &str,
44) -> Result<String> {
45    let ret = match exchange {
46        "binance" => exchanges::binance::fetch_l2_snapshot(market_type, symbol),
47        "bitfinex" => exchanges::bitfinex::BitfinexRestClient::fetch_l2_snapshot(symbol),
48        "bitget" => exchanges::bitget::fetch_l2_snapshot(market_type, symbol),
49        "bithumb" => exchanges::bithumb::BithumbRestClient::fetch_l2_snapshot(symbol),
50        "bitmex" => exchanges::bitmex::BitmexRestClient::fetch_l2_snapshot(symbol),
51        "bitstamp" => exchanges::bitstamp::BitstampRestClient::fetch_l2_snapshot(symbol),
52        "bitz" => exchanges::bitz::fetch_l2_snapshot(market_type, symbol),
53        "bybit" => exchanges::bybit::BybitRestClient::fetch_l2_snapshot(symbol),
54        "coinbase_pro" => exchanges::coinbase_pro::CoinbaseProRestClient::fetch_l2_snapshot(symbol),
55        "deribit" => exchanges::deribit::DeribitRestClient::fetch_l2_snapshot(symbol),
56        "dydx" => exchanges::dydx::fetch_l2_snapshot(market_type, symbol),
57        "ftx" => exchanges::ftx::FtxRestClient::fetch_l2_snapshot(symbol),
58        "gate" => exchanges::gate::fetch_l2_snapshot(market_type, symbol),
59        "huobi" => exchanges::huobi::fetch_l2_snapshot(market_type, symbol),
60        "kraken" => exchanges::kraken::fetch_l2_snapshot(market_type, symbol),
61        "kucoin" => exchanges::kucoin::fetch_l2_snapshot(market_type, symbol),
62        "mexc" => exchanges::mexc::fetch_l2_snapshot(market_type, symbol),
63        "okx" => exchanges::okx::OkxRestClient::fetch_l2_snapshot(symbol),
64        "zb" => exchanges::zb::fetch_l2_snapshot(market_type, symbol),
65        "zbg" => exchanges::zbg::fetch_l2_snapshot(market_type, symbol),
66        _ => panic!("Unknown exchange {exchange}"),
67    };
68    match ret {
69        Ok(s) => Ok(s.trim().to_string()),
70        Err(_) => ret,
71    }
72}
73
74pub fn fetch_l3_snapshot_internal(
75    exchange: &str,
76    market_type: MarketType,
77    symbol: &str,
78) -> Result<String> {
79    let ret = match exchange {
80        "bitfinex" => exchanges::bitfinex::BitfinexRestClient::fetch_l3_snapshot(symbol),
81        "bitstamp" => exchanges::bitstamp::BitstampRestClient::fetch_l3_snapshot(symbol),
82        "coinbase_pro" => exchanges::coinbase_pro::CoinbaseProRestClient::fetch_l3_snapshot(symbol),
83        "kucoin" => exchanges::kucoin::fetch_l3_snapshot(market_type, symbol),
84        _ => panic!("{exchange} {market_type} does NOT provide level3 orderbook data"),
85    };
86    match ret {
87        Ok(s) => Ok(s.trim().to_string()),
88        Err(_) => ret,
89    }
90}
91
92/// Fetch open interest.
93///
94/// `symbol` None means fetch all symbols.
95pub fn fetch_open_interest(
96    exchange: &str,
97    market_type: MarketType,
98    symbol: Option<&str>,
99) -> Result<String> {
100    let ret = match exchange {
101        "binance" => exchanges::binance::fetch_open_interest(market_type, symbol.unwrap()),
102        "bitget" => exchanges::bitget::fetch_open_interest(market_type, symbol.unwrap()),
103        "bybit" => exchanges::bybit::BybitRestClient::fetch_open_interest(symbol.unwrap()),
104        "bitz" => exchanges::bitz::fetch_open_interest(market_type, symbol),
105        "deribit" => exchanges::deribit::DeribitRestClient::fetch_open_interest(symbol),
106        "dydx" => exchanges::dydx::fetch_open_interest(market_type),
107        "ftx" => exchanges::ftx::FtxRestClient::fetch_open_interest(),
108        "gate" => exchanges::gate::fetch_open_interest(market_type, symbol.unwrap()),
109        "huobi" => exchanges::huobi::fetch_open_interest(market_type, symbol),
110        "kucoin" => exchanges::kucoin::fetch_open_interest(market_type),
111        "okx" => exchanges::okx::OkxRestClient::fetch_open_interest(market_type, symbol),
112        "zbg" => exchanges::zbg::fetch_open_interest(market_type, symbol.unwrap()),
113        _ => panic!("{exchange} does NOT have open interest RESTful API"),
114    };
115    match ret {
116        Ok(s) => Ok(s.trim().to_string()),
117        Err(_) => ret,
118    }
119}
120
121pub fn fetch_long_short_ratio(
122    exchange: &str,
123    market_type: MarketType,
124    symbol: &str,
125) -> Result<String> {
126    let ret = match exchange {
127        "bybit" => exchanges::bybit::BybitRestClient::fetch_long_short_ratio(symbol),
128        _ => panic!("{exchange} {market_type} does NOT provide level3 orderbook data"),
129    };
130    match ret {
131        Ok(s) => Ok(s.trim().to_string()),
132        Err(_) => ret,
133    }
134}
135
136/// Fetch level2 orderbook snapshot.
137///
138/// `retry` None means no retry; Some(0) means retry unlimited times; Some(n)
139/// means retry n times.
140pub fn fetch_l2_snapshot(
141    exchange: &str,
142    market_type: MarketType,
143    symbol: &str,
144    retry: Option<u64>,
145) -> Result<String> {
146    retriable(exchange, market_type, symbol, fetch_l2_snapshot_internal, retry)
147}
148
149/// Fetch level3 orderbook snapshot.
150///
151/// `retry` None means no retry; Some(0) means retry unlimited times; Some(n)
152/// means retry n times.
153pub fn fetch_l3_snapshot(
154    exchange: &str,
155    market_type: MarketType,
156    symbol: &str,
157    retry: Option<u64>,
158) -> Result<String> {
159    retriable(exchange, market_type, symbol, fetch_l3_snapshot_internal, retry)
160}
161
162// `retry` None means no retry; Some(0) means retry unlimited times; Some(n)
163// means retry n times.
164fn retriable(
165    exchange: &str,
166    market_type: MarketType,
167    symbol: &str,
168    crawl_func: fn(&str, MarketType, &str) -> Result<String>,
169    retry: Option<u64>,
170) -> Result<String> {
171    let retry_count = {
172        let count = retry.unwrap_or(1);
173        if count == 0 { u64::MAX } else { count }
174    };
175    if retry_count == 1 {
176        return crawl_func(exchange, market_type, symbol);
177    }
178    let mut backoff_factor = 0;
179    let cooldown_time = Duration::from_secs(2);
180    for _ in 0..retry_count {
181        let resp = crawl_func(exchange, market_type, symbol);
182        match resp {
183            Ok(msg) => return Ok(msg),
184            Err(err) => {
185                let current_timestamp =
186                    SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis()
187                        as u64;
188                warn!(
189                    "{} {} {} {} {}, error: {}, back off for {} milliseconds",
190                    current_timestamp,
191                    backoff_factor,
192                    exchange,
193                    market_type,
194                    symbol,
195                    err,
196                    (backoff_factor * cooldown_time).as_millis()
197                );
198                std::thread::sleep(backoff_factor * cooldown_time);
199                if err.0.contains("429") {
200                    backoff_factor += 1;
201                } else {
202                    // Handle 403, 418, etc.
203                    backoff_factor *= 2;
204                }
205            }
206        }
207    }
208    Err(Error(format!(
209        "Failed {exchange} {market_type} {symbol} after retrying {retry_count} times"
210    )))
211}