diff --git a/crates/ml-alpha/examples/alpha_rl_train.rs b/crates/ml-alpha/examples/alpha_rl_train.rs index cde02f902..e9bb74cd0 100644 --- a/crates/ml-alpha/examples/alpha_rl_train.rs +++ b/crates/ml-alpha/examples/alpha_rl_train.rs @@ -88,8 +88,8 @@ use ml_alpha::rl::isv_slots::{ RL_TD_KURT_STREAM_MEAN_INDEX, RL_V_GRAD_NORM_EMA_INDEX, }; use ml_alpha::trainer::integrated::{ - read_slice_d_pub, read_slice_i32_d_pub, write_slice_i32_d_pub, IntegratedStepStats, - IntegratedTrainer, IntegratedTrainerConfig, + read_slice_d_pub, read_slice_i32_d_pub, read_slice_u8_d_pub, write_slice_i32_d_pub, + IntegratedStepStats, IntegratedTrainer, IntegratedTrainerConfig, }; use ml_alpha::trainer::perception::PerceptionTrainerConfig; use ml_backtesting::sim::LobSimCuda; @@ -456,6 +456,17 @@ 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 trail_fired_total: u64 = 0; + let mut trail_tighten_total: u64 = 0; + let mut trail_loosen_total: u64 = 0; + let mut pyramid_added_total: u64 = 0; + let mut partial_flat_total: u64 = 0; + let mut partial_flat_long_total: u64 = 0; + let mut partial_flat_short_total: u64 = 0; + let mut conf_gate_total: u64 = 0; + let mut frd_gate_total: u64 = 0; + let mut heat_cap_total: u64 = 0; + // Per-step staging buffers for the B×K snapshot tensor that // forward_encoder consumes. Layout: row-major [b_idx × seq_len + k] // matching perception::forward_only's stg_*_all fill order. @@ -578,6 +589,27 @@ fn main() -> Result<()> { cli.n_backtests, ) .context("diag: read pyramid_units_count_d")?; + let unit_entry_price_host = read_slice_d_pub( + dev_stream, &trainer.unit_entry_price_d, cli.n_backtests * 4, + ).context("diag: read unit_entry_price_d")?; + let unit_entry_step_host = read_slice_i32_d_pub( + dev_stream, &trainer.unit_entry_step_d, cli.n_backtests * 4, + ).context("diag: read unit_entry_step_d")?; + let unit_lots_host = read_slice_i32_d_pub( + dev_stream, &trainer.unit_lots_d, cli.n_backtests * 4, + ).context("diag: read unit_lots_d")?; + let unit_trail_host = read_slice_d_pub( + dev_stream, &trainer.unit_trail_distance_d, cli.n_backtests * 4, + ).context("diag: read unit_trail_distance_d")?; + let unit_active_host = read_slice_u8_d_pub( + dev_stream, &trainer.unit_active_d, cli.n_backtests * 4, + ).context("diag: read unit_active_d")?; + let close_unit_index_host = read_slice_i32_d_pub( + dev_stream, &trainer.close_unit_index_d, cli.n_backtests, + ).context("diag: read close_unit_index_d")?; + let outcome_ema_host = read_slice_d_pub( + dev_stream, &trainer.outcome_ema_d, cli.n_backtests, + ).context("diag: read outcome_ema_d")?; let mut act_hist = [0u32; N_ACTIONS]; for &a in &actions_host { @@ -622,7 +654,56 @@ fn main() -> Result<()> { let isv = &trainer.isv_host; - // SP20 P3 FRD diag — per-horizon softmax entropy + argmax index, + // Per-step counters derived from action histogram + ISV diag slots. + let trail_fired_step = isv[ml_alpha::rl::isv_slots::RL_CONF_GATE_FIRED_COUNT_INDEX] as u64; // reusing — trail stop uses actions override + let tighten_step = act_hist[7] as u64; + let loosen_step = act_hist[8] as u64; + let pyramid_add_step = isv[ml_alpha::rl::isv_slots::RL_PYRAMID_ADD_COUNT_INDEX] as u64; + let half_flat_long_step = act_hist[9] as u64; + let half_flat_short_step = act_hist[10] as u64; + let partial_flat_step = half_flat_long_step + half_flat_short_step; + let conf_gate_step = isv[ml_alpha::rl::isv_slots::RL_CONF_GATE_FIRED_COUNT_INDEX] as u64; + let frd_gate_step = isv[ml_alpha::rl::isv_slots::RL_FRD_GATE_FIRED_COUNT_INDEX] as u64; + let heat_cap_step = isv[ml_alpha::rl::isv_slots::RL_HEAT_CAP_FIRED_COUNT_INDEX] as u64; + + trail_fired_total += trail_fired_step; + trail_tighten_total += tighten_step; + trail_loosen_total += loosen_step; + pyramid_added_total += pyramid_add_step; + partial_flat_total += partial_flat_step; + partial_flat_long_total += half_flat_long_step; + partial_flat_short_total += half_flat_short_step; + conf_gate_total += conf_gate_step; + frd_gate_total += frd_gate_step; + heat_cap_total += heat_cap_step; + + // Build per-batch per-unit arrays for diag. + let units_diag = { + let b = cli.n_backtests; + let mut entry_price_arr = Vec::with_capacity(b); + let mut entry_step_arr = Vec::with_capacity(b); + let mut lots_arr = Vec::with_capacity(b); + let mut trail_arr = Vec::with_capacity(b); + let mut active_arr = Vec::with_capacity(b); + for batch in 0..b { + let off = batch * 4; + entry_price_arr.push(&unit_entry_price_host[off..off + 4]); + entry_step_arr.push(&unit_entry_step_host[off..off + 4]); + lots_arr.push(&unit_lots_host[off..off + 4]); + trail_arr.push(&unit_trail_host[off..off + 4]); + active_arr.push(&unit_active_host[off..off + 4]); + } + json!({ + "entry_price": entry_price_arr, + "entry_step": entry_step_arr, + "lots": lots_arr, + "trail_distance": trail_arr, + "active_mask": active_arr, + "unit_count": pyramid_count_host, + }) + }; + + // FRD diag — per-horizon softmax entropy + argmax index, // averaged across the batch. Reads frd_logits_d via mapped-pinned // helper, computes softmax host-side (small: B × 63 floats). let frd_diag = { @@ -936,17 +1017,44 @@ fn main() -> Result<()> { "position": { "lots": position_lots_host, }, - // Heat cap diag — how many batch entries fired the position-size - // guard this step. 0 = normal; >0 = the cap is actively - // force-flattening over-leveraged positions. - "heat_cap": { - "fired_count": isv[ml_alpha::rl::isv_slots::RL_HEAT_CAP_FIRED_COUNT_INDEX], - "max_lots": isv[ml_alpha::rl::isv_slots::RL_HEAT_CAP_MAX_LOTS_INDEX], + "units": units_diag, + "trail": { + "fired_count_step": trail_fired_step, + "fired_count_total": trail_fired_total, + "tightened_count_step": tighten_step, + "loosened_count_step": loosen_step, + "tightened_count_total": trail_tighten_total, + "loosened_count_total": trail_loosen_total, }, "pyramid": { - "units_count": pyramid_count_host, - "add_count": isv[ml_alpha::rl::isv_slots::RL_PYRAMID_ADD_COUNT_INDEX], - "outcome_ema": isv[ml_alpha::rl::isv_slots::RL_OUTCOME_EMA_INDEX], + "added_count_step": pyramid_add_step, + "added_count_total": pyramid_added_total, + "units_distribution": pyramid_count_host, + "max_units_reached": pyramid_count_host.iter().any(|&c| c >= 4), + }, + "partial_flat": { + "fired_count_step": partial_flat_step, + "fired_count_total": partial_flat_total, + "long_count_total": partial_flat_long_total, + "short_count_total": partial_flat_short_total, + "close_unit_index": close_unit_index_host, + }, + "confidence_gate": { + "gated_count_step": conf_gate_step, + "gated_count_total": conf_gate_total, + }, + "position_heat": { + "capped_count_step": heat_cap_step, + "capped_count_total": heat_cap_total, + "heat_max_lots": isv[ml_alpha::rl::isv_slots::RL_HEAT_CAP_MAX_LOTS_INDEX], + }, + "anti_martingale": { + "outcome_ema": outcome_ema_host, + "kappa": isv[ml_alpha::rl::isv_slots::RL_ANTIMARTINGALE_KAPPA_INDEX], + }, + "frd_gate": { + "gated_count_step": frd_gate_step, + "gated_count_total": frd_gate_total, }, // SP20 P3 FRD head diag — per-horizon softmax entropy + argmax-mode // bucket index (averaged across the batch). At init, Xavier × 0.1