#![cfg(feature = "databento")]
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use rustrade_data::exchange::databento::{load_quotes_from_dbn, load_trades_from_dbn};
use rustrade_instrument::exchange::ExchangeId;
use std::path::Path;
const FIXTURES_DIR: &str = "tests/fixtures/databento";
#[test]
fn test_load_trades_from_dbn_fixture() {
let path = Path::new(FIXTURES_DIR).join("es_trades_sample.dbn.zst");
if !path.exists() {
eprintln!(
"Skipping test: fixture not found at {}. Run download_databento_fixtures example.",
path.display()
);
return;
}
let trades: Vec<_> = load_trades_from_dbn(&path, ExchangeId::DatabentoGlbx, "ESM4")
.expect("Failed to open DBN file")
.collect();
assert!(!trades.is_empty(), "Expected at least one trade record");
let (successes, failures): (Vec<_>, Vec<_>) = trades.into_iter().partition(|r| r.is_ok());
println!(
"Loaded {} trades successfully, {} failed",
successes.len(),
failures.len()
);
assert!(
!successes.is_empty(),
"Expected at least one valid trade record"
);
let first_trade = successes.into_iter().next().unwrap().unwrap();
assert_eq!(first_trade.exchange, ExchangeId::DatabentoGlbx);
assert_eq!(first_trade.instrument, "ESM4");
assert!(
first_trade.kind.price > Decimal::ZERO,
"Price should be positive"
);
assert!(
first_trade.kind.amount > Decimal::ZERO,
"Amount should be positive"
);
assert!(
first_trade.kind.price > dec!(1000) && first_trade.kind.price < dec!(10000),
"ES price {} outside expected range",
first_trade.kind.price
);
}
#[test]
fn test_load_quotes_from_dbn_fixture() {
let path = Path::new(FIXTURES_DIR).join("es_quotes_sample.dbn.zst");
if !path.exists() {
eprintln!(
"Skipping test: fixture not found at {}. Run download_databento_fixtures example.",
path.display()
);
return;
}
let quotes: Vec<_> = load_quotes_from_dbn(&path, ExchangeId::DatabentoGlbx, "ESM4")
.expect("Failed to open DBN file")
.collect();
assert!(!quotes.is_empty(), "Expected at least one quote record");
let (successes, failures): (Vec<_>, Vec<_>) = quotes.into_iter().partition(|r| r.is_ok());
println!(
"Loaded {} quotes successfully, {} failed",
successes.len(),
failures.len()
);
assert!(
!successes.is_empty(),
"Expected at least one valid quote record"
);
let first_quote = successes.into_iter().next().unwrap().unwrap();
assert_eq!(first_quote.exchange, ExchangeId::DatabentoGlbx);
assert_eq!(first_quote.instrument, "ESM4");
let quote = &first_quote.kind;
assert!(
quote.bid_price > Decimal::ZERO,
"Bid price should be positive"
);
assert!(
quote.ask_price > Decimal::ZERO,
"Ask price should be positive"
);
assert!(
quote.ask_price >= quote.bid_price,
"Ask should be >= bid, got bid={} ask={}",
quote.bid_price,
quote.ask_price
);
assert!(
quote.bid_price > dec!(1000) && quote.bid_price < dec!(10000),
"ES bid {} outside expected range",
quote.bid_price
);
}
#[test]
fn test_trade_timestamp_ordering() {
let path = Path::new(FIXTURES_DIR).join("es_trades_sample.dbn.zst");
if !path.exists() {
return;
}
let trades: Vec<_> = load_trades_from_dbn(&path, ExchangeId::DatabentoGlbx, "ESM4")
.expect("Failed to open DBN file")
.filter_map(|r| r.ok())
.take(100) .collect();
for window in trades.windows(2) {
assert!(
window[1].time_exchange >= window[0].time_exchange,
"Timestamps should be monotonically increasing"
);
}
}
#[test]
fn test_quote_spread_is_reasonable() {
let path = Path::new(FIXTURES_DIR).join("es_quotes_sample.dbn.zst");
if !path.exists() {
return;
}
let quotes: Vec<_> = load_quotes_from_dbn(&path, ExchangeId::DatabentoGlbx, "ESM4")
.expect("Failed to open DBN file")
.filter_map(|r| r.ok())
.take(100)
.collect();
for quote in "es {
let spread = quote.kind.ask_price - quote.kind.bid_price;
assert!(
spread >= Decimal::ZERO && spread < dec!(10),
"Spread {} is unreasonable for ES futures",
spread
);
}
}