test(cli): RED — mc --real refuses a short window instead of fitting the roller (#239)
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
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user