test(stage1-r-sweep): RED — grid all four knobs from CSV flags
Executable-spec for the strategy-research enabling change: expose the
stage1-r sweep's hardcoded timescales as CLI grids so a slow
time-series-momentum edge can be screened across timeframes.
- sweep_strategy_stage1_r_grids_all_four_knobs_from_csv_flags (RED):
`aura sweep --strategy stage1-r --fast 240 --slow 960
--stop-length 240 --stop-k 2.0` must yield a 1-member family whose
member params are fast=240, slow=960, stop_length=240, stop_k=2.0.
Fails today: the four flags are rejected (exit 2), gridding absent.
- sweep_strategy_stage1_r_no_flags_keeps_the_default_grid (guard,
green today): a no-flags sweep still yields the {2,3}×{6,12} 4-member
family with the stop pinned at 3 / 2.0, so existing goldens stay
byte-green once the feature lands.
GREEN side follows via implement mini-mode.
refs #137
This commit is contained in:
@@ -1826,3 +1826,131 @@ fn sweep_strategy_stage1_r_trace_persists_member_dirs_with_r_equity() {
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property (#137 — headline): `aura sweep --strategy stage1-r` accepts the four
|
||||
/// optional comma-separated grid flags `--fast`, `--slow`, `--stop-length`,
|
||||
/// `--stop-k`, which become the sweep's grid axes over the four stage1_r_graph
|
||||
/// knobs (sma fast length, sma slow length, vol-stop length, vol-stop k). The
|
||||
/// family is the cartesian product of the supplied lists; a single-value list per
|
||||
/// axis is a 1-member family. This is the enabling change that lets the stop
|
||||
/// timescale track a *slow* signal (the pinned 3-bar = 3-minute vol-stop is far too
|
||||
/// fast for a 240/960-bar momentum cross) — so a slow time-series-momentum edge can
|
||||
/// be screened across timeframes, stop included.
|
||||
///
|
||||
/// Mirrors `sweep_strategy_stage1_r_ranks_a_family_by_sqn`: drive the built binary,
|
||||
/// read each stdout member line (the assigned `family_id` + the nested RunReport),
|
||||
/// and inspect the manifest `params` array — whose on-disk shape is
|
||||
/// `["fast.length",{"I64":N}]` / `["stop_k",{"F64":K}]` (verified against the live
|
||||
/// CLI output). The single member must carry exactly fast=240, slow=960,
|
||||
/// stop_length=240, stop_k=2.0. Today every one of these four flags is an unknown
|
||||
/// token rejected with exit 2 (the grid only honours the hardcoded fast×slow), so
|
||||
/// this is RED for the right reason: the flags + gridding are absent.
|
||||
#[test]
|
||||
fn sweep_strategy_stage1_r_grids_all_four_knobs_from_csv_flags() {
|
||||
let cwd = temp_cwd("sweep-stage1-r-grid-flags");
|
||||
let out = Command::new(BIN)
|
||||
.args([
|
||||
"sweep",
|
||||
"--strategy",
|
||||
"stage1-r",
|
||||
"--fast",
|
||||
"240",
|
||||
"--slow",
|
||||
"960",
|
||||
"--stop-length",
|
||||
"240",
|
||||
"--stop-k",
|
||||
"2.0",
|
||||
"--name",
|
||||
"g1",
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura sweep --strategy stage1-r with grid flags");
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"gridded stage1-r sweep must succeed (exit 0): {:?}; stderr: {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let stdout = String::from_utf8(out.stdout).expect("utf-8");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
// a one-value-per-axis grid is a 1-member family (cartesian product of singletons).
|
||||
assert_eq!(
|
||||
lines.len(),
|
||||
1,
|
||||
"single-value lists on all four axes = a 1-member family: {stdout:?}"
|
||||
);
|
||||
let member = lines[0];
|
||||
// the member is a real run (carries an R block), not a "0 ran" no-op.
|
||||
assert!(member.contains("\"r\":{"), "member must carry an r block: {member}");
|
||||
// the single member's four griddable knobs take the supplied values — the
|
||||
// on-disk manifest-params shape (verified against live CLI output).
|
||||
for needle in [
|
||||
"[\"fast.length\",{\"I64\":240}]",
|
||||
"[\"slow.length\",{\"I64\":960}]",
|
||||
"[\"stop_length\",{\"I64\":240}]",
|
||||
"[\"stop_k\",{\"F64\":2.0}]",
|
||||
] {
|
||||
assert!(
|
||||
member.contains(needle),
|
||||
"member must grid {needle} from the CSV flag: {member}"
|
||||
);
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property (#137 — default preservation, the hard constraint): when the grid flags
|
||||
/// are ABSENT, `aura sweep --strategy stage1-r` must produce *exactly today's*
|
||||
/// family — the 2×2 fast×slow grid {2,3}×{6,12} (4 members) with the stop pinned at
|
||||
/// stop_length=3, stop_k=2.0. This guards the byte-greenness of every existing
|
||||
/// golden/sweep test once the four flags land: a no-flags invocation falls back to
|
||||
/// the historical defaults, not to an empty/changed grid. It pins the contract that
|
||||
/// the flag wiring must not move the no-flags baseline.
|
||||
#[test]
|
||||
fn sweep_strategy_stage1_r_no_flags_keeps_the_default_grid() {
|
||||
let cwd = temp_cwd("sweep-stage1-r-default-grid");
|
||||
let out = Command::new(BIN)
|
||||
.args(["sweep", "--strategy", "stage1-r"])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.expect("spawn aura sweep --strategy stage1-r");
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"no-flags stage1-r sweep exit: {:?}; stderr: {}",
|
||||
out.status,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let stdout = String::from_utf8(out.stdout).expect("utf-8");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines.len(), 4, "no-flags grid = the historical 2×2 = 4 members: {stdout:?}");
|
||||
// the four members are exactly the {2,3}×{6,12} fast×slow cartesian product,
|
||||
// each with the historical pinned stop (stop_length=3, stop_k=2.0).
|
||||
let mut fast_slow: Vec<(&str, &str)> = Vec::new();
|
||||
for m in &lines {
|
||||
assert!(
|
||||
m.contains("[\"stop_length\",{\"I64\":3}]") && m.contains("[\"stop_k\",{\"F64\":2.0}]"),
|
||||
"no-flags member must keep the historical pinned stop: {m}"
|
||||
);
|
||||
let fast = if m.contains("[\"fast.length\",{\"I64\":2}]") {
|
||||
"2"
|
||||
} else {
|
||||
assert!(m.contains("[\"fast.length\",{\"I64\":3}]"), "fast must be 2 or 3: {m}");
|
||||
"3"
|
||||
};
|
||||
let slow = if m.contains("[\"slow.length\",{\"I64\":6}]") {
|
||||
"6"
|
||||
} else {
|
||||
assert!(m.contains("[\"slow.length\",{\"I64\":12}]"), "slow must be 6 or 12: {m}");
|
||||
"12"
|
||||
};
|
||||
fast_slow.push((fast, slow));
|
||||
}
|
||||
fast_slow.sort_unstable();
|
||||
assert_eq!(
|
||||
fast_slow,
|
||||
vec![("2", "12"), ("2", "6"), ("3", "12"), ("3", "6")],
|
||||
"no-flags grid must be exactly {{2,3}}×{{6,12}}: {fast_slow:?}"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user