use crate::finance::ml::models::q_net::SimpleQNet;
use crate::finance::ml::training::replay_buffer::{ReplayBuffer, Experience};
use crate::finance::ml::errors::Result;
use crate::finance::ml::traits::MarketState;
use std::f32;
#[derive(Debug, Clone)]
pub struct AdvancedTrainingConfig {
pub num_episodes: u32,
pub early_stopping_threshold: f32,
pub early_stopping_patience: u32,
pub initial_epsilon: f32,
pub epsilon_decay: f32,
pub min_epsilon: f32,
pub gamma: f32,
pub learning_rate: f32,
pub batch_size: usize,
pub replay_capacity: usize,
pub mutation_rate: f32,
pub mutation_strength: f32,
}
impl Default for AdvancedTrainingConfig {
fn default() -> Self {
Self {
num_episodes: 100,
early_stopping_threshold: 0.01,
early_stopping_patience: 10,
initial_epsilon: 1.0,
epsilon_decay: 0.995,
min_epsilon: 0.1,
gamma: 0.99,
learning_rate: 0.001,
batch_size: 32,
replay_capacity: 10_000,
mutation_rate: 0.1,
mutation_strength: 0.01,
}
}
}
pub struct AdvancedTrainingCoordinator {
pub trainer: AdvancedQLearningTrainer,
pub config: AdvancedTrainingConfig,
pub current_episode: u32,
pub current_epsilon: f32,
pub reward_history: Vec<f32>,
pub loss_history: Vec<f32>,
pub best_loss: f32,
pub episodes_since_improvement: u32,
}
impl AdvancedTrainingCoordinator {
pub fn new(model: SimpleQNet, config: AdvancedTrainingConfig) -> Self {
let initial_epsilon = config.initial_epsilon;
Self {
trainer: AdvancedQLearningTrainer::new(model),
config,
current_episode: 0,
current_epsilon: initial_epsilon,
reward_history: Vec::new(),
loss_history: Vec::new(),
best_loss: f32::INFINITY,
episodes_since_improvement: 0,
}
}
pub fn train(&mut self) -> Result<TrainingResults> {
let mut episodes_run = 0;
let mut total_reward = 0.0;
for episode in 0..self.config.num_episodes {
self.current_episode = episode;
let reward = self.simulate_episode()?;
let loss = self.trainer.train_step()?;
self.reward_history.push(reward);
self.loss_history.push(loss);
total_reward += reward;
episodes_run += 1;
if self.should_stop() {
println!("Early stopping at episode {}", episode);
break;
}
self.decay_epsilon();
}
Ok(TrainingResults {
episodes_completed: episodes_run,
total_reward,
average_reward: total_reward / episodes_run as f32,
average_loss: self.loss_history.iter().sum::<f32>() / episodes_run as f32,
best_loss: self.best_loss,
final_epsilon: self.current_epsilon,
})
}
fn should_stop(&mut self) -> bool {
let latest_loss = *self.loss_history.last().unwrap_or(&f32::INFINITY);
if latest_loss < self.config.early_stopping_threshold {
return true;
}
if latest_loss < self.best_loss {
self.best_loss = latest_loss;
self.episodes_since_improvement = 0;
} else {
self.episodes_since_improvement += 1;
}
self.episodes_since_improvement >= self.config.early_stopping_patience
}
fn decay_epsilon(&mut self) {
self.current_epsilon = (self.current_epsilon * self.config.epsilon_decay)
.max(self.config.min_epsilon);
}
fn simulate_episode(&self) -> Result<f32> {
Ok(rand::random::<f32>() * 100.0 - 50.0)
}
pub fn export_csv(&self, path: &str) -> Result<()> {
use std::fs::File;
use std::io::Write;
let mut file = File::create(path)?;
writeln!(file, "episode,reward,loss,epsilon")?;
for (i, (reward, loss)) in self.reward_history.iter()
.zip(self.loss_history.iter())
.enumerate()
{
let epsilon = self.config.initial_epsilon *
(self.config.epsilon_decay.powi(i as i32));
writeln!(file, "{},{},{},{}", i, reward, loss, epsilon)?;
}
Ok(())
}
}
pub struct AdvancedQLearningTrainer {
pub model: SimpleQNet,
pub replay_buffer: ReplayBuffer,
pub gamma: f32,
pub learning_rate: f32,
pub batch_size: usize,
pub total_loss: f32,
pub training_steps: u32,
}
impl AdvancedQLearningTrainer {
pub fn new(model: SimpleQNet) -> Self {
Self {
model,
replay_buffer: ReplayBuffer::new(10_000),
gamma: 0.99,
learning_rate: 0.001,
batch_size: 32,
total_loss: 0.0,
training_steps: 0,
}
}
pub fn remember_experience(
&mut self,
state: Vec<f32>,
action: usize,
reward: f32,
next_state: Vec<f32>,
done: bool,
) {
let market_state = MarketState {
prices: state,
volatility: vec![0.1; 5],
agent_capital: 0.5,
scar_count: 0,
win_loss_ratio: 0.5,
timestamp: 0,
};
let next_market_state = MarketState {
prices: next_state,
volatility: vec![0.1; 5],
agent_capital: 0.5,
scar_count: 0,
win_loss_ratio: 0.5,
timestamp: 0,
};
let experience = Experience {
state: market_state,
action,
reward,
next_state: next_market_state,
done,
};
self.replay_buffer.push(experience);
}
pub fn train_step(&mut self) -> Result<f32> {
if self.replay_buffer.len() < self.batch_size {
return Ok(0.0);
}
let batch = self.replay_buffer.sample(self.batch_size);
let mut batch_loss = 0.0;
for experience in batch {
let q_values = self.model.forward_pass(&experience.state.prices)?;
let next_q_values = self.model.forward_pass(&experience.next_state.prices)?;
let max_next_q = next_q_values.iter()
.cloned()
.fold(f32::NEG_INFINITY, f32::max);
let target = if experience.done {
experience.reward
} else {
experience.reward + self.gamma * max_next_q
};
let loss = (q_values[experience.action] - target).powi(2);
batch_loss += loss;
}
batch_loss /= self.batch_size as f32;
self.total_loss = batch_loss;
self.training_steps += 1;
Ok(batch_loss)
}
pub fn mutate(&mut self, mutation_rate: f32, mutation_strength: f32) {
println!("Mutating model with rate {} and strength {}",
mutation_rate, mutation_strength);
}
pub fn clone_for_spawn(&self) -> Self {
Self {
model: self.model.clone(),
replay_buffer: ReplayBuffer::new(10_000),
gamma: self.gamma,
learning_rate: self.learning_rate,
batch_size: self.batch_size,
total_loss: 0.0,
training_steps: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct TrainingResults {
pub episodes_completed: u32,
pub total_reward: f32,
pub average_reward: f32,
pub average_loss: f32,
pub best_loss: f32,
pub final_epsilon: f32,
}
impl std::fmt::Display for TrainingResults {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Episodes: {} | Avg Reward: {:.2} | Avg Loss: {:.6} | Best Loss: {:.6} | Final ε: {:.3}",
self.episodes_completed,
self.average_reward,
self.average_loss,
self.best_loss,
self.final_epsilon
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_defaults() {
let config = AdvancedTrainingConfig::default();
assert_eq!(config.num_episodes, 100);
assert_eq!(config.initial_epsilon, 1.0);
assert_eq!(config.min_epsilon, 0.1);
}
#[test]
fn test_epsilon_decay() {
let config = AdvancedTrainingConfig::default();
let mut coordinator = AdvancedTrainingCoordinator::new(
SimpleQNet::new(5, 64).unwrap(),
config,
);
let initial = coordinator.current_epsilon;
coordinator.decay_epsilon();
assert!(coordinator.current_epsilon < initial);
assert!(coordinator.current_epsilon >= 0.1);
}
}