feat(diag): add per-trade PnL, win rate, hold time, sps to JSONL + log
New diag fields in the "trading" object: - pnl_cum_usd: cumulative realized PnL in USD (shaped, pre-scale) - total_trades: lifetime trade count - win_rate: fraction of positive-PnL closes - avg_hold_steps: mean hold duration in steps - raw_reward_sum: per-step sum of shaped rewards Log line now shows sps (steps/sec), pnl (cumulative $), wr (win rate). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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::<f32>(),
|
||||
},
|
||||
// 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);
|
||||
|
||||
@@ -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<f32>,
|
||||
raw_rewards_d: CudaSlice<f32>,
|
||||
pub raw_rewards_d: CudaSlice<f32>,
|
||||
/// Trainer-owned snapshot of `lobsim.pos.position_lots` for the
|
||||
/// done-flag detection: `done = (prev != 0 && current == 0)`.
|
||||
pub prev_position_lots_d: CudaSlice<i32>,
|
||||
|
||||
Reference in New Issue
Block a user