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