mod utils; use crypto_market_type::MarketType; use crypto_pair::{get_market_type, normalize_currency, normalize_pair}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use utils::http_get; const EXCHANGE_NAME: &str = "bitget"; // See https://bitgetlimited.github.io/apidoc/en/spot/#get-all-instruments #[derive(Clone, Serialize, Deserialize)] #[allow(non_snake_case)] struct SpotMarket { symbol: String, // symbol Id symbolName: String, // symbol name baseCoin: String, // Base coin quoteCoin: String, // Denomination coin status: String, // Status #[serde(flatten)] extra: HashMap, } // See https://bitgetlimited.github.io/apidoc/en/mix/#get-all-symbols #[derive(Clone, Serialize, Deserialize)] #[allow(non_snake_case)] struct SwapMarket { symbol: String, // symbol Id baseCoin: String, // Base coin quoteCoin: String, // Denomination coin #[serde(flatten)] extra: HashMap, } // See https://bitgetlimited.github.io/apidoc/en/spot/#get-all-instruments fn fetch_spot_markets_raw() -> Vec { #[derive(Serialize, Deserialize)] #[allow(non_snake_case)] struct Response { code: String, msg: String, data: Vec, requestTime: Value, #[serde(flatten)] extra: HashMap, } let txt = http_get("https://api.bitget.com/api/spot/v1/public/products").unwrap(); let resp = serde_json::from_str::(&txt).unwrap(); if resp.msg != "success" { Vec::new() } else { resp.data .into_iter() // Ignored ETH_SPBL and BTC_SPBL for now because they're not tradable .filter(|x| x.status == "online" && x.symbol.ends_with("USDT_SPBL")) .collect::>() } } // See https://bitgetlimited.github.io/apidoc/en/mix/#get-all-symbols // product_type: umcbl, LinearSwap; dmcbl, InverseSwap; fn fetch_swap_markets_raw(product_type: &str) -> Vec { #[derive(Serialize, Deserialize)] #[allow(non_snake_case)] struct Response { code: String, msg: String, data: Vec, requestTime: Value, #[serde(flatten)] extra: HashMap, } let txt = http_get( format!("https://api.bitget.com/api/mix/v1/market/contracts?productType={product_type}") .as_str(), ) .unwrap(); let resp = serde_json::from_str::(&txt).unwrap(); if resp.msg != "success" { Vec::new() } else { resp.data } } #[test] fn verify_spot_symbols() { let markets = fetch_spot_markets_raw(); for market in markets.iter() { let pair = normalize_pair(&market.symbol, EXCHANGE_NAME).unwrap(); let pair_expected = format!( "{}/{}", normalize_currency(&market.baseCoin, EXCHANGE_NAME), normalize_currency(&market.quoteCoin, EXCHANGE_NAME) ); assert_eq!(pair.as_str(), pair_expected); assert_eq!(MarketType::Spot, get_market_type(&market.symbol, EXCHANGE_NAME, None)); } } #[test] fn verify_swap_symbols() { let usdt_linear_markets = fetch_swap_markets_raw("umcbl"); let usdc_linear_markets = fetch_swap_markets_raw("cmcbl"); let inverse_markets = fetch_swap_markets_raw("dmcbl"); let markets = usdt_linear_markets .into_iter() .chain(usdc_linear_markets) .chain(inverse_markets) .collect::>(); for market in markets.iter() { let pair = normalize_pair(&market.symbol, EXCHANGE_NAME).unwrap(); let pair_expected = format!( "{}/{}", normalize_currency(&market.baseCoin, EXCHANGE_NAME), normalize_currency( if market.symbol.ends_with("PERP_CMCBL") { "USDC" } else { &market.quoteCoin }, EXCHANGE_NAME ) ); assert_eq!(pair.as_str(), pair_expected); let market_type = get_market_type(&market.symbol, EXCHANGE_NAME, None); assert!( market_type == MarketType::LinearSwap || market_type == MarketType::InverseSwap || market_type == MarketType::InverseFuture ); } }