feat(campaign,cli): permit --select plateau in the walk_forward stage (#215)

The campaign walk_forward stage picked its per-window in-sample winner by
optimize_deflated (argmax) unconditionally, dropping the stage's `select`
field, and both the preflight and the `walkforward` verb's `--real` campaign
sub-branch refused plateau:* with a forward-pointer here (the #210 decision-4
deferral; the synthetic non-`--real` sub-branch has accepted plateau since
#173). Honour plateau selection when no gate precedes the wf stage.

- Preflight: the plateau-in-wf refusal is narrowed to gate-preceded stages.
  With no preceding gate the survivors are the full sweep grid, so the
  parameter lattice (grid.axis_lens) is exact and optimize_plateau is sound; a
  gate filters survivors below the grid, breaking the lattice, so plateau after
  any gate stays refused. The refusal is static — document validity must be
  data-independent (a gate's filtering depends on the data) — and keeps the
  existing "a gated survivor subset has no parameter lattice" message, which now
  describes exactly the only case it fires.
- Executor: run_walk_forward_stage threads the per-cell grid.axis_lens and picks
  each window's in-sample winner via select_sweep_winner — the same dispatch the
  sweep stage uses. (Argmax, deflate=true) is byte-identical to the old
  optimize_deflated call, so the argmax grade and content-id pins are unchanged;
  the plateau arms read axis_lens and ignore deflate.
- CLI: the walkforward verb threads --select through translate_walkforward /
  run_walkforward_sugar (a walkforward-local param, not the shared four-verb
  SugarInvocation), mapping the parsed Selection to the research SelectRule via
  select_rule_of; the refusal and its select_is_plateau helper are deleted.
  Scope is walkforward-only — the mc verb's wf-stage select is untouched.

The plateau:worst grade anchor (stitched_total_pips = -9683776.67) is distinct
from the argmax anchor (-10398606.67), proving the plateau path genuinely
changes winner selection rather than falling back to argmax. A gate-free plateau
process validates clean while a gate-preceded one is refused (a new
campaign-validate e2e). Argmax defaults stay byte-identical.

closes #215
This commit is contained in:
2026-07-10 02:59:33 +02:00
parent 35b996e3bf
commit 94cdbf90cc
6 changed files with 335 additions and 67 deletions
+82 -9
View File
@@ -1801,11 +1801,13 @@ fn walkforward_dissolved_refuses_a_multi_value_stop() {
assert!(stderr.contains("single risk regime"), "stop-regime refusal: {stderr}");
}
/// Fork B: --select plateau is refused on the dissolved path (exit 2) with a
/// pointer to #215 until the wf-stage plateau relaxation ships.
/// #215: --select plateau is no longer refused on the dissolved path — it now
/// threads through to the wf stage exactly like the built-in synthetic path.
/// Gated on the shared GER40 archive; skips cleanly on a data refusal (exit 1),
/// same tolerance as the sibling stop-knob-defaulting test.
#[test]
fn walkforward_dissolved_refuses_select_plateau() {
let cwd = temp_cwd("walkforward-plateau");
fn walkforward_dissolved_accepts_select_plateau() {
let (cwd, _g) = fresh_project();
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
@@ -1814,12 +1816,83 @@ fn walkforward_dissolved_refuses_select_plateau() {
"--stop-length", "14", "--stop-k", "2.0",
"--select", "plateau:worst",
])
.current_dir(&cwd)
.current_dir(cwd)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "plateau is refused on the dissolved path");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("#215"), "forward pointer to the plateau follow-up: {stderr}");
if out.status.code() == Some(1) {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
"exit 1 must be a data refusal, got: {stderr}"
);
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(
out.status.code(),
Some(0),
"--select plateau:worst must no longer be refused on the dissolved path: {}",
String::from_utf8_lossy(&out.stderr)
);
}
/// Characterization pin (#215): the same identical invocation as
/// `walkforward_real_e2e_pins_the_exact_current_grade` (fixture, real
/// instrument, axes, stop, window), but selecting `plateau:worst` instead of
/// the default argmax. Plateau scores the closed neighbourhood over the full
/// IS grid rather than picking the single best point, so its winner (and
/// therefore its OOS stitch) legitimately differs from the argmax anchor;
/// this pins that divergence exactly, so a regression that silently
/// collapsed plateau back to argmax (or vice versa) inside
/// `run_walk_forward_stage`'s `select_sweep_winner` dispatch goes red here
/// even though the argmax anchor stays green. Gated on the shared GER40
/// archive; skips cleanly on a data refusal.
#[test]
fn walkforward_real_e2e_pins_the_exact_current_plateau_grade() {
const FROM_MS: &str = "1735689600000";
const TO_MS: &str = "1767225599000";
let (cwd, _g) = fresh_project();
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"walkforward", &fixture, "--real", "GER40",
"--axis", "sma_signal.fast.length=3,5", "--axis", "sma_signal.slow.length=12,20",
"--stop-length", "14", "--stop-k", "2.0",
"--from", FROM_MS, "--to", TO_MS,
"--select", "plateau:worst",
])
.current_dir(cwd)
.output()
.expect("spawn aura");
if out.status.code() == Some(1) {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
"exit 1 must be a data refusal, got: {stderr}"
);
eprintln!("skip: no local GER40 data");
return;
}
assert_eq!(out.status.code(), Some(0), "exit: {:?}", out.status);
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
let grade_line = stdout
.lines()
.find(|l| l.starts_with("{\"walkforward\":"))
.unwrap_or_else(|| panic!("the walkforward summary line: {stdout}"));
let v: serde_json::Value = serde_json::from_str(grade_line).expect("summary line parses as JSON");
let wf = &v["walkforward"];
assert_eq!(wf["windows"].as_u64(), Some(9), "window count: {grade_line}");
// EXACT floats -- the byte-identity anchor a plateau-vs-argmax regression must preserve.
assert_eq!(wf["stitched_total_pips"].as_f64(), Some(-9683776.66663749), "stitched OOS pips: {grade_line}");
let r = &wf["oos_r"];
assert_eq!(r["n_trades"].as_u64(), Some(20667), "pooled OOS trade count: {grade_line}");
assert_eq!(r["expectancy_r"].as_f64(), Some(0.007635120926372627), "pooled OOS expectancy R: {grade_line}");
// param_stability varies across windows -> the per-window plateau refit picks
// different winners than the argmax anchor; pinning the means locks the
// plateau winner-selection path.
let ps = wf["param_stability"].as_array().expect("param_stability is an array");
assert_eq!(ps[0]["mean"].as_f64(), Some(4.777777777777778), "fast-MA refit mean: {grade_line}");
assert_eq!(ps[1]["mean"].as_f64(), Some(16.444444444444443), "slow-MA refit mean: {grade_line}");
}
/// Property (#217): `walkforward_args_from` no longer requires both stop knobs
@@ -1916,7 +1989,7 @@ fn walkforward_dissolved_refuses_an_unknown_axis_name() {
/// Front-end parity: an unknown `--select` token is refused (exit 2) on the
/// dissolved path exactly as it is on the inline path — it must not be silently
/// swallowed by `select_is_plateau`'s plateau-only check and fall through to argmax.
/// swallowed by `select_rule_of`'s conversion and fall through to argmax.
#[test]
fn walkforward_dissolved_refuses_an_unknown_select_token() {
let cwd = temp_cwd("walkforward-bad-select");