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>
480 lines
16 KiB
Rust
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");
|
|
}
|