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:
@@ -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)]
|
||||
@@ -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)]
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user