Files
foxhunt/adaptive-strategy/tests/algorithm_comprehensive.rs
jgrusewski 89d98f8c5a 🧪 Waves 100-102: Test Coverage Initiative + Compilation Fixes
WAVE 100: Test Coverage Expansion (8/10 agents, 308 tests added)
├─ Agent 4: Execution error path tests (trading_service)
├─ Agent 5: ML training pipeline timeout analysis
├─ Agent 6: Audit persistence comprehensive tests
├─ Agent 7: ML pipeline coverage tests + rate limiting
├─ Agent 8: Algorithm comprehensive tests (adaptive-strategy)
├─ Agent 9: Coverage measurement analysis
└─ Result: 308 new tests across 8 components

WAVE 101: Compilation Error Fixes (14 errors → 0)
├─ Fixed backtesting_comprehensive.rs (6 compilation errors)
│  ├─ Added `use rust_decimal::MathematicalOps;` import
│  ├─ Removed 3 invalid `?` operators from void methods
│  └─ Fixed 4 i64 type casting issues for ChronoDuration::days()
├─ performance_tracking_comprehensive.rs: Already fixed (38/38 tests pass)
└─ algorithm_comprehensive.rs: Already fixed (38/40 tests pass)

WAVE 102: Runtime Test Failure Analysis (10 failures documented)
├─ Issue #1: Benchmark comparison stub (backtesting/metrics.rs:657-669)
│  └─ Always returns None, needs beta/alpha/tracking error implementation
├─ Issue #2: Daily returns calculation edge cases (3 tests affected)
│  └─ Returns empty Vec for < 2 snapshots, triggers "No daily returns calculated"
├─ Issue #3: Timestamp offsets in replay tests (1 hour, 60 day differences)
│  └─ Possible timezone/DST issue or Utc::now() non-determinism
├─ Issue #4: Monthly performance calculation (< 11 months generated)
└─ Issue #5: Max drawdown peak-to-trough assertion

TEST RESULTS:
├─ Compilation:  100% (all 3 Wave 100 test files compile)
├─ Test Pass Rate: 108/118 tests (91.5%)
│  ├─ algorithm_comprehensive: 38/40 (95%)
│  ├─ backtesting_comprehensive: 32/40 (80%)
│  └─ performance_tracking: 38/38 (100%)
└─ Coverage Impact: Estimated +5-10 points toward 95% target

FILES CHANGED:
├─ New Tests: 11 files (algorithm, backtesting, performance tracking, etc.)
├─ Fixed: backtesting_comprehensive.rs (6 compilation errors resolved)
├─ Documentation: 8 new agent reports (Wave 100-101)
└─ Analysis: wave102_test_failures_analysis.txt

TIMELINE:
├─ Wave 100: 308 tests added (90% completion, 2 agents hit timeout)
├─ Wave 101: All compilation errors resolved (100% success)
├─ Wave 102: Root cause analysis complete (10 failures documented)
└─ Next: Wave 103 to fix 10 runtime test failures (5-10 hours estimated)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 16:05:34 +02:00

695 lines
23 KiB
Rust

//! Comprehensive algorithm tests for adaptive-strategy crate
//!
//! This test suite provides extensive coverage for:
//! - Strategy signal generation algorithms
//! - Position sizing logic (Kelly, PPO, risk parity)
//! - Ensemble model coordination and voting
//! - Performance tracking and metrics
//! - Model factory and registry operations
//! - Regime detection and adaptation
//!
//! Target: Increase coverage from 40-50% → 75%+
use adaptive_strategy::config::{
AdaptiveStrategyConfig, ExecutionAlgorithm, ModelConfig, PositionSizingMethod,
RegimeDetectionMethod,
};
use adaptive_strategy::ensemble::EnsembleCoordinator;
use adaptive_strategy::models::{
ModelFactory, ModelMetadata, ModelRegistry, ModelTrait, TrainingData,
};
use adaptive_strategy::risk::{
KellyConfig, KellyPositionSizer, PortfolioRiskMetrics, PositionRiskMetrics,
PositionSizeRecommendation, RiskManager,
};
use adaptive_strategy::{AdaptiveStrategy, PerformanceMetrics, StrategyState};
use common::{MarketRegime, Position};
use rust_decimal::Decimal;
use std::collections::HashMap;
// ============================================================================
// STRATEGY ALGORITHM TESTS (10 tests)
// ============================================================================
#[tokio::test]
async fn test_adaptive_strategy_creation_with_default_config() {
let config = AdaptiveStrategyConfig::default();
let strategy = AdaptiveStrategy::new(config).await;
assert!(strategy.is_ok(), "Strategy creation should succeed");
let strategy = strategy.unwrap();
let state = strategy.get_state().await;
assert!(!state.active, "Initial strategy should be inactive");
assert_eq!(state.current_regime, "unknown");
}
#[tokio::test]
async fn test_adaptive_strategy_state_transitions() {
let config = AdaptiveStrategyConfig::default();
let strategy = AdaptiveStrategy::new(config).await.unwrap();
// Initial state
let initial_state = strategy.get_state().await;
assert!(!initial_state.active);
// Strategy lifecycle would be tested here if start() didn't run indefinitely
// In production, we'd use a mock or time-limited execution
}
#[tokio::test]
async fn test_strategy_performance_metrics_initialization() {
let metrics = PerformanceMetrics::default();
assert_eq!(metrics.sharpe_ratio, 0.0);
assert_eq!(metrics.max_drawdown, 0.0);
assert_eq!(metrics.total_return, 0.0);
assert_eq!(metrics.win_rate, 0.0);
assert_eq!(metrics.trade_count, 0);
}
#[tokio::test]
async fn test_strategy_config_update() {
let config = AdaptiveStrategyConfig::default();
let mut strategy = AdaptiveStrategy::new(config.clone()).await.unwrap();
// Create modified config
let mut new_config = config;
new_config.risk.max_leverage = 3.0;
let result = strategy.update_config(new_config).await;
assert!(result.is_ok(), "Config update should succeed");
}
#[tokio::test]
async fn test_strategy_state_serialization() {
let state = StrategyState {
active: true,
current_regime: "bull".to_string(),
model_weights: HashMap::from([
("mamba2".to_string(), 0.4),
("tlob".to_string(), 0.6),
]),
last_update: chrono::Utc::now(),
performance: PerformanceMetrics::default(),
};
let serialized = serde_json::to_string(&state);
assert!(serialized.is_ok(), "State should be serializable");
let deserialized: Result<StrategyState, _> = serde_json::from_str(&serialized.unwrap());
assert!(deserialized.is_ok(), "State should be deserializable");
}
#[tokio::test]
async fn test_strategy_with_kelly_position_sizing() {
let mut config = AdaptiveStrategyConfig::default();
config.risk.position_sizing_method = PositionSizingMethod::Kelly;
config.risk.kelly_fraction = 0.25;
let strategy = AdaptiveStrategy::new(config).await;
assert!(strategy.is_ok(), "Strategy with Kelly sizing should create successfully");
}
#[tokio::test]
async fn test_strategy_with_ppo_position_sizing() {
let mut config = AdaptiveStrategyConfig::default();
config.risk.position_sizing_method = PositionSizingMethod::PPO;
let strategy = AdaptiveStrategy::new(config).await;
assert!(strategy.is_ok(), "Strategy with PPO sizing should create successfully");
}
#[tokio::test]
async fn test_strategy_with_custom_execution_algorithm() {
let mut config = AdaptiveStrategyConfig::default();
config.execution.algorithm = ExecutionAlgorithm::VWAP;
config.execution.max_slippage_bps = 5.0;
let strategy = AdaptiveStrategy::new(config).await;
assert!(strategy.is_ok(), "Strategy with VWAP should create successfully");
}
#[tokio::test]
async fn test_strategy_with_hmm_regime_detection() {
let mut config = AdaptiveStrategyConfig::default();
config.regime.detection_method = RegimeDetectionMethod::HMM;
config.regime.lookback_window = 252;
let strategy = AdaptiveStrategy::new(config).await;
assert!(strategy.is_ok(), "Strategy with HMM regime detection should succeed");
}
#[tokio::test]
async fn test_strategy_with_multiple_models() {
let mut config = AdaptiveStrategyConfig::default();
config.ensemble.models = vec![
ModelConfig {
id: "mamba2".to_string(),
name: "mamba2_model".to_string(),
model_type: "mamba2".to_string(),
parameters: serde_json::Value::Null,
initial_weight: 0.25,
enabled: true,
},
ModelConfig {
id: "tlob".to_string(),
name: "tlob_model".to_string(),
model_type: "tlob".to_string(),
parameters: serde_json::Value::Null,
initial_weight: 0.25,
enabled: true,
},
ModelConfig {
id: "lstm".to_string(),
name: "lstm_model".to_string(),
model_type: "lstm".to_string(),
parameters: serde_json::Value::Null,
initial_weight: 0.25,
enabled: true,
},
ModelConfig {
id: "transformer".to_string(),
name: "transformer_model".to_string(),
model_type: "transformer".to_string(),
parameters: serde_json::Value::Null,
initial_weight: 0.25,
enabled: true,
},
];
let strategy = AdaptiveStrategy::new(config).await;
assert!(strategy.is_ok(), "Strategy with 4 models should create successfully");
}
// ============================================================================
// POSITION SIZING ALGORITHM TESTS (10 tests)
// ============================================================================
#[tokio::test]
async fn test_kelly_position_sizer_creation() {
let config = KellyConfig {
max_fraction: 0.25,
min_fraction: 0.01,
lookback_period: 252,
confidence_threshold: 0.6,
volatility_adjustment: true,
drawdown_protection: true,
dynamic_risk_scaling: true,
max_concentration: 0.20,
correlation_adjustment: 0.85,
base_kelly: 0.25,
};
let sizer = KellyPositionSizer::new(config);
assert!(sizer.is_ok(), "Kelly sizer should create successfully");
}
#[tokio::test]
async fn test_kelly_position_sizing_calculation() {
let config = KellyConfig::default();
let mut sizer = KellyPositionSizer::new(config).unwrap();
let historical_returns = vec![
0.05, -0.02, 0.08, -0.03, 0.06, -0.01, 0.04, -0.02,
0.07, -0.01, 0.03, -0.04, 0.09, -0.02, 0.05, -0.03,
];
let mut market_data = adaptive_strategy::risk::MarketData {
prices: HashMap::new(),
volatilities: HashMap::new(),
correlations: HashMap::new(),
timestamp: chrono::Utc::now(),
volatility_index: Some(20.0),
sentiment_indicators: HashMap::new(),
};
market_data.prices.insert("AAPL".to_string(), 150.0);
market_data.volatilities.insert("AAPL".to_string(), 0.25);
let recommendation = sizer
.calculate_position_size("AAPL", 0.08, 0.75, &historical_returns, &market_data)
.await;
assert!(recommendation.is_ok(), "Kelly calculation should succeed");
let rec = recommendation.unwrap();
assert!(rec.recommended_fraction >= 0.0 && rec.recommended_fraction <= 0.25);
assert!(rec.confidence > 0.0 && rec.confidence <= 1.0);
}
#[tokio::test]
async fn test_kelly_with_high_volatility_regime() {
let config = KellyConfig::default();
let mut sizer = KellyPositionSizer::new(config).unwrap();
// Update to high volatility regime
sizer
.update_market_regime(adaptive_strategy::regime::MarketRegime::HighVolatility)
.await
.unwrap();
let historical_returns = vec![0.05; 20];
let mut market_data = adaptive_strategy::risk::MarketData {
prices: HashMap::new(),
volatilities: HashMap::new(),
correlations: HashMap::new(),
timestamp: chrono::Utc::now(),
volatility_index: Some(40.0), // High VIX
sentiment_indicators: HashMap::new(),
};
market_data.prices.insert("SPY".to_string(), 450.0);
market_data.volatilities.insert("SPY".to_string(), 0.35); // High volatility
let recommendation = sizer
.calculate_position_size("SPY", 0.05, 0.7, &historical_returns, &market_data)
.await
.unwrap();
// In high volatility, Kelly should recommend smaller positions
assert!(
recommendation.recommended_fraction < 0.15,
"High volatility should reduce position size"
);
}
#[tokio::test]
async fn test_kelly_with_drawdown_protection() {
let config = KellyConfig {
drawdown_protection: true,
max_fraction: 0.25,
..Default::default()
};
let sizer = KellyPositionSizer::new(config);
assert!(sizer.is_ok());
// Drawdown protection should reduce position sizes during losses
// This would be tested with actual drawdown tracking in production
}
#[tokio::test]
async fn test_fixed_fractional_position_sizing() {
let mut config = AdaptiveStrategyConfig::default();
config.risk.position_sizing_method = PositionSizingMethod::FixedFractional(0.1);
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let recommendation = risk_manager
.calculate_position_size("MSFT", 0.06, 0.8, 300.0)
.await;
assert!(recommendation.is_ok());
let rec = recommendation.unwrap();
assert!(rec.size > 0.0, "Should recommend non-zero position");
}
#[tokio::test]
async fn test_risk_parity_position_sizing() {
let mut config = AdaptiveStrategyConfig::default();
config.risk.position_sizing_method = PositionSizingMethod::RiskParity;
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let recommendation = risk_manager
.calculate_position_size("GOOGL", 0.07, 0.85, 140.0)
.await;
assert!(recommendation.is_ok());
}
#[tokio::test]
async fn test_volatility_target_position_sizing() {
let mut config = AdaptiveStrategyConfig::default();
config.risk.position_sizing_method = PositionSizingMethod::VolatilityTarget;
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let recommendation = risk_manager
.calculate_position_size("TSLA", 0.10, 0.65, 250.0)
.await;
assert!(recommendation.is_ok());
}
#[tokio::test]
async fn test_position_size_with_risk_limits() {
let mut config = AdaptiveStrategyConfig::default();
config.risk.max_position_size = 0.05; // 5% limit
config.risk.max_leverage = 1.5;
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let recommendation = risk_manager
.calculate_position_size("NVDA", 0.15, 0.9, 500.0)
.await
.unwrap();
// Position should be constrained by risk limits
assert!(
recommendation.size <= recommendation.max_allowed_size,
"Position should not exceed max allowed"
);
}
#[tokio::test]
async fn test_position_sizing_risk_metrics() {
let config = AdaptiveStrategyConfig::default();
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let recommendation = risk_manager
.calculate_position_size("AMZN", 0.08, 0.75, 180.0)
.await
.unwrap();
// Verify risk metrics are calculated
assert!(recommendation.risk_metrics.expected_return > 0.0);
assert!(recommendation.risk_metrics.expected_volatility >= 0.0);
assert!(recommendation.risk_metrics.var_95 >= 0.0);
assert!(recommendation.risk_metrics.cvar_95 >= recommendation.risk_metrics.var_95);
}
#[tokio::test]
async fn test_position_size_recommendation_serialization() {
let recommendation = PositionSizeRecommendation {
size: 100.0,
confidence: 0.85,
max_allowed_size: 150.0,
method: "Kelly".to_string(),
risk_metrics: PositionRiskMetrics {
expected_return: 0.08,
expected_volatility: 0.20,
sharpe_ratio: 0.4,
var_95: 20.0,
cvar_95: 25.0,
max_loss: 100.0,
},
timestamp: chrono::Utc::now(),
};
let serialized = serde_json::to_string(&recommendation);
assert!(serialized.is_ok());
let deserialized: Result<PositionSizeRecommendation, _> =
serde_json::from_str(&serialized.unwrap());
assert!(deserialized.is_ok());
}
// ============================================================================
// ENSEMBLE MODEL COORDINATION TESTS (5 tests)
// ============================================================================
#[tokio::test]
async fn test_ensemble_coordinator_creation() {
let config = AdaptiveStrategyConfig::default();
let coordinator = EnsembleCoordinator::new(&config).await;
assert!(coordinator.is_ok(), "Ensemble coordinator should create successfully");
}
#[tokio::test]
async fn test_ensemble_prediction_generation() {
let config = AdaptiveStrategyConfig::default();
let coordinator = EnsembleCoordinator::new(&config).await.unwrap();
let features = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let horizon = chrono::Duration::seconds(60);
let prediction = coordinator.predict(&features, horizon).await;
assert!(prediction.is_ok(), "Ensemble prediction should succeed");
let pred = prediction.unwrap();
assert!(
pred.confidence >= 0.0 && pred.confidence <= 1.0,
"Confidence should be in [0,1]"
);
assert!(!pred.model_contributions.is_empty(), "Should have model contributions");
}
#[tokio::test]
async fn test_ensemble_weight_updates() {
let config = AdaptiveStrategyConfig::default();
let coordinator = EnsembleCoordinator::new(&config).await.unwrap();
let initial_weights = coordinator.get_weights().await;
assert!(!initial_weights.is_empty(), "Should have initial weights");
// Update weights
let result = coordinator.update_weights_default().await;
assert!(result.is_ok(), "Weight update should succeed");
let updated_weights = coordinator.get_weights().await;
assert_eq!(initial_weights.len(), updated_weights.len());
}
#[tokio::test]
async fn test_ensemble_outcome_recording() {
let config = AdaptiveStrategyConfig::default();
let coordinator = EnsembleCoordinator::new(&config).await.unwrap();
let timestamp = chrono::Utc::now();
let actual_outcome = 0.05;
let result = coordinator.record_outcome_legacy(timestamp, actual_outcome).await;
assert!(result.is_ok(), "Outcome recording should succeed");
}
#[tokio::test]
async fn test_ensemble_performance_tracking() {
let config = AdaptiveStrategyConfig::default();
let coordinator = EnsembleCoordinator::new(&config).await.unwrap();
let performance = coordinator.get_performance().await;
assert_eq!(performance.accuracy().len(), 0, "Initial accuracy should be empty");
}
// ============================================================================
// MODEL FACTORY AND REGISTRY TESTS (5 tests)
// ============================================================================
#[tokio::test]
async fn test_model_factory_available_models() {
let models = ModelFactory::available_models();
assert!(models.contains(&"lstm"), "Should support LSTM");
assert!(models.contains(&"transformer"), "Should support Transformer");
assert!(models.contains(&"random_forest"), "Should support Random Forest");
assert!(models.contains(&"xgboost"), "Should support XGBoost");
assert!(models.contains(&"tlob"), "Should support TLOB");
}
#[tokio::test]
async fn test_model_factory_creation() {
let config = adaptive_strategy::models::ModelConfig::default();
let model = ModelFactory::create_model("lstm", "test_lstm".to_string(), config).await;
assert!(model.is_ok(), "LSTM model creation should succeed");
let model = model.unwrap();
assert_eq!(model.name(), "test_lstm");
assert_eq!(model.model_type(), "lstm");
}
#[tokio::test]
async fn test_model_registry_operations() {
let mut registry = ModelRegistry::new();
assert_eq!(registry.list_models().len(), 0, "Registry should start empty");
let config = adaptive_strategy::models::ModelConfig::default();
let model = ModelFactory::create_model("mock", "test_model".to_string(), config)
.await
.unwrap();
registry.register(model);
assert_eq!(registry.list_models().len(), 1);
assert!(registry.get("test_model").is_some());
let removed = registry.remove("test_model");
assert!(removed.is_some());
assert_eq!(registry.list_models().len(), 0);
}
#[tokio::test]
async fn test_model_training_data_validation() {
let features = vec![vec![1.0, 2.0, 3.0]; 10];
let targets = vec![0.5; 10];
let feature_names = vec!["f1".to_string(), "f2".to_string(), "f3".to_string()];
let data = TrainingData::new(features, targets, feature_names);
assert!(data.validate().is_ok(), "Valid data should pass validation");
assert_eq!(data.len(), 10);
assert!(!data.is_empty());
}
#[tokio::test]
async fn test_model_training_data_invalid() {
let features = vec![vec![1.0, 2.0]; 5];
let targets = vec![0.5; 3]; // Mismatched length
let feature_names = vec!["f1".to_string(), "f2".to_string()];
let data = TrainingData::new(features, targets, feature_names);
assert!(data.validate().is_err(), "Invalid data should fail validation");
}
// ============================================================================
// RISK MANAGEMENT INTEGRATION TESTS (5 tests)
// ============================================================================
#[tokio::test]
async fn test_risk_manager_position_update() {
let config = AdaptiveStrategyConfig::default();
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let position = Position {
id: uuid::Uuid::new_v4(),
symbol: "AAPL".into(),
quantity: Decimal::from(100),
avg_price: Decimal::from(150),
avg_cost: Decimal::from(150),
basis: Decimal::from(15000),
average_price: Decimal::from(150),
market_value: Decimal::from(15000),
unrealized_pnl: Decimal::ZERO,
realized_pnl: Decimal::ZERO,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
last_updated: chrono::Utc::now(),
current_price: Some(Decimal::from(155)),
notional_value: Decimal::from(15500),
margin_requirement: Decimal::from(1550),
};
let result = risk_manager.update_position(position).await;
assert!(result.is_ok(), "Position update should succeed");
}
#[tokio::test]
async fn test_risk_manager_portfolio_metrics() {
let config = AdaptiveStrategyConfig::default();
let risk_manager = RiskManager::new(config.risk).unwrap();
let metrics = risk_manager.get_portfolio_risk_metrics().await;
assert!(metrics.is_ok(), "Portfolio metrics should be calculated");
let metrics = metrics.unwrap();
assert!(metrics.portfolio_var >= 0.0);
assert!(metrics.leverage >= 0.0);
assert!(metrics.current_drawdown >= 0.0);
}
#[tokio::test]
async fn test_risk_manager_trade_risk_check() {
let config = AdaptiveStrategyConfig::default();
let risk_manager = RiskManager::new(config.risk).unwrap();
let passes = risk_manager.check_trade_risk("MSFT", 50.0, 300.0).await;
assert!(passes.is_ok(), "Trade risk check should execute");
}
#[tokio::test]
async fn test_risk_manager_limits_status() {
let config = AdaptiveStrategyConfig::default();
let risk_manager = RiskManager::new(config.risk).unwrap();
let status = risk_manager.get_risk_limits_status().await;
assert!(status.is_ok(), "Should retrieve risk limits status");
let status = status.unwrap();
assert!(status.contains_key("leverage") || status.contains_key("drawdown"));
}
#[tokio::test]
async fn test_risk_manager_market_regime_update() {
let config = AdaptiveStrategyConfig::default();
let mut risk_manager = RiskManager::new(config.risk).unwrap();
let result = risk_manager.update_market_regime(MarketRegime::Bull).await;
assert!(result.is_ok(), "Market regime update should succeed");
let result = risk_manager.update_market_regime(MarketRegime::HighVolatility).await;
assert!(result.is_ok(), "High volatility regime update should succeed");
}
// ============================================================================
// PERFORMANCE METRICS AND TRACKING TESTS (5 tests)
// ============================================================================
#[tokio::test]
async fn test_portfolio_risk_metrics_serialization() {
let metrics = PortfolioRiskMetrics {
portfolio_var: 0.015,
portfolio_cvar: 0.019,
leverage: 1.8,
current_drawdown: 0.03,
max_drawdown: 0.05,
sharpe_ratio: 1.5,
sortino_ratio: 1.8,
beta: Some(0.95),
concentration_risk: 0.15,
timestamp: chrono::Utc::now(),
};
let serialized = serde_json::to_string(&metrics);
assert!(serialized.is_ok());
let deserialized: Result<PortfolioRiskMetrics, _> =
serde_json::from_str(&serialized.unwrap());
assert!(deserialized.is_ok());
}
#[tokio::test]
async fn test_position_risk_metrics_calculation() {
let metrics = PositionRiskMetrics {
expected_return: 0.08,
expected_volatility: 0.20,
sharpe_ratio: 0.4,
var_95: 1000.0,
cvar_95: 1280.0,
max_loss: 5000.0,
};
assert!(metrics.sharpe_ratio > 0.0);
assert!(metrics.cvar_95 > metrics.var_95, "CVaR should exceed VaR");
assert!(metrics.max_loss >= metrics.cvar_95, "Max loss should be highest");
}
#[tokio::test]
async fn test_performance_metrics_updates() {
let mut metrics = PerformanceMetrics::default();
// Simulate trading results
metrics.sharpe_ratio = 1.8;
metrics.max_drawdown = 0.05;
metrics.total_return = 0.25;
metrics.win_rate = 0.62;
metrics.trade_count = 150;
assert!(metrics.sharpe_ratio > 0.0);
assert!(metrics.win_rate > 0.5);
assert!(metrics.trade_count > 0);
}
#[tokio::test]
async fn test_metadata_creation() {
let metadata = ModelMetadata {
name: "test_model".to_string(),
model_type: "lstm".to_string(),
version: "1.0.0".to_string(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
parameters: HashMap::new(),
input_dimensions: 128,
description: Some("Test model".to_string()),
};
assert_eq!(metadata.name, "test_model");
assert_eq!(metadata.input_dimensions, 128);
}
#[tokio::test]
async fn test_concentration_metrics() {
let config = AdaptiveStrategyConfig::default();
let risk_manager = RiskManager::new(config.risk).unwrap();
let concentration = risk_manager.get_concentration_metrics().await;
// May be None if Kelly sizer not initialized
assert!(concentration.is_ok());
}