#![allow(unsafe_code)] // Intentional unsafe for AVX2 vectorized backtesting performance //! Adaptive Strategy Runner for Backtesting //! //! This module provides the bridge between the backtesting engine and the adaptive strategy //! system, allowing historical data to flow through real ML models for validation. use anyhow::Result; use async_trait::async_trait; use common::{OrderSide, OrderStatus, Position, Symbol, Quantity, Price}; use common::Order; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use trading_engine::types::events::MarketEvent; // Use canonical types from ML module use ml::{Features, ModelPrediction}; // Mock ML registry /// Mock ML registry for testing and development pub struct MockMLRegistry; impl MockMLRegistry { /// Predict using selected models /// /// # Arguments /// * `_models` - List of model names to use for prediction /// * `_features` - Feature vector for prediction /// /// # Returns /// * `Vec>` - Vector of prediction results from each model pub async fn predict_selected( &self, _models: &[String], _features: &Features, ) -> Vec> { // Return a default prediction for now vec![Ok(ModelPrediction::new("mock_model".to_string(), 0.0, 0.5))] } /// Get available model names /// /// # Returns /// * `Vec` - List of available model names pub fn get_model_names(&self) -> Vec { vec!["mock_model".to_string()] } } /// Get global ML registry instance /// /// # Returns /// * `MockMLRegistry` - Global registry for model access pub fn get_global_registry() -> MockMLRegistry { MockMLRegistry } use dashmap::DashMap; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use chrono::{DateTime, Duration, Utc}; use std::collections::HashMap; use std::sync::Arc; use tracing::{debug, info, warn}; // SIMD optimizations for HFT performance #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; use crate::strategy_tester::{SignalType, Strategy, StrategyConfig, StrategyContext, StrategyResult, TradingSignal}; /// Adaptive strategy runner that integrates ML models with backtesting pub struct AdaptiveStrategyRunner { /// Strategy configuration config: AdaptiveStrategyConfig, /// Current market state market_state: Arc>, /// Model predictions cache (lock-free for HFT performance) predictions_cache: Arc>, /// Performance tracking performance_tracker: Arc>, /// Feature extractor feature_extractor: Arc, /// Risk manager risk_manager: Arc, } /// Configuration for adaptive strategy #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AdaptiveStrategyConfig { /// Models to use for predictions pub active_models: Vec, /// Minimum confidence threshold for trading pub min_confidence: f64, /// Maximum position size as fraction of portfolio pub max_position_size: f64, /// Lookback period for feature extraction pub lookback_period: usize, /// Model update frequency (in ticks) pub model_update_frequency: u64, /// Risk management settings pub risk_settings: RiskSettings, /// Feature extraction settings pub feature_settings: FeatureSettings, } impl Default for AdaptiveStrategyConfig { fn default() -> Self { Self { active_models: vec![ "TLOB".to_string(), "MAMBA".to_string(), "TFT".to_string(), "DQN".to_string(), "PPO".to_string(), ], min_confidence: 0.65, max_position_size: 0.05, // 5% max position lookback_period: 100, model_update_frequency: 1000, risk_settings: RiskSettings::default(), feature_settings: FeatureSettings::default(), } } } /// Risk management settings #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RiskSettings { /// Maximum drawdown before stopping pub max_drawdown: f64, /// Stop loss percentage pub stop_loss: f64, /// Take profit percentage pub take_profit: f64, /// Kelly fraction multiplier pub kelly_fraction: f64, } impl Default for RiskSettings { fn default() -> Self { Self { max_drawdown: 0.10, // 10% max drawdown stop_loss: 0.02, // 2% stop loss take_profit: 0.04, // 4% take profit kelly_fraction: 0.25, // Conservative 25% of Kelly } } } /// Feature extraction settings #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FeatureSettings { /// Price features to extract pub price_features: Vec, /// Volume features to extract pub volume_features: Vec, /// Technical indicators to compute pub technical_indicators: Vec, /// Microstructure features pub microstructure_features: Vec, } impl Default for FeatureSettings { fn default() -> Self { Self { price_features: vec![ "returns".to_string(), "log_returns".to_string(), "volatility".to_string(), "price_momentum".to_string(), ], volume_features: vec![ "volume".to_string(), "volume_momentum".to_string(), "vwap".to_string(), ], technical_indicators: vec![ "rsi".to_string(), "macd".to_string(), "bollinger_bands".to_string(), ], microstructure_features: vec![ "bid_ask_spread".to_string(), "order_flow_imbalance".to_string(), "market_impact".to_string(), ], } } } /// Current market state #[derive(Debug, Clone)] struct MarketState { /// Current timestamp current_time: DateTime, /// Price history price_history: Vec<(DateTime, Decimal)>, /// Volume history volume_history: Vec<(DateTime, Decimal)>, /// Current position current_position: Option, /// Last prediction time #[allow(dead_code)] last_prediction_time: Option>, } impl Default for MarketState { fn default() -> Self { Self { current_time: Utc::now(), price_history: Vec::new(), volume_history: Vec::new(), current_position: None, last_prediction_time: None, } } } /// Performance tracking for the strategy #[derive(Debug, Clone)] struct PerformanceTracker { /// Total trades executed total_trades: u64, /// Winning trades winning_trades: u64, /// Total PnL total_pnl: Decimal, /// Maximum drawdown max_drawdown: Decimal, /// Current drawdown current_drawdown: Decimal, /// Peak portfolio value peak_value: Decimal, /// Model prediction accuracy model_accuracy: HashMap, } impl Default for PerformanceTracker { fn default() -> Self { Self { total_trades: 0, winning_trades: 0, total_pnl: Decimal::ZERO, max_drawdown: Decimal::ZERO, current_drawdown: Decimal::ZERO, peak_value: Decimal::ZERO, model_accuracy: HashMap::new(), } } } /// Feature extractor for ML models with object pooling for performance struct FeatureExtractor { config: FeatureSettings, // OPTIMIZATION: Reusable buffers to avoid allocations in hot paths price_buffer: Vec, volume_buffer: Vec, returns_buffer: Vec, gains_buffer: Vec, losses_buffer: Vec, } impl FeatureExtractor { /// Create new feature extractor with configuration /// /// # Arguments /// * `config` - Feature extraction configuration /// /// # Returns /// * `Self` - New feature extractor instance with pre-allocated buffers fn new(config: FeatureSettings) -> Self { Self { config, // Pre-allocate buffers with reasonable capacity price_buffer: Vec::with_capacity(1024), volume_buffer: Vec::with_capacity(1024), returns_buffer: Vec::with_capacity(1024), gains_buffer: Vec::with_capacity(1024), losses_buffer: Vec::with_capacity(1024), } } /// Extract features from market data /// /// # Arguments /// * `market_state` - Current market state containing price and volume history /// /// # Returns /// * `Result` - Extracted feature vector ready for ML model input /// /// # Errors /// Returns error if feature extraction fails or insufficient data async fn extract_features(&self, market_state: &MarketState) -> Result { let mut feature_values = Vec::new(); let mut feature_names = Vec::new(); // Extract price features if let Some(features) = self.extract_price_features(market_state).await? { feature_values.extend(features.0); feature_names.extend(features.1); } // Extract volume features if let Some(features) = self.extract_volume_features(market_state).await? { feature_values.extend(features.0); feature_names.extend(features.1); } // Extract technical indicators if let Some(features) = self.extract_technical_features(market_state).await? { feature_values.extend(features.0); feature_names.extend(features.1); } Ok(Features { values: feature_values, names: feature_names, timestamp: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_micros() as u64, symbol: None, // Symbol will be set by the calling context when available }) } /// Extract price-based features from market data /// /// # Arguments /// * `market_state` - Market state with price history /// /// # Returns /// * `Result, Vec)>>` - Feature values and names, or None if insufficient data async fn extract_price_features( &self, market_state: &MarketState, ) -> Result, Vec)>> { if market_state.price_history.len() < 2 { return Ok(None); } let mut values = Vec::new(); let mut names = Vec::new(); let prices: Vec = market_state .price_history .iter() .map(|(_, price)| price.to_f64().unwrap_or(0.0)) .collect(); // Calculate returns if self.config.price_features.contains(&"returns".to_string()) { let returns = self.calculate_returns(&prices); values.extend(returns); names.push("returns".to_string()); } // Calculate volatility if self .config .price_features .contains(&"volatility".to_string()) { let volatility = self.calculate_volatility(&prices); values.push(volatility); names.push("volatility".to_string()); } Ok(Some((values, names))) } /// Extract volume-based features from market data /// /// # Arguments /// * `market_state` - Market state with volume history /// /// # Returns /// * `Result, Vec)>>` - Feature values and names, or None if insufficient data async fn extract_volume_features( &self, market_state: &MarketState, ) -> Result, Vec)>> { if market_state.volume_history.len() < 2 { return Ok(None); } let mut values = Vec::new(); let mut names = Vec::new(); let volumes: Vec = market_state .volume_history .iter() .map(|(_, volume)| volume.to_f64().unwrap_or(0.0)) .collect(); // Average volume if self.config.volume_features.contains(&"volume".to_string()) { let avg_volume = volumes.iter().sum::() / volumes.len() as f64; values.push(avg_volume); names.push("avg_volume".to_string()); } Ok(Some((values, names))) } /// Extract technical indicator features from market data /// /// # Arguments /// * `market_state` - Market state with sufficient price history for indicators /// /// # Returns /// * `Result, Vec)>>` - Technical indicator values and names, or None if insufficient data async fn extract_technical_features( &self, market_state: &MarketState, ) -> Result, Vec)>> { if market_state.price_history.len() < 14 { // Need minimum data for indicators return Ok(None); } let mut values = Vec::new(); let mut names = Vec::new(); let prices: Vec = market_state .price_history .iter() .map(|(_, price)| price.to_f64().unwrap_or(0.0)) .collect(); // RSI if self .config .technical_indicators .contains(&"rsi".to_string()) { let rsi = self.calculate_rsi(&prices, 14); values.push(rsi); names.push("rsi_14".to_string()); } Ok(Some((values, names))) } /// Calculate returns from price series with SIMD optimization /// /// # Arguments /// * `prices` - Array of price values /// /// # Returns /// * `Vec` - Vector of return percentages /// /// # Note /// Uses SIMD instructions on x86_64 for performance when available fn calculate_returns(&self, prices: &[f64]) -> Vec { // OPTIMIZATION: Use SIMD for vectorized return calculations if prices.len() < 2 { return Vec::new(); } #[cfg(target_arch = "x86_64")] { if is_x86_feature_detected!("avx2") { return self.calculate_returns_simd(prices); } } // Fallback to scalar implementation self.calculate_returns_scalar(prices) } /// Calculate returns using scalar operations (fallback) /// /// # Arguments /// * `prices` - Array of price values /// /// # Returns /// * `Vec` - Vector of return percentages fn calculate_returns_scalar(&self, prices: &[f64]) -> Vec { prices .windows(2) .map(|window| { if window[0] != 0.0 { (window[1] - window[0]) / window[0] } else { 0.0 } }) .collect() } /// Calculate returns using SIMD AVX2 instructions (x86_64 only) /// /// # Arguments /// * `prices` - Array of price values /// /// # Returns /// * `Vec` - Vector of return percentages /// /// # Safety /// Uses unsafe AVX2 intrinsics for vectorized computation #[cfg(target_arch = "x86_64")] fn calculate_returns_simd(&self, prices: &[f64]) -> Vec { let mut returns = Vec::with_capacity(prices.len() - 1); let len = prices.len() - 1; unsafe { // Process 4 elements at a time with AVX2 let mut i = 0; while i + 4 <= len { let prev = _mm256_loadu_pd(prices.as_ptr().add(i)); let curr = _mm256_loadu_pd(prices.as_ptr().add(i + 1)); // Calculate (curr - prev) / prev let diff = _mm256_sub_pd(curr, prev); let result = _mm256_div_pd(diff, prev); // Store results let mut temp = [0.0; 4]; _mm256_storeu_pd(temp.as_mut_ptr(), result); for j in 0..4 { returns.push(if prices[i + j] != 0.0 { temp[j] } else { 0.0 }); } i += 4; } // Handle remaining elements for j in i..len { let ret = if prices[j] != 0.0 { (prices[j + 1] - prices[j]) / prices[j] } else { 0.0 }; returns.push(ret); } } returns } /// Calculate price volatility (standard deviation of returns) /// /// # Arguments /// * `prices` - Array of price values /// /// # Returns /// * `f64` - Volatility as standard deviation of returns fn calculate_volatility(&self, prices: &[f64]) -> f64 { let returns = self.calculate_returns(prices); if returns.is_empty() { return 0.0; } let mean = returns.iter().sum::() / returns.len() as f64; let variance = returns.iter().map(|r| (r - mean).powi(2)).sum::() / returns.len() as f64; variance.sqrt() } /// Calculate Relative Strength Index (RSI) /// /// # Arguments /// * `prices` - Array of price values /// * `period` - Period for RSI calculation (typically 14) /// /// # Returns /// * `f64` - RSI value between 0 and 100 fn calculate_rsi(&self, prices: &[f64], period: usize) -> f64 { if prices.len() < period + 1 { return 50.0; // Neutral RSI } let mut gains = Vec::new(); let mut losses = Vec::new(); for window in prices.windows(2) { let change = window[1] - window[0]; if change > 0.0 { gains.push(change); losses.push(0.0); } else { gains.push(0.0); losses.push(-change); } } if gains.len() < period { return 50.0; } let avg_gain = gains.iter().rev().take(period).sum::() / period as f64; let avg_loss = losses.iter().rev().take(period).sum::() / period as f64; if avg_loss == 0.0 { return 100.0; } let rs = avg_gain / avg_loss; 100.0 - (100.0 / (1.0 + rs)) } } /// Risk manager for position sizing and risk controls struct RiskManager { config: RiskSettings, } impl RiskManager { /// Create new risk manager with configuration /// /// # Arguments /// * `config` - Risk management settings /// /// # Returns /// * `Self` - New risk manager instance fn new(config: RiskSettings) -> Self { Self { config } } /// Calculate position size using Kelly criterion /// /// # Arguments /// * `prediction` - Model prediction with confidence score /// * `account_value` - Current account value /// * `current_price` - Current market price /// /// # Returns /// * `Result` - Position size in shares/units /// /// # Note /// Uses conservative Kelly fraction scaling for risk management fn calculate_position_size( &self, prediction: &ModelPrediction, account_value: Decimal, current_price: Decimal, ) -> Result { // Simple Kelly-based position sizing let confidence = prediction.confidence; let edge = (confidence - 0.5) * 2.0; // Convert to [-1, 1] range if edge <= 0.0 { return Ok(Decimal::ZERO); } // Kelly fraction with conservative scaling let kelly_size = Decimal::try_from(edge * self.config.kelly_fraction).unwrap_or(Decimal::ZERO); let max_size = account_value * Decimal::try_from(self.config.max_drawdown).unwrap_or(Decimal::new(5, 2)); // 5% fallback let position_value = kelly_size * account_value; let position_size = if current_price > Decimal::ZERO { position_value / current_price } else { Decimal::ZERO }; Ok(position_size.min(max_size / current_price)) } /// Check if trade passes risk checks /// /// # Arguments /// * `signal` - Trading signal to validate /// * `current_position` - Current position if any /// * `account_value` - Current account value /// /// # Returns /// * `Result` - True if trade passes all risk checks fn validate_trade( &self, signal: &TradingSignal, _current_position: Option<&Position>, account_value: Decimal, ) -> Result { // Check position size limits if let Some(price) = signal.target_price { let trade_value = signal.quantity.to_decimal()? * price.to_decimal()?; let position_fraction = trade_value / account_value; if position_fraction > Decimal::try_from(self.config.max_drawdown).unwrap_or(Decimal::new(10, 2)) { debug!( "Trade rejected: position size too large ({:.2}%)", position_fraction * Decimal::from(100) ); return Ok(false); } } // Additional risk checks can be added here Ok(true) } } impl AdaptiveStrategyRunner { /// Create new adaptive strategy runner /// /// # Arguments /// * `config` - Configuration for adaptive strategy /// /// # Returns /// * `Self` - New adaptive strategy runner with initialized components pub fn new(config: AdaptiveStrategyConfig) -> Self { let feature_extractor = Arc::new(FeatureExtractor::new(config.feature_settings.clone())); let risk_manager = Arc::new(RiskManager::new(config.risk_settings.clone())); Self { config, market_state: Arc::new(RwLock::new(MarketState::default())), predictions_cache: Arc::new(DashMap::new()), performance_tracker: Arc::new(RwLock::new(PerformanceTracker::default())), feature_extractor, risk_manager, } } /// Get ensemble prediction from all active models (optimized for HFT performance) /// /// # Arguments /// * `features` - Feature vector for prediction /// /// # Returns /// * `Result` - Ensemble prediction with confidence-weighted averaging /// /// # Note /// Uses lock-free caching and parallel model execution for low-latency performance async fn get_ensemble_prediction(&self, features: &Features) -> Result { let registry = get_global_registry(); // OPTIMIZATION: Use predict_selected for parallel model execution let predictions = registry .predict_selected(&self.config.active_models, features) .await; // Filter successful predictions let valid_predictions: Vec = predictions .into_iter() .filter_map(|result| result.ok()) .collect(); if valid_predictions.is_empty() { return Err(anyhow::anyhow!("No valid predictions from any model")); } // Ensemble using confidence-weighted average let total_confidence: f64 = valid_predictions.iter().map(|p| p.confidence).sum(); if total_confidence == 0.0 { return Err(anyhow::anyhow!("Zero total confidence in predictions")); } let weighted_value = valid_predictions .iter() .map(|p| p.value * p.confidence) .sum::() / total_confidence; let ensemble_confidence = valid_predictions.iter().map(|p| p.confidence).sum::() / valid_predictions.len() as f64; // OPTIMIZATION: Use lock-free DashMap instead of async RwLock for prediction in &valid_predictions { self.predictions_cache .insert(prediction.model_id.clone(), prediction.clone()); } Ok(ModelPrediction::new( "ensemble".to_string(), weighted_value, ensemble_confidence, )) } /// Generate trading signal from prediction /// /// # Arguments /// * `prediction` - Model prediction with confidence and direction /// * `symbol` - Symbol to trade /// * `current_price` - Current market price /// * `account_value` - Current account value for position sizing /// /// # Returns /// * `Result>` - Trading signal if confidence threshold is met fn generate_signal( &self, prediction: &ModelPrediction, symbol: Symbol, current_price: Decimal, account_value: Decimal, ) -> Result> { // Check confidence threshold if prediction.confidence < self.config.min_confidence { debug!( "Prediction confidence {:.3} below threshold {:.3}", prediction.confidence, self.config.min_confidence ); return Ok(None); } // Determine trade direction let side = if prediction.value > 0.5 { OrderSide::Buy } else if prediction.value < -0.5 { OrderSide::Sell } else { return Ok(None); // Neutral signal }; // Calculate position size let quantity = self.risk_manager .calculate_position_size(prediction, account_value, current_price)?; if quantity <= Decimal::ZERO { return Ok(None); } let signal_type = match side { OrderSide::Buy => SignalType::Buy, OrderSide::Sell => SignalType::Sell, }; let quantity_as_quantity = Quantity::from_f64(quantity.to_f64().unwrap_or(0.0))?; let mut metadata = HashMap::new(); metadata.insert("strategy".to_string(), serde_json::json!("adaptive_ml")); metadata.insert( "ensemble_confidence".to_string(), serde_json::json!(prediction.confidence), ); metadata.insert( "prediction_value".to_string(), serde_json::json!(prediction.value), ); metadata.insert( "model_count".to_string(), serde_json::json!(self.config.active_models.len()), ); let signal = TradingSignal { symbol, signal_type, quantity: quantity_as_quantity, target_price: Some(Price::from_f64(current_price.to_f64().unwrap_or(0.0))?), stop_loss: None, take_profit: None, confidence: Decimal::try_from(prediction.confidence).unwrap_or(Decimal::ZERO), metadata, }; Ok(Some(signal)) } } #[async_trait(?Send)] impl Strategy for AdaptiveStrategyRunner { fn name(&self) -> &str { "adaptive_ml_strategy" } async fn initialize( &mut self, initial_capital: Decimal, _config: StrategyConfig, ) -> Result<()> { info!( "Initializing Adaptive ML Strategy with capital: {}", initial_capital ); { let mut tracker = self.performance_tracker.write(); tracker.peak_value = initial_capital; } // Verify models are available let registry = get_global_registry(); let available_models = registry.get_model_names(); for model_name in &self.config.active_models { if !available_models.contains(model_name) { warn!("Model {} not found in registry", model_name); } } info!("Adaptive ML Strategy initialized successfully"); Ok(()) } async fn on_market_event( &mut self, event: &MarketEvent, context: &StrategyContext, ) -> Result> { let mut signals = Vec::new(); match event { MarketEvent::Trade { symbol, price, timestamp, .. } => { // Update market state { let mut state = self.market_state.write(); state.current_time = *timestamp; state.price_history.push((*timestamp, price.to_decimal()?)); // Note: volume not available in MarketEvent::Trade, using placeholder // state.volume_history.push((*timestamp, Volume::ZERO)); // Keep only recent history let max_history = self.config.lookback_period; if state.price_history.len() > max_history { let excess = state.price_history.len() - max_history; state.price_history.drain(0..excess); } if state.volume_history.len() > max_history { let excess = state.volume_history.len() - max_history; state.volume_history.drain(0..excess); } } // Extract features and get prediction let market_state = self.market_state.read().clone(); if market_state.price_history.len() >= 10 { // Minimum data for prediction match self.feature_extractor.extract_features(&market_state).await { Ok(features) => { match self.get_ensemble_prediction(&features).await { Ok(prediction) => { if let Some(signal) = self.generate_signal( &prediction, symbol.clone(), price.to_decimal()?, context.account_balance, )? { // Validate trade with risk manager let current_position = context.positions.get(symbol); if self.risk_manager.validate_trade( &signal, current_position, context.account_balance, )? { signals.push(signal); } } } Err(e) => { debug!("Prediction failed: {}", e); } } } Err(e) => { debug!("Feature extraction failed: {}", e); } } } } _ => { // Handle other event types if needed } } Ok(signals) } async fn on_order_update(&mut self, order: &Order, _context: &StrategyContext) -> Result<()> { if order.status == OrderStatus::Filled { let mut tracker = self.performance_tracker.write(); tracker.total_trades += 1; info!( "Order filled: {} {} @ {}", order.side, order.quantity, order .average_price .unwrap_or_else(|| order.price.unwrap_or(Price::ZERO)) ); } Ok(()) } async fn on_position_update( &mut self, position: &Position, context: &StrategyContext, ) -> Result<()> { // Update market state with current position { let mut state = self.market_state.write(); state.current_position = Some(position.clone()); } // Update performance tracking { let mut tracker = self.performance_tracker.write(); if context.account_balance > tracker.peak_value { tracker.peak_value = context.account_balance; tracker.current_drawdown = Decimal::ZERO; } else { tracker.current_drawdown = (tracker.peak_value - context.account_balance) / tracker.peak_value; if tracker.current_drawdown > tracker.max_drawdown { tracker.max_drawdown = tracker.current_drawdown; } } } Ok(()) } async fn finalize(&mut self, context: &StrategyContext) -> Result { let tracker = self.performance_tracker.read(); let total_return = if tracker.peak_value > Decimal::ZERO { (context.account_balance - tracker.peak_value) / tracker.peak_value } else { Decimal::ZERO }; let win_rate = if tracker.total_trades > 0 { Decimal::from(tracker.winning_trades) / Decimal::from(tracker.total_trades) } else { Decimal::ZERO }; // Calculate Sharpe ratio (simplified) let sharpe_ratio = if tracker.max_drawdown > Decimal::ZERO { total_return / tracker.max_drawdown } else { Decimal::ZERO }; Ok(StrategyResult { strategy_name: "adaptive_ml_strategy".to_string(), total_return, annualized_return: total_return, // Simplified max_drawdown: tracker.max_drawdown, sharpe_ratio, total_trades: tracker.total_trades, win_rate, avg_trade_return: if tracker.total_trades > 0 { tracker.total_pnl / Decimal::from(tracker.total_trades) } else { Decimal::ZERO }, final_value: context.account_balance, trades: vec![], // Would be populated with detailed trade records performance_timeline: vec![], // Would be populated with performance snapshots }) } async fn get_state(&self) -> Result { let market_state = self.market_state.read(); let tracker = self.performance_tracker.read(); let cache = &self.predictions_cache; Ok(serde_json::json!({ "strategy_name": "adaptive_ml_strategy", "config": self.config, "current_time": market_state.current_time, "price_history_length": market_state.price_history.len(), "volume_history_length": market_state.volume_history.len(), "current_position": market_state.current_position, "total_trades": tracker.total_trades, "max_drawdown": tracker.max_drawdown, "cached_predictions": cache.len(), "active_models": self.config.active_models, })) } } /// Create a configured adaptive strategy runner /// /// # Returns /// * `AdaptiveStrategyRunner` - Strategy runner with default configuration pub fn create_adaptive_strategy() -> AdaptiveStrategyRunner { AdaptiveStrategyRunner::new(AdaptiveStrategyConfig::default()) } /// Create adaptive strategy with custom configuration /// /// # Arguments /// * `config` - Custom adaptive strategy configuration /// /// # Returns /// * `AdaptiveStrategyRunner` - Strategy runner with specified configuration pub fn create_adaptive_strategy_with_config( config: AdaptiveStrategyConfig, ) -> AdaptiveStrategyRunner { AdaptiveStrategyRunner::new(config) } #[cfg(test)] mod tests { use super::*; #[test] fn test_adaptive_strategy_config_default() { let config = AdaptiveStrategyConfig::default(); assert_eq!(config.active_models.len(), 5); assert_eq!(config.min_confidence, 0.65); assert!(config.max_position_size > 0.0); } #[test] fn test_risk_settings_default() { let risk = RiskSettings::default(); assert_eq!(risk.max_drawdown, 0.10); assert!(risk.kelly_fraction > 0.0); } #[tokio::test] async fn test_adaptive_strategy_creation() { let strategy = create_adaptive_strategy(); assert_eq!(strategy.name(), "adaptive_ml_strategy"); } #[tokio::test] async fn test_feature_extractor() { let config = FeatureSettings::default(); let extractor = FeatureExtractor::new(config); let mut market_state = MarketState::default(); market_state.price_history = vec![ (Utc::now(), Decimal::from(100)), (Utc::now(), Decimal::from(101)), (Utc::now(), Decimal::from(102)), ]; let features = extractor.extract_features(&market_state).await; assert!(features.is_ok()); } }