use digdigdig3::connector_manager::ExchangeHub;
use digdigdig3::core::types::{AccountType, ExchangeId};
struct Check {
field: &'static str,
populated: bool,
}
fn check(field: &'static str, populated: bool) -> Check {
Check { field, populated }
}
struct Probe {
venue: &'static str,
endpoint: &'static str,
checks: Vec<Check>,
error: Option<String>,
}
#[tokio::main]
async fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let want = |v: &str| args.is_empty() || args.iter().any(|a| a.eq_ignore_ascii_case(v));
let hub = ExchangeHub::new();
let mut probes: Vec<Probe> = Vec::new();
if want("Binance") {
let _ = hub.connect_public(ExchangeId::Binance, false).await;
if let Some(c) = hub.rest(ExchangeId::Binance) {
match c.get_recent_trades("BTCUSDT".into(), Some(5), AccountType::Spot).await {
Ok(ts) => {
let t = ts.first();
probes.push(Probe {
venue: "Binance", endpoint: "recent_trades",
checks: vec![
check("quote_qty", t.map_or(false, |t| t.quote_qty.is_some())),
check("is_best_match", t.map_or(false, |t| t.is_best_match.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Binance", "recent_trades", e)),
}
match c.get_klines("BTCUSDT".into(), "1m", Some(2), AccountType::Spot, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "Binance", endpoint: "klines",
checks: vec![
check("taker_buy_base_volume", k.map_or(false, |k| k.taker_buy_base_volume.is_some())),
check("taker_buy_quote_volume", k.map_or(false, |k| k.taker_buy_quote_volume.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Binance", "klines", e)),
}
match c.get_funding_rate_history("BTCUSDT".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => probes.push(Probe {
venue: "Binance", endpoint: "funding_history",
checks: vec![check("mark_price", fs.first().map_or(false, |f| f.mark_price.is_some()))],
error: None,
}),
Err(e) => probes.push(err("Binance", "funding_history", e)),
}
match c.get_ticker("BTCUSDT".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Binance", endpoint: "ticker_spot",
checks: vec![
check("weighted_avg_price", t.weighted_avg_price.is_some()),
check("open_price", t.open_price.is_some()),
check("prev_close_price", t.prev_close_price.is_some()),
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
check("last_qty", t.last_qty.is_some()),
check("first_id", t.first_id.is_some()),
check("last_id", t.last_id.is_some()),
check("count", t.count.is_some()),
check("open_time", t.open_time.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Binance", "ticker_spot", e)),
}
match c.get_long_short_ratio_history("BTCUSDT".into(), "1h", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ls) => {
let l = ls.first();
probes.push(Probe {
venue: "Binance", endpoint: "topLSR_global",
checks: vec![
check("long_ratio_nonzero", l.map_or(false, |l| l.long_ratio > 0.0)),
],
error: None,
});
}
Err(e) => probes.push(err("Binance", "topLSR_global", e)),
}
match c.get_orderbook("BTCUSDT".into(), Some(5), AccountType::FuturesCross).await {
Ok(ob) => probes.push(Probe {
venue: "Binance", endpoint: "depth_futures",
checks: vec![
check("event_time", ob.event_time.is_some()),
check("transaction_time", ob.transaction_time.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Binance", "depth_futures", e)),
}
use digdigdig3::l3::open::crypto::cex::binance::{BinanceConnector, BinanceParser};
if let Some(bn) = c.as_any().downcast_ref::<BinanceConnector>() {
match bn.get_basis_history("BTCUSDT", "PERPETUAL", "1h", Some(2), None, None).await {
Ok(raw) => match BinanceParser::parse_basis_history(&raw) {
Ok(bs) => {
let b = bs.first();
probes.push(Probe {
venue: "Binance", endpoint: "basis_history",
checks: vec![
check("index_price", b.map_or(false, |b| b.index_price.is_some())),
check("futures_price",b.map_or(false, |b| b.futures_price.is_some())),
check("basis_rate", b.map_or(false, |b| b.basis_rate.is_some())),
check("contract_type",b.map_or(false, |b| b.contract_type.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Binance", "basis_history", e)),
},
Err(e) => probes.push(err("Binance", "basis_history", e)),
}
}
}
}
if want("OKX") {
let _ = hub.connect_public(ExchangeId::OKX, false).await;
if let Some(c) = hub.rest(ExchangeId::OKX) {
match c.get_funding_rate_history("BTC-USDT-SWAP".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => probes.push(Probe {
venue: "OKX", endpoint: "funding_history",
checks: vec![check("realized_rate", fs.first().map_or(false, |f| f.realized_rate.is_some()))],
error: None,
}),
Err(e) => probes.push(err("OKX", "funding_history", e)),
}
match c.get_open_interest_history("BTC-USDT-SWAP".into(), "5m", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ois) => probes.push(Probe {
venue: "OKX", endpoint: "open_interest",
checks: vec![
check("open_interest_ccy", ois.first().map_or(false, |o| o.open_interest_ccy.is_some())),
check("open_interest_usd", ois.first().map_or(false, |o| o.open_interest_usd.is_some())),
],
error: None,
}),
Err(e) => probes.push(err("OKX", "open_interest", e)),
}
match c.get_ticker("BTC-USDT-SWAP".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "OKX", endpoint: "ticker_swap",
checks: vec![
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
check("open_price", t.open_price.is_some()),
check("last_qty", t.last_qty.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("OKX", "ticker_swap", e)),
}
match c.get_orderbook("BTC-USDT-SWAP".into(), Some(5), AccountType::FuturesCross).await {
Ok(ob) => probes.push(Probe {
venue: "OKX", endpoint: "orderbook_swap",
checks: vec![check("sequence", ob.sequence.is_some())],
error: None,
}),
Err(e) => probes.push(err("OKX", "orderbook_swap", e)),
}
match c.get_klines("BTC-USDT-SWAP".into(), "1m", Some(2), AccountType::FuturesCross, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "OKX", endpoint: "klines_swap",
checks: vec![
check("quote_volume_is_some", k.map_or(false, |k| k.quote_volume.is_some())),
check("quote_volume_gt_volume",
k.map_or(false, |k| k.quote_volume.map_or(false, |q| q > k.volume))),
],
error: None,
});
}
Err(e) => probes.push(err("OKX", "klines_swap", e)),
}
}
}
if want("BitMEX") {
let _ = hub.connect_public(ExchangeId::Bitmex, false).await;
if let Some(c) = hub.rest(ExchangeId::Bitmex) {
match c.get_recent_trades("XBTUSDT".into(), Some(5), AccountType::FuturesCross).await {
Ok(ts) => {
let t = ts.first();
probes.push(Probe {
venue: "BitMEX", endpoint: "recent_trades",
checks: vec![
check("gross_value", t.map_or(false, |t| t.gross_value.is_some())),
check("home_notional", t.map_or(false, |t| t.home_notional.is_some())),
check("foreign_notional",t.map_or(false, |t| t.foreign_notional.is_some())),
check("tick_direction", t.map_or(false, |t| t.tick_direction.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("BitMEX", "recent_trades", e)),
}
match c.get_ticker("XBTUSD".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "BitMEX", endpoint: "ticker",
checks: vec![
check("mark_price", t.mark_price.is_some()),
check("open_interest",t.open_interest.is_some()),
check("funding_rate", t.funding_rate.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("BitMEX", "ticker", e)),
}
match c.get_klines("XBTUSD".into(), "1m", Some(2), AccountType::FuturesCross, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "BitMEX", endpoint: "klines",
checks: vec![
check("last_size", k.map_or(false, |k| k.last_size.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("BitMEX", "klines", e)),
}
}
}
if want("Deribit") {
let _ = hub.connect_public(ExchangeId::Deribit, false).await;
if let Some(c) = hub.rest(ExchangeId::Deribit) {
match c.get_recent_trades("BTC-PERPETUAL".into(), Some(5), AccountType::FuturesCross).await {
Ok(ts) => {
let t = ts.first();
probes.push(Probe {
venue: "Deribit", endpoint: "recent_trades",
checks: vec![
check("index_price", t.map_or(false, |t| t.index_price.is_some())),
check("mark_price", t.map_or(false, |t| t.mark_price.is_some())),
check("trade_seq", t.map_or(false, |t| t.trade_seq.is_some())),
check("tick_direction",t.map_or(false, |t| t.tick_direction.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Deribit", "recent_trades", e)),
}
}
}
if want("GateIO") {
let _ = hub.connect_public(ExchangeId::GateIO, false).await;
if let Some(c) = hub.rest(ExchangeId::GateIO) {
match c.get_long_short_ratio_history("BTC_USDT".into(), "5m", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ls) => probes.push(Probe {
venue: "GateIO", endpoint: "long_short_ratio",
checks: vec![
check("top_lsr_size", ls.first().map_or(false, |l| l.top_lsr_size.is_some())),
check("long_users", ls.first().map_or(false, |l| l.long_users.is_some())),
],
error: None,
}),
Err(e) => probes.push(err("GateIO", "long_short_ratio", e)),
}
match c.get_liquidation_bucket_history("BTC_USDT".into(), "5m", None, None, Some(2), AccountType::FuturesCross).await {
Ok(la) => probes.push(Probe {
venue: "GateIO", endpoint: "liquidation_bucket",
checks: vec![check("rows_returned", !la.is_empty())],
error: None,
}),
Err(e) => probes.push(err("GateIO", "liquidation_bucket", e)),
}
match c.get_insurance_fund(None, AccountType::FuturesCross).await {
Ok(ifd) => probes.push(Probe {
venue: "GateIO", endpoint: "insurance_fund",
checks: vec![check("balance", ifd.first().map_or(false, |i| i.balance > 0.0))],
error: None,
}),
Err(e) => probes.push(err("GateIO", "insurance_fund", e)),
}
match c.get_klines("BTC_USDT".into(), "1m", Some(2), AccountType::Spot, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "GateIO", endpoint: "klines_spot",
checks: vec![
check("quote_volume_is_some", k.map_or(false, |k| k.quote_volume.is_some())),
check("quote_volume_gt_volume",
k.map_or(false, |k| k.quote_volume.map_or(false, |q| q > k.volume))),
],
error: None,
});
}
Err(e) => probes.push(err("GateIO", "klines_spot", e)),
}
match c.get_ticker("BTC_USDT".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "GateIO", endpoint: "ticker_spot",
checks: vec![
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("GateIO", "ticker_spot", e)),
}
match c.get_ticker("BTC_USDT".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "GateIO", endpoint: "ticker_futures",
checks: vec![
check("mark_price", t.mark_price.is_some()),
check("index_price", t.index_price.is_some()),
check("funding_rate", t.funding_rate.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("GateIO", "ticker_futures", e)),
}
}
}
if want("Bitfinex") {
let _ = hub.connect_public(ExchangeId::Bitfinex, false).await;
if let Some(c) = hub.rest(ExchangeId::Bitfinex) {
match c.get_insurance_fund(Some("tBTCF0:USTF0".into()), AccountType::FuturesCross).await {
Ok(ifd) => probes.push(Probe {
venue: "Bitfinex", endpoint: "insurance_fund",
checks: vec![check("balance", ifd.first().map_or(false, |i| i.balance > 0.0))],
error: None,
}),
Err(e) => probes.push(err("Bitfinex", "insurance_fund", e)),
}
match c.get_premium_index(Some("tBTCF0:USTF0".into()), AccountType::FuturesCross).await {
Ok(mp) => probes.push(Probe {
venue: "Bitfinex", endpoint: "premium_index(mark)",
checks: vec![
check("spot_price", mp.first().map_or(false, |m| m.spot_price.is_some())),
check("index_price", mp.first().map_or(false, |m| m.index_price.is_some())),
],
error: None,
}),
Err(e) => probes.push(err("Bitfinex", "premium_index(mark)", e)),
}
match c.get_ticker("tBTCUSD".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Bitfinex", endpoint: "ticker_spot",
checks: vec![
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Bitfinex", "ticker_spot", e)),
}
}
}
if want("Deribit") {
if let Some(c) = hub.rest(ExchangeId::Deribit) {
match c.get_ticker("BTC-PERPETUAL".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "Deribit", endpoint: "ticker",
checks: vec![
check("mark_price", t.mark_price.is_some()),
check("open_interest", t.open_interest.is_some()),
check("settlement_price",t.settlement_price.is_some()),
check("min_price", t.min_price.is_some()),
check("max_price", t.max_price.is_some()),
check("volume_notional", t.volume_notional.is_some()),
check("state", t.state.is_some()),
check("interest_value", t.interest_value.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Deribit", "ticker", e)),
}
match c.get_funding_rate_history("BTC-PERPETUAL".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "Deribit", endpoint: "funding_history",
checks: vec![
check("interest_8h", f.map_or(false, |f| f.interest_8h.is_some())),
check("index_price", f.map_or(false, |f| f.index_price.is_some())),
check("prev_index_price", f.map_or(false, |f| f.prev_index_price.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Deribit", "funding_history", e)),
}
use digdigdig3::l3::open::crypto::cex::deribit::DeribitConnector;
if let Some(dr) = c.as_any().downcast_ref::<DeribitConnector>() {
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64;
let one_day_ago = now_ms - 24 * 3600 * 1000;
match dr.get_volatility_index_data("BTC", one_day_ago, now_ms, Some(3600)).await {
Ok(vols) => {
let v = vols.first();
probes.push(Probe {
venue: "Deribit", endpoint: "dvol",
checks: vec![
check("open", v.map_or(false, |v| v.open.is_some())),
check("high", v.map_or(false, |v| v.high.is_some())),
check("low", v.map_or(false, |v| v.low.is_some())),
check("close", v.map_or(false, |v| v.close.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Deribit", "dvol", e)),
}
} else {
probes.push(Probe {
venue: "Deribit", endpoint: "dvol",
checks: Vec::new(),
error: Some("downcast to DeribitConnector failed".to_string()),
});
}
match c.get_historical_volatility("BTC").await {
Ok(hvs) => {
let h = hvs.first();
probes.push(Probe {
venue: "Deribit", endpoint: "historical_volatility",
checks: vec![
check("ts_nonzero", h.map_or(false, |h| h.timestamp > 0)),
check("volatility_finite",h.map_or(false, |h| h.volatility.is_finite() && h.volatility > 0.0)),
check("rows_returned", !hvs.is_empty()),
],
error: None,
});
}
Err(e) => probes.push(err("Deribit", "historical_volatility", e)),
}
}
}
if want("Bybit") {
let _ = hub.connect_public(ExchangeId::Bybit, false).await;
if let Some(c) = hub.rest(ExchangeId::Bybit) {
match c.get_ticker("BTCUSDT".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "Bybit", endpoint: "ticker(linear)",
checks: vec![
check("mark_price", t.mark_price.is_some()),
check("open_interest", t.open_interest.is_some()),
check("funding_rate", t.funding_rate.is_some()),
check("funding_interval_hour",t.funding_interval_hour.is_some()),
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Bybit", "ticker(linear)", e)),
}
match c.get_orderbook("BTCUSDT".into(), Some(5), AccountType::FuturesCross).await {
Ok(ob) => probes.push(Probe {
venue: "Bybit", endpoint: "orderbook_futures",
checks: vec![
check("sequence", ob.sequence.is_some()),
check("cts", ob.cts.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Bybit", "orderbook_futures", e)),
}
}
}
if want("Bitget") {
let _ = hub.connect_public(ExchangeId::Bitget, false).await;
if let Some(c) = hub.rest(ExchangeId::Bitget) {
match c.get_long_short_ratio_history("BTCUSDT".into(), "1H", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ls) => {
let l = ls.first();
probes.push(Probe {
venue: "Bitget", endpoint: "lsr_trait",
checks: vec![
check("long_ratio_nonzero", l.map_or(false, |l| l.long_ratio > 0.0)),
],
error: None,
});
}
Err(e) => probes.push(err("Bitget", "lsr_trait", e)),
}
use digdigdig3::l3::open::crypto::cex::bitget::BitgetConnector;
if let Some(bg) = c.as_any().downcast_ref::<BitgetConnector>() {
match bg.get_account_long_short_ratio_history("BTCUSDT".into(), "1H", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ls) => {
let l = ls.first();
probes.push(Probe {
venue: "Bitget", endpoint: "account_lsr",
checks: vec![ check("long_ratio_nonzero", l.map_or(false, |l| l.long_ratio > 0.0)) ],
error: None,
});
}
Err(e) => probes.push(err("Bitget", "account_lsr", e)),
}
match bg.get_position_long_short_ratio_history("BTCUSDT".into(), "1H", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ls) => {
let l = ls.first();
probes.push(Probe {
venue: "Bitget", endpoint: "position_lsr",
checks: vec![ check("long_ratio_nonzero", l.map_or(false, |l| l.long_ratio > 0.0)) ],
error: None,
});
}
Err(e) => probes.push(err("Bitget", "position_lsr", e)),
}
}
}
}
if want("HTX") {
let _ = hub.connect_public(ExchangeId::HTX, false).await;
if let Some(c) = hub.rest(ExchangeId::HTX) {
match c.get_basis_history("BTC-USDT".into(), "60min", None, None, Some(2), AccountType::FuturesCross).await {
Ok(bs) => {
let b = bs.first();
probes.push(Probe {
venue: "HTX", endpoint: "basis_history",
checks: vec![
check("index_price", b.map_or(false, |b| b.index_price.is_some())),
check("futures_price",b.map_or(false, |b| b.futures_price.is_some())),
check("basis_rate", b.map_or(false, |b| b.basis_rate.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("HTX", "basis_history", e)),
}
match c.get_open_interest_history("BTC-USDT".into(), "60min", None, None, Some(2), AccountType::FuturesCross).await {
Ok(ois) => {
let o = ois.first();
probes.push(Probe {
venue: "HTX", endpoint: "oi_history",
checks: vec![
check("open_interest_value", o.map_or(false, |o| o.open_interest_value.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("HTX", "oi_history", e)),
}
match c.get_ticker("btcusdt".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "HTX", endpoint: "ticker_spot",
checks: vec![
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
check("open_price", t.open_price.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("HTX", "ticker_spot", e)),
}
}
}
if want("dYdX") {
let _ = hub.connect_public(ExchangeId::Dydx, false).await;
if let Some(c) = hub.rest(ExchangeId::Dydx) {
match c.get_ticker("BTC-USD".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "dYdX", endpoint: "ticker",
checks: vec![
check("funding_rate", t.funding_rate.is_some()),
check("open_interest", t.open_interest.is_some()),
check("count", t.count.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("dYdX", "ticker", e)),
}
match c.get_funding_rate_history("BTC-USD".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "dYdX", endpoint: "funding_history",
checks: vec![
check("symbol", f.map_or(false, |f| f.symbol.is_some())),
check("mark_price", f.map_or(false, |f| f.mark_price.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("dYdX", "funding_history", e)),
}
probes.push(Probe {
venue: "dYdX", endpoint: "oi_history",
checks: Vec::new(),
error: Some("OI from candles: dYdX has no OI history endpoint; OI lives in ticker.open_interest".to_string()),
});
}
}
if want("HyperLiquid") {
let _ = hub.connect_public(ExchangeId::HyperLiquid, false).await;
if let Some(c) = hub.rest(ExchangeId::HyperLiquid) {
match c.get_ticker("BTC".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "HyperLiquid", endpoint: "ticker",
checks: vec![
check("open_interest", t.open_interest.is_some()),
check("funding_rate", t.funding_rate.is_some()),
check("index_price", t.index_price.is_some()),
check("mark_price", t.mark_price.is_some()),
check("volume_24h", t.volume_24h.is_some()),
check("quote_volume_24h", t.quote_volume_24h.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("HyperLiquid", "ticker", e)),
}
match c.get_funding_rate_history("BTC".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "HyperLiquid", endpoint: "funding_history",
checks: vec![
check("symbol", f.map_or(false, |f| f.symbol.is_some())),
check("premium", f.map_or(false, |f| f.premium.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("HyperLiquid", "funding_history", e)),
}
}
}
if want("Lighter") {
let _ = hub.connect_public(ExchangeId::Lighter, false).await;
if let Some(c) = hub.rest(ExchangeId::Lighter) {
match c.get_funding_rate_history("BTC".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "Lighter", endpoint: "funding_history",
checks: vec![
check("symbol", f.map_or(false, |f| f.symbol.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Lighter", "funding_history", e)),
}
}
}
if want("Kraken") {
let _ = hub.connect_public(ExchangeId::Kraken, false).await;
if let Some(c) = hub.rest(ExchangeId::Kraken) {
match c.get_ticker("XXBTZUSD".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Kraken", endpoint: "ticker_spot",
checks: vec![
check("weighted_avg_price", t.weighted_avg_price.is_some()),
check("count", t.count.is_some()),
check("open_price", t.open_price.is_some()),
check("last_qty", t.last_qty.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Kraken", "ticker_spot", e)),
}
match c.get_ticker("PI_XBTUSD".into(), AccountType::FuturesCross).await {
Ok(t) => probes.push(Probe {
venue: "Kraken", endpoint: "ticker_futures",
checks: vec![
check("weighted_avg_price", t.weighted_avg_price.is_some()),
check("last_qty", t.last_qty.is_some()),
check("last_trade_time", t.last_trade_time.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Kraken", "ticker_futures", e)),
}
match c.get_funding_rate_history("PI_XBTUSD".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "Kraken", endpoint: "funding_history",
checks: vec![
check("relative_funding_rate", f.map_or(false, |f| f.relative_funding_rate.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("Kraken", "funding_history", e)),
}
}
}
if want("MEXC") {
let _ = hub.connect_public(ExchangeId::MEXC, false).await;
if let Some(c) = hub.rest(ExchangeId::MEXC) {
match c.get_funding_rate_history("BTC_USDT".into(), None, None, Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "MEXC", endpoint: "funding_history",
checks: vec![
check("symbol", f.map_or(false, |f| f.symbol.is_some())),
check("funding_interval_hours",f.map_or(false, |f| f.funding_interval_hours.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("MEXC", "funding_history", e)),
}
match c.get_klines("BTC_USDT".into(), "Min1", Some(2), AccountType::FuturesCross, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "MEXC", endpoint: "klines_futures",
checks: vec![
check("quote_volume", k.map_or(false, |k| k.quote_volume.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("MEXC", "klines_futures", e)),
}
match c.get_ticker("BTCUSDT".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "MEXC", endpoint: "ticker_spot",
checks: vec![
check("open_price", t.open_price.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("MEXC", "ticker_spot", e)),
}
}
}
if want("BingX") {
let _ = hub.connect_public(ExchangeId::BingX, false).await;
if let Some(c) = hub.rest(ExchangeId::BingX) {
match c.get_ticker("BTC-USDT".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "BingX", endpoint: "ticker_spot",
checks: vec![
check("bid_qty", t.bid_qty.is_some()),
check("ask_qty", t.ask_qty.is_some()),
check("open_price", t.open_price.is_some()),
check("open_time", t.open_time.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("BingX", "ticker_spot", e)),
}
match c.get_open_interest("BTC-USDT", AccountType::FuturesCross).await {
Ok(oi) => probes.push(Probe {
venue: "BingX", endpoint: "oi_swap",
checks: vec![
check("symbol_nonempty", oi.symbol.as_deref().map_or(false, |s| !s.is_empty())),
check("timestamp_nonzero", oi.timestamp != 0),
],
error: None,
}),
Err(e) => probes.push(err("BingX", "oi_swap", e)),
}
}
}
if want("Coinbase") {
let _ = hub.connect_public(ExchangeId::Coinbase, false).await;
if let Some(c) = hub.rest(ExchangeId::Coinbase) {
match c.get_klines("BTC-USD".into(), "60", Some(2), AccountType::Spot, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "Coinbase", endpoint: "klines",
checks: vec![
check("close_time_is_some", k.map_or(false, |k| k.close_time.is_some())),
check("close_time_gt_open_time",
k.map_or(false, |k| k.close_time.map_or(false, |ct| ct > k.open_time))),
],
error: None,
});
}
Err(e) => probes.push(err("Coinbase", "klines", e)),
}
match c.get_ticker("BTC-USD".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Coinbase", endpoint: "ticker_spot",
checks: vec![
check("last_qty", t.last_qty.is_some()),
check("last_price_nz", t.last_price > 0.0),
check("timestamp_nz", t.timestamp > 0),
],
error: None,
}),
Err(e) => probes.push(err("Coinbase", "ticker_spot", e)),
}
}
}
if want("Upbit") {
let _ = hub.connect_public(ExchangeId::Upbit, false).await;
if let Some(c) = hub.rest(ExchangeId::Upbit) {
match c.get_klines("KRW-BTC".into(), "1", Some(2), AccountType::Spot, None).await {
Ok(ks) => {
let k = ks.first();
probes.push(Probe {
venue: "Upbit", endpoint: "klines_1m",
checks: vec![
check("open_time_boundary", k.map_or(false, |k| k.open_time % 60_000 == 0)),
],
error: None,
});
}
Err(e) => probes.push(err("Upbit", "klines_1m", e)),
}
match c.get_ticker("KRW-BTC".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Upbit", endpoint: "ticker_spot",
checks: vec![check("open_price", t.open_price.is_some())],
error: None,
}),
Err(e) => probes.push(err("Upbit", "ticker_spot", e)),
}
}
}
if want("Bitstamp") {
let _ = hub.connect_public(ExchangeId::Bitstamp, false).await;
if let Some(c) = hub.rest(ExchangeId::Bitstamp) {
match c.get_ticker("btcusd".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Bitstamp", endpoint: "ticker_spot",
checks: vec![
check("weighted_avg_price", t.weighted_avg_price.is_some()),
check("open_price", t.open_price.is_some()),
check("quote_volume_24h_is_none", !t.quote_volume_24h.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Bitstamp", "ticker_spot", e)),
}
}
}
if want("Gemini") {
let _ = hub.connect_public(ExchangeId::Gemini, false).await;
if let Some(c) = hub.rest(ExchangeId::Gemini) {
match c.get_ticker("btcusd".into(), AccountType::Spot).await {
Ok(t) => probes.push(Probe {
venue: "Gemini", endpoint: "ticker_spot",
checks: vec![
check("volume_24h", t.volume_24h.is_some()),
check("quote_volume_24h", t.quote_volume_24h.is_some()),
check("open_price", t.open_price.is_some()),
],
error: None,
}),
Err(e) => probes.push(err("Gemini", "ticker_spot", e)),
}
}
}
if want("CryptoCom") {
let _ = hub.connect_public(ExchangeId::CryptoCom, false).await;
if let Some(c) = hub.rest(ExchangeId::CryptoCom) {
match c.get_orderbook("BTC_USDT".into(), Some(5), AccountType::Spot).await {
Ok(ob) => probes.push(Probe {
venue: "CryptoCom", endpoint: "orderbook_spot",
checks: vec![
check("bids_order_count",
ob.bids.first().map_or(false, |l| l.order_count.is_some())),
],
error: None,
}),
Err(e) => probes.push(err("CryptoCom", "orderbook_spot", e)),
}
}
}
if want("KuCoin") {
let _ = hub.connect_public(ExchangeId::KuCoin, false).await;
if let Some(c) = hub.rest(ExchangeId::KuCoin) {
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64;
let seven_days_ago = now_ms - 7 * 24 * 3600 * 1000;
match c.get_funding_rate_history("XBTUSDTM".into(), Some(seven_days_ago), Some(now_ms), Some(2), AccountType::FuturesCross).await {
Ok(fs) => {
let f = fs.first();
probes.push(Probe {
venue: "KuCoin", endpoint: "funding_history",
checks: vec![
check("symbol", f.map_or(false, |f| f.symbol.is_some())),
],
error: None,
});
}
Err(e) => probes.push(err("KuCoin", "funding_history", e)),
}
match c.get_open_interest("XBTUSDTM", AccountType::FuturesCross).await {
Ok(oi) => probes.push(Probe {
venue: "KuCoin", endpoint: "open_interest",
checks: vec![
check("open_interest_nonzero", oi.open_interest > 0.0),
],
error: None,
}),
Err(e) => probes.push(err("KuCoin", "open_interest", e)),
}
}
}
println!("\n── lossless_verify ─ live RAW-pump field coverage ──────────────");
let mut total = 0usize;
let mut populated = 0usize;
let mut errors = 0usize;
for p in &probes {
if let Some(e) = &p.error {
println!(" [ERR ] {:<12} {:<28} {}", p.venue, p.endpoint, e);
errors += 1;
continue;
}
for c in &p.checks {
total += 1;
let mark = if c.populated { populated += 1; "ok " } else { "MISS" };
println!(" [{}] {:<12} {:<28} {}", mark, p.venue, p.endpoint, c.field);
}
}
let missed = total - populated;
println!("────────────────────────────────────────────────────────────────");
println!(" fields populated: {populated}/{total} missed: {missed} call-errors: {errors}");
if missed > 0 {
eprintln!("\nFAIL: {missed} enriched field(s) came back empty — the pump dropped data.");
std::process::exit(1);
}
if populated == 0 {
eprintln!("\nFAIL: no fields verified (all calls errored?) — not a proof of losslessness.");
std::process::exit(2);
}
println!("\nPASS: every probed enriched field is populated from the live wire.");
}
fn err(venue: &'static str, endpoint: &'static str, e: impl std::fmt::Display) -> Probe {
Probe { venue, endpoint, checks: Vec::new(), error: Some(e.to_string()) }
}