use std::{thread, time::Duration};
use ndaxrs::{
ws::{NdaxWsAPI, NdaxWsConfig, PrivateConfig},
NdaxCredentials,
};
#[test]
#[ignore]
fn test_websocket_connect() {
let config = NdaxWsConfig::builder().build();
let api =
NdaxWsAPI::new(config).expect("Failed to connect to NDAX WebSocket");
thread::sleep(Duration::from_secs(2));
assert!(
!api.is_closed(),
"WebSocket connection should still be open"
);
api.close();
}
#[test]
#[ignore]
fn test_get_instruments() {
let config = NdaxWsConfig::builder().build();
let api = NdaxWsAPI::new(config).expect("Failed to connect to NDAX");
let instruments = api.get_instruments().expect("Failed to get instruments");
assert!(
!instruments.is_empty(),
"Should receive at least one instrument"
);
let btc_cad = instruments.iter().find(|i| i.symbol.contains("BTCCAD"));
assert!(btc_cad.is_some(), "BTC/CAD instrument should exist on NDAX");
let btc_cad = btc_cad.unwrap();
println!(
"Found BTC/CAD: instrument_id={}, symbol={}",
btc_cad.instrument_id, btc_cad.symbol
);
println!(
" Base: {} (ID {})",
btc_cad.product1_symbol, btc_cad.product1
);
println!(
" Quote: {} (ID {})",
btc_cad.product2_symbol, btc_cad.product2
);
println!(" Session: {}", btc_cad.session_status);
assert_eq!(btc_cad.trading_pair(), "BTC/CAD");
assert_eq!(btc_cad.base_symbol(), "BTC");
assert_eq!(btc_cad.quote_symbol(), "CAD");
api.close();
}
#[test]
#[ignore]
fn test_subscribe_level2() {
let btc_cad_instrument_id = 1;
let config = NdaxWsConfig::builder()
.subscribe_level2(vec![btc_cad_instrument_id])
.book_depth(10)
.build();
let api = NdaxWsAPI::new(config).expect("Failed to connect to NDAX");
let max_wait = Duration::from_secs(10);
let start = std::time::Instant::now();
let mut received_data = false;
while start.elapsed() < max_wait {
if let Some(book) = api.get_book(btc_cad_instrument_id) {
if !book.bids.is_empty() || !book.asks.is_empty() {
received_data = true;
println!(
"Received Level2 data for instrument {}",
btc_cad_instrument_id
);
println!(" Bids: {} levels", book.bids.len());
println!(" Asks: {} levels", book.asks.len());
if let Some((price, qty)) = book.best_bid() {
println!(" Best bid: {} @ {}", qty, price);
}
if let Some((price, qty)) = book.best_ask() {
println!(" Best ask: {} @ {}", qty, price);
}
if let Some(spread) = book.spread() {
println!(" Spread: {}", spread);
}
if let Some(mid) = book.mid_price() {
println!(" Mid price: {}", mid);
}
break;
}
}
thread::sleep(Duration::from_millis(500));
}
assert!(
received_data,
"Should have received Level2 data within {} seconds",
max_wait.as_secs()
);
api.close();
}
#[test]
#[ignore]
fn test_subscribe_level1() {
let btc_cad_instrument_id = 1;
let config = NdaxWsConfig::builder()
.subscribe_level1(vec![btc_cad_instrument_id])
.build();
let api = NdaxWsAPI::new(config).expect("Failed to connect to NDAX");
let max_wait = Duration::from_secs(10);
let start = std::time::Instant::now();
let mut received_data = false;
while start.elapsed() < max_wait {
if let Some(ticker) = api.get_level1(btc_cad_instrument_id) {
if ticker.best_bid > rust_decimal::Decimal::ZERO
|| ticker.best_ask > rust_decimal::Decimal::ZERO
{
received_data = true;
println!(
"Received Level1 data for instrument {}",
btc_cad_instrument_id
);
println!(" Best Bid: {}", ticker.best_bid);
println!(" Best Ask: {}", ticker.best_ask);
println!(" Last Price: {}", ticker.last_price);
println!(" Volume: {}", ticker.volume);
println!(" Mid Price: {}", ticker.mid_price());
println!(" Spread: {}", ticker.spread());
break;
}
}
thread::sleep(Duration::from_millis(500));
}
assert!(
received_data,
"Should have received Level1 data within {} seconds",
max_wait.as_secs()
);
api.close();
}
#[test]
#[ignore]
fn test_authenticate() {
let credentials = match NdaxCredentials::from_env() {
Ok(c) => c,
Err(_) => {
eprintln!("Skipping auth test: no credentials available");
eprintln!(
"Set NDAX_API_KEY, NDAX_API_SECRET, NDAX_USER_ID to run this test"
);
return;
}
};
println!(
"Testing authentication with user_id: {}",
credentials.user_id
);
let config = NdaxWsConfig::builder().credentials(credentials).build();
let api = match NdaxWsAPI::new(config) {
Ok(api) => api,
Err(e) => {
panic!("Authentication failed: {}", e);
}
};
assert!(
api.is_authenticated(),
"Should be authenticated after successful connection"
);
println!("Authentication successful!");
api.close();
}
#[test]
#[ignore]
fn test_get_account_positions() {
let credentials = match NdaxCredentials::from_env() {
Ok(c) => c,
Err(_) => {
eprintln!("Skipping test: no credentials available");
return;
}
};
let account_id: u64 = match credentials.user_id.parse() {
Ok(id) => id,
Err(_) => {
eprintln!("Skipping test: invalid user_id format");
return;
}
};
let config = NdaxWsConfig::builder()
.credentials(credentials)
.private(PrivateConfig::new(account_id).with_account_events())
.build();
let api = NdaxWsAPI::new(config).expect("Failed to connect to NDAX");
let positions = api
.get_account_positions(account_id)
.expect("Failed to get account positions");
println!(
"Account {} has {} position(s):",
account_id,
positions.len()
);
for pos in &positions {
let available = pos.amount - pos.hold;
println!(
" {}: Total={}, Available={}, Hold={}",
pos.product_symbol, pos.amount, available, pos.hold
);
}
for pos in &positions {
assert!(!pos.product_symbol.is_empty());
assert_eq!(pos.account_id, account_id);
}
api.close();
}
#[test]
#[ignore]
fn test_place_and_cancel_order() {
use ndaxrs::{
messages::orders::{create_limit_order, CancelOrderRequest},
Side,
};
use rust_decimal_macros::dec;
let credentials = match NdaxCredentials::from_env() {
Ok(c) => c,
Err(_) => {
eprintln!("Skipping test: no credentials available");
return;
}
};
let account_id: u64 = match credentials.user_id.parse() {
Ok(id) => id,
Err(_) => {
eprintln!("Skipping test: invalid user_id format");
return;
}
};
let config = NdaxWsConfig::builder()
.credentials(credentials)
.private(PrivateConfig::new(account_id).with_account_events())
.build();
let api = NdaxWsAPI::new(config).expect("Failed to connect to NDAX");
let btc_cad_instrument_id = 1;
let order = create_limit_order(
btc_cad_instrument_id,
account_id,
Side::Buy,
dec!(0.0001), dec!(1.00), );
println!("Placing test order: Buy 0.0001 BTC @ $1 CAD");
let response = api.send_order(&order).expect("Failed to send order");
println!("Order response: status={}", response.status);
let order_id = match response.order_id {
Some(id) => {
println!("Order placed successfully, order_id={}", id);
id
}
None => {
if response.status == "Rejected" {
println!("Order rejected: {}", response.errormsg.unwrap_or_default());
api.close();
return;
}
panic!("No order ID returned for accepted order");
}
};
thread::sleep(Duration::from_secs(1));
println!("Canceling order {}", order_id);
let cancel_request = CancelOrderRequest {
oms_id: 1,
account_id,
order_id: Some(order_id),
client_order_id: None,
};
let cancel_response = api
.cancel_order(&cancel_request)
.expect("Failed to cancel order");
println!("Cancel response: result={}", cancel_response.result);
thread::sleep(Duration::from_secs(1));
let open_orders = api
.get_open_orders(account_id)
.expect("Failed to get open orders");
let order_still_exists = open_orders.iter().any(|o| o.order_id == order_id);
assert!(
!order_still_exists,
"Order {} should have been canceled",
order_id
);
println!("Test completed successfully - order placed and canceled");
api.close();
}
#[test]
#[ignore]
fn test_invalid_url_fails() {
let config = NdaxWsConfig::builder()
.ws_url("wss://invalid.example.com:12345/WSGateway/")
.build();
let result = NdaxWsAPI::new(config);
assert!(result.is_err(), "Connection to invalid URL should fail");
}
#[test]
#[ignore]
fn test_invalid_instrument_subscription() {
let invalid_instrument_id = 999999;
let config = NdaxWsConfig::builder()
.subscribe_level2(vec![invalid_instrument_id])
.build();
let api = NdaxWsAPI::new(config).expect("Connection should succeed");
thread::sleep(Duration::from_secs(2));
let book = api.get_book(invalid_instrument_id);
if let Some(book) = book {
println!(
"Invalid instrument returned book with {} bids, {} asks",
book.bids.len(),
book.asks.len()
);
} else {
println!("Invalid instrument returned None as expected");
}
api.close();
}
#[test]
#[ignore]
fn test_multiple_instrument_subscriptions() {
let instruments = vec![1, 4, 8];
let config = NdaxWsConfig::builder()
.subscribe_level2(instruments.clone())
.book_depth(5)
.build();
let api = NdaxWsAPI::new(config).expect("Failed to connect to NDAX");
thread::sleep(Duration::from_secs(5));
let mut found_data = false;
for &id in &instruments {
if let Some(book) = api.get_book(id) {
if !book.bids.is_empty() || !book.asks.is_empty() {
println!(
"Instrument {}: {} bids, {} asks",
id,
book.bids.len(),
book.asks.len()
);
found_data = true;
}
}
}
assert!(
found_data,
"Should have received data for at least one instrument"
);
api.close();
}