From 5265a0186ca88dcb70e0e7f25568b83e04f10850 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Fri, 15 May 2026 16:42:56 +0200 Subject: [PATCH] =?UTF-8?q?refactor(alpha):=20rename=20phase1*.rs=20?= =?UTF-8?q?=E2=86=92=20alpha=5F*.rs=20+=20add=20--alpha-cache-out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System-scoped naming for the alpha trading system's training binaries — same rationale as the earlier phase_e_* → alpha_* rename. These binaries produce / validate the durable alpha-system components (Mamba2 + stacker + calibration); they're tooling, not milestone artifacts. phase1a.rs → alpha_bar_baseline.rs phase1a_detailed.rs → alpha_bar_detailed.rs phase1d_calibrate.rs → alpha_calibrate.rs phase1d_mamba.rs → alpha_mamba_baseline.rs phase1d_long_horizon.rs → alpha_train_stacker.rs clap `name = "..."` strings updated to match new filenames; cross-refs in docstrings (alpha_calibrate.rs, gbm_baseline.rs) fixed. Plus: NEW `--alpha-cache-out ` flag on alpha_train_stacker.rs (Phase E.1 Task 12b). After the existing Mamba2 + stacker training completes, runs stacker inference on ALL bars (not just val/test) and dumps the resulting alpha_logits as a little-endian binary file: [u32 n_bars] [f32 logits[n_bars]] Bars `< seq_len − 1` are written as 0.0 (Pearl A sentinel — no history). Inference uses the same Block-S column normalisation (col_mean/col_std) computed during stacker training, applied to all bars consistently. This cache is consumed by alpha_dqn_h600_smoke.rs (next commit) which loads it and populates SnapshotRow.alpha_logit — replacing the current hardcoded 0.0 placeholder with the real Phase 1d.3 stacker output. Build verified: `cargo build -p ml-alpha --release --example alpha_train_stacker` completes clean in 40s. --- .../{phase1a.rs => alpha_bar_baseline.rs} | 2 +- ...se1a_detailed.rs => alpha_bar_detailed.rs} | 2 +- ...hase1d_calibrate.rs => alpha_calibrate.rs} | 4 +- ...ase1d_mamba.rs => alpha_mamba_baseline.rs} | 2 +- ...long_horizon.rs => alpha_train_stacker.rs} | 81 ++++++++++++++++++- crates/ml-alpha/examples/gbm_baseline.rs | 2 +- 6 files changed, 86 insertions(+), 7 deletions(-) rename crates/ml-alpha/examples/{phase1a.rs => alpha_bar_baseline.rs} (97%) rename crates/ml-alpha/examples/{phase1a_detailed.rs => alpha_bar_detailed.rs} (98%) rename crates/ml-alpha/examples/{phase1d_calibrate.rs => alpha_calibrate.rs} (96%) rename crates/ml-alpha/examples/{phase1d_mamba.rs => alpha_mamba_baseline.rs} (99%) rename crates/ml-alpha/examples/{phase1d_long_horizon.rs => alpha_train_stacker.rs} (87%) diff --git a/crates/ml-alpha/examples/phase1a.rs b/crates/ml-alpha/examples/alpha_bar_baseline.rs similarity index 97% rename from crates/ml-alpha/examples/phase1a.rs rename to crates/ml-alpha/examples/alpha_bar_baseline.rs index 52bc078a3..61254770e 100644 --- a/crates/ml-alpha/examples/phase1a.rs +++ b/crates/ml-alpha/examples/alpha_bar_baseline.rs @@ -34,7 +34,7 @@ use tracing_subscriber::EnvFilter; use ml_alpha::training::{Phase1aConfig, Phase1aTrainer}; #[derive(Debug, Parser)] -#[command(name = "phase1a", about = "FoxhuntQ-Δ Phase 1a falsification smoke")] +#[command(name = "alpha_bar_baseline", about = "FoxhuntQ-Δ Phase 1a falsification smoke")] struct Cli { /// Override fxcache path. Defaults to local test_data fxcache. #[arg(long)] diff --git a/crates/ml-alpha/examples/phase1a_detailed.rs b/crates/ml-alpha/examples/alpha_bar_detailed.rs similarity index 98% rename from crates/ml-alpha/examples/phase1a_detailed.rs rename to crates/ml-alpha/examples/alpha_bar_detailed.rs index cb9d411fe..0f96d2f42 100644 --- a/crates/ml-alpha/examples/phase1a_detailed.rs +++ b/crates/ml-alpha/examples/alpha_bar_detailed.rs @@ -26,7 +26,7 @@ use ml_alpha::metrics_detail::{ use ml_alpha::training::{Phase1aConfig, Phase1aTrainer}; #[derive(Parser, Debug)] -#[command(name = "phase1a_detailed", about = "Phase 1c detailed metrics (calibration + stratification)")] +#[command(name = "alpha_bar_detailed", about = "Phase 1c detailed metrics (calibration + stratification)")] struct Cli { /// Path to fxcache file (alpha feature column required). #[arg(long)] diff --git a/crates/ml-alpha/examples/phase1d_calibrate.rs b/crates/ml-alpha/examples/alpha_calibrate.rs similarity index 96% rename from crates/ml-alpha/examples/phase1d_calibrate.rs rename to crates/ml-alpha/examples/alpha_calibrate.rs index b85367bb7..dd50ba4a1 100644 --- a/crates/ml-alpha/examples/phase1d_calibrate.rs +++ b/crates/ml-alpha/examples/alpha_calibrate.rs @@ -1,6 +1,6 @@ //! Phase 1d.0 — calibration smoke. //! -//! Train the snapshot-level MLP exactly as `phase1a_detailed` does, then: +//! Train the snapshot-level MLP exactly as `alpha_bar_detailed` does, then: //! 1. Split val 50/50 into (calibration set, held-out test set). //! 2. Fit Platt and isotonic on the calibration set. //! 3. Apply each to the held-out test set; report Brier + log-loss for @@ -18,7 +18,7 @@ use ml_alpha::metrics_detail::{brier_score, log_loss}; use ml_alpha::training::{Phase1aConfig, Phase1aTrainer}; #[derive(Parser, Debug)] -#[command(name = "phase1d_calibrate", about = "FoxhuntQ-Δ Phase 1d.0 calibration smoke")] +#[command(name = "alpha_calibrate", about = "FoxhuntQ-Δ Phase 1d.0 calibration smoke")] struct Cli { #[arg(long)] fxcache_path: String, diff --git a/crates/ml-alpha/examples/phase1d_mamba.rs b/crates/ml-alpha/examples/alpha_mamba_baseline.rs similarity index 99% rename from crates/ml-alpha/examples/phase1d_mamba.rs rename to crates/ml-alpha/examples/alpha_mamba_baseline.rs index 9e33e8c14..a555dce2b 100644 --- a/crates/ml-alpha/examples/phase1d_mamba.rs +++ b/crates/ml-alpha/examples/alpha_mamba_baseline.rs @@ -39,7 +39,7 @@ use ml_alpha::training::{prepare_phase1a_data, Phase1aConfig}; use ml_core::cuda_autograd::gpu_tensor::GpuTensor; #[derive(Parser, Debug)] -#[command(name = "phase1d_mamba", about = "FoxhuntQ-Δ Phase 1d.1 Mamba2 sequence smoke")] +#[command(name = "alpha_mamba_baseline", about = "FoxhuntQ-Δ Phase 1d.1 Mamba2 sequence smoke")] struct Cli { #[arg(long)] fxcache_path: String, #[arg(long, default_value_t = 100)] horizon: usize, diff --git a/crates/ml-alpha/examples/phase1d_long_horizon.rs b/crates/ml-alpha/examples/alpha_train_stacker.rs similarity index 87% rename from crates/ml-alpha/examples/phase1d_long_horizon.rs rename to crates/ml-alpha/examples/alpha_train_stacker.rs index 6554b7a04..a3d2364ae 100644 --- a/crates/ml-alpha/examples/phase1d_long_horizon.rs +++ b/crates/ml-alpha/examples/alpha_train_stacker.rs @@ -44,7 +44,7 @@ use ml_alpha::multi_horizon_labels::generate_labels; use ml_core::cuda_autograd::gpu_tensor::GpuTensor; #[derive(Parser, Debug)] -#[command(name = "phase1d_long_horizon", about = "FoxhuntQ-Δ Phase 1d.2 multi-minute Mamba smoke")] +#[command(name = "alpha_train_stacker", about = "FoxhuntQ-Δ Phase 1d.2 multi-minute Mamba smoke")] struct Cli { #[arg(long)] fxcache_path: String, /// Label horizon in snapshots. K=6000 ≈ 1-5 min forward at typical rates. @@ -76,6 +76,12 @@ struct Cli { #[arg(long, default_value_t = 1024)] stacker_batch: usize, /// Round-trip transaction cost in price units (ES.FUT: 0.25 = 1 tick = $12.50/contract). #[arg(long, default_value_t = 0.25)] cost_per_trade: f32, + /// Phase E.1 Task 12b: dump per-bar alpha_logits (stacker output) to a + /// binary file consumable by `alpha_dqn_h600_smoke.rs`. The file is a + /// little-endian u32 length prefix followed by n_bars f32 values; bars + /// `< seq_len - 1` are written as 0 (no history). Empty / unset → no + /// cache dumped. + #[arg(long)] alpha_cache_out: Option, } fn main() -> Result<()> { @@ -444,6 +450,79 @@ fn main() -> Result<()> { println!(" Stacker: acc={:.4} AUC={:.4} Brier={:.5} log-loss={:.5} (n_test={})", s_acc, s_auc, s_brier, s_logl, stacker_test_n); + // ── Phase E.1 Task 12b: alpha cache export ───────────── + // Run stacker inference on ALL bars (not just val/test) and + // dump the resulting alpha_logits to a binary file. The + // alpha_dqn_h600_smoke.rs binary loads this cache and + // attaches it to SnapshotRow.alpha_logit, giving the + // execution policy a real directional signal. + // + // Bars with end_bar < seq_len - 1 lack history and are + // written as 0.0 (Pearl A sentinel — the env's downstream + // consumers treat 0.0 as "no signal"). + if let Some(out_path) = cli.alpha_cache_out.as_ref() { + use std::io::Write as _; + println!("\n=== Phase E.1 alpha-cache export ==="); + println!(" Computing stacker inference on all {} bars", n_bars); + let mut alpha_cache: Vec = vec![0.0; n_bars]; + let inf_batch = cli.batch_size; + let start = cli.seq_len - 1; + let mut end_bar = start; + while end_bar < n_bars { + let this = inf_batch.min(n_bars - end_bar); + // Build (this, seq_len, alpha_dim) batch + let mut host: Vec = Vec::with_capacity(this * cli.seq_len * alpha_dim); + for b in 0..this { + let eb = end_bar + b; + let start_bar = eb + 1 - cli.seq_len; + for t in 0..cli.seq_len { + let off = (start_bar + t) * alpha_dim; + host.extend_from_slice(&feature_matrix[off..off + alpha_dim]); + } + } + let dev = stream.clone_htod(&host)?; + let tensor = GpuTensor::new(dev, vec![this, cli.seq_len, alpha_dim]) + .map_err(|e| anyhow::anyhow!("alpha-cache batch: {e}"))?; + let logit = block.forward(&tensor)?; + let mamba_logits = logit.to_host(&stream)?; + // Build stacker input: [mamba_logit, normalized Block-S features] + let mut s_in: Vec = Vec::with_capacity(this * STACKER_IN_DIM); + for b in 0..this { + let eb = end_bar + b; + s_in.push(mamba_logits[b]); + for (k, &off) in BS_OFFSETS.iter().enumerate() { + let raw = feature_matrix[eb * alpha_dim + off]; + let norm = (raw - col_mean[k + 1] as f32) / col_std[k + 1]; + s_in.push(norm); + } + } + let s_out = stacker.forward_infer(&s_in, this)?; + for b in 0..this { + alpha_cache[end_bar + b] = s_out[b]; + } + end_bar += this; + if end_bar % 100_000 < inf_batch { + println!(" alpha-cache progress: {}/{}", end_bar, n_bars); + } + } + let mut f = std::fs::File::create(out_path) + .with_context(|| format!("create alpha cache {}", out_path))?; + let n: u32 = n_bars as u32; + f.write_all(&n.to_le_bytes())?; + for v in &alpha_cache { + f.write_all(&v.to_le_bytes())?; + } + let mean_logit: f32 = alpha_cache.iter().sum::() / n_bars as f32; + let mut variance: f32 = 0.0; + for v in &alpha_cache { + let d = v - mean_logit; + variance += d * d; + } + variance /= n_bars as f32; + println!(" Wrote {} f32 entries to {}", n_bars, out_path); + println!(" Stats: mean = {:+.4}, std = {:.4}", mean_logit, variance.sqrt()); + } + // Stratified accuracy of the STACKER on Block-S features — // tells us whether the stacker absorbed the regime conditioning // (uniform accuracy across quintiles) or just learned a sharper diff --git a/crates/ml-alpha/examples/gbm_baseline.rs b/crates/ml-alpha/examples/gbm_baseline.rs index ddd0efbfc..f018e65bb 100644 --- a/crates/ml-alpha/examples/gbm_baseline.rs +++ b/crates/ml-alpha/examples/gbm_baseline.rs @@ -2,7 +2,7 @@ //! //! ## Why //! -//! The MLP baseline (see `phase1a.rs`) hits ~0.495 validation accuracy on +//! The MLP baseline (see `alpha_bar_baseline.rs`) hits ~0.495 validation accuracy on //! properly-aligned, normalized data. Per the Grinsztajn et al. NeurIPS 2022 //! survey ("Why do tree-based models still outperform deep learning on typical //! tabular data?"), gradient-boosted trees are the canonical baseline for