use std::collections::HashMap;
use super::utils::http_get;
use crate::{
error::Result,
market::{Fees, Precision},
Market, MarketType,
};
use chrono::DateTime;
use crypto_pair::get_market_type;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub(crate) fn fetch_symbols(market_type: MarketType) -> Result<Vec<String>> {
let instruments = fetch_instruments(market_type)?;
Ok(instruments.into_iter().map(|x| x.symbol).collect::<Vec<String>>())
}
pub(crate) fn fetch_markets(market_type: MarketType) -> Result<Vec<Market>> {
let instruments = fetch_instruments(market_type)?;
let markets: Vec<Market> = instruments
.into_iter()
.map(|x| {
let info = serde_json::to_value(&x).unwrap().as_object().unwrap().clone();
let base_id = x.underlying;
let quote_id = x.quoteCurrency;
let pair = crypto_pair::normalize_pair(&x.symbol, "bitmex").unwrap();
let (base, quote) = {
let v: Vec<&str> = pair.split('/').collect();
(v[0].to_string(), v[1].to_string())
};
let market_type = if market_type == MarketType::Unknown {
get_market_type(&x.symbol, "bitmex", None)
} else {
market_type
};
Market {
exchange: "bitmex".to_string(),
market_type,
symbol: x.symbol,
base_id,
quote_id,
settle_id: Some(x.settlCurrency.clone()),
base,
quote,
settle: Some(crypto_pair::normalize_currency(x.settlCurrency.as_str(), "bitmex")),
active: x.state == "Open",
margin: true,
fees: Fees { maker: x.makerFee, taker: x.takerFee },
precision: Precision { tick_size: x.tickSize, lot_size: x.lotSize },
quantity_limit: None,
contract_value: if market_type != MarketType::Spot {
if let Some(y) = x.underlyingToSettleMultiplier {
Some(x.multiplier / y)
} else {
Some(x.multiplier / x.quoteToSettleMultiplier.unwrap())
}
} else {
None
},
delivery_date: if let Some(expiry) = x.expiry {
let timestamp = DateTime::parse_from_rfc3339(&expiry).unwrap();
Some(timestamp.timestamp_millis() as u64)
} else {
None
},
info,
}
})
.collect();
Ok(markets)
}
#[derive(Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
struct Instrument {
symbol: String, rootSymbol: String, state: String,
typ: String, listing: String,
front: Option<String>,
expiry: Option<String>,
settle: Option<String>,
listedSettle: Option<String>,
inverseLeg: Option<String>,
positionCurrency: String,
underlying: String, quoteCurrency: String, underlyingSymbol: String, reference: String, referenceSymbol: String, calcInterval: Option<String>,
publishInterval: Option<String>,
publishTime: Option<String>,
maxOrderQty: i64,
maxPrice: f64,
lotSize: f64,
tickSize: f64,
multiplier: f64,
settlCurrency: String,
underlyingToPositionMultiplier: Option<f64>,
underlyingToSettleMultiplier: Option<f64>,
quoteToSettleMultiplier: Option<f64>,
isQuanto: bool,
isInverse: bool,
initMargin: f64,
maintMargin: f64,
riskLimit: Option<f64>,
riskStep: Option<i64>,
limit: Option<f64>,
capped: bool,
taxed: bool,
deleverage: bool,
makerFee: f64,
takerFee: f64,
settlementFee: f64,
insuranceFee: f64,
fundingBaseSymbol: String,
fundingQuoteSymbol: String,
fundingPremiumSymbol: String,
fundingTimestamp: Option<String>,
fundingInterval: Option<String>,
fundingRate: Option<f64>,
indicativeFundingRate: Option<f64>,
rebalanceTimestamp: Option<String>,
rebalanceInterval: Option<String>,
openingTimestamp: String,
closingTimestamp: String,
sessionInterval: String,
prevTotalVolume: i64,
totalVolume: i64,
volume: i64,
volume24h: i64,
prevTotalTurnover: i64,
totalTurnover: i64,
turnover: i64,
turnover24h: i64,
homeNotional24h: f64,
foreignNotional24h: f64,
lastTickDirection: String,
hasLiquidity: bool,
openInterest: i64,
openValue: i64,
fairMethod: String,
markMethod: String,
timestamp: String,
#[serde(flatten)]
extra: HashMap<String, Value>,
}
fn fetch_instruments(market_type: MarketType) -> Result<Vec<Instrument>> {
let text = http_get("https://www.bitmex.com/api/v1/instrument/active", None)?;
let instruments: Vec<Instrument> = serde_json::from_str::<Vec<Instrument>>(&text)?
.into_iter()
.filter(|x| x.state == "Open" && x.hasLiquidity && x.volume24h > 0 && x.turnover24h > 0)
.collect();
let spot: Vec<Instrument> = instruments.iter().filter(|x| x.typ == "IFXXXP").cloned().collect();
let swap: Vec<Instrument> = instruments.iter().filter(|x| x.typ == "FFWCSX").cloned().collect();
let futures: Vec<Instrument> =
instruments.iter().filter(|x| x.typ == "FFCCSX").cloned().collect();
for x in swap.iter() {
assert_eq!("FundingRate", x.fairMethod.as_str());
assert!(x.expiry.is_none());
assert!(x.symbol[x.symbol.len() - 1..].parse::<i32>().is_err());
if let Some(pos) = x.symbol.rfind('_') {
assert_eq!(&(x.symbol[..pos]), format!("{}{}", x.underlying, x.quoteCurrency));
} else {
assert_eq!(x.symbol, format!("{}{}", x.underlying, x.quoteCurrency));
}
}
for x in futures.iter() {
assert_eq!("ImpactMidPrice", x.fairMethod.as_str());
assert!(x.expiry.is_some());
if let Some(pos) = x.symbol.rfind('_') {
assert!(x.symbol[pos - 2..pos].parse::<i32>().is_ok());
} else {
assert!(x.symbol[x.symbol.len() - 2..].parse::<i32>().is_ok());
}
}
for x in instruments.iter().filter(|x| x.isInverse) {
assert!(x.multiplier < 0.0);
assert_eq!(x.quoteCurrency, x.positionCurrency);
}
for x in instruments.iter().filter(|x| x.isQuanto) {
assert!(x.positionCurrency.is_empty());
assert_eq!(x.settlCurrency.to_uppercase(), "XBT");
if x.typ != "FFWCSF" {
assert!(x.quoteCurrency == "USD" || x.quoteCurrency == "USDC");
}
}
for x in instruments.iter().filter(|x| x.positionCurrency.is_empty() && x.typ != "IFXXXP") {
assert!(x.isQuanto);
}
for x in instruments.iter().filter(|x| !x.isQuanto && !x.isInverse && x.typ != "IFXXXP") {
assert_eq!(x.settlCurrency.to_uppercase(), x.quoteCurrency);
}
let filtered: Vec<Instrument> = match market_type {
MarketType::Unknown => instruments,
MarketType::Spot => spot,
MarketType::LinearSwap => {
swap.iter().filter(|x| !x.isQuanto && !x.isInverse).cloned().collect()
}
MarketType::InverseSwap => {
swap.iter().filter(|x| !x.isQuanto && x.isInverse).cloned().collect()
}
MarketType::QuantoSwap => swap.iter().filter(|x| x.isQuanto).cloned().collect(),
MarketType::LinearFuture => {
futures.iter().filter(|x| !x.isInverse && !x.isQuanto).cloned().collect()
}
MarketType::InverseFuture => futures.iter().filter(|x| x.isInverse).cloned().collect(),
MarketType::QuantoFuture => futures.iter().filter(|x| x.isQuanto).cloned().collect(),
_ => panic!("Unsupported market_type: {market_type}"),
};
Ok(filtered)
}