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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user