From 889d9263f4cb1d7d3ee4d97c7625b610f1ea67a3 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Mon, 23 Mar 2026 09:41:14 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20monitoring=20+=20metrics=20arrays=205?= =?UTF-8?q?=E2=86=929,=20factored=2045=E2=86=9281?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated all fixed-size arrays across monitoring.rs, financials.rs, metrics.rs, training_loop.rs from [_;5]→[_;9] and [_;45]→[_;81]. DIRECTION_LUT expanded to 9 levels (-1.0 to +1.0 in 0.25 steps). Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/ml/src/trainers/dqn/financials.rs | 10 +++++----- crates/ml/src/trainers/dqn/monitoring.rs | 16 ++++++++-------- crates/ml/src/trainers/dqn/trainer/metrics.rs | 18 +++++++++--------- .../src/trainers/dqn/trainer/training_loop.rs | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/ml/src/trainers/dqn/financials.rs b/crates/ml/src/trainers/dqn/financials.rs index e77e51555..73f4381d6 100644 --- a/crates/ml/src/trainers/dqn/financials.rs +++ b/crates/ml/src/trainers/dqn/financials.rs @@ -26,7 +26,7 @@ pub(crate) struct EpochFinancials { /// - `initial_capital`: starting equity for return calculation (default 100_000) pub(crate) fn compute_epoch_financials( pnl_history: &VecDeque, - action_counts: &[usize; 5], + action_counts: &[usize; 9], initial_capital: f64, ) -> EpochFinancials { if pnl_history.is_empty() { @@ -135,7 +135,7 @@ mod tests { #[test] fn test_empty_history() { - let f = compute_epoch_financials(&VecDeque::new(), &[0; 5], 100_000.0); + let f = compute_epoch_financials(&VecDeque::new(), &[0; 9], 100_000.0); assert_eq!(f.total_trades, 0); assert_eq!(f.sharpe, 0.0); } @@ -143,7 +143,7 @@ mod tests { #[test] fn test_all_winning() { let pnl: VecDeque = vec![10.0, 20.0, 30.0, 15.0, 25.0].into(); - let f = compute_epoch_financials(&pnl, &[0; 5], 100_000.0); + let f = compute_epoch_financials(&pnl, &[0; 9], 100_000.0); assert_eq!(f.win_rate, 1.0); assert_eq!(f.total_trades, 5); assert!(f.sharpe > 0.0); @@ -154,7 +154,7 @@ mod tests { #[test] fn test_mixed_pnl() { let pnl: VecDeque = vec![100.0, -50.0, 75.0, -25.0, 50.0].into(); - let f = compute_epoch_financials(&pnl, &[0; 5], 100_000.0); + let f = compute_epoch_financials(&pnl, &[0; 9], 100_000.0); assert_eq!(f.total_trades, 5); assert!((f.win_rate - 0.6).abs() < 1e-10); assert!(f.total_return > 0.0); @@ -165,7 +165,7 @@ mod tests { #[test] fn test_action_distribution() { - let mut actions = [0usize; 5]; + let mut actions = [0usize; 9]; actions[4] = 100; // Long100 → BUY actions[0] = 50; // Short100 → SELL actions[2] = 50; // Flat → HOLD diff --git a/crates/ml/src/trainers/dqn/monitoring.rs b/crates/ml/src/trainers/dqn/monitoring.rs index a9d6bf05b..c2f7bb203 100644 --- a/crates/ml/src/trainers/dqn/monitoring.rs +++ b/crates/ml/src/trainers/dqn/monitoring.rs @@ -12,14 +12,14 @@ use crate::dqn::action_space::FactoredAction; pub(crate) struct TrainingMonitor { pub(crate) epoch: usize, pub(crate) reward_history: Vec, - pub(crate) action_counts: [usize; 5], // 5 exposure levels (DQN action space) - pub(crate) q_value_sums: [f64; 5], // Sum of Q-values per exposure action - pub(crate) q_value_counts: [usize; 5], // Count of Q-values per exposure action + pub(crate) action_counts: [usize; 9], // 5 exposure levels (DQN action space) + pub(crate) q_value_sums: [f64; 9], // Sum of Q-values per exposure action + pub(crate) q_value_counts: [usize; 9], // Count of Q-values per exposure action pub(crate) order_type_counts: [usize; 3], // Market, LimitMaker, IoC pub(crate) urgency_counts: [usize; 3], // Patient, Normal, Aggressive /// Factored action counts: 5 exposure × 3 order × 3 urgency = 45 actions. /// Index = exposure * 9 + order * 3 + urgency (0-44). - pub(crate) factored_action_counts: [usize; 45], + pub(crate) factored_action_counts: [usize; 81], pub(crate) consecutive_constant_epochs: usize, // Q-value range tracking (WAVE 9-11 production monitoring) pub(crate) q_value_min: f64, @@ -37,12 +37,12 @@ impl TrainingMonitor { Self { epoch, reward_history: Vec::new(), - action_counts: [0; 5], - q_value_sums: [0.0; 5], - q_value_counts: [0; 5], + action_counts: [0; 9], + q_value_sums: [0.0; 9], + q_value_counts: [0; 9], order_type_counts: [0; 3], urgency_counts: [0; 3], - factored_action_counts: [0; 45], + factored_action_counts: [0; 81], consecutive_constant_epochs: 0, q_value_min: f64::INFINITY, q_value_max: f64::NEG_INFINITY, diff --git a/crates/ml/src/trainers/dqn/trainer/metrics.rs b/crates/ml/src/trainers/dqn/trainer/metrics.rs index 79ac17dc4..9d57fc532 100644 --- a/crates/ml/src/trainers/dqn/trainer/metrics.rs +++ b/crates/ml/src/trainers/dqn/trainer/metrics.rs @@ -42,8 +42,8 @@ impl DQNTrainer { num_epochs: usize, training_duration: std::time::Duration, early_stopped: bool, - total_action_counts: [usize; 5], // 5 exposure levels - total_factored_action_counts: [usize; 45], // 45 factored actions + total_action_counts: [usize; 9], // 5 exposure levels + total_factored_action_counts: [usize; 81], // 45 factored actions ) -> Result { let final_loss = total_loss / num_epochs as f64; let avg_q_value_final = total_q_value / num_epochs as f64; @@ -189,10 +189,10 @@ impl DQNTrainer { /// /// Returns (gap_stats, per_action_avgs) where: /// - gap_stats: (mean_gap, min_gap, max_gap) of Q_best - Q_second_best - /// - per_action_avgs: `[f64; 5]` averages (one per exposure action, capped at 5) + /// - per_action_avgs: `[f64; 9]` averages (one per exposure action, capped at 5) pub(crate) async fn compute_epoch_q_diagnostics(&mut self) -> Option<( (f64, f64, f64), - [f64; 5], + [f64; 9], )> { let agent = self.agent.read().await; let buffer = agent.memory(); @@ -236,7 +236,7 @@ impl DQNTrainer { let cols = total_actions; let rows = sample_size; let mut gaps = Vec::with_capacity(rows); - let mut col_sums = [0.0_f64; 5]; + let mut col_sums = [0.0_f64; 9]; for r in 0..rows { let row_offset = r * cols; let mut best_val = f32::NEG_INFINITY; @@ -272,7 +272,7 @@ impl DQNTrainer { let gap_mean = gaps.iter().sum::() / rows as f32; let gap_min = gaps.iter().copied().fold(f32::INFINITY, f32::min); let gap_max = gaps.iter().copied().fold(f32::NEG_INFINITY, f32::max); - let mut per_action_avgs = [0.0_f64; 5]; + let mut per_action_avgs = [0.0_f64; 9]; for (i, avg) in per_action_avgs.iter_mut().enumerate() { *avg = col_sums.get(i).copied().unwrap_or(0.0) / rows_f64; } @@ -483,8 +483,8 @@ impl DQNTrainer { // ── CPU greedy action selection ── // Direction LUT maps exposure head index → position size direction. // 0=Short100(-1.0), 1=Short50(-0.5), 2=Flat(0.0), 3=Long50(0.5), 4=Long100(1.0) - const DIRECTION_LUT: [f32; 5] = [-1.0, -0.5, 0.0, 0.5, 1.0]; - let use_branching = self.hyperparams.use_branching && total_actions >= 11; + const DIRECTION_LUT: [f32; 9] = [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]; + let use_branching = self.hyperparams.use_branching && total_actions >= 15; let rewards: Vec = (0..sample_size).map(|i| { let row_start = i * total_actions; @@ -507,7 +507,7 @@ impl DQNTrainer { .map(|(j, _)| j.min(4)) .unwrap_or(2) // default: Flat }; - let direction = DIRECTION_LUT[exposure_idx.min(4)]; + let direction = DIRECTION_LUT[exposure_idx.min(8)]; // PnL-based reward: direction × (next_close - cur_close) / cur_close let cur = current_closes.get(i).copied().unwrap_or(0.0); diff --git a/crates/ml/src/trainers/dqn/trainer/training_loop.rs b/crates/ml/src/trainers/dqn/trainer/training_loop.rs index 29f0b66b5..c9919bd3a 100644 --- a/crates/ml/src/trainers/dqn/trainer/training_loop.rs +++ b/crates/ml/src/trainers/dqn/trainer/training_loop.rs @@ -70,8 +70,8 @@ impl DQNTrainer { let mut total_q_value = 0.0; let mut total_gradient_norm = 0.0; let mut total_reward = 0.0; - let mut total_action_counts = [0_usize; 5]; - let mut total_factored_action_counts = [0_usize; 45]; + let mut total_action_counts = [0_usize; 9]; + let mut total_factored_action_counts = [0_usize; 81]; self.log_training_config().await; @@ -1446,8 +1446,8 @@ impl DQNTrainer { boundary: &Option, monitor: &mut TrainingMonitor, epoch_duration: std::time::Duration, - total_action_counts: &mut [usize; 5], - total_factored_action_counts: &mut [usize; 45], + total_action_counts: &mut [usize; 9], + total_factored_action_counts: &mut [usize; 81], ) -> Result { // Calculate epoch metrics (average over training steps) let (epoch_loss, epoch_q_value, epoch_gradient_norm) = match boundary {