Files
foxhunt/config/tests/asset_classification_tests.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade

Files updated:
- Cargo.lock: Dependency resolution for Tonic 0.14.2
- All build.rs: Updated for tonic-prost-build
- Proto files: Regenerated with tonic-prost 0.14
- Examples/tests: Updated for new gRPC API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 07:34:26 +02:00

480 lines
16 KiB
Rust

//! Comprehensive test suite for asset classification system
//!
//! Tests cover pattern matching, database integration, trading parameters,
//! volatility profiling, and ConfigManager integration.
#![allow(unused_crate_dependencies)]
use chrono::Utc;
use config::asset_classification_integration::MarketCapTier;
use config::{
create_default_configurations, manager::ConfigManagerBuilder, AssetClass,
AssetClassificationManager, AssetConfig, CryptoType, DetailedVolatilityProfile, EquitySector,
ExecutionConfig, ForexPairType, GeographicRegion, JumpRiskProfile, OrderType, PositionLimits,
RiskThresholds, ServiceConfig, SettlementConfig, TimeInForce, TradingParameters,
};
use rust_decimal::Decimal;
use std::str::FromStr;
use uuid::Uuid;
#[tokio::test]
async fn test_asset_classification_manager_creation() {
let manager = AssetClassificationManager::new();
// Test initial state
assert_eq!(manager.get_active_configurations().len(), 0);
// Initially no configurations loaded, so classification may be unknown
}
#[tokio::test]
async fn test_default_configurations_loading() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
assert!(
!configs.is_empty(),
"Default configurations should not be empty"
);
manager.load_configurations(configs).await.unwrap();
// Test that configurations were loaded
assert!(!manager.get_active_configurations().is_empty());
}
#[tokio::test]
async fn test_symbol_classification() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
// Test blue chip equity classification
let aapl_class = manager.classify_symbol("AAPL");
match aapl_class {
AssetClass::Equity {
sector: EquitySector::Technology,
..
} => {}
_ => panic!("AAPL should be classified as Technology equity"),
}
// Test crypto classification
let btc_class = manager.classify_symbol("BTCUSD");
match btc_class {
AssetClass::Crypto {
crypto_type: CryptoType::Bitcoin,
..
} => {}
_ => panic!("BTCUSD should be classified as Bitcoin crypto"),
}
// Test forex classification
let eur_class = manager.classify_symbol("EURUSD");
match eur_class {
AssetClass::Forex {
pair_type: ForexPairType::Major,
..
} => {}
_ => panic!("EURUSD should be classified as Major forex pair"),
}
// Test unknown symbol
assert_eq!(
manager.classify_symbol("UNKNOWN_SYMBOL"),
AssetClass::Unknown
);
}
#[tokio::test]
async fn test_volatility_profiles() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
// Test AAPL volatility
let aapl_vol = manager.get_daily_volatility("AAPL");
assert!(aapl_vol > 0.0, "AAPL should have positive volatility");
assert!(aapl_vol < 0.1, "AAPL daily volatility should be reasonable");
let aapl_profile = manager.get_volatility_profile("AAPL");
assert!(
aapl_profile.is_some(),
"AAPL should have volatility profile"
);
if let Some(profile) = aapl_profile {
assert!(profile.base_annual_volatility > 0.0);
assert!(profile.stress_volatility_multiplier >= 1.0);
assert!(profile.jump_risk.jump_probability >= 0.0);
assert!(profile.jump_risk.jump_probability <= 1.0);
}
// Test crypto has higher volatility than equity
let btc_vol = manager.get_daily_volatility("BTCUSD");
assert!(
btc_vol > aapl_vol,
"Crypto should have higher volatility than equity"
);
}
#[tokio::test]
async fn test_trading_parameters() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
// Test AAPL trading parameters
let aapl_params = manager.get_trading_parameters("AAPL");
assert!(aapl_params.is_some(), "AAPL should have trading parameters");
if let Some(params) = aapl_params {
assert!(params.position_limits.max_position_fraction > 0.0);
assert!(params.position_limits.max_position_fraction <= 1.0);
assert!(params.position_limits.max_leverage >= 1.0);
assert!(params.risk_thresholds.daily_loss_limit > 0.0);
assert!(!params.execution_config.preferred_order_types.is_empty());
}
}
#[tokio::test]
async fn test_position_size_recommendations() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
let portfolio_nav = Decimal::from_str("1000000.00").unwrap(); // $1M
// Test AAPL position sizing
let aapl_size = manager.get_position_size_recommendation("AAPL", portfolio_nav);
assert!(
aapl_size.is_some(),
"Should get position size recommendation for AAPL"
);
if let Some(size) = aapl_size {
assert!(size > Decimal::ZERO, "Position size should be positive");
assert!(
size <= portfolio_nav,
"Position size should not exceed portfolio NAV"
);
}
// Test that crypto has smaller recommended position than equity
let btc_size = manager.get_position_size_recommendation("BTCUSD", portfolio_nav);
if let (Some(aapl), Some(btc)) = (aapl_size, btc_size) {
assert!(
btc < aapl,
"Crypto position should be smaller than equity due to higher risk"
);
}
}
#[tokio::test]
async fn test_trading_hours() {
let mut manager = AssetClassificationManager::new();
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
let timestamp = Utc::now();
// Test equity trading hours (should have restrictions)
let _aapl_active = manager.is_trading_active("AAPL", timestamp);
// Test crypto trading hours (should be 24/7)
let btc_active = manager.is_trading_active("BTCUSD", timestamp);
assert!(btc_active, "Crypto should trade 24/7");
// Note: AAPL result depends on current time, but should not panic
// This tests the mechanism works
}
#[tokio::test]
async fn test_custom_asset_configuration() {
let mut manager = AssetClassificationManager::new();
// Create custom configuration
let custom_config = create_test_configuration();
let configs = vec![custom_config];
manager.load_configurations(configs).await.unwrap();
// Test that custom configuration works
let test_class = manager.classify_symbol("TESTSTOCK");
match test_class {
AssetClass::Equity {
sector: EquitySector::Technology,
market_cap: MarketCapTier::SmallCap,
..
} => {}
_ => panic!("TESTSTOCK should match custom configuration"),
}
// Test parameters
let params = manager.get_trading_parameters("TESTSTOCK");
assert!(
params.is_some(),
"Custom configuration should have trading parameters"
);
}
#[tokio::test]
async fn test_pattern_priority() {
let mut manager = AssetClassificationManager::new();
// Create configurations with different priorities
let high_priority = create_priority_test_config("^TEST.*$", 100);
let low_priority = create_priority_test_config("^TEST.*$", 50);
let configs = vec![low_priority, high_priority]; // Load in reverse priority order
manager.load_configurations(configs).await.unwrap();
// Should match high priority configuration
let test_class = manager.classify_symbol("TESTPATTERN");
match test_class {
AssetClass::Equity {
market_cap: MarketCapTier::LargeCap,
..
} => {}
_ => panic!("Should match high priority configuration"),
}
}
#[tokio::test]
async fn test_config_manager_integration() {
let mut asset_manager = AssetClassificationManager::new();
let configs = create_default_configurations();
asset_manager.load_configurations(configs).await.unwrap();
let service_config = ServiceConfig {
name: "test_service".to_string(),
environment: "test".to_string(),
version: "1.0.0".to_string(),
settings: serde_json::json!({}),
};
let config_manager = ConfigManagerBuilder::new(service_config)
.with_asset_classification(asset_manager)
.build();
// Test integration methods
let asset_class = config_manager.classify_symbol("AAPL");
assert_ne!(asset_class, AssetClass::Unknown);
let daily_vol = config_manager.get_daily_volatility("AAPL");
assert!(daily_vol > 0.0);
let params = config_manager.get_trading_parameters("AAPL");
assert!(params.is_some());
let portfolio_nav = Decimal::from_str("100000.00").unwrap();
let position_size = config_manager.get_position_size_recommendation("AAPL", portfolio_nav);
assert!(position_size.is_some());
}
#[tokio::test]
async fn test_cache_functionality() {
let service_config = ServiceConfig {
name: "test_service".to_string(),
environment: "test".to_string(),
version: "1.0.0".to_string(),
settings: serde_json::json!({}),
};
let config_manager = ConfigManagerBuilder::new(service_config)
.with_cache_timeout(std::time::Duration::from_secs(1))
.build();
// Test cache set/get
let test_value = serde_json::json!({"test": "value"});
config_manager.set_cached_config("test_key".to_string(), test_value.clone());
let cached = config_manager.get_cached_config("test_key");
assert_eq!(cached, Some(test_value));
// Test cache expiration
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let expired = config_manager.get_cached_config("test_key");
assert_eq!(expired, None);
}
#[tokio::test]
async fn test_configuration_validation() {
let manager = AssetClassificationManager::new();
// Test with invalid regex pattern
let invalid_config = AssetConfig {
id: Uuid::new_v4(),
name: "Invalid Pattern".to_string(),
symbol_pattern: "[invalid_regex".to_string(), // Invalid regex
compiled_pattern: None,
asset_class: AssetClass::Unknown,
volatility_profile: create_default_volatility_profile(),
trading_parameters: create_default_trading_parameters(),
priority: 100,
is_active: true,
created_at: Utc::now(),
updated_at: Utc::now(),
trading_hours: None,
settlement_config: SettlementConfig {
settlement_days: 2,
settlement_currency: "USD".to_string(),
physical_settlement: false,
},
};
// Should handle invalid configuration gracefully
let mut test_manager = manager;
let result = test_manager.load_configurations(vec![invalid_config]).await;
assert!(
result.is_ok(),
"Should handle invalid configurations gracefully"
);
}
// Helper functions for test configurations
fn create_test_configuration() -> AssetConfig {
let now = Utc::now();
AssetConfig {
id: Uuid::new_v4(),
name: "Test Configuration".to_string(),
symbol_pattern: "^TESTSTOCK$".to_string(),
compiled_pattern: None,
asset_class: AssetClass::Equity {
sector: EquitySector::Technology,
market_cap: MarketCapTier::SmallCap,
region: GeographicRegion::NorthAmerica,
},
volatility_profile: create_default_volatility_profile(),
trading_parameters: create_default_trading_parameters(),
priority: 100,
is_active: true,
created_at: now,
updated_at: now,
trading_hours: None,
settlement_config: SettlementConfig {
settlement_days: 2,
settlement_currency: "USD".to_string(),
physical_settlement: false,
},
}
}
fn create_priority_test_config(pattern: &str, priority: u32) -> AssetConfig {
let now = Utc::now();
let market_cap = if priority > 75 {
MarketCapTier::LargeCap
} else {
MarketCapTier::SmallCap
};
AssetConfig {
id: Uuid::new_v4(),
name: format!("Priority {} Configuration", priority),
symbol_pattern: pattern.to_string(),
compiled_pattern: None,
asset_class: AssetClass::Equity {
sector: EquitySector::Technology,
market_cap,
region: GeographicRegion::NorthAmerica,
},
volatility_profile: create_default_volatility_profile(),
trading_parameters: create_default_trading_parameters(),
priority,
is_active: true,
created_at: now,
updated_at: now,
trading_hours: None,
settlement_config: SettlementConfig {
settlement_days: 2,
settlement_currency: "USD".to_string(),
physical_settlement: false,
},
}
}
fn create_default_volatility_profile() -> DetailedVolatilityProfile {
DetailedVolatilityProfile {
base_annual_volatility: 0.25,
stress_volatility_multiplier: 2.0,
intraday_pattern: vec![1.0; 24],
volatility_persistence: 0.85,
jump_risk: JumpRiskProfile {
jump_probability: 0.02,
jump_magnitude: 0.05,
max_jump_size: 0.15,
},
}
}
fn create_default_trading_parameters() -> TradingParameters {
TradingParameters {
position_limits: PositionLimits {
max_position_fraction: 0.10,
max_leverage: 2.0,
concentration_limit: 0.20,
min_position_size: Decimal::from(100),
},
risk_thresholds: RiskThresholds {
var_limit: 0.05,
daily_loss_limit: 0.03,
stop_loss_threshold: 0.10,
volatility_circuit_breaker: 0.05,
max_drawdown_threshold: 0.15,
},
execution_config: ExecutionConfig {
preferred_order_types: vec![OrderType::Limit, OrderType::Market],
tick_size: Decimal::from_str("0.01").unwrap(),
min_order_size: Decimal::from(1),
max_order_size: Decimal::from(10000),
time_in_force_default: TimeInForce::Day,
slippage_tolerance: 0.001,
},
market_making: None,
}
}
#[tokio::test]
async fn test_comprehensive_workflow() {
// This test demonstrates a complete workflow
println!("🧪 Running comprehensive asset classification workflow test");
// 1. Initialize manager
let mut manager = AssetClassificationManager::new();
// 2. Load configurations
let configs = create_default_configurations();
manager.load_configurations(configs).await.unwrap();
// 3. Test multiple symbols
let symbols = vec!["AAPL", "MSFT", "BTCUSD", "EURUSD", "UNKNOWN"];
for symbol in symbols {
let asset_class = manager.classify_symbol(symbol);
let daily_vol = manager.get_daily_volatility(symbol);
let trading_params = manager.get_trading_parameters(symbol);
println!(
"Symbol: {} | Class: {:?} | Daily Vol: {:.2}% | Has Params: {}",
symbol,
asset_class,
daily_vol * 100.0,
trading_params.is_some()
);
}
// 4. Test position sizing
let portfolio_nav = Decimal::from_str("500000.00").unwrap();
for symbol in ["AAPL", "BTCUSD"] {
if let Some(size) = manager.get_position_size_recommendation(symbol, portfolio_nav) {
let percentage = (size / portfolio_nav) * Decimal::from(100);
println!(
"Recommended position for {}: ${} ({:.1}%)",
symbol, size, percentage
);
}
}
println!("✅ Comprehensive workflow test completed successfully");
}