diff --git a/crates/ml/src/hyperopt/adapters/diffusion.rs b/crates/ml/src/hyperopt/adapters/diffusion.rs index 07df9986b..07602a340 100644 --- a/crates/ml/src/hyperopt/adapters/diffusion.rs +++ b/crates/ml/src/hyperopt/adapters/diffusion.rs @@ -44,6 +44,10 @@ pub struct DiffusionParams { pub weight_decay: f64, /// Gradient clipping max norm (log scale). pub grad_clip: f64, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for DiffusionParams { @@ -58,6 +62,8 @@ impl Default for DiffusionParams { batch_size: 32, weight_decay: 1e-4, grad_clip: 1.0, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } @@ -74,12 +80,14 @@ impl ParameterSpace for DiffusionParams { (4.0, 256.0), // batch_size (1e-6_f64.ln(), 1e-2_f64.ln()), // weight_decay (log) (0.5_f64.ln(), 5.0_f64.ln()), // grad_clip (log) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } fn from_continuous(x: &[f64]) -> Result { - if x.len() != 9 { - return Err(MLError::ConfigError(format!("Expected 9 params, got {}", x.len()))); + if x.len() != 11 { + return Err(MLError::ConfigError(format!("Expected 11 params, got {}", x.len()))); } Ok(Self { learning_rate: x.first().copied().unwrap_or(-9.21).exp(), @@ -91,6 +99,8 @@ impl ParameterSpace for DiffusionParams { batch_size: x.get(6).copied().unwrap_or(32.0).round().max(4.0) as usize, weight_decay: x.get(7).copied().unwrap_or(-9.21).exp(), grad_clip: x.get(8).copied().unwrap_or(0.0).exp(), + signal_high_bps: x.get(9).copied().unwrap_or(10.0).clamp(1.0, 30.0), + signal_low_bps: x.get(10).copied().unwrap_or(5.0).clamp(0.5, 15.0), }) } @@ -105,6 +115,8 @@ impl ParameterSpace for DiffusionParams { self.batch_size as f64, self.weight_decay.ln(), self.grad_clip.ln(), + self.signal_high_bps, + self.signal_low_bps, ] } @@ -113,6 +125,7 @@ impl ParameterSpace for DiffusionParams { "learning_rate", "num_timesteps", "sampling_steps", "hidden_dim", "num_layers", "time_embed_dim", "batch_size", "weight_decay", "grad_clip", + "signal_high_bps", "signal_low_bps", ] } @@ -146,6 +159,10 @@ pub struct DiffusionMetrics { pub train_loss: f64, /// Number of epochs completed. pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } impl Default for DiffusionMetrics { @@ -154,6 +171,8 @@ impl Default for DiffusionMetrics { val_loss: 0.0, train_loss: 0.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, } } } @@ -327,6 +346,8 @@ impl HyperparameterOptimizable for DiffusionTrainer { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: epoch, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -364,6 +385,8 @@ impl HyperparameterOptimizable for DiffusionTrainer { val_loss: best_val_loss, train_loss: last_train_loss, epochs_completed: epoch + 1, + backtest_sharpe: None, + backtest_trades: None, }); } } @@ -372,6 +395,8 @@ impl HyperparameterOptimizable for DiffusionTrainer { val_loss: best_val_loss, train_loss: last_train_loss, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }) })); @@ -379,11 +404,11 @@ impl HyperparameterOptimizable for DiffusionTrainer { Ok(Ok(m)) => m, Ok(Err(e)) => { warn!("Diffusion training failed: {}", e); - DiffusionMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0 } + DiffusionMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } Err(_) => { warn!("Diffusion training panicked (likely OOM)"); - DiffusionMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0 } + DiffusionMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } }; @@ -414,6 +439,9 @@ impl HyperparameterOptimizable for DiffusionTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } diff --git a/crates/ml/src/hyperopt/adapters/kan.rs b/crates/ml/src/hyperopt/adapters/kan.rs index 0b8a3d9ee..41abe1d8e 100644 --- a/crates/ml/src/hyperopt/adapters/kan.rs +++ b/crates/ml/src/hyperopt/adapters/kan.rs @@ -43,6 +43,10 @@ pub struct KANParams { pub grad_clip: f64, /// Training batch size pub batch_size: usize, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for KANParams { @@ -56,12 +60,14 @@ impl Default for KANParams { weight_decay: 1e-4, grad_clip: 1.0, batch_size: 32, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } /// Number of continuous parameters in the KAN parameter space. -const NUM_PARAMS: usize = 8; +const NUM_PARAMS: usize = 10; impl ParameterSpace for KANParams { fn continuous_bounds() -> Vec<(f64, f64)> { @@ -74,6 +80,8 @@ impl ParameterSpace for KANParams { (1e-6_f64.ln(), 1e-2_f64.ln()), // weight_decay (log) (0.5_f64.ln(), 5.0_f64.ln()), // grad_clip (log) (8.0, 512.0), // batch_size (linear) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } @@ -90,6 +98,8 @@ impl ParameterSpace for KANParams { weight_decay: x.get(5).copied().unwrap_or(0.0).exp(), grad_clip: x.get(6).copied().unwrap_or(0.0).exp(), batch_size: x.get(7).copied().unwrap_or(32.0).round().max(8.0) as usize, + signal_high_bps: x.get(8).copied().unwrap_or(10.0).clamp(1.0, 30.0), + signal_low_bps: x.get(9).copied().unwrap_or(5.0).clamp(0.5, 15.0), }) } @@ -103,6 +113,8 @@ impl ParameterSpace for KANParams { self.weight_decay.ln(), self.grad_clip.ln(), self.batch_size as f64, + self.signal_high_bps, + self.signal_low_bps, ] } @@ -116,6 +128,8 @@ impl ParameterSpace for KANParams { "weight_decay", "grad_clip", "batch_size", + "signal_high_bps", + "signal_low_bps", ] } @@ -149,6 +163,10 @@ pub struct KANMetrics { pub train_loss: f64, /// Number of epochs completed pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// KAN trainer for hyperparameter optimization. @@ -315,6 +333,8 @@ impl HyperparameterOptimizable for KANTrainer { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: epoch, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -352,6 +372,8 @@ impl HyperparameterOptimizable for KANTrainer { val_loss: best_val_loss, train_loss: last_train_loss, epochs_completed: epoch + 1, + backtest_sharpe: None, + backtest_trades: None, }); } } @@ -360,6 +382,8 @@ impl HyperparameterOptimizable for KANTrainer { val_loss: best_val_loss, train_loss: last_train_loss, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }) })); @@ -367,11 +391,11 @@ impl HyperparameterOptimizable for KANTrainer { Ok(Ok(m)) => m, Ok(Err(e)) => { warn!("KAN training failed: {}", e); - KANMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0 } + KANMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } Err(_) => { warn!("KAN training panicked (likely OOM)"); - KANMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0 } + KANMetrics { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } }; @@ -402,6 +426,9 @@ impl HyperparameterOptimizable for KANTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } @@ -450,7 +477,7 @@ mod tests { assert!(result.is_err()); let err_msg = format!("{}", result.unwrap_err()); assert!( - err_msg.contains("Expected 8"), + err_msg.contains("Expected 10"), "Error should mention expected count: {}", err_msg ); diff --git a/crates/ml/src/hyperopt/adapters/liquid.rs b/crates/ml/src/hyperopt/adapters/liquid.rs index 4194130af..5d72a1637 100644 --- a/crates/ml/src/hyperopt/adapters/liquid.rs +++ b/crates/ml/src/hyperopt/adapters/liquid.rs @@ -38,7 +38,7 @@ const MODEL_OVERHEAD_MB: f64 = 40.0; const MB_PER_SAMPLE: f64 = 0.008; /// Number of continuous dimensions in the Liquid CfC v2 parameter space. -const NUM_PARAMS: usize = 8; +const NUM_PARAMS: usize = 10; /// Hyperparameters for Liquid CfC v2 network optimization. /// @@ -64,6 +64,10 @@ pub struct LiquidParams { pub gradient_clip: f64, /// Batch size for training (linear scale) pub batch_size: usize, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for LiquidParams { @@ -77,6 +81,8 @@ impl Default for LiquidParams { dropout: 0.1, gradient_clip: 1.0, batch_size: 32, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } @@ -92,6 +98,8 @@ impl ParameterSpace for LiquidParams { (0.0, 0.5), // 5: dropout (linear) (0.1_f64.ln(), 10.0_f64.ln()), // 6: gradient_clip (log scale) (8.0, 512.0), // 7: batch_size (linear) + (1.0, 30.0), // 8: signal_high_bps + (0.5, 15.0), // 9: signal_low_bps ] } @@ -134,6 +142,15 @@ impl ParameterSpace for LiquidParams { .ok_or_else(|| MLError::ConfigError("LiquidParams: missing batch_size at index 7".to_owned()))? .round() as usize; + let signal_high_bps = x + .get(8) + .ok_or_else(|| MLError::ConfigError("LiquidParams: missing signal_high_bps at index 8".to_owned()))? + .clamp(1.0, 30.0); + let signal_low_bps = x + .get(9) + .ok_or_else(|| MLError::ConfigError("LiquidParams: missing signal_low_bps at index 9".to_owned()))? + .clamp(0.5, 15.0); + Ok(Self { learning_rate, hidden_size, @@ -143,6 +160,8 @@ impl ParameterSpace for LiquidParams { dropout, gradient_clip, batch_size, + signal_high_bps, + signal_low_bps, }) } @@ -156,6 +175,8 @@ impl ParameterSpace for LiquidParams { self.dropout, // 5: linear self.gradient_clip.ln(), // 6: log scale self.batch_size as f64, // 7: linear + self.signal_high_bps, // 8: linear + self.signal_low_bps, // 9: linear ] } @@ -169,6 +190,8 @@ impl ParameterSpace for LiquidParams { "dropout", "gradient_clip", "batch_size", + "signal_high_bps", + "signal_low_bps", ] } @@ -202,6 +225,10 @@ pub struct LiquidMetrics { pub train_loss: f64, /// Number of epochs completed pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// Liquid CfC v2 trainer for hyperparameter optimization. @@ -381,6 +408,8 @@ impl HyperparameterOptimizable for LiquidTrainer { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: epoch, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -419,6 +448,8 @@ impl HyperparameterOptimizable for LiquidTrainer { val_loss: best_val_loss, train_loss: last_train_loss, epochs_completed: epoch + 1, + backtest_sharpe: None, + backtest_trades: None, }); } } @@ -427,6 +458,8 @@ impl HyperparameterOptimizable for LiquidTrainer { val_loss: best_val_loss, train_loss: last_train_loss, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }) })); @@ -438,6 +471,8 @@ impl HyperparameterOptimizable for LiquidTrainer { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, } } Err(_) => { @@ -446,6 +481,8 @@ impl HyperparameterOptimizable for LiquidTrainer { val_loss: 1000.0, train_loss: 1000.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, } } }; @@ -480,6 +517,9 @@ impl HyperparameterOptimizable for LiquidTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } @@ -556,7 +596,7 @@ mod tests { let result = LiquidParams::from_continuous(&too_short); assert!(result.is_err()); - let too_long = vec![0.0; 10]; + let too_long = vec![0.0; 12]; let result = LiquidParams::from_continuous(&too_long); assert!(result.is_err()); } diff --git a/crates/ml/src/hyperopt/adapters/mamba2.rs b/crates/ml/src/hyperopt/adapters/mamba2.rs index 4b592820a..50c0752fe 100644 --- a/crates/ml/src/hyperopt/adapters/mamba2.rs +++ b/crates/ml/src/hyperopt/adapters/mamba2.rs @@ -93,6 +93,10 @@ pub struct Mamba2Params { pub sequence_stride: usize, /// P2: Normalization epsilon for layer norm (log-scale) pub norm_eps: f64, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for Mamba2Params { @@ -110,6 +114,8 @@ impl Default for Mamba2Params { lookback_window: 60, sequence_stride: 1, norm_eps: 1e-5, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } @@ -129,12 +135,14 @@ impl ParameterSpace for Mamba2Params { (30.0, 120.0), // lookback_window (linear) (1.0, 5.0), // sequence_stride (linear) (1e-6_f64.ln(), 1e-4_f64.ln()), // norm_eps (log scale) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } fn from_continuous(x: &[f64]) -> Result { - if x.len() != 12 { - return Err(MLError::ConfigError(format!("Expected 12 parameters, got {}", x.len()))); + if x.len() != 14 { + return Err(MLError::ConfigError(format!("Expected 14 parameters, got {}", x.len()))); } Ok(Self { @@ -150,6 +158,8 @@ impl ParameterSpace for Mamba2Params { lookback_window: x[9].round().max(30.0).min(120.0) as usize, sequence_stride: x[10].round().max(1.0).min(5.0) as usize, norm_eps: x[11].exp(), + signal_high_bps: x[12].clamp(1.0, 30.0), + signal_low_bps: x[13].clamp(0.5, 15.0), }) } @@ -167,6 +177,8 @@ impl ParameterSpace for Mamba2Params { self.lookback_window as f64, self.sequence_stride as f64, self.norm_eps.ln(), + self.signal_high_bps, + self.signal_low_bps, ] } @@ -184,6 +196,8 @@ impl ParameterSpace for Mamba2Params { "lookback_window", "sequence_stride", "norm_eps", + "signal_high_bps", + "signal_low_bps", ] } @@ -220,6 +234,10 @@ pub struct Mamba2Metrics { pub r_squared: f64, /// Number of epochs completed pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// MAMBA-2 trainer for hyperparameter optimization @@ -863,6 +881,8 @@ impl HyperparameterOptimizable for Mamba2Trainer { rmse: 1000.0, r_squared: 0.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -923,6 +943,8 @@ impl HyperparameterOptimizable for Mamba2Trainer { rmse: 1000.0, r_squared: 0.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, }); }, Err(_) => { @@ -937,6 +959,8 @@ impl HyperparameterOptimizable for Mamba2Trainer { rmse: 1000.0, r_squared: 0.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, }); }, }; @@ -957,6 +981,8 @@ impl HyperparameterOptimizable for Mamba2Trainer { rmse: final_epoch.loss.sqrt(), // Approximation r_squared: 1.0 - final_epoch.loss.min(1.0), // Approximation epochs_completed: training_history.len(), + backtest_sharpe: None, + backtest_trades: None, }; info!("Training completed:"); @@ -1018,6 +1044,9 @@ impl HyperparameterOptimizable for Mamba2Trainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } @@ -1041,10 +1070,12 @@ mod tests { lookback_window: 90, sequence_stride: 3, norm_eps: 5e-5, + signal_high_bps: 10.0, + signal_low_bps: 5.0, }; let continuous = params.to_continuous(); - assert_eq!(continuous.len(), 12, "Expected 12 continuous parameters"); + assert_eq!(continuous.len(), 14, "Expected 14 continuous parameters"); let recovered = Mamba2Params::from_continuous(&continuous).unwrap(); @@ -1060,12 +1091,14 @@ mod tests { assert_eq!(recovered.lookback_window, params.lookback_window); assert_eq!(recovered.sequence_stride, params.sequence_stride); assert!((recovered.norm_eps - params.norm_eps).abs() < 1e-12); + assert!((recovered.signal_high_bps - params.signal_high_bps).abs() < 1e-10); + assert!((recovered.signal_low_bps - params.signal_low_bps).abs() < 1e-10); } #[test] fn test_mamba2_params_bounds() { let bounds = Mamba2Params::continuous_bounds(); - assert_eq!(bounds.len(), 12, "Expected 12 parameter bounds"); + assert_eq!(bounds.len(), 14, "Expected 14 parameter bounds"); // Check log-scale bounds are reasonable assert!(bounds[0].0 < bounds[0].1); // learning_rate @@ -1087,7 +1120,7 @@ mod tests { #[test] fn test_param_names() { let names = Mamba2Params::param_names(); - assert_eq!(names.len(), 12, "Expected 12 parameter names"); + assert_eq!(names.len(), 14, "Expected 14 parameter names"); assert_eq!(names[0], "learning_rate"); assert_eq!(names[1], "batch_size"); assert_eq!(names[2], "dropout"); @@ -1100,6 +1133,8 @@ mod tests { assert_eq!(names[9], "lookback_window"); assert_eq!(names[10], "sequence_stride"); assert_eq!(names[11], "norm_eps"); + assert_eq!(names[12], "signal_high_bps"); + assert_eq!(names[13], "signal_low_bps"); } #[test] diff --git a/crates/ml/src/hyperopt/adapters/tft.rs b/crates/ml/src/hyperopt/adapters/tft.rs index 569dcfa18..613b03658 100644 --- a/crates/ml/src/hyperopt/adapters/tft.rs +++ b/crates/ml/src/hyperopt/adapters/tft.rs @@ -80,6 +80,10 @@ pub struct TFTParams { pub num_heads: usize, /// Dropout rate for regularization (linear scale) pub dropout: f64, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for TFTParams { @@ -90,6 +94,8 @@ impl Default for TFTParams { hidden_size: 256, num_heads: 8, dropout: 0.1, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } @@ -102,12 +108,14 @@ impl ParameterSpace for TFTParams { (0.0, 4.0), // hidden_size_index (0=128, 1=256, 2=512, 3=1024, 4=2048) (0.0, 2.0), // num_heads_index (0=4, 1=8, 2=16) (0.0, 0.3), // dropout (linear) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } fn from_continuous(x: &[f64]) -> Result { - if x.len() != 5 { - return Err(MLError::ConfigError(format!("Expected 5 parameters, got {}", x.len()))); + if x.len() != 7 { + return Err(MLError::ConfigError(format!("Expected 7 parameters, got {}", x.len()))); } // Map continuous indices to discrete values @@ -123,6 +131,8 @@ impl ParameterSpace for TFTParams { hidden_size: hidden_sizes[hidden_size_idx], num_heads: num_heads_options[num_heads_idx], dropout: x[4].clamp(0.0, 0.3), + signal_high_bps: x[5].clamp(1.0, 30.0), + signal_low_bps: x[6].clamp(0.5, 15.0), }) } @@ -150,6 +160,8 @@ impl ParameterSpace for TFTParams { hidden_size_idx, num_heads_idx, self.dropout, + self.signal_high_bps, + self.signal_low_bps, ] } @@ -160,6 +172,8 @@ impl ParameterSpace for TFTParams { "hidden_size", "num_heads", "dropout", + "signal_high_bps", + "signal_low_bps", ] } @@ -206,6 +220,10 @@ pub struct TFTMetrics { pub val_rmse: f64, /// Number of epochs completed pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// TFT trainer for hyperparameter optimization @@ -412,6 +430,8 @@ impl HyperparameterOptimizable for TFTTrainer { train_loss: 1000.0, val_rmse: 1000.0, epochs_completed: 0, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -474,6 +494,8 @@ impl HyperparameterOptimizable for TFTTrainer { train_loss: training_metrics.train_loss, val_rmse: training_metrics.rmse, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }; info!( @@ -523,7 +545,11 @@ impl HyperparameterOptimizable for TFTTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { - // Minimize validation loss (primary objective) + // Prefer GPU backtest fitness when available + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } + // Fallback: minimize validation loss metrics.val_loss } } @@ -541,6 +567,8 @@ mod tests { hidden_size: 256, num_heads: 8, dropout: 0.1, + signal_high_bps: 10.0, + signal_low_bps: 5.0, }; let continuous = params.to_continuous(); @@ -556,7 +584,7 @@ mod tests { #[test] fn test_tft_params_bounds() { let bounds = TFTParams::continuous_bounds(); - assert_eq!(bounds.len(), 5); + assert_eq!(bounds.len(), 7); // Check log-scale bounds are reasonable assert!(bounds[0].0 < bounds[0].1); // learning_rate @@ -568,20 +596,24 @@ mod tests { // Check linear bounds assert_eq!(bounds[1], (16.0, 512.0)); // batch_size assert_eq!(bounds[4], (0.0, 0.3)); // dropout + + // Check signal threshold bounds + assert_eq!(bounds[5], (1.0, 30.0)); // signal_high_bps + assert_eq!(bounds[6], (0.5, 15.0)); // signal_low_bps } #[test] fn test_tft_discrete_params() { // Test all discrete hidden_size values for (idx, expected_size) in [(0.0, 128), (1.0, 256), (2.0, 512)] { - let x = vec![(-4.6_f64).ln(), 64.0, idx, 1.0, 0.1]; + let x = vec![(-4.6_f64).ln(), 64.0, idx, 1.0, 0.1, 10.0, 5.0]; let params = TFTParams::from_continuous(&x).unwrap(); assert_eq!(params.hidden_size, expected_size); } // Test all discrete num_heads values for (idx, expected_heads) in [(0.0, 4), (1.0, 8), (2.0, 16)] { - let x = vec![(-4.6_f64).ln(), 64.0, 1.0, idx, 0.1]; + let x = vec![(-4.6_f64).ln(), 64.0, 1.0, idx, 0.1, 10.0, 5.0]; let params = TFTParams::from_continuous(&x).unwrap(); assert_eq!(params.num_heads, expected_heads); } @@ -590,12 +622,14 @@ mod tests { #[test] fn test_param_names() { let names = TFTParams::param_names(); - assert_eq!(names.len(), 5); + assert_eq!(names.len(), 7); assert_eq!(names[0], "learning_rate"); assert_eq!(names[1], "batch_size"); assert_eq!(names[2], "hidden_size"); assert_eq!(names[3], "num_heads"); assert_eq!(names[4], "dropout"); + assert_eq!(names[5], "signal_high_bps"); + assert_eq!(names[6], "signal_low_bps"); } #[test] diff --git a/crates/ml/src/hyperopt/adapters/tggn.rs b/crates/ml/src/hyperopt/adapters/tggn.rs index 39ce59757..16bdf8cf2 100644 --- a/crates/ml/src/hyperopt/adapters/tggn.rs +++ b/crates/ml/src/hyperopt/adapters/tggn.rs @@ -53,6 +53,10 @@ pub struct TGGNParams { pub weight_decay: f64, /// Maximum gradient norm for clipping (log-scale) pub grad_clip: f64, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for TGGNParams { @@ -68,12 +72,14 @@ impl Default for TGGNParams { batch_size: 32, weight_decay: 1e-4, grad_clip: 1.0, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } /// Number of continuous parameters in the TGGN parameter space. -const NUM_PARAMS: usize = 10; +const NUM_PARAMS: usize = 12; impl ParameterSpace for TGGNParams { fn continuous_bounds() -> Vec<(f64, f64)> { @@ -88,6 +94,8 @@ impl ParameterSpace for TGGNParams { (8.0, 512.0), // batch_size (linear) (1e-6_f64.ln(), 1e-2_f64.ln()), // weight_decay (log) (0.5_f64.ln(), 5.0_f64.ln()), // grad_clip (log) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } @@ -106,6 +114,8 @@ impl ParameterSpace for TGGNParams { batch_size: x.get(7).copied().unwrap_or(32.0).round().max(8.0) as usize, weight_decay: x.get(8).copied().unwrap_or(0.0).exp(), grad_clip: x.get(9).copied().unwrap_or(0.0).exp(), + signal_high_bps: x.get(10).copied().unwrap_or(10.0).clamp(1.0, 30.0), + signal_low_bps: x.get(11).copied().unwrap_or(5.0).clamp(0.5, 15.0), }) } @@ -121,6 +131,8 @@ impl ParameterSpace for TGGNParams { self.batch_size as f64, self.weight_decay.ln(), self.grad_clip.ln(), + self.signal_high_bps, + self.signal_low_bps, ] } @@ -136,6 +148,8 @@ impl ParameterSpace for TGGNParams { "batch_size", "weight_decay", "grad_clip", + "signal_high_bps", + "signal_low_bps", ] } @@ -174,6 +188,10 @@ pub struct TGGNMetrics { pub directional_accuracy: f64, /// Number of epochs completed pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// TGGN trainer for hyperparameter optimization. @@ -341,6 +359,8 @@ impl HyperparameterOptimizable for TGGNTrainer { train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: epoch, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -379,6 +399,8 @@ impl HyperparameterOptimizable for TGGNTrainer { train_loss: last_train_loss, directional_accuracy: 0.0, epochs_completed: epoch + 1, + backtest_sharpe: None, + backtest_trades: None, }); } } @@ -388,6 +410,8 @@ impl HyperparameterOptimizable for TGGNTrainer { train_loss: last_train_loss, directional_accuracy: 0.0, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }) })); @@ -395,11 +419,11 @@ impl HyperparameterOptimizable for TGGNTrainer { Ok(Ok(m)) => m, Ok(Err(e)) => { warn!("TGGN training failed: {}", e); - TGGNMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0 } + TGGNMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } Err(_) => { warn!("TGGN training panicked (likely OOM)"); - TGGNMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0 } + TGGNMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } }; @@ -430,6 +454,9 @@ impl HyperparameterOptimizable for TGGNTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } @@ -482,7 +509,7 @@ mod tests { let result = TGGNParams::from_continuous(&[0.1, 0.2]); assert!(result.is_err()); let err_msg = format!("{}", result.unwrap_err()); - assert!(err_msg.contains("Expected 10"), "Error should mention expected count: {}", err_msg); + assert!(err_msg.contains("Expected 12"), "Error should mention expected count: {}", err_msg); } #[test] diff --git a/crates/ml/src/hyperopt/adapters/tlob.rs b/crates/ml/src/hyperopt/adapters/tlob.rs index c6e6f5de3..f669332c9 100644 --- a/crates/ml/src/hyperopt/adapters/tlob.rs +++ b/crates/ml/src/hyperopt/adapters/tlob.rs @@ -49,6 +49,10 @@ pub struct TLOBParams { pub weight_decay: f64, /// Maximum gradient norm for clipping (log-scale) pub grad_clip: f64, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for TLOBParams { @@ -63,12 +67,14 @@ impl Default for TLOBParams { batch_size: 32, weight_decay: 1e-4, grad_clip: 1.0, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } /// Number of continuous parameters in the TLOB parameter space. -const NUM_PARAMS: usize = 9; +const NUM_PARAMS: usize = 11; impl ParameterSpace for TLOBParams { fn continuous_bounds() -> Vec<(f64, f64)> { @@ -82,6 +88,8 @@ impl ParameterSpace for TLOBParams { (8.0, 512.0), // batch_size (linear) (1e-6_f64.ln(), 1e-2_f64.ln()), // weight_decay (log) (0.5_f64.ln(), 5.0_f64.ln()), // grad_clip (log) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } @@ -99,6 +107,8 @@ impl ParameterSpace for TLOBParams { batch_size: x.get(6).copied().unwrap_or(32.0).round().max(8.0) as usize, weight_decay: x.get(7).copied().unwrap_or(0.0).exp(), grad_clip: x.get(8).copied().unwrap_or(0.0).exp(), + signal_high_bps: x.get(9).copied().unwrap_or(10.0).clamp(1.0, 30.0), + signal_low_bps: x.get(10).copied().unwrap_or(5.0).clamp(0.5, 15.0), }) } @@ -113,6 +123,8 @@ impl ParameterSpace for TLOBParams { self.batch_size as f64, self.weight_decay.ln(), self.grad_clip.ln(), + self.signal_high_bps, + self.signal_low_bps, ] } @@ -127,6 +139,8 @@ impl ParameterSpace for TLOBParams { "batch_size", "weight_decay", "grad_clip", + "signal_high_bps", + "signal_low_bps", ] } @@ -162,6 +176,10 @@ pub struct TLOBMetrics { pub directional_accuracy: f64, /// Number of epochs completed pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// TLOB trainer for hyperparameter optimization. @@ -321,6 +339,8 @@ impl HyperparameterOptimizable for TLOBTrainer { train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: epoch, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -359,6 +379,8 @@ impl HyperparameterOptimizable for TLOBTrainer { train_loss: last_train_loss, directional_accuracy: 0.0, epochs_completed: epoch + 1, + backtest_sharpe: None, + backtest_trades: None, }); } } @@ -368,6 +390,8 @@ impl HyperparameterOptimizable for TLOBTrainer { train_loss: last_train_loss, directional_accuracy: 0.0, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }) })); @@ -375,11 +399,11 @@ impl HyperparameterOptimizable for TLOBTrainer { Ok(Ok(m)) => m, Ok(Err(e)) => { warn!("TLOB training failed: {}", e); - TLOBMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0 } + TLOBMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } Err(_) => { warn!("TLOB training panicked (likely OOM)"); - TLOBMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0 } + TLOBMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } }; @@ -410,6 +434,9 @@ impl HyperparameterOptimizable for TLOBTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } @@ -458,7 +485,7 @@ mod tests { let result = TLOBParams::from_continuous(&[0.1, 0.2]); assert!(result.is_err()); let err_msg = format!("{}", result.unwrap_err()); - assert!(err_msg.contains("Expected 9"), "Error should mention expected count: {}", err_msg); + assert!(err_msg.contains("Expected 11"), "Error should mention expected count: {}", err_msg); } #[test] diff --git a/crates/ml/src/hyperopt/adapters/xlstm.rs b/crates/ml/src/hyperopt/adapters/xlstm.rs index e288e12bf..f864abcd1 100644 --- a/crates/ml/src/hyperopt/adapters/xlstm.rs +++ b/crates/ml/src/hyperopt/adapters/xlstm.rs @@ -35,6 +35,10 @@ pub struct XLSTMParams { pub batch_size: usize, pub weight_decay: f64, pub grad_clip: f64, + /// Signal high threshold in basis points (GPU backtest action mapping) + pub signal_high_bps: f64, + /// Signal low threshold in basis points (GPU backtest action mapping) + pub signal_low_bps: f64, } impl Default for XLSTMParams { @@ -49,6 +53,8 @@ impl Default for XLSTMParams { batch_size: 32, weight_decay: 1e-4, grad_clip: 1.0, + signal_high_bps: 10.0, + signal_low_bps: 5.0, } } } @@ -65,12 +71,14 @@ impl ParameterSpace for XLSTMParams { (8.0, 512.0), // batch_size (1e-6_f64.ln(), 1e-2_f64.ln()), // weight_decay (log) (0.5_f64.ln(), 5.0_f64.ln()), // grad_clip (log) + (1.0, 30.0), // signal_high_bps + (0.5, 15.0), // signal_low_bps ] } fn from_continuous(x: &[f64]) -> Result { - if x.len() != 9 { - return Err(MLError::ConfigError(format!("Expected 9 params, got {}", x.len()))); + if x.len() != 11 { + return Err(MLError::ConfigError(format!("Expected 11 params, got {}", x.len()))); } let hidden_dim = x.get(1).copied().unwrap_or(64.0).round().max(32.0) as usize; let num_heads = x.get(3).copied().unwrap_or(4.0).round().max(1.0) as usize; @@ -86,6 +94,8 @@ impl ParameterSpace for XLSTMParams { batch_size: x.get(6).copied().unwrap_or(32.0).round().max(8.0) as usize, weight_decay: x.get(7).copied().unwrap_or(-9.21).exp(), grad_clip: x.get(8).copied().unwrap_or(0.0).exp(), + signal_high_bps: x.get(9).copied().unwrap_or(10.0).clamp(1.0, 30.0), + signal_low_bps: x.get(10).copied().unwrap_or(5.0).clamp(0.5, 15.0), }) } @@ -100,6 +110,8 @@ impl ParameterSpace for XLSTMParams { self.batch_size as f64, self.weight_decay.ln(), self.grad_clip.ln(), + self.signal_high_bps, + self.signal_low_bps, ] } @@ -107,6 +119,7 @@ impl ParameterSpace for XLSTMParams { vec![ "learning_rate", "hidden_dim", "num_blocks", "num_heads", "slstm_ratio", "dropout", "batch_size", "weight_decay", "grad_clip", + "signal_high_bps", "signal_low_bps", ] } @@ -138,6 +151,10 @@ pub struct XLSTMMetrics { pub train_loss: f64, pub directional_accuracy: f64, pub epochs_completed: usize, + /// GPU walk-forward backtest Sharpe ratio + pub backtest_sharpe: Option, + /// GPU walk-forward backtest total trades + pub backtest_trades: Option, } /// xLSTM trainer for hyperparameter optimization. @@ -304,6 +321,8 @@ impl HyperparameterOptimizable for XLSTMTrainer { train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: epoch, + backtest_sharpe: None, + backtest_trades: None, }); } @@ -342,6 +361,8 @@ impl HyperparameterOptimizable for XLSTMTrainer { train_loss: last_train_loss, directional_accuracy: 0.0, epochs_completed: epoch + 1, + backtest_sharpe: None, + backtest_trades: None, }); } } @@ -351,6 +372,8 @@ impl HyperparameterOptimizable for XLSTMTrainer { train_loss: last_train_loss, directional_accuracy: 0.0, epochs_completed: self.epochs, + backtest_sharpe: None, + backtest_trades: None, }) })); @@ -358,11 +381,11 @@ impl HyperparameterOptimizable for XLSTMTrainer { Ok(Ok(m)) => m, Ok(Err(e)) => { warn!("xLSTM training failed: {}", e); - XLSTMMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0 } + XLSTMMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } Err(_) => { warn!("xLSTM training panicked (likely OOM)"); - XLSTMMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0 } + XLSTMMetrics { val_loss: 1000.0, train_loss: 1000.0, directional_accuracy: 0.0, epochs_completed: 0, backtest_sharpe: None, backtest_trades: None } } }; @@ -393,6 +416,9 @@ impl HyperparameterOptimizable for XLSTMTrainer { } fn extract_objective(metrics: &Self::Metrics) -> f64 { + if let (Some(sharpe), Some(trades)) = (metrics.backtest_sharpe, metrics.backtest_trades) { + return crate::cuda_pipeline::signal_adapter::backtest_fitness(sharpe, trades, 30, 0.0); + } metrics.val_loss } } diff --git a/crates/ml/src/hyperopt/tests_argmin.rs b/crates/ml/src/hyperopt/tests_argmin.rs index 85228a9f9..6a20e6847 100644 --- a/crates/ml/src/hyperopt/tests_argmin.rs +++ b/crates/ml/src/hyperopt/tests_argmin.rs @@ -290,6 +290,8 @@ mod tests { lookback_window: 60, sequence_stride: 1, norm_eps: 1e-5, + signal_high_bps: 10.0, + signal_low_bps: 5.0, }; let continuous = params.to_continuous();