feat: monitoring + metrics arrays 5→9, factored 45→81
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<f64>,
|
||||
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<f64> = 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<f64> = 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
|
||||
|
||||
@@ -12,14 +12,14 @@ use crate::dqn::action_space::FactoredAction;
|
||||
pub(crate) struct TrainingMonitor {
|
||||
pub(crate) epoch: usize,
|
||||
pub(crate) reward_history: Vec<f32>,
|
||||
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,
|
||||
|
||||
@@ -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<TrainingMetrics> {
|
||||
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::<f32>() / 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<f32> = (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);
|
||||
|
||||
@@ -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<EpochBoundaryMetrics>,
|
||||
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<EpochLogOutput> {
|
||||
// Calculate epoch metrics (average over training steps)
|
||||
let (epoch_loss, epoch_q_value, epoch_gradient_norm) = match boundary {
|
||||
|
||||
Reference in New Issue
Block a user