From a081c14532216045dd1184e5df8bb340747836e6 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 11 Jul 2026 16:24:33 +0200 Subject: [PATCH] =?UTF-8?q?test(cli):=20RED=20=E2=80=94=20mc=20--real=20re?= =?UTF-8?q?fuses=20a=20short=20window=20instead=20of=20fitting=20the=20rol?= =?UTF-8?q?ler=20(#239)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dissolved mc/walkforward dispatches stamp the fixed 90/30/30-day roller into the generated process document regardless of the resolved campaign window; a window under 120 days dead-ends at the executor with no remedy reachable from the verb surface. refs #239 --- crates/aura-cli/tests/cli_run.rs | 105 +++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index 5bed84f..3b2553b 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -4702,6 +4702,111 @@ fn mc_dissolves_through_the_campaign_path() { ); } +/// Property (#239): a real-data `aura mc --real` invocation over a window SHORTER +/// than the fixed 90-day-in-sample / 30-day-out-of-sample roller runs the R-bootstrap +/// to completion by fitting the injected walk-forward roller down to the campaign +/// window, instead of refusing late at the executor with "walk_forward windows do not +/// fit the campaign window" (exit 1, no remedy reachable from the mc surface). The +/// short window is DERIVED from the live archive — probe GER40's full span via a +/// no-window single-cell sweep, then take the last ~30 days — the same live-archive +/// derivation `generalize_without_explicit_window_resolves_the_intersection_of_all_symbols` +/// uses, so the window stays short as the archive grows and always holds recent bars. +/// The leading sweep stage runs first over the short window; the refusal today fires at +/// stage 1 (the walk_forward roller), so this bites only when the window genuinely has +/// data. Gated on the GER40 archive; skips cleanly on a data-less host. +#[test] +fn mc_real_fits_the_injected_roller_to_a_short_window() { + if !local_data_present() { + eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH); + return; + } + let (cwd, _g) = fresh_project(); + let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR")); + + // Probe GER40's full archive window: a single-cell, no-window sweep records + // `campaign_window_ms(full_window)` into its generated campaign document, in the + // same Unix-ms currency `--from`/`--to` speak (the generalize no-window test's + // derivation). + let full_window = { + let name = "probe-ger40-span"; + let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) + .args([ + "sweep", &fixture, "--real", "GER40", + "--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12", + "--name", name, + ]) + .current_dir(cwd) + .output() + .expect("spawn aura sweep probe"); + assert_eq!( + out.status.code(), Some(0), + "the full-archive window-probe sweep must run to exit 0: {}", + String::from_utf8_lossy(&out.stderr) + ); + let dir = cwd.join("runs").join("campaigns"); + let mut resolved = None; + for entry in std::fs::read_dir(&dir).expect("campaigns dir exists after a run") { + let path = entry.expect("readable dir entry").path(); + let doc: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&path).expect("read campaign doc")) + .expect("campaign doc parses as JSON"); + if doc["name"].as_str() == Some(name) { + let w = &doc["data"]["windows"][0]; + resolved = Some(( + w["from_ms"].as_i64().expect("from_ms is an integer"), + w["to_ms"].as_i64().expect("to_ms is an integer"), + )); + break; + } + } + resolved.expect("the probe campaign document records GER40's full window") + }; + + // The last ~30 days of the archive: far shorter than the fixed 90+30-day roller, + // so the injected walk_forward cannot fit unless it scales down to the window. + const THIRTY_DAYS_MS: i64 = 30 * 86_400_000; + let sub_from = full_window.1 - THIRTY_DAYS_MS; + let sub_to = full_window.1; + assert!( + sub_from > full_window.0, + "the derived ~30-day sub-window must sit inside the archive span {full_window:?}" + ); + let sub_from_s = sub_from.to_string(); + let sub_to_s = sub_to.to_string(); + + let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) + .args([ + "mc", &fixture, "--real", "GER40", + "--axis", "sma_signal.fast.length=3,5", "--axis", "sma_signal.slow.length=12,20", + "--stop-length", "14", "--stop-k", "2.0", + "--block-len", "5", "--resamples", "100", "--seed", "42", + "--from", &sub_from_s, "--to", &sub_to_s, + ]) + .current_dir(cwd) + .output() + .expect("spawn aura mc"); + + let stderr = String::from_utf8_lossy(&out.stderr); + // Defensive: a genuine data refusal (never the late window-fit refusal this test + // pins) still means "no usable data on this host" — skip, don't fail. + if out.status.code() == Some(1) + && (stderr.contains("no local data") || stderr.contains("no recorded geometry")) + { + eprintln!("skip: no local GER40 data"); + return; + } + assert_eq!( + out.status.code(), Some(0), + "a short real window must fit the injected roller and run to completion, \ + not refuse late; stderr: {stderr}" + ); + let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); + assert!( + stdout.lines().any(|l| l.starts_with("{\"mc_r_bootstrap\":")), + "the mc_r_bootstrap grade line must be emitted for the fitted short window: {stdout}" + ); +} + /// Fork A: the stop is a single risk regime on the dissolved mc path — a multi-value /// `--stop-length`/`--stop-k` is a usage refusal (exit 2), not a swept axis. Mirrors /// `walkforward_dissolved_refuses_a_multi_value_stop`. NOT gated — the refusal is pure