refactor(alpha): rename phase1*.rs → alpha_*.rs + add --alpha-cache-out

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 <PATH>` 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.
This commit is contained in:
jgrusewski
2026-05-15 16:42:56 +02:00
parent 8548d126fd
commit 5265a0186c
6 changed files with 86 additions and 7 deletions

View File

@@ -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)]

View File

@@ -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)]

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<String>,
}
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<f32> = 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<f32> = 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<f32> = 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::<f32>() / 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

View File

@@ -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