ndaxrs 0.1.0

Rust client library for the NDAX cryptocurrency exchange API
Documentation
// Copyright (C) 2026 ndaxrs Art Morozov
// SPDX-License-Identifier: GPL-3.0-only

//! Public Market Data Example
//!
//! This example demonstrates how to use the ndaxrs crate to connect to the NDAX
//! WebSocket API without authentication and subscribe to public market data.
//!
//! Run with: cargo run --example public_data

use std::{
  thread,
  time::{Duration, Instant},
};

use ndaxrs::ws::{NdaxWsAPI, NdaxWsConfig};

fn main() {
  // Step 1: Initialize logging
  // This enables log output from the library. Set RUST_LOG=debug for more
  // detail.
  env_logger::init();
  log::info!("Starting NDAX public data example");

  // Step 2: Create configuration for the WebSocket connection
  // Since we're only accessing public data, we don't need credentials.
  // We subscribe to Level2 data for BTC/CAD (instrument_id=1) initially.
  let config = NdaxWsConfig::builder()
        .subscribe_level2(vec![1]) // Subscribe to order book updates for BTC/CAD
        .book_depth(10) // Keep top 10 price levels
        .build();

  log::info!("Connecting to NDAX WebSocket...");

  // Step 3: Connect to the NDAX WebSocket
  // This establishes the connection and automatically subscribes to configured
  // feeds.
  let api = match NdaxWsAPI::new(config) {
    Ok(api) => {
      log::info!("Successfully connected to NDAX WebSocket");
      api
    }
    Err(e) => {
      eprintln!("Error: Failed to connect to NDAX: {}", e);
      eprintln!("Make sure you have network connectivity.");
      return;
    }
  };

  // Step 4: Get the list of available instruments
  log::info!("Fetching available instruments...");
  match api.get_instruments() {
    Ok(instruments) => {
      println!("\n=== Available Trading Pairs ===");
      for inst in &instruments {
        println!(
          "  ID {}: {} ({})",
          inst.instrument_id,
          inst.trading_pair(),
          inst.symbol
        );
      }
      println!("Total: {} instruments\n", instruments.len());

      // Find BTC/CAD instrument
      if let Some(btc_cad) =
        instruments.iter().find(|i| i.symbol.contains("BTCCAD"))
      {
        log::info!(
          "Found BTC/CAD: instrument_id={}, qty_increment={}, \
           price_increment={}",
          btc_cad.instrument_id,
          btc_cad.quantity_increment,
          btc_cad.price_increment
        );
      }
    }
    Err(e) => {
      log::warn!("Failed to fetch instruments: {}", e);
      log::info!("Continuing with default instrument_id=1 for BTC/CAD");
    }
  }

  // Step 5: Allow some time for initial order book data to arrive
  let btc_cad_instrument_id: u64 = 1;
  log::info!("Waiting for initial order book data...");
  thread::sleep(Duration::from_secs(2));

  // Step 6: Loop for 30 seconds, printing order book updates
  log::info!("Starting 30-second market data loop for BTC/CAD...");
  log::info!("Press Ctrl+C to stop early.");

  let start_time = Instant::now();
  let duration = Duration::from_secs(30);
  let mut last_mid_price = None;

  while start_time.elapsed() < duration {
    // Check if connection is still alive
    if api.is_closed() {
      log::error!("WebSocket connection closed unexpectedly");
      break;
    }

    // Get the current order book for BTC/CAD
    if let Some(book) = api.get_book(btc_cad_instrument_id) {
      let best_bid = book.best_bid();
      let best_ask = book.best_ask();
      let mid_price = book.mid_price();
      let spread = book.spread();

      // Only print if there's actual data and price changed
      if (best_bid.is_some() || best_ask.is_some())
        && mid_price != last_mid_price
      {
        println!("=== BTC/CAD Order Book ===");

        if let Some((bid_price, bid_qty)) = best_bid {
          println!("  Best Bid: {} CAD (qty: {})", bid_price, bid_qty);
        } else {
          println!("  Best Bid: No bids");
        }

        if let Some((ask_price, ask_qty)) = best_ask {
          println!("  Best Ask: {} CAD (qty: {})", ask_price, ask_qty);
        } else {
          println!("  Best Ask: No asks");
        }

        if let Some(mid) = mid_price {
          println!("  Mid Price: {} CAD", mid);
        }

        if let Some(s) = spread {
          println!("  Spread: {} CAD", s);
        }

        let time_remaining = duration.saturating_sub(start_time.elapsed());
        println!(
          "  ({} bid levels, {} ask levels, {}s remaining)",
          book.bids.len(),
          book.asks.len(),
          time_remaining.as_secs()
        );
        println!();

        last_mid_price = mid_price;
      }
    }

    // Small sleep to avoid busy-waiting
    thread::sleep(Duration::from_millis(500));
  }

  // Step 7: Gracefully disconnect
  log::info!("Closing WebSocket connection...");
  api.close();
  log::info!("Disconnected. Example complete.");
}