Files
foxhunt/services/trading_agent_service/tests/universe_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

924 lines
28 KiB
Rust

//! Integration tests for universe selection module
use trading_agent_service::universe::{AssetClass, Region, UniverseCriteria, UniverseSelector};
#[tokio::test]
async fn test_select_universe_with_default_criteria() {
// Setup database connection
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
// Run migrations
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
let selector = UniverseSelector::new(pool);
// Test with default criteria
let criteria = UniverseCriteria::default();
let result = selector.select_universe(criteria).await;
assert!(
result.is_ok(),
"Universe selection should succeed with default criteria"
);
let universe = result.expect("Universe should be present");
// Verify universe properties
assert!(!universe.universe_id.is_empty());
assert!(!universe.instruments.is_empty(), "Should have instruments");
assert_eq!(
universe.metrics.total_instruments,
universe.instruments.len()
);
assert!(universe.metrics.avg_liquidity_score > 0.0);
assert!(universe.metrics.avg_volatility > 0.0);
}
#[tokio::test]
async fn test_select_universe_with_high_liquidity() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with high liquidity requirement
let criteria = UniverseCriteria {
min_liquidity: 0.90, // Only ES.FUT, NQ.FUT, CL.FUT qualify
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// All instruments should have liquidity >= 0.90
for instrument in &universe.instruments {
assert!(
instrument.liquidity_score >= 0.90,
"Instrument {} has liquidity {} which is below threshold",
instrument.symbol,
instrument.liquidity_score
);
}
}
#[tokio::test]
async fn test_select_universe_with_low_volatility() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with low volatility requirement
let criteria = UniverseCriteria {
max_volatility: 0.20, // Only ES.FUT, ZN.FUT, 6E.FUT qualify
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// All instruments should have volatility <= 0.20
for instrument in &universe.instruments {
assert!(
instrument.volatility <= 0.20,
"Instrument {} has volatility {} which exceeds threshold",
instrument.symbol,
instrument.volatility
);
}
}
#[tokio::test]
async fn test_select_universe_by_asset_class() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with currencies only
let criteria = UniverseCriteria {
asset_classes: vec![AssetClass::Currencies],
regions: vec![Region::Global], // 6E.FUT is in Global region
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// Should only include 6E.FUT
assert_eq!(universe.instruments.len(), 1);
assert_eq!(universe.instruments[0].symbol.as_str(), "6E.FUT");
}
#[tokio::test]
async fn test_select_universe_by_region() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with Global region only
let criteria = UniverseCriteria {
regions: vec![Region::Global],
asset_classes: vec![
AssetClass::Futures,
AssetClass::Currencies,
AssetClass::Commodities,
],
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// Should only include instruments from Global region
for instrument in &universe.instruments {
assert_eq!(instrument.region, Region::Global);
}
}
#[tokio::test]
async fn test_get_universe_by_id() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Create universe
let criteria = UniverseCriteria::default();
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to create universe");
// Retrieve universe by ID
let retrieved = selector.get_universe(&universe.universe_id).await;
assert!(retrieved.is_ok());
let retrieved_universe = retrieved.expect("Universe should be retrieved");
assert_eq!(retrieved_universe.universe_id, universe.universe_id);
assert_eq!(
retrieved_universe.instruments.len(),
universe.instruments.len()
);
}
#[tokio::test]
async fn test_get_nonexistent_universe() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Try to get non-existent universe
let result = selector.get_universe("nonexistent_id").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_update_criteria() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Create universe
let criteria = UniverseCriteria::default();
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to create universe");
// Update criteria
let new_criteria = UniverseCriteria {
min_liquidity: 0.95, // Very high threshold
..UniverseCriteria::default()
};
let result = selector
.update_criteria(&universe.universe_id, new_criteria)
.await;
assert!(result.is_ok());
let updated_universe = result.expect("Update should succeed");
// New universe should have different ID (new universe created)
assert_ne!(updated_universe.universe_id, universe.universe_id);
// New universe should have fewer instruments (higher threshold)
assert!(updated_universe.instruments.len() <= universe.instruments.len());
}
#[tokio::test]
async fn test_universe_performance() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let start = std::time::Instant::now();
let criteria = UniverseCriteria::default();
let _universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
let duration = start.elapsed();
// Performance target: < 1 second
assert!(
duration.as_millis() < 1000,
"Universe selection took {}ms (target: <1000ms)",
duration.as_millis()
);
println!("Universe selection completed in {}ms", duration.as_millis());
}
#[tokio::test]
async fn test_invalid_criteria_min_liquidity() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria {
min_liquidity: 1.5, // Invalid (> 1.0)
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_invalid_criteria_max_volatility() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria {
max_volatility: -0.1, // Invalid (< 0.0)
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_no_instruments_match() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Set impossible criteria
let criteria = UniverseCriteria {
min_liquidity: 0.99, // Very high
max_volatility: 0.01, // Very low — no instrument can satisfy both
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_err());
}
// ============================================================================
// EDGE CASE TESTS
// ============================================================================
#[tokio::test]
async fn test_extreme_liquidity_threshold() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with extremely high liquidity threshold
let criteria = UniverseCriteria {
min_liquidity: 0.99, // Only instruments with 99%+ liquidity
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
// Should fail because no instruments meet this threshold
assert!(
result.is_err(),
"Should fail with extreme liquidity threshold"
);
}
#[tokio::test]
async fn test_minimal_liquidity_threshold() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with minimal liquidity threshold
let criteria = UniverseCriteria {
min_liquidity: 0.0, // Accept all instruments
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// Should include all instruments that pass other filters
assert!(
!universe.instruments.is_empty(),
"Should have at least one instrument"
);
}
#[tokio::test]
async fn test_single_symbol_universe() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Create criteria that only matches ES.FUT
let criteria = UniverseCriteria {
min_liquidity: 0.95, // Only ES.FUT has 0.95
max_volatility: 0.20, // ES.FUT has 0.20
asset_classes: vec![AssetClass::Futures],
regions: vec![Region::NorthAmerica],
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// Should only have ES.FUT
assert_eq!(universe.instruments.len(), 1);
assert_eq!(universe.instruments[0].symbol.as_str(), "ES.FUT");
}
#[tokio::test]
async fn test_multiple_asset_classes() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with multiple asset classes
let criteria = UniverseCriteria {
asset_classes: vec![
AssetClass::Futures,
AssetClass::Currencies,
AssetClass::Commodities,
],
regions: vec![Region::NorthAmerica, Region::Global],
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// Should have instruments from multiple asset classes
let mut asset_class_names = std::collections::HashSet::new();
for instrument in &universe.instruments {
asset_class_names.insert(format!("{:?}", instrument.asset_class));
}
assert!(
asset_class_names.len() >= 2,
"Should have instruments from multiple asset classes"
);
}
#[tokio::test]
async fn test_market_cap_filtering() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Test with high market cap threshold
let criteria = UniverseCriteria {
min_market_cap: Some(8_000_000_000.0), // $8B - only ES.FUT and NQ.FUT
..UniverseCriteria::default()
};
let result = selector.select_universe(criteria).await;
assert!(result.is_ok());
let universe = result.expect("Universe should be present");
// All instruments should have market cap >= $8B
for instrument in &universe.instruments {
if let Some(cap) = instrument.market_cap {
assert!(
cap >= 8_000_000_000.0,
"Instrument {} has market cap ${} which is below threshold",
instrument.symbol,
cap
);
}
}
// Should only have ES.FUT and NQ.FUT
assert!(universe.instruments.len() <= 2);
}
// ============================================================================
// DETERMINISM AND REPRODUCIBILITY TESTS
// ============================================================================
#[tokio::test]
async fn test_deterministic_results() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria::default();
// Run selection twice with same criteria
let universe1 = selector
.select_universe(criteria.clone())
.await
.expect("First selection failed");
let universe2 = selector
.select_universe(criteria.clone())
.await
.expect("Second selection failed");
// Results should be deterministic (same number of instruments)
assert_eq!(
universe1.instruments.len(),
universe2.instruments.len(),
"Results should be deterministic"
);
// Same symbols should be selected (in any order)
let symbols1: std::collections::HashSet<_> = universe1
.instruments
.iter()
.map(|i| i.symbol.as_str())
.collect();
let symbols2: std::collections::HashSet<_> = universe2
.instruments
.iter()
.map(|i| i.symbol.as_str())
.collect();
assert_eq!(symbols1, symbols2, "Same symbols should be selected");
}
#[tokio::test]
async fn test_reproducible_metrics() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria::default();
// Run selection twice
let universe1 = selector
.select_universe(criteria.clone())
.await
.expect("First selection failed");
let universe2 = selector
.select_universe(criteria.clone())
.await
.expect("Second selection failed");
// Metrics should be identical
assert_eq!(
universe1.metrics.total_instruments,
universe2.metrics.total_instruments
);
assert!(
(universe1.metrics.avg_liquidity_score - universe2.metrics.avg_liquidity_score).abs()
< 1e-10
);
assert!((universe1.metrics.avg_volatility - universe2.metrics.avg_volatility).abs() < 1e-10);
assert!((universe1.metrics.avg_spread_bps - universe2.metrics.avg_spread_bps).abs() < 1e-10);
}
// ============================================================================
// PERFORMANCE TESTS
// ============================================================================
#[tokio::test]
async fn test_performance_with_multiple_filters() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let start = std::time::Instant::now();
// Test with complex criteria
let criteria = UniverseCriteria {
min_liquidity: 0.85,
max_volatility: 0.30,
asset_classes: vec![
AssetClass::Futures,
AssetClass::Currencies,
AssetClass::Commodities,
],
regions: vec![Region::NorthAmerica, Region::Global],
min_market_cap: Some(4_000_000_000.0),
..UniverseCriteria::default()
};
let _universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
let duration = start.elapsed();
// Performance target: < 1 second
assert!(
duration.as_millis() < 1000,
"Universe selection with multiple filters took {}ms (target: <1000ms)",
duration.as_millis()
);
println!(
"Complex universe selection completed in {}ms",
duration.as_millis()
);
}
#[tokio::test]
async fn test_performance_sequential_selections() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let start = std::time::Instant::now();
// Run 10 sequential selections
for _ in 0..10 {
let criteria = UniverseCriteria::default();
let _universe = selector
.select_universe(criteria)
.await
.expect("Selection failed");
}
let duration = start.elapsed();
let avg_duration = duration.as_millis() / 10;
// Average should be well under 1 second
assert!(
avg_duration < 1000,
"Average universe selection took {}ms (target: <1000ms)",
avg_duration
);
println!(
"10 sequential selections completed in {}ms (avg: {}ms)",
duration.as_millis(),
avg_duration
);
}
// ============================================================================
// METRICS VALIDATION TESTS
// ============================================================================
#[tokio::test]
async fn test_metrics_accuracy() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria::default();
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
// Verify metrics are calculated correctly
assert_eq!(
universe.metrics.total_instruments,
universe.instruments.len()
);
// Calculate expected averages
let expected_avg_liquidity = universe
.instruments
.iter()
.map(|i| i.liquidity_score)
.sum::<f64>()
/ universe.instruments.len() as f64;
let expected_avg_volatility = universe
.instruments
.iter()
.map(|i| i.volatility)
.sum::<f64>()
/ universe.instruments.len() as f64;
let expected_avg_spread = universe
.instruments
.iter()
.map(|i| i.spread_bps)
.sum::<f64>()
/ universe.instruments.len() as f64;
// Verify metrics match calculations
assert!((universe.metrics.avg_liquidity_score - expected_avg_liquidity).abs() < 1e-10);
assert!((universe.metrics.avg_volatility - expected_avg_volatility).abs() < 1e-10);
assert!((universe.metrics.avg_spread_bps - expected_avg_spread).abs() < 1e-10);
}
#[tokio::test]
async fn test_asset_class_distribution() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria {
asset_classes: vec![AssetClass::Futures, AssetClass::Currencies],
regions: vec![Region::NorthAmerica, Region::Global],
..UniverseCriteria::default()
};
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
// Verify asset class distribution matches actual instruments
let mut expected_distribution = std::collections::HashMap::new();
for instrument in &universe.instruments {
let key = format!("{:?}", instrument.asset_class);
*expected_distribution.entry(key).or_insert(0) += 1;
}
assert_eq!(
universe.metrics.asset_class_distribution,
expected_distribution
);
}
// ============================================================================
// REAL SYMBOL VALIDATION TESTS
// ============================================================================
#[tokio::test]
async fn test_real_symbols_es_nq() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria {
min_liquidity: 0.90, // ES.FUT (0.95), NQ.FUT (0.92), CL.FUT (0.90)
..UniverseCriteria::default()
};
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
// Should include ES.FUT and NQ.FUT
let symbols: std::collections::HashSet<_> = universe
.instruments
.iter()
.map(|i| i.symbol.as_str())
.collect();
assert!(symbols.contains("ES.FUT"), "Should include ES.FUT");
assert!(symbols.contains("NQ.FUT"), "Should include NQ.FUT");
}
#[tokio::test]
async fn test_real_symbols_all_available() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
// Very permissive criteria to get all symbols
let criteria = UniverseCriteria {
min_liquidity: 0.0,
max_volatility: 1.0,
asset_classes: vec![
AssetClass::Futures,
AssetClass::Currencies,
AssetClass::Commodities,
],
regions: vec![
Region::NorthAmerica,
Region::Global,
Region::Europe,
Region::Asia,
],
min_market_cap: None,
..UniverseCriteria::default()
};
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
// Should have all 5 instruments: ES.FUT, NQ.FUT, ZN.FUT, 6E.FUT, CL.FUT
let symbols: std::collections::HashSet<_> = universe
.instruments
.iter()
.map(|i| i.symbol.as_str())
.collect();
assert_eq!(symbols.len(), 5, "Should have all 5 instruments");
assert!(symbols.contains("ES.FUT"));
assert!(symbols.contains("NQ.FUT"));
assert!(symbols.contains("ZN.FUT"));
assert!(symbols.contains("6E.FUT"));
assert!(symbols.contains("CL.FUT"));
}
#[tokio::test]
async fn test_real_symbol_properties() {
let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
});
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
let selector = UniverseSelector::new(pool);
let criteria = UniverseCriteria::default();
let universe = selector
.select_universe(criteria)
.await
.expect("Failed to select universe");
// Verify properties of real symbols
for instrument in &universe.instruments {
// All should have valid properties
assert!(instrument.liquidity_score > 0.0 && instrument.liquidity_score <= 1.0);
assert!(instrument.volatility > 0.0 && instrument.volatility <= 1.0);
assert!(instrument.avg_daily_volume > 0.0);
assert!(instrument.spread_bps > 0.0);
assert_eq!(instrument.exchange, "CME");
}
}