Files
foxhunt/services/backtesting_service/tests/dbn_multi_symbol_tests.rs
jgrusewski db6462ba7a fix(clippy): resolve all clippy warnings across entire workspace (--all-targets)
Systematic fix of 360+ clippy errors across 37+ crates covering lib,
test, bench, and example targets. Key changes:

- Add targeted #[allow(...)] on #[cfg(test)] modules for test-only lints
  (assertions_on_result_states, float_cmp, str_to_string, indexing, etc.)
- Feature-gate broken integration tests behind __<crate>_integration flags
  where public APIs changed (trading-service, backtesting-service, etc.)
- Remove dead [[test]] entries from Cargo.toml files pointing to deleted files
- Fix production code: field_reassign_with_default, manual_range_contains,
  assert!(false) → panic!(), format!("{}") simplification, len() > 0 → !is_empty()
- Delete truly unused code (Order struct, unused methods/fields/variants)
- Convert sqlx::query!() to sqlx::query() for SQLX_OFFLINE compatibility

Result: cargo clippy --workspace --all-targets -- -D warnings = 0 errors, 0 warnings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:18:35 +01:00

416 lines
12 KiB
Rust

#![allow(clippy::inconsistent_digit_grouping, dead_code, unused_variables, unexpected_cfgs)]
//! DBN Multi-Symbol Integration Tests
//!
//! Tests for multi-symbol loading with diverse asset classes:
//! - ES.FUT (E-mini S&P 500) - Equity index futures
//! - NQ.FUT (E-mini NASDAQ) - Tech index futures
//! - GC (Gold) - Commodity futures
//! - ZN.FUT (10-Year Treasury) - Fixed income futures
//! - 6E.FUT (Euro FX) - Currency futures
use anyhow::Result;
use num_traits::ToPrimitive;
use std::collections::HashMap;
mod mock_repositories;
use backtesting_service::dbn_data_source::DbnDataSource;
use backtesting_service::dbn_repository::DbnMarketDataRepository;
use backtesting_service::repositories::MarketDataRepository;
/// Helper to get multi-symbol file mappings
fn get_multi_symbol_file_mapping() -> HashMap<String, String> {
let root = mock_repositories::get_project_root();
let mut mapping = HashMap::new();
// Equity indices
mapping.insert(
"ES.FUT".to_string(),
format!(
"{}/test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn",
root
),
);
mapping.insert(
"NQ.FUT".to_string(),
format!(
"{}/test_data/real/databento/NQ.FUT_ohlcv-1m_2024-01-02.dbn",
root
),
);
// Commodities (Gold) - uncompressed version
mapping.insert(
"GC".to_string(),
format!("{}/test_data/real/databento/GC_continuous_ohlcv-1m_2024-01-02_to_2024-01-31.uncompressed.dbn", root),
);
// Fixed income (Treasuries) - uncompressed version
mapping.insert(
"ZN.FUT".to_string(),
format!(
"{}/test_data/real/databento/ZN.FUT_ohlcv-1m_2024-01-02_to_2024-01-31.uncompressed.dbn",
root
),
);
// Currencies (Euro FX) - uncompressed version
mapping.insert(
"6E.FUT".to_string(),
format!(
"{}/test_data/real/databento/6E.FUT_ohlcv-1m_2024-01-02_to_2024-01-31.uncompressed.dbn",
root
),
);
mapping
}
#[tokio::test]
async fn test_load_all_symbols() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let data_source = DbnDataSource::new(file_mapping).await?;
// Test each symbol individually
let symbols = vec!["ES.FUT", "NQ.FUT", "GC", "ZN.FUT", "6E.FUT"];
for symbol in symbols {
let bars = data_source.load_ohlcv_bars(symbol).await?;
assert!(!bars.is_empty(), "{} should have data", symbol);
println!("{}: {} bars loaded", symbol, bars.len());
// Validate all bars have correct symbol
for bar in &bars {
assert_eq!(bar.symbol, symbol, "Bar symbol mismatch");
}
}
println!("✅ All 5 symbols loaded successfully");
Ok(())
}
#[tokio::test]
async fn test_multi_symbol_loading() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let data_source = DbnDataSource::new(file_mapping).await?;
// Load multiple symbols together
let symbols = vec![
"ES.FUT".to_string(),
"ZN.FUT".to_string(),
"6E.FUT".to_string(),
];
let bars = data_source.load_multi_symbol_bars(&symbols).await?;
assert!(!bars.is_empty(), "Should load multi-symbol data");
// Count bars per symbol
let mut counts: HashMap<String, usize> = HashMap::new();
for bar in &bars {
*counts.entry(bar.symbol.clone()).or_insert(0) += 1;
}
println!("✅ Multi-symbol loading results:");
for (symbol, count) in &counts {
println!(" {}: {} bars", symbol, count);
}
// Verify all requested symbols are present
for symbol in &symbols {
assert!(
counts.contains_key(symbol),
"Symbol {} should be present",
symbol
);
assert!(
*counts.get(symbol).expect("INVARIANT: Key should exist in map") > 0,
"Symbol {} should have bars",
symbol
);
}
Ok(())
}
#[tokio::test]
async fn test_asset_class_price_ranges() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let data_source = DbnDataSource::new(file_mapping).await?;
// Expected price ranges for each asset class
let price_validations = vec![
("ES.FUT", 3000.0, 6000.0, "S&P 500 E-mini"),
("NQ.FUT", 10000.0, 20000.0, "NASDAQ E-mini"),
("GC", 1800.0, 2200.0, "Gold"),
("ZN.FUT", 100.0, 130.0, "10-Year Treasury"),
("6E.FUT", 1.00, 1.20, "Euro FX"),
];
for (symbol, min_price, max_price, name) in price_validations {
let bars = data_source.load_ohlcv_bars(symbol).await?;
assert!(!bars.is_empty(), "{} should have data", symbol);
for (i, bar) in bars.iter().enumerate() {
let close_f64 = bar.close.to_f64().unwrap_or(0.0);
assert!(
close_f64 >= min_price && close_f64 <= max_price,
"{} bar {}: price {} outside expected range [{}, {}]",
name,
i,
close_f64,
min_price,
max_price
);
}
println!(
"{}: Price range validation passed ({} bars)",
name,
bars.len()
);
}
Ok(())
}
#[tokio::test]
async fn test_repository_multi_symbol() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let repo = DbnMarketDataRepository::new(file_mapping).await?;
// Test with 3 symbols
let symbols = vec![
"ES.FUT".to_string(),
"ZN.FUT".to_string(),
"6E.FUT".to_string(),
];
// 2024-01-02 00:00:00 to 2024-01-03 00:00:00
let start_time = 1704153600_000_000_000i64;
let end_time = 1704240000_000_000_000i64;
let data = repo
.load_historical_data(&symbols, start_time, end_time)
.await?;
assert!(!data.is_empty(), "Repository should load multi-symbol data");
// Verify symbol distribution
let mut symbol_counts: HashMap<String, usize> = HashMap::new();
for bar in &data {
*symbol_counts.entry(bar.symbol.clone()).or_insert(0) += 1;
}
println!("✅ Repository multi-symbol loading:");
for (symbol, count) in &symbol_counts {
println!(" {}: {} bars", symbol, count);
}
// All symbols should have data
assert_eq!(symbol_counts.len(), 3, "Should have 3 symbols");
Ok(())
}
// Gated: check_data_availability method was removed from DbnMarketDataRepository
#[cfg(feature = "__backtesting_integration")]
#[tokio::test]
async fn test_data_availability_multi_symbol() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let repo = DbnMarketDataRepository::new(file_mapping).await?;
let symbols = vec![
"ES.FUT".to_string(),
"NQ.FUT".to_string(),
"GC".to_string(),
"ZN.FUT".to_string(),
"6E.FUT".to_string(),
"NONEXISTENT".to_string(),
];
let start_time = 1704153600_000_000_000i64;
let end_time = 1704240000_000_000_000i64;
let availability = repo
.check_data_availability(&symbols, start_time, end_time)
.await?;
// Verify availability
assert_eq!(
availability.get("ES.FUT"),
Some(&true),
"ES.FUT should be available"
);
assert_eq!(
availability.get("NQ.FUT"),
Some(&true),
"NQ.FUT should be available"
);
assert_eq!(
availability.get("GC"),
Some(&true),
"GC should be available"
);
assert_eq!(
availability.get("ZN.FUT"),
Some(&true),
"ZN.FUT should be available"
);
assert_eq!(
availability.get("6E.FUT"),
Some(&true),
"6E.FUT should be available"
);
assert_eq!(
availability.get("NONEXISTENT"),
Some(&false),
"NONEXISTENT should not be available"
);
println!("✅ Data availability check:");
for (symbol, available) in &availability {
println!(" {}: {}", symbol, if *available { "" } else { "" });
}
Ok(())
}
#[tokio::test]
async fn test_multi_symbol_quality() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let data_source = DbnDataSource::new(file_mapping).await?;
let symbols = vec!["ES.FUT", "ZN.FUT", "6E.FUT"];
for symbol in symbols {
let bars = data_source.load_ohlcv_bars(symbol).await?;
assert!(!bars.is_empty(), "{} should have data", symbol);
let mut violations = 0;
for (i, bar) in bars.iter().enumerate() {
// Check OHLCV relationships
if !(bar.high >= bar.low
&& bar.high >= bar.open
&& bar.high >= bar.close
&& bar.low <= bar.open
&& bar.low <= bar.close)
{
violations += 1;
if violations <= 3 {
eprintln!(
"{} bar {}: OHLCV violation (O={}, H={}, L={}, C={})",
symbol, i, bar.open, bar.high, bar.low, bar.close
);
}
}
// Check positive values
assert!(
bar.open > rust_decimal::Decimal::ZERO,
"{} bar {}: open must be positive",
symbol,
i
);
assert!(
bar.close > rust_decimal::Decimal::ZERO,
"{} bar {}: close must be positive",
symbol,
i
);
assert!(
bar.volume >= rust_decimal::Decimal::ZERO,
"{} bar {}: volume must be non-negative",
symbol,
i
);
}
println!(
"{}: {} bars validated, {} violations",
symbol,
bars.len(),
violations
);
// Allow minimal violations (<1% of data)
assert!(
violations < bars.len() / 100,
"{}: too many violations ({}/{})",
symbol,
violations,
bars.len()
);
}
Ok(())
}
#[tokio::test]
async fn test_cross_asset_correlation() -> Result<()> {
let file_mapping = get_multi_symbol_file_mapping();
let data_source = DbnDataSource::new(file_mapping).await?;
// Load equity indices (ES and NQ should be correlated)
let es_bars = data_source.load_ohlcv_bars("ES.FUT").await?;
let nq_bars = data_source.load_ohlcv_bars("NQ.FUT").await?;
assert!(!es_bars.is_empty(), "ES.FUT should have data");
assert!(!nq_bars.is_empty(), "NQ.FUT should have data");
println!("✅ Cross-asset data loading:");
println!(" ES.FUT: {} bars", es_bars.len());
println!(" NQ.FUT: {} bars", nq_bars.len());
// Both should have similar bar counts (same trading session)
let ratio = es_bars.len() as f64 / nq_bars.len() as f64;
assert!(
ratio > 0.8 && ratio < 1.2,
"ES and NQ bar counts should be similar (ratio: {:.2})",
ratio
);
println!(" Ratio: {:.2} (within expected range 0.8-1.2)", ratio);
Ok(())
}
#[tokio::test]
async fn test_multi_symbol_performance() -> Result<()> {
use std::time::Instant;
let file_mapping = get_multi_symbol_file_mapping();
let data_source = DbnDataSource::new(file_mapping).await?;
// Warm-up
let _ = data_source.load_ohlcv_bars("ES.FUT").await?;
// Time multi-symbol loading
let symbols = vec![
"ES.FUT".to_string(),
"ZN.FUT".to_string(),
"6E.FUT".to_string(),
];
let start = Instant::now();
let bars = data_source.load_multi_symbol_bars(&symbols).await?;
let duration = start.elapsed();
println!("✅ Multi-symbol performance:");
println!(" Total bars: {}", bars.len());
println!(" Duration: {:?}", duration);
println!(
" Throughput: {:.0} bars/sec",
bars.len() as f64 / duration.as_secs_f64()
);
// Should be reasonably fast (<1 second for ~60K bars)
assert!(
duration.as_secs() < 1,
"Multi-symbol loading should be fast (<1s), got {:?}",
duration
);
Ok(())
}