diff --git a/crates/ml-alpha/examples/alpha_rl_train.rs b/crates/ml-alpha/examples/alpha_rl_train.rs index 83ace56b1..ab9fc597e 100644 --- a/crates/ml-alpha/examples/alpha_rl_train.rs +++ b/crates/ml-alpha/examples/alpha_rl_train.rs @@ -455,6 +455,10 @@ fn main() -> Result<()> { let mut windowed_act_hist: [f32; N_ACTIONS] = [0.0; N_ACTIONS]; const WINDOWED_ACT_ALPHA: f32 = 1.0 / 1000.0; + let mut pnl_cum_usd: f64 = 0.0; + let mut win_count: u64 = 0; + let mut total_trades: u64 = 0; + let mut hold_time_sum: f64 = 0.0; let mut trail_fired_total: u64 = 0; let mut trail_tighten_total: u64 = 0; let mut trail_loosen_total: u64 = 0; @@ -609,6 +613,12 @@ fn main() -> Result<()> { let outcome_ema_host = read_slice_d_pub( dev_stream, &trainer.outcome_ema_d, cli.n_backtests, ).context("diag: read outcome_ema_d")?; + let raw_rewards_host = read_slice_d_pub( + dev_stream, &trainer.raw_rewards_d, cli.n_backtests, + ).context("diag: read raw_rewards_d")?; + let trade_duration_host = read_slice_d_pub( + dev_stream, &trainer.trade_duration_emit_d, cli.n_backtests, + ).context("diag: read trade_duration_emit_d")?; let mut act_hist = [0u32; N_ACTIONS]; for &a in &actions_host { @@ -651,6 +661,19 @@ fn main() -> Result<()> { let reward_abs_max = rewards_host.iter().map(|r| r.abs()).fold(0.0f32, f32::max); let done_count: u32 = dones_host.iter().map(|&d| if d > 0.5 { 1 } else { 0 }).sum(); + // Per-trade cumulative stats (surfer validation). + for b in 0..cli.n_backtests { + if dones_host[b] > 0.5 { + let pnl = raw_rewards_host[b] as f64; + pnl_cum_usd += pnl; + total_trades += 1; + if pnl > 0.0 { win_count += 1; } + hold_time_sum += trade_duration_host[b] as f64; + } + } + let win_rate = if total_trades > 0 { win_count as f64 / total_trades as f64 } else { 0.0 }; + let avg_hold = if total_trades > 0 { hold_time_sum / total_trades as f64 } else { 0.0 }; + let isv = &trainer.isv_host; // Per-step counters derived from action histogram + ISV diag slots. @@ -1055,6 +1078,13 @@ fn main() -> Result<()> { "gated_count_step": frd_gate_step, "gated_count_total": frd_gate_total, }, + "trading": { + "pnl_cum_usd": pnl_cum_usd, + "total_trades": total_trades, + "win_rate": win_rate, + "avg_hold_steps": avg_hold, + "raw_reward_sum": raw_rewards_host.iter().sum::(), + }, // SP20 P3 FRD head diag — per-horizon softmax entropy + argmax-mode // bucket index (averaged across the batch). At init, Xavier × 0.1 // weights → logits ≈ 0 → entropy ≈ ln(21) = 3.044 and the argmax @@ -1067,10 +1097,12 @@ fn main() -> Result<()> { if step % cli.log_every == 0 || step == cli.n_steps - 1 { diag.flush().context("diag: flush")?; + let elapsed = t_start.elapsed().as_secs_f32(); + let sps = if elapsed > 0.0 { (step + 1) as f32 / elapsed } else { 0.0 }; eprintln!( "step {:>6}/{}: l_q={:.4} l_pi={:.4} l_v={:.4} l_total={:.4} \ γ={:.4} ε={:.4} per_α={:.4} scale={:.4} \ - replay={} dones={} rew_sum={:.3} elapsed={:.1}s", + replay={} dones={} rew_sum={:.3} sps={:.0} pnl=${:.0} wr={:.2} elapsed={:.1}s", step, cli.n_steps, stats.l_q, @@ -1084,7 +1116,10 @@ fn main() -> Result<()> { trainer.replay.len(), done_count, reward_sum, - t_start.elapsed().as_secs_f32() + sps, + pnl_cum_usd, + win_rate, + elapsed, ); } last_stats = Some(stats); diff --git a/crates/ml-alpha/src/trainer/integrated.rs b/crates/ml-alpha/src/trainer/integrated.rs index 4ff6904ed..837c59b4b 100644 --- a/crates/ml-alpha/src/trainer/integrated.rs +++ b/crates/ml-alpha/src/trainer/integrated.rs @@ -643,7 +643,7 @@ pub struct IntegratedTrainer { /// `LobSimCuda::prev_realized_pnl_d` (which serves the production /// decision pipeline, not the RL trainer). pub prev_realized_pnl_d: CudaSlice, - raw_rewards_d: CudaSlice, + pub raw_rewards_d: CudaSlice, /// Trainer-owned snapshot of `lobsim.pos.position_lots` for the /// done-flag detection: `done = (prev != 0 && current == 0)`. pub prev_position_lots_d: CudaSlice,