use std::{env, time::Duration};
use futures_util::StreamExt;
use nautilus_bitmex::{
common::enums::BitmexEnvironment, http::client::BitmexHttpClient,
websocket::client::BitmexWebSocketClient,
};
use nautilus_model::{data::bar::BarType, identifiers::InstrumentId};
use nautilus_network::websocket::TransportBackend;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
nautilus_common::logging::ensure_logging_initialized();
let args: Vec<String> = env::args().collect();
let subscription_type = args.get(1).map_or("all", String::as_str);
let symbol = args.get(2).map_or("XBTUSD", String::as_str);
let environment = if args.get(3).is_some_and(|s| s == "testnet") {
BitmexEnvironment::Testnet
} else {
BitmexEnvironment::Mainnet
};
log::info!("Starting Bitmex WebSocket test");
log::info!("Subscription type: {subscription_type}");
log::info!("Symbol: {symbol}");
log::info!("Environment: {environment}");
let (http_url, ws_url) = match environment {
BitmexEnvironment::Testnet => (
Some("https://testnet.bitmex.com".to_string()),
Some("wss://ws.testnet.bitmex.com/realtime".to_string()),
),
BitmexEnvironment::Mainnet => (None, None),
};
log::info!("Fetching instruments from HTTP API...");
let http_client = BitmexHttpClient::new(
http_url, None, None, environment, 60, 3, 1_000, 10_000, 10_000, 10, 120, None, )
.expect("Failed to create HTTP client");
let instruments = http_client
.request_instruments(true) .await?;
log::info!("Fetched {} instruments", instruments.len());
let mut ws_client = BitmexWebSocketClient::new(
ws_url, None, None, None, 5, None, TransportBackend::default(),
None,
)
.unwrap();
ws_client.connect().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let instrument_id = InstrumentId::from(format!("{symbol}.BITMEX"));
log::info!("Using instrument_id: {instrument_id}");
match subscription_type {
"quotes" => {
log::info!("Subscribing to quotes for {instrument_id}");
ws_client.subscribe_quotes(instrument_id).await?;
}
"trades" => {
log::info!("Subscribing to trades for {instrument_id}");
ws_client.subscribe_trades(instrument_id).await?;
}
"orderbook" | "book" => {
log::info!("Subscribing to order book L2 for {instrument_id}");
ws_client.subscribe_book(instrument_id).await?;
}
"orderbook25" | "book25" => {
log::info!("Subscribing to order book L2_25 for {instrument_id}");
ws_client.subscribe_book_25(instrument_id).await?;
}
"depth10" | "book10" => {
log::info!("Subscribing to order book depth 10 for {instrument_id}");
ws_client.subscribe_book_depth10(instrument_id).await?;
}
"bars" => {
let bar_type = BarType::from(format!("{symbol}.BITMEX-1-MINUTE-LAST-EXTERNAL"));
log::info!("Subscribing to bars: {bar_type}");
ws_client.subscribe_bars(bar_type).await?;
}
"funding" => {
log::info!("Subscribing to funding rates");
log::warn!("Funding rate subscription may not be implemented yet");
}
"liquidation" => {
log::info!("Subscribing to liquidations");
log::warn!("Liquidation subscription may not be implemented yet");
}
"all" => {
log::info!("Subscribing to all available data types for {instrument_id}",);
log::info!("- Subscribing to quotes");
if let Err(e) = ws_client.subscribe_quotes(instrument_id).await {
log::error!("Failed to subscribe to quotes: {e}");
} else {
log::info!(" ✓ Quotes subscription successful");
}
tokio::time::sleep(Duration::from_millis(100)).await;
log::info!("- Subscribing to trades");
if let Err(e) = ws_client.subscribe_trades(instrument_id).await {
log::error!("Failed to subscribe to trades: {e}");
} else {
log::info!(" ✓ Trades subscription successful");
}
tokio::time::sleep(Duration::from_millis(100)).await;
log::info!("- Subscribing to order book L2");
if let Err(e) = ws_client.subscribe_book(instrument_id).await {
log::error!("Failed to subscribe to order book: {e}");
} else {
log::info!(" ✓ Order book L2 subscription successful");
}
tokio::time::sleep(Duration::from_millis(100)).await;
log::info!("- Subscribing to order book L2_25");
if let Err(e) = ws_client.subscribe_book_25(instrument_id).await {
log::error!("Failed to subscribe to order book 25: {e}");
} else {
log::info!(" ✓ Order book L2_25 subscription successful");
}
tokio::time::sleep(Duration::from_millis(100)).await;
log::info!("- Subscribing to order book depth 10");
if let Err(e) = ws_client.subscribe_book_depth10(instrument_id).await {
log::error!("Failed to subscribe to depth 10: {e}");
} else {
log::info!(" ✓ Order book depth 10 subscription successful");
}
tokio::time::sleep(Duration::from_millis(100)).await;
let bar_type = BarType::from(format!("{symbol}.BITMEX-1-MINUTE-LAST-EXTERNAL"));
log::info!("- Subscribing to bars: {bar_type}");
if let Err(e) = ws_client.subscribe_bars(bar_type).await {
log::error!("Failed to subscribe to bars: {e}");
} else {
log::info!(" ✓ Bars subscription successful");
}
}
_ => {
log::error!("Unknown subscription type: {subscription_type}");
log::info!(
"Available types: quotes, trades, orderbook, orderbook25, depth10, bars, funding, liquidation, all"
);
return Ok(());
}
}
log::info!("Subscriptions completed, waiting for data...");
log::info!("Press CTRL+C to stop");
let sigint = tokio::signal::ctrl_c();
tokio::pin!(sigint);
let stream = ws_client.stream();
tokio::pin!(stream);
let mut should_close = false;
let mut message_count = 0u64;
loop {
tokio::select! {
Some(msg) = stream.next() => {
message_count += 1;
log::info!("[Message #{message_count}] {msg:?}");
}
_ = &mut sigint => {
log::info!("Received SIGINT, closing connection...");
should_close = true;
break;
}
else => {
log::warn!("Stream ended unexpectedly");
break;
}
}
}
if should_close {
log::info!("Total messages received: {message_count}");
ws_client.close().await?;
log::info!("Connection closed successfully");
}
Ok(())
}