feat(hyperopt): add signal threshold params + backtest fitness to all 8 supervised adapters

All supervised hyperopt adapters (TFT, Mamba2, Liquid, KAN, xLSTM, TGGN,
TLOB, Diffusion) now include:
- signal_high_bps and signal_low_bps in ParameterSpace (tunable thresholds)
- backtest_sharpe and backtest_trades fields in Metrics structs
- extract_objective prefers GPU backtest fitness over val_loss when available
- All tests updated (101 passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-11 15:59:38 +01:00
parent 7750c558c5
commit e690b1c60e
9 changed files with 280 additions and 34 deletions

View File

@@ -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<Self, MLError> {
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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
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
}
}

View File

@@ -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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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
);

View File

@@ -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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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());
}

View File

@@ -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<Self, MLError> {
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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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]

View File

@@ -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<Self, MLError> {
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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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]

View File

@@ -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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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]

View File

@@ -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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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]

View File

@@ -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<Self, MLError> {
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<f32>,
/// GPU walk-forward backtest total trades
pub backtest_trades: Option<u32>,
}
/// 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
}
}

View File

@@ -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();